From 3d3114a728070b4715078a89e64b497bb27c7217 Mon Sep 17 00:00:00 2001 From: minhvu2212 Date: Thu, 15 Jan 2026 01:04:04 +0700 Subject: [PATCH] feat(desktop): open external links in system browser instead of webview - Add global click handler to intercept all tag clicks with http/https URLs - Add user setting to toggle between external browser and in-app navigation - Add command palette entry for quick toggling - Add UI toggle button in sidebar (desktop only) - Default behavior opens links in system browser for better UX This fixes the issue where clicking links in AI responses would navigate inside the Tauri webview, making it difficult for users to return to the app. --- packages/app/src/app.tsx | 2 +- packages/app/src/context/layout.tsx | 28 ++++++++++++++++++++++++++++ packages/app/src/pages/layout.tsx | 23 +++++++++++++++++++++++ packages/desktop/src/index.tsx | 21 ++++++++++++++++----- 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index d0678dc5369a..1c3054b60a34 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -33,7 +33,7 @@ const Loading = () =>
!x) }, }, + links: { + openExternally: createMemo(() => store.links?.openExternally ?? true), + setOpenExternally(value: boolean) { + if (!store.links) { + setStore("links", { openExternally: value }) + return + } + setStore("links", "openExternally", value) + }, + toggle() { + const current = store.links?.openExternally ?? true + if (!store.links) { + setStore("links", { openExternally: !current }) + return + } + setStore("links", "openExternally", !current) + }, + }, view(sessionKey: string) { touch(sessionKey) scroll.seed(sessionKey) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 39f397ac4669..0384c602450d 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -599,6 +599,12 @@ export default function Layout(props: ParentProps) { keybind: "mod+shift+t", onSelect: () => cycleTheme(1), }, + { + id: "links.toggle-external", + title: layout.links.openExternally() ? "Open links in app" : "Open links in browser", + category: "Settings", + onSelect: () => layout.links.toggle(), + }, ] for (const [id, definition] of availableThemeEntries()) { @@ -1236,6 +1242,23 @@ export default function Layout(props: ParentProps) { Share feedback + + + + +
) diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index f05a28e14887..eefb145ad474 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -292,12 +292,23 @@ root?.addEventListener("mousewheel", (e) => { e.stopPropagation() }) -// Handle external links - open in system browser instead of webview -document.addEventListener("click", (e) => { - const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null - if (link?.href) { +// Intercept all link clicks and open external URLs in system browser +root?.addEventListener("click", (e) => { + const anchor = (e.target as HTMLElement).closest("a") + if (!anchor) return + + const href = anchor.getAttribute("href") + if (!href) return + + // Only intercept external URLs (http/https) + if (href.startsWith("http://") || href.startsWith("https://")) { + // Check if user wants to open links externally (default: true) + const openExternally = window.__OPENCODE__?.openLinksExternally ?? true + if (!openExternally) return + e.preventDefault() - platform.openLink(link.href) + e.stopPropagation() + void shellOpen(href).catch(() => undefined) } })