Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/knowledge/core-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 10 additions & 6 deletions packages/dashboard/src/components/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false}>
// `.dark` / `.light` on <html> 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).
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={false}
value={{ dark: "dark", light: "light" }}
>
<ClerkProvider publishableKey={publishableKey} appearance={clerkAppearance}>
{children}
<Toaster
Expand Down
25 changes: 12 additions & 13 deletions packages/dashboard/src/components/theme-script.astro
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
---
/**
* Inline (pre-hydration) theme script — included in <head> 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 <html> 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.
*/
Expand All @@ -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 (_) {}
Expand Down
12 changes: 12 additions & 0 deletions packages/dashboard/src/lib/preset-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
88 changes: 45 additions & 43 deletions packages/dashboard/src/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -163,6 +124,7 @@
--chart-3: #d2a8ff;
--chart-4: #ff7b72;
--chart-5: #d29922;
--radius: 0px;
--sidebar: #0d1117;
--sidebar-foreground: #e6edf3;
--sidebar-primary: #3fb950;
Expand All @@ -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;
}

Expand Down