{
  "schema_version": 1,
  "type": "skill",
  "slug": "demo-recording",
  "title": "demo recording",
  "brief": "Pre-production and recording workflow for demo videos: writing the script, generating captions, verifying setup, choreographing panels, and driving the recording. Hands off to video-post for post-prod",
  "version": "1.0.0",
  "tags": [],
  "license": "MIT",
  "discovery_triggers": [
    "make a demo",
    "record a demo",
    "make me a demo",
    "create a walkthrough",
    "record a video of",
    "record my screen",
    "screen recording",
    "demo video",
    "narrate a walkthrough",
    "capture a demo",
    "make a movie"
  ],
  "discovery_pitch": "demo-recording drives the full pre-production + recording flow for Adom demos: scene-by-scene script (with TTS-safe narration), per-section recording, storyboard review, concat, and wiki publish. Hand-off partners: video-post for post-production, tts-pronunciation for narration phonetics.",
  "sample_prompts": [
    {
      "label": "Record a demo",
      "prompt": "Record a demo of the chipfit visual lint flow"
    },
    {
      "label": "Walkthrough video",
      "prompt": "Make a 90s walkthrough video for adom-tsci"
    },
    {
      "label": "Demo script",
      "prompt": "Write the demo script for adom-rc and record it"
    },
    {
      "label": "Voiceover demo",
      "prompt": "Record a demo with Andrew Neural voiceover"
    },
    {
      "label": "Capture feature",
      "prompt": "Capture a demo of the new measure tool"
    }
  ],
  "source_path": "SKILL.md",
  "readme": "---\nname: demo-recording\ndescription: >\n  Use when the user wants to record a demo, make a video, create a walkthrough,\n  record their screen, or capture a demo of a feature. Handles the full\n  pre-production and recording workflow: writing the demo script, generating\n  captions, verifying the setup, choreographing panels, and driving the\n  recording. Hands off to video-post for post-production (speedup, voiceover,\n  publish). Trigger words: record a video, record a demo, make a movie, create\n  a walkthrough, record my screen, demo video, screen recording, narrate a\n  walkthrough, capture a demo, make a demo.\n---\n\n# Demo Recording\n\nRecord polished demo videos of Adom features. This skill handles everything\nbefore and during the recording. For post-production (speedup, voiceover,\npublish to wiki), hand off to the `video-post` skill.\n\n## What \"a real demo\" means at Adom — non-negotiable shape\n\nWhen the user says \"make a real demo,\" \"make me a demo,\" \"publish a demo,\"\n\"polished tour,\" or anything similar **that is NOT explicitly the quick path\nin Step 1**, every one of these steps has to ship — none of them is optional:\n\n1. **Sectioned script in the repo** at `<repo>/demo/<name>-demo-script.md`\n   with a row-per-scene table containing **caption + narration + target\n   length + driver actions**. The narration column is phoneticized for TTS\n   per [`tts-pronunciation`](../tts-pronunciation/SKILL.md).\n2. **One short clip per section, recorded separately** — never a single long\n   take covering every feature. Per-clip lengths track narration ±15%.\n3. **Per-clip TTS via `adom-tts say`** (uses Adom's pronunciation cache + house\n   voice `en-US-AndrewMultilingualNeural`). Falls back to `edge-tts` only\n   when adom-tts isn't installed.\n4. **Per-clip mux** — TTS audio combined with the silent recording into one\n   `<id>-narrated.webm` (`ffmpeg -i clip.webm -i tts.mp3 ... libopus`).\n5. **`video-post manifest add`** for every clip with a `--description` that\n   states **intent** (not just feature name) — see video-post skill rules.\n6. **`video-post storyboard <manifest>` review with the user** — MANDATORY\n   gate before concat. Per-clip review catches \"clip 5 measured the wrong\n   thing\" in seconds; reviewing a 3-min concat blind takes 20× as long.\n   Skip this step ONLY if the user has explicitly told you they're\n   unavailable (e.g. \"I'm at the gym, just ship it\") — and call it out\n   prominently in the final report so they can re-review later.\n7. **`ffmpeg -f concat -safe 0 -c copy` to the final** — `video-post concat`\n   currently drops audio (known bug, documented in video-post skill).\n8. **Hero image** — `ffmpeg -ss <t> -i final.webm -frames:v 1 hero.png`\n   from a representative frame.\n9. **Upload BOTH to the wiki** — `adom-wiki asset upload <page>\n   --asset-type video --file final.webm` AND `--asset-type hero-image\n   --file hero.png`. Re-publish the page so `pub_version` bumps.\n10. **Demo script published to the wiki too** so other Adom users can\n    reproduce — `adom-wiki page edit` adds the script content under the\n    \"Demo\" section of the page body.\n\nIf any of those steps gets dropped, the output is not a \"real demo\" — it's a\nscreen capture. Tell the user explicitly which steps you skipped and why.\n\n## 🚨 Wiki webm: keyframes every 1 s — NEVER upload raw recordings\n\n**Every webm uploaded to the wiki must be re-encoded with a keyframe\ninterval of ~1 s (`-g 30 -keyint_min 30` for 30 fps).** Without that,\nthe wiki server's video element can't seek — the user can play the\nclip start-to-end but can't scrub the timeline, jump to a section, or\nshare a deep-linked timestamp. `<video>` seeks land on the nearest\nkeyframe; without dense keyframes, the entire clip is one giant\nseekable unit.\n\nRaw `adom-cli hydrogen recording stop` output and raw CDP-frames mux\noutput **default to one keyframe at frame 0 only** — every subsequent\nframe is a P-frame, no scrub possible. This bites every time someone\nuploads a single-shot recording straight to `adom-wiki asset upload`\nwithout going through the video-post pipeline. (Bit me on\n`molecules/brianna-led-nameplate`'s `brianna-demo.mp4`; bit Kyle the\nsame day.)\n\n**The rule:** the LAST ffmpeg pass on any clip destined for the wiki\nmust include `-g 30 -keyint_min 30`. For 60 fps recordings use\n`-g 60 -keyint_min 60`. The exact incantation depends on codec:\n\n| Codec | Keyframe flags |\n|---|---|\n| `libvpx-vp9` (webm) | `-g 30 -keyint_min 30` |\n| `libx264` (mp4) | `-g 30 -keyint_min 30 -force_key_frames \"expr:gte(t,n_forced*1)\"` |\n| `libvpx` (legacy webm) | `-g 30 -keyint_min 30` |\n\nThe `-force_key_frames` expression on x264 is belt-and-suspenders —\nsome x264 builds honor `-g` as a soft hint and let scene-detection\noverride it; the expression forces a keyframe at integer-second\nboundaries regardless.\n\n**Verify after encode:**\n\n```bash\nffprobe -v error -select_streams v:0 -show_entries packet=pts_time,flags \\\n  -of csv=p=0 final.webm | grep ',K' | head -10\n# Should show keyframes (\",K_\") at ~0.0s, ~1.0s, ~2.0s, ...\n# If you only see one \",K_\" line at 0.0s, the encode dropped the rule.\n```\n\nIf you find yourself running `adom-wiki asset upload --asset-type\nvideo --file <raw>.webm` without a re-encode pass first, **stop**.\nPipe through `ffmpeg -i raw.webm -c:v libvpx-vp9 -b:v 2M -g 30\n-keyint_min 30 -c:a copy out.webm` first, even on a single-shot\nrecording you weren't going to edit. The re-encode adds ~10 s for a\n30 s clip and saves the user from a non-scrubbable wiki video.\n\nThe video-post `concat` path SHOULD apply this on the final mux — if\nyour demo went snippet → manifest → concat, you're covered. The\nsingle-shot path (just `recording stop` → upload) is where this gets\nsilently skipped, so the rule above applies *especially* there.\n\n## Recording surface — Hydrogen by default, pup when Hydrogen is busy\n\nThe default recording surface is `adom-cli hydrogen recording start` — the\nHydrogen webview captures the full editor (panels + workspace) into a clean\nwebm, with `caption show` integrated.\n\n**When Hydrogen is unavailable** — another Adom tool is occupying the webview\npanels (e.g. `aci` running a build, `adom-tsci` showing a 3D preview, an\nexisting demo storyboard up for review) — switch to **pup** (a separate\nChrome window via Puppeteer). Pup recordings happen on the user's Windows\ndesktop, fully orthogonal to anything happening inside Hydrogen, so the two\nsurfaces never compete.\n\n| Surface | When | Capture mechanism | Caption tooling | Notes |\n|---|---|---|---|---|\n| **Hydrogen** | Default. Demoing a Hydrogen-hosted feature. | `adom-cli hydrogen recording start --reason ...` → MediaRecorder on the workspace iframe. | `adom-cli hydrogen caption show \"<text>\"` overlays cleanly. | Best resolution + cleanest output. Don't use if another tab in the same workspace is occupying webviews you'd want to recapture later. |\n| **pup** | \"Aci is using webviews\", \"don't touch my Hydrogen layout\", \"the tool I'm demoing has its own UI in a Chrome tab anyway.\" | `adom-desktop browser_open_window` + `browser_focus_window` + `browser_record_start` (CDP screencast on the pup tab). | No `caption show` — render the caption inside the page itself with a temporary `<div>` injected via `browser_eval`, OR rely entirely on TTS narration. | Frame rate is paint-throttled by Chrome unless the window is OS-foregrounded — see \"🔥 The pup foreground-rebump trick\" below. Tar pulls of large recordings may exceed the 60-second WebSocket timeout — pull frames in batches of ~40 if `pull_file` returns null. |\n\n### 🔥 The pup foreground-rebump trick — required for high frame rate\n\nChrome **paint-throttles** any tab whose window is not the OS foreground. CDP `Page.startScreencast` only emits a frame when the page repaints, so a backgrounded pup tab silently drops to ~5 fps even when you asked for 15 fps. Symptoms: 275 captured frames in 46 s of wall-clock = ~6 fps real, video plays back ~3× faster than reality, narration desyncs from action.\n\n**The trick:** `browser_focus_window` calls Win32 `SetForegroundWindow` to raise the pup window to the OS foreground. Calling it ONCE before `browser_record_start` is not enough — anything that grabs OS focus during the recording (Hydrogen tab switch, a notification, the user clicking elsewhere) re-throttles the pup tab. The fix is to **re-bump foreground before every action**, not just at the start.\n\n```bash\nSESS=\"demo-snippet\"\n\n# Open + initial focus\nadom-desktop browser_open_window \"$(jq -nc --arg s \"$SESS\" --arg u \"$URL\" \\\n  '{sessionId:$s, profile:$s, url:$u}')\"\nsleep 4\nadom-desktop browser_focus_window \"$(jq -nc --arg s \"$SESS\" '{sessionId:$s}')\"\nsleep 1\n\n# Re-bump RIGHT BEFORE record_start — this is the critical one\nadom-desktop browser_focus_window \"$(jq -nc --arg s \"$SESS\" '{sessionId:$s}')\"\nRID=$(adom-desktop browser_record_start \"$(jq -nc --arg s \"$SESS\" \\\n  '{sessionId:$s, fps:15, quality:75}')\" | jq -r '.recordingId')\n\n# Wrap every action so foreground gets re-bumped before each one\nfocused_eval() {\n  adom-desktop browser_focus_window \"$(jq -nc --arg s \"$SESS\" '{sessionId:$s}')\" >/dev/null\n  adom-desktop browser_eval \"$(jq -nc --arg s \"$SESS\" --arg e \"$1\" \\\n    '{sessionId:$s, expr:$e}')\" >/dev/null\n}\n\nfocused_eval 'document.getElementById(\"btn-foo\").click()'\nsleep 3                        # short sleeps are fine — focus persists across them\nfocused_eval 'document.getElementById(\"btn-bar\").click()'\nsleep 3\nadom-desktop browser_record_stop \"$(jq -nc --arg s \"$SESS\" --arg r \"$RID\" \\\n  '{sessionId:$s, recordingId:$r}')\"\n```\n\n**Verification — sniff-test the captured frame count vs wall-clock duration:**\n\n```bash\n# After every recording, check this BEFORE you commit to mux + TTS:\nSTOP=$(adom-desktop browser_record_stop ...)\nFRAMES=$(echo \"$STOP\" | jq -r '.frameCount')\nDUR_MS=$(echo \"$STOP\" | jq -r '.durationMs')\nACTUAL_FPS=$(python3 -c \"print(${FRAMES}/(${DUR_MS}/1000))\")\necho \"captured ${ACTUAL_FPS} fps (asked for 15)\"\n# Below 12 fps → the window dropped foreground at some point. Re-record with the rebump pattern, don't try to mask with playback speedup.\n```\n\n**Don't substitute `browser_alert_window`** — it flashes the taskbar but explicitly does NOT steal foreground (so Chrome stays paint-throttled). Don't substitute `desktop_bring_to_front` either — it works but per the project memory (\"use browser_focus_window for pup, not desktop_bring_to_front\"), the browser-typed call is the canonical one.\n\n**Side-effect to know about:** because `SetForegroundWindow` is OS-level, the user briefly sees the pup window raise to front during recording. Tell them in advance (\"you'll see a Chrome window pop forward for ~30 s\") so they don't think your code is fighting their workspace.\n\n### Foreground alone isn't enough — also keep something painting\n\nForeground-rebump fixes Chrome's window-level paint throttle, but there's a **second** throttle: CDP `Page.startScreencast` is **event-driven**. It only emits a frame when the page actually repaints. A static, idle page (e.g. \"user looking at the player view, video paused, no animation\") produces zero frames per second even if the window is fully foregrounded.\n\nSymptom: 7 seconds of \"let the player view sit\" captures 1 frame. The whole demo plays back too fast.\n\nTwo fixes, pick one:\n\n1. **Keep a small animation always running on the page.** A 1-second CSS pulse on a non-distracting element does it — e.g. on the `.hud .dot`:\n\n   ```css\n   .hud .dot { animation: hud-pulse 1s infinite ease-in-out; }\n   @keyframes hud-pulse { 0%,100%{opacity:.6} 50%{opacity:1} }\n   ```\n\n   This keeps the compositor running, which keeps frames flowing through CDP. Cost: a barely-visible animated dot. Benefit: full requested frame rate during static sections.\n\n2. **Force micro-motion via `browser_eval`** between actions when you can't add CSS to the target page. Inject a 1-pixel scroll back-and-forth every 200 ms, or update a CSS variable that affects a transform:\n\n   ```bash\n   # Background loop nudging the page during the recording\n   (\n     while [ -f /tmp/avdemo/.recording ]; do\n       focused_eval 'document.documentElement.style.setProperty(\"--nudge\", Math.random())'\n       sleep 0.2\n     done\n   ) &\n   ```\n\n**Sniff-test** with the same `actual_fps` formula above: if it's still <12 after the foreground rebump, the issue is paint-throttling on a static page, not OS focus.\n\n### Before you re-record — try Hydrogen instead\n\nIf you've fought pup paint-throttling for two attempts and the demo still isn't smooth, **switch to Hydrogen recording** (`adom-cli hydrogen recording start`). Hydrogen captures the workspace via `getDisplayMedia` + `MediaRecorder`, which polls the framebuffer at the requested rate regardless of whether the page is repainting. Cost: you need the Hydrogen workspace free of competing tabs. Benefit: full 30 fps with no rebump tricks.\n\nPup is the right answer when Hydrogen is genuinely occupied. When Hydrogen is free, prefer it.\n\n**Pup-recording per-section pattern** (substitute for the Hydrogen recording\nblock in Step 5 below when the user's Hydrogen is busy):\n\n```bash\nURL=\"https://<container>.adom.cloud/proxy/$PORT/\"   # the proxied URL of the tool being demoed\n\n# Per section:\nSESS=\"demo-$ID\"\nadom-desktop browser_open_window \"$(jq -nc --arg s \"$SESS\" --arg u \"$URL\" \\\n  '{sessionId:$s, profile:$s, url:$u}')\"\nsleep 4\nadom-desktop browser_focus_window \"$(jq -nc --arg s \"$SESS\" '{sessionId:$s}')\"\nsleep 1\n\nRID=$(adom-desktop browser_record_start \"$(jq -nc --arg s \"$SESS\" \\\n  '{sessionId:$s, fps:15, quality:75}')\" | jq -r '.recordingId')\n\n# ... drive the section via browser_eval calls ...\nadom-desktop browser_eval \"$(jq -nc --arg s \"$SESS\" --arg e \"<JS>\" \\\n  '{sessionId:$s, expr:$e}')\"\n\nSTOP=$(adom-desktop browser_record_stop \"$(jq -nc --arg s \"$SESS\" --arg r \"$RID\" \\\n  '{sessionId:$s, recordingId:$r}')\")\nTAR=$(echo \"$STOP\" | jq -r '.tarPath')\n\nadom-desktop pull_file \"$(jq -nc --arg t \"$TAR\" '{filePaths:[$t], saveTo:\"/tmp/demo/tar\"}')\"\nadom-desktop browser_close_window \"$(jq -nc --arg s \"$SESS\" '{sessionId:$s}')\"\n\n# Mux the CDP frames → webm\nEXTRACT=/tmp/demo/tar/${ID}-extract && mkdir -p \"$EXTRACT\"\ntar -xf \"/tmp/demo/tar/$(basename \"$TAR\")\" -C \"$EXTRACT\"\nFRAME_DIR=$(find \"$EXTRACT\" -name ffmpeg-concat.txt -printf '%h\\n' | head -1)\n( cd \"$FRAME_DIR\" && ffmpeg -y -loglevel error -f concat -safe 0 \\\n    -i ffmpeg-concat.txt -c:v libvpx-vp9 -b:v 2M -deadline realtime \\\n    -cpu-used 8 -row-mt 1 -g 30 -keyint_min 30 \\\n    -an \"/tmp/demo/raw/${ID}.webm\" )\n\n# Then standard TTS + mux + manifest add (same as Hydrogen path)\nadom-tts say \"<narration>\" --out /tmp/demo/tts/${ID}.mp3 --voice en-US-AndrewMultilingualNeural\nffmpeg -y -loglevel error -i /tmp/demo/raw/${ID}.webm -i /tmp/demo/tts/${ID}.mp3 \\\n  -filter_complex \"[0:v]tpad=stop_mode=clone:stop_duration=10[v]\" \\\n  -map \"[v]\" -map 1:a -c:v libvpx-vp9 -b:v 2M -deadline realtime -cpu-used 8 \\\n  -g 30 -keyint_min 30 \\\n  -c:a libopus -b:a 96k -shortest /tmp/demo/narrated/${ID}.webm\nvideo-post manifest add /tmp/demo/manifest.json --id \"$ID\" --title \"...\" \\\n  --description \"...\" --raw /tmp/demo/narrated/${ID}.webm /tmp/demo/narrated/${ID}.webm\n```\n\nThe `tpad=stop_mode=clone:stop_duration=10` filter holds the last frame so\nshort recordings don't cut the narration off — `-shortest` then trims the\noutput to whichever stream ends first (almost always the audio).\n\n## Step 1: Ask how they want to record\n\nPresent the user with a choice using AskUserQuestion:\n\n> **How would you like to record?**\n>\n> 1. **Quick recording** — I'll start recording your screen with your mic on.\n>    You narrate live, stop when done. Ready to upload immediately.\n> 2. **Polished recording** — I'll write a sectioned script first, then record\n>    each section as its own clip, generate TTS per section, and video-post\n>    concats them into one final video. This is the canonical path for\n>    feature-tour demos.\n> 3. **Just post-process** — I already have a recording. (→ hand off to\n>    video-post skill)\n\n### Quick recording\n\n```bash\nadom-cli hydrogen recording start --reason \"Descriptive reason here\"\n# User narrates and demonstrates live\nadom-cli hydrogen recording stop   # saves to ~/project/recordings/\n```\n\nDone. Upload to wiki via `adom-wiki asset upload`.\n\n### 🎬 Polished recording — record sections INDIVIDUALLY (non-negotiable)\n\n**The rule:** a polished demo is a sequence of **separately-recorded\nclips**, one per section / feature, each with its own TTS narration.\n`video-post concat` stitches them together at the end. Do NOT record\none long take covering every feature. Reasons:\n\n- A single long recording turns every mistake into a re-shoot of\n  everything. One-take demos always have at least one mumbled line\n  or a mis-click and then you re-record 4 minutes to fix a 5-second\n  flub.\n- Per-clip TTS means Andrew-neural (or whatever voice) narrates each\n  section cleanly. A single long TTS over a mixed recording loses\n  the intentional pauses between features.\n- Hero image selection is trivial when each section is its own file —\n  just pick the best frame from clip #1 or #2.\n- Re-shoots are surgical: if the Measure tool scene is bad, re-record\n  `measure.webm`, update the manifest entry, `video-post concat` again.\n  The other 12 clips are untouched.\n\n**The flow (detailed in Steps 2-8 below):**\n\n1. Write a **sectioned** script (one section per feature / example).\n2. Set up a manifest: `video-post manifest init --output /tmp/<name>.json`.\n3. For each section:\n   - Display a caption in the workspace (`adom-cli hydrogen caption show \"<section title>\"`).\n   - Start a recording (`adom-cli hydrogen recording start --reason \"<section name>\"`).\n   - Drive the UI silently for that section's beats.\n   - Stop the recording.\n   - Generate TTS: `adom-tts say \"<narration for this section>\" --out /tmp/clips/<section>.mp3` (shares the Adom pronunciation-override table + source-hash cache — see [adom-tts skill](../adom-tts/SKILL.md)). Falls back to `edge-tts --voice en-US-AndrewNeural --text \"...\" --write-media ...` only if adom-tts isn't installed.\n   - Mux TTS onto the clip with ffmpeg (audio+video → one .webm).\n   - Register: `video-post manifest add --id <id> --title \"<title>\" --description \"<full INTENT for this clip>\" <clip.webm>`.\n4. **🛑 MANDATORY: open `video-post storyboard <manifest>` and tell the\n   user to review each clip one-by-one.** Wait for their feedback. Fix\n   each bad clip, re-run storyboard. Do NOT skip to concat / upload.\n5. Concat the reviewed manifest into one video (use ffmpeg concat\n   demuxer — `video-post concat` currently drops audio; known bug).\n6. Verify audio is present (`ffprobe | grep codec_type` should list\n   both video and audio).\n7. Extract a hero image (`ffmpeg -ss <t> -i final.webm -frames:v 1 hero.png`).\n8. Publish: `adom-wiki asset upload <page> --asset-type video --file final.webm` and another upload for the hero image.\n\nFollow Steps 2-8 below for the detail on each phase.\n\n### Why the storyboard gate exists (read this before skipping it)\n\nAI-made demos fail in specific, localized ways: \"clip 5 measured the\nwrong pads,\" \"clip 8 never clicked anything,\" \"clip 11 walkthrough\ndidn't advance past step 1.\" The human reviewer catches these\ninstantly **when clips are numbered + described + individually\nplayable**. On a 3–5 minute uninterrupted video they can't — they'd\nhave to scrub to the minute:second timestamp and guess which segment\nyou meant. Skipping storyboard forces the user to do something that's\ngenuinely hard, and turns the feedback loop from \"clip 5 is broken\"\n(precise) into \"something's wrong somewhere\" (useless).\n\nEvery `manifest add` carries a `--description` so the user can read\nyour INTENT per clip before hitting play. If your intent is wrong\n(e.g. you thought \"measure tool = some clicks\" instead of \"measure\ntool = commit three pad-to-pad distances showing HUD readouts\"), they\ncan catch the intent bug without even watching the clip.\n\n## Step 2: Write the demo script — self-contained, reproducible, checked into the repo\n\nThe demo script is a **first-class artifact** of the repo, not scratch work. It must be checked into the project git repo at `<repo>/demo/<name>-demo-script.md` (not just `/tmp/`) and — at the end of Step 7 — published to the wiki so other Adom users can run, review, or tweak the demo without digging through chat history. Treat it the way you'd treat a `Makefile`: if another person (or another Claude) can't reproduce the demo from this file alone, the file is incomplete.\n\n> **Repo location.** `<repo>/demo/<name>-demo-script.md` is the canonical home. Put companion files (record.py, mux.py, generated manifest, any custom TTS overrides) in the same `demo/` directory. `/tmp/` paths are fine *during execution* but the source of truth lives in the repo so git history tracks every demo revision.\n\n### The script MUST be self-sufficient — every piece, one file\n\nThe script is a runnable spec. A fresh Claude thread (or a teammate) reading it should be able to re-render the entire demo without asking any questions. That means every setting that affects the output goes in the file, not in the assistant's head:\n\n- **Metadata header** — version being demoed, target duration, recording date, hero frame timestamp, final output path\n- **Tooling deps** — `edge-tts` / `adom-cli` / `video-post` / `ffmpeg` versions / required env vars\n- **Voice + rate** — `en-US-AndrewMultilingualNeural` / `+5%` / any per-scene overrides\n- **Workspace setup** — canonical split ratio (default 0.30 AI / 0.70 feature), which panels, which apps pre-opened\n- **Scene list** — numbered, with captions, interactions, commands, panel choreography, speedup markers, **and narration text** (see table below)\n- **Manifest path** — canonical `/tmp/<name>-manifest.json` location + `<repo>/demo/manifest.json` checked-in copy\n- **Reproduction block** — the exact commands another user runs to regenerate the demo end-to-end (see template below)\n- **Wiki page target** — where this demo publishes (`apps/<page>`)\n\nOmitting any of the above means the next person who tries to regenerate the demo has to reverse-engineer your choices from the muxed output. Don't do that to them.\n\n### Template — start every demo script from this shape\n\n```markdown\n# <Product Name> <version> — Feature Tour\n\n**Target duration:** 4:30–5:00\n**Recorded:** 2026-04-23\n**Final output:** `~/project/recordings/<name>-<version>-tour.webm`\n**Wiki target:** `apps/<page>`\n**Hero frame:** `00:12` of scene 1 (BME680 first iso shot)\n\n## Dependencies\n\n- `edge-tts` — `pip install edge-tts` (tested with 6.1.x)\n- `adom-cli` — container-provided\n- `video-post` — built from `/home/adom/project/video-post/` (`cargo build --release`)\n- `ffmpeg 6+` — container-provided\n- Env vars: none required beyond the container defaults\n\n## Recording config\n\n- **Voice:** `en-US-AndrewMultilingualNeural`\n- **Rate:** `+5%`\n- **Workspace split:** 0.30 (VS Code / AI left, feature pane right)\n- **Captions:** on, via `adom-cli hydrogen caption show`\n- **Resolution:** native (1280×…)\n\n## Scenes\n\n| # | id | Caption (on-screen) | Narration (TTS, phonetics applied) | Target length | Commands + choreography |\n|---|---|---|---|---|---|\n| 1 | intro | `adom-tsci 1.3.7` | `This is adom t s c i version one point three point seven — the interactive t s circuit preview.` | 8s | `adom-tsci view isometric --port 8853` — cinematic slow orbit |\n| 2 | examples | `8 example molecules` | `Adom ships eight example molecules — let's open a BME680 breakout.` | 10s | switch to example folder, `adom-tsci start <dir>` |\n| … | … | … | … | … | … |\n\n(13 scenes for the adom-tsci 1.3.7 tour; ~5:00 total)\n\n## Reproduction\n\nAnother user regenerates this demo end-to-end with:\n\n\\`\\`\\`bash\ncd <repo>\npython3 demo/gen_tts.py           # renders TTS from this script's narration column\npython3 demo/record.py            # drives scene-by-scene recording\npython3 demo/mux.py               # muxes TTS onto clips, concatenates final\nvideo-post storyboard /tmp/<name>-manifest.json   # review\n# after review:\nvideo-post concat /tmp/<name>-manifest.json --kind raw --output ~/project/recordings/<name>-<version>-tour.webm\nadom-wiki asset upload apps/<page> --asset-type video --file ~/project/recordings/<name>-<version>-tour.webm\n\\`\\`\\`\n\n## Known issues from this rev\n\n- Clip 02 originally recorded as one 2:17 mega-clip; split into 8 × 12s clips in revision 2 for surgical re-shoot support.\n- TTS in rev 1 mispronounced `adom-tsci` as one word; fixed in rev 2 by spelling as `adom t s c i` in narration column.\n```\n\n### Why the repo + wiki double-home\n\n- **In the repo:** git history tracks every demo revision. If someone files an issue \"the demo is out of date after v1.4.0 ships\" you can diff the script.\n- **On the wiki:** other Adom users find it when browsing `apps/<page>`. They can click \"reproduce\" and a Claude thread says \"I see a demo-script for this app — want me to re-record against the current version?\"\n- **Both:** git is the source of truth (atomic with code changes); wiki is the discoverable surface.\n\n### Copy this script file during recording, don't write straight to `/tmp/`\n\n```bash\nmkdir -p <repo>/demo\ncp /tmp/<name>-demo-script.md <repo>/demo/<name>-demo-script.md    # if you drafted in /tmp\n# ...or write directly to the repo path in the first place (preferred).\n```\n\nBefore touching the record button, show the script to the user for approval. Save to `<repo>/demo/<name>-demo-script.md`.\n\n### 🗣️ TTS pronunciation — consult the `tts-pronunciation` skill\n\nNeural TTS engines mispronounce many Adom product names, acronyms, and hyphenated terms. The canonical table lives in the [`tts-pronunciation` skill](../tts-pronunciation/SKILL.md) (data file: [`pronunciations.json`](../tts-pronunciation/pronunciations.json)). For every narration scene, look up each proper-noun / acronym / version-number in that table and substitute its `narration` spelling into the narration column. Captions and on-screen text keep the real spelling; only the narration-column text gets phoneticized.\n\nMost common fixes (the full table is in the tts-pronunciation skill):\n\n- `adom-tsci` → `adom t s c i`\n- `JLCPCB` / `FPGA` / `PCB` / `SPI` → spell letters with spaces\n- `1.3.7` → `one point three point seven`\n- `KiCad` → `kai cad`\n- `BOM` → `bill of materials` (TTS engines read \"BOM\" as the word \"bomb\")\n\n**If a term you need isn't in the table, add it.** The contribution flow is in `tts-pronunciation/SKILL.md` — verify the mispronunciation with a one-shot `edge-tts`, find a spelling that fixes it, append to `pronunciations.json`, commit. Every user benefits.\n\n**Verify before rendering the full tour:**\n\n```bash\nedge-tts --voice en-US-AndrewMultilingualNeural \\\n  --text \"adom t s c i version one point three point seven\" \\\n  --write-media /tmp/_test.mp3\n# Listen with ffplay /tmp/_test.mp3 — it should spell out T S C I, not say \"sci\" as a word.\n```\n\nFixing narrations at script time costs seconds. Fixing them after you've muxed 13 clips means re-running TTS, re-muxing, re-concat, re-storyboarding.\n\n### 📏 Clip duration ≈ narration duration, ±15%\n\nDon't let a clip's video length drift too far from its narration length. A 2:17 video with 0:30 of narration is broken either way:\n\n- **Either** the narration is too thin — tighten the video (\"cycle through 8 example molecules\" is one scene if narrated in 30s, but only if you can render all 8 in 30s).\n- **Or** the video is genuinely 2:17 — then split it into multiple clips, each with its own matched-length narration.\n\n**Split-before-speedup heuristic.** When a single clip concept spans distinct sub-actions (open / click / close, or cycle-through-N-things), the correct path is N clips with N narrations, not one mega-clip with a speedup pass. Reasons:\n\n- Surgical re-shoots — if one of 8 examples fails to render, a single split clip rerecord fixes it. A mega-clip has to be fully rerecorded.\n- Pacing control — app-side lag (e.g. loading a new example molecule) bakes into a mega-clip. Smaller clips let you cut-on-ready.\n- Narration clarity — \"now we see the BME680... and the BHI360... and the SN65HVD230...\" is unnatural. \"This is the BME680.\" [clip break] \"The BHI360.\" is tight.\n\n\nThe script MUST include:\n\n### Scene list\n\nNumbered scenes with:\n- What's on screen\n- Commands to run\n- Expected duration (real time + after speedup)\n\n### 🎙️ Narration text for every scene — the TTS that will be rendered\n\n**The full spoken narration for every scene goes in the demo script, visible to the user for approval BEFORE any recording or TTS rendering happens.** This is the single most important line-item in the script. Everything else (captions, interactions, commands) is cheaper to change than narration once rendered — if you skip the script-time review and discover the wrong pronunciation / wrong wording / wrong length at mux time, you're re-rendering N clips to fix it.\n\nThe script table should have a dedicated **Narration** column next to each scene:\n\n| Scene | Caption (seen) | Narration (heard) — TTS-safe phonetics applied | Target length |\n|---|---|---|---|\n| 1 | `adom-tsci 1.3.7` | `This is adom t s c i version one point three point seven — the interactive t s circuit preview.` | 8s |\n| 2 | `BME680 breakout` | `Let's open a BME680 breakout molecule. Four in one sensor: temperature, humidity, pressure, gas.` | 10s |\n| … | … | … | … |\n\n**Rules for the narration column:**\n\n1. Apply the **TTS pronunciation** table (above) — the user approves the phonetic spelling, so what they read in the script is exactly what the TTS will say. `adom-tsci` never appears literally in narration column; it's always `adom t s c i`.\n2. **Target length per scene** must be consistent with the planned video length (±15%, per the section below). If a scene is planned for 15 s of video, narration is ~80–120 words. Count words as a sanity check; at 160 wpm spoken pace that's ~13–17 s.\n3. User-approved narration text is the **only** source of truth for `edge-tts --text \"…\"` later. No paraphrasing at record time. If you need to change narration during recording, stop and update the script (and re-get approval).\n4. **Narration ≠ caption.** Caption is short, on-screen, SEO-ish (\"BME680 breakout\"). Narration is conversational, spoken (\"Let's look at the BME680 breakout molecule…\"). Both are required. Don't reuse one for the other.\n\n**Why it lives in Step 2 (approved) not Step 4 (rendered):** Fixing a typo or a pronunciation issue in the approved script costs seconds. Finding it after mux costs 13 × (TTS render + ffmpeg mux + concat) minutes + another storyboard-review round. The script-approval step is where all cheap-to-fix-now mistakes get caught.\n\n### Captions for every scene\n\n**Always generate a caption for every scene.** Captions are rendered live in\nthe workspace during recording via `adom-cli hydrogen caption show`. They\nserve two purposes:\n\n1. **Make the video self-explanatory** — viewers understand each scene on mute\n2. **Narrator cues** — during the voiceover pass, the user sees the captions\n   in the video and knows exactly when and what to say for each scene\n\nDisplay captions at the START of each scene:\n\n```bash\nadom-cli hydrogen caption show \"Isometric view — full board layout\" -d 5\n```\n\nHide when transitioning: `adom-cli hydrogen caption hide`\n\n**Do NOT use video-post `--label` for captions.** Those are ugly ffmpeg text\noverlays. Use `adom-cli hydrogen caption show` for clean, rendered captions\nthat appear natively in the workspace.\n\n**Do NOT use Adom Viewer (AV) for captions or narration — ever.** No\n`html_interactive` caption panels, no `av_display` narration overlays, no\ncustom AV widgets that exist just to show status text. The Hydrogen caption\noverlay is the only supported captioning path for demos and for any live\nnarration of a multi-step task (\"show me what's happening as you go\"). AV is\nfor visual *content* (3D models, schematics, gerbers, scope screenshots) —\nnever for captions *about* that content. This applies to every demo skill,\nincluding tours and any ad-hoc \"narrate while you work\" requests.\n\nShow the full caption table to the user as part of the script:\n\n| Scene | Caption |\n|-------|---------|\n| 1 | `shotlog health — server running` |\n| 2a | `Isometric view — full board layout` |\n| 2b | `Top-down — trace routing and pad layout` |\n| 3 | `Timeline — 4 screenshots with descriptions` |\n\n### Panel choreography\n\nIf the demo involves multiple panels (e.g. a 3D viewer AND a log viewer),\nscript **explicit panel switches** between each action so the viewer sees both\nsides. Don't run 4 CLI commands with one panel visible — alternate:\n\n```text\n1. Show 3D viewer → rotate to isometric\n2. Switch to shotlog → screenshot appeared in timeline\n3. Switch to 3D viewer → rotate to top-down\n4. Switch to shotlog → 2 entries now\n```\n\nThis makes the demo visually interesting instead of staring at one panel while\nCLI commands run off-screen.\n\n### Speedup markers\n\nMark slow operations (exports, searches, AI latency, screenshot capture) with\ntheir speedup factor. Do NOT use `--label` for captions — speedup labels are\nonly for internal tracking. Visual captions come from `adom-cli hydrogen caption`.\n\n```text\nSpeedup: 10x — screenshot capture + inject\nSpeedup: 20x — exporting gerbers\n```\n\n### Voiceover draft\n\nWrite a short narration script the user can read during the voiceover pass.\nThe live captions displayed during recording serve as **narrator cues** — when\nthe user watches the sped-up video during voiceover, they see each caption\nappear and know exactly what to say at that moment. Structure the voiceover\nscript as caption-keyed paragraphs:\n\n```text\n[Caption: \"Isometric view — full board layout\"]\n\"Here's our CAN transceiver molecule from the isometric angle, showing the\nfull board with standoffs and the SN65HVD230 IC in the center.\"\n\n[Caption: \"Top-down — trace routing\"]\n\"From the top, you can see the trace routing and pad layout clearly.\"\n```\n\nKeep total narration under 60 seconds for most demos.\n\n### Target final duration\n\nAim for 30-60s. Nobody watches a 5-minute demo.\n\n## Step 3: Verify your setup (ralph loop yourself)\n\n**Before hitting record, screenshot the workspace and LOOK at it.** Check:\n\n- Are the right panels visible and loaded? (not empty states, loading spinners,\n  dead webviews, or cloud icons)\n- Is the content you're about to demo actually rendering?\n- Is the workspace layout clean — no overlapping panels, no stale tabs?\n- Do all the tools/CLIs you need actually work? (test a command first)\n\nIf anything looks wrong, fix it before recording. Do NOT start recording and\nhope for the best. This is the #1 cause of wasted takes.\n\n**Common setup pitfalls:**\n- Webview tabs showing blank/cloud icon → navigate with full DNS URL, not\n  relative `/proxy/` path\n- Shotlog server has stale VSCODE_IPC_HOOK_CLI → restart shotlog so it picks\n  up the current socket\n- adom-vscode not installed → `gh release download` from adom-inc/adom-vscode\n- Panel not loading → check the server is running (`health` command)\n\n### 🎞 Keep motion on screen — no static panels\n\nAny demo scene that lingers on a table, list, or panel needs visible\n**action** happening inside it, not a held still. Examples:\n\n- **BOM / Parts list** — click through several rows, not just one. Each\n  click swaps the detail view on the right and makes the scene feel\n  alive. 3-4 part clicks over 15 seconds beats one click and dead air.\n- **Components HUD** — toggle a couple of groups or individual chips\n  on/off rather than staring at the list.\n- **Schematic / PCB tabs** — pan or zoom so the artwork isn't frozen.\n- **Walkthrough / tour** — let it auto-advance; don't pause on a step.\n\nA frozen panel reads as \"this tool is boring.\" Action — even small\nclicks that add up to a few state changes — reads as \"this tool is\nresponsive and worth learning.\" Every scene longer than 8 seconds\nneeds at least one visible interaction.\n\n### 📐 Frame the split at 30% AI / 70% feature — don't maximize the feature pane\n\nWhen recording `adom-*` demos the VS Code pane (showing Claude Code live in\nthe chat) MUST stay meaningfully visible. The canonical ratio is **0.30**\n— 30% of the width is VS Code / Claude chugging, 70% is the feature\n(adom-tsci, KiCad, Fusion, etc.). That's the Adom pitch: **AI-driven EDA**.\nThe viewer needs to see Claude actually *doing* the thing they're\nwatching in the feature pane. A full-width feature pane with no chat looks\nlike any other tool. A split where Claude is chugging on the left and the\nfeature is responding live on the right is what makes Adom Adom.\n\n```bash\n# Canonical: 30% AI / 70% feature\nadom-cli hydrogen workspace resize --split-id \"$SPLIT_ID\" --ratio 0.30\n\n# Wrong: feature pane maximized, AI hidden\nadom-cli hydrogen workspace resize --split-id \"$SPLIT_ID\" --ratio 0.01\n# Wrong: too much AI, feature squeezed\nadom-cli hydrogen workspace resize --split-id \"$SPLIT_ID\" --ratio 0.50\n```\n\nException: if the demo is genuinely AI-free (a static pitch deck, a hero\nshot for thumbnails, a no-interaction explainer), maximizing the feature\npane is fine. Default is 0.30 until you have a reason otherwise.\n\n## Step 4: Record section by section\n\nFor a polished / feature-tour demo, use the **per-section** flow. One\nclip per section, TTS per section, aggregated at the end.\n\n### 🛑 Screenshot-verify the target tab BEFORE `recording start` — every clip, no exceptions\n\n**The rule:** between opening/activating a tab and pressing `recording start`, **take a screenshot of the tab and actually LOOK at it.** If what you see isn't what you expected to record, you're about to capture that bad state for the entire clip duration — and you won't notice until post-mux storyboard review (best case) or until the user plays the final and asks \"why is every example black?\" (worst case, cost = full re-shoot).\n\nWhy this breaks: `adom-cli hydrogen workspace active-tab` can silently miss. `webview open-or-refresh` returns 204 before the page has loaded its WASM / Babylon / whatever. A 3D viewer may need 5-15 seconds after `--url` lands before the board mesh is actually visible. Every second of `recording start` before the scene is visually ready = one second of black frames locked into the muxed clip.\n\n**The check, one-liner before each `recording start`:**\n\n```bash\n# Activate, then SLEEP + screenshot + visual verify before recording\nadom-cli hydrogen workspace active-tab --name \"<tab>\"\nsleep 2\nadom-cli hydrogen screenshot panel --name \"<tab>\" --reason \"pre-record verify for <clip-id>\"\n# Read the saved file. If it's not showing what you intended — DO NOT RECORD YET.\n# Poll-and-retry, longer sleep, re-navigate, etc.\n```\n\nIn a driver script the pattern is:\n\n```python\ndef wait_for_ready(tab_name, check_substring=None, timeout=15):\n    for i in range(timeout):\n        img = take_panel_screenshot(tab_name)\n        if looks_ready(img, check_substring):\n            return True\n        time.sleep(1)\n    raise RuntimeError(f'tab {tab_name} never rendered')\n```\n\nWhere `looks_ready()` does one or more of:\n- Size/histogram check — a black frame is low-variance; a board has mid-tones\n- OCR substring — caption or chip label visible\n- DOM probe via `adom-tsci eval` — `window.viewer && viewer.getScene().meshes.length > 10`\n\n**Cost of skipping this rule:** past incident — shot 36 scenes of a demo, every one of them a blank webview because active-tab missed. Full re-shoot. Same has happened on 18 of 20 scenes in the 1.3.8 tour. Don't be the next case study.\n\n### The record loop\n\n```bash\nMANIFEST=/tmp/<name>-manifest.json\nCLIPS=/tmp/<name>-clips/\nmkdir -p $CLIPS\nvideo-post manifest init --output $MANIFEST\n\nfor each section in script:\n  # 1. Show caption (viewer-side context)\n  adom-cli hydrogen caption show \"<section title>\" -d 0     # leave up for whole clip\n  # 2. Activate the target tab + any CLI pre-positioning\n  adom-cli hydrogen workspace active-tab --name \"<feature tab>\"\n  adom-tsci view isometric --port <port>                    # example\n  # 3. 🛑 SCREENSHOT-VERIFY — skip this and you're shooting blanks (see above)\n  adom-cli hydrogen screenshot panel --name \"<feature tab>\" \\\n    --reason \"pre-record verify <clip-id>\"\n  # Read the file. Confirm the board is visible. Retry if not.\n  # 4. Start recording\n  adom-cli hydrogen recording start --share tab --mic false --countdown 0 \\\n    --reason \"<section title> — <what you'll see>\"\n  # 5. Drive UI silently — 8-25 seconds of focused action\n  #    adom-tsci view top / hover / click / etc\n  # 6. Stop\n  adom-cli hydrogen recording stop\n  # last recording is saved to ~/project/recordings/<timestamp>.webm\n  CLIP=$(ls -t ~/project/recordings/*.webm | head -1)\n  mv \"$CLIP\" \"$CLIPS/<id>.webm\"\n  # 5. Generate TTS narration\n  edge-tts --voice en-US-AndrewNeural \\\n    --text \"<narration text for this section>\" \\\n    --write-media \"$CLIPS/<id>.mp3\"\n  # 6. Mux audio onto the video (replace the silent track)\n  ffmpeg -y -i \"$CLIPS/<id>.webm\" -i \"$CLIPS/<id>.mp3\" \\\n    -map 0:v -map 1:a -c:v copy -c:a libopus -shortest \\\n    \"$CLIPS/<id>-narrated.webm\"\n  # 7. Register in manifest — --description is MANDATORY, see below\n  video-post manifest add \\\n    --id \"<id>\" --title \"<short label>\" \\\n    --description \"<full intent: what this clip shows, which project, \\\n                   which features, what the viewer should notice>\" \\\n    --raw \"$CLIPS/<id>-narrated.webm\" \\\n    $MANIFEST\n  # 8. Hide caption before the next section\n  adom-cli hydrogen caption hide\n```\n\n### 📝 `--description` on every `manifest add` — not optional\n\nEvery clip registered in the manifest MUST carry a `--description` that\nsays **what you INTENDED the clip to show**. Not just the feature name:\nthe project, the interactions, the expected visible payoff. The\nstoryboard UI (Step 5 below) renders this under each clip so the user\ncan tell \"did Claude even understand what this clip was supposed to\ndo?\" before watching it.\n\nWithout per-clip descriptions, the user watches a 3-5 minute video\nblind and has to guess which segment is broken. With descriptions, they\nlook at clip N's intent, watch its 15-second snippet, and tell you\nexactly \"clip 5 measured the wrong pads\" or \"clip 8 never clicked\nanything.\" That feedback loop is the whole reason this skill exists.\n\nRule of thumb: if a teammate reading just the description couldn't\nwrite the clip's ffmpeg filter / interaction script themselves, the\ndescription is too vague. Examples of GOOD descriptions:\n\n- ✅ \"BME680 cinematic slow orbit for 15s. Camera starts iso, completes\n  one full rotation. No HUDs, no tool activation — just the board.\"\n- ✅ \"iCE40-USB Measure: commit MP1→MP3 (diagonal 181 mm), clear, commit\n  U1→J1 (chip to USB-C connector, ~50 mm), clear, commit MC_USB_DP→DM.\n  Each measurement's ΔX/ΔY/distance HUD stays visible for 5s.\"\n- ❌ \"show measure tool\"\n- ❌ \"inspect demo\"\n\n**Always use a descriptive `--reason`** on `recording start`. The user\nsees it in the approval dialog. Bad: \"Demo recording\". Good: \"Recording\nadom-tsci Inspect tool — hover pads, show net + JLCPCB part card.\"\n\n**TTS voice:** `en-US-AndrewNeural` is the default. Alternatives when\nthe demo needs variety: `en-US-AriaNeural` (polished female),\n`en-US-GuyNeural` (warm neutral male). Pick ONE voice for a whole\ndemo — switching voices mid-demo is jarring.\n\n**TTS future-compat:** when the `aci voice` subcommand ships\n(`ACI_VOICE_API` env var + `aci voice say \"<text>\" > out.mp3`), prefer\nthat — it's the same Edge-neural backend but the request goes through\nAdom's infrastructure. Until then, `edge-tts` on PATH is correct.\n\n## Step 5: 🛑 MANDATORY — open video-post storyboard and let the user review every clip\n\n**This step is not optional.** The whole per-section recording flow\nexists so the user can audit each clip individually, because AI-driven\ndemos routinely screw up details a full-video watch can't pinpoint.\nSkipping this step — going straight from manifest → concat → wiki\nupload — destroys the user's ability to tell you \"clip 5 measured the\nwrong thing\" or \"clip 8 never clicked anything.\" Don't do it.\n\n```bash\n# Opens a Hydrogen webview listing every clip in the manifest with its\n# title, description, and a player. The user can play each one and\n# tell you which clips are bad without watching the whole video.\nvideo-post storyboard $MANIFEST \\\n  --tab-name 'video-post: <name> review' \\\n  --port 8797\n```\n\nThen:\n\n1. **Actively point the user at the storyboard tab.** Activate it,\n   screenshot to confirm it rendered, tell the user the tab name to look\n   at. \"The storyboard is open in tab `video-post: …`, 13 clips, take a\n   look and tell me which ones are wrong.\"\n2. **Wait for user feedback.** The user will tell you \"clip 5 needs\n   more parts clicked,\" \"clip 11 walkthrough isn't advancing,\" etc. —\n   or just \"looks good.\"\n3. **Re-shoot surgically.** `video-post manifest remove --id <id>`,\n   re-record that one clip, `manifest add` with updated description,\n   point the user back at storyboard. Repeat until they approve.\n4. **Only then** proceed to Step 6 (concat + publish).\n\nNever concatenate or upload a demo the user hasn't seen clip-by-clip.\n\n## Step 6: Concat + publish — ONLY after storyboard approval\n\n```bash\n# Option A: `video-post concat` (drops audio today — KNOWN BUG, use B)\n# Option B: ffmpeg concat demuxer (preserves all streams)\nLIST=$(mktemp)\nfor c in $CLIPS/*-narrated.webm; do echo \"file '$c'\" >> $LIST; done\nffmpeg -y -f concat -safe 0 -i $LIST -c copy \\\n  ~/project/recordings/<name>-final.webm\nrm $LIST\n```\n\nVerify audio is present (voiceover is the whole point) — an all-video,\nno-audio output is a bug to catch here, not after upload:\n\n```bash\nffprobe -v error -show_streams <name>-final.webm | grep codec_type\n# Must show both `video` and `audio`.\n```\n\nThen publish (Step 7). If any section is bad, `video-post manifest\nremove --id <id>`, re-record that ONE clip, re-register with updated\ndescription, reopen storyboard for another review pass, then re-concat.\nThe surgical re-shoot is the whole point.\n\n## Step 6: Hero image + supplementary screenshots\n\nPick one frame from the final video as the wiki hero. Usually the\nframe where the marquee feature is clearest (e.g. the Inspect card\nhovering a chip). Then pull 2-3 more for the README / wiki body.\n\n```bash\nffmpeg -y -ss <t> -i final.webm -frames:v 1 hero.png\nshotlog resize hero.png     # keeps wiki pages lean\n```\n\n## Step 7: Publish to wiki — video, hero, screenshots, AND the demo script itself\n\n```bash\n# Final video\nadom-wiki asset upload apps/<page> --asset-type video \\\n  --file <name>-final.webm \\\n  --caption \"<one-line summary — what the viewer learns>\"\n\n# Hero image\nadom-wiki asset upload apps/<page> --asset-type hero \\\n  --file hero.png \\\n  --caption \"<the marquee feature in one phrase>\"\n\n# Supplementary section screenshots\nfor s in hero section1 section2 ...; do\n  adom-wiki asset upload apps/<page> --asset-type screenshot \\\n    --file $s.png --caption \"<what this shot shows>\"\ndone\n\n# ⭐ Demo script itself — upload as a \"reproducible recipe\" asset\n# so another Adom user (or another Claude thread) can find + replay\n# it without spelunking the source repo.\nadom-wiki asset upload apps/<page> --asset-type demo-script \\\n  --file <repo>/demo/<name>-demo-script.md \\\n  --caption \"Reproducible script for this demo — scenes, narration, TTS config, reproduction commands\"\n```\n\n### Commit the script to the repo in the same PR as the app changes\n\nThe demo script is *code* for the purposes of review and diff. When you release a new version of the app, the demo script gets bumped too (new features → new scenes → new narration). Rule of thumb: **the version at the top of the demo script and the version of the app shipping in the PR must match.** A PR that bumps the app without bumping the demo script is incomplete.\n\n```bash\ngit add <repo>/demo/<name>-demo-script.md <repo>/demo/record.py <repo>/demo/manifest.json\ngit commit -m \"demo-script: update for <version> — <what changed>\"\n```\n\n### Why the script is a first-class asset\n\n- **Discoverability.** Another Adom user browsing the wiki app page sees the video and the script. They click the script and learn: \"oh, here's exactly how this was demonstrated, and I can re-render it for my fork.\"\n- **AI-reproducibility.** A fresh Claude thread pointed at the wiki page can pull the script and re-record the demo against a new version of the app without hand-holding. That's the payoff of putting the narration + TTS config + reproduction block in the file.\n- **Review + tweak.** Someone spots a typo in scene 7's narration? They edit the script file, open a PR, CI re-renders the affected clip, hero regenerates. No mystery about what changed.\n\nThe hero image is what shows up on the wiki landing page, in\nlink-preview cards, and as the social-share image — pick it\ndeliberately. A board on a white background beats a dark canvas full\nof toolbar buttons. A card / panel that's clearly *adom-tsci* beats a\ngeneric 3D render.\n\n## Step 8: 🧹 MANDATORY — restore the user's workspace before you stop\n\n**Do not leave the user's Hydrogen workspace cluttered with demo\nartifacts.** The recording flow adds tabs, swings split ratios to 20/80,\nactivates panels, and spawns background servers (storyboard, dev-harness,\nanimation-server, whatever). None of that should persist past \"demo\npublished.\" Leaving the workspace in its mid-recording state is the\nsame bug as AI panels parking themselves over VS Code — the user has\nto manually drag everything back every single time.\n\n### What to restore\n\nBefore `recording start`, capture three things:\n\n1. **Split ratios.** `adom-cli hydrogen workspace get` → walk the tree\n   → note every split's `ratio`. Save them to a temp file (you'll need\n   them for the restore).\n2. **Pre-existing tabs.** `adom-cli hydrogen workspace tabs` → record\n   `{ tabId, name }` for every tab that existed before you started. Any\n   tab that DIDN'T exist before is yours to remove at the end.\n3. **The focused / active tab per pane.** So you don't leave the user\n   staring at your storyboard tab after the demo ends.\n\nAfter `adom-wiki asset upload` finishes, tear down in reverse:\n\n```bash\n# 1. Kill background servers you spawned.\npkill -f \"video-post storyboard\"\npkill -f \"target/debug/examples/standalone\"   # aci dev-harness\n# (add any app-specific dev servers you started, e.g. tsci dev on 8850)\n\n# 2. Remove every tab you added during the demo. Compare the current\n#    tab list against the pre-demo snapshot; the delta is yours.\nfor name in \"<tab you added>\" \"<video-post: ... review>\"; do\n  adom-cli hydrogen workspace remove-tab --name \"$name\"\ndone\n\n# 3. Restore every split's ratio from the pre-demo snapshot.\nadom-cli hydrogen workspace resize --split-id \"<id>\" --ratio <original>\n\n# 4. Re-activate the tab the user was looking at before you started.\nadom-cli hydrogen workspace active-tab --name \"<pre-demo active tab>\"\n\n# 5. Clean up any scratch files you created in the project tree\n#    (temp `.kicad_pcb` copies, /tmp/aci-demo-clips/ if you want —\n#    though leaving clips in /tmp is generally fine since /tmp is\n#    scoped; the hard rule is the user's visible project tree).\nrm -f <any temp files you copied into project dirs>\n```\n\n### Verification\n\nEnd the demo with a `workspace get` screenshot to confirm the tree\nmatches the pre-demo snapshot (same split ratios, same tab count, same\nfocused tab). If it doesn't match, keep cleaning until it does.\n\n### Why this matters\n\nA user asking for a \"demo\" is asking for a deliverable (video + wiki\npage). They are NOT asking to have their workspace rearranged for half\nan hour. The mid-recording 20/80 split + temporary tabs are part of the\n*recording apparatus*, not part of the deliverable. Treat them like\nscaffolding — build with them, demolish them when the video is up.\n\nIf a future demo skill (like the adom-tsci skill, or an adom-chipfit\ndemo) inherits this doctrine, it should do the same: snapshot before\nrecord, restore after publish. Record a follow-up `feedback` memory\nentry if you discover an artifact type this skill didn't cover\n(e.g. \"adom-tsci demo left tsci dev running on :8850\" → add a `pkill`\nline to the tool-specific demo flow).\n\n## Legacy one-take + speedup path (not preferred)\n\nThe older flow recorded a single long take then sped up slow parts\nwith `video-post process --markers`. It still works and is the right\ncall for *live* narration demos where timing is inseparable from\nspeech — but for **scripted feature tours**, the per-section path\nabove beats it on every axis: easier re-shoots, cleaner TTS, better\nhero selection, no \"narrate fast here, slow there\" timing puzzle.\n\nIf you do need the legacy path:\n\n```bash\nvideo-post inspect                      # show speedup summary\nvideo-post process --input <file>.webm --markers /tmp/video-post-markers.jsonl\nvideo-post voiceover --input <file>-fast.webm   # live-narration UI\nadom-wiki asset upload apps/<page> --asset-type video --file <final>.webm \\\n  --caption \"...\"\n```\n",
  "author": {
    "id": "695820315b5f1e4db2fcf602",
    "name": "Kyle Bergstedt",
    "email": "[email protected]"
  },
  "visibility": {
    "public": true
  },
  "hero": null,
  "metadata": {},
  "created_at": "2026-05-28T05:29:38.896Z",
  "updated_at": "2026-05-28T05:29:38.896Z",
  "sub_skills": [],
  "parent_app": null
}