feat(diff): block-format-change family — native w:pPrChange/w:tcPrChange/w:trPrChange/w:tblPrChange/w:tblGridChange/w:sectPrChange#252
Merged
Conversation
…Pr/trPr/tblPr/tblGrid/tcPr/sectPr untracked today)
Detect paragraph-and-above property deltas at block pairing (ParaKey folded into IrModeledFormat.BlockSignature under a new internal TrackBlockFormatChanges flag; direct numId/ilvl now modeled in IrParaFormat). Render native w:pPrChange (and w:pPr/w:rPr/w:rPrChange for a changed paragraph mark) in the FormatOnly, Modify, and move-destination paths, carrying the OLD (left) pPr so accept ≡ right and reject ≡ left hold at the pPr level. Surface a Paragraph-scoped DocxDiffFormatChange (new DocxDiffFormatChangeScope; WmlComparerCompatible excludes non-Run scopes by construction). Consolidate forced off (v1 ceiling, pinned). Also fixes a latent consume-side bug: RejectRevisions dropped an inline w:sectPr when rebuilding pPr from a w:pPrChange (section break vanished on reject).
…markup Phase 2 (table family): split the lumped table-shell digest into per-element TblPrDigest/TblGridDigest (IrTable) + TrPrDigest (IrRow), flattened so empty≡absent. Render native w:tblPrChange/w:tblGridChange/w:trPrChange (FormatOnly + Modified table paths) and w:tcPrChange (Modified cells) carrying the OLD shell; reject restores shell bytes. Report Table/TableRow/TableCell-scope FormatChanged revisions (digest-grade). This makes the #250-noted cell-shell edits tracked, not just visible. Phase 3 (section): stamp native w:sectPrChange on the trailing body sectPr when its PROPERTIES differ (page size/margins/orientation/type/columns), applying the right properties and preserving the left in the marker; references (header/footer machinery) untouched. Section-scope FormatChanged revision (modeled fields). Fixes a latent RevisionProcessor bug: rejecting a sectPrChange dropped header/footer references (CT_SectPrBase has none) — now the references are preserved across reject. AssertRoundTrip strengthened with per-table shell digests + a reference-normalized trailing-sectPr property digest, so the whole renderer battery verifies shell/section round-trip. Consolidate stays forced-off (v1 ceiling). Golden IR snapshots regenerated (digest layout only; ContentHash unchanged).
… round-trip, determinism, LibreOffice load backstop
…Ops/npm/python), ir_diff_engine + format_change_detection + ooxml_corner_cases + CLAUDE + CHANGELOG
…PrEx schema order, revision/markup shell-digest consistency - Gate the table-shell REVISION emitters on TrackBlockFormatChanges so the Consolidate v1 ceiling holds on GetConsolidatedRevisions too (a table-shell-only reviewer edit was reporting a Table/TableRow/TableCell FormatChanged the composite markup never backed). - InsertShellInSchemaOrder: a freshly-created w:trPr now lands AFTER an existing w:tblPrEx (CT_Row orders tblPrEx before trPr) — the AddFirst placement produced schema-invalid rows. - Add flattened per-element digests IrRow.TrPrShellDigest (w:trPr children only, no tblPrEx) and IrCell.TcPrShellDigest (empty≡absent), and drive the revision surface + round-trip harness from them, so GetRevisions and Compare's markup agree: a w:tblPrEx-only or empty-vs-absent-shell change is untracked in BOTH (was over-reported by the revision only). TrPrDigest (full row shell) still drives the fingerprint, so tblPrEx changes stay visible. Reviewer: pr-review-toolkit:code-reviewer (findings 1/2/3).
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
Closes the last "Word compares Formatting, we don't" scope gap in
DocxDiff(the IR diff engine). Word's Compare tracks paragraph-and-above property changes; DocxDiff tracked only run-level format changes (w:rPrChange). A change to a paragraph's, table's, cell's, row's, or section's properties (with identical text) was either invisible (silently classified unchanged) or applied untracked — the right-side properties won with no revision, sorejectdid not restore the left.Now DocxDiff produces the native markup Word itself renders, gated by the existing
FormatComparisonpolicy, with the round-trip contract (accept ≡ right,reject ≡ left) extended to the property-byte level:w:pPrChange(+w:pPr/w:rPr/w:rPrChangefor a changed paragraph mark)w:tcPrChangew:trPrChangew:tblPrChange+w:tblGridChangew:sectPrChangeThis makes the cell-shell (
w:tcPr) edits that became visible in #250 actually tracked — reject now restores the shell bytes, not just the text.How
w:rPrChangearchitecture):IrModeledFormat.BlockSignaturegains a paragraph key (directw:numPrnumId/ilvl now modeled); the lumped table-shell digest is split into per-elementTblPrDigest/TblGridDigest/TrPrDigest. An internalIrDiffSettings.TrackBlockFormatChanges(default on) governs it.*PrChangecarries the OLD (left) properties (CT_*Base inners).DocxDiffRevision.FormatChange.Scope— newDocxDiffFormatChangeScope(Rundefault +Paragraph/TableCell/TableRow/Table/Section).WmlComparerCompatibleexcludes non-Runscopes by construction (the oracle produces none — the 179-count parity scoreboard stays meaningful). Additivescopeon the revisions wire (Ops/npm/python); edit-script JSON unchanged.RevisionProcessorno longer drops the section's header/footer references (rejectingw:sectPrChange) or an inlinew:sectPr(rejectingw:pPrChange) — those live outside the tracked change (CT_SectPrBase / CT_PPrBase). Seedocs/ooxml_corner_cases.md.v1 ceilings (documented + pinned)
Consolidatedoes not track block-format changes —IrCompositeMerger(and the composite markup/revision renderers) forceTrackBlockFormatChangesoff; a reviewer's pPr/shell/section-only edit is ignored (explicit, nothing silently dropped); text+pPr edits keep routing to the conflict path.w:tblPrEx(row-level exceptions) and mid-documentw:sectPrare untracked-but-visible; split/merge/note/header-footer paragraphs don't emitw:pPrChange.Verification
IrParityScoreboardTests(179),IrMarkupParityScoreboardTests(39),ConsolidateParityScoreboardTests(84),IrVsWmlComparerTestsdifferential.BlockFormatChangeTests(per-member detection/markup/revision/round-trip, the Consolidate pins, and the 3 code-review-finding regressions),BlockFormatChangeRealDocTests(a real corpus doc mutated across all five family members → all markers present, schema-valid, round-tripping, deterministic, with a headless-LibreOffice load backstop).IrMarkupRendererTestsround-trip battery strengthened with per-table shell digests + a reference-normalized trailing-sectPr property digest.code-reviewerpass surfaced 3 revision-surface findings (Consolidate revision leak, a trPr-vs-tblPrEx schema-order edge, revision/markup shell-digest consistency) — all fixed with regression tests in the final commit.Follow-ups (out of scope)
Consolidateblock-format merge;w:tblPrExChange; mid-documentw:sectPrChange; extendingw:pPrChangeto split/merge/note/header-footer scopes; WASM/npm/python surfacing ofTrackBlockFormatChanges(internal-only in v1).