Parent
#328.
What to build
A person selects "the venue budget" in the pad and leaves a Comment. In chat, the Comment's jump button then scrolls only to the section heading, not to the sentence. The pad searches for the quote one text node at a time. Bold, italic or a link splits a sentence into several text nodes, so the quote never matches. After this change, the jump finds the quote inside one block, whatever marks it crosses.
Acceptance criteria
Blocked by
None — can start now.
Agent brief
Type: AFK — an agent can finish this alone.
Category: bug
Current behavior:
- A text anchor stores
editor.state.doc.textBetween(selection.from, selection.to, '\n') (buildTextCommentAnchorFromEditor, apps/webapp/src/services/commentAnchor.ts:89-100).
- The jump runs
focusTextAnchor (apps/webapp/src/utils/scrollToCommentAnchor.ts:54-76). It walks doc.descendants and calls indexOf on each text node alone (:59-66). A quote that spans two text nodes never matches, so scrollToCommentAnchor falls back to scrollToHeading(anchor.heading_id) (:90).
Desired behavior:
- Walk textblocks, not text nodes. For each textblock, build its text with the same call the anchor used:
node.textBetween(0, node.content.size, '\n'). Leaf and separator handling then match exactly.
- On the first block whose text contains the trimmed quote, scroll that block's DOM (
editor.view.nodeDOM(pos)) with the existing scrollElementIntoEditorView.
- Keep the media path, the first-match rule and the heading fallback.
- Keep the jump free of
editor.focus() and of any selection change, so no iOS keyboard opens.
Where to start: focusTextAnchor in apps/webapp/src/utils/scrollToCommentAnchor.ts. The one caller of scrollToCommentAnchor is onJump in apps/webapp/src/components/chatroom/components/MessageCard/components/MessageContent/components/CommentReference.tsx:29. Line numbers are hints as of 2026-09-28; the agent searches by symbol.
Rules that apply:
- apps/webapp/CLAUDE.md §Document Comments ("Jump-to-doc targets the anchored content, not the heading"; do not revert to heading-only scroll).
- AGENTS.md §Test Policy: a test is optional. If you add one, extract the block matcher as a pure function over a ProseMirror doc. Test it with a quote across a bold mark. Case (b) applies: this bug shipped.
- If you add a test, keep the matcher free of the
@stores import, so the test does not load the store. Build the doc with getSchema as apps/webapp/src/components/TipTap/extensions/shared/match-section.test.ts does, and add the Bold mark.
- AGENTS.md §Test Policy: a phone check needs a real phone or a mobile user agent. A narrow desktop window still renders the desktop shell.
Verify:
cd apps/webapp && bun run typecheck, then bun run lint at the root.
- If a test was added:
cd apps/webapp && bun run test -- --testPathPatterns scrollToCommentAnchor. Sabotage: restore the per-text-node search and see the test fail.
- Browser, desktop, light and dark: in a long pad, bold one word in a paragraph far below its heading. Select a phrase across that word and leave a Comment. Scroll away. Click the Comment's jump button in chat: the paragraph scrolls into view, not the heading.
- Phone (iOS Safari and Android Chrome): open that Comment in the chat sheet and tap its jump button. The pad scrolls to the paragraph, and no keyboard opens.
Out of scope
- A quote that spans two blocks (it holds
\n). It keeps the heading fallback.
- Preferring a match inside the Comment's own section over the first match in the pad.
- Selecting or highlighting the quoted text after the jump.
Parent
#328.
What to build
A person selects "the venue budget" in the pad and leaves a Comment. In chat, the Comment's jump button then scrolls only to the section heading, not to the sentence. The pad searches for the quote one text node at a time. Bold, italic or a link splits a sentence into several text nodes, so the quote never matches. After this change, the jump finds the quote inside one block, whatever marks it crosses.
Acceptance criteria
Blocked by
None — can start now.
Agent brief
Type: AFK — an agent can finish this alone.
Category: bug
Current behavior:
editor.state.doc.textBetween(selection.from, selection.to, '\n')(buildTextCommentAnchorFromEditor,apps/webapp/src/services/commentAnchor.ts:89-100).focusTextAnchor(apps/webapp/src/utils/scrollToCommentAnchor.ts:54-76). It walksdoc.descendantsand callsindexOfon each text node alone (:59-66). A quote that spans two text nodes never matches, soscrollToCommentAnchorfalls back toscrollToHeading(anchor.heading_id)(:90).Desired behavior:
node.textBetween(0, node.content.size, '\n'). Leaf and separator handling then match exactly.editor.view.nodeDOM(pos)) with the existingscrollElementIntoEditorView.editor.focus()and of any selection change, so no iOS keyboard opens.Where to start:
focusTextAnchorinapps/webapp/src/utils/scrollToCommentAnchor.ts. The one caller ofscrollToCommentAnchorisonJumpinapps/webapp/src/components/chatroom/components/MessageCard/components/MessageContent/components/CommentReference.tsx:29. Line numbers are hints as of 2026-09-28; the agent searches by symbol.Rules that apply:
@storesimport, so the test does not load the store. Build the doc withgetSchemaasapps/webapp/src/components/TipTap/extensions/shared/match-section.test.tsdoes, and add theBoldmark.Verify:
cd apps/webapp && bun run typecheck, thenbun run lintat the root.cd apps/webapp && bun run test -- --testPathPatterns scrollToCommentAnchor. Sabotage: restore the per-text-node search and see the test fail.Out of scope
\n). It keeps the heading fallback.