From bd39962e9bfa24cd8efcaba77137e88d9fc0b680 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Sat, 18 Jul 2026 18:34:49 +0200 Subject: [PATCH] TimeSeriesChart: drop downtime bands entirely; gaps in the line speak for themselves --- src/components/time-series-chart/chart.tsx | 17 +-- src/components/time-series-chart/index.tsx | 1 - src/components/time-series-chart/series.tsx | 117 +------------------- 3 files changed, 8 insertions(+), 127 deletions(-) diff --git a/src/components/time-series-chart/chart.tsx b/src/components/time-series-chart/chart.tsx index bdaca025..bdd76dfe 100644 --- a/src/components/time-series-chart/chart.tsx +++ b/src/components/time-series-chart/chart.tsx @@ -6,7 +6,7 @@ import { type LineWithColor, } from "./scales"; import { YAxis, XAxis } from "./axis"; -import { SeriesGradients, SeriesPaths, DowntimeBands, HoverMarkers, type DrawnLine } from "./series"; +import { SeriesGradients, SeriesPaths, HoverMarkers, type DrawnLine } from "./series"; import { Legend } from "./legend"; import { Tooltip } from "./tooltip"; @@ -22,11 +22,6 @@ type ChartProps = { onZoom?: (next: { startFrac: number; endFrac: number } | null) => void; onToggleExclude?: (slug: string) => void; onResetExcluded?: () => void; - /** Only render the downtime bands + pill labels when the parent bench - * opted in (spec declared live_activity). Otherwise natural nulls in - * any bench's series24h would fire a "DATA MISSING" pill on charts - * that never asked for the feature (observed on rpc-reliability). */ - showDowntime?: boolean; }; export function Chart({ @@ -38,7 +33,6 @@ export function Chart({ onZoom, onToggleExclude, onResetExcluded, - showDowntime = false, }: ChartProps) { const W = 1000; const H = 360; @@ -392,12 +386,9 @@ export function Chart({ {/* X tick labels */} - {/* Downtime bands. Rendered before the lines so the stroke stays - on top of the highlight. Contiguous null-buckets (from spec - `unless changes == 0`) become visible red columns. */} - {showDowntime && } - - {/* Areas + lines */} + {/* Areas + lines. Gaps in the series render as natural breaks in + the stroke — no tinted band, no pill: the missing pixels are + the signal. */} {/* Drag-to-zoom selection rect. Brushed window highlighted in diff --git a/src/components/time-series-chart/index.tsx b/src/components/time-series-chart/index.tsx index 4a2e2bc3..6ed8ddb3 100644 --- a/src/components/time-series-chart/index.tsx +++ b/src/components/time-series-chart/index.tsx @@ -483,7 +483,6 @@ export function TimeSeriesChart({ onZoom={setZoom} onToggleExclude={toggle} onResetExcluded={excluded.size > 0 ? reset : undefined} - showDowntime={benchmark.results.some((r) => r.liveStatus != null)} /> )} diff --git a/src/components/time-series-chart/series.tsx b/src/components/time-series-chart/series.tsx index 3b6865b1..afe17328 100644 --- a/src/components/time-series-chart/series.tsx +++ b/src/components/time-series-chart/series.tsx @@ -16,119 +16,10 @@ type SeriesPathsProps = { unit: string; }; -type DowntimeBandsProps = { - drawn: DrawnLine[]; - padT: number; - innerH: number; -}; - -/** Colored rectangles that highlight the exact window each line was - * silent (contiguous gap indices), plus a high-contrast pill label - * above each band naming the provider that dropped off. Pills stack - * vertically when multiple providers went silent at the same time so - * simultaneous outages read as separate lines instead of colliding - * as unreadable overlapping text. Rendered under SeriesPaths so the - * line stroke stays on top; skipped for excluded (legend-toggled) - * providers so their bands disappear with the line. */ -export function DowntimeBands({ drawn, padT, innerH }: DowntimeBandsProps) { - // Collect every gap band across every visible provider. Labels used to - // be rendered on top of each band; they were removed because the pill - // often mislabelled the cause (harness reconnect vs real crash vs - // sample alignment artefact). The tinted band + dashed edges alone - // read as "no data here" without asserting a cause. - type Band = { - slug: string; - color: string; - x: number; - w: number; - }; - const all: Band[] = []; - for (const d of drawn) { - if (d.excluded) continue; - // Only count gap runs that start AFTER the first observed sample. - // A leading run of nulls (Prom retention shorter than the visible - // window, harness started mid-range, or provider added recently) - // is "we didn't measure yet", not "provider was down for a week". - // Without this guard the 30D view on aggregator-head-lag rendered - // a chart-wide band during Prom's initial fill period. - let seenData = false; - let runStart: number | null = null; - const push = (endX: number) => { - if (runStart == null) return; - if (!seenData) { - runStart = null; - return; - } - const startX = d.pts[runStart].x; - all.push({ - slug: d.slug, - color: d.color, - x: startX, - w: Math.max(6, endX - startX), - }); - runStart = null; - }; - for (let i = 0; i < d.pts.length; i++) { - const p = d.pts[i]; - if (p.gap) { - if (runStart == null) runStart = i; - } else { - push(d.pts[i].x); - seenData = true; - } - } - // Trailing gap that runs to the current time — meaningful only - // when the series had prior data (same seenData guard). - if (runStart != null) push(d.pts[d.pts.length - 1].x); - } - if (all.length === 0) return null; - - // Label pills were removed intentionally: the guard upstream can't - // always tell "aggregator crash" apart from "our harness reconnect" - // or "worker step misalignment on a 30 min silence in a 7 d view", - // and a mislabelled pill blames the wrong side. The tinted band + - // dashed edges alone read as "no data recorded here" without - // asserting a cause. When we have a first-party signal that a - // specific provider went down, a proper status badge will be added - // separately — the sparkline is not the place to assign blame. - - return ( - - {all.map((b, i) => ( - - - - - - ))} - - ); -} +// DowntimeBands was removed: tinted bands + pill labels were misleading +// (mislabelled provider, alignment artefacts). Gaps in the series line +// now speak for themselves — SeriesPaths renders a natural break wherever +// the underlying values are null. export function SeriesPaths({ drawn, unit }: SeriesPathsProps) { return (