From eab19af2b462afe956bddfeae1e142f9add516f2 Mon Sep 17 00:00:00 2001 From: Jay1 Date: Sun, 31 May 2026 18:38:55 -0400 Subject: [PATCH 1/6] web: add theme surface tokens --- apps/web/src/index.css | 38 +++++++++++-- apps/web/src/theme/theme.logic.test.ts | 75 ++++++++++++++++++++++++++ apps/web/src/theme/theme.logic.ts | 47 ++++++++++++++++ 3 files changed, 157 insertions(+), 3 deletions(-) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index a34e0703a..d0289ef7e 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -58,12 +58,12 @@ @layer components { .sidebar-icon-button { - @apply items-center justify-center rounded-sm p-0.5 text-muted-foreground/60 transition-colors hover:text-foreground/82 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring; - background: transparent; + @apply items-center justify-center rounded-sm p-0.5 text-[var(--app-control-icon-fg,var(--muted-foreground))] transition-colors hover:text-[var(--app-control-icon-hover-fg,var(--foreground))] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[color:var(--app-state-focus,var(--ring))]; + background: var(--app-control-icon-bg, transparent); } .sidebar-icon-button:hover { - background: var(--color-background-button-secondary-hover); + background: var(--app-control-icon-hover-bg, var(--color-background-button-secondary-hover)); } /* Let the renderer switch between Codex-style translucent and opaque shells @@ -234,10 +234,42 @@ --app-surface-card: var(--card); --app-surface-card-header: var(--muted); --app-surface-composer: var(--card); + --app-surface-toolbar: var(--app-surface-topbar, var(--background)); + --app-surface-toolbar-hover: var(--app-state-hover, var(--accent)); + --app-surface-toolbar-active: var(--app-state-selected, var(--accent)); + --app-surface-toolbar-border: var(--border); --app-state-hover: var(--accent); --app-state-selected: var(--sidebar-accent-active); --app-state-selected-border: var(--primary); --app-state-focus: var(--ring); + --app-text-metadata: var(--muted-foreground); + --app-text-metadata-strong: var(--foreground); + --app-control-icon-fg: var(--app-text-metadata, var(--muted-foreground)); + --app-control-icon-hover-fg: var(--app-text-metadata-strong, var(--foreground)); + --app-control-icon-bg: transparent; + --app-control-icon-hover-bg: var(--app-surface-toolbar-hover, var(--accent)); + --app-control-icon-border: transparent; + --app-chrome-control-bg: var( + --color-background-control, + var(--app-surface-toolbar-hover, var(--accent)) + ); + --app-chrome-control-border: var(--app-surface-toolbar-border, var(--border)); + --app-chrome-control-fg: var(--color-text-foreground-secondary, var(--foreground)); + --app-chrome-control-hover-bg: var( + --color-background-button-secondary-hover, + var(--app-surface-toolbar-active, var(--accent)) + ); + --app-chrome-control-hover-fg: var(--color-text-foreground, var(--foreground)); + --app-chrome-control-active-bg: var( + --color-background-button-secondary-active, + var(--app-state-selected, var(--accent)) + ); + --app-metadata-fg: var(--app-text-metadata-strong, var(--foreground)); + --app-metadata-muted-fg: var(--app-text-metadata, var(--muted-foreground)); + --app-work-row-bg: var(--app-surface-card, var(--card)); + --app-work-row-hover-bg: var(--app-surface-card-header, var(--muted)); + --app-work-row-border: var(--border); + --app-work-row-icon: var(--app-text-metadata, var(--muted-foreground)); --app-status-error-bg: var(--destructive); --app-status-error-border: var(--destructive); --app-status-error-dot: var(--destructive-foreground); diff --git a/apps/web/src/theme/theme.logic.test.ts b/apps/web/src/theme/theme.logic.test.ts index b168faaf3..faaa16b1b 100644 --- a/apps/web/src/theme/theme.logic.test.ts +++ b/apps/web/src/theme/theme.logic.test.ts @@ -32,10 +32,33 @@ const REQUIRED_APP_DEPTH_TOKENS = [ "--app-surface-card", "--app-surface-card-header", "--app-surface-composer", + "--app-surface-toolbar", + "--app-surface-toolbar-hover", + "--app-surface-toolbar-active", + "--app-surface-toolbar-border", + "--app-chrome-control-bg", + "--app-chrome-control-border", + "--app-chrome-control-fg", + "--app-chrome-control-hover-bg", + "--app-chrome-control-hover-fg", + "--app-chrome-control-active-bg", "--app-state-hover", "--app-state-selected", "--app-state-selected-border", "--app-state-focus", + "--app-metadata-fg", + "--app-metadata-muted-fg", + "--app-text-metadata", + "--app-text-metadata-strong", + "--app-control-icon-fg", + "--app-control-icon-hover-fg", + "--app-control-icon-bg", + "--app-control-icon-hover-bg", + "--app-control-icon-border", + "--app-work-row-bg", + "--app-work-row-hover-bg", + "--app-work-row-border", + "--app-work-row-icon", "--app-status-error-bg", "--app-status-error-border", "--app-status-error-dot", @@ -441,6 +464,33 @@ describe("buildThemeCssVariables", () => { expect(tokens.derived.textButtonPrimary).not.toBe(tokens.derived.buttonPrimaryBackground); }); + it("derives semantic chrome control tokens from non-palette theme math", () => { + const importedTheme = parseThemeShareString(PROVIDED_THEME_STRING); + const pack = { + codeThemeId: importedTheme.codeThemeId, + theme: importedTheme.theme, + }; + const tokens = buildResolvedThemeTokens(pack, importedTheme.variant); + const cssVariables = buildThemeCssVariables(pack, importedTheme.variant); + + expect(cssVariables.variables["--app-chrome-control-bg"]).toBe( + tokens.derived.controlBackground, + ); + expect(cssVariables.variables["--app-chrome-control-border"]).toBe(tokens.derived.borderLight); + expect(cssVariables.variables["--app-chrome-control-fg"]).toBe( + tokens.derived.textForegroundSecondary, + ); + expect(cssVariables.variables["--app-chrome-control-hover-bg"]).toBe( + tokens.derived.buttonSecondaryBackgroundHover, + ); + expect(cssVariables.variables["--app-chrome-control-hover-fg"]).toBe( + tokens.derived.textForeground, + ); + expect(cssVariables.variables["--app-chrome-control-active-bg"]).toBe( + tokens.derived.buttonSecondaryBackgroundActive, + ); + }); + it("emits Catppuccin Mocha app-depth tokens from official palette layers", () => { const cssVariables = buildThemeCssVariables( { @@ -455,6 +505,31 @@ describe("buildThemeCssVariables", () => { expect(cssVariables.variables["--app-surface-topbar"]).toBe("#181825"); expect(cssVariables.variables["--app-surface-card"]).toBe("#1e1e2e"); expect(cssVariables.variables["--app-surface-card-header"]).toBe("#313244"); + expect(cssVariables.variables["--app-surface-toolbar"]).toBe("#181825"); + expect(cssVariables.variables["--app-surface-toolbar-hover"]).toBe("#313244"); + expect(cssVariables.variables["--app-surface-toolbar-active"]).toBe("#45475a"); + expect(cssVariables.variables["--app-surface-toolbar-border"]).toBe("#45475a"); + expect(cssVariables.variables["--app-chrome-control-bg"]).toBe("#313244"); + expect(cssVariables.variables["--app-chrome-control-border"]).toBe("#45475a"); + expect(cssVariables.variables["--app-chrome-control-fg"]).toBe("#cdd6f4"); + expect(cssVariables.variables["--app-chrome-control-hover-bg"]).toBe("#45475a"); + expect(cssVariables.variables["--app-chrome-control-hover-fg"]).toBe("#cdd6f4"); + expect(cssVariables.variables["--app-chrome-control-active-bg"]).toBe( + "rgba(203, 166, 247, 0.14)", + ); + expect(cssVariables.variables["--app-metadata-fg"]).toBe("rgba(205, 214, 244, 0.86)"); + expect(cssVariables.variables["--app-metadata-muted-fg"]).toBe("rgba(205, 214, 244, 0.62)"); + expect(cssVariables.variables["--app-text-metadata"]).toBe("rgba(205, 214, 244, 0.62)"); + expect(cssVariables.variables["--app-text-metadata-strong"]).toBe("rgba(205, 214, 244, 0.86)"); + expect(cssVariables.variables["--app-control-icon-fg"]).toBe("rgba(205, 214, 244, 0.62)"); + expect(cssVariables.variables["--app-control-icon-hover-fg"]).toBe("rgba(205, 214, 244, 0.86)"); + expect(cssVariables.variables["--app-control-icon-bg"]).toBe("transparent"); + expect(cssVariables.variables["--app-control-icon-hover-bg"]).toBe("#313244"); + expect(cssVariables.variables["--app-control-icon-border"]).toBe("rgba(69, 71, 90, 0.55)"); + expect(cssVariables.variables["--app-work-row-bg"]).toBe("rgba(30, 30, 46, 0.82)"); + expect(cssVariables.variables["--app-work-row-hover-bg"]).toBe("#313244"); + expect(cssVariables.variables["--app-work-row-border"]).toBe("rgba(69, 71, 90, 0.52)"); + expect(cssVariables.variables["--app-work-row-icon"]).toBe("rgba(205, 214, 244, 0.48)"); expect(cssVariables.variables["--app-state-hover"]).toBe("#313244"); expect(cssVariables.variables["--app-state-selected"]).toBe("rgba(203, 166, 247, 0.14)"); expect(cssVariables.variables["--app-state-selected-border"]).toBe("#cba6f7"); diff --git a/apps/web/src/theme/theme.logic.ts b/apps/web/src/theme/theme.logic.ts index 853b6d6ac..455506d97 100644 --- a/apps/web/src/theme/theme.logic.ts +++ b/apps/web/src/theme/theme.logic.ts @@ -982,10 +982,25 @@ function buildAppDepthVariables( "--app-scroll-button-hover-fg": palette.mauve, "--app-scrollbar-thumb": formatRgba(surface1, variant === "dark" ? 0.72 : 0.64), "--app-scrollbar-thumb-hover": formatRgba(surface1, variant === "dark" ? 0.92 : 0.82), + "--app-chrome-control-bg": palette.surface0, + "--app-chrome-control-border": palette.surface1, + "--app-chrome-control-fg": pack.theme.ink, + "--app-chrome-control-hover-bg": palette.surface1, + "--app-chrome-control-hover-fg": pack.theme.ink, + "--app-chrome-control-active-bg": formatRgba(accent, variant === "dark" ? 0.14 : 0.12), + "--app-control-icon-bg": "transparent", + "--app-control-icon-border": formatRgba(surface1, variant === "dark" ? 0.55 : 0.46), + "--app-control-icon-fg": formatRgba(parseHexColor(pack.theme.ink), 0.62), + "--app-control-icon-hover-bg": palette.surface0, + "--app-control-icon-hover-fg": formatRgba(parseHexColor(pack.theme.ink), 0.86), "--app-state-focus": palette.blue, "--app-state-hover": palette.surface0, "--app-state-selected": formatRgba(accent, variant === "dark" ? 0.14 : 0.12), "--app-state-selected-border": pack.theme.accent, + "--app-metadata-fg": formatRgba(parseHexColor(pack.theme.ink), 0.86), + "--app-metadata-muted-fg": formatRgba(parseHexColor(pack.theme.ink), 0.62), + "--app-text-metadata": formatRgba(parseHexColor(pack.theme.ink), 0.62), + "--app-text-metadata-strong": formatRgba(parseHexColor(pack.theme.ink), 0.86), "--app-status-error-bg": formatRgba(diffRemoved, variant === "dark" ? 0.14 : 0.1), "--app-status-error-border": formatRgba(diffRemoved, variant === "dark" ? 0.42 : 0.32), ...buildStatusVariables( @@ -1016,6 +1031,10 @@ function buildAppDepthVariables( "--app-surface-panel": variant === "dark" ? palette.mantle : "#ffffff", "--app-surface-sidebar": variant === "dark" ? palette.mantle : palette.mantle, "--app-surface-topbar": variant === "dark" ? palette.mantle : palette.mantle, + "--app-surface-toolbar": variant === "dark" ? palette.mantle : palette.mantle, + "--app-surface-toolbar-active": palette.surface1, + "--app-surface-toolbar-border": palette.surface1, + "--app-surface-toolbar-hover": palette.surface0, ...buildSubagentAccentVariables([ palette.red, palette.green, @@ -1032,6 +1051,10 @@ function buildAppDepthVariables( "--app-terminal-search-match-bg": palette.surface1, "--app-terminal-search-match-border": palette.blue, "--app-terminal-search-match-overview": palette.peach, + "--app-work-row-bg": formatRgba(parseHexColor(palette.base), 0.82), + "--app-work-row-border": formatRgba(surface1, 0.52), + "--app-work-row-hover-bg": palette.surface0, + "--app-work-row-icon": formatRgba(parseHexColor(pack.theme.ink), 0.48), "--app-wordmark-prefix": APP_WORDMARK_PREFIX_BLOOD_RED, }; } @@ -1115,6 +1138,7 @@ function buildProfileAppDepthVariables( const accent = parseHexColor(pack.theme.accent); const diffRemoved = parseHexColor(pack.theme.semanticColors.diffRemoved); const diffAdded = parseHexColor(pack.theme.semanticColors.diffAdded); + const ink = parseHexColor(pack.theme.ink); const warningColor = profile.warning ?? (variant === "dark" ? "#f5b44a" : "#d97706"); const warning = parseHexColor(warningColor); const toneLift = getThemeDepthToneLift(profile.tone, variant); @@ -1180,10 +1204,25 @@ function buildProfileAppDepthVariables( "--app-scroll-button-hover-fg": pack.theme.accent, "--app-scrollbar-thumb": resolvedTokens.derived.iconTertiary, "--app-scrollbar-thumb-hover": resolvedTokens.derived.iconSecondary, + "--app-chrome-control-bg": resolvedTokens.derived.controlBackground, + "--app-chrome-control-border": resolvedTokens.derived.borderLight, + "--app-chrome-control-fg": resolvedTokens.derived.textForegroundSecondary, + "--app-chrome-control-hover-bg": resolvedTokens.derived.buttonSecondaryBackgroundHover, + "--app-chrome-control-hover-fg": resolvedTokens.derived.textForeground, + "--app-chrome-control-active-bg": resolvedTokens.derived.buttonSecondaryBackgroundActive, + "--app-control-icon-bg": "transparent", + "--app-control-icon-border": resolvedTokens.derived.borderLight, + "--app-control-icon-fg": resolvedTokens.derived.textForegroundTertiary, + "--app-control-icon-hover-bg": resolvedTokens.derived.buttonSecondaryBackgroundHover, + "--app-control-icon-hover-fg": resolvedTokens.derived.textForeground, "--app-state-focus": resolvedTokens.derived.borderFocus, "--app-state-hover": resolvedTokens.derived.buttonSecondaryBackgroundHover, "--app-state-selected": formatRgba(accent, selectedAlpha), "--app-state-selected-border": pack.theme.accent, + "--app-metadata-fg": formatRgba(ink, variant === "dark" ? 0.86 : 0.88), + "--app-metadata-muted-fg": formatRgba(ink, variant === "dark" ? 0.62 : 0.66), + "--app-text-metadata": formatRgba(ink, variant === "dark" ? 0.62 : 0.66), + "--app-text-metadata-strong": formatRgba(ink, variant === "dark" ? 0.86 : 0.88), "--app-status-error-bg": formatRgba(diffRemoved, variant === "dark" ? 0.12 : 0.08), "--app-status-error-border": formatRgba(diffRemoved, variant === "dark" ? 0.36 : 0.28), ...buildStatusVariables( @@ -1222,12 +1261,20 @@ function buildProfileAppDepthVariables( "--app-surface-panel": panel, "--app-surface-sidebar": sidebar, "--app-surface-topbar": topbar, + "--app-surface-toolbar": topbar, + "--app-surface-toolbar-active": resolvedTokens.derived.buttonSecondaryBackgroundActive, + "--app-surface-toolbar-border": resolvedTokens.derived.border, + "--app-surface-toolbar-hover": resolvedTokens.derived.buttonSecondaryBackgroundHover, "--app-terminal-search-active-match-bg": activeSearchBackground, "--app-terminal-search-active-match-border": warningColor, "--app-terminal-search-active-match-overview": warningColor, "--app-terminal-search-match-bg": cardHeader, "--app-terminal-search-match-border": pack.theme.accent, "--app-terminal-search-match-overview": pack.theme.semanticColors.diffAdded, + "--app-work-row-bg": formatRgba(parseHexColor(panel), variant === "dark" ? 0.82 : 0.88), + "--app-work-row-border": resolvedTokens.derived.borderLight, + "--app-work-row-hover-bg": resolvedTokens.derived.elevatedSecondary, + "--app-work-row-icon": formatRgba(ink, variant === "dark" ? 0.48 : 0.52), "--app-wordmark-prefix": APP_WORDMARK_PREFIX_BLOOD_RED, }; } From fc7a65f91de3ee035e48cedd8d326b2a5b6b07e2 Mon Sep 17 00:00:00 2001 From: Jay1 Date: Sun, 31 May 2026 18:39:02 -0400 Subject: [PATCH 2/6] web: theme app chrome controls --- apps/web/src/components/BranchToolbar.tsx | 55 +++++++++---------- apps/web/src/components/ChatView.tsx | 12 ++-- apps/web/src/components/GitActionsControl.tsx | 4 +- apps/web/src/components/chat/ChatHeader.tsx | 6 +- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/apps/web/src/components/BranchToolbar.tsx b/apps/web/src/components/BranchToolbar.tsx index 5478ce9cd..ca14bfecb 100644 --- a/apps/web/src/components/BranchToolbar.tsx +++ b/apps/web/src/components/BranchToolbar.tsx @@ -77,16 +77,11 @@ export function RuntimeUsageControls({ className, }: RuntimeUsageControlsProps) { return ( -
+
{runtimeMode && onRuntimeModeChange ? ( )} {canSwitchToWorktree ? ( ) : null} {effectiveEnvMode === "worktree" && !canHandoffToLocal ? (
- - {environmentPresentation.worktreeOptionLabel} + + + {environmentPresentation.worktreeOptionLabel} + { setEnvPickerOpen(false); onHandoffToWorktree(); }} > - + Hand off to new worktree ) : null} {canHandoffToLocal && onHandoffToLocal ? ( ) : null}
-
+
- + Rate limits remaining @@ -433,7 +432,7 @@ export default function BranchToolbar({ ) : ( - + {environmentPresentation.shortLabel} diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 1d9267a95..0ddaf0060 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -768,7 +768,7 @@ function ComposerControlSkeleton(props: { widthClassName: string }) {