docs(skill): slideshow authoring guidance + standalone harness reference#1583
Conversation
67380f5 to
3f95540
Compare
miga-heygen
left a comment
There was a problem hiding this comment.
Review — docs(skill): slideshow authoring guidance + standalone harness reference
Overall this is a solid and thorough docs PR. The authoring contract in SKILL.md is well-structured, the worked example is substantive, and the standalone harness reference in standalone-harness.md does a good job covering the interim workarounds with clear code samples and the foot-gun checklist. A few items need fixing before merge:
Factual inaccuracies (must fix)
-
sceneIdresolution claim contradicts the linter and itself.In the
SlideRefschema table (SKILL.md, sceneId row), the docs say:"The lint rule resolves both
data-composition-idand.clip[id]."This is wrong. The lint rule in
packages/core/src/lint/rules/slideshow.ts(PR #1580) callscollectCompositionIdScenes, which only collects elements withdata-composition-id. There is an explicit test that flags a sceneId matching only a.clip[id]:it("flags a sceneId that matches only a .clip[id] (not a data-composition-id)", ...
Confusingly, the Validation section at the bottom of the same file correctly says "resolves to an existing scene (by
data-composition-id)." The schema table and the validation section contradict each other within the same document.Fix: Remove the
.clip[id]claim from the schema table. Something like: "Must match a scene'sdata-composition-id. A sceneId that only matches a.clip[id]is flagged as invalid." -
Fragment boundary semantics.
The docs say:
"Fragment times must be strictly inside
[start, end]."The actual validation in
parseSlideshow.tsuses:if (f < start || f > end)
This means fragments at
startor atendare valid — the range is inclusive, not strict. Change "strictly inside" to "within" (or say "inside the inclusive range[start, end]").
Missing details (should fix)
-
slideSequencesoptionality not marked.The
SlideshowManifestschema block showsslideSequencesas a peer ofslidesbut never marks it optional. The actual type isslideSequences?: SlideSequence[]. Since many decks have no branching, an explicit "optional" note (or a?in the schema comment) would prevent authors from thinking they must include an empty array. -
Partial startTime/endTime override behavior undocumented.
The schema table lists
startTimeandendTimeas optional overrides, but doesn't explain what happens when you provide one but not the other. The implementation fills the missing bound from the scene'sdata-start/data-duration. If the scene doesn't exist and only one bound is provided, the linter gives a specific error (e.g.,slide "x" sets endTime but startTime cannot be resolved). A one-line note in the table would save authors from trial-and-error.
Nits / clarity (optional)
-
Worked example:
pain-3animation timing at 13.0 vs. fragment times at 11.0 and 15.0.The GSAP timeline inserts
pain-3's entrance at absolute time 13.0, which is between the two fragment hold-points (11.0 and 15.0). This is technically valid — fragments and GSAP timelines are independent — but a reader might expect each fragment to correspond to exactly one element reveal. A brief inline comment like// enters between fragments — visible after fragment 1 fireswould preempt confusion. -
Standalone harness: Section 3 duplicates
#three-canvasCSS.The Three.js canvas
position:fixed; ...style is set once inline via JS (canvas.style.cssText = ...) and again in the CSS block at the end of Section 7. Not a bug, but a reader might wonder which one wins. Consider noting that the JS path is the runtime assignment and the CSS block is the stylesheet equivalent for reference.
What's good
- The "two pieces" framing (scenes + JSON island) is the right mental model and will land well with new authors.
- The slide writing rules section is opinionated in exactly the right way — these are the kinds of constraints that prevent mediocre decks.
- The foot-gun checklist in the standalone harness is excellent. Every entry maps to a real failure mode with a concrete fix.
- Correctly marking the standalone patterns as interim with pointers to the durable engine-hosted path avoids locking in tech debt.
Items 1-2 are factual errors that should be fixed before merge. Items 3-4 are gaps worth addressing. Items 5-6 are optional polish.
Review by Miga
## 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
left a comment
There was a problem hiding this comment.
Reviewed at: 55f16d3d5b2644ba387aa909528fd34bbad11065 (HEAD)
Verdict: Comment — solid docs with two factual errors against the linter + parser, one structural omission (registry-item.json), and a branching-strategy flag. Miga (review) already caught the two semantic mismatches; layering on top, plus a few concerns that fall outside docs-vs-code drift.
Confirming Miga's two factual errors (must fix)
I independently re-verified both against packages/core/src/lint/rules/slideshow.ts and packages/core/src/slideshow/parseSlideshow.ts at this SHA — both findings stand:
sceneIdrow claims.clip[id]resolution.SKILL.md:90— "The lint rule resolves bothdata-composition-idand.clip[id]" — contradictsslideshow.ts:42-50(onlycollectCompositionIdScenesruns;.clip[id]collection was deleted earlier in the stack) and the test atparseSlideshow.test.tsthat explicitly flags the.clip[id]-only case. The Validation section atSKILL.mdline ~388 has the right wording — the table is the wrong one. Confirming Miga's fix.- Fragment range "strictly inside".
SKILL.md:154says "strictly inside[start, end]"; actual validation atparseSlideshow.ts:90-94isif (f < start || f > end)— both bounds are inclusive. (PR #1585 does not change this; the inclusive behavior is preserved.) Confirming Miga's fix.
Additional findings not in Miga's review
-
Worked example: GSAP
.from()semantics could mislead.SKILL.mdworked example (around line 290) usestl.from("#pain-1", { opacity: 0, y: 20, duration: 0.4 }, 11.0);andpain-3at13.0. Two issues for a reader following cold:- The
data-composition-id="problem"div hasdata-start="6"but its inner.clipalso hasdata-start="6"— and the elements haveopacity: 0inline. GSAP.from()animates fromopacity:0to the current style, which is alsoopacity:0— the element will land atopacity:0permanently. Should betl.to(...)or remove the inlineopacity: 0. A reader copy-pasting this verbatim will see no reveals. - Miga's nit about
pain-3at13.0between fragments 11.0 and 15.0 — the deeper issue is thatpain-3never lands at a fragment hold at all (it enters mid-second-segment, not at a hold). Worth a sentence explaining "entrances can land outside fragment holds; fragments are pause points, not animation triggers" or movingpain-3to15.0so each fragment maps cleanly.
- The
-
slideSequencesoptionality (Miga raised; adding context). TheisManifestvalidator on the next PR (parseSlideshow.tsin #1585, lines 40-50) now strictly validatesslideSequencesasarray | undefined— aslideSequences: nullvalue will fail validation asslideshow_invalid. Worth noting in the docs: omit the key entirely, don't passnull. -
Standalone harness reference — Section 7 CSS / Section 3 JS duplication (Miga raised). The doc is 597 lines and the foot-gun checklist is excellent, but one pattern I'd flag: the recommendation to use
console.errorsuppression for WebGL detection (mirrored from airbnb-deck's pattern) is both documented in the harness and shipped in #1584's actual code. If the engine-hosted path lands, both copies need to be updated. Worth a// TODO: drop when engine-hosted preview shipsmarker tied to a tracking issue. -
No mention of
registry-item.jsonfor the demo decks. The harness reference atstandalone-harness.md:18-19points readers toregistry/examples/airbnb-deck/index.htmlandstartup-pitch/index.htmlas "living reference implementations." Those examples in #1584 ship withoutregistry-item.json(all otherregistry/examples/*have one — seedecision-tree,play-mode,kinetic-type,nyt-graph). That means a reader can'tnpx hyperframes add airbnb-deck, and the catalog generator (scripts/generate-catalog-pages.ts) won't pick them up. Either:- Fix in #1584 (add
registry-item.jsonto each new example), and the doc reference stands. - Or update this doc to say "these examples are not yet registry-installable; clone the repo to follow along."
Flagging here because the docs' usefulness depends on the examples being discoverable the way the docs imply.
- Fix in #1584 (add
Branching strategy concern (cross-PR, non-blocking for docs content)
This PR stacks on ss-skill → ss-studio (#1582) → ss-player (#1581 monolith) → ss-core (#1580). The parallel split path #1589 ss-player-a → #1590 ss-player-b → #1591 ss-studio-a → #1592 ss-studio-b is open simultaneously. If the team picks the split, this stack (and #1584, #1585) needs a rebase onto the new split tip. Worth deciding the monolith-vs-split outcome before this docs PR lands, or the SKILL.md "where to read next" references could be wrong.
Nits
SKILL.md:144— "A fragment is a time (in seconds) within a slide's[start, end]range where the controller pauses before the next reveal." Combined with the "fragment times are absolute composition-timeline positions" note three paragraphs down, the wording can read as "this is a relative offset measured in seconds." Consider hoisting the absolute-coordinate clarification into the first definition sentence.- Schema tables — for parity with #1585's validator, consider noting that
hotspotsrequiresArray.isArray(not just truthy) and that barenullfor any optional field is rejected.
What's good
- The two-pieces framing (scenes + island) is the right pedagogical wedge.
- Slide writing rules section will actually prevent bad decks — the headline-as-claim and bottom-up TAM rules are the right shape.
- Foot-gun checklist in the standalone-harness reference is high-density and well-targeted.
- Interim framing on the harness is correctly load-bearing — the doc explicitly says "don't treat as blessed" and points at the durable path.
Items 1-2 (Miga) and 6 (registry-item.json gap) are the blocking items. 3 worked-example correctness, 4-5 are tightening, the rest are nits.
Review by Rames D Jusso
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>
3f95540 to
5121992
Compare
The base branch was changed.

Slideshow mode — 4/5: authoring skill & harness reference
Docs only. Teaches an agent how to author a valid slideshow composition, and documents the interim standalone-runtime patterns. Builds on #1582.
Key changes
skills/slideshow/SKILL.md— the authoring contract: the JSON-island schema, the slide writing rules (headline-as-claim, one idea per slide, bottom-up market math, font minimums), fragments, hotspots/branching, the<hyperframes-slideshow>wrapper, and validation viahyperframes lint. Includes a full worked example.skills/slideshow/references/standalone-harness.md— the patterns for running a deck standalone on the bare player bundle (no engine): playhead-driven scene visibility, imperative-on-activate entrances, content-visible-at-rest, the scenes bootstrap postMessage, audio/SFX played in the parent frame, Three.js on its own rAF, and a foot-gun checklist. Framed explicitly as interim — the durable answer is engine-hosted slideshow preview + the component reading the island from the composition.skills/hyperframes/SKILL.md— routes slideshow/presentation/pitch-deck intent to/slideshow.Stack
core (#1580) → player (#1581) → studio (#1582) → skill → examples (#1584).
🤖 Generated with Claude Code