Skip to content

fix(web): Cite works on text selected inside HTML renders - #33106

Open
lol2x wants to merge 1 commit into
pingdotgg:mainfrom
lol2x:fix/html-render-cite
Open

lol2x wants to merge 1 commit into
pingdotgg:mainfrom
lol2x:fix/html-render-cite

Conversation

@lol2x

@lol2x lol2x commented Nov 8, 2026

Copy link
Copy Markdown

Problem

Selecting text inside an HTML render in a thread never shows Cite, so nothing in a render can be quoted into the composer. Cite works on normal assistant text. The render runs in a sandboxed frame with an opaque origin, and the selection toolbar only reads the host document's selection, so it never sees one made inside the frame. Fixes #33039.

Change

  • packages/shared/src/htmlRender.ts: the page bootstrap reports its selection to the host as t3/notifications/selection-changed. The message carries the text, the last line box (to place the button), and the whole selection's bounds (to decide whether it is visible).
    • A drag selection is held until the pointer is released.
    • A clear is sent when the selection collapses, when the frame loses focus (for example to another render), and when a scroll moves the selection.
    • readHtmlRenderSelection validates the message.
  • BrowserDocumentFrame.tsx: HtmlRenderDocument accepts a selection only while its frame has focus, clamps the rects to the frame, and re-dispatches it as a DOM event on the frame. Clears always pass.
    • Unlike links, keys and images, a selection does not need recent user activation. It only shows a button, and nothing reaches the composer until the reader clicks Cite. Transient activation also runs out during long drags and touch selections.
  • AssistantSelectionToolbar shows the same Cite button for a frame selection. The focused document owns the selection, so selecting host text works as before.
    • selectionActions gets selectionReported(), because focus moving into the frame blurs the host window and would otherwise dismiss the toolbar.
  • The citation keeps the v1 format. messageId carries the render's turn item id, which history paging already resolves.
    • citationProcedure recognizes a completed HTML render tool item, and no other tool.
    • AssistantCitationSource gets an opaque mode, so "Go to source" lands on the render instead of showing "The quoted text has changed".
    • sourceAnchor is optional, so the comment popover opens anchored at the chip.
  • Docs: docs/internals/assistant-citations.md and docs/user/composer.md.

Scope and approval

#33039 has not been triaged yet. The fix keeps to the reported path: inline HTML renders in a thread's timeline. These are left out on purpose:

  • Full-screen render dialog: a modal with CSS zoom that covers the composer. Cite from the inline render.
  • Renders published before this change: the server writes the bootstrap into the stored page when it publishes (apps/server/src/htmlRender/HtmlRender.ts), so only new renders report selections.
  • Guest shares: no composer to cite into.
  • File previews: not a timeline item.
  • MCP app frames: a different bridge, without this bootstrap.
  • Native mobile app: has no Cite at all.

Verification

Tests that fail on main and pass on this branch:

  • htmlRender.test.ts: readHtmlRenderSelection reads a selection with its bounds and last line box, reads a clear, and rejects anything else.
  • BrowserDocumentFrame.browser.test.tsx: a real sandboxed frame with the bootstrap, selected with a real mouse drag.
    • The selection is reported only while the frame has focus.
    • Scrolling an unrelated element keeps it.
    • Moving focus out of the frame clears it.
  • MessagesTimeline.browser.test.tsx: an HTML render row shows Cite and cites { messageId: <row id>, text }. This includes a selection whose last line is below the chat viewport.
  • citationProcedure.test.ts: a cited render is found by its item id, and no other tool is.

vp run check:changed passes on the rebased branch: format, lint, knip, typecheck (36 packages), 261 tests in 13 files, and the React Compiler check.

I also checked it once in a real web client: vp run dev, headless Chromium through Playwright, on a thread where Claude Haiku published a render.

  • Selecting text in the render shows Cite next to it.
  • Cite adds the chip.
  • Clicking into the composer hides Cite.
  • The chip's link lands on the render without a toast.
  • At 360px wide, Cite still shows.
  • Cite on normal assistant text still works.
Before (main) After
Before: text selected inside the render, no Cite button After: Cite appears next to the selected text
Text selected in the render. No Cite. Cite appears next to the selection.

After clicking Cite, the quote from the render is in the composer:

After clicking Cite: a chip with the quoted render text in the composer

