Skip to content

feat: PresentationDeck engine + deck primitives with a behavioral eval suite#2

Merged
theclaymethod merged 6 commits into
mainfrom
feat/presentation-deck
Jul 15, 2026
Merged

feat: PresentationDeck engine + deck primitives with a behavioral eval suite#2
theclaymethod merged 6 commits into
mainfrom
feat/presentation-deck

Conversation

@theclaymethod

@theclaymethod theclaymethod commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

A second deck engine for presented (not scrolled) decks, generalized from a production deck built with this pipeline — plus an eval suite that IS its behavioral contract.

  • PresentationDeck / PresentationSlide (visual-explainer-mdx/presentation.tsx): fixed 1920×1080 stage scaled to fit via ResizeObserver (min(w/1920, h/1080), letterboxed on the new --ve-deck-letterbox token), collapsible slide rail (auto-collapse → dot column, hover expand, active indicator), keyboard nav (arrows/Space/PageUp/PageDown/Home/End), 80px edge click zones, mono slide counter, cubic-bezier(0.22,1,0.36,1) transitions.
  • Drill-down primitives: DrillCard (corner-anchored expansion over neighbors via transform-origin), DrillChip (CTA discipline: primary = solid accent fill, secondary = outline), DrillSheet (Escape/X + click-anywhere-to-close guarded by closest('button, a, input, select, textarea, [data-interactive], [data-drill-target]')), LayerExplorer.
  • Composition primitives: LadderDiagram/FanoutDiagram (grid paper with opaque solid-over-grid fills — the CSS equivalent of solidTint()), PullQuote, Metric/StatRow, HairlineList, Stepper, CodePanel, MonoLabel/DisplayText, IconChip + geometric icons, ShineOverlay/trackShine, hover-lift CSS.
  • Zero brand values: everything consumes --ve-* tokens; slide tones reuse Slide's data-ve-tone--ve-slide-* mapping, so all existing presets skin the deck untouched (independent of the design-system-registry PR). prefers-reduced-motion collapses all deck motion.
  • Existing SlideDeck/Slide exports are untouched; the new components re-export through visual-explainer-mdx/components.tsx and the roster-sync guard now parses re-exports.

The eval suite is the spec

evals/run-presentation.mjs (npm run ve:eval-presentation, documented sibling of evals/run.mjs, wired into the CI evals job) exports the demo through the standard ve:export path and replays the contract headlessly — 18 evals:

Group Evals
interaction dismiss-guard matrix (each of button/a/input/select/textarea/[data-interactive] keeps the sheet open; passive prose closes it; Escape + X always close), keyboard-nav matrix with counter assertions incl. clamping, drill CTA contract (every trigger opens via click/Enter/Space; primary solid vs secondary outline computed styles), LayerExplorer selection, reduced-motion, no-console-errors
geometry stage transform == fitStage() at 1440×900 / 1920×1080 / 1024×768 / 800×1000, letterbox on mismatched ratio, rail 260↔44px collapse/hover-expand + rail navigation, Metric no-wrap/no-overflow under the widest display font
tokens second-preset export changes computed surface/CTA/font values; an allowlist-based literal scan: the shipped module must contain NO color literals (hex/rgb()/hsl()/oklch()/oklab()) and no concrete font-family at all — every visual value must arrive via a --ve-* token. The only allowlisted literals are two documented neutral var(..., fallback) values and one docstring example

Falsification check: each eval class was verified to fail by sabotaging the behavior and reverting — guard return true → dismiss-matrix failed; fitStage min→max → letterbox failed; a hardcoded CTA color → CTA-style, preset-reskin, and literal-scan evals failed.

Unit tests (npm test, in CI): scale-to-fit math, dismiss guard via linkedom, tint/solidTint (21 tests incl. existing diagram-layout suite).

Verifier

One scoped fix: the bundled Tailwind CSS always contains SlideDeck's scroll-snap utilities, so detectProfile classified every fixed-stage artifact as slides and then failed deck-scroll-snap-axis against a deck that never scrolls. data-ve-presentation artifacts now verify as pages. ve-verify on the demo artifact: profile=page, errors=0 (1 warn: sub-44px hit targets, inherent to a scaled presenter stage). ve:eval (all 130+ seeded-violation fixtures) stays green with the detection change.

Demo

examples/visual-explainer-mdx/presentation-deck.tsx — 4 slides exercising engine + primitives, wired into ve:check. The slide-2 drill sheet doubles as the interaction fixture (one of every guarded element class).

Paper-ink preset Terminal preset (same source)
paper-ink swap preset="terminal" — asserted by the preset-reskin eval

Test plan

  • npm test — 21/21
  • npm run ve:check — roster sync (incl. re-exports), all 15 example exports + SSR content probes, integrity fixtures
  • npm run ve:eval — all seeded violations + clean fixtures pass with the profile-detection change
  • npm run ve:eval-presentation — 17/17; each eval demonstrated falsifiable
  • npm run check:manifests
  • ve-verify on the exported demo: errors=0
  • Playwright screenshots at 1440×900 and 1600×1000 (paper-ink + terminal, drill sheet open) confirm rendering

Docs

docs/presentation-deck.md (PresentationDeck vs SlideDeck), README, CHANGELOG (Unreleased), CONTRIBUTING (engine changes must run both suites), SKILL.md component roster.

🤖 Generated with Claude Code

2026-07-15: token eval generalized — instead of scanning for known-bad values, it now asserts the module ships zero color/font literals outside a documented allowlist (strictly stronger). Branch history rewritten before merge to keep the diff self-contained; content otherwise identical.

