fix(ui): accept whitespace-only differences in restore verification, snap excluded selection starts, and merge folder-session chrome re-stamps (#1541)
* fix(ui): accept whitespace-only differences in restore verification A restore whose stored positions resolve onto exactly the right text was rejected whenever the annotation spanned two block elements. `originalText` is the browser's selection string, which carries a blank line between blocks; the painted highlight is the wrapper marks concatenated with nothing between them, because sibling blocks are rendered from a `.map()` with no whitespace text node. The verification collapsed whitespace to a single space instead of removing it, so "Alpha.\n\nBeta." never matched "Alpha.Beta." — the correct highlight was removed, the text search could not bridge the boundary either, and the annotation came back from a reload, a resubmit or a markdown edit with no highlight at all. Compare with `compactText`, the helper the same change already added for the quote repair's identical asymmetry. Content drift — the #1509 case the guard exists for — still differs once whitespace is gone, so the guard keeps its teeth. * fix(ui): snap a selection that starts inside excluded chrome web-highlighter never enters an `.annotation-exclude` subtree: its painter skips such an element before the "is this the start node" check, so a range that STARTS inside one never flips its in-selection flag and paints only the trailing text node. A drag from a GitHub alert's icon — where the visually hidden "Tip: " lives — through the alert body therefore highlighted the body alone, silently dropping the title, while the quote still carried the invisible word. A title holding any inline markup reproduced it within the title row itself, because the quote repair only recognizes the shape where the whole non-excluded selection is one trailing run. Move the range's start off the excluded subtree, onto the first annotatable text position the range covers, before anything paints or quotes it: by node identity, never by matching text. Applied on all three paths that reach the highlighter — the pointer-end selection (on the capture phase, since web-highlighter's own handler reads the live selection), `highlightRange` for pinpoint clicks and vim visual selections, and the touch selectionchange bridge. * fix(ui): repair excluded quotes by node position, not by text search The quote repair removed the FIRST occurrence of each excluded element's text anywhere in the selection string. With body prose that legitimately reads "Tip: ", a selection running through a tip alert's title row had the reviewer's own words removed instead of the hidden ones; the result then failed the repair's own content check and reverted, leaving the invisible word in the quote after all — the exact bug the repair exists to fix. Locate the excluded runs positionally instead, from the range's own text nodes captured before painting (painting splits and re-parents them, so the range itself is stale by the time the highlight is created). The runs are matched in the quote's whitespace-free coordinate space, where the selection string and the concatenated runs agree character for character, so the right occurrence is removed and real prose survives. The painted highlight stays the last word on content: a shape this cannot read leaves the quote exactly as before. * fix(annotate): route the chrome re-stamp through the same merge Annotation activity re-stamps the HTML chrome record so the preference does not expire for a reviewer who is still annotating. That write went straight to saveHtmlChromeState with the live sidebar/panel/tools values, bypassing mergeHtmlChromeState, which the save effect uses. It was unreachable in folder sessions while the restore was suppressed there; once the restore was enabled, one comment in a folder session — whose file browser forces the sidebar open — rewrote both halves, so the reviewer's next `annotate page.html` opened with the sidebar and the annotations drawer forced open. Both call sites now share one saveChrome() helper, which is the only writer of the record, so a third write cannot reintroduce the bypass.
M
Michael Ramos committed
ec7a3eb8e8229d057e056ff5e8c8e68b282ccf6a
Parent: 11cbc94
Committed by GitHub <noreply@github.com>
on 9/15/2026, 2:32:13 PM