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/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 (
+ 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/bun.lock b/bun.lock index 64631eab5..5e85f83e5 100644 --- a/bun.lock +++ b/bun.lock @@ -90,7 +90,7 @@ "@corbits/chat": "workspace:*", "@corbits/chat-ui": "workspace:*", "@corbits/command-palette": "workspace:*", - "@corbits/react-ui": "github:corbitsdev/react-ui#4b8952c820b44dbf83b423b97a9ad0f4513df1e0", + "@corbits/react-ui": "github:corbitsdev/react-ui#bd5057b0f740947ad117fc2c9cbe82bf992423d2", "@corbits/routines": "workspace:*", "@corbits/settings-ui": "workspace:*", "@intx/types": "workspace:*", @@ -178,7 +178,7 @@ "name": "@corbits/bench-ui", "version": "0.0.1", "dependencies": { - "@corbits/react-ui": "github:corbitsdev/react-ui#4b8952c820b44dbf83b423b97a9ad0f4513df1e0", + "@corbits/react-ui": "github:corbitsdev/react-ui#bd5057b0f740947ad117fc2c9cbe82bf992423d2", "@intx/types": "workspace:*", "arktype": "catalog:", "lucide-react": "^1.27.0", @@ -226,7 +226,7 @@ "version": "0.0.1", "dependencies": { "@corbits/chat": "workspace:*", - "@corbits/react-ui": "github:corbitsdev/react-ui#4b8952c820b44dbf83b423b97a9ad0f4513df1e0", + "@corbits/react-ui": "github:corbitsdev/react-ui#bd5057b0f740947ad117fc2c9cbe82bf992423d2", "arktype": "catalog:", "lucide-react": "^1.27.0", "react": "^19.2.0", @@ -386,7 +386,7 @@ "dependencies": { "@corbits/bench-ui": "workspace:*", "@corbits/chat-ui": "workspace:*", - "@corbits/react-ui": "github:corbitsdev/react-ui#4b8952c820b44dbf83b423b97a9ad0f4513df1e0", + "@corbits/react-ui": "github:corbitsdev/react-ui#bd5057b0f740947ad117fc2c9cbe82bf992423d2", "@intx/types": "workspace:*", "arktype": "catalog:", "lucide-react": "^1.27.0", @@ -853,7 +853,7 @@ "@corbits/notify": ["@corbits/notify@workspace:packages/notify"], - "@corbits/react-ui": ["@corbits/react-ui@github:corbitsdev/react-ui#4b8952c", { "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "tailwind-merge": "^3.3.1" }, "peerDependencies": { "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.3", "@tanstack/react-query": "^5.90.2", "lucide-react": "^0.545.0 || ^1.0.0", "react": "^18.2.0 || ^19.0.0", "react-dom": "^18.2.0 || ^19.0.0", "sonner": "^2.0.7" }, "optionalPeers": ["@tanstack/react-query"] }, "corbitsdev-react-ui-4b8952c", "sha512-2Pb2CQfFRchm2Q3kWUNyA/0pr3au4Pye+zOoNqJrOqjaQhDPPqoMYIAvxiPLBCbShfOtZ/A4dj1cfkj0WaUn4A=="], + "@corbits/react-ui": ["@corbits/react-ui@github:corbitsdev/react-ui#bd5057b", { "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "tailwind-merge": "^3.3.1" }, "peerDependencies": { "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.3", "@tanstack/react-query": "^5.90.2", "lucide-react": "^0.545.0 || ^1.0.0", "react": "^18.2.0 || ^19.0.0", "react-dom": "^18.2.0 || ^19.0.0", "sonner": "^2.0.7" }, "optionalPeers": ["@tanstack/react-query"] }, "corbitsdev-react-ui-bd5057b", "sha512-sAuh/ES7keqMBLMAZ2SFVQ0PINMb1SPVozXUR/jny5pE0ALAEm3hufl/Ofbb07FxhACtO3y4LPWl/3XyFsxnOw=="], "@corbits/routines": ["@corbits/routines@workspace:packages/routines"], diff --git a/packages/artifact-ui/src/kind-color.ts b/packages/artifact-ui/src/kind-color.ts index 86b04330b..d8943d32f 100644 --- a/packages/artifact-ui/src/kind-color.ts +++ b/packages/artifact-ui/src/kind-color.ts @@ -1,17 +1,16 @@ // Kind-colored gallery tiles: each artifact kind gets a stable background -// color so a grid of mixed kinds reads as distinct groups at a glance, the -// way the reference gallery's fill-per-kind cards do. The mapping is a hash -// over the kind string rather than a hand-maintained table, so a workflow -// that emits a brand-new kind still gets a consistent color instead of -// falling through to an "unknown" gray. +// drawn from the Corbits chart token series so light/dark (and preset +// overlays) stay on-brand. The mapping is a hash over the kind string +// rather than a hand-maintained table, so a workflow that emits a brand-new +// kind still gets a consistent color instead of falling through to muted. const PALETTE = [ - "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 hashKind(kind: string): number { diff --git a/packages/bench-ui/package.json b/packages/bench-ui/package.json index b43403184..4e2dfd070 100644 --- a/packages/bench-ui/package.json +++ b/packages/bench-ui/package.json @@ -14,7 +14,7 @@ "test": "bun test" }, "dependencies": { - "@corbits/react-ui": "github:corbitsdev/react-ui#4b8952c820b44dbf83b423b97a9ad0f4513df1e0", + "@corbits/react-ui": "github:corbitsdev/react-ui#bd5057b0f740947ad117fc2c9cbe82bf992423d2", "@intx/types": "workspace:*", "arktype": "catalog:", "lucide-react": "^1.27.0", diff --git a/packages/bench-ui/src/styles.css b/packages/bench-ui/src/styles.css index e803baea4..c491d3b91 100644 --- a/packages/bench-ui/src/styles.css +++ b/packages/bench-ui/src/styles.css @@ -64,8 +64,8 @@ padding: 0.25rem 0; background: var(--background); box-shadow: - 0 4px 6px rgb(0 0 0 / 0.07), - 0 10px 15px rgb(0 0 0 / 0.1); + 0 4px 6px color-mix(in srgb, var(--foreground) 8%, transparent), + 0 10px 15px color-mix(in srgb, var(--foreground) 12%, transparent); } .bench-switcher-option, diff --git a/packages/chat-ui/package.json b/packages/chat-ui/package.json index 0b423a87c..b672e637a 100644 --- a/packages/chat-ui/package.json +++ b/packages/chat-ui/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@corbits/chat": "workspace:*", - "@corbits/react-ui": "github:corbitsdev/react-ui#4b8952c820b44dbf83b423b97a9ad0f4513df1e0", + "@corbits/react-ui": "github:corbitsdev/react-ui#bd5057b0f740947ad117fc2c9cbe82bf992423d2", "arktype": "catalog:", "lucide-react": "^1.27.0", "react": "^19.2.0", diff --git a/packages/chat-ui/src/styles.css b/packages/chat-ui/src/styles.css index 03ef0ba41..d2d74651a 100644 --- a/packages/chat-ui/src/styles.css +++ b/packages/chat-ui/src/styles.css @@ -3,10 +3,9 @@ `@corbits/react-ui`'s prebuilt stylesheet and the theme's tokens. */ .chat-workspace { - /* Summit Blue accent for the agent badge — the one literal allowed by the - brand review, defined once here as a token rather than sprinkled at the - usage site. Swap this for a consumed theme token if/when one ships. */ - --chat-agent-accent: #607c9a; + /* Agent badge uses the theme chart-1 series so light/dark and preset + overlays stay consistent without a package-local hex. */ + --chat-agent-accent: var(--chart-1); display: flex; flex: 1; diff --git a/packages/settings-ui/package.json b/packages/settings-ui/package.json index 94e77ad7f..26c67c90f 100644 --- a/packages/settings-ui/package.json +++ b/packages/settings-ui/package.json @@ -16,7 +16,7 @@ "dependencies": { "@corbits/bench-ui": "workspace:*", "@corbits/chat-ui": "workspace:*", - "@corbits/react-ui": "github:corbitsdev/react-ui#4b8952c820b44dbf83b423b97a9ad0f4513df1e0", + "@corbits/react-ui": "github:corbitsdev/react-ui#bd5057b0f740947ad117fc2c9cbe82bf992423d2", "@intx/types": "workspace:*", "arktype": "catalog:", "lucide-react": "^1.27.0", diff --git a/packages/settings-ui/src/styles.css b/packages/settings-ui/src/styles.css index 6040369e9..48ceb5880 100644 --- a/packages/settings-ui/src/styles.css +++ b/packages/settings-ui/src/styles.css @@ -74,7 +74,7 @@ .settings-inline-error { margin: 0; - color: var(--destructive, #b91c1c); + color: var(--destructive); font-size: 0.8125rem; } 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 {