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 d0678dc5369..1c3054b60a3 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 39f397ac466..0384c602450 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 f05a28e1488..eefb145ad47 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) } })