Lossless Changelog
product

Calmstorm-Decks UI + Component Library — patterns ready to seed every future client-site

A long day inside calmstorm-decks surfaced four patterns that are now ready to migrate up the tree and seed every other client-site: a unified two-axis navigation grammar across Scroll and Play modes, a two-route discipline that splits the design contract from the component catalog, a typed component registry as single source of truth for both stakeholders and the embedded chat-composer, and a `ContentFit + swappable info-display` mechanic that ends the per-slide font-and-margin tuning war. None of these are calmstorm-specific.

Michael StatonMusic Editor

Calmstorm-Decks UI + Component Library — Patterns to Lift Up the Tree

This is the REFLECT phase of the lifecycle for today's calmstorm-decks work (see client-sites/calmstorm-decks/context-v/changelogs/2026-05-12_01.md for the in-project changelog). What lands here is the cross-cutting summary — what's reusable, what other client-sites should inherit, what the framework for future engagements should look like.

What graduates from calmstorm-decks into the DidiDecks framework

1. Two-axis navigation grammar (now consistent across modes)

Every DidiDecks deck has at least two viewing modes — Scroll (web-page feel, contiguous reading) and Play (keynote-feel, one slide at a time). Each mode has two orthogonal navigation axes: slide-to-slide and variant-to- variant. The axes are rotated between modes:

Slide-to-slideVariant-to-variant
Scroll↑ / ↓ (scroll-snap)← / → (variant-cycle pill)
Play← / → (PPT-style)↑ / ↓ (variant-cycle pill)

The "primary axis" of the mode (vertical for Scroll, horizontal for Play) walks the deck. The "secondary axis" cycles design variants of the current slide. Same data, different layout — the mechanic the deck-iteration workflow has been pointing at for weeks.

A small pill UI — ‹ V1 · BASELINE (1/3) › with click-arrows + counter — appears in the chrome of both modes. Today's calmstorm work brought the pill to the Play UI to close a long-standing asymmetry. Same component shape, same keyboard semantics, regardless of mode. Hash is preserved across cycles, so jumping from v1 slide 7 to v2 slide 7 stays on slide 7.

Where this graduates: every future client-site (chroma-decks, reach-edu- hub, the next one after that) inherits this two-axis grammar. It's a default of the framework, not a calmstorm-specific feature.

2. Two-route discipline — /design-system/ AND /component-library/

The astro-knots family puts everything in one /design-system/ route. For DidiDecks we deliberately split:

  • /design-system/ — the contract. Tokens, typography, modes, the eight prose sections of the Google Stitch open spec. Live rendering of repo-root DESIGN.md. Stable — changes ripple through every component.

  • /component-library/ — the catalog. Hub → category menu → one sub-page per component with five sections (live demo, props, CSS contract, composes-from, used-by). Iterative — per-engagement, per-component.

Why the split: DidiDecks's defining loop is the embedded chat-composer generating slides from named components. A single hub conflates "what are the rules?" with "what's in the kit?" Splitting them lets each surface do one job.

Both routes are private tier (noindex, behind the cover-page gate). The deck-viewing audience never sees them; contributors and stakeholders find them via the audit dashboard at /.

Where this graduates: every client-site adopts both routes from day one. The category-page + per-component-page templates are generic — they read from the registry. The first site rolling this out costs ~500 lines of route code; the second site forks them with zero changes.

3. The component registry — single source of truth for humans AND agents

src/lib/component-registry.ts is the canonical enumeration of every Astro component in a calmstorm-decks engagement. Today it carries 56 entries across 9 categories — 13 built, 31 planned (with evidence fields showing exactly which slides exhibit the pattern), 8 future, plus 4 basics fully documented.

The hub at /component-library/, the category-page template, AND the per-component inspector template all read from this one module. Adding a component is one TypeScript object literal — its category page, its inspector page, its evidence-count chip on the hub all regenerate automatically.

Why this matters for the agent loop: Claude in the embedded chat-composer consumes the registry (or its /component-library/llms.txt projection, a Phase 4 deliverable) to learn what's composable before generating a new slide. The same data structure serves the human iteration loop AND the agent inference loop. No duplicate documentation.

Where this graduates: the registry shape — ComponentEntry / CategoryEntry types, the 9-category taxonomy with decision rules, the evidence field for cataloging-before-migration — is the framework's canonical component-metadata model. Every client-site stands up its own registry; the categories and rules are shared.

The 9 categories with their decision rules (from calmstorm-decks/context-v/plans/Componentize-Slides-and-Establish-Component-Library.md):

  1. publish/ — its presence in the dist makes a publicness claim (sitemap, llms.txt, canonical, JSON-LD, analytics). Tier-gated.

  2. share/ — its presence supports unfurling to a known correspondent (OG, Twitter Card). Tier-gated.

  3. basics/ — universal chrome that lives on every page regardless of tier.

  4. audit/ — shows the deck's state (status tables, asset audits), not the deck's content.

  5. slides/ — the slide shell itself (SlideCanvas, ContentFit).

  6. primitives/ — one concept, no layout opinion.

  7. patterns/ — composed primitives used on ≥2 slides.

  8. diagrams/ — recurring visual / mental models (Matrix2x2, Sankey, Timeline — sibling spec territory).

  9. markdown/ — LFM renderers.