Coordination with PR #3 (design-system registry): the branches conflict in 6 files (package.json scripts, scripts/ve-mdx/check.mjs roster guard, visual-explainer-mdx/components.tsx VisualPreset/exports, CHANGELOG, README, SKILL.md) — all additive; whichever lands second rebases. One resolution rule: the components.tsx VisualPreset conflict must resolve in favor of PR #3's registry-open type (… | (string & {})) or registry presets won't typecheck (this PR's PresentationDeck already takes preset: string, so it is compatible either way).

@theclaymethod
theclaymethod force-pushed the feat/presentation-deck branch from 75cf063 to dc7c886 Compare July 15, 2026 18:38
Clayton Kim and others added 6 commits July 15, 2026 12:34
A second deck engine for presented (not scrolled) decks, generalized from a
production deck: fixed 1920x1080 stage scaled to fit (ResizeObserver +
letterbox), collapsible slide rail with dot-column collapsed state, keyboard
nav, 80px edge click zones, and drill-down primitives (DrillCard/DrillChip/
DrillSheet with a click-anywhere-to-close guard, LayerExplorer, Ladder/
FanoutDiagram, PullQuote, Metric/StatRow, Stepper, CodePanel, icons, shine).

Everything consumes --ve-* preset tokens (no color or font literals ship in
the module); slide tones reuse the data-ve-tone -> --ve-slide-* mapping. Pure
logic (fitStage math, dismiss guard, tint/solidTint) lives in
presentation-core.ts so node:test and the eval harness import it directly.
Re-exported through components.tsx; the roster-sync guard now also parses
PascalCase re-exports. Demo deck at
examples/visual-explainer-mdx/presentation-deck.tsx is wired into ve:check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…deck

evals/run-presentation.mjs (npm run ve:eval-presentation) is the engine's
behavioral contract, run as a documented sibling of evals/run.mjs: it exports
the demo deck through the standard ve:export path and replays headlessly
- interaction: click-anywhere-to-close guard matrix (button/a/input/select/
  textarea/[data-interactive] stay open; passive prose closes; Escape and the
  X always close), keyboard-nav matrix with counter assertions, drill CTA
  contract (every trigger opens via click/Enter/Space; primary solid fill vs
  secondary outline computed styles), LayerExplorer selection, reduced motion
- geometry: stage transform equals fitStage() across four viewports,
  letterboxing on mismatched ratios, rail collapse/expand widths
- tokens: a second preset changes computed surface/CTA/font values, and an
  allowlist-based scan asserts the shipped module contains NO color or font
  literals at all (every visual value must arrive via a --ve-* token)

Each eval was verified to fail by breaking the behavior (guard return, scale
min->max, a hardcoded CTA color) and reverting. Unit tests cover the pure
logic (scale-to-fit math, dismiss guard via linkedom, tint/solidTint). Both
wired into CI (npm test in static job, ve:eval-presentation in evals job).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bundled Tailwind CSS always carries SlideDeck's scroll-snap utilities, so
the slides-profile heuristic misclassified every PresentationDeck artifact
and then failed deck-scroll-snap-axis against a deck that never scrolls.
Artifacts carrying data-ve-presentation now verify as pages (both in
lib/profile.mjs and the browser-dev tool's local detector). The demo artifact
verifies clean: errors=0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs/presentation-deck.md explains when to reach for each deck engine, the
primitive inventory, and the eval-pinned behavioral contract. CONTRIBUTING
gains a section requiring npm test + ve:eval-presentation for engine changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fonts

Long Metric values wrapped under mono-display presets ("1920×1080" broke
into two lines, reading as a different number). The value now renders
white-space: nowrap with a character-count size clamp (values longer than 8
chars shrink proportionally) so it fits at any preset's glyph width. Added a
metric-value-no-wrap eval that measures every [data-ve-metric-value] on both
preset exports — terminal's fully monospace display font is the worst case —
and fails on wrap or cell overflow (verified against the pre-fix behavior).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… CTA ink

Three merge-blockers, each pinned by a new falsifiable eval:

- Keyboard guard: modifier chords (meta/ctrl/alt) are never intercepted, and
  focus inside input/textarea/select/[contenteditable] now owns EVERY key
  (arrows move the caret, Space types a space) instead of only Space/Enter.
  Eval: keyboard-guard-typing-and-modifiers (verified failing pre-fix: the
  old handler navigated on Meta+ArrowRight and swallowed typed spaces).

- Sheet-gated navigation: the 80px edge click zones (z-50) sit above an open
  drill sheet (z-40); both they and the nav keys are now gated while a sheet
  is open, so a click or keystroke can never blow past the sheet and change
  slides. Escape closes the sheet first, then navigation resumes. Eval:
  nav-gated-while-sheet-open.

- Light-tone CTA ink: global.css remaps --ve-accent to the ink color on
  light-tone slides, so a primary chip filled with it rendered ink-on-ink
  (measured 1.06:1 contrast). Light tone now flips --ve-pres-cta-ink to the
  slide surface exactly like the accent tone. Demo slide 3 (light) gains a
  primary DrillChip + tone-remapping sheet; eval
  light-tone-primary-cta-contrast asserts >= 3:1 computed contrast.

Also folded after the rebase onto 0.8.0 main: registry-open VisualPreset kept
alongside the presentation re-exports, combined npm test script, deduped CI
test step, CHANGELOG entries folded into 0.8.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@theclaymethod
theclaymethod force-pushed the feat/presentation-deck branch from a06979f to 591877f Compare July 15, 2026 19:43
@theclaymethod
theclaymethod merged commit 73e11f8 into main Jul 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant