Skip to content

Hit-test the most specific span, not the first one - #93

Merged
Wavesonics merged 2 commits into
mainfrom
fix/span-hit-test-innermost
Aug 4, 2026
Merged

Hit-test the most specific span, not the first one#93
Wavesonics merged 2 commits into
mainfrom
fix/span-hit-test-innermost

Conversation

@Wavesonics

Copy link
Copy Markdown
Collaborator

Right clicking a spell-check squiggle inside a heading, list item, blockquote or code fence opened the plain Paste/Select All menu instead of the suggestions menu. Typing a character anywhere in the document made the same click start working.

Cause

findSpanAtPosition returned the first span in the line's list that contained the position, and that list is in span-set iteration order rather than any hit-testing priority.

Headings, list items, blockquotes and code fences each carry a whole-line RichSpan. A spell-check squiggle is a word-sized span sitting inside it. On a freshly imported document the block markers are added first, so they came first in the set and won the hit test; SpellCheckingTextEditor then saw a span whose style isn't SpellCheckStyle and fell back to the standard menu.

The order also flips: any text edit re-folds the span set through mergeLineAnchoredDuplicates, which rebuilds it as (others + merged) and moves every line-anchored marker to the end. After that the spell span comes first and the click works, which is why a keystroke elsewhere in the document appeared to fix it.

Links were shadowed the same way: a markdown link inside a list item or heading lost its click to the block marker.

Fix

Narrowest span wins, line-anchored markers last. A position no decoration covers still resolves to the block span, so host onRichSpanClick listeners are unaffected.

Tests

New SpanHitTestTest covers a heading and a blockquote shadowing a word decoration, order-independence between the two, and the block span still answering positions its decorations don't cover. The two block-added-first cases fail without the fix.

:ComposeTextEditor:desktopTest and :ComposeTextEditorSpellCheck:desktopTest are green.

findSpanAtPosition returned the first span in the line's list that
contained the position, and that list is in span-set iteration order.
A heading, list item, blockquote or code fence carries a whole-line
RichSpan, so on a freshly imported document the block marker won the
hit test over the word-sized spell-check or link span inside it, and
a right click on a squiggle got the plain context menu.

Set order also flips: any text edit re-folds the set through
mergeLineAnchoredDuplicates, which moves line-anchored markers to the
end, so the same click answered differently before and after a
keystroke elsewhere in the document.

Narrowest span wins now, line-anchored markers last.
@codacy-production

codacy-production Bot commented Aug 4, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 19 complexity · 4 duplication

Metric Results
Complexity 19
Duplication 4

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Content spans answer first, then editor decorations, then the line-anchored
marker. Width is measured within the clicked line, and the order is total so
the winner never falls back to span-set iteration order.
@Wavesonics Wavesonics changed the title Hit-test the innermost span, not the first one Hit-test the most specific span, not the first one Aug 4, 2026
@Wavesonics
Wavesonics merged commit 1b49665 into main Aug 4, 2026
2 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.

1 participant