Browse Source

fix(app): remove oc-1 theme

Adam 1 month ago
parent
commit
6388cbaf92

+ 36 - 0
packages/app/e2e/settings/settings.spec.ts

@@ -116,6 +116,42 @@ test("changing theme persists in localStorage", async ({ page, gotoSession }) =>
   expect(dataTheme).toBe(storedThemeId)
   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 }) => {
 test("changing font persists in localStorage and updates CSS variable", async ({ page, gotoSession }) => {
   await gotoSession()
   await gotoSession()
 
 

+ 9 - 1
packages/app/public/oc-theme-preload.js

@@ -1,5 +1,13 @@
 ;(function () {
 ;(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 scheme = localStorage.getItem("opencode-color-scheme") || "system"
   var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
   var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)

+ 46 - 0
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;")
+  })
+})

+ 35 - 10
packages/ui/src/theme/context.tsx

@@ -16,6 +16,15 @@ const STORAGE_KEYS = {
 
 
 const THEME_STYLE_ID = "oc-theme"
 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 {
 function ensureThemeStyleElement(): HTMLStyleElement {
   const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
   const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
   if (existing) return existing
   if (existing) return existing
@@ -71,7 +80,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
   init: (props: { defaultTheme?: string }) => {
   init: (props: { defaultTheme?: string }) => {
     const [store, setStore] = createStore({
     const [store, setStore] = createStore({
       themes: DEFAULT_THEMES as Record<string, DesktopTheme>,
       themes: DEFAULT_THEMES as Record<string, DesktopTheme>,
-      themeId: props.defaultTheme ?? "oc-2",
+      themeId: normalize(props.defaultTheme) ?? "oc-2",
       colorScheme: "system" as ColorScheme,
       colorScheme: "system" as ColorScheme,
       mode: getSystemMode(),
       mode: getSystemMode(),
       previewThemeId: null as string | null,
       previewThemeId: null as string | null,
@@ -89,9 +98,14 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
       onCleanup(() => mediaQuery.removeEventListener("change", handler))
       onCleanup(() => mediaQuery.removeEventListener("change", handler))
 
 
       const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME_ID)
       const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME_ID)
+      const themeId = normalize(savedTheme)
       const savedScheme = localStorage.getItem(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null
       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) {
       if (savedScheme) {
         setStore("colorScheme", savedScheme)
         setStore("colorScheme", savedScheme)
@@ -113,14 +127,23 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
     })
     })
 
 
     const setTheme = (id: string) => {
     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) {
       if (!theme) {
         console.warn(`Theme "${id}" not found`)
         console.warn(`Theme "${id}" not found`)
         return
         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) => {
     const setColorScheme = (scheme: ColorScheme) => {
@@ -138,15 +161,17 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
       setColorScheme,
       setColorScheme,
       registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme),
       registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme),
       previewTheme: (id: string) => {
       previewTheme: (id: string) => {
-        const theme = store.themes[id]
+        const next = normalize(id)
+        if (!next) return
+        const theme = store.themes[next]
         if (!theme) return
         if (!theme) return
-        setStore("previewThemeId", id)
+        setStore("previewThemeId", next)
         const previewMode = store.previewScheme
         const previewMode = store.previewScheme
           ? store.previewScheme === "system"
           ? store.previewScheme === "system"
             ? getSystemMode()
             ? getSystemMode()
             : store.previewScheme
             : store.previewScheme
           : store.mode
           : store.mode
-        applyThemeCss(theme, id, previewMode)
+        applyThemeCss(theme, next, previewMode)
       },
       },
       previewColorScheme: (scheme: ColorScheme) => {
       previewColorScheme: (scheme: ColorScheme) => {
         setStore("previewScheme", scheme)
         setStore("previewScheme", scheme)

+ 0 - 3
packages/ui/src/theme/default-themes.ts

@@ -1,5 +1,4 @@
 import type { DesktopTheme } from "./types"
 import type { DesktopTheme } from "./types"
-import oc1ThemeJson from "./themes/oc-1.json"
 import oc2ThemeJson from "./themes/oc-2.json"
 import oc2ThemeJson from "./themes/oc-2.json"
 import tokyoThemeJson from "./themes/tokyonight.json"
 import tokyoThemeJson from "./themes/tokyonight.json"
 import draculaThemeJson from "./themes/dracula.json"
 import draculaThemeJson from "./themes/dracula.json"
@@ -16,7 +15,6 @@ import carbonfoxThemeJson from "./themes/carbonfox.json"
 import gruvboxThemeJson from "./themes/gruvbox.json"
 import gruvboxThemeJson from "./themes/gruvbox.json"
 import auraThemeJson from "./themes/aura.json"
 import auraThemeJson from "./themes/aura.json"
 
 
-export const oc1Theme = oc1ThemeJson as DesktopTheme
 export const oc2Theme = oc2ThemeJson as DesktopTheme
 export const oc2Theme = oc2ThemeJson as DesktopTheme
 export const tokyonightTheme = tokyoThemeJson as DesktopTheme
 export const tokyonightTheme = tokyoThemeJson as DesktopTheme
 export const draculaTheme = draculaThemeJson 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> = {
 export const DEFAULT_THEMES: Record<string, DesktopTheme> = {
   "oc-2": oc2Theme,
   "oc-2": oc2Theme,
-  "oc-1": oc1Theme,
   aura: auraTheme,
   aura: auraTheme,
   ayu: ayuTheme,
   ayu: ayuTheme,
   carbonfox: carbonfoxTheme,
   carbonfox: carbonfoxTheme,

+ 0 - 1
packages/ui/src/theme/index.ts

@@ -35,7 +35,6 @@ export { ThemeProvider, useTheme, type ColorScheme } from "./context"
 
 
 export {
 export {
   DEFAULT_THEMES,
   DEFAULT_THEMES,
-  oc1Theme,
   oc2Theme,
   oc2Theme,
   tokyonightTheme,
   tokyonightTheme,
   draculaTheme,
   draculaTheme,

+ 0 - 35
packages/ui/src/theme/themes/oc-1.json

@@ -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"
-    }
-  }
-}