diff --git a/src/app/(auth)/forgot-password/page.tsx b/src/app/(auth)/forgot-password/page.tsx index e1c5183..d2a1784 100644 --- a/src/app/(auth)/forgot-password/page.tsx +++ b/src/app/(auth)/forgot-password/page.tsx @@ -119,7 +119,7 @@ export default function ForgotPasswordPage() { Back to sign in diff --git a/src/components/automations/automation-builder.tsx b/src/components/automations/automation-builder.tsx index e06e569..d1034bd 100644 --- a/src/components/automations/automation-builder.tsx +++ b/src/components/automations/automation-builder.tsx @@ -587,7 +587,7 @@ export function AutomationBuilder({ initial }: { initial: BuilderInitial }) { {/* Canvas */}
-
+
- +

Click to choose a CSV file diff --git a/src/components/dashboard/activity-feed.tsx b/src/components/dashboard/activity-feed.tsx index 7eac577..346a485 100644 --- a/src/components/dashboard/activity-feed.tsx +++ b/src/components/dashboard/activity-feed.tsx @@ -85,9 +85,10 @@ export function ActivityFeed({ items, loading }: ActivityFeedProps) { {visible.map((it, i) => { const theme = KIND_THEME[it.kind] const Icon = theme.icon - // Alternating row background for scanability — dark-theme - // translation of the spec's white / #f9fafb stripes. - const stripe = i % 2 === 0 ? 'bg-transparent' : 'bg-card/40' + // Alternating row background for scanability. bg-muted/40 + // keeps the stripe visible in both light and dark modes + // (bg-card/40 vanishes against a white card surface in light). + const stripe = i % 2 === 0 ? 'bg-transparent' : 'bg-muted/40' const row = (

diff --git a/src/components/dashboard/pipeline-donut.tsx b/src/components/dashboard/pipeline-donut.tsx index 2d027d4..43063db 100644 --- a/src/components/dashboard/pipeline-donut.tsx +++ b/src/components/dashboard/pipeline-donut.tsx @@ -98,7 +98,7 @@ function Donut({ data, currency }: { data: PipelineDonutData; currency: string }
{/* background ring */} - + {segments.map((seg) => ( {reply && ( - + )}
void; + /** True when embedded inside an outbound (primary-filled) bubble, so the + * quote must read against the primary surface rather than the neutral + * foreground — otherwise it goes low-contrast in light mode. */ + onPrimary?: boolean; } export function ReplyQuote({ authorLabel, preview, onDismiss, + onPrimary = false, }: ReplyQuoteProps) { const isChip = !!onDismiss; return (
-
+
{authorLabel}
{/* Wrap the preview instead of truncating to a single line. diff --git a/src/components/pipelines/pipeline-board.tsx b/src/components/pipelines/pipeline-board.tsx index be934a5..6477207 100644 --- a/src/components/pipelines/pipeline-board.tsx +++ b/src/components/pipelines/pipeline-board.tsx @@ -164,7 +164,7 @@ export function PipelineBoard({ @media (hover: hover) and (pointer: fine) { .pipeline-scroll { scrollbar-width: thin; - scrollbar-color: rgb(51 65 85) transparent; + scrollbar-color: var(--border) transparent; } .pipeline-scroll::-webkit-scrollbar { height: 8px; @@ -173,11 +173,11 @@ export function PipelineBoard({ background: transparent; } .pipeline-scroll::-webkit-scrollbar-thumb { - background-color: rgb(51 65 85); + background-color: var(--border); border-radius: 9999px; } .pipeline-scroll::-webkit-scrollbar-thumb:hover { - background-color: rgb(71 85 105); + background-color: var(--muted-foreground); } } `} 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 (