Skip to content

fix(web): links in HTML renders open like chat links - #18897

Merged
maria-rcks merged 2 commits into
mainfrom
t3code/html-render-links
Oct 13, 2026
Merged

maria-rcks merged 2 commits into
mainfrom
t3code/html-render-links

Conversation

@maria-rcks

@maria-rcks maria-rcks commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Every link inside an agent's HTML render opened a new browser tab, even a pull request link that opens in the right panel when it shows up in chat.

HTML render links now go through the same path as chat links. A pull request from the thread's project opens in the right panel. Any other link follows Settings > Open links in. Cmd/Ctrl-click goes straight to the system browser without reading settings. The page bootstrap now sends the click's Cmd/Ctrl state with its ui/open-link request, and the frame gets the thread from the timeline and from the files panel.

The server injects the bootstrap when the page is published, so only renders published after this change send the modifier state. Older renders still get panel routing on a plain click.

Evidence

Dev server, web client, a Claude agent publishing a render with a link to #18887. Same thread and render in both runs.

Before (main): a plain click opens GitHub in a new tab, and no panel opens.

before: clicking the PR link in an HTML render leaves the thread with no right panel

before: recording of the PR link click, no panel opens

After: the pull request opens in the right panel.

after: clicking the PR link in an HTML render opens #18887 in the right panel

after: recording of the PR link click opening the right panel

Verification

  • vp test run packages/shared/src/htmlRender.test.ts apps/web/src/components/files/AttachmentFilePreview.test.tsx, plus web typecheck and lint on the changed files (all ran on Blacksmith)
  • Unverified: Cmd/Ctrl-click to the system browser. The headless test browser can't send a modifier click or show an external browser.

Written by Claude Opus 5.5 (claude-opus-5-5) in Claude Code, running in T3 Code.

🤖 Generated with Claude Code

A pull request opens in the right panel, other links follow the Open links
in setting, and Cmd/Ctrl-click goes to the system browser.
@maria-rcks
maria-rcks added this pull request to stack #18899 October 13, 2026 03:21
@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Oct 13, 2026
@github-actions github-actions Bot added the size:M 30-99 changed lines (additions + deletions). label Oct 13, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 13, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 13, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This changes the production default behavior for links inside HTML renders, routing pull requests to the right panel and other links through the configured link-target preference instead of always opening a browser tab. The implementation is localized, but the user-visible default change warrants deliberate review.

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

@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 20.9 KiB 20.9 KiB 0 B (0.0%) 29.3 KiB ✅
Codex Live turn messages 2 2 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.2 KiB 21.2 KiB 0 B (0.0%) 29.3 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: 80664d5 · PR result: b3e3b1d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6b431c62-9b3f-4f19-b681-9c923cfd7845
📥 Commits

Reviewing files that changed from the base of the PR and between 5889d9b and b3e3b1d.

📒 Files selected for processing (1)
  • apps/web/src/components/files/BrowserDocumentFrame.tsx

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

📝 Walkthrough

HTML-render link requests now carry Cmd/Ctrl modifier state. Preview components pass an optional thread reference to HTML-render documents, which route links through change-request handling or the app’s link-opening hooks instead of calling window.open directly.

Changes: HTML render links

Layer / File(s) Summary
Link request modifier protocol
packages/shared/src/htmlRender.ts, packages/shared/src/htmlRender.test.ts
The bootstrap includes click modifier keys in link requests. The parser returns each modifier as true only when its value is exactly true. Tests cover omitted and non-boolean values.
Thread-aware link routing
apps/web/src/components/chat/HtmlRenderFrame.tsx, apps/web/src/components/chat/MessagesTimeline.tsx, apps/web/src/components/files/AttachmentFilePreview.tsx, apps/web/src/components/files/BrowserDocumentFrame.tsx, apps/web/src/components/files/FilePreviewPanel.tsx
Chat and attachment previews pass an optional thread reference to HTML-render documents. Cmd/Ctrl-clicked links open externally. Other links use change-request handling when a thread exists, then fall back to the app’s link-opening hook.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant HtmlRenderBootstrap
  participant HtmlRenderDocument
  participant openChangeRequestLink
  participant openLink
  HtmlRenderBootstrap->>HtmlRenderDocument: Send URL and modifier keys
  HtmlRenderDocument->>openChangeRequestLink: Offer URL when a thread reference exists
  openChangeRequestLink-->>HtmlRenderDocument: Return handled or unhandled result
  HtmlRenderDocument->>openLink: Pass URL when unhandled or no thread exists
Loading

Suggested reviewers: t3dotgg

Merge Risk: ⚪ Minimal · up to b3e3b