The before shot is the same thread in the main web client. Both clients run on a copy of the same database.

Clicking Cite logs React's "flushSync was called from inside a lifecycle method". I measured the same on main when citing normal assistant text, and #28016 noted it too.

Not checked:

  • Touch long-press selection on a real phone.
  • Firefox and Safari.
  • The desktop app.
  • Side chat in a real client. It renders through the same timeline.

Model: Claude Opus 5.5 in Claude Code, running in T3 Code. Research and diff review: GPT-6.1 Sol in Codex.

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:unvouched Author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Nov 8, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 077b456

Macroscope's review found this PR approvable — Adds citation support for text selected inside inline HTML renders, with navigation back to the render row. Existing sandbox isolation remains intact, and text enters the composer only through an explicit citation action.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The change reports text selections from HTML-render frames and connects them to the chat citation toolbar. Citations identify the render timeline item and can omit a DOM text anchor. The change also adds tests and documentation for this flow.

Changes: HTML-render citations

Layer / File(s) Summary
Selection notification protocol
packages/shared/src/htmlRender.ts, packages/shared/src/htmlRender.test.ts
The HTML-render bootstrap reports selection text and geometry. A shared reader validates notifications and coordinates. Tests cover valid, blank, and invalid reports.
Frame relay and citation toolbar
apps/web/src/components/files/BrowserDocumentFrame.tsx, apps/web/src/components/chat/AssistantSelectionToolbar.tsx, apps/web/src/lib/selectionActions.ts, apps/web/src/components/files/BrowserDocumentFrame.browser.test.tsx, apps/web/src/components/chat/HtmlRenderFrame.tsx
The frame dispatches selection updates as host events. The toolbar positions the citation action for valid, visible selections and clears it when selection or focus changes. Browser tests cover selection reporting and focus behavior.
Citation routing and insertion
apps/web/src/components/chat/AssistantCitationSource.tsx, apps/web/src/components/chat/TimelineAssistantRows.tsx, apps/web/src/components/chat/citationProcedure.ts, apps/web/src/components/chat/citationProcedure.test.ts, apps/web/src/components/chat/ChatComposer.tsx, apps/web/src/components/ChatView.tsx, apps/web/src/components/ComposerPromptEditorTiptap.tsx, apps/web/src/components/chat/MessagesTimeline.tsx, apps/web/src/components/chat/MessagesTimeline.browser.test.tsx, packages/contracts/src/assistantCitations.ts, docs/internals/assistant-citations.md, docs/user/composer.md
HTML-render rows act as citation sources, and citation resolution matches them by item ID. Citation insertion accepts an undefined source anchor. Tests and documentation describe the flow and its limits.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant HtmlRenderBootstrap
  participant BrowserDocumentFrame
  participant AssistantSelectionToolbar
  HtmlRenderBootstrap->>BrowserDocumentFrame: Send selection notification
  BrowserDocumentFrame->>AssistantSelectionToolbar: Dispatch selection event
  AssistantSelectionToolbar->>AssistantSelectionToolbar: Position citation action
Loading

Suggested reviewers: juliusmarminge, shivamhwp

Merge Risk: Minimal · up to b3cfc

The change adds a Cite action for text selected inside HTML renders. The reported tests and scenario runs show the flow working in a web client. No concrete merge-blocking risk was identified. Touch selection, Firefox, Safari and the desktop shell were not exercised.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title "fix(web): Cite works on text selected inside HTML renders" names the main change: selections made inside inline HTML renders now show the Cite action. The changeset adds iframe selection re…
Description check Passed The description follows the template sections: Problem, Change, Scope, and Verification. The Problem section states the bug and links #33039. The Change section explains the bridge, frame, toolbar, an…
Linked Issues check Passed Issue #33039 requires Cite for text selected inside an inline HTML render in the chat, with a citation that targets the render row instead of an assistant message. The whole-PR summary (merge base 10f…
Out of Scope Changes check Passed The changed files support the inline HTML-render citation path: the frame bridge and shared validator, the selection toolbar, composer and citation insertion, citation resolution, opaque source naviga…
Full details: Title check

Explanation

The title "fix(web): Cite works on text selected inside HTML renders" names the main change: selections made inside inline HTML renders now show the Cite action. The changeset adds iframe selection reporting, toolbar handling, and citation support for HTML renders, which the title covers. It uses a conventional commit prefix and is 57 characters long.

