summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/app/index.html31
-rw-r--r--packages/app/script/inject-theme-preload.ts18
-rw-r--r--packages/app/vite.js18
-rw-r--r--packages/desktop/index.html31
-rw-r--r--packages/ui/src/theme/context.tsx14
-rw-r--r--packages/ui/src/theme/preload.ts58
6 files changed, 85 insertions, 85 deletions
diff --git a/packages/app/index.html b/packages/app/index.html
index 4f003c05b..ea4237804 100644
--- a/packages/app/index.html
+++ b/packages/app/index.html
@@ -13,9 +13,36 @@
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
- <!-- Theme preload script - injected by Vite plugin to avoid FOUC -->
+ <!-- Theme preload script - applies cached theme to avoid FOUC -->
<script id="oc-theme-preload-script">
- /* THEME_PRELOAD_SCRIPT */
+ ;(function () {
+ var themeId = localStorage.getItem("opencode-theme-id")
+ if (!themeId) return
+
+ var scheme = localStorage.getItem("opencode-color-scheme") || "system"
+ var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
+ var mode = isDark ? "dark" : "light"
+
+ document.documentElement.dataset.theme = themeId
+ document.documentElement.dataset.colorScheme = mode
+
+ if (themeId === "oc-1") return
+
+ var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
+ if (css) {
+ var style = document.createElement("style")
+ style.id = "oc-theme-preload"
+ style.textContent =
+ ":root{color-scheme:" +
+ mode +
+ ";--text-mix-blend-mode:" +
+ (isDark ? "plus-lighter" : "multiply") +
+ ";" +
+ css +
+ "}"
+ document.head.appendChild(style)
+ }
+ })()
</script>
</head>
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
diff --git a/packages/app/script/inject-theme-preload.ts b/packages/app/script/inject-theme-preload.ts
deleted file mode 100644
index 511ab7a3b..000000000
--- a/packages/app/script/inject-theme-preload.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Injects the theme preload script into index.html.
- * Run this as part of the build process.
- */
-
-import { generatePreloadScript } from "@opencode-ai/ui/theme"
-
-const htmlPath = new URL("../index.html", import.meta.url).pathname
-const html = await Bun.file(htmlPath).text()
-
-const script = generatePreloadScript()
-const injectedHtml = html.replace(
- /<script id="oc-theme-preload-script">\s*\/\* THEME_PRELOAD_SCRIPT \*\/\s*<\/script>/,
- `<script id="oc-theme-preload-script">${script}</script>`,
-)
-
-await Bun.write(htmlPath, injectedHtml)
-console.log("Injected theme preload script into index.html")
diff --git a/packages/app/vite.js b/packages/app/vite.js
index 5ab368836..6b8fd6137 100644
--- a/packages/app/vite.js
+++ b/packages/app/vite.js
@@ -1,23 +1,6 @@
import solidPlugin from "vite-plugin-solid"
import tailwindcss from "@tailwindcss/vite"
import { fileURLToPath } from "url"
-import { generatePreloadScript } from "@opencode-ai/ui/theme"
-
-/**
- * Vite plugin that injects the theme preload script into index.html.
- * This ensures the theme is applied before the page renders, avoiding FOUC.
- * @type {import("vite").Plugin}
- */
-const themePreloadPlugin = {
- name: "opencode-desktop:theme-preload",
- transformIndexHtml(html) {
- const script = generatePreloadScript()
- return html.replace(
- /<script id="oc-theme-preload-script">\s*\/\* THEME_PRELOAD_SCRIPT \*\/\s*<\/script>/,
- `<script id="oc-theme-preload-script">${script}</script>`,
- )
- },
-}
/**
* @type {import("vite").PluginOption}
@@ -38,7 +21,6 @@ export default [
}
},
},
- themePreloadPlugin,
tailwindcss(),
solidPlugin(),
]
diff --git a/packages/desktop/index.html b/packages/desktop/index.html
index ea656068c..83826b602 100644
--- a/packages/desktop/index.html
+++ b/packages/desktop/index.html
@@ -13,9 +13,36 @@
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
- <!-- Theme preload script - injected by Vite plugin to avoid FOUC -->
+ <!-- Theme preload script - applies cached theme to avoid FOUC -->
<script id="oc-theme-preload-script">
- /* THEME_PRELOAD_SCRIPT */
+ ;(function () {
+ var themeId = localStorage.getItem("opencode-theme-id")
+ if (!themeId) return
+
+ var scheme = localStorage.getItem("opencode-color-scheme") || "system"
+ var isDark = scheme === "dark" || (scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches)
+ var mode = isDark ? "dark" : "light"
+
+ document.documentElement.dataset.theme = themeId
+ document.documentElement.dataset.colorScheme = mode
+
+ if (themeId === "oc-1") return
+
+ var css = localStorage.getItem("opencode-theme-css-" + themeId + "-" + mode)
+ if (css) {
+ var style = document.createElement("style")
+ style.id = "oc-theme-preload"
+ style.textContent =
+ ":root{color-scheme:" +
+ mode +
+ ";--text-mix-blend-mode:" +
+ (isDark ? "plus-lighter" : "multiply") +
+ ";" +
+ css +
+ "}"
+ document.head.appendChild(style)
+ }
+ })()
</script>
</head>
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx
index d8ca6d507..9a2013ee9 100644
--- a/packages/ui/src/theme/context.tsx
+++ b/packages/ui/src/theme/context.tsx
@@ -126,11 +126,13 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da
const css = themeToCss(tokens)
// Cache to localStorage for preload script
- const cacheKey = getThemeCacheKey(themeId, mode)
- try {
- localStorage.setItem(cacheKey, css)
- } catch {
- // localStorage might be full or disabled
+ if (themeId !== "oc-1") {
+ const cacheKey = getThemeCacheKey(themeId, mode)
+ try {
+ localStorage.setItem(cacheKey, css)
+ } catch {
+ // localStorage might be full or disabled
+ }
}
// Build full CSS
@@ -159,6 +161,8 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da
* Cache both light and dark variants of a theme
*/
function cacheThemeVariants(theme: DesktopTheme, themeId: string): void {
+ if (themeId === "oc-1") return
+
for (const mode of ["light", "dark"] as const) {
const isDark = mode === "dark"
const variant = isDark ? theme.dark : theme.light
diff --git a/packages/ui/src/theme/preload.ts b/packages/ui/src/theme/preload.ts
index eb54082fa..7e1143202 100644
--- a/packages/ui/src/theme/preload.ts
+++ b/packages/ui/src/theme/preload.ts
@@ -4,15 +4,13 @@
* Generates a minimal inline script that:
* 1. Reads theme preferences from localStorage
* 2. Applies cached theme CSS immediately (avoiding FOUC)
- * 3. Falls back to embedded default theme CSS on first visit
+ *
+ * The default (oc-1) theme is provided by `@opencode-ai/ui/styles` via `theme.css`,
+ * so the preload script only runs when a non-default theme is selected.
*
* The script should be placed in the document <head> before any stylesheets.
*/
-import { resolveThemeVariant, themeToCss } from "./resolve"
-import type { DesktopTheme } from "./types"
-import oc1Theme from "./themes/oc-1.json"
-
// Storage keys used by both the preload script and the ThemeProvider
export const STORAGE_KEYS = {
THEME_ID: "opencode-theme-id",
@@ -28,33 +26,16 @@ export function getThemeCacheKey(themeId: string, mode: "light" | "dark"): strin
}
/**
- * Generate the embedded default theme CSS for the preload script.
- * This is used as a fallback when no cached theme exists.
- */
-function generateEmbeddedDefaults(): { light: string; dark: string } {
- const theme = oc1Theme as DesktopTheme
- const lightTokens = resolveThemeVariant(theme.light, false)
- const darkTokens = resolveThemeVariant(theme.dark, true)
-
- return {
- light: themeToCss(lightTokens),
- dark: themeToCss(darkTokens),
- }
-}
-
-/**
* Generate the inline preload script.
*
* This script should be placed in the document <head> to avoid FOUC.
- * It reads theme preferences from localStorage and applies the theme CSS
- * immediately, falling back to an embedded default theme.
+ * It reads theme preferences from localStorage and applies cached theme CSS
+ * immediately.
*/
export function generatePreloadScript(): string {
- const defaults = generateEmbeddedDefaults()
-
// Minified version of the preload logic
// Variables: T=themeId, S=scheme, D=isDark, M=mode, C=css, K=cacheKey
- return `(function(){var T=localStorage.getItem("${STORAGE_KEYS.THEME_ID}")||"oc-1";var S=localStorage.getItem("${STORAGE_KEYS.COLOR_SCHEME}")||"system";var D=S==="dark"||(S==="system"&&matchMedia("(prefers-color-scheme:dark)").matches);var M=D?"dark":"light";var K="${STORAGE_KEYS.THEME_CSS_PREFIX}-"+T+"-"+M;var C=localStorage.getItem(K);if(!C&&T==="oc-1"){C=D?${JSON.stringify(defaults.dark)}:${JSON.stringify(defaults.light)}}if(C){var s=document.createElement("style");s.id="oc-theme-preload";s.textContent=":root{color-scheme:"+M+";--text-mix-blend-mode:"+(D?"plus-lighter":"multiply")+";"+C+"}";document.head.appendChild(s)}document.documentElement.dataset.theme=T;document.documentElement.dataset.colorScheme=M})();`
+ return `(function(){var T=localStorage.getItem("${STORAGE_KEYS.THEME_ID}");if(!T)return;var S=localStorage.getItem("${STORAGE_KEYS.COLOR_SCHEME}")||"system";var D=S==="dark"||(S==="system"&&matchMedia("(prefers-color-scheme:dark)").matches);var M=D?"dark":"light";document.documentElement.dataset.theme=T;document.documentElement.dataset.colorScheme=M;if(T==="oc-1")return;var K="${STORAGE_KEYS.THEME_CSS_PREFIX}-"+T+"-"+M;var C=localStorage.getItem(K);if(C){var s=document.createElement("style");s.id="oc-theme-preload";s.textContent=":root{color-scheme:"+M+";--text-mix-blend-mode:"+(D?"plus-lighter":"multiply")+";"+C+"}";document.head.appendChild(s)}})();`
}
/**
@@ -62,15 +43,16 @@ export function generatePreloadScript(): string {
* Useful for debugging.
*/
export function generatePreloadScriptFormatted(): string {
- const defaults = generateEmbeddedDefaults()
-
return `(function() {
var THEME_KEY = "${STORAGE_KEYS.THEME_ID}";
var SCHEME_KEY = "${STORAGE_KEYS.COLOR_SCHEME}";
var CSS_PREFIX = "${STORAGE_KEYS.THEME_CSS_PREFIX}";
- // Read preferences from localStorage
- var themeId = localStorage.getItem(THEME_KEY) || "oc-1";
+ // Only preload when a theme is selected
+ var themeId = localStorage.getItem(THEME_KEY);
+ if (!themeId) return;
+
+ // Read color scheme preference
var scheme = localStorage.getItem(SCHEME_KEY) || "system";
// Determine if dark mode
@@ -78,17 +60,17 @@ export function generatePreloadScriptFormatted(): string {
(scheme === "system" && matchMedia("(prefers-color-scheme: dark)").matches);
var mode = isDark ? "dark" : "light";
+ // Set data attributes for CSS/JS reference
+ document.documentElement.dataset.theme = themeId;
+ document.documentElement.dataset.colorScheme = mode;
+
+ // Default theme is handled by theme.css
+ if (themeId === "oc-1") return;
+
// Try to get cached CSS for this theme + mode
var cacheKey = CSS_PREFIX + "-" + themeId + "-" + mode;
var css = localStorage.getItem(cacheKey);
- // Fallback to embedded default for oc-1 theme
- if (!css && themeId === "oc-1") {
- css = isDark
- ? ${JSON.stringify(defaults.dark)}
- : ${JSON.stringify(defaults.light)};
- }
-
// Apply CSS if we have it
if (css) {
var style = document.createElement("style");
@@ -100,9 +82,5 @@ export function generatePreloadScriptFormatted(): string {
"}";
document.head.appendChild(style);
}
-
- // Set data attributes for CSS/JS reference
- document.documentElement.dataset.theme = themeId;
- document.documentElement.dataset.colorScheme = mode;
})();`
}