---
name: demo-recording
description: >
  Use when the user wants to record a demo, make a video, create a walkthrough,
  record their screen, or capture a demo of a feature. Handles the full
  pre-production and recording workflow: writing the demo script, generating
  captions, verifying the setup, choreographing panels, and driving the
  recording. Hands off to adom-video-post for post-production (speedup, voiceover,
  publish). Trigger words: record a video, record a demo, make a movie, create
  a walkthrough, record my screen, demo video, screen recording, narrate a
  walkthrough, capture a demo, make a demo.
---

# Demo Recording — orchestration

Record polished demo videos of Adom features. This file is the
**orchestration entry point**. Detailed mechanics live in three
sub-docs you should follow when their phase becomes relevant:

| Sub-doc | When to load it |
|---|---|
| [recording-mechanics.md](recording-mechanics.md) | Before you touch `recording start` / `browser_record_start`. Covers Hydrogen vs pup, foreground-rebump, page-side repaint, and the **NEVER stitch screenshots** rule. |
| [script-and-tts.md](script-and-tts.md) | Before you record the first clip. Script template, narration phonetics, captions, panel choreography, voiceover draft. |
| [per-snippet-flow.md](per-snippet-flow.md) | While you record + after. Setup verification, the record loop, manifest add, **storyboard review (mandatory)**, concat, hero, wiki upload, workspace restore. |

For post-production after recording (speedup, voiceover, publish-to-wiki),
hand off to the `adom-video-post` skill.

## What "a real demo" means at Adom — non-negotiable shape

When the user says "make a real demo," "make me a demo," "publish a demo,"
"polished tour," or anything similar **that is NOT explicitly the quick path
in [per-snippet-flow.md](per-snippet-flow.md)** ("Quick recording" section),
every one of these steps has to ship — none of them is optional:

1. **Sectioned script in the repo** at `<repo>/demo/<name>-demo-script.md`
   with a row-per-scene table containing **caption + narration + target
   length + driver actions**. The narration column is phoneticized for TTS
   per [`tts-pronunciation`](../tts-pronunciation/SKILL.md). Detail in
   [script-and-tts.md](script-and-tts.md).
2. **One short clip per section, recorded separately** — never a single long
   take covering every feature. Per-clip lengths track narration ±15%.
3. **Per-clip TTS via `adom-tts say`** (uses Adom's pronunciation cache + house
   voice `en-US-AndrewMultilingualNeural`). Falls back to `edge-tts` only
   when adom-tts isn't installed.
4. **Per-clip mux** — TTS audio combined with the silent recording into one
   `<id>-narrated.webm` (`ffmpeg -i clip.webm -i tts.mp3 ... libopus`).
5. **`adom-video-post manifest add`** for every clip with a `--description` that
   states **intent** (not just feature name) — see adom-video-post skill rules.
6. **`adom-video-post storyboard <manifest>` review with the user** — MANDATORY
   gate before concat. Per-clip review catches "clip 5 measured the wrong
   thing" in seconds; reviewing a 3-min concat blind takes 20× as long.
   Skip this step ONLY if the user has explicitly told you they're
   unavailable (e.g. "I'm at the gym, just ship it") — and call it out
   prominently in the final report so they can re-review later.
7. **`ffmpeg -f concat -safe 0 -c copy` to the final** — `adom-video-post concat`
   currently drops audio (known bug, documented in adom-video-post skill).
8. **Hero image** — `ffmpeg -ss <t> -i final.webm -frames:v 1 hero.png`
   from a representative frame.
9. **Show the final concat in `adom-video` BEFORE wiki upload.** Drop the
   webm into `/tmp/videolib/`, run `adom-video play /tmp/videolib/<final>.webm`,
   then open the proxy URL `https://<container>.adom.cloud/proxy/<port>/?file=<final>.webm`
   as a pup tab. The status line of `adom-video play` prints the port (or
   `adom-video status` if the server is already up). adom-video is the
   canonical playback surface — frame-accurate scrub, A-B loop, pixel zoom
   to 8×, frame-extract to PNG, trim & export. **Never** `python3 -m
   http.server` + raw file URL; that loses scrub fidelity and you lose every
   adom-video CLI hook (`seek`, `extract-frame`, `trim`). Memory rule:
   `feedback_video_playback_uses_adom_video.md`.
