diff --git a/apps/webapp/app/components/code/QueryResultsChart.tsx b/apps/webapp/app/components/code/QueryResultsChart.tsx
index fd7b3c32f..ef4030b10 100644
--- a/apps/webapp/app/components/code/QueryResultsChart.tsx
+++ b/apps/webapp/app/components/code/QueryResultsChart.tsx
@@ -12,7 +12,7 @@ import { ChartBlankState } from "../primitives/charts/ChartBlankState";
import { Callout } from "../primitives/Callout";
import type { AggregationType, ChartConfiguration } from "../metrics/QueryWidget";
import { aggregateValues } from "../primitives/charts/aggregation";
-import { getRunStatusHexColor } from "~/components/runs/v3/TaskRunStatus";
+import { getRunStatusChartColor } from "~/components/runs/v3/TaskRunStatus";
import { getSeriesColor } from "./chartColors";
const MAX_SERIES = 50;
@@ -936,7 +936,7 @@ export const QueryResultsChart = memo(function QueryResultsChart({
const chartConfig = useMemo(() => {
const cfg: ChartConfig = {};
sortedSeries.forEach((s, i) => {
- const statusColor = groupByIsRunStatus ? getRunStatusHexColor(s) : undefined;
+ const statusColor = groupByIsRunStatus ? getRunStatusChartColor(s) : undefined;
const originalIndex = config.yAxisColumns.indexOf(s);
const colorIndex = originalIndex >= 0 ? originalIndex : i;
cfg[s] = {
diff --git a/apps/webapp/app/components/primitives/charts/Chart.tsx b/apps/webapp/app/components/primitives/charts/Chart.tsx
index ed23d41cd..85d283368 100644
--- a/apps/webapp/app/components/primitives/charts/Chart.tsx
+++ b/apps/webapp/app/components/primitives/charts/Chart.tsx
@@ -5,7 +5,7 @@ import { AnimatedNumber } from "../AnimatedNumber";
import TooltipPortal from "../TooltipPortal";
// Format: { THEME_NAME: CSS_SELECTOR }
-const THEMES = { light: "", dark: ".dark" } as const;
+const THEMES = { light: "", dark: '[data-theme="dark"]' } as const;
export type ChartState = "loading" | "noData" | "invalid" | "loaded" | undefined;
diff --git a/apps/webapp/app/components/primitives/charts/ChartBar.tsx b/apps/webapp/app/components/primitives/charts/ChartBar.tsx
index ef9daeb53..73ffb35c5 100644
--- a/apps/webapp/app/components/primitives/charts/ChartBar.tsx
+++ b/apps/webapp/app/components/primitives/charts/ChartBar.tsx
@@ -20,8 +20,8 @@ import { useXAxisTicks } from "./useXAxisTicks";
import { useChartSync } from "./ChartSyncContext";
import { ZoomTooltip, useZoomHandlers } from "./ChartZoom";
-// charcoal-500: dashed line mirroring the hovered x across synced charts.
-const SYNC_LINE_COLOR = "#5F6570";
+// Dashed line mirroring the hovered x across synced charts.
+const SYNC_LINE_COLOR = "var(--color-text-faint)";
// Shared with ChartLine so bar/line align when toggling. Right margin keeps the
// centered last x-axis label from clipping; bottom gives angled labels room.
@@ -228,7 +228,7 @@ export function ChartBarRenderer({
onClick={zoomHandlers.onClick}
onMouseLeave={handleMouseLeave}
>
-
+
)}
@@ -332,9 +332,9 @@ export function ChartBarRenderer({
x1={syncZoomSelection.start}
x2={syncZoomSelection.current}
isFront
- stroke="#3B82F6"
+ stroke="var(--color-pending)"
strokeOpacity={0.3}
- fill="#3B82F6"
+ fill="var(--color-pending)"
fillOpacity={0.15}
className="pointer-events-none"
/>
@@ -360,11 +360,11 @@ export function ChartBarRenderer({
label={{
position: "top",
value: referenceLine.label,
- fill: "#878C99",
+ fill: "var(--color-text-dimmed)",
fontSize: 11,
}}
isFront={true}
- stroke="#3B3E45"
+ stroke="var(--color-border-bright)"
strokeDasharray="4 4"
className="pointer-events-none"
/>
@@ -374,7 +374,7 @@ export function ChartBarRenderer({
{enableZoom && zoom?.inspectionLine && (
{
diff --git a/apps/webapp/app/components/primitives/charts/statusColors.ts b/apps/webapp/app/components/primitives/charts/statusColors.ts
index 2ae7470a2..8439b4d32 100644
--- a/apps/webapp/app/components/primitives/charts/statusColors.ts
+++ b/apps/webapp/app/components/primitives/charts/statusColors.ts
@@ -1,17 +1,18 @@
-/** Shared status → color map for the task/agent activity charts. */
+/** Shared status → color map for the task/agent activity charts.
+ * Values are CSS variables so they follow the theme; CSS contexts only. */
export const STATUS_COLOR: Record = {
// Run-status groups
- COMPLETED: "#28BF5C",
- RUNNING: "#3B82F6",
- FAILED: "#E11D48",
- CANCELED: "#878C99",
+ COMPLETED: "var(--color-success)",
+ RUNNING: "var(--color-pending)",
+ FAILED: "var(--color-error)",
+ CANCELED: "var(--color-text-dimmed)",
// Agent session statuses
- ACTIVE: "#3B82F6",
- CLOSED: "#28BF5C",
- EXPIRED: "#878C99",
+ ACTIVE: "var(--color-pending)",
+ CLOSED: "var(--color-success)",
+ EXPIRED: "var(--color-text-dimmed)",
};
-export const STATUS_COLOR_FALLBACK = "#9CA3AF";
+export const STATUS_COLOR_FALLBACK = "var(--color-text-dimmed)";
export function statusColor(status: string): string {
return STATUS_COLOR[status] ?? STATUS_COLOR_FALLBACK;
diff --git a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx
index bccb30a32..9d20f5a52 100644
--- a/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx
+++ b/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx
@@ -290,40 +290,41 @@ export const runStatusTitleFromStatus: Record
const titlesStatusesArray = Object.entries(runStatusTitleFromStatus);
/**
- * Hex color for each TaskRunStatus, mirroring `runStatusClassNameColor` but as
- * concrete hex values for non-CSS contexts (e.g. chart series colors).
+ * Chart series color for each TaskRunStatus, mirroring `runStatusClassNameColor`.
+ * Values are CSS variables (defined in tailwind.css) so they follow the theme;
+ * only usable in CSS contexts (SVG attributes, chart config styles).
*/
-const RUN_STATUS_HEX_COLORS: Record = {
- PENDING: "#5F6570", // charcoal-500
- DELAYED: "#6B7580", // charcoal ~450
- PENDING_VERSION: "#f59e0b", // amber-500
- WAITING_FOR_DEPLOY: "#d97706", // amber-600
- EXECUTING: "#3b82f6", // blue-500
- RETRYING_AFTER_FAILURE: "#2f6fec", // blue ~550
- DEQUEUED: "#4D8EF5", // blue ~475
- WAITING_TO_RESUME: "#555D67", // charcoal ~550
- PAUSED: "#fbbf24", // amber-400
- CANCELED: "#78828C", // charcoal ~400
- EXPIRED: "#848D96", // charcoal ~350
- INTERRUPTED: "#D52C4D", // rose — evenly spaced (error)
- COMPLETED_SUCCESSFULLY: "#28BF5C", // mint-500 (success)
- COMPLETED_WITH_ERRORS: "#DE405C", // rose — evenly spaced (error)
- SYSTEM_FAILURE: "#E7536C", // rose — evenly spaced (error)
- CRASHED: "#cc193d", // rose — darkest (error)
- TIMED_OUT: "#F0667B", // rose — lightest (error)
+const RUN_STATUS_CHART_COLORS: Record = {
+ PENDING: "var(--color-run-pending)",
+ DELAYED: "var(--color-run-delayed)",
+ PENDING_VERSION: "var(--color-run-pending-version)",
+ WAITING_FOR_DEPLOY: "var(--color-run-waiting-for-deploy)",
+ EXECUTING: "var(--color-run-executing)",
+ RETRYING_AFTER_FAILURE: "var(--color-run-retrying-after-failure)",
+ DEQUEUED: "var(--color-run-dequeued)",
+ WAITING_TO_RESUME: "var(--color-run-waiting-to-resume)",
+ PAUSED: "var(--color-run-paused)",
+ CANCELED: "var(--color-run-canceled)",
+ EXPIRED: "var(--color-run-expired)",
+ INTERRUPTED: "var(--color-run-interrupted)",
+ COMPLETED_SUCCESSFULLY: "var(--color-run-completed-successfully)",
+ COMPLETED_WITH_ERRORS: "var(--color-run-completed-with-errors)",
+ SYSTEM_FAILURE: "var(--color-run-system-failure)",
+ CRASHED: "var(--color-run-crashed)",
+ TIMED_OUT: "var(--color-run-timed-out)",
};
/**
- * Get the hex color for a run status value. Accepts either a raw TaskRunStatus
+ * Get the chart color for a run status value. Accepts either a raw TaskRunStatus
* (e.g. "COMPLETED_SUCCESSFULLY") or a friendly name (e.g. "Completed").
* Returns `undefined` when the value is not a recognised status.
*/
-export function getRunStatusHexColor(value: string): string | undefined {
+export function getRunStatusChartColor(value: string): string | undefined {
if (isTaskRunStatus(value)) {
- return RUN_STATUS_HEX_COLORS[value];
+ return RUN_STATUS_CHART_COLORS[value];
}
if (isRunFriendlyStatus(value)) {
- return RUN_STATUS_HEX_COLORS[runStatusFromFriendlyTitle(value)];
+ return RUN_STATUS_CHART_COLORS[runStatusFromFriendlyTitle(value)];
}
return undefined;
}
diff --git a/apps/webapp/app/root.tsx b/apps/webapp/app/root.tsx
index 66a33b5ce..b1027c498 100644
--- a/apps/webapp/app/root.tsx
+++ b/apps/webapp/app/root.tsx
@@ -93,7 +93,7 @@ export const shouldRevalidate: ShouldRevalidateFunction = (options) => {
export function ErrorBoundary() {
return (
<>
-
+
@@ -121,7 +121,7 @@ export default function App() {
return (
<>
-
+
diff --git a/apps/webapp/app/tailwind.css b/apps/webapp/app/tailwind.css
index 7d663fb54..d1bf953a3 100644
--- a/apps/webapp/app/tailwind.css
+++ b/apps/webapp/app/tailwind.css
@@ -212,22 +212,50 @@
}
/*
- shadcn/ui tokens used by streamdown's internals. Inline so the HSL vars
- resolve where the utility is used - .streamdown-container redefines them locally.
+ Run-status chart colors. In-between shades are intentional - statuses within
+ a family (blues, roses, charcoals) are evenly spaced so chart series stay
+ distinguishable. Referenced from JS via var(--color-run-*).
+*/
+@theme {
+ --color-run-pending: var(--color-charcoal-500);
+ --color-run-delayed: #6b7580;
+ --color-run-pending-version: var(--color-warning);
+ --color-run-waiting-for-deploy: #d97706;
+ --color-run-executing: var(--color-pending);
+ --color-run-retrying-after-failure: #2f6fec;
+ --color-run-dequeued: #4d8ef5;
+ --color-run-waiting-to-resume: #555d67;
+ --color-run-paused: #fbbf24;
+ --color-run-canceled: #78828c;
+ --color-run-expired: #848d96;
+ --color-run-interrupted: #d52c4d;
+ --color-run-completed-successfully: var(--color-success);
+ --color-run-completed-with-errors: #de405c;
+ --color-run-system-failure: #e7536c;
+ --color-run-crashed: #cc193d;
+ --color-run-timed-out: #f0667b;
+}
+
+/*
+ shadcn/ui tokens used by streamdown's internals. Inline so the vars resolve
+ where the utility is used - .streamdown-container redefines them locally.
*/
@theme inline {
- --color-background: hsl(var(--background, 230 16% 9%));
- --color-foreground: hsl(var(--foreground, 215 19% 87%));
- --color-muted: hsl(var(--muted, 220 8% 17%));
- --color-muted-foreground: hsl(var(--muted-foreground, 220 8% 57%));
- --color-border: hsl(var(--border, 216 7% 27%));
- --color-sidebar: hsl(var(--sidebar, 228 10% 11%));
- --color-primary-foreground: hsl(var(--primary-foreground, 230 16% 9%));
+ --color-background: var(--background, #121317);
+ --color-foreground: var(--foreground, #d7d9dd);
+ --color-muted: var(--muted, #1c1e21);
+ --color-muted-foreground: var(--muted-foreground, #878c99);
+ --color-border: var(--border, #2c3034);
+ --color-sidebar: var(--sidebar, #15171a);
+ --color-primary-foreground: var(--primary-foreground, #121317);
}
@custom-variant lg-height (@media (max-height: 750px));
@custom-variant md-height (@media (max-height: 600px));
+/* dark: follows the app theme (data-theme on ), not the OS preference */
+@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
+
@utility focus-custom {
&:focus-visible {
outline: 1px solid var(--color-text-link);
@@ -328,6 +356,14 @@
@apply bg-text-bright/30 text-text-bright;
}
+ /* Native form controls, scrollbars etc. follow the app theme */
+ :root {
+ color-scheme: dark;
+ }
+ [data-theme="light"] {
+ color-scheme: light;
+ }
+
/* shadcn charts: https://ui.shadcn.com/docs/components/chart#add-a-grid */
:root {
--chart-1: 12 76% 61%;
@@ -337,7 +373,7 @@
--chart-5: 27 87% 67%;
}
- .dark {
+ [data-theme="dark"] {
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
@@ -385,18 +421,18 @@
/* Streamdown markdown styling */
.streamdown-container {
- /* Streamdown uses shadcn/ui CSS variables - define them for our theme.
- These map Tailwind utility classes like bg-background, bg-primary, etc.
- that streamdown uses internally for its link safety modal, code blocks,
- and other interactive elements. */
- --background: 230 16% 9%; /* charcoal-900 #121317 */
- --foreground: 215 19% 87%; /* charcoal-200 #D7D9DD */
- --muted: 220 8% 17%; /* charcoal-775 #1C1E21 */
- --muted-foreground: 220 8% 57%; /* charcoal-400 #878C99 */
- --border: 216 7% 27%; /* charcoal-650 #2C3034 */
- --primary: 95 100% 66%; /* apple-500 #A8FF53 */
- --primary-foreground: 230 16% 9%; /* charcoal-900 */
- --sidebar: 228 10% 11%; /* charcoal-850 #15171A */
+ /* Streamdown uses shadcn/ui CSS variables - derive them from our semantic
+ tokens so they follow the theme. They map Tailwind utility classes like
+ bg-background, bg-primary, etc. that streamdown uses internally for its
+ link safety modal, code blocks, and other interactive elements. */
+ --background: var(--color-background-deep);
+ --foreground: var(--color-text-bright);
+ --muted: var(--color-charcoal-775);
+ --muted-foreground: var(--color-text-dimmed);
+ --border: var(--color-border-bright);
+ --primary: var(--color-primary);
+ --primary-foreground: var(--color-background-deep);
+ --sidebar: var(--color-background-dimmed);
/* Code block styling */
& [data-code-block-container] {
@@ -479,13 +515,6 @@
& [data-code-block-header] {
@apply bg-charcoal-800 text-text-dimmed border-b border-charcoal-700;
}
- /* Hide light mode code block, show dark mode */
- & [data-code-block].dark\:hidden {
- display: none !important;
- }
- & [data-code-block].hidden.dark\:block {
- display: block !important;
- }
/* Override the bg-muted/40 class to let inline styles work */
& [data-code-block] pre {
background-color: inherit !important;