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 {