You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(web): Cite works on text selected inside HTML renders - #33106
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
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:
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.
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.
It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.
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.
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.
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.
pingdotgg/t3code#33124: Both changes allow citation insertion without a source anchor; this PR uses that path for HTML-render citations.
Suggested reviewers:juliusmarminge, shivamhwp
Merge Risk: Minimal · up to bb948
Selecting text inside an inline HTML render now offers Cite and inserts the quote into the composer. No merge-blocking defect remains. Keyboard-only users cannot reach Cite from a frame selection, which is a small follow-up rather than a blocker.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title "fix(web): Cite works on text selected inside HTML renders" names the main change. The PR lets users cite text selected inside inline HTML renders in a thread timeline, and the changeset add…
Description check
The description follows the repository template. It has Problem, Change, Scope, and Verification sections, and it uses a conventional-style problem statement. The Problem section states the bug and th…
Linked Issues check
Issue #33039 requires Cite for text selected inside an inline HTML render in a thread, with the citation pointing at the render row, and the sandbox is kept. The whole-PR summary (merge base c5e6b24 t…
Out of Scope Changes check
Every changed file supports the inline HTML-render citation path. The frame bridge and shared validator, the selection toolbar, the composer and citation insertion, citation resolution, opaque source …
Full details: Title check
Explanation
The title "fix(web): Cite works on text selected inside HTML renders" names the main change. The PR lets users cite text selected inside inline HTML renders in a thread timeline, and the changeset adds the frame selection bridge, toolbar handling, and citation support for HTML-render tool items. The title is short, uses a conventional commit prefix, and matches the scope in the description and the linked issue.
Full details: Description check
Explanation
The description follows the repository template. It has Problem, Change, Scope, and Verification sections, and it uses a conventional-style problem statement. The Problem section states the bug and the cause. The Change section explains the bridge from frame selections to the host toolbar and the citation changes. The Scope section links issue #33039, explains why it has no triage yet, and lists exclusions. The Verification section names focused tests, a manual web-client check, and the untested areas. The template asks for a before/after screenshot for UI changes, and the description provides a before/after table with images. The Scope section does not state a maintainer request, but the template allows that item to be omitted when none exists. The description also includes the model and harness, as the template requests when an agent was used. The description is mostly complete.
Full details: Linked Issues check
Explanation
Issue #33039 requires Cite for text selected inside an inline HTML render in a thread, with the citation pointing at the render row, and the sandbox is kept. The whole-PR summary (merge base c5e6b24 to head bb948f1) shows this path. packages/shared/src/htmlRender.ts adds the selection-changed notification and readHtmlRenderSelection, which validates it. BrowserDocumentFrame.tsx 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 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 is a related surface that the PR excludes. No stated requirement remains unmet. The previous PASS still holds. The current summary does not show a change that alters this conclusion.
Full details: Out of Scope Changes check
Explanation
Every changed file supports the inline HTML-render citation path. The frame bridge and shared validator, the selection toolbar, the composer and citation insertion, citation resolution, opaque source navigation, the timeline row wrapper, the tests, and the two docs pages all serve this path. selectionActions.ts adds selectionReported() so focus moving into the frame does not dismiss the toolbar. Optional sourceAnchor widening in ChatView.tsx, ComposerPromptEditorTiptap.tsx, ChatComposer.tsx, and MessagesTimeline.tsx supports citations without an anchor on the opaque path. The HtmlRenderFrame.tsx change only makes the load-failure text non-selectable. The contracts change adds only a comment. No change extends Cite to excluded surfaces: the full-screen dialog, pre-change renders, guest shares, file previews, MCP app frames, or native mobile. No unrelated change is demonstrated. External evidence links in the objectives were not used as proof of code.
Finishing Touches
Generate unit tests (beta)
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.
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:
same report text on the PR:
selection, citation comment, source navigation, counter, and send:
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.
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.
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.
Fixespingdotgg#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 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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
needs-rebaseConflicts with its base branch. Closes after 7 days with no activity.size:L100-499 changed lines (additions + deletions).vouch:unvouchedAuthor is not yet trusted in the VOUCHED list.
3 participants
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.
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 ast3/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).readHtmlRenderSelectionvalidates the message.BrowserDocumentFrame.tsx:HtmlRenderDocumentaccepts 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.AssistantSelectionToolbarshows the same Cite button for a frame selection. The focused document owns the selection, so selecting host text works as before.selectionActionsgetsselectionReported(), because focus moving into the frame blurs the host window and would otherwise dismiss the toolbar.messageIdcarries the render's turn item id, which history paging already resolves.citationProcedurerecognizes a completed HTML render tool item, and no other tool.AssistantCitationSourcegets anopaquemode, so "Go to source" lands on the render instead of showing "The quoted text has changed".sourceAnchoris optional, so the comment popover opens anchored at the chip.docs/internals/assistant-citations.mdanddocs/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:
apps/server/src/htmlRender/HtmlRender.ts), so only new renders report selections.Verification
Tests that fail on
mainand pass on this branch:htmlRender.test.ts:readHtmlRenderSelectionreads 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.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:changedpasses 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.main)After clicking Cite, the quote from the render is in the composer:
The before shot is the same thread in the
mainweb 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
mainwhen citing normal assistant text, and #28016 noted it too.Not checked:
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