From 3733ce572c780a97129e56b7695689c5b43858c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 10:37:27 +0000 Subject: [PATCH] Give spiral-engine its own series colors Only `datafusion:*` and `duckdb:*` pairs were pinned in `KEYED`, and the per-format defaults in `FORMAT` deliberately reuse datafusion's colors. Any third engine therefore fell through to `FORMAT` and drew in exactly datafusion's shade -- `spiral-engine` and datafusion were indistinguishable on every chart where both appear. Pin `spiral-engine` for the five formats datafusion also reads, each a third shade that stays clear of both the datafusion and the duckdb color and reads on the light and the dark card, and keep the existing importance-tier widths. Add a regression test asserting no spiral-engine pair shares a color with datafusion or duckdb. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Sa2mGanAjiQAb16Ak8zoyp Signed-off-by: Claude --- web/lib/chart-format.test.ts | 17 +++++++++++++++++ web/lib/chart-format.ts | 15 +++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/web/lib/chart-format.test.ts b/web/lib/chart-format.test.ts index d5254e2..a09bcc2 100644 --- a/web/lib/chart-format.test.ts +++ b/web/lib/chart-format.test.ts @@ -163,6 +163,23 @@ describe('seriesStyle', () => { expect(compact.color).not.toBe('#22c55e'); }); + it("keeps spiral-engine off datafusion's color on every shared format", () => { + for (const format of [ + 'vortex-file-compressed', + 'vortex-compact', + 'parquet', + 'lance', + 'arrow', + ]) { + const df = seriesStyle(`datafusion:${format}`, tag('datafusion', format)); + const spiral = seriesStyle(`spiral-engine:${format}`, tag('spiral-engine', format)); + const duck = seriesStyle(`duckdb:${format}`, tag('duckdb', format)); + expect(spiral.color).not.toBe(df.color); + expect(spiral.color).not.toBe(duck.color); + expect(spiral.width).toBe(df.width); + } + }); + it('derives engine/format from the label when meta is absent', () => { expect(seriesStyle('datafusion:vortex-file-compressed', undefined).color).toBe('#ef4444'); }); diff --git a/web/lib/chart-format.ts b/web/lib/chart-format.ts index c0c9bb0..8b8cdad 100644 --- a/web/lib/chart-format.ts +++ b/web/lib/chart-format.ts @@ -131,7 +131,8 @@ export function assignStableColors( // native format (gold). Everything else is muted and thin so it recedes and // never out-shouts the comparison -- lance is neutral slate (it is the least // important and is hidden by default soon), and `datafusion:arrow` keeps an -// orange where it is still worth reading. +// orange where it is still worth reading. A third engine (`spiral-engine`) takes +// a third shade of each format so it never draws in datafusion's color. // --------------------------------------------------------------------------- /** The resolved line style for one series: a color and a width (px). */ @@ -151,18 +152,28 @@ const MUTED_WIDTH = 1.4; * an importance-tier width. The Vortex hero is the on-disk `vortex-file-compressed` * format -- red on datafusion, green on duckdb. The two lance engines carry * distinct slate shades so they stay apart. A pair not listed here falls through - * to the per-format defaults in `FORMAT`. */ + * to the per-format defaults in `FORMAT`, and those defaults reuse datafusion's + * colors -- so EVERY engine that reads a format must be pinned here, or it draws + * on top of datafusion in the same color. `spiral-engine` is pinned for that + * reason: teal / pink / deep sky blue / warm gray / rust, one step away from both + * the datafusion and duckdb shade of each format, and each readable on the light + * and the dark card. */ const KEYED: Record = { 'datafusion:vortex-file-compressed': { color: '#ef4444', width: HERO_WIDTH }, // bright red 'duckdb:vortex-file-compressed': { color: '#22c55e', width: HERO_WIDTH }, // neon green + 'spiral-engine:vortex-file-compressed': { color: '#0d9488', width: HERO_WIDTH }, // teal 'datafusion:vortex-compact': { color: '#a855f7', width: COMPACT_WIDTH }, // bright purple 'duckdb:vortex-compact': { color: '#7e22ce', width: COMPACT_WIDTH }, // deep purple + 'spiral-engine:vortex-compact': { color: '#db2777', width: COMPACT_WIDTH }, // pink 'datafusion:parquet': { color: '#38bdf8', width: SECONDARY_WIDTH }, // light blue 'duckdb:parquet': { color: '#2563eb', width: SECONDARY_WIDTH }, // dark(er) blue + 'spiral-engine:parquet': { color: '#075985', width: SECONDARY_WIDTH }, // deep sky blue 'duckdb:duckdb': { color: '#eab308', width: NATIVE_WIDTH }, // gold 'datafusion:lance': { color: '#94a3b8', width: MUTED_WIDTH }, // neutral slate 'duckdb:lance': { color: '#475569', width: MUTED_WIDTH }, // darker slate + 'spiral-engine:lance': { color: '#78716c', width: MUTED_WIDTH }, // warm gray 'datafusion:arrow': { color: '#f97316', width: MUTED_WIDTH }, // orange + 'spiral-engine:arrow': { color: '#9a3412', width: MUTED_WIDTH }, // rust }; /** Per-format defaults, used for the engine-less compression-time series and as