10. **Upload BOTH to the wiki** — `adom-wiki asset upload <page>
    --asset-type video --file final.webm` AND `--asset-type hero-image
    --file hero.png`. Re-publish the page so `pub_version` bumps.
11. **Demo script published to the wiki too** so other Adom users can
    reproduce — `adom-wiki page edit` adds the script content under the
    "Demo" section of the page body.

If any of those steps gets dropped, the output is not a "real demo" — it's a
screen capture. Tell the user explicitly which steps you skipped and why.

🛑 **Step 9 is the most-frequently-missed step.** I produce a final webm,
report `final.webm at /tmp/...`, and stop — leaving the user to find it,
copy it locally, and open it in some random player. That's wrong. The user
should never have to leave their pup window to review a video. Default
behavior: when a final webm is produced, it gets shown in adom-video as
part of the same response.

## 🚨 Ten failure modes that wreck demos every session — read this BEFORE recording

Every demo session that goes wrong goes wrong in the same ten ways. If
you catch yourself in any of these patterns, **stop and re-read this
section.** The user has paid the cost of teaching this lesson many
times; they should not have to teach it again.

### 1. Skipping the snippet flow because you read the skill once and "got the gist"

Symptom: you record clips back-to-back, mux them, upload to wiki, ask
the user to review the final video.

Why it happens: scanning the skill once feels like understanding it.
The "🛑 MANDATORY storyboard review" gate looks like ceremony you can
skip if you're confident.

Why it is wrong: per-clip review catches "clip 5 measured the wrong
thing" in 30 seconds. Watching a 60–90 s mux to find the same problem
takes 20× longer AND the user has to re-watch every revision.

Fix: the storyboard gate is non-negotiable. If the user says "watch
later" once, that does NOT extend to follow-up shoots. Open
`adom-video-post storyboard <manifest>` after every recording pass. Detail
in [per-snippet-flow.md](per-snippet-flow.md) ("Step 5" section).

### 2. One-shot batch instead of iterative

Symptom: record-all → mux → upload → "is it good?" → "no, redo X" →
record-all → mux → upload → repeat.

Why it happens: feels like progress because each cycle ends with a
shippable artifact.

Why it is wrong: round-trip on one snippet ≈ 30 s. Round-trip on a
full mux + upload + watch ≈ 5 min. Three rounds of one-shot = 15
minutes of the user watching slop they didn't approve.

Fix: per-clip flow. Record → manifest add (with intent description) →
storyboard review → re-shoot only the bad clips → mux is the LAST
step, and it runs in the background while the user reviews snippets.

### 3. Lame shots because you ran out of stamina

Symptom: clips 1–3 are well-staged, clips 4–6 are programmatic
`/eval` shortcuts that "kind of show the feature."

Why it happens: by clip 5 you've burned an hour wrestling the capture
pipeline. The remaining clips become "good enough, ship."

Why it is wrong: this is the user-facing artifact. "Good enough"
reads as "this team didn't care."

Fix: every clip gets the same staging treatment. If you don't have
the bandwidth for six high-quality clips, drop to four. Don't ship
six mixed-quality clips and hope the average lands well.

### 4. Faking interactions you can't actually drive

Symptom: user asks for "click an edge in the 3D viewer." You can't
synthesize a real face-pick from `/eval` because Babylon's pick uses
live OS pointer state. Instead of saying so, you fake it with
`addMeasurePick({point: bbox.center})` and ship a clip that doesn't
show what was asked.

Why it happens: faking is faster than admitting a limitation.

Why it is wrong: the user can tell. Faked interactions look faked.
The original feature request goes un-tested.

Fix: when you hit a UI capability you can't reach via current APIs,
you have **two acceptable paths** — and faking is never an acceptable
third option:
- **(a) Tell the user explicitly:** "I can't synthesize a face-click
  from `/eval`. Want to do that part yourself in 5 seconds, or want
  me to add a CLI command for it?"
