From 2f5a238b511e3ac4d8235da4d6e7fdbaf06c9b84 Mon Sep 17 00:00:00 2001
From: adamelmore <2363879+adamdottv@users.noreply.github.com>
Date: Tue, 27 Jan 2026 08:30:23 -0600
Subject: [PATCH] feat(app): update settings in general settings
---
.../app/src/components/settings-general.tsx | 124 +++++++++++++++---
packages/app/src/context/settings.tsx | 12 ++
packages/app/src/i18n/en.ts | 9 ++
packages/app/src/pages/layout.tsx | 23 +++-
4 files changed, 148 insertions(+), 20 deletions(-)
diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx
index 57efcfdfa..180a99c73 100644
--- a/packages/app/src/components/settings-general.tsx
+++ b/packages/app/src/components/settings-general.tsx
@@ -1,8 +1,12 @@
import { Component, createMemo, type JSX } from "solid-js"
+import { createStore } from "solid-js/store"
+import { Button } from "@opencode-ai/ui/button"
import { Select } from "@opencode-ai/ui/select"
import { Switch } from "@opencode-ai/ui/switch"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
+import { showToast } from "@opencode-ai/ui/toast"
import { useLanguage } from "@/context/language"
+import { usePlatform } from "@/context/platform"
import { useSettings, monoFontFamily } from "@/context/settings"
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "./link"
@@ -29,8 +33,67 @@ const playDemoSound = (src: string) => {
export const SettingsGeneral: Component = () => {
const theme = useTheme()
const language = useLanguage()
+ const platform = usePlatform()
const settings = useSettings()
+ const [store, setStore] = createStore({
+ checking: false,
+ })
+
+ const check = () => {
+ if (!platform.checkUpdate) return
+ setStore("checking", true)
+
+ void platform
+ .checkUpdate()
+ .then((result) => {
+ if (!result.updateAvailable) {
+ showToast({
+ variant: "success",
+ icon: "circle-check",
+ title: language.t("settings.updates.toast.latest.title"),
+ description: language.t("settings.updates.toast.latest.description", { version: platform.version ?? "" }),
+ })
+ return
+ }
+
+ const actions =
+ platform.update && platform.restart
+ ? [
+ {
+ label: language.t("toast.update.action.installRestart"),
+ onClick: async () => {
+ await platform.update!()
+ await platform.restart!()
+ },
+ },
+ {
+ label: language.t("toast.update.action.notYet"),
+ onClick: "dismiss" as const,
+ },
+ ]
+ : [
+ {
+ label: language.t("toast.update.action.notYet"),
+ onClick: "dismiss" as const,
+ },
+ ]
+
+ showToast({
+ persistent: true,
+ icon: "download",
+ title: language.t("toast.update.title"),
+ description: language.t("toast.update.description", { version: result.version ?? "" }),
+ actions,
+ })
+ })
+ .catch((err: unknown) => {
+ const message = err instanceof Error ? err.message : String(err)
+ showToast({ title: language.t("common.requestFailed"), description: message })
+ })
+ .finally(() => setStore("checking", false))
+ }
+
const themeOptions = createMemo(() =>
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
)
@@ -208,23 +271,6 @@ export const SettingsGeneral: Component = () => {
- {/* Updates Section */}
-
-
{language.t("settings.general.section.updates")}
-
-
-
- settings.general.setReleaseNotes(checked)}
- />
-
-
-
-
{/* Sound effects Section */}
{language.t("settings.general.section.sounds")}
@@ -303,6 +349,50 @@ export const SettingsGeneral: Component = () => {
+
+ {/* Updates Section */}
+
+
{language.t("settings.general.section.updates")}
+
+
+
+ settings.updates.setStartup(checked)}
+ />
+
+
+
+ settings.general.setReleaseNotes(checked)}
+ />
+
+
+
+
+
+
+
)
diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx
index 67e907a63..19b3846f8 100644
--- a/packages/app/src/context/settings.tsx
+++ b/packages/app/src/context/settings.tsx
@@ -20,6 +20,9 @@ export interface Settings {
autoSave: boolean
releaseNotes: boolean
}
+ updates: {
+ startup: boolean
+ }
appearance: {
fontSize: number
font: string
@@ -37,6 +40,9 @@ const defaultSettings: Settings = {
autoSave: true,
releaseNotes: true,
},
+ updates: {
+ startup: true,
+ },
appearance: {
fontSize: 14,
font: "ibm-plex-mono",
@@ -104,6 +110,12 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setStore("general", "releaseNotes", value)
},
},
+ updates: {
+ startup: createMemo(() => store.updates?.startup ?? defaultSettings.updates.startup),
+ setStartup(value: boolean) {
+ setStore("updates", "startup", value)
+ },
+ },
appearance: {
fontSize: createMemo(() => store.appearance?.fontSize ?? defaultSettings.appearance.fontSize),
setFontSize(value: number) {
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index abbe497dc..8fb819798 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -540,6 +540,15 @@ export const dict = {
"settings.general.row.releaseNotes.title": "Release notes",
"settings.general.row.releaseNotes.description": "Show What's New popups after updates",
+
+ "settings.updates.row.startup.title": "Check for updates on startup",
+ "settings.updates.row.startup.description": "Automatically check for updates when OpenCode launches",
+ "settings.updates.row.check.title": "Check for updates",
+ "settings.updates.row.check.description": "Manually check for updates and install if available",
+ "settings.updates.action.checkNow": "Check now",
+ "settings.updates.action.checking": "Checking...",
+ "settings.updates.toast.latest.title": "You're up to date",
+ "settings.updates.toast.latest.description": "You're running the latest version of OpenCode.",
"font.option.ibmPlexMono": "IBM Plex Mono",
"font.option.cascadiaCode": "Cascadia Code",
"font.option.firaCode": "Fira Code",
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index 15557dedb..82a3fa6c9 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -332,6 +332,7 @@ export default function Layout(props: ParentProps) {
if (!platform.checkUpdate || !platform.update || !platform.restart) return
let toastId: number | undefined
+ let interval: ReturnType | undefined
async function pollUpdate() {
const { updateAvailable, version } = await platform.checkUpdate!()
@@ -358,9 +359,25 @@ export default function Layout(props: ParentProps) {
}
}
- pollUpdate()
- const interval = setInterval(pollUpdate, 10 * 60 * 1000)
- onCleanup(() => clearInterval(interval))
+ createEffect(() => {
+ if (!settings.ready()) return
+
+ if (!settings.updates.startup()) {
+ if (interval === undefined) return
+ clearInterval(interval)
+ interval = undefined
+ return
+ }
+
+ if (interval !== undefined) return
+ void pollUpdate()
+ interval = setInterval(pollUpdate, 10 * 60 * 1000)
+ })
+
+ onCleanup(() => {
+ if (interval === undefined) return
+ clearInterval(interval)
+ })
})
onMount(() => {