HTML-render links now use thread-aware pull-request routing and the app’s link opener. No concrete user-facing failure is established that warrants delaying merge.

Security architecture risk: 🟡 Moderate · up to b3e3b

Existing sandbox and permission checks remain, but interactive HTML gains access to additional navigation outcomes inside the signed-in app. Its scripts can request navigation during recent user activity without proving that the reader selected that destination. The exposure is constrained by thread context, browser preferences, and permissions.

Retained concerns

  • Medium · security · inferred: HTML scripts can use ambient frame focus and user activation to request arbitrary HTTP(S) navigation, without a host-verifiable association to a reader-selected link. This admission policy predates the PR, but its outcomes now include authenticated preview opens under the configured browser profile and thread-aware PR-panel routing. Thread, capability, preference, and permission checks bound the exposure; they do not distinguish an actual link click from another message emitted by the frame.
Security review details

Security Blast Radius

  • inferred — The demonstrated additional reach is navigation within the supplied thread's environment and eligible PR projects. Preview reach requires app-browser preference, runtime availability, and preview-operate permission. The inspected evidence does not establish cross-environment access or the ultimate browser network and credential exposure.

Security Findings and Attack Paths

  • inferred — A script in an attacker-influenced render can post its own ui/open-link message while the frame is focused and user activation is available. The injected listener's trusted-anchor check is not a host-side requirement. The PR lets such a message reach preview or PR-panel routing rather than only the previous external opener; downstream compromise has not been established.

Trust Boundaries and Controls

  • observed — Preview-operate scope is checked by the client and enforced against the authenticated connection's scopes on the server. PR routing restricts project selection to the thread environment and requires pull-request capability; host-based fallback additionally requires threadPullRequests capability.

Resilience and Maintainability Implications

  • observed — Navigation dispatch and asynchronous completion are separate: the HTML result is posted before preview or external opening settles. Failures are handled by the existing opener or logged by the host. The inspected protocol does not establish a completed-navigation acknowledgement or request-id deduplication guarantee.

Hardening Proposals

  • proposed — Consider host-controlled confirmation for navigation from sandboxed HTML into authenticated preview contexts, or retain external-only routing where reader intent cannot be established. A one-shot admission policy could also bound repeated requests during a single activation window.
Pre-merge checks · Passed 4
Passed Passed checks (4 passed)
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely describes the main change: HTML render links now use chat-link behavior.
Description check Passed The description covers the problem, the implementation, affected behavior, evidence, verification commands, an unverified modifier-click case, and the agent details. It does not include a separate Sco…
✨ Finishing Touches 💡 1
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@macroscopeapp
macroscopeapp Bot dismissed their stale review October 13, 2026 03:51

Dismissing prior approval to re-evaluate b3e3b1d

