feat(web): choose whether links open in the default browser or in T3 Code - #9339

Merged
juliusmarminge merged 4 commits into
mainfrom
browser-link-target
Sep 3, 2026
Merged

feat(web): choose whether links open in the default browser or in T3 Code#9339
juliusmarminge merged 4 commits into
mainfrom
browser-link-target

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

The in-app browser has become a general-purpose browser (profiles, cookie import), but every link a user clicks still went to the OS browser. The only way to get one into the in-app browser was the right-click menu on chat links, and terminal links forced a two-item menu on every click.

This adds a Browser setting, Open links in: Your default browser (the default, so nothing changes until it is switched) or T3 Code. With T3 Code selected, links clicked beside a thread open as a tab in the in-app browser. Cmd/Ctrl-click on a chat link still opens the default browser as the way out, mirroring how change-request links already treat the modifier; the right-click menu keeps offering both explicitly.

What follows the setting:

  • Markdown links in the chat (and PR descriptions, which reuse the same renderer)
  • Hyperlinks in the terminal — these no longer show a menu on every click; the setting decides
  • CI check Details in the PR panel
  • A PR link that has no checked-out project to open in the panel
  • The repository a publish just created

What stays on the system browser, because the control names its destination: Open on GitHub, the external-link icons in the PR header, the in-app browser's own "open in system browser" escape hatch, release notes. Non-web schemes (mailto:, vscode://) always go to the shell. The hosted web app and mobile have no in-app browser, so the setting does not render there and links behave as before.

Implementation: one pure resolver (browser/browserLinkTarget.ts) turns preference + click into a target, and a useOpenLink(threadRef) hook wraps it for the non-markdown buttons so every entry point makes the same decision.

Setting

Open links in setting

Chat link with T3 Code selected

Chat link opened in the in-app browser

Terminal link with T3 Code selected (no menu)

Terminal link opened in the in-app browser

Verified in the desktop app: chat link plain-click → in-app tab; Cmd-click → default browser; terminal link → in-app tab, no menu; a foreign PR link → in-app tab; setting back to default → unchanged pre-feature behaviour.

Claude Fable 5 via Claude Code.


Note

Medium Risk
Wide user-facing link routing change across chat, terminal, and PR surfaces, though default system preserves prior behavior until users opt in; in-app open paths add failure/fallback complexity beside threads.

Overview
Adds a client setting Open links in (browserLinkTarget: system by default, or app on desktop) under Integrations → Browser, with search, dirty-state labels, and restore-defaults support.

Central behavior:resolveLinkTarget and useOpenLink(threadRef) decide whether http(s) links beside a thread open in the in-app preview or via shell.openExternal, with in-app failure falling back to the system browser. Cmd/Ctrl on chat links still forces the system browser; non-web schemes always go to the shell; clients without an in-app browser ignore app.

Where it applies: chat markdown (including PR bodies via ChatMarkdown), terminal hyperlinks (the per-click preview/browser context menu is removed—the setting drives the path), CI check details, publish-repo links, and PR URLs that cannot open in-panel. PR UI passes threadRef through markdown editors and panels so previews and inline links follow the preference. Explicit “open on host” controls in the timeline still use the shell.

Contracts:BrowserLinkTarget and browserLinkTarget added to client settings schema and patches.

Reviewed by Cursor Bugbot for commit 69ee98f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add setting to open links in default browser or in-app browser

  • Adds a BrowserLinkTarget client setting (system | in-app, default system) and an "Open links in" control in Integrations settings, with search and dirty-tracking support
  • Introduces resolveLinkTarget in browserLinkTarget.ts and a shared useOpenLink hook that route link clicks based on the hydrated preference, falling back to the system browser for Cmd/Ctrl clicks, unsupported runtimes, and non-HTTP(S) URLs
  • Wires the active thread reference through chat markdown, terminal links, pull-request detail/timeline, and Git action controls so in-app preview opening has the context it needs
  • Removes the old terminal-link context menu; terminal links now open directly via the setting-aware path with system-browser fallback on failure
  • Risk: openTerminalLinkInPreview removes TerminalLinkContextMenuShowError and the per-click preview/browser choice; any out-of-tree callers of that error class or the context-menu flow will break. Non-desktop clients ignore the in-app choice and always use the system browser.

Macroscope summarized 69ee98f.

