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 →

Note

Reviews paused

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.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

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: Feature · 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

Possibly related PRs

  • 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: 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. The PR lets users cite text selected inside inline HTML renders in a thread timeline, and the changeset add…
Description check Passed 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 Passed 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 Passed 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
Recent review info

Run configuration

Commits

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

Files selected for processing: 11

  • 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
  • apps/web/src/components/chat/TimelineAssistantRows.tsx
  • apps/web/src/components/chat/citationProcedure.test.ts
  • apps/web/src/components/chat/citationProcedure.ts
  • apps/web/src/components/files/BrowserDocumentFrame.browser.test.tsx
  • apps/web/src/components/files/BrowserDocumentFrame.tsx
  • packages/shared/src/htmlRender.ts

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 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@lol2x
lol2x force-pushed the fix/html-render-cite branch from b3cfc1d to bb948f1 Compare November 15, 2026 18:18

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