summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-09 08:25:34 -0500
committerAdam <[email protected]>2026-03-09 08:25:41 -0500
commit6388cbaf9261a71ec8e5e90d09ccb01450201dff (patch)
tree6a1a39c3270d92151fff12ccb268a566eafa4594 /packages
parent5cc61e1b536c047a9ff2b348d99711807c6c270f (diff)
downloadopencode-6388cbaf9261a71ec8e5e90d09ccb01450201dff.tar.gz
opencode-6388cbaf9261a71ec8e5e90d09ccb01450201dff.zip
fix(app): remove oc-1 theme
Diffstat (limited to 'packages')
-rw-r--r--packages/app/e2e/settings/settings.spec.ts36
-rw-r--r--packages/app/public/oc-theme-preload.js10
-rw-r--r--packages/app/src/theme-preload.test.ts46
-rw-r--r--packages/ui/src/theme/context.tsx45
-rw-r--r--packages/ui/src/theme/default-themes.ts3
-rw-r--r--packages/ui/src/theme/index.ts1
-rw-r--r--packages/ui/src/theme/themes/oc-1.json35
7 files changed, 126 insertions, 50 deletions
diff --git a/packages/app/e2e/settings/settings.spec.ts b/packages/app/e2e/settings/settings.spec.ts
index 42fe0b06c..f25e91a31 100644
--- a/packages/app/e2e/settings/settings.spec.ts
+++ b/packages/app/e2e/settings/settings.spec.ts
@@ -116,6 +116,42 @@ test("changing theme persists in localStorage", async ({ page, gotoSession }) =>
expect(dataTheme).toBe(storedThemeId)
})
+test("legacy oc-1 theme migrates to oc-2", async ({ page, gotoSession }) => {
+ await page.addInitScript(() => {
+ localStorage.setItem("opencode-theme-id", "oc-1")
+ localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
+ localStorage.setItem("opencode-theme-css-dark", "--background-base:#000;")
+ })
+
+ await gotoSession()
+
+ await expect(page.locator("html")).toHaveAttribute("data-theme", "oc-2")
+
+ await expect
+ .poll(async () => {
+ return await page.evaluate(() => {
+ return localStorage.getItem("opencode-theme-id")
+ })
+ })
+ .toBe("oc-2")
+
+ await expect
+ .poll(async () => {
+ return await page.evaluate(() => {
+ return localStorage.getItem("opencode-theme-css-light")
+ })
+ })
+ .toBeNull()
+
+ await expect
+ .poll(async () => {
+ return await page.evaluate(() => {
+ return localStorage.getItem("opencode-theme-css-dark")
+ })
+ })
+ .toBeNull()
+})
+
test("changing font persists in localStorage and updates CSS variable", async ({ page, gotoSession }) => {
await gotoSession()
diff --git a/packages/app/public/oc-theme-preload.js b/packages/app/public/oc-theme-preload.js
index 5851f756e..36fa5d726 100644
--- a/packages/app/public/oc-theme-preload.js
+++ b/packages/app/public/oc-theme-preload.js
@@ -1,5 +1,13 @@
;(function () {
- var themeId = localStorage.getItem("opencode-theme-id") || "oc-2"
+ var key = "opencode-theme-id"
+ var themeId = localStorage.getItem(key) || "oc-2"
+
+ if (themeId === "oc-1") {
+ themeId = "oc-2"
+ localStorage.setItem(key, themeId)
+ localStorage.removeItem("opencode-theme-css-light")
+ localStorage.removeItem("opencode-theme-css-dark")
+ }
var scheme = localStorage.getItem("opencode-color-scheme") || "system"
var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
diff --git a/packages/app/src/theme-preload.test.ts b/packages/app/src/theme-preload.test.ts
new file mode 100644
index 000000000..00d7da239
--- /dev/null
+++ b/packages/app/src/theme-preload.test.ts
@@ -0,0 +1,46 @@
+import { beforeEach, describe, expect, test } from "bun:test"
+
+const src = await Bun.file(new URL("../public/oc-theme-preload.js", import.meta.url)).text()
+
+const run = () => Function(src)()
+
+beforeEach(() => {
+ document.head.innerHTML = ""
+ document.documentElement.removeAttribute("data-theme")
+ document.documentElement.removeAttribute("data-color-scheme")
+ localStorage.clear()
+ Object.defineProperty(window, "matchMedia", {
+ value: () =>
+ ({
+ matches: false,
+ }) as MediaQueryList,
+ configurable: true,
+ })
+})
+
+describe("theme preload", () => {
+ test("migrates legacy oc-1 to oc-2 before mount", () => {
+ localStorage.setItem("opencode-theme-id", "oc-1")
+ localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
+ localStorage.setItem("opencode-theme-css-dark", "--background-base:#000;")
+
+ run()
+
+ expect(document.documentElement.dataset.theme).toBe("oc-2")
+ expect(document.documentElement.dataset.colorScheme).toBe("light")
+ expect(localStorage.getItem("opencode-theme-id")).toBe("oc-2")
+ expect(localStorage.getItem("opencode-theme-css-light")).toBeNull()
+ expect(localStorage.getItem("opencode-theme-css-dark")).toBeNull()
+ expect(document.getElementById("oc-theme-preload")).toBeNull()
+ })
+
+ test("keeps cached css for non-default themes", () => {
+ localStorage.setItem("opencode-theme-id", "nightowl")
+ localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
+
+ run()
+
+ expect(document.documentElement.dataset.theme).toBe("nightowl")
+ expect(document.getElementById("oc-theme-preload")?.textContent).toContain("--background-base:#fff;")
+ })
+})
diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx
index 600c6121c..cda967697 100644
--- a/packages/ui/src/theme/context.tsx
+++ b/packages/ui/src/theme/context.tsx
@@ -16,6 +16,15 @@ const STORAGE_KEYS = {
const THEME_STYLE_ID = "oc-theme"
+function normalize(id: string | null | undefined) {
+ return id === "oc-1" ? "oc-2" : id
+}
+
+function clear() {
+ localStorage.removeItem(STORAGE_KEYS.THEME_CSS_LIGHT)
+ localStorage.removeItem(STORAGE_KEYS.THEME_CSS_DARK)
+}
+
function ensureThemeStyleElement(): HTMLStyleElement {
const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
if (existing) return existing
@@ -71,7 +80,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
init: (props: { defaultTheme?: string }) => {
const [store, setStore] = createStore({
themes: DEFAULT_THEMES as Record<string, DesktopTheme>,
- themeId: props.defaultTheme ?? "oc-2",
+ themeId: normalize(props.defaultTheme) ?? "oc-2",
colorScheme: "system" as ColorScheme,
mode: getSystemMode(),
previewThemeId: null as string | null,
@@ -89,9 +98,14 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
onCleanup(() => mediaQuery.removeEventListener("change", handler))
const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME_ID)
+ const themeId = normalize(savedTheme)
const savedScheme = localStorage.getItem(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null
- if (savedTheme && store.themes[savedTheme]) {
- setStore("themeId", savedTheme)
+ if (themeId && store.themes[themeId]) {
+ setStore("themeId", themeId)
+ }
+ if (savedTheme && themeId && savedTheme !== themeId) {
+ localStorage.setItem(STORAGE_KEYS.THEME_ID, themeId)
+ clear()
}
if (savedScheme) {
setStore("colorScheme", savedScheme)
@@ -113,14 +127,23 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
})
const setTheme = (id: string) => {
- const theme = store.themes[id]
+ const next = normalize(id)
+ if (!next) {
+ console.warn(`Theme "${id}" not found`)
+ return
+ }
+ const theme = store.themes[next]
if (!theme) {
console.warn(`Theme "${id}" not found`)
return
}
- setStore("themeId", id)
- localStorage.setItem(STORAGE_KEYS.THEME_ID, id)
- cacheThemeVariants(theme, id)
+ setStore("themeId", next)
+ localStorage.setItem(STORAGE_KEYS.THEME_ID, next)
+ if (next === "oc-2") {
+ clear()
+ return
+ }
+ cacheThemeVariants(theme, next)
}
const setColorScheme = (scheme: ColorScheme) => {
@@ -138,15 +161,17 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
setColorScheme,
registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme),
previewTheme: (id: string) => {
- const theme = store.themes[id]
+ const next = normalize(id)
+ if (!next) return
+ const theme = store.themes[next]
if (!theme) return
- setStore("previewThemeId", id)
+ setStore("previewThemeId", next)
const previewMode = store.previewScheme
? store.previewScheme === "system"
? getSystemMode()
: store.previewScheme
: store.mode
- applyThemeCss(theme, id, previewMode)
+ applyThemeCss(theme, next, previewMode)
},
previewColorScheme: (scheme: ColorScheme) => {
setStore("previewScheme", scheme)
diff --git a/packages/ui/src/theme/default-themes.ts b/packages/ui/src/theme/default-themes.ts
index 657d21c3c..b3cfb340c 100644
--- a/packages/ui/src/theme/default-themes.ts
+++ b/packages/ui/src/theme/default-themes.ts
@@ -1,5 +1,4 @@
import type { DesktopTheme } from "./types"
-import oc1ThemeJson from "./themes/oc-1.json"
import oc2ThemeJson from "./themes/oc-2.json"
import tokyoThemeJson from "./themes/tokyonight.json"
import draculaThemeJson from "./themes/dracula.json"
@@ -16,7 +15,6 @@ import carbonfoxThemeJson from "./themes/carbonfox.json"
import gruvboxThemeJson from "./themes/gruvbox.json"
import auraThemeJson from "./themes/aura.json"
-export const oc1Theme = oc1ThemeJson as DesktopTheme
export const oc2Theme = oc2ThemeJson as DesktopTheme
export const tokyonightTheme = tokyoThemeJson as DesktopTheme
export const draculaTheme = draculaThemeJson as DesktopTheme
@@ -35,7 +33,6 @@ export const auraTheme = auraThemeJson as DesktopTheme
export const DEFAULT_THEMES: Record<string, DesktopTheme> = {
"oc-2": oc2Theme,
- "oc-1": oc1Theme,
aura: auraTheme,
ayu: ayuTheme,
carbonfox: carbonfoxTheme,
diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts
index 1e6fb7932..bfd55e60b 100644
--- a/packages/ui/src/theme/index.ts
+++ b/packages/ui/src/theme/index.ts
@@ -35,7 +35,6 @@ export { ThemeProvider, useTheme, type ColorScheme } from "./context"
export {
DEFAULT_THEMES,
- oc1Theme,
oc2Theme,
tokyonightTheme,
draculaTheme,
diff --git a/packages/ui/src/theme/themes/oc-1.json b/packages/ui/src/theme/themes/oc-1.json
deleted file mode 100644
index 7dec9cb80..000000000
--- a/packages/ui/src/theme/themes/oc-1.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "$schema": "https://opencode.ai/desktop-theme.json",
- "name": "OC-1",
- "id": "oc-1",
- "light": {
- "palette": {
- "neutral": "#8e8b8b",
- "ink": "#656363",
- "primary": "#dcde8d",
- "accent": "#fb4804",
- "success": "#12c905",
- "warning": "#ffdc17",
- "error": "#fc533a",
- "info": "#a753ae",
- "interactive": "#034cff",
- "diffAdd": "#9ff29a",
- "diffDelete": "#fc533a"
- }
- },
- "dark": {
- "palette": {
- "neutral": "#716c6b",
- "ink": "#b7b1b1",
- "primary": "#fab283",
- "accent": "#ffba92",
- "success": "#12c905",
- "warning": "#fcd53a",
- "error": "#fc533a",
- "info": "#edb2f1",
- "interactive": "#034cff",
- "diffAdd": "#c8ffc4",
- "diffDelete": "#fc533a"
- }
- }
-}