diff --git a/docs/knowledge/core-memory.md b/docs/knowledge/core-memory.md index 127b678..20b98a7 100644 --- a/docs/knowledge/core-memory.md +++ b/docs/knowledge/core-memory.md @@ -12,7 +12,7 @@ Durable notes for recurring maintenance. Keep this file small and update it inst - In sandboxed maintenance runs, set Bun and Wrangler writable paths when needed: `BUN_TMPDIR=/private/tmp/codex-bun-tmp`, `BUN_INSTALL_CACHE_DIR=/private/tmp/codex-bun-cache`, and `XDG_CONFIG_HOME=/private/tmp/codex-wrangler-config`. - Root `bun run deploy` should delegate to `cd packages/api && bun run deploy`, and the API deploy script must prepare `wrangler.deploy.jsonc` first so deploys can omit unavailable Vectorize or cron bindings without editing `wrangler.jsonc`. - Validate dashboard changes with `cd packages/dashboard && bun run build`; raw `bunx tsc --noEmit` can fail in fresh checkouts before Next generates route type files. -- Dashboard + marketing share a terminal TUI: JetBrains Mono only, radius 0, GitHub-dark canvas `#0d1117`, green accent `#3fb950`. Landing copy reads like a README (`#` headings, `$` prompts, `[bracket]` links). Do not reintroduce Inter, rounded chrome, or the old vermilion accent. +- Dashboard + marketing share a terminal TUI: JetBrains Mono only, radius 0, GitHub-dark canvas `#0d1117`, green accent `#3fb950`. Dark is the CSS default (`:root`); light is opt-in via `html.light`. Only an explicit `localStorage.theme === "light"` switches away from dark (legacy `"system"` stays dark). Landing copy reads like a README (`#` headings, `$` prompts, `[bracket]` links). Do not reintroduce Inter, rounded chrome, or the old vermilion accent. - Workers Cache is enabled (`"cache": { "enabled": true }` in `packages/api/wrangler.jsonc`). Only the public static GETs (`/api`, `/llms.txt`, `/agents.md`, `/openapi.json`) set `Cache-Control: public`; every Bearer-authed / per-project route stays uncacheable (and auto-bypasses on the `Authorization` header). Details + the rule: `docs/knowledge/workers-cache.md`. ## Review Memory diff --git a/packages/dashboard/src/components/providers.tsx b/packages/dashboard/src/components/providers.tsx index bdb9685..46700fa 100644 --- a/packages/dashboard/src/components/providers.tsx +++ b/packages/dashboard/src/components/providers.tsx @@ -17,12 +17,16 @@ const publishableKey = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY; export function Providers({ children }: { children: React.ReactNode }) { return ( - // `.dark` is the single theme mechanism (see tokens.css) — must match the - // inline theme-script.astro, which toggles the same class before paint. - // `attribute="class"` keeps next-themes from fighting the inline script - // by writing a separate data-theme attribute that global.css/tokens.css - // don't key their color tokens off. - + // `.dark` / `.light` on drive tokens.css. Dark tokens also live on + // `:root` so a missing class still paints the terminal canvas. + // `attribute="class"` with explicit dark/light values keeps next-themes + // from falling back to "system" (which used to resolve to light). + {children} by both layouts. - * 1. Applies the saved theme before paint (no FOUC). Defaults to dark - * (terminal TUI) when localStorage has no explicit choice. - * 2. Delegates clicks on any [data-theme-toggle] to flip + persist the theme. + * 1. Applies the saved theme before paint (no FOUC). Dark is the default; + * only an explicit localStorage value of "light" opts into light. + * Legacy next-themes "system" (and any other non-light value) stays dark. * 2. Delegates clicks on any [data-theme-toggle] to flip + persist the theme. * `is:inline` so it runs immediately and is not bundled/delayed. * - * `.dark` is the single mechanism tokens.css/global.css key their color - * tokens off (via `@custom-variant dark (&:is(.dark *))`); it is the source - * of truth here. `data-theme`/`data-mode` are cosmetic-only (drive the sun/ - * moon icon swap in tokens.css) — do not gate colors on them. Persists to + * `.dark` / `.light` on drive tokens.css. Dark tokens also live on + * `:root`, so a missing class still paints the terminal canvas. Persists to * `localStorage.theme`, the same key next-themes (attribute="class") reads * on hydration, so the client island and this pre-paint script never disagree. */ @@ -20,16 +18,17 @@ is:inline set:html={`(function () { var el = document.documentElement; - var stored = localStorage.theme; - var d = stored ? stored === 'dark' : true; - el.classList.toggle('dark', d); - el.setAttribute('data-mode', d ? 'dark' : 'light'); - el.setAttribute('data-theme', d ? 'dark' : 'light'); + var light = localStorage.theme === 'light'; + el.classList.toggle('dark', !light); + el.classList.toggle('light', light); + el.setAttribute('data-mode', light ? 'light' : 'dark'); + el.setAttribute('data-theme', light ? 'light' : 'dark'); document.addEventListener('click', function (e) { var b = e.target.closest && e.target.closest('[data-theme-toggle]'); if (!b) return; - var next = el.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; + var next = el.classList.contains('dark') ? 'light' : 'dark'; el.classList.toggle('dark', next === 'dark'); + el.classList.toggle('light', next === 'light'); el.setAttribute('data-mode', next); el.setAttribute('data-theme', next); try { localStorage.theme = next; } catch (_) {} diff --git a/packages/dashboard/src/lib/preset-stack.test.ts b/packages/dashboard/src/lib/preset-stack.test.ts index d49204a..f50ab54 100644 --- a/packages/dashboard/src/lib/preset-stack.test.ts +++ b/packages/dashboard/src/lib/preset-stack.test.ts @@ -35,6 +35,18 @@ describe("terminal TUI stack", () => { expect(tokens).toContain("--radius: 0px"); }); + test("tokens default to dark; light is an opt-in html.light class", () => { + const tokens = read("src/styles/tokens.css"); + expect(tokens).toContain(":root,"); + expect(tokens).toContain(".dark {"); + expect(tokens).toContain(".light {"); + expect(tokens).toMatch(/:root,\s*\n\.dark \{[^}]*color-scheme: dark/); + expect(tokens).toMatch(/\.light \{[^}]*color-scheme: light/); + const script = read("src/components/theme-script.astro"); + expect(script).toContain("localStorage.theme === 'light'"); + expect(script).not.toContain("prefers-color-scheme"); + }); + test("layouts that serve / do not import the old grotesk/inter packages", () => { for (const file of [ "src/layouts/RootLayout.astro", diff --git a/packages/dashboard/src/styles/tokens.css b/packages/dashboard/src/styles/tokens.css index 0e99fb4..59cc4a1 100644 --- a/packages/dashboard/src/styles/tokens.css +++ b/packages/dashboard/src/styles/tokens.css @@ -97,47 +97,8 @@ --color-swatch-dark2: #161b22; } -:root { - color-scheme: light; - --background: #f6f8fa; - --foreground: #1f2328; - --card: #ffffff; - --card-foreground: #1f2328; - --popover: #ffffff; - --popover-foreground: #1f2328; - --primary: #1a7f37; - --primary-foreground: #f6f8fa; - --secondary: #eaeef2; - --secondary-foreground: #1f2328; - --muted: #eaeef2; - --muted-foreground: #656d76; - --accent: #eaeef2; - --accent-foreground: #1f2328; - --destructive: #cf222e; - --border: #d0d7de; - --input: #d0d7de; - --ring: #1a7f37; - --chart-1: #1a7f37; - --chart-2: #0969da; - --chart-3: #8250df; - --chart-4: #bf3989; - --chart-5: #d29922; - --radius: 0px; - --sidebar: #ffffff; - --sidebar-foreground: #1f2328; - --sidebar-primary: #1a7f37; - --sidebar-primary-foreground: #f6f8fa; - --sidebar-accent: #eaeef2; - --sidebar-accent-foreground: #1f2328; - --sidebar-border: #d0d7de; - --sidebar-ring: #1a7f37; - --brand-soft: color-mix(in srgb, var(--primary) 12%, transparent); - --brand-line: color-mix(in srgb, var(--primary) 28%, transparent); - --tui-key: #576b3d; - --color-pos: #1a7f37; - --color-warn: #9a6700; -} - +/* Dark is the default (terminal TUI). Light is opt-in via html.light. */ +:root, .dark { color-scheme: dark; --background: #0d1117; @@ -163,6 +124,7 @@ --chart-3: #d2a8ff; --chart-4: #ff7b72; --chart-5: #d29922; + --radius: 0px; --sidebar: #0d1117; --sidebar-foreground: #e6edf3; --sidebar-primary: #3fb950; @@ -178,16 +140,56 @@ --color-warn: #d29922; } +.light { + color-scheme: light; + --background: #f6f8fa; + --foreground: #1f2328; + --card: #ffffff; + --card-foreground: #1f2328; + --popover: #ffffff; + --popover-foreground: #1f2328; + --primary: #1a7f37; + --primary-foreground: #f6f8fa; + --secondary: #eaeef2; + --secondary-foreground: #1f2328; + --muted: #eaeef2; + --muted-foreground: #656d76; + --accent: #eaeef2; + --accent-foreground: #1f2328; + --destructive: #cf222e; + --border: #d0d7de; + --input: #d0d7de; + --ring: #1a7f37; + --chart-1: #1a7f37; + --chart-2: #0969da; + --chart-3: #8250df; + --chart-4: #bf3989; + --chart-5: #d29922; + --sidebar: #ffffff; + --sidebar-foreground: #1f2328; + --sidebar-primary: #1a7f37; + --sidebar-primary-foreground: #f6f8fa; + --sidebar-accent: #eaeef2; + --sidebar-accent-foreground: #1f2328; + --sidebar-border: #d0d7de; + --sidebar-ring: #1a7f37; + --brand-soft: color-mix(in srgb, var(--primary) 12%, transparent); + --brand-line: color-mix(in srgb, var(--primary) 28%, transparent); + --tui-key: #576b3d; + --color-pos: #1a7f37; + --color-warn: #9a6700; +} + [data-theme-toggle] .icon-sun { display: none; } [data-theme-toggle] .icon-moon { display: grid; } -html:not(.dark) [data-theme-toggle] .icon-sun { +html.light [data-theme-toggle] .icon-sun { display: grid; } -html:not(.dark) [data-theme-toggle] .icon-moon { +html.light [data-theme-toggle] .icon-moon { display: none; }