Skip to content

Commit 676873e

Browse files
author
Claude Agent
committed
feat(desktop): add setting to open links in external browser
Adds a new setting in Settings → General → Browser that allows users to choose whether clicking links should open them in the system's default browser (default) or within the desktop app. This is useful for users who prefer to use their browser's features like extensions, bookmarks, or separate window management. Closes #10309
1 parent 8a216a6 commit 676873e

File tree

3 files changed

+76
-6
lines changed

3 files changed

+76
-6
lines changed

packages/app/src/components/settings-general.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import { Component, createMemo, type JSX } from "solid-js"
1+
import { Component, createMemo, Show, type JSX } from "solid-js"
22
import { Select } from "@opencode-ai/ui/select"
33
import { Switch } from "@opencode-ai/ui/switch"
44
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
55
import { useLanguage } from "@/context/language"
66
import { useSettings, monoFontFamily } from "@/context/settings"
7+
import { usePlatform } from "@/context/platform"
78
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
89
import { Link } from "./link"
910

1011
export const SettingsGeneral: Component = () => {
1112
const theme = useTheme()
1213
const language = useLanguage()
1314
const settings = useSettings()
15+
const platform = usePlatform()
1416

1517
const themeOptions = createMemo(() =>
1618
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
@@ -272,6 +274,25 @@ export const SettingsGeneral: Component = () => {
272274
</SettingsRow>
273275
</div>
274276
</div>
277+
278+
{/* Browser Section - Desktop only */}
279+
<Show when={platform.platform === "desktop"}>
280+
<div class="flex flex-col gap-1">
281+
<h3 class="text-14-medium text-text-strong pb-2">Browser</h3>
282+
283+
<div class="bg-surface-raised-base px-4 rounded-lg">
284+
<SettingsRow
285+
title="Open Links in External Browser"
286+
description="When enabled, clicking links will open them in your default system browser instead of within the app."
287+
>
288+
<Switch
289+
checked={settings.browser.openLinksExternally()}
290+
onChange={(checked) => settings.browser.setOpenLinksExternally(checked)}
291+
/>
292+
</SettingsRow>
293+
</div>
294+
</div>
295+
</Show>
275296
</div>
276297
</div>
277298
)

packages/app/src/context/settings.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export interface SoundSettings {
1515
errors: string
1616
}
1717

18+
export interface BrowserSettings {
19+
openLinksExternally: boolean
20+
}
21+
1822
export interface Settings {
1923
general: {
2024
autoSave: boolean
@@ -29,6 +33,7 @@ export interface Settings {
2933
}
3034
notifications: NotificationSettings
3135
sounds: SoundSettings
36+
browser: BrowserSettings
3237
}
3338

3439
const defaultSettings: Settings = {
@@ -53,6 +58,9 @@ const defaultSettings: Settings = {
5358
permissions: "staplebops-02",
5459
errors: "nope-03",
5560
},
61+
browser: {
62+
openLinksExternally: true,
63+
},
5664
}
5765

5866
const monoFallback =
@@ -153,6 +161,14 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
153161
setStore("sounds", "errors", value)
154162
},
155163
},
164+
browser: {
165+
openLinksExternally: createMemo(
166+
() => store.browser?.openLinksExternally ?? defaultSettings.browser.openLinksExternally,
167+
),
168+
setOpenLinksExternally(value: boolean) {
169+
setStore("browser", "openLinksExternally", value)
170+
},
171+
},
156172
}
157173
},
158174
})

packages/desktop/src/index.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ window.getComputedStyle = ((elt: Element, pseudoElt?: string | null) => {
4141

4242
let update: Update | null = null
4343

44+
// Settings key for reading browser preferences
45+
const SETTINGS_KEY = "settings.v3"
46+
47+
// Read browser settings from store to check if links should open externally
48+
async function shouldOpenLinksExternally(): Promise<boolean> {
49+
try {
50+
const store = await Store.load("opencode.global.dat")
51+
const settings = await store.get(SETTINGS_KEY)
52+
if (settings && typeof settings === "object" && "browser" in (settings as object)) {
53+
const browser = (settings as { browser?: { openLinksExternally?: boolean } }).browser
54+
return browser?.openLinksExternally !== false // Default to true
55+
}
56+
} catch {
57+
// Ignore errors, default to true
58+
}
59+
return true // Default: open links externally
60+
}
61+
4462
const createPlatform = (password: Accessor<string | null>): Platform => ({
4563
platform: "desktop",
4664
os: (() => {
@@ -329,11 +347,26 @@ render(() => {
329347
const platform = createPlatform(() => serverPassword())
330348

331349
function handleClick(e: MouseEvent) {
332-
const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null
333-
if (link?.href) {
334-
e.preventDefault()
335-
platform.openLink(link.href)
336-
}
350+
const link = (e.target as HTMLElement).closest("a") as HTMLAnchorElement | null
351+
if (!link?.href) return
352+
353+
// Check if it's an external link (http/https)
354+
const isExternal = link.href.startsWith("http://") || link.href.startsWith("https://")
355+
if (!isExternal) return
356+
357+
// MUST preventDefault immediately (before any async), otherwise browser handles it
358+
e.preventDefault()
359+
const url = link.href
360+
361+
// Check user preference and handle accordingly
362+
shouldOpenLinksExternally().then((openExternally) => {
363+
if (openExternally) {
364+
platform.openLink(url)
365+
} else {
366+
// Open in app by navigating
367+
window.location.href = url
368+
}
369+
})
337370
}
338371

339372
onMount(() => {

0 commit comments

Comments
 (0)