From 27dc48d1c9b76690d14112fc8f00c3ce556b70f7 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 01:58:49 -0700 Subject: [PATCH 01/12] feat(web): one accent blue, lifted dark surfaces, quieter PR numbers Links use the action color, dark raised surfaces and overlays sit a step above the canvas, and sidebar PR numbers stay muted while the icon keeps the state color. Co-Authored-By: Claude Opus 5.5 --- apps/web/src/index.css | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index cfb320a6c9e58..9f721d7dbd382 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1123,6 +1123,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --message-action: var(--primary); --message-action-foreground: var(--primary-foreground); --message-action-hover: color-mix(in srgb, var(--primary) 90%, var(--background)); + /* Links share the action color so blue always means "clickable". */ + --link: var(--primary); --accent: var(--color-zinc-100); --accent-foreground: var(--color-zinc-900); --error: var(--color-red-500); @@ -1168,16 +1170,17 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil @variant dark { color-scheme: dark; - /* Keep controls and floating surfaces close to the neutral-black canvas. - Borders and hover states provide separation without milky gray fills. */ + /* Raised surfaces (composer, cards) sit one step above the neutral-black + canvas and overlays one more, so they read without milky gray fills. */ --background: var(--color-neutral-950); - --surface-raised: color-mix(in srgb, var(--background) 97%, var(--color-white)); + --surface-raised: color-mix(in srgb, var(--background) 93%, var(--color-white)); --foreground: var(--color-neutral-100); - --card: color-mix(in srgb, var(--background) 97%, var(--color-white)); + --card: color-mix(in srgb, var(--background) 93%, var(--color-white)); --card-foreground: var(--color-neutral-100); - --popover: color-mix(in srgb, var(--background) 97%, var(--color-white)); + --popover: color-mix(in srgb, var(--background) 90%, var(--color-white)); --popover-foreground: var(--color-neutral-100); --primary: oklch(0.571 0.21 264); + --link: color-mix(in oklab, var(--primary), var(--color-white) 28%); --secondary: --alpha(var(--color-white) / 3%); --secondary-foreground: var(--color-neutral-100); --muted: --alpha(var(--color-white) / 3%); @@ -1973,7 +1976,7 @@ code { /* File links and context chips own their color and focus ring. */ .chat-markdown a:not(.chat-markdown-file-link, :has([data-slot="context-chip-label"])) { - color: var(--info-foreground); + color: var(--link); } .chat-markdown From 5000963cf77e1b8535b65f3006d68b2a92a3f765 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 02:08:18 -0700 Subject: [PATCH 02/12] fix(web): keep placeholder and themed link contrast above 4.5:1 Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/ComposerPromptEditorTiptap.tsx | 2 +- apps/web/src/index.css | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/ComposerPromptEditorTiptap.tsx b/apps/web/src/components/ComposerPromptEditorTiptap.tsx index 3958f0149fc7e..31b984be55e4a 100644 --- a/apps/web/src/components/ComposerPromptEditorTiptap.tsx +++ b/apps/web/src/components/ComposerPromptEditorTiptap.tsx @@ -1839,7 +1839,7 @@ function ComposerPromptEditorTiptapInner( {isEmpty && contextRecords.size === 0 && placeholder ? (
diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 9f721d7dbd382..8cc58d32a3550 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1286,6 +1286,9 @@ html[data-theme-id]:not([data-theme-id=""]) { a distinct action color make these identical. */ --primary: var(--app-theme-message-action); --primary-foreground: var(--app-theme-message-action-foreground); + /* Theme action colors are tuned for filled buttons, not text, so themed + links keep the info color. */ + --link: var(--info-foreground); --secondary: var(--app-theme-secondary); --secondary-foreground: var(--app-theme-secondary-foreground); --muted: var(--app-theme-muted); From b2cf696d6e5652d1e707319e25beb535d1c94077 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 02:14:07 -0700 Subject: [PATCH 03/12] fix(web): mobile sidebar sheet keeps its color under lifted cards Co-Authored-By: Claude Opus 5.5 --- apps/web/src/index.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 8cc58d32a3550..1be531213ef92 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1200,12 +1200,14 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --warning-surface: color-mix(in srgb, var(--warning) 16%, transparent); --update-foreground: var(--color-blue-400); --update-surface: color-mix(in srgb, var(--update) 18%, transparent); - --sidebar: var(--card); + /* Sidebar surfaces outside [data-app-sidebar] (mobile sheet) keep the flat + step instead of following the lifted card. */ + --sidebar: color-mix(in srgb, var(--background) 97%, var(--color-white)); --sidebar-control-surface: var(--muted); --sidebar-row-hover: var(--accent); --sidebar-row-active: var(--accent); --sidebar-row-selected: var(--muted); - --sidebar-stage-fade: var(--card); + --sidebar-stage-fade: var(--sidebar); --terminal-cursor: rgb(180 203 255); --terminal-selection-background: rgb(180 203 255 / 25%); } From 3133dccaff7cf6b16ee615304867d943bff15b82 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 03:46:59 -0700 Subject: [PATCH 04/12] feat(web): blue marks the current row, settings page and focused composer Co-Authored-By: Claude Opus 5.5 --- .../src/components/chat/ComposerSurface.tsx | 1 + .../components/sidebar/SidebarThreadRow.tsx | 4 ++-- apps/web/src/index.css | 21 ++++++++++--------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/chat/ComposerSurface.tsx b/apps/web/src/components/chat/ComposerSurface.tsx index f2420eefa046b..6bceb7518a87e 100644 --- a/apps/web/src/components/chat/ComposerSurface.tsx +++ b/apps/web/src/components/chat/ComposerSurface.tsx @@ -18,6 +18,7 @@ function Shell({ "[html[data-theme-id]_&]:[--chat-composer-glass-surface:var(--app-theme-surface-raised)] [html[data-theme-id]_&]:[--chat-composer-outline:var(--app-theme-toolbar-border)]", "dark:[html[data-theme-id]:not([data-theme-id=t3-chat])_&]:[--chat-composer-highlight:color-mix(in_srgb,var(--app-theme-input)_12%,transparent)] dark:[html[data-theme-id]:not([data-theme-id=t3-chat])_&]:[--chat-composer-outline:color-mix(in_srgb,var(--app-theme-input)_30%,var(--background))]", "dark:[html[data-theme-id=t3-chat]_&]:[--chat-composer-highlight:color-mix(in_srgb,#432d48_12%,transparent)] dark:[html[data-theme-id=t3-chat]_&]:[--chat-composer-outline:#241e28]", + "has-[[contenteditable]:focus]:[--chat-composer-outline:color-mix(in_oklab,var(--ring)_45%,transparent)]", "before:pointer-events-none before:absolute before:inset-0 before:z-0 before:rounded-2xl before:bg-(--chat-composer-glass-surface)/(--glass-opacity) before:backdrop-blur-(--glass-blur) before:backdrop-saturate-(--glass-saturation)", "not-supports-[((backdrop-filter:blur(1px))_or_(-webkit-backdrop-filter:blur(1px)))]:before:bg-(--chat-composer-glass-surface)", "has-data-[composer-banner-surface=attached]:before:hidden data-model-strip-transition:before:hidden", diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx index 1daec98502a3b..64fe4f66c4825 100644 --- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx +++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx @@ -896,9 +896,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP !props.isActive && "[&:not(:hover):not(:focus-within)_*]:text-secondary-label/70", props.isActive - ? "bg-sidebar-row-active text-sidebar-foreground light:shadow-xs light:ring-1 light:ring-inset light:ring-sidebar-border/70" + ? "bg-sidebar-row-active text-sidebar-foreground light:shadow-xs light:ring-1 light:ring-inset light:ring-primary/15" : isSelected || props.sweepAction !== null - ? "bg-sidebar-row-selected text-sidebar-foreground light:ring-1 light:ring-inset light:ring-sidebar-border/70" + ? "bg-sidebar-row-selected text-sidebar-foreground light:ring-1 light:ring-inset light:ring-primary/15" : hasUnsentDraft ? cn(draftSurfaceClassName, "text-sidebar-foreground") : shouldRecede diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 1be531213ef92..60dc56125d0ba 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1157,8 +1157,9 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --sidebar-muted-foreground: var(--muted-foreground); --sidebar-control-surface: var(--color-zinc-100); --sidebar-row-hover: var(--color-zinc-25); - --sidebar-row-active: var(--color-white); - --sidebar-row-selected: var(--color-white); + /* The current row and settings page carry the accent: blue means "you are here". */ + --sidebar-row-active: color-mix(in oklab, var(--primary) 10%, var(--color-white)); + --sidebar-row-selected: color-mix(in oklab, var(--primary) 7%, var(--color-white)); --sidebar-border: var(--border); --sidebar-stage-fade: var(--sidebar); --code-background: color-mix(in srgb, var(--card) 90%, var(--background)); @@ -1205,8 +1206,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --sidebar: color-mix(in srgb, var(--background) 97%, var(--color-white)); --sidebar-control-surface: var(--muted); --sidebar-row-hover: var(--accent); - --sidebar-row-active: var(--accent); - --sidebar-row-selected: var(--muted); + --sidebar-row-active: color-mix(in oklab, var(--primary) 24%, transparent); + --sidebar-row-selected: color-mix(in oklab, var(--primary) 14%, transparent); --sidebar-stage-fade: var(--sidebar); --terminal-cursor: rgb(180 203 255); --terminal-selection-background: rgb(180 203 255 / 25%); @@ -1214,8 +1215,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil } /* Keep both sidebar implementations (default and legacy) on the same quiet - zinc hierarchy: zinc-50 navigation, zinc-25 hover, and white selected/raised - surfaces. */ + zinc hierarchy: zinc-50 navigation, zinc-25 hover, white raised surfaces, + and accent-tinted current rows. */ [data-app-sidebar] { --background: var(--color-zinc-25); --foreground: var(--color-zinc-900); @@ -1232,8 +1233,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --sidebar-muted-foreground: var(--color-zinc-500); --sidebar-control-surface: var(--color-zinc-100); --sidebar-row-hover: var(--color-zinc-25); - --sidebar-row-active: var(--color-white); - --sidebar-row-selected: var(--color-white); + --sidebar-row-active: color-mix(in oklab, var(--primary) 10%, var(--color-white)); + --sidebar-row-selected: color-mix(in oklab, var(--primary) 7%, var(--color-white)); --sidebar-border: var(--color-zinc-200); --sidebar-stage-fade: var(--sidebar); @@ -1253,8 +1254,8 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --sidebar-muted-foreground: var(--muted-foreground); --sidebar-control-surface: var(--muted); --sidebar-row-hover: color-mix(in srgb, var(--contrast-foreground) 8%, transparent); - --sidebar-row-active: color-mix(in srgb, var(--contrast-foreground) 11%, transparent); - --sidebar-row-selected: color-mix(in srgb, var(--contrast-foreground) 7%, transparent); + --sidebar-row-active: color-mix(in oklab, var(--primary) 24%, transparent); + --sidebar-row-selected: color-mix(in oklab, var(--primary) 14%, transparent); --sidebar-border: var(--border); --sidebar-stage-fade: var(--card); } From d995468350589a9f30396c47c221c8c044fe7680 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 03:51:24 -0700 Subject: [PATCH 05/12] fix(web): composer focus shows on every theme, full light placeholder Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/ComposerPromptEditorTiptap.tsx | 2 +- apps/web/src/components/chat/ComposerSurface.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/ComposerPromptEditorTiptap.tsx b/apps/web/src/components/ComposerPromptEditorTiptap.tsx index 31b984be55e4a..8234408e320fc 100644 --- a/apps/web/src/components/ComposerPromptEditorTiptap.tsx +++ b/apps/web/src/components/ComposerPromptEditorTiptap.tsx @@ -1839,7 +1839,7 @@ function ComposerPromptEditorTiptapInner( {isEmpty && contextRecords.size === 0 && placeholder ? (
diff --git a/apps/web/src/components/chat/ComposerSurface.tsx b/apps/web/src/components/chat/ComposerSurface.tsx index 6bceb7518a87e..7f977dea9c056 100644 --- a/apps/web/src/components/chat/ComposerSurface.tsx +++ b/apps/web/src/components/chat/ComposerSurface.tsx @@ -18,7 +18,6 @@ function Shell({ "[html[data-theme-id]_&]:[--chat-composer-glass-surface:var(--app-theme-surface-raised)] [html[data-theme-id]_&]:[--chat-composer-outline:var(--app-theme-toolbar-border)]", "dark:[html[data-theme-id]:not([data-theme-id=t3-chat])_&]:[--chat-composer-highlight:color-mix(in_srgb,var(--app-theme-input)_12%,transparent)] dark:[html[data-theme-id]:not([data-theme-id=t3-chat])_&]:[--chat-composer-outline:color-mix(in_srgb,var(--app-theme-input)_30%,var(--background))]", "dark:[html[data-theme-id=t3-chat]_&]:[--chat-composer-highlight:color-mix(in_srgb,#432d48_12%,transparent)] dark:[html[data-theme-id=t3-chat]_&]:[--chat-composer-outline:#241e28]", - "has-[[contenteditable]:focus]:[--chat-composer-outline:color-mix(in_oklab,var(--ring)_45%,transparent)]", "before:pointer-events-none before:absolute before:inset-0 before:z-0 before:rounded-2xl before:bg-(--chat-composer-glass-surface)/(--glass-opacity) before:backdrop-blur-(--glass-blur) before:backdrop-saturate-(--glass-saturation)", "not-supports-[((backdrop-filter:blur(1px))_or_(-webkit-backdrop-filter:blur(1px)))]:before:bg-(--chat-composer-glass-surface)", "has-data-[composer-banner-surface=attached]:before:hidden data-model-strip-transition:before:hidden", @@ -35,8 +34,9 @@ function Shell({ ); } +// Focus sets the border directly so named themes, which override the outline variable, still show it. const outlineClasses = - "after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-(--chat-composer-outline) dark:after:inset-shadow-2xs dark:after:inset-shadow-(color:--chat-composer-highlight)"; + "after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-(--chat-composer-outline) dark:after:inset-shadow-2xs dark:after:inset-shadow-(color:--chat-composer-highlight) group-has-[[contenteditable]:focus]/composer-surface:after:border-ring/65 dark:group-has-[[contenteditable]:focus]/composer-surface:after:border-ring/90"; const contextSeamClasses = "group-data-with-context/composer-surface:after:[clip-path:polygon(0_0,100%_0,100%_100%,calc(100%-22px)_100%,calc(100%-22px)_calc(100%-2px),22px_calc(100%-2px),22px_100%,0_100%)]"; From d8860159ceddca770cc1195c7ee486a0c170f973 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 03:55:12 -0700 Subject: [PATCH 06/12] fix(web): composer placeholder at full placeholder color Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/ComposerPromptEditorTiptap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/ComposerPromptEditorTiptap.tsx b/apps/web/src/components/ComposerPromptEditorTiptap.tsx index 8234408e320fc..030d34adb40cc 100644 --- a/apps/web/src/components/ComposerPromptEditorTiptap.tsx +++ b/apps/web/src/components/ComposerPromptEditorTiptap.tsx @@ -1839,7 +1839,7 @@ function ComposerPromptEditorTiptapInner( {isEmpty && contextRecords.size === 0 && placeholder ? (
From 609b4e60ecd1f64b0eb97474de4bd83c28b88657 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 04:03:43 -0700 Subject: [PATCH 07/12] fix(web): one current-page tint, neutral press, details card off the popover step Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/sidebar/SidebarThreadRow.tsx | 4 ++-- apps/web/src/components/ui/sidebar.tsx | 6 +++--- apps/web/src/index.css | 11 ++++++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx index 64fe4f66c4825..ef57e37f74094 100644 --- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx +++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx @@ -896,9 +896,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP !props.isActive && "[&:not(:hover):not(:focus-within)_*]:text-secondary-label/70", props.isActive - ? "bg-sidebar-row-active text-sidebar-foreground light:shadow-xs light:ring-1 light:ring-inset light:ring-primary/15" + ? "bg-sidebar-row-active text-sidebar-foreground light:shadow-xs light:ring-1 light:ring-inset light:ring-sidebar-row-ring" : isSelected || props.sweepAction !== null - ? "bg-sidebar-row-selected text-sidebar-foreground light:ring-1 light:ring-inset light:ring-primary/15" + ? "bg-sidebar-row-selected text-sidebar-foreground light:ring-1 light:ring-inset light:ring-sidebar-row-ring" : hasUnsentDraft ? cn(draftSurfaceClassName, "text-sidebar-foreground") : shouldRecede diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index 226d168715640..14512ac533bc1 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -682,7 +682,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { } const sidebarMenuButtonVariants = cva( - "peer/menu-button flex w-full cursor-pointer items-center gap-[var(--sidebar-control-gap)] overflow-hidden text-left outline-hidden ring-ring transition-[width,height,padding] hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-inset active:bg-sidebar-row-active active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 data-[active=true]:bg-sidebar-row-selected data-[active=true]:font-medium data-[active=true]:text-sidebar-foreground data-[state=open]:hover:bg-sidebar-row-hover data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-[var(--sidebar-content-inset)]! [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-[var(--sidebar-icon-color)] hover:[&>svg]:text-sidebar-foreground active:[&>svg]:text-sidebar-foreground data-[active=true]:[&>svg]:text-sidebar-foreground", + "peer/menu-button flex w-full cursor-pointer items-center gap-[var(--sidebar-control-gap)] overflow-hidden text-left outline-hidden ring-ring transition-[width,height,padding] hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-inset active:bg-sidebar-row-hover active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 data-[active=true]:bg-sidebar-row-active data-[active=true]:font-medium data-[active=true]:text-sidebar-foreground data-[state=open]:hover:bg-sidebar-row-hover data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-[var(--sidebar-content-inset)]! [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-[var(--sidebar-icon-color)] hover:[&>svg]:text-sidebar-foreground active:[&>svg]:text-sidebar-foreground data-[active=true]:[&>svg]:text-sidebar-foreground", { defaultVariants: { size: "default", @@ -795,8 +795,8 @@ function SidebarMenuSubButton({ }) { const defaultProps = { className: cn( - "-translate-x-px flex h-7 min-w-0 cursor-pointer items-center gap-2 overflow-hidden rounded-lg px-2 text-sidebar-foreground outline-hidden ring-ring hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-inset active:bg-sidebar-row-active active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-muted-foreground", - "data-[active=true]:bg-sidebar-row-selected data-[active=true]:text-sidebar-foreground", + "-translate-x-px flex h-7 min-w-0 cursor-pointer items-center gap-2 overflow-hidden rounded-lg px-2 text-sidebar-foreground outline-hidden ring-ring hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-inset active:bg-sidebar-row-hover active:text-sidebar-foreground disabled:pointer-events-none disabled:opacity-64 aria-disabled:pointer-events-none aria-disabled:opacity-64 [&>span:last-child]:truncate [&>svg:not([class*='size-'])]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-muted-foreground", + "data-[active=true]:bg-sidebar-row-active data-[active=true]:text-sidebar-foreground", size === "sm" && "text-xs", size === "md" && "text-sm", "group-data-[collapsible=icon]:hidden", diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 60dc56125d0ba..5c8d49094df12 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -281,6 +281,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --color-sidebar-row-hover: var(--sidebar-row-hover); --color-sidebar-row-active: var(--sidebar-row-active); --color-sidebar-row-selected: var(--sidebar-row-selected); + --color-sidebar-row-ring: var(--sidebar-row-ring); --color-sidebar-border: var(--contrast-sidebar-border); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); @@ -1160,6 +1161,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil /* The current row and settings page carry the accent: blue means "you are here". */ --sidebar-row-active: color-mix(in oklab, var(--primary) 10%, var(--color-white)); --sidebar-row-selected: color-mix(in oklab, var(--primary) 7%, var(--color-white)); + --sidebar-row-ring: color-mix(in oklab, var(--primary) 15%, transparent); --sidebar-border: var(--border); --sidebar-stage-fade: var(--sidebar); --code-background: color-mix(in srgb, var(--card) 90%, var(--background)); @@ -1171,14 +1173,14 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil @variant dark { color-scheme: dark; - /* Raised surfaces (composer, cards) sit one step above the neutral-black - canvas and overlays one more, so they read without milky gray fills. */ + /* Raised surfaces (composer, cards, popovers) sit one step above the + neutral-black canvas; menus separate by border and shadow. */ --background: var(--color-neutral-950); --surface-raised: color-mix(in srgb, var(--background) 93%, var(--color-white)); --foreground: var(--color-neutral-100); --card: color-mix(in srgb, var(--background) 93%, var(--color-white)); --card-foreground: var(--color-neutral-100); - --popover: color-mix(in srgb, var(--background) 90%, var(--color-white)); + --popover: color-mix(in srgb, var(--background) 93%, var(--color-white)); --popover-foreground: var(--color-neutral-100); --primary: oklch(0.571 0.21 264); --link: color-mix(in oklab, var(--primary), var(--color-white) 28%); @@ -1235,6 +1237,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --sidebar-row-hover: var(--color-zinc-25); --sidebar-row-active: color-mix(in oklab, var(--primary) 10%, var(--color-white)); --sidebar-row-selected: color-mix(in oklab, var(--primary) 7%, var(--color-white)); + --sidebar-row-ring: color-mix(in oklab, var(--primary) 15%, transparent); --sidebar-border: var(--color-zinc-200); --sidebar-stage-fade: var(--sidebar); @@ -1328,6 +1331,7 @@ html[data-theme-id]:not([data-theme-id=""]) { --sidebar-row-hover: var(--app-theme-sidebar-row-hover); --sidebar-row-active: var(--app-theme-sidebar-row-active); --sidebar-row-selected: var(--app-theme-sidebar-row-selected); + --sidebar-row-ring: color-mix(in srgb, var(--contrast-sidebar-border) 70%, transparent); --sidebar-border: var(--app-theme-sidebar-border); --sidebar-stage-fade: var(--app-theme-sidebar); --code-background: var(--app-theme-code-background); @@ -1528,6 +1532,7 @@ html[data-theme-id] [data-app-sidebar] { --sidebar-row-hover: var(--app-theme-sidebar-row-hover); --sidebar-row-active: var(--app-theme-sidebar-row-active); --sidebar-row-selected: var(--app-theme-sidebar-row-selected); + --sidebar-row-ring: color-mix(in srgb, var(--contrast-sidebar-border) 70%, transparent); --sidebar-border: var(--app-theme-sidebar-border); --sidebar-stage-fade: var(--app-theme-sidebar); border-color: color-mix(in srgb, var(--contrast-sidebar-foreground) 10%, transparent); From 27f55f90dac82a277dc07f140ded782cee3ba8a5 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 04:07:06 -0700 Subject: [PATCH 08/12] fix(web): settings search highlight stays neutral Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/settings/SettingsSidebarNav.tsx | 1 + apps/web/src/components/ui/sidebar.tsx | 3 +++ 2 files changed, 4 insertions(+) diff --git a/apps/web/src/components/settings/SettingsSidebarNav.tsx b/apps/web/src/components/settings/SettingsSidebarNav.tsx index 9d4642bb4785b..739ab040dfa8c 100644 --- a/apps/web/src/components/settings/SettingsSidebarNav.tsx +++ b/apps/web/src/components/settings/SettingsSidebarNav.tsx @@ -310,6 +310,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { aria-selected={index === activeResultIndex} tabIndex={-1} size="sm" + variant="option" isActive={index === activeResultIndex} className="h-auto min-h-10 items-start" onMouseMove={() => setActiveResultIndex(index)} diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index 14512ac533bc1..0684d2ea66a04 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -699,6 +699,9 @@ const sidebarMenuButtonVariants = cva( variant: { default: "font-medium text-sidebar-muted-foreground/80", outline: "bg-sidebar-control-surface ring-1 ring-sidebar-border", + // Listbox results: the highlight follows the pointer, so it stays neutral; blue marks the current page. + option: + "font-medium text-sidebar-muted-foreground/80 data-[active=true]:bg-sidebar-row-hover", }, }, }, From 6103d1a1c05c857ff0655ecb308a970bc9f082ec Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 04:11:24 -0700 Subject: [PATCH 09/12] feat(web): bolder light current row, themed light rows take the action color Co-Authored-By: Claude Opus 5.5 --- apps/web/src/index.css | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 5c8d49094df12..5855efb91f34c 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1159,9 +1159,9 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --sidebar-control-surface: var(--color-zinc-100); --sidebar-row-hover: var(--color-zinc-25); /* The current row and settings page carry the accent: blue means "you are here". */ - --sidebar-row-active: color-mix(in oklab, var(--primary) 10%, var(--color-white)); - --sidebar-row-selected: color-mix(in oklab, var(--primary) 7%, var(--color-white)); - --sidebar-row-ring: color-mix(in oklab, var(--primary) 15%, transparent); + --sidebar-row-active: color-mix(in oklab, var(--primary) 18%, var(--color-white)); + --sidebar-row-selected: color-mix(in oklab, var(--primary) 12%, var(--color-white)); + --sidebar-row-ring: color-mix(in oklab, var(--primary) 35%, transparent); --sidebar-border: var(--border); --sidebar-stage-fade: var(--sidebar); --code-background: color-mix(in srgb, var(--card) 90%, var(--background)); @@ -1235,9 +1235,9 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil --sidebar-muted-foreground: var(--color-zinc-500); --sidebar-control-surface: var(--color-zinc-100); --sidebar-row-hover: var(--color-zinc-25); - --sidebar-row-active: color-mix(in oklab, var(--primary) 10%, var(--color-white)); - --sidebar-row-selected: color-mix(in oklab, var(--primary) 7%, var(--color-white)); - --sidebar-row-ring: color-mix(in oklab, var(--primary) 15%, transparent); + --sidebar-row-active: color-mix(in oklab, var(--primary) 18%, var(--color-white)); + --sidebar-row-selected: color-mix(in oklab, var(--primary) 12%, var(--color-white)); + --sidebar-row-ring: color-mix(in oklab, var(--primary) 35%, transparent); --sidebar-border: var(--color-zinc-200); --sidebar-stage-fade: var(--sidebar); @@ -1331,7 +1331,17 @@ html[data-theme-id]:not([data-theme-id=""]) { --sidebar-row-hover: var(--app-theme-sidebar-row-hover); --sidebar-row-active: var(--app-theme-sidebar-row-active); --sidebar-row-selected: var(--app-theme-sidebar-row-selected); - --sidebar-row-ring: color-mix(in srgb, var(--contrast-sidebar-border) 70%, transparent); + --sidebar-row-ring: color-mix(in oklab, var(--primary) 30%, transparent); + + /* Light theme row fills can sit near white, so the current row also takes + the theme's action color. */ + @variant light { + --sidebar-row-active: color-mix( + in oklab, + var(--primary) 16%, + var(--app-theme-sidebar-row-active) + ); + } --sidebar-border: var(--app-theme-sidebar-border); --sidebar-stage-fade: var(--app-theme-sidebar); --code-background: var(--app-theme-code-background); @@ -1532,7 +1542,17 @@ html[data-theme-id] [data-app-sidebar] { --sidebar-row-hover: var(--app-theme-sidebar-row-hover); --sidebar-row-active: var(--app-theme-sidebar-row-active); --sidebar-row-selected: var(--app-theme-sidebar-row-selected); - --sidebar-row-ring: color-mix(in srgb, var(--contrast-sidebar-border) 70%, transparent); + --sidebar-row-ring: color-mix(in oklab, var(--primary) 30%, transparent); + + /* Light theme row fills can sit near white, so the current row also takes + the theme's action color. */ + @variant light { + --sidebar-row-active: color-mix( + in oklab, + var(--primary) 16%, + var(--app-theme-sidebar-row-active) + ); + } --sidebar-border: var(--app-theme-sidebar-border); --sidebar-stage-fade: var(--app-theme-sidebar); border-color: color-mix(in srgb, var(--contrast-sidebar-foreground) 10%, transparent); From 0e16181162b3145f6c54a0e525818a3918f6ae1b Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 04:15:06 -0700 Subject: [PATCH 10/12] fix(web): themed light current row matches the default strength Co-Authored-By: Claude Opus 5.5 --- apps/web/src/index.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 5855efb91f34c..020cd559861b0 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1331,14 +1331,14 @@ html[data-theme-id]:not([data-theme-id=""]) { --sidebar-row-hover: var(--app-theme-sidebar-row-hover); --sidebar-row-active: var(--app-theme-sidebar-row-active); --sidebar-row-selected: var(--app-theme-sidebar-row-selected); - --sidebar-row-ring: color-mix(in oklab, var(--primary) 30%, transparent); + --sidebar-row-ring: color-mix(in oklab, var(--primary) 35%, transparent); /* Light theme row fills can sit near white, so the current row also takes the theme's action color. */ @variant light { --sidebar-row-active: color-mix( in oklab, - var(--primary) 16%, + var(--primary) 20%, var(--app-theme-sidebar-row-active) ); } @@ -1542,14 +1542,14 @@ html[data-theme-id] [data-app-sidebar] { --sidebar-row-hover: var(--app-theme-sidebar-row-hover); --sidebar-row-active: var(--app-theme-sidebar-row-active); --sidebar-row-selected: var(--app-theme-sidebar-row-selected); - --sidebar-row-ring: color-mix(in oklab, var(--primary) 30%, transparent); + --sidebar-row-ring: color-mix(in oklab, var(--primary) 35%, transparent); /* Light theme row fills can sit near white, so the current row also takes the theme's action color. */ @variant light { --sidebar-row-active: color-mix( in oklab, - var(--primary) 16%, + var(--primary) 20%, var(--app-theme-sidebar-row-active) ); } From cd0d1ee88cb910b05c6ae13b2eceadb70c12f1c6 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 04:19:35 -0700 Subject: [PATCH 11/12] fix(web): muted text on tinted light rows keeps 4.5:1 Co-Authored-By: Claude Opus 5.5 --- .../components/sidebar/SidebarThreadRow.tsx | 4 ++-- apps/web/src/index.css | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx index ef57e37f74094..cc3bd0c5355ae 100644 --- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx +++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx @@ -896,9 +896,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP !props.isActive && "[&:not(:hover):not(:focus-within)_*]:text-secondary-label/70", props.isActive - ? "bg-sidebar-row-active text-sidebar-foreground light:shadow-xs light:ring-1 light:ring-inset light:ring-sidebar-row-ring" + ? "bg-sidebar-row-active text-sidebar-foreground light:shadow-xs light:ring-1 light:ring-inset light:ring-sidebar-row-ring light:sidebar-row-tinted-text" : isSelected || props.sweepAction !== null - ? "bg-sidebar-row-selected text-sidebar-foreground light:ring-1 light:ring-inset light:ring-sidebar-row-ring" + ? "bg-sidebar-row-selected text-sidebar-foreground light:ring-1 light:ring-inset light:ring-sidebar-row-ring light:sidebar-row-tinted-text" : hasUnsentDraft ? cn(draftSurfaceClassName, "text-sidebar-foreground") : shouldRecede diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 020cd559861b0..3b15bf7876884 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -340,6 +340,26 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil } } +/* Tinted current rows: muted text steps toward the foreground to keep 4.5:1 + on the light accent fill. Mixing the theme's own colors keeps theme hues. */ +@utility sidebar-row-tinted-text { + --contrast-secondary-label: color-mix( + in oklab, + var(--secondary-label), + var(--sidebar-foreground) 30% + ); + --contrast-muted-foreground: color-mix( + in oklab, + var(--muted-foreground), + var(--sidebar-foreground) 30% + ); + --contrast-sidebar-muted-foreground: color-mix( + in oklab, + var(--sidebar-muted-foreground), + var(--sidebar-foreground) 30% + ); +} + @utility surface-glass { background: color-mix(in srgb, var(--background) var(--glass-opacity), transparent); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); From 011b61452e5adba95f925f01e8bb43df360d05f2 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Sun, 25 Oct 2026 04:22:34 -0700 Subject: [PATCH 12/12] fix(web): tinted row text follows the contrast setting Co-Authored-By: Claude Opus 5.5 --- apps/web/src/index.css | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 3b15bf7876884..9be6248ec2d3b 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -341,22 +341,37 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil } /* Tinted current rows: muted text steps toward the foreground to keep 4.5:1 - on the light accent fill. Mixing the theme's own colors keeps theme hues. */ + on the light accent fill, inside the same contrast wrap as other text. */ @utility sidebar-row-tinted-text { --contrast-secondary-label: color-mix( in oklab, - var(--secondary-label), - var(--sidebar-foreground) 30% + color-mix( + in oklab, + color-mix(in oklab, var(--secondary-label), var(--sidebar-foreground) 30%) + var(--appearance-contrast-base), + var(--background) + ), + var(--appearance-contrast-target) var(--appearance-contrast-boost) ); --contrast-muted-foreground: color-mix( in oklab, - var(--muted-foreground), - var(--sidebar-foreground) 30% + color-mix( + in oklab, + color-mix(in oklab, var(--muted-foreground), var(--sidebar-foreground) 30%) + var(--appearance-contrast-base), + var(--background) + ), + var(--appearance-contrast-target) var(--appearance-contrast-boost) ); --contrast-sidebar-muted-foreground: color-mix( in oklab, - var(--sidebar-muted-foreground), - var(--sidebar-foreground) 30% + color-mix( + in oklab, + color-mix(in oklab, var(--sidebar-muted-foreground), var(--sidebar-foreground) 30%) + var(--appearance-contrast-base), + var(--sidebar) + ), + var(--appearance-contrast-target) var(--appearance-contrast-boost) ); }