Decide the token-stream arrangement mode for regions without block correspondence - #698
Merged
Conversation
…ions Issue #694 asks for the architecture of DocxDiff's second arrangement mode — the one that renders a region as a single word-level token stream instead of block-wise — to be settled before anyone implements it. Reading the engine first changes what the decision is. IrCrossParagraphSegmenter .SegmentRegion is already the GENERAL region form: members are a region's paragraphs per side, pairs lists the aligner's word-matched pairs, and any member not covered by a pair is one-sided at any position — zero pairs included. IrEditScriptBuilder.TryBuildStoryFinalMixedRegionOp already admits interior zero-pair regions. The mode is built and shipping; what is narrow is the gate, not the mechanism. So the note records the decisions around the algorithm and cites the segmenter's class remarks for the algorithm itself, rather than restating them where they would go stale. Placement is settled as a per-region second mode over a region the block aligner delimits, keeping every guarantee the block path already carries. Reversibility is written out rather than asserted, from the three construction invariants the walk asserts (single-member cells, contiguous slices, a total disjoint cover in stream order) — and the argument is structural, so widening the gate cannot break accept ≡ right or reject ≡ left, including paragraph order. Formatting provenance, structure boundaries, cost and moves each record the contract the code already holds, and say which a wider gate stresses first. One point is left open, deliberately: the selection criterion. The two literals that stand in for it today — the eight-member region cap and the crossUnitMatches >= 2 || constructPairs > 0 ship gate — are exactly the tuned thresholds the issue rules out, and every replacement that can be written from here fails one of its own constraints: a coverage fraction is the forbidden threshold (and the segmenter has already been burned twice by density and matched-char floors, whose classes overlap on coverage), "stream every zero-pair region" contradicts the only evidence in hand, and "emit the whole-story LCS" assumes a premise the decode never covered. The criterion has to be decoded from reference output for LARGE zero-pair regions, not designed, so the note names that measurement as the prerequisite and files it as the first child issue. The test plan names the three guards a widening must pass — DocxDiffFuzzRoundTrip Tests on the wide sweep, DocxDiffCorpusBaselineTests, and DocxDiffGapArrangement Tests — and says what a moved pin in each would mean. Closes #694 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxehJkp2547AyP8LSd6uFS
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SxehJkp2547AyP8LSd6uFS
JSv4
pushed a commit
that referenced
this pull request
Sep 4, 2026
The release cut is the reason this merge needed hands. main emptied [Unreleased] into a 12.0.0 section, and git's auto-merge put this demo's entry back at the position that text now occupies -- inside the released section, where it would have claimed the demo shipped in a version it is not in. Moved it back under [Unreleased], and dropped the Office Math entry that came with it, because that one did ship in 12.0.0 and would otherwise appear twice. Re-pinned docs/demo/redline.html from 11.0.0 to 12.0.0. The release re-pins every sibling demo page but not this one, since this one is not on main yet; what is new is that #654's engine-pin guard globs docs/demo/*.html and requires them all to name one version, so a stale pin here is now a failing check rather than a quiet inconsistency. Confirmed jsDelivr actually serves 12.0.0 before moving it. The README conflict was the same event seen from the other side: seven pages (this branch adds one) at the new pin. #697 fixes the comparison timeout that made the last CI run red. That failure was #693's, bisected and filed as #695; the fix warms the WASM engine before the first real comparison rather than touching the aligner, so my guess at the cause in that issue was wrong even though the bisect was right. Verified here: the test that timed out at 60 seconds now passes. Build and pretest clean, 61 node checks, 14 browser assertions, and the engine-pin guard passing on all seven pages. Not re-measured: #698 changes token-stream arrangement for regions with no block correspondence, which the demo's 3 KB document does not exercise.
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.
Settles #694: the architecture of
DocxDiff's second arrangement mode — the one that renders aregion as a single word-level token stream instead of block-wise — as a design note under
docs/architecture/.Reading the engine first changed what the decision is
#694 describes the token-stream mode as a set of "narrowly gated constructs" and asks for an
architecture to be decided before anyone implements one. Most of it is already decided and shipping:
IrCrossParagraphSegmenter.SegmentRegionis already the general region form. Members are aregion's paragraphs in document order per side,
pairslists the aligner's word-matched pairs, andany member not covered by a pair is one-sided at any position — leading, interior or trailing.
Zero pairs is a legal input.
IrEditScriptBuilder.TryBuildStoryFinalMixedRegionOpalready admits interior zero-pair regions.What is narrow is the gate, not the mechanism. So the note records the decisions around the
algorithm and cites the segmenter's class remarks for the algorithm itself, rather than restating
them in worse prose where they would go stale.
What it settles
replacement for block alignment. A wholesale replacement would have to re-derive everything the
block path gets right, and the evidence base for the block path is not transferable to it.
segmenter's walk already asserts (single-member cells, contiguous slices, a total disjoint cover in
stream order). The argument is structural, which is the load-bearing part: widening the gate
cannot break
accept ≡ right/reject ≡ left, including paragraph order, because the propertydoes not depend on which regions are selected or on how good the matching is.
already holds (per-cell re-diff through
IrTokenDiffer;IsStreamable+HasStructuralCarrierexclusions with whole-region maximality;
LcsCellCap; the move-source decline) and says which awider gate stresses first. Cost is the obvious one.
What it deliberately leaves open
The selection criterion, and the note says why that is the honest answer rather than a gap.
Two literals stand in for it today, and they are exactly the tuned thresholds #694's point 2 rules
out: the
> 8member cap in the builder, andcrossUnitMatches >= 2 || constructPairs > 0in thesegmenter. Every replacement that can be written from here fails one of the issue's own constraints:
twice this way; its own pass-1 remark records that "the retain-in-place and yield-to-cross classes
overlap on coverage, so no threshold separates them";
contradicts the only evidence in hand — the code comment carrying the size cap says the stream
constructs were all decoded from small regions while the replace-gap grammar was validated on the
large ones;
scale, which is DocxDiff: decide the token-stream arrangement mode for regions with no block correspondence #694's premise and is unverified exactly where it matters.
So the criterion has to be decoded, not designed. The note names the measurement that would
settle it — reference compare output for large zero-pair regions (template vs filled-in copy, memo vs
the contract that replaced it, two drafts rewritten rather than edited), scored on whether surviving
fragments stay in place or the region replaces whole — and files it as the first child issue. A
decision about how to decide is what an architecture note owes when the evidence is not in yet.
Test plan
Named in the note, with what a moved pin in each would mean:
DocxDiffFuzzRoundTripTestson the widesweep (
DOCXODUS_FUZZ_SEEDS=2000, since the order-sensitive seeds from #288 are rare),DocxDiffCorpusBaselineTests, andDocxDiffGapArrangementTests.Scope
Documentation only — no code, no behaviour change, so no CHANGELOG entry. Cross-referenced from
CLAUDE.md's diff/comparison list and fromir_diff_engine.md, whose stage list did not mentionIrCrossParagraphSegmenterat all.Child issues are filed and linked below; #694 closes on the decision, not on the children.
Closes #694
🤖 Generated with Claude Code
https://claude.ai/code/session_01SxehJkp2547AyP8LSd6uFS