chore(webapp): theme-ready charts, streamdown and dark variant
Run-status and chart colors in JS now reference var(--color-*) tokens (run-status palette added to the theme). Streamdown container vars derive from semantic tokens instead of hardcoded dark HSL values; removed dead forced-dark code block overrides. dark: variant is now driven by data-theme on <html> (set to dark) instead of the OS preference, with color-scheme following the theme.
This commit is contained in:
@@ -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] = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<CartesianGrid vertical={false} stroke="#272A2E" />
|
||||
<CartesianGrid vertical={false} stroke="var(--color-grid-bright)" />
|
||||
<XAxis
|
||||
dataKey={dataKey}
|
||||
tickLine={false}
|
||||
@@ -237,7 +237,7 @@ export function ChartBarRenderer({
|
||||
ticks={baseXTicks}
|
||||
interval={baseXInterval}
|
||||
tick={{
|
||||
fill: "#878C99",
|
||||
fill: "var(--color-text-dimmed)",
|
||||
fontSize: 11,
|
||||
style: { fontVariantNumeric: "tabular-nums" },
|
||||
}}
|
||||
@@ -249,7 +249,7 @@ export function ChartBarRenderer({
|
||||
tickMargin={8}
|
||||
width={computedYAxisWidth}
|
||||
tick={{
|
||||
fill: "#878C99",
|
||||
fill: "var(--color-text-dimmed)",
|
||||
fontSize: 11,
|
||||
style: { fontVariantNumeric: "tabular-nums" },
|
||||
}}
|
||||
@@ -288,7 +288,7 @@ export function ChartBarRenderer({
|
||||
x1={zoom.refAreaLeft}
|
||||
x2={zoom.refAreaRight}
|
||||
strokeOpacity={0.4}
|
||||
fill="#3B82F6"
|
||||
fill="var(--color-pending)"
|
||||
fillOpacity={0.3}
|
||||
/>
|
||||
)}
|
||||
@@ -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 && (
|
||||
<ReferenceLine
|
||||
x={zoom.inspectionLine}
|
||||
stroke="#D7D9DD"
|
||||
stroke="var(--color-text-bright)"
|
||||
strokeWidth={2}
|
||||
isFront={true}
|
||||
onClick={(e: any) => {
|
||||
|
||||
@@ -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<string, string> = {
|
||||
// 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;
|
||||
|
||||
@@ -290,40 +290,41 @@ export const runStatusTitleFromStatus: Record<TaskRunStatus, RunFriendlyStatus>
|
||||
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<TaskRunStatus, string> = {
|
||||
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<TaskRunStatus, string> = {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export const shouldRevalidate: ShouldRevalidateFunction = (options) => {
|
||||
export function ErrorBoundary() {
|
||||
return (
|
||||
<>
|
||||
<html lang="en" className="h-full">
|
||||
<html lang="en" className="h-full" data-theme="dark">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function App() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<html lang="en" className="h-full">
|
||||
<html lang="en" className="h-full" data-theme="dark">
|
||||
<head>
|
||||
<Meta />
|
||||
<Links />
|
||||
|
||||
@@ -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 <html>), 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;
|
||||
|
||||
Reference in New Issue
Block a user