From 6388cbaf9261a71ec8e5e90d09ccb01450201dff Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 9 Mar 2026 08:25:34 -0500 Subject: [PATCH] fix(app): remove oc-1 theme --- packages/app/e2e/settings/settings.spec.ts | 36 +++++++++++++++++ packages/app/public/oc-theme-preload.js | 10 ++++- packages/app/src/theme-preload.test.ts | 46 ++++++++++++++++++++++ packages/ui/src/theme/context.tsx | 45 ++++++++++++++++----- packages/ui/src/theme/default-themes.ts | 3 -- packages/ui/src/theme/index.ts | 1 - packages/ui/src/theme/themes/oc-1.json | 35 ---------------- 7 files changed, 126 insertions(+), 50 deletions(-) create mode 100644 packages/app/src/theme-preload.test.ts delete mode 100644 packages/ui/src/theme/themes/oc-1.json 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, - 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 = { "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" - } - } -}