- **(b) Add the CLI command.** Pattern: `<tool> <verb> --face <mesh>
  --uv 0.5,0.5` synthesizes a real `PickingInfo` and routes through
  the same handlers the mouse pick fires. Now the demo CLI doubles
  as proof the feature works headlessly.

### 5. Punting on snippets that fail because the underlying feature is broken

Symptom: the Inspector clip won't record because something in the
inspector breaks `scene.activeCamera` access mid-clip. You drop the
clip from the demo and ship N-1 instead of N.

Why it happens: dropping the clip is one line. Investigating the bug
is unbounded.

Why it is wrong: **the demo IS a unit test of the user's product.**
Failing to record told you there's a real bug in feature X. Skipping
the clip means the demo is incomplete AND the bug stays. Both bad.

Fix: investigate the failure. Fix the underlying bug. Get the
snippet. The demo and the product co-evolve. If the bug is genuinely
beyond the demo session's budget, raise it explicitly: "Inspector is
hitting bug X. Options: 30 min to investigate, drop clip with a
follow-up tracking item, or you decide." Don't decide unilaterally.

### 6. Treating mux/concat as the finish line instead of as a slow background task

Symptom: snippets done → run `ffmpeg concat` (60–90 s of waiting) →
upload → ask user to review final.

Why it happens: ffmpeg's "the file is done" output feels like
completion.

Why it is wrong: ffmpeg concat doesn't need user attention. The user
reviewing snippets in storyboard doesn't need ffmpeg done. These
should run in parallel.

Fix: the moment the last snippet is recorded, kick off
`(ffmpeg concat ... &)` (background bash) AND
`adom-video-post storyboard <manifest>` (foreground for the user). By the
time the user clicks through snippets, the mux is either done OR
you've already killed it because they flagged a re-shoot. Detail in
[per-snippet-flow.md](per-snippet-flow.md) ("Background-mux while the user reviews" section).

### 7. Surprise-omitting parts of what was asked

Symptom: midway through recording you realize Inspector won't work or
Strobe is unstable. You silently drop those clips. The user finds out
from the final video.

Why it happens: surprise omissions feel less awkward than confessing
a gap mid-flow.

Why it is wrong: the user expects what they asked for. Silent omits
look like lying. They erode trust on every wrap-up message you write
afterward.

Fix: as soon as a snippet fails twice and you don't see the fix path,
message the user immediately: "Inspector clip is failing because of
X. Three options: I investigate (~30 min), I drop with a follow-up
tracking item, or you confirm we ship without it." Wait for the
answer. Don't decide unilaterally.

### 8. Failing to capture lessons in the skill so the next session repeats them

Symptom: same demo session, same ten failure modes, three different
conversations. The user is the only continuity.

Why it happens: it's "obvious" you'll remember next time. You won't —
your memory ends with the conversation.

Why it is wrong: skill files in gallia load on every Claude Code
session. Memory is local. Lessons that apply to "demos in general"
belong in the skill file, not in chat.

Fix: if the user gives you feedback that applies to FUTURE demo
sessions, the conversation isn't done until you've written the
lesson into the skill file and committed it to gallia.

### 9. Tolerating a slow capture mechanism instead of fixing the page

Symptom: you spend 60–90 minutes wrestling with paint-throttling,
screenshot polling, and frame deduplication. You eventually settle
for a 1–2 fps slideshow instead of fixing the page-side repaint bug.

Why it happens: the capture API is the visible surface; "the page
isn't repainting" is invisible.

Why it is wrong: documented in the "NEVER stitch screenshots" rule
in [recording-mechanics.md](recording-mechanics.md). The capture
mechanism (`browser_record_start`) is fine. The page-side fix
(continuous setInterval pump that mutates camera + `scene.render()`;
strip ArcRotateCamera input handlers) is the answer.

Fix: read the "NEVER stitch screenshots" rule before starting any
pup recording. Treat it as a precondition, not a fallback to
remember after the slideshow ships.

### 10. Inflating the wrap-up message past what was actually delivered

Symptom: "All 6 clips at 20-30 fps with real motion!" — when actually
clip 5 is barely above the SSIM threshold, clip 6 was deferred,
Inspector got skipped, and the measure picks are bbox-centroid fakes.

