Skip to content

fix(client): anchor annotation selector so highlighting works on nested inline elements#3

Closed
auspham wants to merge 1 commit into
mainfrom
austinpham/annotation-selector-anchor
Closed

fix(client): anchor annotation selector so highlighting works on nested inline elements#3
auspham wants to merge 1 commit into
mainfrom
austinpham/annotation-selector-anchor

Conversation

@auspham

@auspham auspham commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

Some lines in a review could not be highlighted or commented on at all: selecting them just flashed and cleared, with no annotation menu. This happened for any id-less nested inline element (a <strong> inside a list item, a link, etc.) when an earlier part of the document shared the same tag/nth-of-type skeleton.

The cause was in cssPath (client/util.js). It built an unrooted selector for a selection's anchor element, for example div:nth-of-type(1) > ul > li:nth-of-type(2) > strong. document.querySelector matches an unrooted path against any subtree at any depth, so when an earlier subtree matched the same skeleton (e.g. a <strong> nested in a card grid) the path re-resolved to that decoy element instead of the intended one. resolveRange's text guard then correctly detected the text mismatch and returned null, pendingRects came back empty, and openSelMenu called clearPending - which closed the menu and wiped the selection. The net effect was a silent "can't highlight this line".

The fix roots the selector at an ancestor id when there is one, otherwise at <body>, and climbs the full ancestor chain instead of capping at six segments, so the path always resolves absolutely to the correct element. Lines that used to resolve correctly are unaffected; only the previously-wrong resolutions are corrected.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor or tooling
  • Other:

How was this tested?

Added a browser regression test (tests/client/selector-anchor.test.mjs + fixtures/selector-collision.html) whose fixture reproduces the colliding skeleton: a decoy <strong> nested in a card grid earlier in document order, then the real target <strong> in a list. Selecting the target asserts the annotation menu opens. It fails before the fix (menu display:none) and passes after (flex). Also verified end-to-end against the real artifact that surfaced this, where the target line now opens the menu.

  • python -m pytest tests/python (45 passed)
  • ruff check skills/htmlit tests/python (clean)
  • npm test (browser client - 20 passed, including the new regression test)

Checklist

  • I added or updated tests for this change (a bug fix has a test that fails before the fix).
  • I did not add any runtime dependency to the skill (standard library only).
  • I did not commit vendored assets (Mermaid, highlight.js, Idiomorph).
  • Commits follow Conventional Commits and are signed off (git commit -s).

…ed inline elements

cssPath emitted an unrooted selector (e.g. "div:nth-of-type(1) > ul >
li:nth-of-type(2) > strong"). document.querySelector matches such a path
against any subtree at any depth, so when an earlier element shared the same
tag/nth-of-type skeleton (e.g. a <strong> nested in a card grid) it resolved to
that decoy instead of the intended element.

resolveRange's text guard then failed, pendingRects returned empty, and
openSelMenu called clearPending - closing the annotation menu and wiping the
selection. The result was that some lines (a <strong> inside a list, a link, any
id-less nested inline element) silently could not be highlighted or commented on.

Root the path at an ancestor id, otherwise <body>, and climb the full ancestor
chain instead of capping at six segments, so it always resolves absolutely to
the correct element.

Adds a browser regression test whose fixture reproduces the colliding skeleton;
it fails (menu display:none) before the fix and passes (flex) after.

Signed-off-by: Austin (Ngoc Thang) Pham <austinpham@microsoft.com>
@auspham

auspham commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

Folded into #2 to keep everything in a single PR (the selector-anchor commit was cherry-picked onto that branch). Closing this one.

@auspham auspham closed this Jul 8, 2026
@auspham auspham deleted the austinpham/annotation-selector-anchor branch July 8, 2026 11:14
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.

1 participant