+
{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 (