fix: match an agent's quote against rendered text - #69
Merged
Conversation
An agent quotes the Markdown source, but the page matched that quote against a block's textContent. A quote carrying `**`, a backtick or a `##` therefore never matched the very block it was copied from: the thread lost its anchor, and with the anchor went the click handler that selects the target, so the card did nothing when clicked. Reading the Markdown in a quote is the server's half now, because the server is what rendered the document and a second parser in the page would drift from what is on screen. NormalizeAnchorQuote puts the quote through the same converter as the document body; the page keeps the anchor resolution, where the spec-element-N numbering lives, and compares against the text it is handed. anchorQuoteText is derived on every read and never stored, so a sidecar written before it existed resolves its threads too. Markdown only: a diff resolves its quotes against the diff lines in Go, and a Markdown reading of a line of source code would be a trap there. Two cases the browser check caught: a tag becomes nothing rather than a space, because textContent draws no whitespace from an element; and a table row quoted on its own is given the delimiter row GFM wants, since a tr is a comment target whose pipes no rendered row contains. User request: reproduce the bug first, then fix it. Reproduction had to be both a headless-browser measurement and a test that go test can run, so the quote normalization was moved into Go rather than adding the browser test harness AGENTS.md says this repository does without. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merged
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.
Why this change is necessary
A thread the agent opens carries an Anchor Quote, and the page turns that quote into an Anchor so the card can point at a block. The agent quotes the Markdown source, but the page matched that quote against a block's rendered text, so any quote carrying
**, a backtick or a##never matched the very block it was copied from. The thread lost its target, and with the target went the click handler: the card did nothing when clicked, and the block it was written about carried no Comment Indicator at all.This was not an edge case. Every agent-opened thread in this project's own session transcripts quoted the source with its markup intact — which is exactly what
review_replyasks for ("copy it exactly from the document"). The agent was doing as it was told; the comparison was the half that was wrong.Approach
Reading the Markdown in a quote is the server's half now. The server is what rendered the document, so putting the quote through that same converter is what keeps the two from drifting — a second, approximate parser in the page is precisely the failure this avoids. The page keeps the anchor resolution, because
spec-element-Nexists only in its own DOM walk and reproducing that numbering in Go is the duplication the anchor rules forbid.The derived text is computed on every read and never stored, so the quote the agent wrote stays the single source of truth and a sidecar written before this existed resolves its threads too. Markdown only: a diff resolves its quotes against the diff lines in Go, where a Markdown reading of a line of source code would be nothing but a trap.
Review Points
textContentdraws no whitespace from an element and a space costs the match on a code span that ends a sentence; and a table row quoted on its own is handed the delimiter row GFM wants, because atris a comment target whose pipes no rendered row contains.commentCard()returns the first card for an anchor, so when two threads quote the same block, clicking the second selects the first one's line. That predates this change and is reachable with two human comments on one paragraph.