Full details: Description check

Explanation

The description follows the template sections: Problem, Change, Scope, and Verification. The Problem section states the bug and links #33039. The Change section explains the bridge, frame, toolbar, and citation edits. The Scope section lists exclusions and links the issue. The Verification section lists focused tests, a manual web-client check, and unchecked areas. Two template items are weaker: the Scope section does not say why the PR is a bug fix rather than a feature, and the description cites a model and harness at the end as the template requests. Neither gap blocks merge review.

Full details: Linked Issues check

Explanation

Issue #33039 requires Cite for text selected inside an inline HTML render in the chat, with a citation that targets the render row instead of an assistant message. The whole-PR summary (merge base 10fbff5 to head b3cfc1d) shows this path. In packages/shared/src/htmlRender.ts, the bootstrap sends a selection-changed notification with text, last line box, and whole-selection bounds. readHtmlRenderSelection validates it. In BrowserDocumentFrame.tsx, HtmlRenderDocument accepts a non-null selection only while the frame has focus, clamps rects to the frame, and dispatches HTML_RENDER_SELECTION_EVENT. AssistantSelectionToolbar shows Cite for that event and builds a citation whose messageId is the render's turn item ID. citationProcedure resolves completed HTML render tool items by item ID. AssistantCitationSource in opaque mode navigates to the render without the changed-text warning. Tests cover message validation (htmlRender.test.ts), the real sandboxed frame selection and focus behavior (BrowserDocumentFrame.browser.test.tsx), the timeline Cite action (MessagesTimeline.browser.test.tsx), and citation resolution (citationProcedure.test.ts). The MCP app frame mention in the issue diagnosis is a described related surface, not a stated requirement; the PR excludes it explicitly. Since this check depends only on the whole-PR summary and no excluded file was needed, no requirement remains unmet.

Full details: Out of Scope Changes check

Explanation

The changed files support the inline HTML-render citation path: the frame bridge and shared validator, the selection toolbar, composer and citation insertion, citation resolution, opaque source navigation, the timeline row wrapper, tests, and two docs pages. The contracts change only adds a comment to AssistantCitation.messageId. The selectionActions.ts change adds selectionReported() so that focus moving into the frame does not dismiss the toolbar. The HtmlRenderFrame.tsx change makes only the load-failure text non-selectable. The ChatView.tsx and ComposerPromptEditorTiptap.tsx changes widen the optional sourceAnchor type to allow citations without an anchor, which the opaque render path requires. No change extends Cite to the excluded surfaces (full-screen dialog, pre-change renders, guest shares, file previews, MCP app frames, native mobile). No unrelated change is demonstrated.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between cca0a73 and b3cfc1d.

Files selected for processing: 5

  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/ComposerPromptEditorTiptap.tsx
  • apps/web/src/components/chat/ChatComposer.tsx
  • apps/web/src/components/chat/MessagesTimeline.browser.test.tsx
  • apps/web/src/components/chat/MessagesTimeline.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@maria-rcks

maria-rcks commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified as-is at 077b456907bcf2f0205d9f8540aa32073e6bc89c on a Linux Boat VM, using repo Playwright 1.60.0 and /usr/bin/google-chrome against the running dev web app. projects and threads were created through the UI. real Codex gpt-6.1-sol turns published the HTML and received the citation. no mocks or seeded conversations were used.

  • reproduced on github/main (bcee0f5d28) in a separate worktree: dragging across the report sentence selects it but shows no Cite button.
  • on the exact PR head, the same sentence shows Cite. clicking it inserts the citation, opens the comment editor, and saves the comment.
  • clicking the citation returns to the render without a changed-text or missing-source warning. focusing the desktop-width composer dismisses Cite.
  • sending the citation reaches Codex, which replies with the exact selected sentence: Amber foxes carry seven blue notebooks. the protocol transcript confirms the render item ID and quote in turn/start with model gpt-6.1-sol.
  • neighboring behavior: normal assistant text still supports Cite, insertion, and source navigation. the render's Count button still increments from 0 to 1. Cite also appears with wrapped report text at 390px width.
  • 73 existing focused tests passed through blacksmith-check: HTML render protocol (12), citation resolution (17), browser frame behavior (3), and messages timeline (41). the PR has 31 completed CI checks, with none failing (some were skipped). no source changes or pushes were needed.

