- Add N and T axes alongside dataset/algorithm; chips populated from runs
in the list, axis group hidden when there's a single unique value.
- Dataset+algorithm on row 1, N+T on row 2 via two explicit
.runs-filter-row flex containers (cleaner than a sentinel break elem
that double-counted the row-gap).
- 'all' and 'none' meta-chips now wrap as a unit inside .chip-meta-wrap
so one doesn't orphan to the next line.
- Row is hidden entirely when every axis in it collapses to a single
value (:has selector on .runs-filter-row).
- /compare accepts ?stem=…&stem=… (repeated) for 2-8 runs; legacy ?a=&b=
still works. compare.js parses multi-stem; template drops stem_a/_b
data attrs that were unused.
- compare-select.js: MAX bumped to 8, button enables at 2-8 selected.
URL emitted as ?stem=… per selection.
- runs list gets a dataset/algorithm chip filter bar above #runs-slot
(pattern ported from metrics.js). Chips reflect the union of values in
the current list; selection state persists across htmx swaps. Non-
matching rows get .filtered-out (display:none).
- _runs.html li now carries data-embedder/data-generator so the filter
can key on them.
- panel-grid.js (new): exports mountPanels({host, controls, stems}) → {destroy}.
Moved createPanel + shared control wiring + linked-hover + pad-to-match
time mapping out of compare.js. Stem-count-agnostic; works for 1, 2, or N.
- Panel DOM is cloned from <template id=compare-panel-tpl> on each page.
- compare.js is now a ~10-line shim: parse ?a=&b=, call mountPanels.
- Per-panel color is viridis-sampled by index/N (middle viridis for N=1,
ends-of-palette for N=2, linear lerp for N≤8, cycle at N≥9). Set as
--panel-color on the panel element; CSS reads it for tag/time-seg.
- Homepage <dialog id=run-modal> + run-modal.js hijack the 'embedding' link
(plain click → modal; meta/ctrl/middle-click still opens plotly HTML).
Dialog close disposes every panel's renderer/geometry/material.
- .compare-grid → repeat(auto-fit, minmax(360px, 1fr)) handles N=1..many,
replaces the <900px one-column media rule.
- Runs list: relabel Prefect's 'Late' state as 'Queued' — more honest
description of what the runner is doing at the concurrency cap.
When two runs share identical params they write to the same figs/<stem>.html,
and the most recent overwrites the earlier. Previously both got a compare
checkbox with the same data-stem, so toggling one toggled both via the JS
Set. Now we flag older duplicates server-side (first occurrence wins — Prefect
returns runs START_TIME_DESC), drop their checkbox, fade the row, and mark
'overwritten' on the outputs line.
- per-run checkbox when embedding HTML exists; cap at 2 selected
- sticky 'compare selected' button opens /compare?a=&b= in new tab
- selection state persists across the 3s htmx poll via a Set keyed by stem
- /compare stub validates stems, renders scaffolding (three.js UI next)