<DeckMatrix> iteration — vacuous-Ready bug fixed, rollups merged into one pill, variant filter chips, OPEN button retired, plan revised to keep per-variant TOC alive
Three in-browser review passes on chroma's index produced four DeckMatrix refinements and one plan revision. The matrix the morning shipped was structurally right but visually noisy and wrong on two specific decisions — the per-surface rollup rendered as five separate count pills wrapping onto two lines per surface (ten pills per column for a 4-variant deck); the column-level OPEN link buried the scroll-vs-play surface distinction into one button that dumped readers into 'the deck' without saying which surface; and a `getStaticPaths`-free variant with zero slots was vacuously satisfying the Ready badge's 'no blockers found' check. Fixing those produced one larger consequence: the per-variant `/toc/[deck]/[variant]/` route, originally slated for deprecation-to-redirect in Phase 6 of the redesign plan, gets to stay alive — the deck-level matrix and the per-variant TOC serve different workflow needs (cross-variant comparison vs. land-in-one-variant), and the variant-name in each column header now links to the latter as the variant's landing surface.
<DeckMatrix> iteration — vacuous-Ready bug fixed, rollups merged into one pill, variant filter chips, OPEN button retired, plan revised to keep per-variant TOC alive
Why care?
<DeckMatrix> shipped this morning with the right axes (variants × slots, dual-surface chips per cell) and the right rollups (per-column Ready badge, per-row drift indicator). What it didn't have was a column header that survived contact with a 4-variant deck. Looking at chroma's / in browser this afternoon, the failure modes lined up:
The per-surface rollup rendered as five separate pills (
★ 3 / P 9 / C 4 / U 1 / — 0) per surface × two surfaces × four variants. Forty pills in the header alone, wrapping onto multiple lines per column.The column-level OPEN link claimed "open this variant" but the destination —
/scroll/[deck]/[variant]/— was a scroll deck, not a landing. Click and you're not at a variant index, you're at slide 01.A variant with no
SLOTSentry (chroma'senhanced-v1today) showed READY because theisBlockercheck correctly returnedfalsefor everymissing-slotcell, leaving the rollup's initialready: truestate intact. Vacuous truth, false positive in the badge.
Past those three, a quieter observation: a 4-variant deck has more columns than the layout cleanly carries, and adding a 5th or 6th variant (which a richer engagement will) made the case worse. Needed a filter.
What's new
Vacuous-Ready bug fixed
rollup.readyis now initialized tovariantSlotCount > 0, not unconditionaltrue. A variant with no slots cannot be "ready" — there's nothing to be ready about.enhanced-v1(zeroSLOTSentry today) correctly shows Not ready instead of Ready. The all-zero rollup counts still display so the structural-empty state is legible.Comment in-body flags the bug pattern for future similar aggregates: completion checks defaulting to "no blockers found" are vacuous-true when there's nothing to check. Always pair with a non-emptiness guard.
Merged rollup pill
One pill per surface instead of five. Layout:
[★3 | P9 | C4 | U1 | —0]— single rounded capsule, status-colored segments inline, thin separator between each segment, status-glyph emphasized + count tabular-num.Zero counts fade to 0.3 opacity inside the pill (preserves the previous "non-zero counts pop" affordance without each non-zero needing its own pill outline).
Play-side appends
✕N(red ✕ glyph, muted count) when there are missing-file slots.A 4-variant column header collapses from ~4 stacked rows (variant label, scroll pills wrapping 2 lines, play pills wrapping 2 lines, ready badge) to a clean 4 rows of single-line items.
Variant filter chip strip
Above the matrix, a row of clickable chips per variant, with up to 3 selected at a time. Hard cap; selected chips are accent-tinted, unselected dimmed.
State is in the URL via
?variants=slug1,slug2,slug3. Each chip'shrefprecomputes "what would the selection be if I clicked this?" — pure-server toggle navigation, no JS state, nouseState/useEffectequivalent, no hydration needed.FIFO swap on cap: when 3 chips are selected and you click a 4th, the oldest selected drops off. The chip's
titletooltip says so explicitly so the user knows what'll happen before clicking.Default selection when no URL param is present: the last 3 variants in registry order. Chroma's registry orders
proto → enhanced-v1 → enhanced-v2 → enhanced-v3(baseline → most-refined), so the default shows the three most-refined and parks the baseline as a toggleable reference. ClickPrototo bring it in; the oldest of the visible set drops.The display label above the chips reads
VARIANTS (3 of 4)so the cap and the total are legible at a glance.
OPEN button retired; surface rows are launchers
Removed the
[open ↗]link from each column header. It was a one-click affordance that buried the surface choice and dumped the reader into "the deck" without specifying scroll or play.Each surface row is now its own link:
Scroll row
[scroll-icon] [rollup pill]→/scroll/[deck]/[variant]/Play row
[play-icon] [rollup pill]→/play/[deck]/[variant]/(which redirects to the first playable slot via the existing index route)
Surface-row hover affordance: subtle background fill + border, so it's obvious the whole row is clickable, not just the icon.
Play row gracefully disabled when no playable slots exist (proto and enhanced-v1 today): row dims to 0.45 opacity, cursor stays
not-allowed, hover affordance suppressed, tooltip explains "no per-slide Play-UI files exist for this variant yet." Better than rendering as a clickable link that 404s on the redirect.
Variant name → per-variant TOC landing
The column header's variant label is now a link to
/toc/[deck]/[variant]/— the per-variant TOC.Hover affordance: text color shifts to accent on hover.
This decision drove the plan revision below.
Plan revision — Phase 6 keeps per-variant TOC alive
context-v/plans/Redesign-TOC-as-Deck-Level-Dual-Surface-Review-Matrix.mdPhase 6 reframed in place. The original scope was "deprecate/toc/[deck]/[variant]/to a 302 redirect to/toc/[deck]/?variant={slug}." The revised scope keeps the per-variant route alive as the variant landing/index.Why the change: the two surfaces serve different workflow questions. The deck-level matrix answers "which variant is closest to ready, and where is the drift?" — cross-variant. The per-variant TOC answers "I'm working in this variant now; what slots does it have, what's the rank of each?" — single-variant. The founder reaches for both depending on which question is in front of them. Folding either into the other loses the surface.
Lede + scope-boundary also updated to reflect the kept-alive decision; the original "folds the per-variant TOC into a redirect" framing is annotated as superseded inline.
Revised Phase 6 scope: keep the route, audit links to verify it's reachable as a landing target, optional polish-pass (variant lede above the table, surface-launch buttons paralleling the matrix's column header, a "now showing in matrix view ↗" back-link). Not load-bearing; judgment work.
The story
The matrix was meant to ship and immediately get used. The way it actually got used was: open it next to the previous day's chroma /index.astro rollup matrix, eyeball the difference, and start naming what's off. Three rounds of naming, three rounds of fixes.
Round 1 was the wrap density. The five-pill grid worked at 1-variant width; at 4 it was a wall. Founder framing: "that could be tighter, where instead of all of them having their own pill, they are all merged into one pill." Done. The merged pill collapsed the visual density by ~75% without losing any of the counts.
Round 2 surfaced the variant overflow. At 4 variants the header was already crowded; at 6+ it'd be unworkable. Founder framing: "we should allow for too many variants to fit cleanly into one display, three is likely the max." The chip-strip implementation kept the change boring — ?variants= URL param, FIFO swap at the cap, every chip an <a href> that precomputes the next state. No client-side state machine.
Round 3 was the vacuous Ready, which I caught when chroma's enhanced-v1 (no SLOTS registered yet) showed READY in the rollup with all-zero counts and the founder asked the right question: "Why is Enhanced-v1 showing up as READY?" That one was a one-line fix but a generalizable lesson — completion-style aggregates default to "no blockers found"; pair them with a non-emptiness guard or they fire false positives on empty sets.
Round 4 was the OPEN button, which the founder had noticed earlier but hadn't named: "that Open button, which I don't really like that much, used to take me to a landing/index page." The previous-life landing they remembered was the per-variant TOC at /toc/[deck]/[variant]/, which is still in tree and (per this morning's plan) was about to be deprecated to a redirect. Surfacing the underlying question — what surface should "open variant" land on? — produced the plan revision, kept the per-variant TOC alive, and split OPEN's single ambiguous action into three explicit ones: variant-name → landing TOC, scroll-row → scroll deck, play-row → play deck.
How it works (or "Under the hood")
URL-as-state for the variant filter
const variantsParam = Astro.url.searchParams.get("variants");
const requestedSlugs = variantsParam?.split(",").map(s => s.trim()).filter(Boolean) ?? [];
const validRequested = requestedSlugs.filter(s => allVariants.some(v => v.slug === s));
const visibleSlugs = validRequested.length > 0
? validRequested.slice(0, MAX_VISIBLE_VARIANTS)
: allVariants.slice(-MAX_VISIBLE_VARIANTS).map(v => v.slug);
function chipHrefFor(slug: string): string {
const isSelected = visibleSet.has(slug);
let nextSlugs;
if (isSelected) nextSlugs = visibleSlugs.filter(s => s !== slug);
else if (visibleSlugs.length < MAX_VISIBLE_VARIANTS) nextSlugs = [...visibleSlugs, slug];
else nextSlugs = [...visibleSlugs.slice(1), slug]; // FIFO swap
const params = new URLSearchParams(Astro.url.search);
if (nextSlugs.length === 0) params.delete("variants");
else params.set("variants", nextSlugs.join(","));
return `${Astro.url.pathname}${params.toString() ? `?${params}` : ""}`;
}Each chip emits <a href={chipHrefFor(slug)}> — clicking is a plain navigation. No JS, no client-state, no hydration. The Astro SSR pipeline reads the new URL, recomputes visibleSlugs, and re-renders. URL-driven state also means the toggle persists across reload, is bookmarkable, and can be deep-linked in a Slack message ("here's the v1/v2/v3 view of chroma").
The FIFO-swap-on-cap is the only subtle bit — visually a 3-cap with no swap would feel "broken" when the user clicks an unselected chip and nothing happens. Swapping the oldest preserves intentionality (user's most-recent picks survive) without violating the cap.
Why default to the last 3 in registry order
The registry orders variants from baseline (proto, the literal recreation of the management-supplied PDF) to most-refined (enhanced-v3, the latest iteration). The interesting workflow comparison is between iterations — proto is reference material once iterations exist. So the default visible set is the last 3 entries, which puts the iterations side-by-side. Click Proto to swap it in for cross-baseline comparison; it FIFO-drops the oldest iteration.
Generalizable to any deck with N variants: default = slice(-3). If a deck has fewer than 3 variants, all are visible (the slice(-3) returns all of them). If a deck adds a 5th or 6th variant later, default behavior shifts to the newest 3 automatically — no per-deck configuration.
Vacuous Ready, the pattern
The general shape of the bug:
let ready = true; // default: nothing wrong yet
for (const item of items) {
if (isBlocker(item)) ready = false;
}Looks right. Reads right. Wrong when items is empty: ready stays true, which is logically "no blockers found" but presentationally "this thing is done." Pair with items.length > 0 or initialize ready = items.length > 0 to guard the vacuous case.
<DeckMatrix> fixed by initializing ready: variantSlotCount > 0. Worth flagging as a recurring pattern — anywhere a completion aggregate defaults to "no failures found," check the empty-set case.
What's next
Commit this round. Vacuous Ready + merged pill + chip strip + surface-row launchers + variant-name link + plan revision = one substantive iteration commit; the in-flight matrix-menu polish from earlier today (
Switch Variant ▾popover on<DeckFrame--Play-UI>) also still pending. Bundle or keep separate per founder preference.Phase 6 redux — execute the revised scope: audit links to verify the per-variant TOC is reachable as a landing target from every entry point (matrix variant name, chroma's variant cards, any other deep references). Optional polish pass on the page itself.
Phase 7 — cross-cutting verification + version bump (
0.1.0-rc.1from0.1.0-rc.0).Three-cap might want a knob. The
MAX_VISIBLE_VARIANTS = 3is hardcoded; if a deck has 6 variants and the founder regularly wants to see 4 at once, expose it as a prop on<DeckMatrix>. Not urgent; flag if it bites.Empty vs blocker-blocked variants visually distinct? Today both show "Not ready" — structurally empty (no slots) vs blocked (has slots, has problems) read the same on the badge. The all-zero rollup counts telegraph the empty case but a more explicit
"Empty"/"No slots"badge state would be clearer. Judgment-pass work; not urgent.Promote URL-as-state pattern to a reference in the
astro-knotsskill if this shape recurs in other shell-level components. Pure-server toggle navigation is a clean idiom worth naming.
Related
[[2026-05-17_01]] — this morning's bulk shell entry. Today's iteration refines what shipped there.
[[2026-05-17_02]] — calmstorm auth-gate detour (separate).
[[2026-05-17_03]] —
<DeckFrame--Play-UI>+ workflow/presentation intent (separate).[[../context-v/plans/Redesign-TOC-as-Deck-Level-Dual-Surface-Review-Matrix]] — plan, revised in-flight today (Phase 6 + lede + scope boundary).
[[../apps/deck-shell/src/components/DeckMatrix.astro]] — the component this iteration mostly lives in.
astro-knotsskill — candidate destination for the "vacuous-aggregate" gotcha and the "URL-as-state for filter chips" pattern, if either proves to recur.