before, main:

main: selected report text has no Cite button

same report text on the PR:

PR: selecting report text shows Cite

selection, citation comment, source navigation, counter, and send:

real client citation flow

short runtime and provider transcript

Not verified

native desktop shells, iOS/Android, real touch selection, Firefox/Safari, other providers, side chat, and older-history pagination were not exercised. full-screen renders and previously published renders are outside this PR's stated scope.

separate baseline finding: html_preview headless capture timed out after 20 seconds on both main and the PR. html_render publication, inline display, and citation worked on both runs where applicable. this does not establish that the preview timeout is caused by this PR.

Copy link
Copy Markdown
Member

Evidence

Real web client, Chromium, 1440×1000, light theme, fresh synthetic state on each commit. Publish an inline HTML mockup, drag across its sentence, click Cite, then follow the composer quote chip’s source link. The base stops at the missing Cite button; the head completes the flow without a changed-text warning. scenarios:prove exited 0.

Before After
b3fdf2b10f b0fc9fd8f9
Before: selected HTML sentence has no Cite button After: Cite appears beside the selected sentence
Before recording After recording
Blocked by missing Cite After: selected quote inserted into composer
Blocked by missing Cite After: source link returns to the render without a changed-text warning

Scenario and synthetic replay driver

AI-assisted; verified with the tests and tools listed above.

Not shown:

  • Desktop shell and mobile were not exercised; this proof covers the reported web-client reproduction.

@github-actions

Copy link
Copy Markdown
Contributor

This PR conflicts with main. Rebase it or merge main into it. If it still conflicts after 7 days with no activity, it will be closed automatically.

See the conflict window.

@github-actions github-actions Bot added the needs-rebase Conflicts with its base branch. Closes after 7 days with no activity. label Nov 12, 2026
@lol2x
lol2x force-pushed the fix/html-render-cite branch from b0fc9fd to cca0a73 Compare November 12, 2026 15:05
@lol2x

lol2x commented Nov 12, 2026

Copy link
Copy Markdown
Author

Note

Rebased and re-tested by Claude Fable 5.2 on behalf of Adam (@lol2x), keeping the PR mergeable.

Rebased onto main at 2482b03a01. The branch is now one commit, cca0a735a3, with the same 19 files.

Conflicts resolved in four files:

  • packages/shared/src/htmlRender.ts: the bootstrap script keeps main's image gallery and + shortcut changes, with the selection reporting appended.
  • packages/contracts/src/assistantCitations.ts: uses main's ASSISTANT_CITATION_MAX_ID_LENGTH.
  • apps/web/src/components/chat/TimelineAssistantRows.tsx: imports from both sides.
  • apps/web/src/components/files/BrowserDocumentFrame.browser.test.tsx: keeps main's image viewer test and this PR's selection test.

Re-tested on cca0a735a3:

  • vp run check:changed passes: format, lint, knip, typecheck, react compiler, and 326 tests in 15 files.
  • The four focused test files (HTML render protocol, citation resolution, browser frame, messages timeline) pass: 102 tests.
  • Re-ran the html-render-cite.e2e.ts scenario from the evidence comment above with scenarios:prove, base 2482b03a01, head cca0a735a3. It exited 0: the base fails at the missing Cite button, and the head completes the flow (Cite, quote chip in the composer, source link back to the render without a changed-text warning).

Not re-run: the manual pass with real Codex turns, desktop shell, and mobile.

Selecting text inside an HTML render never showed Cite: the render runs in a
sandboxed frame, so the host never sees its selection. The page bootstrap now
reports its selection over the existing frame bridge, the host shows the usual
Cite button for it, and the citation points at the render's timeline item so
"Go to source" scrolls back to it.

Fixes pingdotgg#33039.

Model: Claude Opus 5.5 in Claude Code, running in T3 Code; research and review by GPT-6.1 Sol in Codex.
Rebased onto main and re-tested by Claude Fable 5.2 in Claude Code, running in T3 Code.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Fable 5.2 <[email protected]>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-rebase Conflicts with its base branch. Closes after 7 days with no activity. size:L 100-499 changed lines (additions + deletions). vouch:unvouched Author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cite does not appear when selecting text inside an HTML render in the chat

3 participants