feat(diff): block-format family follow-ups A — tblPrExChange, mid-doc sectPrChange, public opt-out, note/hdr-ftr pPrChange#253
Merged
Conversation
…tive wire; composite still forces off)
…lPrExChange attribution
…ns) — tracked + round-trips
…onFormat, folded into the paragraph fingerprint
… property changes) Fold the inline section format into IrModeledFormat.BlockSignature (so a mid-doc sectPr-only change classifies FormatOnly, not Unchanged, under ModeledOnly), stamp w:sectPrChange inside the emitted pPr/sectPr via an explicit-old-source ApplySectPrChange overload (snapshots old+right props before mutating, since output may alias either), and emit a per-paragraph Section-scope revision. Reject preserves refs (already generic).
…d dispatch) + the split/merge pPrChange decline (D1)
…p the closed v1 ceilings in ir_diff_engine/CLAUDE/CHANGELOG
…ema-order test, fresh-tblPrEx-order test, corrected harness comment, ModeledOnly inline-sectPr blind-spot doc
…s reviewers with consensus/conflict (#254) * feat(ir): IrParagraph.PPrDigest — pPrChange-comparable paragraph-shell digest for composite attribution * feat(diff): Consolidate paragraph-property merge (B1) — pPrChange across reviewers with consensus/conflict Split the internal TrackBlockFormatChanges into a paragraph slice (TrackParagraphFormatChanges, defaults equal so two-way is byte-identical) and the table/section slice. The composite turns the paragraph slice ON while keeping table/section OFF (B2 ceiling). A reviewer's pPr-only edit now surfaces as a FormatOnly op; MergeOneBaseBlock routes multi-reviewer pPr edits through a new ComposePPr (mirroring ComposeCellShell: 0→base, all-agree→consensus, >=2 distinct→recorded conflict resolved by policy) BEFORE the text-based consensus — so a competing pPr is never silently dropped. The composite renderer stamps w:pPrChange authored to the winning reviewer via the existing single-source path; reject ≡ base, accept ≡ policy-winner hold at the pPr-byte level (proven by a multi-reviewer byte-level round-trip stress test). Composite fuzzer (3/4/5-way) + 84-case parity scoreboard unaffected. Flips the former Consolidate ceiling pin. * docs(diff): Consolidate pPr merge (B1) — flip the ceiling in ir_diff_engine/CLAUDE/CHANGELOG (table-shell+section = B2) * fix(diff): Consolidate pPr merge — compose by full BlockSignature, not pPr digest (no silent run-format drop) Adversarial review found a silent-drop regression: IsParagraphPPrOnlyEdit checked that pPr CHANGED, not that the edit was pPr-ONLY, so a FormatOnly op carrying BOTH a pPr change and a run/mark-rPr change passed. When two reviewers agreed on pPr but disagreed on run format, ComposePPr saw matching pPr digests, declared consensus, and dropped the second reviewer's run edit with no conflict. Now IsParagraphFormatEdit + ComposeParagraphFormat compose by the FULL boundary-normalized BlockSignature (run formats + pPr), so any format disagreement records a conflict — a competitor is never silently dropped. Also links the conflict id onto the winning op.
This was referenced Jul 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Follow-up A to the block-format-change family (#252): finishes the two-way surface. Four of the five deferred items land here; the fifth (Consolidate block-format merge) is a separate, larger PR (sub-project B).
Grounded reading reshaped the scope: two items turned out cheaper or better-declined than expected.
w:tblPrExChange(row-level table property exceptions)w:sectPrChange(inline in-pPrsectPr)w:pPrChangew:pPrChangeTrackBlockFormatChangespublic opt-outDocxDiffSettings(+ npm/python wire)How
CompareHeadersFootersripple exactly:DocxDiffSettings.TrackBlockFormatChanges(defaulttrue) →ToIrDiffSettings→DocxDiffOpsJSON keytrackBlockFormatChanges→ npm/python types. WASM/index/dispatcher/session are passthrough. Consolidate still forces it off internally regardless of the value.w:tblPrExChange. NewIrRow.TrPrExDigest(a flattenedtblPrEx-only projection, parallel toTrPrShellDigest), aTblPrExInnerExclude, oneApplyShellChangecall, and aTableRow-scope revision with the distinct changed-name"tblPrEx".RevisionProcessoralready accepted/rejected it. Round-trips (accept ≡ right, reject ≡ left).w:sectPrChange. The reader models the inline sectPr asIrParagraph.InlineSectionFormat(via aMapSectionFormatrefactor shared withBuildSectionBreak), folded into the paragraphFormatFingerprintandIrModeledFormat.BlockSignature(a newSectionKey) so a sectPr-only change classifies FormatOnly underModeledOnlyinstead of Unchanged. The emit stampsw:sectPrChangeinside the paragraph's ownw:pPr/w:sectPr(not thepPrChangeinner — CT_PPrBase excludes sectPr) via a refactored explicit-old-sourceApplySectPrChangethat snapshots both old and right props before mutating (the output element aliases one or the other). A per-paragraph Section revision. One-sided add/remove is structural (untracked, documented).ApplyBlockFormatChangesis gated only by the sharedstate.Settings.TrackBlockFormatChanges(no per-scope gate), and note/story ops route through the sameRenderBlockOpdispatch — so a changed footnote/header/footer paragraph already emitsw:pPrChange. Pinned by tests. Split/merge is declined (D1): a split's members are brand-new paragraphs already tracked by the inserted pilcrow mark, a merge's non-final members are deleted — a pPr "change" isn't well-defined and would fight the reject-fuse.Verification
tscclean; pythonto_wireverified.IrParityScoreboardTests(179),IrMarkupParityScoreboardTests(39),ConsolidateParityScoreboardTests(84),IrVsWmlComparerTests. New revisions are Fine-only non-Run scopes (excluded from compatible mode by construction → parity counts unaffected).BlockFormatChangeTests(tblPrEx, mid-doc sectPr, note/header pPrChange, split decline, both-fire schema order, fresh-tblPrEx order),BlockFormatChangeRealDocTests(a real corpus doc mutated across the full table + section family — all markers present, schema-valid, round-tripping), the strengthenedIrMarkupRendererTestsround-trip battery (tprex:/psec:signatures), and additive wire tests (.NET/npm/python).code-reviewerpass returned SOUND — no correctness bugs; its three LOW-severity notes (a stale comment, two coverage gaps, one blind-spot doc line) are all addressed in the final commit.Follow-up (out of scope)
Sub-project B — Consolidate block-format merge: lift the composite ceiling and merge reviewers' pPr/table-shell/section edits with per-reviewer attribution + a competing-property conflict class, strengthening the byte-level round-trip verifiers. Its own spec/plan/PR.