The "≥2 slides to promote" rule is the discipline that prevents patterns/ from rotting the way basics/ had been.

4. ContentFit + swappable info-displays — the end of the font-tuning war

Historically, calmstorm-decks slides were hand-tuned for the 1920×1080 canvas: hand-pick font-size, hand-tune padding, hand-tune gap so the content "fits." Result: ten engagements would produce ten micro-fights over "this should be 1.2rem not 1.0rem" — slow, low-leverage, never settled.

Today's calmstorm shipped a different mechanic:

ASTRO
<SlideCanvas>
  <ContentFit padding={48} maxScale={2}>
    <TeamGrid members={…} eyebrow="Team" headline={…} subhead={…} />
  </ContentFit>
</SlideCanvas>

The author sizes the info-display at COMFORTABLE reading scale (a normal 1200px web page). ContentFit measures the content's natural size and applies transform: scale(N) so it fills the canvas with the right uniform scale — ResizeObserver + MutationObserver + font-ready + image-load all trigger re-fits. The author never thinks about canvas dimensions or font sizes again.

The swappability piece: the same data shape can render under different info-display components. Three calmstorm slides (04 Identity Pillars, 05 Problem, 06 Opportunity) all share the data shape:

[{ n, title, points: [{label, body}], portfolio?: [...] }]

— and the deck author should be able to pick from NumberedColumnGrid, HorizontalSlatStack, NewspaperGrid as drop-in alternatives. Today's cataloging pass identified 25 such patterns across calmstorm; building any one of them as a real component instantly makes that pattern available on every slide whose data shape matches.

Where this graduates: ContentFit is a domain-neutral primitive (works for any deck, any canvas size, any content). It moves up to the DidiDecks framework as a shared slides/ component. The pattern of authoring an info- display at natural scale and letting the wrapper handle the canvas math becomes a framework convention. The 25 cataloged patterns are calmstorm's catalog; the cataloging methodology (grep for class-token frequency across slides, then per-slide read to surface non-obvious recurrences) is what generalizes.

The relocation HARD STOP — added to the pseudomonorepos skill

Today's session lost ~30 minutes to an env-var disaster: calmstorm-decks was relocated from astro-knots/sites/ to ai-labs/dididecks-ai/client-sites/ without backing up gitignored secrets first; the old directory was deleted; the new clone landed on a stale development branch missing 19 commits of production-relevant work; and a duplicate Vercel project hid which env vars were where. Hours of recovery.

The fix in the framework: a HARD-STOP precondition checklist added to the pseudomonorepos skill (context-v/skills/pseudomonorepos/SKILL.md → "Edge case — moving a repo within the tree") and to a new top-level lossless-monorepo/CLAUDE.md. Three preconditions, one per acknowledgment:

  1. Every local branch synced to remote (no unpushed commits, no stashes).

  2. Every remote branch catalogued (no local-only branches without explicit permission to discard).

  3. Every .env* / .secrets* / gitignored config backed up to a known recovery path (.env.example is NOT authoritative; grep process.env and import.meta.env in source to enumerate the truth).

Bundled "looks good" confirmations are explicitly forbidden. Each precondition gets its own yes.

The recovery recipe is also documented for the next time it happens anyway: find any surviving working copy via filesystem search + Claude session-cache encoded paths, reconstruct env vars from source, audit branch divergence via git log --all.

Where this leaves the framework

Today produced the first client-engagement with all the moving parts of the DidiDecks framework actually moving in concert: gated production deployment with signed access links delivered over WhatsApp; full Scroll + Play viewing modes with the two-axis navigation grammar; component library

  • design system as live routes; component registry as the agent-readable backbone; first proof of ContentFit + swappable info-displays. Calmstorm- decks is the substrate.

The second engagement (chroma-decks, the friend-of-the-house founder fundraise — see context-v/plans/Init-Chroma-Decks-Client-Site.md) becomes the proof that what landed today is framework, not bespoke. The promotion target for the four patterns above is whatever lives at dididecks-ai/packages/ (forthcoming) — a shared library that every client-site under dididecks-ai/client-sites/* consumes.

  • [[../client-sites/calmstorm-decks/context-v/changelogs/2026-05-12_01.md]] — the in-project changelog with the operational play-by-play

  • [[../client-sites/calmstorm-decks/context-v/plans/Componentize-Slides-and-Establish-Component-Library.md]] (parent plan, but the in-client-site context-v carries the deepest decision history)

  • [[context-v/specs/Dididecks-AI-Slide-Decks-as-Code]] — the parent spec these patterns implement against

  • [[context-v/specs/Dididecks-AI-Visual-and-Diagram-Component-Library]] — the sibling spec that the diagrams/ category implements

  • [[../../../CLAUDE.md]] — the new top-level pseudomonorepo agent instructions (HARD STOP relocation rule lives here)

  • [[../../../context-v/skills/pseudomonorepos/SKILL.md]] — the skill update with the edge-case section