Skip to content

test(examples): slideshow demos — airbnb deck, startup pitch, fixture#1584

Merged
vanceingalls merged 6 commits into
mainfrom
ss-examples
Jun 19, 2026
Merged

test(examples): slideshow demos — airbnb deck, startup pitch, fixture#1584
vanceingalls merged 6 commits into
mainfrom
ss-examples

Conversation

@vanceingalls

@vanceingalls vanceingalls commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Slideshow mode — 5/5: demo compositions

Three runnable slideshow compositions exercising the full feature end to end. Builds on #1583.

Demos

  • registry/examples/airbnb-deck/ — a current-Airbnb-branded remake of the 2009 seed pitch deck (11 slides): Three.js cinematic backgrounds (soft coral particle field, low-poly globe + arc routes on the market slide) on their own rAF; GSAP imperative entrances; a fragment reveal on the problem slide; a hotspot → market-sizing branch with full forward/back round-trip; the unified mute+nav capsule; and real HeyGen sound effects (sourced via the HeyGen MCP, played from the parent frame). Includes DESIGN.md and the sfx/ assets.
  • registry/examples/startup-pitch/ — an animated FlowDesk investor pitch (no 3D), demonstrating the same playhead-driven scene/entrance harness.
  • registry/examples/slideshow-demo/ — a minimal fixture used for lint/validate.

Each demo wraps <hyperframes-slideshow><hyperframes-player> and carries the island in the wrapper (the documented interim pattern). All pass hyperframes lint (0 slideshow errors) and were driven headless to verify nav, fragments, branch round-trip, presenter sync, and SFX cues.

Stack

