From 1474657d0305ab9c080ac0ab3bde0b615b55b19c Mon Sep 17 00:00:00 2001 From: Arnas Donauskas Date: Wed, 17 Jun 2026 09:28:22 +0300 Subject: [PATCH] fix(theme): light-mode polish from review pass Self-review of the light/dark sweep surfaced neutral colors that lived in raw inline-style / diff --git a/src/components/pipelines/pipeline-settings.tsx b/src/components/pipelines/pipeline-settings.tsx index 4880767..47036b8 100644 --- a/src/components/pipelines/pipeline-settings.tsx +++ b/src/components/pipelines/pipeline-settings.tsx @@ -292,7 +292,9 @@ export function PipelineSettings({ style={{ backgroundColor: color, borderColor: - newStageColor === color ? "white" : "transparent", + newStageColor === color + ? "var(--foreground)" + : "transparent", }} aria-label={`Pick color ${color}`} /> @@ -450,7 +452,8 @@ function ColorSwatch({ className="h-5 w-5 rounded-full border-2 transition-transform hover:scale-110" style={{ backgroundColor: c, - borderColor: c === value ? "white" : "transparent", + borderColor: + c === value ? "var(--foreground)" : "transparent", }} /> ))} diff --git a/src/components/settings/members-tab.tsx b/src/components/settings/members-tab.tsx index d339dbd..aa30393 100644 --- a/src/components/settings/members-tab.tsx +++ b/src/components/settings/members-tab.tsx @@ -115,7 +115,9 @@ const ROLE_CHIP: Record< viewer: { icon: UserIcon, label: 'Viewer', - className: 'border-border bg-card text-muted-foreground', + // Outline-only so it stays quieter than the filled Agent chip in + // both modes — bg-card would blend into a card surface in light mode. + className: 'border-border bg-transparent text-muted-foreground', }, }; diff --git a/src/components/settings/tag-manager.tsx b/src/components/settings/tag-manager.tsx index 696e7fb..b23fdcf 100644 --- a/src/components/settings/tag-manager.tsx +++ b/src/components/settings/tag-manager.tsx @@ -241,7 +241,7 @@ export function TagManager() { className="relative size-8 rounded-full transition-transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-popover" style={{ backgroundColor: color.value, - boxShadow: selectedColor === color.value ? `0 0 0 2px rgb(15 23 42), 0 0 0 4px ${color.value}` : 'none', + boxShadow: selectedColor === color.value ? `0 0 0 2px var(--popover), 0 0 0 4px ${color.value}` : 'none', }} title={color.name} /> diff --git a/src/components/themed-toaster.tsx b/src/components/themed-toaster.tsx index 9db285b..bd73f30 100644 --- a/src/components/themed-toaster.tsx +++ b/src/components/themed-toaster.tsx @@ -1,8 +1,23 @@ "use client"; +import { useSyncExternalStore } from "react"; import { Toaster } from "sonner"; import { useTheme } from "@/hooks/use-theme"; +import { DEFAULT_MODE } from "@/lib/themes"; + +// Returns false during SSR and the first hydration render, true after — +// the sanctioned (warning-free, no setState-in-effect) way to diverge +// server vs client. Lets us match the server-rendered default on first +// paint, then adopt the real mode. +const noopSubscribe = () => () => {}; +function useIsClient() { + return useSyncExternalStore( + noopSubscribe, + () => true, + () => false, + ); +} /** * Toaster wrapper that tracks the active light/dark mode. @@ -11,12 +26,18 @@ import { useTheme } from "@/hooks/use-theme"; * current mode and hand it to sonner. Colors are driven off the same * CSS tokens as the rest of the app, so a toast looks at home in * either mode without a second palette to maintain. + * + * The theme is gated behind `useIsClient`: the server renders + * DEFAULT_MODE, so first client paint must too, otherwise a light-mode + * user hydrates with a different sonner `theme` attribute than the + * server emitted and React logs a hydration mismatch. */ export function ThemedToaster() { const { mode } = useTheme(); + const isClient = useIsClient(); return (