@maria-rcks
maria-rcks merged commit 98a6246 into main Oct 13, 2026
47 of 50 checks passed
@maria-rcks
maria-rcks deleted the t3code/html-render-links branch October 13, 2026 13:35
github-actions Bot added a commit to davidvanderklay/t3code-flake that referenced this pull request Oct 13, 2026
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Oct 13, 2026
sandscooling pushed a commit to sandscooling/t3code that referenced this pull request Oct 14, 2026
Sync 1013a: 52 upstream commits, clean in git. Fork types adapted:
the orchestration handlers test stub forwards the new prepareRun
(pingdotgg#19102), and the fork's HTML render panel card passes threadRef to
its inline frame so links open like chat links (pingdotgg#18897).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Oct 14, 2026
Merges `pingdotgg/t3code` up to `6002c5c68c` (42 commits, base
`5070a83a32`), using a merge commit. No open merge PR to stack on; this
branches from `main` at `882bb0a638`.

## Resolution

- **`package.json`** (conflict): dropped upstream's `tsc-rs` 0.6.0 bump
(pingdotgg#19187). The fork removed `tsc-rs`, and the `tsc-rs` tripwire holds
that. New path-policy entry: `root-package-json` (`decide`).
- **`pnpm-lock.yaml`** (conflict, `theirs`): took upstream's copy. Its
seven `tsc-rs@0.6.0` entries fail `minimumReleaseAge` before resolution
can prune them, so I stripped them and let `install.mjs` re-derive the
rest. The fork edges (`moatless-api`, `mermaid`) are back, and `verify
--only lockfile` passes.
- 14 other files that both sides touched merged without conflict.
`duplicate-adds` and `resolution-check` are both clean.
`FilePreviewPanel.tsx` (`decide`) took upstream's `threadRef` prop and
kept both fork deltas. The two unlisted files now have `converged`
entries: `terminal-drawer-open-in-editor` and `thread-shell-subtasks`.
In `threadShell.ts`, upstream's `isChildRunLive` and the fork's listing
filter merged together correctly.
- File counts: 136 files landed, against 136 in the upstream range. The
only differences are `package.json` (missing, because its one upstream
change was the dropped bump) and `docs/fork/inventory.json` (extra,
fork-only). The fork delta is 809 files.
- **Script fix:** Moatless replaced its dispatch match arms with a
`MethodTable` (moatless#1138). Because of that,
`unsupported-methods.mjs` found no arms and exited 2. It now reads
`.detached`/`.session`/`.stream` registrations and follows handlers
passed by name. Result: ADD 0, DROP 0, KEEP 2 (`vcs.switchRef`,
`git.preparePullRequestThread`), which matches `gaps.md`.

## Verification

`verify.mjs`: these checks pass: duplicate-adds, tripwires,
resolution-check, unsupported-methods (after the fix), lockfile, fmt,
lint, typecheck, build. `test` fails. Each of the failing files also
failed when run alone, and they are the same five files as in the
2026-10-13 merge, all caused by the sandbox: shared `AgentScope`, server
`ServerBrowserPage` (no Playwright browser), `AcpAdapterV2`,
`OrchestratorReplayFixtures`, and `OpenCodeServerLedger`.

## Usable as-is

- Full-screen visual replies with zoom (pingdotgg#17561). Links inside HTML
renders open the way chat links do (pingdotgg#18897). App shortcuts keep working
after you click into an HTML render (pingdotgg#18898).
- Touch-scrolling in terminal output (pingdotgg#11305). Fixed: answering a
question reset the editor caret (pingdotgg#11337). Links in agent questions are
now clickable (pingdotgg#19197).
- Fixed: "Subagent needs input" stayed after the subagent finished
(pingdotgg#18996). This is client-side and uses `activityRunStatus` with a
fallback to `status`.
- Slash skill search now works mid-message (pingdotgg#19107). Fixed: removing a
project opened a new thread for it (pingdotgg#15864). Settings now warn when an
environment silently drops a setting (pingdotgg#18334).
- Smaller fixes: context handoff details use the available width
(pingdotgg#19098), pace icon alignment (pingdotgg#18580), tool-call collection perf
(pingdotgg#18853), git file paths keep whitespace in the contract schema
(pingdotgg#12465), Android beta links (pingdotgg#18955).
- Pending context-handoff preview when you pick a model (pingdotgg#18369). It is
computed on the client from `server.providers`
(`requiresNewThreadForModelChange`, `continuation.groupKey`). If
Moatless does not report those fields, the preview shows a handoff on
every provider switch.

## Unsupported in Moatless / needs implementation

- Delegated tasks in separate worktrees (pingdotgg#19102): `isolation:
"worktree"` on the orchestrator MCP `delegate_task`, and
`workspaceStrategy` on the child-launch command. Moatless does not
create worktrees (`FEATURES.worktreeSelection`).
- These land behind flags that are already gated: scheduled-task dialog
fix (pingdotgg#18940, `scheduledTasks`), PR merge/auto-merge button sizing
(pingdotgg#18478, pingdotgg#19111, `pullRequestSurface`), remembering editor selections
(pingdotgg#19078, `openInEditor`), device tool startup failures (pingdotgg#18502,
`deviceHub`), npm "update everything" (pingdotgg#18709, `serverUpdateBanner`),
clone folder names in the welcome wizard (pingdotgg#12474), Cmd+Enter
create-project in the palette (pingdotgg#18901).
- No new RPC methods (ADD 0).

## Backend behavior to consider reproducing in Moatless

I added these to the "Runtime fixes upstream made to its own server"
entry in `docs/fork/gaps.md`:
- Worktree isolation for delegated tasks (pingdotgg#19102).
- Snoozed threads still take queued automatic wakes (pingdotgg#19099).
- Indexed failed-thread error lookup (pingdotgg#18628).
- SQLite: no fsync on every commit, and planner stats collected on open
(pingdotgg#19205, pingdotgg#18471).
- Distinct CI checks with overlapping names (pingdotgg#19162).
- Bitbucket short-hash heads in review guides (pingdotgg#18900).
- Webhook template backtracking (pingdotgg#19160).
- OpenCode timeout formatting (pingdotgg#12469).
- Pi tool schemas and turn-start queueing (pingdotgg#18643, pingdotgg#18639).
- Usage hubs without a host, and Antigravity accounts deduplicated
(pingdotgg#11368, pingdotgg#19207).
- Haiku 5.5 as the default for background text generation (pingdotgg#18781).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---
Moatless task:
https://moatless.soaplabstest.com/tasks/db18f6dc-70c1-4d89-ada7-cd4b4f19a247
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants