Skip to content

docs(skill): slideshow authoring guidance + standalone harness reference#1583

Merged
vanceingalls merged 5 commits into
mainfrom
ss-skill
Jun 19, 2026
Merged

docs(skill): slideshow authoring guidance + standalone harness reference#1583
vanceingalls merged 5 commits into
mainfrom
ss-skill

Conversation

@vanceingalls

@vanceingalls vanceingalls commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

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 via hyperframes 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

@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.

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)

  1. sceneId resolution claim contradicts the linter and itself.

    In the SlideRef schema table (SKILL.md, sceneId row), the docs say:

    "The lint rule resolves both data-composition-id and .clip[id]."

    This is wrong. The lint rule in packages/core/src/lint/rules/slideshow.ts (PR #1580) calls collectCompositionIdScenes, which only collects elements with data-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's data-composition-id. A sceneId that only matches a .clip[id] is flagged as invalid."

  2. Fragment boundary semantics.

    The docs say:

    "Fragment times must be strictly inside [start, end]."

    The actual validation in parseSlideshow.ts uses:

    if (f < start || f > end)

    This means fragments at start or at end are valid — the range is inclusive, not strict. Change "strictly inside" to "within" (or say "inside the inclusive range [start, end]").


Missing details (should fix)

  1. slideSequences optionality not marked.

    The SlideshowManifest schema block shows slideSequences as a peer of slides but never marks it optional. The actual type is slideSequences?: 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.

  2. Partial startTime/endTime override behavior undocumented.

    The schema table lists startTime and endTime as 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's data-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)

  1. Worked example: pain-3 animation 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 fires would preempt confusion.

  2. Standalone harness: Section 3 duplicates #three-canvas CSS.

    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

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: 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:

  1. sceneId row claims .clip[id] resolution. SKILL.md:90"The lint rule resolves both data-composition-id and .clip[id]" — contradicts slideshow.ts:42-50 (only collectCompositionIdScenes runs; .clip[id] collection was deleted earlier in the stack) and the test at parseSlideshow.test.ts that explicitly flags the .clip[id]-only case. The Validation section at SKILL.md line ~388 has the right wording — the table is the wrong one. Confirming Miga's fix.
  2. Fragment range "strictly inside". SKILL.md:154 says "strictly inside [start, end]"; actual validation at parseSlideshow.ts:90-94 is if (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

  1. Worked example: GSAP .from() semantics could mislead. SKILL.md worked example (around line 290) uses tl.from("#pain-1", { opacity: 0, y: 20, duration: 0.4 }, 11.0); and pain-3 at 13.0. Two issues for a reader following cold:

    • The data-composition-id="problem" div has data-start="6" but its inner .clip also has data-start="6" — and the elements have opacity: 0 inline. GSAP .from() animates from opacity:0 to the current style, which is also opacity:0 — the element will land at opacity:0 permanently. Should be tl.to(...) or remove the inline opacity: 0. A reader copy-pasting this verbatim will see no reveals.
    • Miga's nit about pain-3 at 13.0 between fragments 11.0 and 15.0 — the deeper issue is that pain-3 never 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 moving pain-3 to 15.0 so each fragment maps cleanly.
  2. slideSequences optionality (Miga raised; adding context). The isManifest validator on the next PR (parseSlideshow.ts in #1585, lines 40-50) now strictly validates slideSequences as array | undefined — a slideSequences: null value will fail validation as slideshow_invalid. Worth noting in the docs: omit the key entirely, don't pass null.

  3. 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.error suppression 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 ships marker tied to a tracking issue.

  4. No mention of registry-item.json for the demo decks. The harness reference at standalone-harness.md:18-19 points readers to registry/examples/airbnb-deck/index.html and startup-pitch/index.html as "living reference implementations." Those examples in #1584 ship without registry-item.json (all other registry/examples/* have one — see decision-tree, play-mode, kinetic-type, nyt-graph). That means a reader can't npx 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.json to 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.

Branching strategy concern (cross-PR, non-blocking for docs content)

This PR stacks on ss-skillss-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 hotspots requires Array.isArray (not just truthy) and that bare null for 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

vanceingalls and others added 5 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>
@vanceingalls vanceingalls changed the base branch from graphite-base/1583 to ss-studio-b June 19, 2026 08:14
Base automatically changed from ss-studio-b to main June 19, 2026 08:34
@vanceingalls vanceingalls dismissed miguel-heygen’s stale review June 19, 2026 08:34

The base branch was changed.

@vanceingalls vanceingalls merged commit b7a1163 into main Jun 19, 2026
18 of 19 checks passed
@vanceingalls vanceingalls deleted the ss-skill branch June 19, 2026 08:35
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