core (#1580) → player (#1581) → studio (#1582) → skill (#1583) → examples.

🤖 Generated with Claude Code

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review — test(examples): slideshow demos

Reviewed the full 3,243-line diff across all seven new files. Overall this is solid demo/fixture work that exercises the slideshow schema from #1580 comprehensively. A few observations:


1. Test coverage — schema exercise ✅

All three examples produce well-formed application/hyperframes-slideshow+json islands that exercise the key schema features:

  • slides[] with sceneId, notes, fragments, hotspots — all covered.
  • slideSequences[] with branch slides (market-math-detail, market-sizing-drill, dive-deeper) — all three examples include at least one branch.
  • hotspots with id, label, target, region {x,y,w,h} — exercised in airbnb-deck and startup-pitch main decks, plus the slideshow-demo fixture.
  • Fragments — exercised in all three (airbnb-deck: 2 fragments, startup-pitch: 3 fragments, slideshow-demo: 2 fragments).

Good breadth. The fixture (slideshow-demo) keeps things minimal while still exercising fragments, hotspots, and branch sequences — exactly what a lint/validate fixture should do.

2. Demo quality ✅

Both full decks (Airbnb and FlowDesk) are realistic, investor-pitch-grade compositions that would actually validate the feature end-to-end:

  • Airbnb deck: 11 main slides + 1 branch, Three.js backgrounds with deterministic seeded LCG (nice — no Math.random()), GSAP entrances, fragment reveals, hotspot branching. The DESIGN.md is a nice touch for documenting brand/design constraints.
  • FlowDesk pitch: 9 main slides + 1 branch, no 3D (validates the no-WebGL path), same playhead-driven visibility + entrance pattern.

These aren't toy examples — they'd genuinely catch regressions in nav, fragments, branch round-trip, and scene lifecycle.

3. Fixture design ✅

slideshow-demo/index.html at 429 lines is focused — 3 main scenes + 2 branch scenes, no extra styling beyond what's needed. It exercises sceneId resolution (the comment at line 2267 explicitly notes this tests collectCompositionIdScenes). Appropriately minimal for a lint/validate target.

4. Hardcoded paths / secrets / environment values

No secrets or env-specific values found. Good.

Two observations on paths:

  • demo.html files reference ../../../packages/player/dist/hyperframes-player.global.js and ../../../packages/player/dist/slideshow/hyperframes-slideshow.global.js — these are relative paths within the monorepo, which is the right pattern for registry examples. ✅
  • CDN references use pinned versions: gsap@3.14.2, three@0.160.0 — good practice, no floating @latest. ✅
  • The postMessage origin uses '*' — acceptable for iframe ↔ parent communication in demo contexts, and documented in comments. Would be worth tightening in production, but fine for examples.

5. DRY — duplication analysis

This is the one area worth flagging:

5a. JSON island duplication (demo.html ↔ index.html) — acknowledged, acceptable

Both airbnb-deck/demo.html and startup-pitch/demo.html carry a full copy of the slideshow JSON island from their respective index.html. This is explicitly called out in comments: "This duplication is intentional: <hyperframes-slideshow> reads the island from its OWN innerHTML (not from the composition loaded by <hyperframes-player>)." The comment also notes this is a "current workaround" to be resolved when the component reads directly from the composition. Fine — it's documented debt, not accidental duplication.

5b. Scene visibility controller + entrance driver — duplicated across all three compositions

The updateVisibility() + fireEntrance() + fragment-reveal pattern is copy-pasted across all three index.html files with only the scene list and fragment config varying. This is ~80-100 lines of near-identical imperative JS per file. For examples/fixtures, this is tolerable — the compositions need to be self-contained, runnable HTML files. But if this pattern proliferates to more examples, extracting a shared slideshow-driver.js utility would be worth it.

5c. Scene list duplication within each composition

Within the airbnb-deck index.html alone, the scene list (IDs + start/end times) appears in three separate places:

  1. The visibility controller
  2. The SFX transition patcher
  3. The postMessage bootstrap

All three must stay in sync manually. This is the most fragile part of the PR — a scene timing change requires updating three arrays in the same file. Not a blocker for examples, but worth a // SYNC: marker comment pointing to the other locations if it isn't there already.

5d. Bélo SVG — repeated 11 times in airbnb-deck

The SVG path for the corner Bélo mark is copy-pasted on every slide (<svg width="36" height="36" viewBox="0 0 100 100" ...>). In a composition that must be a single self-contained HTML file, this is unavoidable — CSS background-image or an <svg><use> pattern could reduce it, but the current approach is the simplest and most maintainable for an example. No action needed.


Minor nits (non-blocking)

  1. postMessage wildcard origin: parent.postMessage({...}, '*') is used in all three compositions. Fine for demos, but a comment like // '*' is safe here — composition and host are always same-origin would be even clearer.

  2. WebGL fallback in airbnb-deck: The console.error suppression trick (lines 1343-1351) is clever but aggressive — it suppresses ALL console.error calls during renderer creation, including potential non-THREE errors. A more targeted approach would be to check WebGLRenderingContext support first. Not a blocker for an example.

  3. startup-pitch/demo.html doesn't include the sound attribute on <hyperframes-slideshow> (line 2358), while airbnb-deck/demo.html does (line 136). Intentional? If FlowDesk has no SFX, removing sound is correct; if it should have SFX too, it's missing.


Verdict

Clean examples that exercise the full slideshow schema surface area — slides, fragments, hotspots, branch sequences, scene visibility, entrance animations, and SFX. No secrets, no environment coupling, good use of pinned CDN versions. The duplication is acknowledged and appropriate for self-contained example compositions. The scene-list-sync fragility within files is the only thing I'd want a // SYNC: comment for.

Recommend approval once the scene-list sync fragility is acknowledged (comment or not — author's call).

Review by Miga

miguel-heygen
miguel-heygen previously approved these changes Jun 19, 2026

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

vanceingalls added a commit that referenced this pull request Jun 19, 2026
## Slideshow mode — 1/5: core schema, parser & lint

Foundation for slideshow mode: a composition can declare an embedded **slideshow manifest** that turns its continuous timeline into a discrete, navigable deck. This PR adds the data model, parser, and validation — no runtime/UI yet.

### What & why
A slide is just an existing scene (`data-composition-id` + `data-start`/`data-duration`) plus metadata declared in one embedded `<script type="application/hyperframes-slideshow+json">` island. Keeping the manifest *in the composition* means no new file format and no build step — slides are additive metadata over a normal composition.

### Key changes
- `slideshow/slideshow.types.ts` — `SlideshowManifest`, `SlideRef`, `SlideHotspot`, `SlideSequence` and their resolved counterparts. TTS fields (`ttsScript`/`ttsAudioUrl`/`ttsDurationMs`) are present but **reserved** (playback not built).
- `slideshow/parseSlideshow.ts` — `parseSlideshowManifest(html)` extracts the island; `resolveSlideshow(manifest, scenes)` resolves each `sceneId` to a `{start,end}` range (honouring optional `startTime`/`endTime` overrides) and returns validation errors for: unresolved sceneId, fragment outside a slide's range, hotspot targeting an unknown sequence, and overlapping main-line slides.
- `lint/rules/slideshow.ts` — surfaces those resolve errors under `hyperframes lint`; derives scenes from `data-composition-id` (matching the runtime's scene source).
- `lint/rules/core.ts` — exempts the slideshow island MIME type from the inline-script-syntax check.
- `./slideshow` subpath export (dev + publishConfig) so downstream packages import only the lightweight parser, keeping core's Node-only barrel out of their typecheck graph.

### Testing
`parseSlideshow.test.ts` + `slideshow.test.ts` (vitest) cover parse, resolution, every error path, and the lint rule.

### Stack
Bottom of a 5-PR stack: **core** → player (#1581) → studio (#1582) → skill (#1583) → examples (#1584).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

@james-russo-rames-d-jusso james-russo-rames-d-jusso left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at: c21951bc041194bffc7088859a355a296bdf7e91 (HEAD)

Verdict: Comment — comprehensive demos, but missing registry-item.json for all 3 new examples breaks hyperframes add / catalog discoverability. Miga (review) caught the DRY + sync-comment items; layering with the registry omission, demo-realism notes, and a fixture-coverage gap.


Blocker — missing registry-item.json for all 3 new examples

Every existing registry/examples/* ships a registry-item.json (verified across decision-tree, play-mode, kinetic-type, nyt-graph, motion-blur). It declares the example to:

  • packages/cli/src/commands/add.ts — enables npx hyperframes add airbnb-deck
  • scripts/generate-catalog-pages.ts — surfaces the example on the docs catalog
  • packages/core/src/registry/types.ts — declares dimensions/duration/files

The three new examples do not have one:

  • registry/examples/airbnb-deck/DESIGN.md, demo.html, index.html, sfx/ only
  • registry/examples/startup-pitch/demo.html, index.html only
  • registry/examples/slideshow-demo/index.html only

Consequence: these demos exist as files in the repo but are invisible to the registry plumbing. The skill doc in #1583 points at these as "living reference implementations" — readers following the doc will be told to npx hyperframes add ... (or expect to) and get nothing. The fixture slideshow-demo is the smaller concern — it's primarily a lint target — but the two showcase decks should be addressable via hyperframes add or the catalog.

Fix: add registry-item.json to each (template: play-mode/registry-item.json). For airbnb-deck this should include the sfx/*.mp3 files in files[] so the installer copies them.


Layered on Miga's findings

  1. Scene-list duplication within each composition (Miga 5c). Confirming the three-place sync risk. In airbnb-deck/index.html the scene IDs/times appear in: visibility controller, SFX patcher, postMessage bootstrap. A // SYNC: marker comment isn't free protection — a clean refactor would be to declare scenes once in a JS object at the top of the file and have all three consumers read from it. That fits within "self-contained HTML file" since it's all inline. Worth the polish before this becomes the reference shape that future examples cargo-cult.

  2. Scene-driver duplication across decks (Miga 5b). updateVisibility()/fireEntrance()/fragment-reveal copy-pastes across all 3 index.html files. Agree it's tolerable for 3 examples but extracting to registry/examples/_shared/slideshow-driver.js (or similar) before the 4th example lands is the cheap-now-vs-expensive-later split. Not a blocker — but the standalone-harness reference in #1583 also documents these patterns verbatim, so the same drift can hit 3 places (each example file, the standalone-harness doc, plus the SKILL.md worked example) any time the controller changes.

Fixture coverage gap

  1. slideshow-demo doesn't exercise data-end / data-hf-authored-end. PR #1585 expands lint parseTiming to accept data-end and data-hf-authored-end (matching runtime). The fixture only uses data-duration. If the fixture is the canonical lint-target test, it should exercise both code paths so a regression in the new data-end branch is caught. A single branch slide using data-end instead of data-duration would do it.

Demo-realism / asset audit

  1. SFX assets — sourced + reasonable size. Verified the 4 .mp3 files (advance, back, branch-enter, fragment) are 9.6KB-54KB each (~155KB total). PR body claims "real HeyGen sound effects sourced via the HeyGen MCP" — no provenance/license note in DESIGN.md or sfx/README. For a registry example that ships under whatever the repo license is, worth a one-line attribution + license note in DESIGN.md so an external consumer doesn't republish without clearance.

  2. postMessage(*, '*') is fine for demos but document the constraint. In all 3 compositions, parent ↔ iframe messaging uses the '*' origin. Miga's nit-1 covers commenting it; a stronger version is to compute iframe.contentWindow.location.origin once and hold it — same effort, less foot-gun for someone copy-pasting the pattern to a non-demo context. Both work; flagging because the harness doc in #1583 documents '*' as the blessed shape.

  3. WebGL fallback console.error suppression (Miga nit 2). Confirming this — the airbnb-deck pattern at index.html lines ~1343-1351 swallows all console.error during new THREE.WebGLRenderer(). A targeted alternative: const canvas = document.createElement('canvas'); const ctx = canvas.getContext('webgl2') || canvas.getContext('webgl'); if (!ctx) { /* fallback */ }. Same intent, no console-mute side effect. Worth it because this pattern is also in the standalone-harness doc and will propagate.

demo.htmlindex.html island duplication (Miga 5a)

Confirming this is documented debt with a comment pointing to the durable fix (engine-hosted preview). Acceptable today; flagging that PR #1585 adds another duplicated island in presenter-test.html — now there are three places to keep in sync for airbnb-deck alone. Not the moment to fix, but worth a single tracking issue so the cleanup happens when the engine-hosted path lands.

Cross-PR branching concern

Same branching strategy note as #1583 — this stack chains on ss-skillss-studio (the monolith #1582) not the split (#1589-#1592). If the team picks the split, this PR needs a rebase. Worth a stack-direction decision before any of #1583/#1584/#1585 lands.

Nits

  • startup-pitch/demo.html legitimately omits sound attribute — verified, no SFX expected. Intentional, not a gap.
  • The airbnb deck DESIGN.md is a strong touch; replicating it (even a 20-line version) for startup-pitch would help readers understand the design intent of the second deck.

What's good

  • Schema coverage is thorough: slides, fragments, hotspots, branch sequences, scene visibility, entrance animations, SFX cues — all exercised end-to-end.
  • Deterministic seeded LCG (no Math.random()) is the right call for examples that may be screenshot-tested or video-exported.
  • Pinned CDN versions for GSAP/Three. Good practice.
  • The fixture (slideshow-demo) is appropriately minimal at 429 lines.
  • DESIGN.md for airbnb-deck is a quality touch worth replicating.

Item missing-registry-item.json is the only blocker. 7-9 are concerns worth addressing. 10-12 are tightening.

Review by Rames D Jusso

vanceingalls and others added 6 commits June 19, 2026 01:12
Stack-split from the original ss-player PR (#1581): the SlideshowController
state machine (stack-based slide/fragment/branch navigation) and its tests.
The <hyperframes-slideshow> web component follows in the next PR.
Stack-split from the original ss-player PR (#1581): the <hyperframes-slideshow>
custom element (wraps <hyperframes-player>, drives the controller),
presenter/audience BroadcastChannel sync, nav chrome, and the player scenes hook.
Stack-split from the original ss-studio PR (#1582): the data layer —
setSlideshowManifest, the useSlideshowPersist hook, and panel helpers.
The editor panel UI follows in the next PR.
Stack-split from the original ss-studio PR (#1582): the SlideshowPanel /
SlideshowSubPanels editor UI, right-panel wiring, and app integration.
New /slideshow skill (island schema, slide rules, fragments, branching,
validation) + a standalone-harness reference doc, and a router entry in
the /hyperframes skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three runnable slideshow compositions: a current-Airbnb-branded remake of
the 2009 seed deck (Three.js backgrounds, GSAP entrances, hotspot branch,
HeyGen SFX), an animated startup pitch, and a minimal fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Base automatically changed from ss-skill to main June 19, 2026 08:35
@vanceingalls vanceingalls dismissed miguel-heygen’s stale review June 19, 2026 08:35

The base branch was changed.

@vanceingalls vanceingalls merged commit ae40498 into main Jun 19, 2026
23 of 31 checks passed
@vanceingalls vanceingalls deleted the ss-examples branch June 19, 2026 08:35
vanceingalls added a commit that referenced this pull request Jun 19, 2026
- .prettierignore: exclude generated demo compositions (registry/examples/**/*.html)
  from oxfmt — large video-pipeline output (GSAP/Three/WebGL), not hand-authored
  source. Was failing 'Format' repo-wide (pre-existing on main via #1584).
- .fallowrc: exempt SlideshowPanel.tsx (health/complexity — section fan-out) and
  the slideshowPanelHelpers.ts / SlideshowPanel.test.ts parallel-structure clones
  (duplicates.ignore). File-level config, not inline comments — inline shifts line
  numbers and breaks fallow's inherited-finding fingerprint (per existing rc note).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vanceingalls added a commit that referenced this pull request Jun 19, 2026
* fix(slideshow): address code-review findings #1580-1584

- player: bundle @hyperframes/core into the IIFE/global build (noExternal)
- player: resolve audience mode from ?mode=audience URL query, not just attr
- player: event-driven waitForScenes + loud failure when no slides resolve
- player: scope window keydown so Space/Backspace don't hijack the host page
- player: audience mirrors full position (branch + fragment) via syncTo
- player: next() reveals remaining fragments even at slide end; enterBranch ignores empty sequences
- core: harden extractScenes against null/non-object scene entries
- core: strict manifest validation; error on inverted ranges & empty hotspot targets; dedup fragments
- core/lint: accept data-end/timeline-derived scene durations (match runtime)
- core+studio: share ISLAND_TYPE + island regex from @hyperframes/core/slideshow
- studio: SlideList reflects manifest slide order; branch-slide authoring (notes/fragments/hotspots)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(player): slideshow fullscreen + presenter-view rework

- fullscreen toggle in the nav chrome (button + 'F' key); standard Fullscreen
  API on the <hyperframes-slideshow> element, icon reflects state
- presenter console: live slide on top, speaker-notes panel below, with the nav
  controls shown in-view; Present button hides once presenting (harness)
- audience (viewer) window: chrome reduced to a fullscreen-only control, no nav
- fix: audience / back() / backToMain() mirror stayed frozen on the first frame —
  a bare paused seek does not repaint some compositions. resumeSlide now plays a
  brief render-nudge (RENDER_NUDGE) past the target so the composition paints,
  then onTime pauses at the hold
- refactor: extract reusable buildNavCluster() + wireChromeButtons(); rework
  buildPresenterLayout into the bottom notes panel
- example: airbnb-deck presenter-test.html harness (Present button + 'F')

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(player): slideshow no auto-progress + presenter slide fits/pins

- navigation jumps to a static frame instead of auto-playing the timeline:
  playTo() seeks to the hold (+ a brief RENDER_NUDGE to repaint) rather than
  sustaining playback, so slides hold until the user advances
- presenter view: pin the live slide to the top and confine the player to the
  region above the notes panel, so the player CONTAINS the composition — the
  full slide stays visible (letterboxed) at any width and re-fits on resize;
  its bottom is no longer cut off by the notes panel
- tests: seek targets updated for the render-nudge offset

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(slideshow): presenter nav flash, slide-1 boundary, branch buttons

Three presenter-mode fixes from testing the airbnb deck: (1) navigation flash — seek to the exact target then play forward to repaint, instead of seeking backward (t-0.2) which painted the previous scene at boundaries; split hold into holdTarget (logical) and holdAt (target+nudge, clamped to slide.end). (2) slide-1 boundary — no-fragment slides rest at the slide midpoint, not slide.end. (3) presenter branch buttons — surface hotspots as buttons in the presenter console (the on-slide pill is lost in the letterboxed view). Also extract paintChrome() to dedupe the three chrome-render sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(slideshow): stop presenter nav buttons flickering / dropping clicks

The presenter elapsed clock called render() every second, which rebuilt the
entire chrome (innerHTML) including the nav buttons — they flickered and any
click landing mid-rebuild was lost. The 1s tick now updates only the elapsed
text node; the nav buttons are rebuilt only on actual navigation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(slideshow): CSP-safe nav hover, UUID editor ids, manifest version

Addresses review feedback on the split stack:

- CSP: replace the 8 inline onmouseover/onmouseout handlers on the nav
  buttons with a [data-hf-nav-cluster] button:hover CSS rule (injected once
  per document). No inline event handlers → works under strict CSP.
- IDs: studio sequence/hotspot id generation used Date.now() (sub-ms
  collision on rapid clicks) — now crypto.randomUUID().
- Versioning: stamp version on the persisted manifest island (preserving an
  existing one); add the optional version field + SLIDESHOW_MANIFEST_VERSION
  to the core schema so future schema changes can migrate older islands.

These live on the review-fixes tip (consistent with the stack's fixup-on-tip
model); the touched code belongs to ss-player-b (#1590), ss-studio-a/b
(#1591/#1592), and ss-core (#1580).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(ci): fix format + fallow gates for slideshow stack

- .prettierignore: exclude generated demo compositions (registry/examples/**/*.html)
  from oxfmt — large video-pipeline output (GSAP/Three/WebGL), not hand-authored
  source. Was failing 'Format' repo-wide (pre-existing on main via #1584).
- .fallowrc: exempt SlideshowPanel.tsx (health/complexity — section fan-out) and
  the slideshowPanelHelpers.ts / SlideshowPanel.test.ts parallel-structure clones
  (duplicates.ignore). File-level config, not inline comments — inline shifts line
  numbers and breaks fallow's inherited-finding fingerprint (per existing rc note).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(slideshow): address PR review + CodeQL findings

- CodeQL #638 (parseSlideshow): complete the regex metachar escape in
  slideshowIslandRegex (was missing backslash); add JSDoc on the factory +
  lastIndex caveat (reviewer 5a/16).
- CodeQL #639/#640 + review items 13/17: remove registry/examples/airbnb-deck/
  presenter-test.html — a generated test harness (postMessage w/o origin check,
  proto-pollution) that was scope-creep into a fix PR and a 3rd duplicate island.
  Regenerate locally via the scratchpad script when testing.
- Review item 15 (docs drift in skills/slideshow/SKILL.md): lint resolves scenes
  by data-composition-id only (not .clip[id]); fragments are valid INCLUSIVE of
  [start,end], not 'strictly inside'.

IIFE bundles core confirmed (0 external @hyperframes/core refs in the slideshow
global build). format/lint/fallow green.

* feat(cli): add 'present' command — serve a deck in presenter mode

hyperframes present [dir] starts a lightweight HTTP server, wraps the
composition in <hyperframes-slideshow> with its island inlined, and opens
the browser. A real HTTP origin is required for presenter mode: present()
opens the audience window via window.open(?mode=audience) and the two sync
over BroadcastChannel — neither works from file://.

- New utils/compositionServer.ts factors the server scaffolding shared with
  'play' (resolve runtime/player/slideshow bundles, inject runtime, asset
  content-types, bind to a free port); play.ts now uses it too.
- Errors clearly if the deck has no slideshow island.
- .fallowrc: exempt the play/present command entrypoints (validation + server
  wiring) and the per-command startup/logging block from the complexity /
  duplication gates.

Verified end-to-end against registry/examples/airbnb-deck: server serves the
wrapper + assets, the component binds and renders (counter 1 / 11).

* fix(cli): present renders the deck (player sizing + self-driving serve)

Two bugs caused a black slide area:
- The <hyperframes-player> had no positioning, so its iframe collapsed to
  zero size — the (absolutely-positioned) chrome showed but the composition
  didn't. Add position:absolute; inset:0 (matches demo.html).
- The composition was served with the engine runtime injected, which leaves
  its timelines engine-paused (blank). Slideshow decks self-drive their own
  timelines (like demo.html / the standalone harness), so serve them raw.

Verified end-to-end on registry/examples/airbnb-deck: cover renders, Next
advances 1/11 -> 2/11 and slide 2 paints.

* fix(cli): present plays slideshow sound effects

The composition (in the player's sandboxed iframe) posts
{ type: 'hf-sfx', name } to the parent on nav, but the iframe is
autoplay-blocked — audio must play in the parent that owns the user gesture.
Add the parent-side hf-sfx handler (the 4 standard clips advance/fragment/
branch-enter/back, served from the deck's sfx/ under /composition/sfx/),
gesture-unlocked and mute-aware, in both presenter and audience windows.

Verified: sfx serve 200 (audio/mpeg) and Next delivers [advance, fragment]
to the parent handler.

* feat(examples): softer mellow slideshow sfx for airbnb-deck

Replace the aggressive percussive pops with gentle sine-tone cues (warm
pitches C5/G4/E5/F4, 12ms attack + exponential decay, lowpassed) — advance/
fragment/branch-enter/back. Much lighter; fragment is the most subtle.

* feat(examples): whoosh + sparkle slideshow sfx for airbnb-deck

Replace the sine-tone cues with airy, designed sounds:
- advance: a soft whoosh (band-limited pink noise, bell-shaped swell)
- back: that whoosh reversed and darkened
- fragment: a light sparkle (staggered high chime blips)
- branch-enter: whoosh + a trailing sparkle (magical entry)

* feat(examples): directional whoosh + richer branch-enter cue (airbnb-deck)

- Going backward a slide now plays the reverse whoosh (back), not advance —
  the sfx logic detects nav direction by scene order instead of firing advance
  for every scene change.
- branch-enter is now a more interesting magical cue: a faint whoosh + an
  ascending C5-E5-G5-C6 chime arpeggio + a trailing sparkle.

Verified: next then prev fires [advance, fragment, back]; no page errors.

* fix(cli): harden present sfx handler + mute-hover affordance (R2 review)

Addresses Rames R2 items 19-21:
- 20: the present audio handler reintroduced the CodeQL classes removed with
  presenter-test.html — add an origin check (same-origin composition iframe)
  and an own-property guard so a 'name' like __proto__ can't resolve to and
  mutate Object.prototype.
- 21: assetContentType used a bare index lookup (ext='__proto__' -> prototype);
  guard with Object.hasOwn.
- 19: the CSP hover rule erased the speaker button's muted color; add a
  higher-specificity [data-hf-muted] [data-hf-mute]:hover override.

Verified: hf-sfx origin matches location.origin (guard passes), advance/fragment
still fire, deck renders + advances. Items 14/18/22 deferred (minor, pre-existing).

* fix(slideshow): address remaining R2 items (14/18/22) + re-remove harness

- 14: resumeSlide now mirrors enterSlide — a no-fragment slide resumes at its
  midpoint (visible-at-rest), not frame-0; fragmented slides still resume to the
  saved fragment or slide.start. Added a dedicated test naming the heuristic.
- 18: fullscreenchange swaps only the fullscreen glyph + aria (hoisted SVGs to
  module consts) instead of re-rendering the whole chrome.
- 22: .prettierignore lists the specific generated demo compositions instead of
  blanket registry/examples/**/*.html, so hand-authored example HTML still formats.
- presenter-test.html: a stray 54a4460 git add -A had re-added the deleted
  harness (reviving CodeQL #639/#640); remove it again.

106 slideshow tests pass; tsc/lint/fallow/format clean; deck still renders.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

4 participants