From d874de2dbb05f968ecf15b1283b8c7c3bdd71a5d Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 9 Aug 2026 09:59:53 -0700 Subject: [PATCH 1/2] Add tests for library ThemeProvider shell integration --- apps/web/package.json | 4 ++-- apps/web/test/auth.test.tsx | 19 +++++++++++-------- apps/web/test/routes.test.tsx | 19 +++++++++++-------- apps/web/test/shell-docks.test.tsx | 24 ++++++++++++++++-------- apps/web/test/tailwind-build.test.ts | 12 ++++++------ apps/web/test/test-query-provider.tsx | 13 ++++++++++--- scripts/checks/web-tailwind-utilities.ts | 12 ++++++------ 7 files changed, 62 insertions(+), 41 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index c0597be0a..8902d1284 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc --noEmit", "build": "vite build", "watch": "vite build --watch", - "test": "bun test" + "test": "bun test ./test" }, "dependencies": { "@corbits/artifact-ui": "workspace:*", @@ -20,7 +20,7 @@ "@corbits/command-palette": "workspace:*", "@corbits/routines": "workspace:*", "@corbits/settings-ui": "workspace:*", - "@corbits/react-ui": "github:corbitsdev/react-ui#4b8952c820b44dbf83b423b97a9ad0f4513df1e0", + "@corbits/react-ui": "github:corbitsdev/react-ui#bd5057b0f740947ad117fc2c9cbe82bf992423d2", "@intx/types": "workspace:*", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-slot": "^1.2.3", diff --git a/apps/web/test/auth.test.tsx b/apps/web/test/auth.test.tsx index 3fd9d09f7..2d1c75fa0 100644 --- a/apps/web/test/auth.test.tsx +++ b/apps/web/test/auth.test.tsx @@ -3,6 +3,7 @@ // make, and the signed-out tree contains the auth screen instead of any // screen that talks to the hub. +import { ThemeProvider } from "@corbits/react-ui"; import { afterEach, describe, expect, test } from "bun:test"; import { renderToStaticMarkup } from "react-dom/server"; @@ -52,14 +53,16 @@ const noop = () => undefined; function renderApp(session: SessionState): string { return renderToStaticMarkup( - , + + + , ); } diff --git a/apps/web/test/routes.test.tsx b/apps/web/test/routes.test.tsx index 6d058d8be..1780cc1af 100644 --- a/apps/web/test/routes.test.tsx +++ b/apps/web/test/routes.test.tsx @@ -4,6 +4,7 @@ // rail-listed pages mark themselves in the rail. Page identity lives in the // panel page band (h2.panel-page-title), not a per-page TopBar. +import { ThemeProvider } from "@corbits/react-ui"; import { describe, expect, test } from "bun:test"; import { renderToStaticMarkup } from "react-dom/server"; @@ -21,14 +22,16 @@ const signedIn: SessionState = { function renderApp(path: string, session: SessionState = signedIn): string { return renderToStaticMarkup( - , + + + , ); } diff --git a/apps/web/test/shell-docks.test.tsx b/apps/web/test/shell-docks.test.tsx index cc70152de..0f4cb904b 100644 --- a/apps/web/test/shell-docks.test.tsx +++ b/apps/web/test/shell-docks.test.tsx @@ -2,6 +2,7 @@ // (never an id, never a network-fetched avatar) with the email as a // tooltip, and the initials derivation holds up against thin accounts. +import { ThemeProvider } from "@corbits/react-ui"; import { describe, expect, test } from "bun:test"; import { renderToStaticMarkup } from "react-dom/server"; @@ -13,13 +14,19 @@ const noop = () => undefined; function renderDock(path: string): string { return renderToStaticMarkup( - - - , + + + + + , ); } @@ -39,12 +46,13 @@ describe("initialsOf", () => { }); describe("RailIdentity", () => { - test("shows the avatar initials, the email, and the settings link", () => { + test("shows the avatar initials, the email, settings, and theme toggle", () => { const markup = renderDock("/"); expect(markup).toContain("AL"); expect(markup).toContain("ada@example.com"); expect(markup).toContain('href="/settings"'); expect(markup).toContain("Sign out"); + expect(markup).toContain("System theme"); expect(markup).not.toContain("user_1"); }); diff --git a/apps/web/test/tailwind-build.test.ts b/apps/web/test/tailwind-build.test.ts index 51739bfaa..d561bb825 100644 --- a/apps/web/test/tailwind-build.test.ts +++ b/apps/web/test/tailwind-build.test.ts @@ -31,12 +31,12 @@ describe("Tailwind production CSS", () => { test("emits artifact kind palette classes", () => { const css = builtCss(); for (const color of [ - "bg-blue-500", - "bg-emerald-500", - "bg-amber-500", - "bg-violet-500", - "bg-rose-500", - "bg-cyan-500", + "bg-\\[var\\(--chart-1\\)\\]", + "bg-\\[var\\(--chart-2\\)\\]", + "bg-\\[var\\(--chart-3\\)\\]", + "bg-\\[var\\(--chart-4\\)\\]", + "bg-\\[var\\(--chart-5\\)\\]", + "bg-muted", ]) { expect(css).toContain(color); } diff --git a/apps/web/test/test-query-provider.tsx b/apps/web/test/test-query-provider.tsx index bbf3d36a8..f16369aef 100644 --- a/apps/web/test/test-query-provider.tsx +++ b/apps/web/test/test-query-provider.tsx @@ -1,6 +1,9 @@ -// Shared QueryClientProvider for component tests that touch useAPIQuery / -// BenchProvider. retry:false + gcTime:0 keep failures loud and cache-free. +// Shared providers for component tests that touch useAPIQuery / BenchProvider +// / ThemeToggle. retry:false + gcTime:0 keep failures loud and cache-free; +// ThemeProvider is the production root (main.tsx) so shell chrome that calls +// useTheme can render under the same contract. +import { ThemeProvider } from "@corbits/react-ui"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactNode } from "react"; @@ -23,5 +26,9 @@ export function TestQueryProvider({ readonly children: ReactNode; readonly client?: QueryClient; }) { - return {children}; + return ( + + {children} + + ); } diff --git a/scripts/checks/web-tailwind-utilities.ts b/scripts/checks/web-tailwind-utilities.ts index 5024a1735..e878ecfb4 100644 --- a/scripts/checks/web-tailwind-utilities.ts +++ b/scripts/checks/web-tailwind-utilities.ts @@ -20,12 +20,12 @@ const REQUIRED_UTILITIES = [ "sm\\:px-7", "w-fit", "min-h-", - "bg-blue-500", - "bg-emerald-500", - "bg-amber-500", - "bg-violet-500", - "bg-rose-500", - "bg-cyan-500", + "bg-\\[var\\(--chart-1\\)\\]", + "bg-\\[var\\(--chart-2\\)\\]", + "bg-\\[var\\(--chart-3\\)\\]", + "bg-\\[var\\(--chart-4\\)\\]", + "bg-\\[var\\(--chart-5\\)\\]", + "bg-muted", ] as const; function newestMtime(dir: string, extensions: readonly string[]): number { From 3bc69bed74d67d3cb237b6b2d5b038b694d840cc Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 9 Aug 2026 09:59:53 -0700 Subject: [PATCH 2/2] CL-5781: Wire library theme system and tokenize package colors --- apps/web/src/main.tsx | 30 +++++++++++++++++--------- apps/web/src/shell/docks.tsx | 7 +++--- bun.lock | 10 ++++----- packages/artifact-ui/src/kind-color.ts | 21 +++++++++--------- packages/bench-ui/package.json | 2 +- packages/bench-ui/src/styles.css | 4 ++-- packages/chat-ui/package.json | 2 +- packages/chat-ui/src/styles.css | 7 +++--- packages/settings-ui/package.json | 2 +- packages/settings-ui/src/styles.css | 2 +- 10 files changed, 48 insertions(+), 39 deletions(-) diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 2d761151c..a67a2a9fc 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -2,6 +2,7 @@ import "@corbits/react-ui/styles.css"; import "./app.css"; import "./tailwind.css"; +import { ThemeProvider } from "@corbits/react-ui"; import { StrictMode, useCallback, useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; @@ -71,17 +72,26 @@ function Root() { void signOut(); }, []); + // Per-user storage when signed in so theme preference follows the account; + // signed-out / loading share the anonymous host key. + const themeStorageKey = + session.kind === "signed-in" + ? `corbits-theme:${session.user.id}` + : "corbits-theme"; + return ( - + + + ); } diff --git a/apps/web/src/shell/docks.tsx b/apps/web/src/shell/docks.tsx index 6aa81cd55..06d1119f8 100644 --- a/apps/web/src/shell/docks.tsx +++ b/apps/web/src/shell/docks.tsx @@ -2,7 +2,7 @@ // small parts rather than one component so the rail's footer slot can // compose them independently of whatever else the shell adds there later. -import { Button } from "@corbits/react-ui"; +import { Button, ThemeToggle } from "@corbits/react-ui"; import { BenchSwitcher } from "@corbits/bench-ui"; import { LogOut, Settings } from "lucide-react"; @@ -51,8 +51,8 @@ export function BenchDock() { } /** Rail footer: who is signed in (initials avatar, tooltip-only email — - * never an id) plus settings and sign-out, stacked to fit the narrow rail - * rather than the wide row the contextual panel used to have room for. */ + * never an id) plus theme, settings and sign-out, stacked to fit the narrow + * rail rather than the wide row the contextual panel used to have room for. */ export function RailIdentity({ path, user, @@ -66,6 +66,7 @@ export function RailIdentity({ const settingsActive = matchesRoute(SETTINGS_PATH, path); return (
+