Skip to content

fix: restore highlights for cross-node and cross-paragraph selections#30

Merged
kasunben merged 2 commits into
mainfrom
fix/anchor-cross-node
Jun 8, 2026
Merged

fix: restore highlights for cross-node and cross-paragraph selections#30
kasunben merged 2 commits into
mainfrom
fix/anchor-cross-node

Conversation

@kasunben

@kasunben kasunben commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Root cause: serializeRange only stored xpath (start container path); for any selection where startContainer ≠ endContainer, restoreRange applied endOffset to the wrong node — producing a null range or a truncated highlight on every page reload
  • Fix 1 — anchor shape: added endXpath: _getXPath(range.endContainer) to the serialized anchor; restoreRange now resolves start and end nodes independently with anchor.endXpath || anchor.xpath fallback for backward compat with existing IDB/server data
  • Fix 2 — multi-mark highlight: range.surroundContents() throws HierarchyRequestError for cross-element ranges; highlightRange now catches this and falls back to _highlightRangeMulti, which collects all text nodes via NodeIterator before any DOM mutation (prevents iterator invalidation), then wraps each in its own <mark>
  • _allMarksOf(mark): new helper returning mark._allMarks || [mark]; all downstream operations (unwrap, is-resolved class, click listeners, _threadId assignment) iterate the full mark group atomically
  • Bumped version to 0.5.14; rebuilt annotate.min.js

Type

  • Bug fix

Affected modes

All modes (offline, BroadcastChannel, server-sync, P2P) — anchor serialization and highlight rendering are shared across all sync modes.

Testing

  • Cross-paragraph selection → highlight covers both paragraphs; reload → both segments re-highlighted; card positioned at start of selection
  • Inline-element selection (e.g. text spanning <strong>) → multi-segment highlight; reload → fully restored
  • Single-node selection (same text node, start = end) → fast path unchanged; existing behaviour preserved
  • Legacy anchors (no endXpath in IDB/server) → restoreRange falls back to anchor.xpath for both nodes; no regressions
  • Resolve / un-resolve on multi-mark highlight → is-resolved class applied/removed on all marks atomically
  • Unwrap (delete) on multi-mark highlight → all mark segments removed cleanly

Breaking changes

None. The endXpath field is additive; restoreRange is fully backward compatible with anchors that lack it.

Checklist

  • Code change in assets/js/annotate.js
  • annotate.min.js rebuilt (npm run build)
  • Version bumped (0.5.130.5.14)
  • README.md updated (anchor data model, core checklist, Roadmap Shipped)
  • CLAUDE.md updated (anchor shape, Highlight Re-anchoring section, Phase M, Key Implementation Notes)

Closes #29

🤖 Generated with Claude Code

kasunben and others added 2 commits June 8, 2026 20:19
…closes #29)

- serializeRange: add endXpath field to anchor shape so the end container
  is serialised independently of the start container; restoreRange resolves
  both nodes separately with legacy fallback (endXpath || xpath) for
  backward compat with existing IDB/server data
- highlightRange: catch HierarchyRequestError from surroundContents and
  fall back to _highlightRangeMulti, which collects text nodes via
  NodeIterator before any DOM mutation, then wraps each in its own <mark>
- _allMarksOf(mark): helper returning mark._allMarks || [mark]; all
  downstream ops (unwrap, is-resolved class, click listeners, _threadId)
  iterate the full mark group atomically
- Bump version to 0.5.14; rebuild annotate.min.js
- Update README.md and CLAUDE.md with endXpath data model, Phase M, new
  manual test cases, and Key Implementation Notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kasunben kasunben marked this pull request as ready for review June 8, 2026 18:25
@kasunben kasunben merged commit 1f56cbd into main Jun 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Highlight lost and card position resets to top after annotating cross-paragraph or non-English text

1 participant