Why it happens: wrap-ups feel like victory laps. You skim past the
compromises.

Why it is wrong: the user can scrub through 70 seconds of video.
They WILL notice. "I claimed it was great" → they distrust every
wrap-up message you write afterward.

Fix: end-of-demo report is brutal-honest. Per-clip table with
quality grade per clip. Mark skipped clips, faked interactions,
lower-quality compromises, and SSIM headroom. End with "OK to ship?"
not "shipped, here's the URL."

## Step 1: Ask how the user wants to record

Present the user with a choice using AskUserQuestion:

> **How would you like to record?**
>
> 1. **Quick recording** — I'll start recording your screen with your mic on.
>    You narrate live, stop when done. Ready to upload immediately.
> 2. **Polished recording** — I'll write a sectioned script first, then record
>    each section as its own clip, generate TTS per section, and `adom-video-post`
>    concats them into one final video. This is the canonical path for
>    feature-tour demos.
> 3. **Just post-process** — I already have a recording. (→ hand off to
>    `adom-video-post` skill)

### Quick recording

If the user picks option 1, see
[per-snippet-flow.md](per-snippet-flow.md) ("Quick recording" section).

### Polished recording (the canonical path)

If the user picks option 2, follow this sequence:

1. **Read [script-and-tts.md](script-and-tts.md)** and write the demo
   script at `<repo>/demo/<name>-demo-script.md`. Show the script to
   the user for approval BEFORE recording.
2. **Read [recording-mechanics.md](recording-mechanics.md)** to pick
   your capture surface (Hydrogen by default; pup if Hydrogen is
   busy) and apply the page-side repaint guarantees.
3. **Read [per-snippet-flow.md](per-snippet-flow.md)** and execute
   Steps 3–8 (verify, record, manifest, storyboard review, concat,
   hero, publish, restore workspace).

Each sub-doc cross-links back here and to siblings — load the one
that matches the phase you're in, not all three at once.

## See also

- [recording-mechanics.md](recording-mechanics.md) — capture path, surfaces, frame-rate sanity, NEVER stitch screenshots
- [script-and-tts.md](script-and-tts.md) — script template, narration phonetics, captions
- [per-snippet-flow.md](per-snippet-flow.md) — verify, record, manifest, storyboard review, concat, hero, publish, workspace restore
- [tts-pronunciation](../tts-pronunciation/SKILL.md) — Adom-specific TTS phonetics
- `adom-video-post` skill (system-installed) — speedup, voiceover, publish-to-wiki post-recording

## Recorded-tab verification BEFORE recording starts

Always `pup browser_screenshot` the target tab and visually confirm it before calling `recording start`. The `active-tab` selector can silently miss (off-screen tab, multi-window setup, recently-closed tab repurposed). A bad active-tab selection can cost an entire 30+ scene demo to re-record.

## Board fills the recording frame

For a PCB-centric demo, never zoom so far that the board looks tiny in the frame. For RP2040-class boards, set radius 1.2–1.5 after `view isometric`. Screenshot the framing before `recording start` and check visually — text-only "view is set" is insufficient proof.

## Demo recording fixes (hard-won)

These are the specific framings and setup tweaks that the user has confirmed across multiple demos:

- **Maximize the Fusion window** before any Fusion-side recording — partial windows look amateurish
- **Use `browser_focus_window` for pup**, NOT `desktop_bring_to_front`. The latter is jarring and disrupts the user; the former just brings the pup window forward without animating
- **Revoke approvals in ALL demo setups** — old "always allow" approvals get bypassed during the recording and the demo looks like the user already trusted everything (which the demo is supposed to be showing as a fresh setup)
- **Never run demos as background bash scripts** — they need foreground control so the recorder can pause/resume cleanly

## Use adom-video for video playback

For "show / play / view / watch / scrub a video" requests, drop the file in `/tmp/videolib/` and open the adom-video viewer at the appropriate port-mapped URL. Gives frame-accurate scrub + A-B loop + pixel zoom. Never serve raw `.webm` over a one-off `python -m http.server` + raw file URL — the viewer features are worth the setup.