…Code
New Browser setting, "Open links in", with the default browser as the
default so nothing changes until it is switched. When set to T3 Code,
links clicked in chat markdown and terminal output open in a tab of the
in-app browser beside the thread. Cmd/Ctrl-click on a chat link still
opens the default browser as the way out; the right-click menu keeps
offering both explicitly.
Terminal links no longer show the two-item menu on every click: the
setting decides, and URLs the in-app browser cannot load still fall
back to the default browser.
…etting
CI check details, a pull request that has no checked-out project to open
in the panel, and the repository a publish just created are ordinary
links beside the thread, so they follow "Open links in" like chat
links do. Controls that name their destination — Open on GitHub, the
external-link icons, the in-app browser's own escape hatch — keep
opening the system browser.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.3 KiB−175 B (−1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.6 KiB6.4 KiB−175 B (−2.6%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB55.5 KiB−1.5 KiB (−2.6%)66.4 KiB
CodexLive turn messages109−1 (−10.0%)21
ClaudeTotal thread wire13.3 KiB13.5 KiB+159 B (+1.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.6 KiB+162 B (+2.5%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB57.8 KiB+1.4 KiB (+2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: 1aa44a0 · PR result: 69ee98f · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

Comment threadapps/web/src/lib/openPullRequestLink.ts Outdated
Comment threadapps/web/src/components/ChatMarkdown.tsx
Comment threadapps/web/src/browser/useOpenLink.ts Outdated
Comment threadapps/web/src/browser/useOpenLink.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c73d67c. Configure here.

Comment threadapps/web/src/browser/useOpenLink.ts Outdated
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a cross-surface link-routing feature and changes terminal links from a per-click choice menu to direct system-browser opening by default. Because the default product behavior changes and the implementation spans chat, terminal, pull-request, and Git-action paths, human review is warranted.

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

Read the preference through a subscription (chat) or after settings
hydrate (terminal, buttons) so a persisted "T3 Code" applies right
after launch. An in-app open that fails falls back to the system
browser instead of leaving the intercepted click dead, in-app opens
through useOpenLink record the visit like chat and terminal do,
useOpenPrLink forwards the target thread it was given, and pull request
bodies and comments pass the panel's thread through so their links
follow the setting too.
@juliusmarminge
juliusmarminge merged commit 18062da into mainSep 3, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the browser-link-target branch September 3, 2026 04:56
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Coriou added a commit to Coriou/txcode that referenced this pull request Sep 3, 2026
…y gate)
Upstream pingdotgg#9339 (browser link target label) and the pull-request diff
credential error (+test) added user-facing 'T3 Code' strings that
auto-merged into fork surfaces. Ported to 'Tx Code' per the fork
identity policy; docs/user/project-settings.md keeps upstream voice by
convention.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(web): choose whether links open in the default browser or in T3 Code - #9339

Merged
juliusmarminge merged 4 commits into
mainfrom
browser-link-target
Sep 3, 2026
Merged

feat(web): choose whether links open in the default browser or in T3 Code#9339
juliusmarminge merged 4 commits into
mainfrom
browser-link-target

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

The in-app browser has become a general-purpose browser (profiles, cookie import), but every link a user clicks still went to the OS browser. The only way to get one into the in-app browser was the right-click menu on chat links, and terminal links forced a two-item menu on every click.

This adds a Browser setting, Open links in: Your default browser (the default, so nothing changes until it is switched) or T3 Code. With T3 Code selected, links clicked beside a thread open as a tab in the in-app browser. Cmd/Ctrl-click on a chat link still opens the default browser as the way out, mirroring how change-request links already treat the modifier; the right-click menu keeps offering both explicitly.

What follows the setting:

  • Markdown links in the chat (and PR descriptions, which reuse the same renderer)
  • Hyperlinks in the terminal — these no longer show a menu on every click; the setting decides
  • CI check Details in the PR panel
  • A PR link that has no checked-out project to open in the panel
  • The repository a publish just created

What stays on the system browser, because the control names its destination: Open on GitHub, the external-link icons in the PR header, the in-app browser's own "open in system browser" escape hatch, release notes. Non-web schemes (mailto:, vscode://) always go to the shell. The hosted web app and mobile have no in-app browser, so the setting does not render there and links behave as before.

Implementation: one pure resolver (browser/browserLinkTarget.ts) turns preference + click into a target, and a useOpenLink(threadRef) hook wraps it for the non-markdown buttons so every entry point makes the same decision.

Setting

Open links in setting

Chat link with T3 Code selected

Chat link opened in the in-app browser

Terminal link with T3 Code selected (no menu)

Terminal link opened in the in-app browser

Verified in the desktop app: chat link plain-click → in-app tab; Cmd-click → default browser; terminal link → in-app tab, no menu; a foreign PR link → in-app tab; setting back to default → unchanged pre-feature behaviour.

Claude Fable 5 via Claude Code.


Note

Medium Risk
Wide user-facing link routing change across chat, terminal, and PR surfaces, though default system preserves prior behavior until users opt in; in-app open paths add failure/fallback complexity beside threads.

Overview
Adds a client setting Open links in (browserLinkTarget: system by default, or app on desktop) under Integrations → Browser, with search, dirty-state labels, and restore-defaults support.

Central behavior:resolveLinkTarget and useOpenLink(threadRef) decide whether http(s) links beside a thread open in the in-app preview or via shell.openExternal, with in-app failure falling back to the system browser. Cmd/Ctrl on chat links still forces the system browser; non-web schemes always go to the shell; clients without an in-app browser ignore app.

Where it applies: chat markdown (including PR bodies via ChatMarkdown), terminal hyperlinks (the per-click preview/browser context menu is removed—the setting drives the path), CI check details, publish-repo links, and PR URLs that cannot open in-panel. PR UI passes threadRef through markdown editors and panels so previews and inline links follow the preference. Explicit “open on host” controls in the timeline still use the shell.

Contracts:BrowserLinkTarget and browserLinkTarget added to client settings schema and patches.

Reviewed by Cursor Bugbot for commit 69ee98f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add setting to open links in default browser or in-app browser

  • Adds a BrowserLinkTarget client setting (system | in-app, default system) and an "Open links in" control in Integrations settings, with search and dirty-tracking support
  • Introduces resolveLinkTarget in browserLinkTarget.ts and a shared useOpenLink hook that route link clicks based on the hydrated preference, falling back to the system browser for Cmd/Ctrl clicks, unsupported runtimes, and non-HTTP(S) URLs
  • Wires the active thread reference through chat markdown, terminal links, pull-request detail/timeline, and Git action controls so in-app preview opening has the context it needs
  • Removes the old terminal-link context menu; terminal links now open directly via the setting-aware path with system-browser fallback on failure
  • Risk: openTerminalLinkInPreview removes TerminalLinkContextMenuShowError and the per-click preview/browser choice; any out-of-tree callers of that error class or the context-menu flow will break. Non-desktop clients ignore the in-app choice and always use the system browser.

Macroscope summarized 69ee98f.

…Code
New Browser setting, "Open links in", with the default browser as the
default so nothing changes until it is switched. When set to T3 Code,
links clicked in chat markdown and terminal output open in a tab of the
in-app browser beside the thread. Cmd/Ctrl-click on a chat link still
opens the default browser as the way out; the right-click menu keeps
offering both explicitly.
Terminal links no longer show the two-item menu on every click: the
setting decides, and URLs the in-app browser cannot load still fall
back to the default browser.
…etting
CI check details, a pull request that has no checked-out project to open
in the panel, and the repository a publish just created are ordinary
links beside the thread, so they follow "Open links in" like chat
links do. Controls that name their destination — Open on GitHub, the
external-link icons, the in-app browser's own escape hatch — keep
opening the system browser.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.3 KiB−175 B (−1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.6 KiB6.4 KiB−175 B (−2.6%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB55.5 KiB−1.5 KiB (−2.6%)66.4 KiB
CodexLive turn messages109−1 (−10.0%)21
ClaudeTotal thread wire13.3 KiB13.5 KiB+159 B (+1.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.6 KiB+162 B (+2.5%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB57.8 KiB+1.4 KiB (+2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: 1aa44a0 · PR result: 69ee98f · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

Comment threadapps/web/src/lib/openPullRequestLink.ts Outdated
Comment threadapps/web/src/components/ChatMarkdown.tsx
Comment threadapps/web/src/browser/useOpenLink.ts Outdated
Comment threadapps/web/src/browser/useOpenLink.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c73d67c. Configure here.

Comment threadapps/web/src/browser/useOpenLink.ts Outdated
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a cross-surface link-routing feature and changes terminal links from a per-click choice menu to direct system-browser opening by default. Because the default product behavior changes and the implementation spans chat, terminal, pull-request, and Git-action paths, human review is warranted.

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

Read the preference through a subscription (chat) or after settings
hydrate (terminal, buttons) so a persisted "T3 Code" applies right
after launch. An in-app open that fails falls back to the system
browser instead of leaving the intercepted click dead, in-app opens
through useOpenLink record the visit like chat and terminal do,
useOpenPrLink forwards the target thread it was given, and pull request
bodies and comments pass the panel's thread through so their links
follow the setting too.
@juliusmarminge
juliusmarminge merged commit 18062da into mainSep 3, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the browser-link-target branch September 3, 2026 04:56
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Coriou added a commit to Coriou/txcode that referenced this pull request Sep 3, 2026
…y gate)
Upstream pingdotgg#9339 (browser link target label) and the pull-request diff
credential error (+test) added user-facing 'T3 Code' strings that
auto-merged into fork surfaces. Ported to 'Tx Code' per the fork
identity policy; docs/user/project-settings.md keeps upstream voice by
convention.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): choose whether links open in the default browser or in T3 Code - #9339

Merged
juliusmarminge merged 4 commits into
mainfrom
browser-link-target
Sep 3, 2026
Merged

feat(web): choose whether links open in the default browser or in T3 Code#9339
juliusmarminge merged 4 commits into
mainfrom
browser-link-target

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

The in-app browser has become a general-purpose browser (profiles, cookie import), but every link a user clicks still went to the OS browser. The only way to get one into the in-app browser was the right-click menu on chat links, and terminal links forced a two-item menu on every click.

This adds a Browser setting, Open links in: Your default browser (the default, so nothing changes until it is switched) or T3 Code. With T3 Code selected, links clicked beside a thread open as a tab in the in-app browser. Cmd/Ctrl-click on a chat link still opens the default browser as the way out, mirroring how change-request links already treat the modifier; the right-click menu keeps offering both explicitly.

What follows the setting:

  • Markdown links in the chat (and PR descriptions, which reuse the same renderer)
  • Hyperlinks in the terminal — these no longer show a menu on every click; the setting decides
  • CI check Details in the PR panel
  • A PR link that has no checked-out project to open in the panel
  • The repository a publish just created

What stays on the system browser, because the control names its destination: Open on GitHub, the external-link icons in the PR header, the in-app browser's own "open in system browser" escape hatch, release notes. Non-web schemes (mailto:, vscode://) always go to the shell. The hosted web app and mobile have no in-app browser, so the setting does not render there and links behave as before.

Implementation: one pure resolver (browser/browserLinkTarget.ts) turns preference + click into a target, and a useOpenLink(threadRef) hook wraps it for the non-markdown buttons so every entry point makes the same decision.

Setting

Open links in setting

Chat link with T3 Code selected

Chat link opened in the in-app browser

Terminal link with T3 Code selected (no menu)

Terminal link opened in the in-app browser

Verified in the desktop app: chat link plain-click → in-app tab; Cmd-click → default browser; terminal link → in-app tab, no menu; a foreign PR link → in-app tab; setting back to default → unchanged pre-feature behaviour.

Claude Fable 5 via Claude Code.


Note

Medium Risk
Wide user-facing link routing change across chat, terminal, and PR surfaces, though default system preserves prior behavior until users opt in; in-app open paths add failure/fallback complexity beside threads.

Overview
Adds a client setting Open links in (browserLinkTarget: system by default, or app on desktop) under Integrations → Browser, with search, dirty-state labels, and restore-defaults support.

Central behavior:resolveLinkTarget and useOpenLink(threadRef) decide whether http(s) links beside a thread open in the in-app preview or via shell.openExternal, with in-app failure falling back to the system browser. Cmd/Ctrl on chat links still forces the system browser; non-web schemes always go to the shell; clients without an in-app browser ignore app.

Where it applies: chat markdown (including PR bodies via ChatMarkdown), terminal hyperlinks (the per-click preview/browser context menu is removed—the setting drives the path), CI check details, publish-repo links, and PR URLs that cannot open in-panel. PR UI passes threadRef through markdown editors and panels so previews and inline links follow the preference. Explicit “open on host” controls in the timeline still use the shell.

Contracts:BrowserLinkTarget and browserLinkTarget added to client settings schema and patches.

Reviewed by Cursor Bugbot for commit 69ee98f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add setting to open links in default browser or in-app browser

  • Adds a BrowserLinkTarget client setting (system | in-app, default system) and an "Open links in" control in Integrations settings, with search and dirty-tracking support
  • Introduces resolveLinkTarget in browserLinkTarget.ts and a shared useOpenLink hook that route link clicks based on the hydrated preference, falling back to the system browser for Cmd/Ctrl clicks, unsupported runtimes, and non-HTTP(S) URLs
  • Wires the active thread reference through chat markdown, terminal links, pull-request detail/timeline, and Git action controls so in-app preview opening has the context it needs
  • Removes the old terminal-link context menu; terminal links now open directly via the setting-aware path with system-browser fallback on failure
  • Risk: openTerminalLinkInPreview removes TerminalLinkContextMenuShowError and the per-click preview/browser choice; any out-of-tree callers of that error class or the context-menu flow will break. Non-desktop clients ignore the in-app choice and always use the system browser.

Macroscope summarized 69ee98f.

…Code
New Browser setting, "Open links in", with the default browser as the
default so nothing changes until it is switched. When set to T3 Code,
links clicked in chat markdown and terminal output open in a tab of the
in-app browser beside the thread. Cmd/Ctrl-click on a chat link still
opens the default browser as the way out; the right-click menu keeps
offering both explicitly.
Terminal links no longer show the two-item menu on every click: the
setting decides, and URLs the in-app browser cannot load still fall
back to the default browser.
…etting
CI check details, a pull request that has no checked-out project to open
in the panel, and the repository a publish just created are ordinary
links beside the thread, so they follow "Open links in" like chat
links do. Controls that name their destination — Open on GitHub, the
external-link icons, the in-app browser's own escape hatch — keep
opening the system browser.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.3 KiB−175 B (−1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.6 KiB6.4 KiB−175 B (−2.6%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB55.5 KiB−1.5 KiB (−2.6%)66.4 KiB
CodexLive turn messages109−1 (−10.0%)21
ClaudeTotal thread wire13.3 KiB13.5 KiB+159 B (+1.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.6 KiB+162 B (+2.5%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB57.8 KiB+1.4 KiB (+2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: 1aa44a0 · PR result: 69ee98f · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

Comment threadapps/web/src/lib/openPullRequestLink.ts Outdated
Comment threadapps/web/src/components/ChatMarkdown.tsx
Comment threadapps/web/src/browser/useOpenLink.ts Outdated
Comment threadapps/web/src/browser/useOpenLink.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c73d67c. Configure here.

Comment threadapps/web/src/browser/useOpenLink.ts Outdated
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a cross-surface link-routing feature and changes terminal links from a per-click choice menu to direct system-browser opening by default. Because the default product behavior changes and the implementation spans chat, terminal, pull-request, and Git-action paths, human review is warranted.

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

Read the preference through a subscription (chat) or after settings
hydrate (terminal, buttons) so a persisted "T3 Code" applies right
after launch. An in-app open that fails falls back to the system
browser instead of leaving the intercepted click dead, in-app opens
through useOpenLink record the visit like chat and terminal do,
useOpenPrLink forwards the target thread it was given, and pull request
bodies and comments pass the panel's thread through so their links
follow the setting too.
@juliusmarminge
juliusmarminge merged commit 18062da into mainSep 3, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the browser-link-target branch September 3, 2026 04:56
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Coriou added a commit to Coriou/txcode that referenced this pull request Sep 3, 2026
…y gate)
Upstream pingdotgg#9339 (browser link target label) and the pull-request diff
credential error (+test) added user-facing 'T3 Code' strings that
auto-merged into fork surfaces. Ported to 'Tx Code' per the fork
identity policy; docs/user/project-settings.md keeps upstream voice by
convention.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): choose whether links open in the default browser or in T3 Code - #9339

Merged
juliusmarminge merged 4 commits into
mainfrom
browser-link-target
Sep 3, 2026
Merged

feat(web): choose whether links open in the default browser or in T3 Code#9339
juliusmarminge merged 4 commits into
mainfrom
browser-link-target

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

The in-app browser has become a general-purpose browser (profiles, cookie import), but every link a user clicks still went to the OS browser. The only way to get one into the in-app browser was the right-click menu on chat links, and terminal links forced a two-item menu on every click.

This adds a Browser setting, Open links in: Your default browser (the default, so nothing changes until it is switched) or T3 Code. With T3 Code selected, links clicked beside a thread open as a tab in the in-app browser. Cmd/Ctrl-click on a chat link still opens the default browser as the way out, mirroring how change-request links already treat the modifier; the right-click menu keeps offering both explicitly.

What follows the setting:

  • Markdown links in the chat (and PR descriptions, which reuse the same renderer)
  • Hyperlinks in the terminal — these no longer show a menu on every click; the setting decides
  • CI check Details in the PR panel
  • A PR link that has no checked-out project to open in the panel
  • The repository a publish just created

What stays on the system browser, because the control names its destination: Open on GitHub, the external-link icons in the PR header, the in-app browser's own "open in system browser" escape hatch, release notes. Non-web schemes (mailto:, vscode://) always go to the shell. The hosted web app and mobile have no in-app browser, so the setting does not render there and links behave as before.

Implementation: one pure resolver (browser/browserLinkTarget.ts) turns preference + click into a target, and a useOpenLink(threadRef) hook wraps it for the non-markdown buttons so every entry point makes the same decision.

Setting

Open links in setting

Chat link with T3 Code selected

Chat link opened in the in-app browser

Terminal link with T3 Code selected (no menu)

Terminal link opened in the in-app browser

Verified in the desktop app: chat link plain-click → in-app tab; Cmd-click → default browser; terminal link → in-app tab, no menu; a foreign PR link → in-app tab; setting back to default → unchanged pre-feature behaviour.

Claude Fable 5 via Claude Code.


Note

Medium Risk
Wide user-facing link routing change across chat, terminal, and PR surfaces, though default system preserves prior behavior until users opt in; in-app open paths add failure/fallback complexity beside threads.

Overview
Adds a client setting Open links in (browserLinkTarget: system by default, or app on desktop) under Integrations → Browser, with search, dirty-state labels, and restore-defaults support.

Central behavior:resolveLinkTarget and useOpenLink(threadRef) decide whether http(s) links beside a thread open in the in-app preview or via shell.openExternal, with in-app failure falling back to the system browser. Cmd/Ctrl on chat links still forces the system browser; non-web schemes always go to the shell; clients without an in-app browser ignore app.

Where it applies: chat markdown (including PR bodies via ChatMarkdown), terminal hyperlinks (the per-click preview/browser context menu is removed—the setting drives the path), CI check details, publish-repo links, and PR URLs that cannot open in-panel. PR UI passes threadRef through markdown editors and panels so previews and inline links follow the preference. Explicit “open on host” controls in the timeline still use the shell.

Contracts:BrowserLinkTarget and browserLinkTarget added to client settings schema and patches.

Reviewed by Cursor Bugbot for commit 69ee98f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add setting to open links in default browser or in-app browser

  • Adds a BrowserLinkTarget client setting (system | in-app, default system) and an "Open links in" control in Integrations settings, with search and dirty-tracking support
  • Introduces resolveLinkTarget in browserLinkTarget.ts and a shared useOpenLink hook that route link clicks based on the hydrated preference, falling back to the system browser for Cmd/Ctrl clicks, unsupported runtimes, and non-HTTP(S) URLs
  • Wires the active thread reference through chat markdown, terminal links, pull-request detail/timeline, and Git action controls so in-app preview opening has the context it needs
  • Removes the old terminal-link context menu; terminal links now open directly via the setting-aware path with system-browser fallback on failure
  • Risk: openTerminalLinkInPreview removes TerminalLinkContextMenuShowError and the per-click preview/browser choice; any out-of-tree callers of that error class or the context-menu flow will break. Non-desktop clients ignore the in-app choice and always use the system browser.

Macroscope summarized 69ee98f.

…Code
New Browser setting, "Open links in", with the default browser as the
default so nothing changes until it is switched. When set to T3 Code,
links clicked in chat markdown and terminal output open in a tab of the
in-app browser beside the thread. Cmd/Ctrl-click on a chat link still
opens the default browser as the way out; the right-click menu keeps
offering both explicitly.
Terminal links no longer show the two-item menu on every click: the
setting decides, and URLs the in-app browser cannot load still fall
back to the default browser.
…etting
CI check details, a pull request that has no checked-out project to open
in the panel, and the repository a publish just created are ordinary
links beside the thread, so they follow "Open links in" like chat
links do. Controls that name their destination — Open on GitHub, the
external-link icons, the in-app browser's own escape hatch — keep
opening the system browser.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.3 KiB−175 B (−1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.6 KiB6.4 KiB−175 B (−2.6%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB55.5 KiB−1.5 KiB (−2.6%)66.4 KiB
CodexLive turn messages109−1 (−10.0%)21
ClaudeTotal thread wire13.3 KiB13.5 KiB+159 B (+1.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.6 KiB+162 B (+2.5%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB57.8 KiB+1.4 KiB (+2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: 1aa44a0 · PR result: 69ee98f · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

Comment threadapps/web/src/lib/openPullRequestLink.ts Outdated
Comment threadapps/web/src/components/ChatMarkdown.tsx
Comment threadapps/web/src/browser/useOpenLink.ts Outdated
Comment threadapps/web/src/browser/useOpenLink.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c73d67c. Configure here.

Comment threadapps/web/src/browser/useOpenLink.ts Outdated
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a cross-surface link-routing feature and changes terminal links from a per-click choice menu to direct system-browser opening by default. Because the default product behavior changes and the implementation spans chat, terminal, pull-request, and Git-action paths, human review is warranted.

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

Read the preference through a subscription (chat) or after settings
hydrate (terminal, buttons) so a persisted "T3 Code" applies right
after launch. An in-app open that fails falls back to the system
browser instead of leaving the intercepted click dead, in-app opens
through useOpenLink record the visit like chat and terminal do,
useOpenPrLink forwards the target thread it was given, and pull request
bodies and comments pass the panel's thread through so their links
follow the setting too.
@juliusmarminge
juliusmarminge merged commit 18062da into mainSep 3, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the browser-link-target branch September 3, 2026 04:56
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Coriou added a commit to Coriou/txcode that referenced this pull request Sep 3, 2026
…y gate)
Upstream pingdotgg#9339 (browser link target label) and the pull-request diff
credential error (+test) added user-facing 'T3 Code' strings that
auto-merged into fork surfaces. Ported to 'Tx Code' per the fork
identity policy; docs/user/project-settings.md keeps upstream voice by
convention.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(web): choose whether links open in the default browser or in T3 Code - #9339

Merged
juliusmarminge merged 4 commits into
mainfrom
browser-link-target
Sep 3, 2026
Merged

feat(web): choose whether links open in the default browser or in T3 Code#9339
juliusmarminge merged 4 commits into
mainfrom
browser-link-target

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

The in-app browser has become a general-purpose browser (profiles, cookie import), but every link a user clicks still went to the OS browser. The only way to get one into the in-app browser was the right-click menu on chat links, and terminal links forced a two-item menu on every click.

This adds a Browser setting, Open links in: Your default browser (the default, so nothing changes until it is switched) or T3 Code. With T3 Code selected, links clicked beside a thread open as a tab in the in-app browser. Cmd/Ctrl-click on a chat link still opens the default browser as the way out, mirroring how change-request links already treat the modifier; the right-click menu keeps offering both explicitly.

What follows the setting:

  • Markdown links in the chat (and PR descriptions, which reuse the same renderer)
  • Hyperlinks in the terminal — these no longer show a menu on every click; the setting decides
  • CI check Details in the PR panel
  • A PR link that has no checked-out project to open in the panel
  • The repository a publish just created

What stays on the system browser, because the control names its destination: Open on GitHub, the external-link icons in the PR header, the in-app browser's own "open in system browser" escape hatch, release notes. Non-web schemes (mailto:, vscode://) always go to the shell. The hosted web app and mobile have no in-app browser, so the setting does not render there and links behave as before.

Implementation: one pure resolver (browser/browserLinkTarget.ts) turns preference + click into a target, and a useOpenLink(threadRef) hook wraps it for the non-markdown buttons so every entry point makes the same decision.

Setting

Open links in setting

Chat link with T3 Code selected

Chat link opened in the in-app browser

Terminal link with T3 Code selected (no menu)

Terminal link opened in the in-app browser

Verified in the desktop app: chat link plain-click → in-app tab; Cmd-click → default browser; terminal link → in-app tab, no menu; a foreign PR link → in-app tab; setting back to default → unchanged pre-feature behaviour.

Claude Fable 5 via Claude Code.


Note

Medium Risk
Wide user-facing link routing change across chat, terminal, and PR surfaces, though default system preserves prior behavior until users opt in; in-app open paths add failure/fallback complexity beside threads.

Overview
Adds a client setting Open links in (browserLinkTarget: system by default, or app on desktop) under Integrations → Browser, with search, dirty-state labels, and restore-defaults support.

Central behavior:resolveLinkTarget and useOpenLink(threadRef) decide whether http(s) links beside a thread open in the in-app preview or via shell.openExternal, with in-app failure falling back to the system browser. Cmd/Ctrl on chat links still forces the system browser; non-web schemes always go to the shell; clients without an in-app browser ignore app.

Where it applies: chat markdown (including PR bodies via ChatMarkdown), terminal hyperlinks (the per-click preview/browser context menu is removed—the setting drives the path), CI check details, publish-repo links, and PR URLs that cannot open in-panel. PR UI passes threadRef through markdown editors and panels so previews and inline links follow the preference. Explicit “open on host” controls in the timeline still use the shell.

Contracts:BrowserLinkTarget and browserLinkTarget added to client settings schema and patches.

Reviewed by Cursor Bugbot for commit 69ee98f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add setting to open links in default browser or in-app browser

  • Adds a BrowserLinkTarget client setting (system | in-app, default system) and an "Open links in" control in Integrations settings, with search and dirty-tracking support
  • Introduces resolveLinkTarget in browserLinkTarget.ts and a shared useOpenLink hook that route link clicks based on the hydrated preference, falling back to the system browser for Cmd/Ctrl clicks, unsupported runtimes, and non-HTTP(S) URLs
  • Wires the active thread reference through chat markdown, terminal links, pull-request detail/timeline, and Git action controls so in-app preview opening has the context it needs
  • Removes the old terminal-link context menu; terminal links now open directly via the setting-aware path with system-browser fallback on failure
  • Risk: openTerminalLinkInPreview removes TerminalLinkContextMenuShowError and the per-click preview/browser choice; any out-of-tree callers of that error class or the context-menu flow will break. Non-desktop clients ignore the in-app choice and always use the system browser.

Macroscope summarized 69ee98f.

…Code
New Browser setting, "Open links in", with the default browser as the
default so nothing changes until it is switched. When set to T3 Code,
links clicked in chat markdown and terminal output open in a tab of the
in-app browser beside the thread. Cmd/Ctrl-click on a chat link still
opens the default browser as the way out; the right-click menu keeps
offering both explicitly.
Terminal links no longer show the two-item menu on every click: the
setting decides, and URLs the in-app browser cannot load still fall
back to the default browser.
…etting
CI check details, a pull request that has no checked-out project to open
in the panel, and the repository a publish just created are ordinary
links beside the thread, so they follow "Open links in" like chat
links do. Controls that name their destination — Open on GitHub, the
external-link icons, the in-app browser's own escape hatch — keep
opening the system browser.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.3 KiB−175 B (−1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.6 KiB6.4 KiB−175 B (−2.6%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB55.5 KiB−1.5 KiB (−2.6%)66.4 KiB
CodexLive turn messages109−1 (−10.0%)21
ClaudeTotal thread wire13.3 KiB13.5 KiB+159 B (+1.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.6 KiB+162 B (+2.5%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB57.8 KiB+1.4 KiB (+2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: 1aa44a0 · PR result: 69ee98f · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

Comment threadapps/web/src/lib/openPullRequestLink.ts Outdated
Comment threadapps/web/src/components/ChatMarkdown.tsx
Comment threadapps/web/src/browser/useOpenLink.ts Outdated
Comment threadapps/web/src/browser/useOpenLink.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c73d67c. Configure here.

Comment threadapps/web/src/browser/useOpenLink.ts Outdated
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a cross-surface link-routing feature and changes terminal links from a per-click choice menu to direct system-browser opening by default. Because the default product behavior changes and the implementation spans chat, terminal, pull-request, and Git-action paths, human review is warranted.

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

Read the preference through a subscription (chat) or after settings
hydrate (terminal, buttons) so a persisted "T3 Code" applies right
after launch. An in-app open that fails falls back to the system
browser instead of leaving the intercepted click dead, in-app opens
through useOpenLink record the visit like chat and terminal do,
useOpenPrLink forwards the target thread it was given, and pull request
bodies and comments pass the panel's thread through so their links
follow the setting too.
@juliusmarminge
juliusmarminge merged commit 18062da into mainSep 3, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the browser-link-target branch September 3, 2026 04:56
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Coriou added a commit to Coriou/txcode that referenced this pull request Sep 3, 2026
…y gate)
Upstream pingdotgg#9339 (browser link target label) and the pull-request diff
credential error (+test) added user-facing 'T3 Code' strings that
auto-merged into fork surfaces. Ported to 'Tx Code' per the fork
identity policy; docs/user/project-settings.md keeps upstream voice by
convention.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): choose whether links open in the default browser or in T3 Code - #9339

Merged
juliusmarminge merged 4 commits into
mainfrom
browser-link-target
Sep 3, 2026
Merged

feat(web): choose whether links open in the default browser or in T3 Code#9339
juliusmarminge merged 4 commits into
mainfrom
browser-link-target

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

The in-app browser has become a general-purpose browser (profiles, cookie import), but every link a user clicks still went to the OS browser. The only way to get one into the in-app browser was the right-click menu on chat links, and terminal links forced a two-item menu on every click.

This adds a Browser setting, Open links in: Your default browser (the default, so nothing changes until it is switched) or T3 Code. With T3 Code selected, links clicked beside a thread open as a tab in the in-app browser. Cmd/Ctrl-click on a chat link still opens the default browser as the way out, mirroring how change-request links already treat the modifier; the right-click menu keeps offering both explicitly.

What follows the setting:

  • Markdown links in the chat (and PR descriptions, which reuse the same renderer)
  • Hyperlinks in the terminal — these no longer show a menu on every click; the setting decides
  • CI check Details in the PR panel
  • A PR link that has no checked-out project to open in the panel
  • The repository a publish just created

What stays on the system browser, because the control names its destination: Open on GitHub, the external-link icons in the PR header, the in-app browser's own "open in system browser" escape hatch, release notes. Non-web schemes (mailto:, vscode://) always go to the shell. The hosted web app and mobile have no in-app browser, so the setting does not render there and links behave as before.

Implementation: one pure resolver (browser/browserLinkTarget.ts) turns preference + click into a target, and a useOpenLink(threadRef) hook wraps it for the non-markdown buttons so every entry point makes the same decision.

Setting

Open links in setting

Chat link with T3 Code selected

Chat link opened in the in-app browser

Terminal link with T3 Code selected (no menu)

Terminal link opened in the in-app browser

Verified in the desktop app: chat link plain-click → in-app tab; Cmd-click → default browser; terminal link → in-app tab, no menu; a foreign PR link → in-app tab; setting back to default → unchanged pre-feature behaviour.

Claude Fable 5 via Claude Code.


Note

Medium Risk
Wide user-facing link routing change across chat, terminal, and PR surfaces, though default system preserves prior behavior until users opt in; in-app open paths add failure/fallback complexity beside threads.

Overview
Adds a client setting Open links in (browserLinkTarget: system by default, or app on desktop) under Integrations → Browser, with search, dirty-state labels, and restore-defaults support.

Central behavior:resolveLinkTarget and useOpenLink(threadRef) decide whether http(s) links beside a thread open in the in-app preview or via shell.openExternal, with in-app failure falling back to the system browser. Cmd/Ctrl on chat links still forces the system browser; non-web schemes always go to the shell; clients without an in-app browser ignore app.

Where it applies: chat markdown (including PR bodies via ChatMarkdown), terminal hyperlinks (the per-click preview/browser context menu is removed—the setting drives the path), CI check details, publish-repo links, and PR URLs that cannot open in-panel. PR UI passes threadRef through markdown editors and panels so previews and inline links follow the preference. Explicit “open on host” controls in the timeline still use the shell.

Contracts:BrowserLinkTarget and browserLinkTarget added to client settings schema and patches.

Reviewed by Cursor Bugbot for commit 69ee98f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add setting to open links in default browser or in-app browser

  • Adds a BrowserLinkTarget client setting (system | in-app, default system) and an "Open links in" control in Integrations settings, with search and dirty-tracking support
  • Introduces resolveLinkTarget in browserLinkTarget.ts and a shared useOpenLink hook that route link clicks based on the hydrated preference, falling back to the system browser for Cmd/Ctrl clicks, unsupported runtimes, and non-HTTP(S) URLs
  • Wires the active thread reference through chat markdown, terminal links, pull-request detail/timeline, and Git action controls so in-app preview opening has the context it needs
  • Removes the old terminal-link context menu; terminal links now open directly via the setting-aware path with system-browser fallback on failure
  • Risk: openTerminalLinkInPreview removes TerminalLinkContextMenuShowError and the per-click preview/browser choice; any out-of-tree callers of that error class or the context-menu flow will break. Non-desktop clients ignore the in-app choice and always use the system browser.

Macroscope summarized 69ee98f.

…Code
New Browser setting, "Open links in", with the default browser as the
default so nothing changes until it is switched. When set to T3 Code,
links clicked in chat markdown and terminal output open in a tab of the
in-app browser beside the thread. Cmd/Ctrl-click on a chat link still
opens the default browser as the way out; the right-click menu keeps
offering both explicitly.
Terminal links no longer show the two-item menu on every click: the
setting decides, and URLs the in-app browser cannot load still fall
back to the default browser.
…etting
CI check details, a pull request that has no checked-out project to open
in the panel, and the repository a publish just created are ordinary
links beside the thread, so they follow "Open links in" like chat
links do. Controls that name their destination — Open on GitHub, the
external-link icons, the in-app browser's own escape hatch — keep
opening the system browser.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.3 KiB−175 B (−1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.6 KiB6.4 KiB−175 B (−2.6%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB55.5 KiB−1.5 KiB (−2.6%)66.4 KiB
CodexLive turn messages109−1 (−10.0%)21
ClaudeTotal thread wire13.3 KiB13.5 KiB+159 B (+1.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.6 KiB+162 B (+2.5%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB57.8 KiB+1.4 KiB (+2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: 1aa44a0 · PR result: 69ee98f · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

Comment threadapps/web/src/lib/openPullRequestLink.ts Outdated
Comment threadapps/web/src/components/ChatMarkdown.tsx
Comment threadapps/web/src/browser/useOpenLink.ts Outdated
Comment threadapps/web/src/browser/useOpenLink.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c73d67c. Configure here.

Comment threadapps/web/src/browser/useOpenLink.ts Outdated
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a cross-surface link-routing feature and changes terminal links from a per-click choice menu to direct system-browser opening by default. Because the default product behavior changes and the implementation spans chat, terminal, pull-request, and Git-action paths, human review is warranted.

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

Read the preference through a subscription (chat) or after settings
hydrate (terminal, buttons) so a persisted "T3 Code" applies right
after launch. An in-app open that fails falls back to the system
browser instead of leaving the intercepted click dead, in-app opens
through useOpenLink record the visit like chat and terminal do,
useOpenPrLink forwards the target thread it was given, and pull request
bodies and comments pass the panel's thread through so their links
follow the setting too.
@juliusmarminge
juliusmarminge merged commit 18062da into mainSep 3, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the browser-link-target branch September 3, 2026 04:56
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Coriou added a commit to Coriou/txcode that referenced this pull request Sep 3, 2026
…y gate)
Upstream pingdotgg#9339 (browser link target label) and the pull-request diff
credential error (+test) added user-facing 'T3 Code' strings that
auto-merged into fork surfaces. Ported to 'Tx Code' per the fork
identity policy; docs/user/project-settings.md keeps upstream voice by
convention.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): choose whether links open in the default browser or in T3 Code - #9339

Merged
juliusmarminge merged 4 commits into
mainfrom
browser-link-target
Sep 3, 2026
Merged

feat(web): choose whether links open in the default browser or in T3 Code#9339
juliusmarminge merged 4 commits into
mainfrom
browser-link-target

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

The in-app browser has become a general-purpose browser (profiles, cookie import), but every link a user clicks still went to the OS browser. The only way to get one into the in-app browser was the right-click menu on chat links, and terminal links forced a two-item menu on every click.

This adds a Browser setting, Open links in: Your default browser (the default, so nothing changes until it is switched) or T3 Code. With T3 Code selected, links clicked beside a thread open as a tab in the in-app browser. Cmd/Ctrl-click on a chat link still opens the default browser as the way out, mirroring how change-request links already treat the modifier; the right-click menu keeps offering both explicitly.

What follows the setting:

  • Markdown links in the chat (and PR descriptions, which reuse the same renderer)
  • Hyperlinks in the terminal — these no longer show a menu on every click; the setting decides
  • CI check Details in the PR panel
  • A PR link that has no checked-out project to open in the panel
  • The repository a publish just created

What stays on the system browser, because the control names its destination: Open on GitHub, the external-link icons in the PR header, the in-app browser's own "open in system browser" escape hatch, release notes. Non-web schemes (mailto:, vscode://) always go to the shell. The hosted web app and mobile have no in-app browser, so the setting does not render there and links behave as before.

Implementation: one pure resolver (browser/browserLinkTarget.ts) turns preference + click into a target, and a useOpenLink(threadRef) hook wraps it for the non-markdown buttons so every entry point makes the same decision.

Setting

Open links in setting

Chat link with T3 Code selected

Chat link opened in the in-app browser

Terminal link with T3 Code selected (no menu)

Terminal link opened in the in-app browser

Verified in the desktop app: chat link plain-click → in-app tab; Cmd-click → default browser; terminal link → in-app tab, no menu; a foreign PR link → in-app tab; setting back to default → unchanged pre-feature behaviour.

Claude Fable 5 via Claude Code.


Note

Medium Risk
Wide user-facing link routing change across chat, terminal, and PR surfaces, though default system preserves prior behavior until users opt in; in-app open paths add failure/fallback complexity beside threads.

Overview
Adds a client setting Open links in (browserLinkTarget: system by default, or app on desktop) under Integrations → Browser, with search, dirty-state labels, and restore-defaults support.

Central behavior:resolveLinkTarget and useOpenLink(threadRef) decide whether http(s) links beside a thread open in the in-app preview or via shell.openExternal, with in-app failure falling back to the system browser. Cmd/Ctrl on chat links still forces the system browser; non-web schemes always go to the shell; clients without an in-app browser ignore app.

Where it applies: chat markdown (including PR bodies via ChatMarkdown), terminal hyperlinks (the per-click preview/browser context menu is removed—the setting drives the path), CI check details, publish-repo links, and PR URLs that cannot open in-panel. PR UI passes threadRef through markdown editors and panels so previews and inline links follow the preference. Explicit “open on host” controls in the timeline still use the shell.

Contracts:BrowserLinkTarget and browserLinkTarget added to client settings schema and patches.

Reviewed by Cursor Bugbot for commit 69ee98f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add setting to open links in default browser or in-app browser

  • Adds a BrowserLinkTarget client setting (system | in-app, default system) and an "Open links in" control in Integrations settings, with search and dirty-tracking support
  • Introduces resolveLinkTarget in browserLinkTarget.ts and a shared useOpenLink hook that route link clicks based on the hydrated preference, falling back to the system browser for Cmd/Ctrl clicks, unsupported runtimes, and non-HTTP(S) URLs
  • Wires the active thread reference through chat markdown, terminal links, pull-request detail/timeline, and Git action controls so in-app preview opening has the context it needs
  • Removes the old terminal-link context menu; terminal links now open directly via the setting-aware path with system-browser fallback on failure
  • Risk: openTerminalLinkInPreview removes TerminalLinkContextMenuShowError and the per-click preview/browser choice; any out-of-tree callers of that error class or the context-menu flow will break. Non-desktop clients ignore the in-app choice and always use the system browser.

Macroscope summarized 69ee98f.

…Code
New Browser setting, "Open links in", with the default browser as the
default so nothing changes until it is switched. When set to T3 Code,
links clicked in chat markdown and terminal output open in a tab of the
in-app browser beside the thread. Cmd/Ctrl-click on a chat link still
opens the default browser as the way out; the right-click menu keeps
offering both explicitly.
Terminal links no longer show the two-item menu on every click: the
setting decides, and URLs the in-app browser cannot load still fall
back to the default browser.
…etting
CI check details, a pull request that has no checked-out project to open
in the panel, and the repository a publish just created are ordinary
links beside the thread, so they follow "Open links in" like chat
links do. Controls that name their destination — Open on GitHub, the
external-link icons, the in-app browser's own escape hatch — keep
opening the system browser.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.3 KiB−175 B (−1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.6 KiB6.4 KiB−175 B (−2.6%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB55.5 KiB−1.5 KiB (−2.6%)66.4 KiB
CodexLive turn messages109−1 (−10.0%)21
ClaudeTotal thread wire13.3 KiB13.5 KiB+159 B (+1.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.6 KiB+162 B (+2.5%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB57.8 KiB+1.4 KiB (+2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: 1aa44a0 · PR result: 69ee98f · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

Comment threadapps/web/src/lib/openPullRequestLink.ts Outdated
Comment threadapps/web/src/components/ChatMarkdown.tsx
Comment threadapps/web/src/browser/useOpenLink.ts Outdated
Comment threadapps/web/src/browser/useOpenLink.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c73d67c. Configure here.

Comment threadapps/web/src/browser/useOpenLink.ts Outdated
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a cross-surface link-routing feature and changes terminal links from a per-click choice menu to direct system-browser opening by default. Because the default product behavior changes and the implementation spans chat, terminal, pull-request, and Git-action paths, human review is warranted.

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

Read the preference through a subscription (chat) or after settings
hydrate (terminal, buttons) so a persisted "T3 Code" applies right
after launch. An in-app open that fails falls back to the system
browser instead of leaving the intercepted click dead, in-app opens
through useOpenLink record the visit like chat and terminal do,
useOpenPrLink forwards the target thread it was given, and pull request
bodies and comments pass the panel's thread through so their links
follow the setting too.
@juliusmarminge
juliusmarminge merged commit 18062da into mainSep 3, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the browser-link-target branch September 3, 2026 04:56
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Coriou added a commit to Coriou/txcode that referenced this pull request Sep 3, 2026
…y gate)
Upstream pingdotgg#9339 (browser link target label) and the pull-request diff
credential error (+test) added user-facing 'T3 Code' strings that
auto-merged into fork surfaces. Ported to 'Tx Code' per the fork
identity policy; docs/user/project-settings.md keeps upstream voice by
convention.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(web): choose whether links open in the default browser or in T3 Code - #9339

Merged
juliusmarminge merged 4 commits into
mainfrom
browser-link-target
Sep 3, 2026
Merged

feat(web): choose whether links open in the default browser or in T3 Code#9339
juliusmarminge merged 4 commits into
mainfrom
browser-link-target

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

The in-app browser has become a general-purpose browser (profiles, cookie import), but every link a user clicks still went to the OS browser. The only way to get one into the in-app browser was the right-click menu on chat links, and terminal links forced a two-item menu on every click.

This adds a Browser setting, Open links in: Your default browser (the default, so nothing changes until it is switched) or T3 Code. With T3 Code selected, links clicked beside a thread open as a tab in the in-app browser. Cmd/Ctrl-click on a chat link still opens the default browser as the way out, mirroring how change-request links already treat the modifier; the right-click menu keeps offering both explicitly.

What follows the setting:

  • Markdown links in the chat (and PR descriptions, which reuse the same renderer)
  • Hyperlinks in the terminal — these no longer show a menu on every click; the setting decides
  • CI check Details in the PR panel
  • A PR link that has no checked-out project to open in the panel
  • The repository a publish just created

What stays on the system browser, because the control names its destination: Open on GitHub, the external-link icons in the PR header, the in-app browser's own "open in system browser" escape hatch, release notes. Non-web schemes (mailto:, vscode://) always go to the shell. The hosted web app and mobile have no in-app browser, so the setting does not render there and links behave as before.

Implementation: one pure resolver (browser/browserLinkTarget.ts) turns preference + click into a target, and a useOpenLink(threadRef) hook wraps it for the non-markdown buttons so every entry point makes the same decision.

Setting

Open links in setting

Chat link with T3 Code selected

Chat link opened in the in-app browser

Terminal link with T3 Code selected (no menu)

Terminal link opened in the in-app browser

Verified in the desktop app: chat link plain-click → in-app tab; Cmd-click → default browser; terminal link → in-app tab, no menu; a foreign PR link → in-app tab; setting back to default → unchanged pre-feature behaviour.

Claude Fable 5 via Claude Code.


Note

Medium Risk
Wide user-facing link routing change across chat, terminal, and PR surfaces, though default system preserves prior behavior until users opt in; in-app open paths add failure/fallback complexity beside threads.

Overview
Adds a client setting Open links in (browserLinkTarget: system by default, or app on desktop) under Integrations → Browser, with search, dirty-state labels, and restore-defaults support.

Central behavior:resolveLinkTarget and useOpenLink(threadRef) decide whether http(s) links beside a thread open in the in-app preview or via shell.openExternal, with in-app failure falling back to the system browser. Cmd/Ctrl on chat links still forces the system browser; non-web schemes always go to the shell; clients without an in-app browser ignore app.

Where it applies: chat markdown (including PR bodies via ChatMarkdown), terminal hyperlinks (the per-click preview/browser context menu is removed—the setting drives the path), CI check details, publish-repo links, and PR URLs that cannot open in-panel. PR UI passes threadRef through markdown editors and panels so previews and inline links follow the preference. Explicit “open on host” controls in the timeline still use the shell.

Contracts:BrowserLinkTarget and browserLinkTarget added to client settings schema and patches.

Reviewed by Cursor Bugbot for commit 69ee98f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add setting to open links in default browser or in-app browser

  • Adds a BrowserLinkTarget client setting (system | in-app, default system) and an "Open links in" control in Integrations settings, with search and dirty-tracking support
  • Introduces resolveLinkTarget in browserLinkTarget.ts and a shared useOpenLink hook that route link clicks based on the hydrated preference, falling back to the system browser for Cmd/Ctrl clicks, unsupported runtimes, and non-HTTP(S) URLs
  • Wires the active thread reference through chat markdown, terminal links, pull-request detail/timeline, and Git action controls so in-app preview opening has the context it needs
  • Removes the old terminal-link context menu; terminal links now open directly via the setting-aware path with system-browser fallback on failure
  • Risk: openTerminalLinkInPreview removes TerminalLinkContextMenuShowError and the per-click preview/browser choice; any out-of-tree callers of that error class or the context-menu flow will break. Non-desktop clients ignore the in-app choice and always use the system browser.

Macroscope summarized 69ee98f.

…Code
New Browser setting, "Open links in", with the default browser as the
default so nothing changes until it is switched. When set to T3 Code,
links clicked in chat markdown and terminal output open in a tab of the
in-app browser beside the thread. Cmd/Ctrl-click on a chat link still
opens the default browser as the way out; the right-click menu keeps
offering both explicitly.
Terminal links no longer show the two-item menu on every click: the
setting decides, and URLs the in-app browser cannot load still fall
back to the default browser.
…etting
CI check details, a pull request that has no checked-out project to open
in the panel, and the repository a publish just created are ordinary
links beside the thread, so they follow "Open links in" like chat
links do. Controls that name their destination — Open on GitHub, the
external-link icons, the in-app browser's own escape hatch — keep
opening the system browser.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.3 KiB−175 B (−1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.6 KiB6.4 KiB−175 B (−2.6%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB55.5 KiB−1.5 KiB (−2.6%)66.4 KiB
CodexLive turn messages109−1 (−10.0%)21
ClaudeTotal thread wire13.3 KiB13.5 KiB+159 B (+1.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.6 KiB+162 B (+2.5%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB57.8 KiB+1.4 KiB (+2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: 1aa44a0 · PR result: 69ee98f · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

Comment threadapps/web/src/lib/openPullRequestLink.ts Outdated
Comment threadapps/web/src/components/ChatMarkdown.tsx
Comment threadapps/web/src/browser/useOpenLink.ts Outdated
Comment threadapps/web/src/browser/useOpenLink.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c73d67c. Configure here.

Comment threadapps/web/src/browser/useOpenLink.ts Outdated
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a cross-surface link-routing feature and changes terminal links from a per-click choice menu to direct system-browser opening by default. Because the default product behavior changes and the implementation spans chat, terminal, pull-request, and Git-action paths, human review is warranted.

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

Read the preference through a subscription (chat) or after settings
hydrate (terminal, buttons) so a persisted "T3 Code" applies right
after launch. An in-app open that fails falls back to the system
browser instead of leaving the intercepted click dead, in-app opens
through useOpenLink record the visit like chat and terminal do,
useOpenPrLink forwards the target thread it was given, and pull request
bodies and comments pass the panel's thread through so their links
follow the setting too.
@juliusmarminge
juliusmarminge merged commit 18062da into mainSep 3, 2026
28 checks passed
@juliusmarminge
juliusmarminge deleted the browser-link-target branch September 3, 2026 04:56
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Coriou added a commit to Coriou/txcode that referenced this pull request Sep 3, 2026
…y gate)
Upstream pingdotgg#9339 (browser link target label) and the pull-request diff
credential error (+test) added user-facing 'T3 Code' strings that
auto-merged into fork surfaces. Ported to 'Tx Code' per the fork
identity policy; docs/user/project-settings.md keeps upstream voice by
convention.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L100-499 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge