Spreadsheet row identity, header rows under a title, and a Mermaid pair that opens as a picture - #51
Merged
Conversation
… row The Spreadsheet track's two "now" items, plus a cross-panel row hover. Row identity — matchRowsByKey.js pairs rows by a composite key across the whole sheet, so a re-sorted export reads as the figures that moved rather than as a rewrite. Duplicate keys pair in order of occurrence and are COUNTED, never hidden. Chosen per sheet in the new Match-rows popover; Auto keeps today's whole-row LCS exactly. Header row — headerPairing scans the first ten rows for a usable header instead of reading rows[0] and dropping silently to positional pairing, which is the failure alignColumns exists to prevent. The band names the row it used when it was not the first. Row hover — the hovered row is marked in BOTH grids; they are two tables, so :hover in one cannot reach the row aligned with it in the other. An outline, not a veil: every row and cell state here is already a background, and a wider border would move the rows the virtualization measures. 2px inset, because 1px read as a different border on beacon and contrast, whose hard --border is a contract. Three bugs found and fixed on the way, two by tests written first: keyColumnsOf let an out-of-range column through (undefined !== null); the Match-rows panel was unclickable because .content carries isolation: isolate and the body-teleported backdrop outranked the whole diff subtree; toolbar-view-menu.spec.mjs still selected .count after the chip moved to ui.css as .btn-count for its second consumer. useSpreadsheetDiff hit its 73-line ratchet and split into the two choices it carried — useToleranceChoice and useKeyColumns. Entry retightened 73 to 69, never raised. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every load path already defaulted to the diagram view — the file dialog, a drop, `diffbro compare`, a relaunched session — because receive() and comparePasted() both ask shouldOpenSemantic. restore() did not: it replayed `payload.semanticView === true`, so a snapshot that recorded NOTHING (saved before the diagram view existed, or carried by a bundle that never wrote the field) came back as text. The view is now a tri-state on the way in: an explicitly recorded view is kept, either way, and only an absent one is taken from the files. That needed session.js to stop coercing an unrecorded view to false, or the distinction never survived being read back. restoredSemanticView lives beside shouldOpenSemantic in viewChrome.js: inlining it put restore() over the complexity cap and diffStore over its line ratchet, and the rule belongs with the one it defers to. tabsStore._withCurrentView no longer forces semanticView at all. It must not INHERIT it either — the toggle belongs to the comparison, not to the reader's last tab (tabsStore.test.js already guarded that, and caught the first attempt) — so an unrecorded view is left for restore() to resolve from the files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Closes the Spreadsheet track's two now items on
docs/roadmap.md, adds the cross-panel row hover, and fixes a Mermaid pair that came back as text.Row identity — key columns
utils/matchRowsByKey.jspairs rows by a composite key across the whole sheet instead of by whole-row LCS, so the same export sorted differently reads as the figures that moved rather than a rewrite. Duplicate keys pair in order of occurrence and are counted, never hidden — a cap that hides is worse than a cap. Chosen per sheet in the new Match-rows popover; Auto keeps today's LCS exactly, because changing the default matcher for every existing diff has no failing case behind it.Display order follows the RIGHT file, with a removed row emitted just before the first surviving left row that followed it, so it keeps its context.
Header row offset
headerPairingscans the first ten rows for a usable header instead of readingrows[0]and dropping silently to positional pairing — the exact failurealignColumnsexists to prevent. Each side scans independently, since a title row added on one side only is the common case, and the status band names the row it used when it was not the first.Row hover across both grids
The hovered row is marked in both panels — they are two
<table>s, so:hoverin one cannot reach the row aligned with it in the other. An outline, not a veil: every row and cell state here is already abackground, and a wider border would move the rows the virtualization measures. 2px inset, because 1px read as a different border onbeaconandcontrast, whose hard--borderis a contract.A restored Mermaid pair opens as a picture
Every load path already defaulted to the diagram — file dialog, drop,
diffbro compare, relaunch — all verified against a real launch.restore()was the gap: it replayedpayload.semanticView === true, so a snapshot that recorded nothing came back as text. The view is now a tri-state on the way in; an explicitly recorded view is kept either way.Bugs found and fixed
keyColumnsOfaccepted an out-of-range column (undefined !== nullis true) and threw..contentcarriesisolation: isolate, so the body-teleported backdrop outranked the whole diff subtree.toolbar-view-menu.spec.mjsstill selected.countafter the chip moved toui.cssas.btn-count.restore()attempt made a new tab inherit the previous tab's toggle;tabsStore.test.jscaught it.Verification
npm run checkgreen: 3088 tests, coverage 95.34 / 88.47 / 95.76 / 96.33 over the 93 / 86 / 92 / 95 floors, theme depth ok on 20 themes, i18n and raw-text clean.useSpreadsheetDiffhit its 73-line ratchet and split intouseToleranceChoice+useKeyColumns; entry retightened 73 → 69, never raised.Spec:
specs/2026-08-09-spreadsheet-row-identity/plan.md.🤖 Generated with Claude Code