Was macht Demo-Video?
Zeichnet die echte Anwendung (niemals einen Mock oder ein altes Video) in einem frischen Headless-Browser auf, spricht sie mit Edge TTS, brennt Untertitel ein und rendert ein MP4. Eine Demo besteht aus zwei Dateien für ihren Prozess: narration.json und flow.cjs. Alles andere wird über den Befehl demo-video-kit in diesem Ordner gesteuert. Das Arbeitsbeispiel ist examples/todomvc/.
<kit> steht für node <dieser Ordner>/bin/demo-video-kit.js (dieser Ordner ist z.B. ~/.claude/skills/demo-video). Wenn das Kit im Benutzerprojekt installiert ist, funktioniert npx demo-video-kit genauso. <kit> help listet alle Befehle auf.
Die Anfrage bestimmt die Aufgabe
| Anfrage | Aktion |
|---|---|
| „Erstelle eine Demo für “ | Den gesamten Workflow unten, in demos/<Prozess>/ des Benutzerprojekts |
| „ Bildschirme haben sich geändert, Demo neu erstellen“ | Probelauf der neuen Bildschirme (Schritt 2), flow.cjs korrigieren und, falls die Wörter nicht mehr passen, narration.json korrigieren; dann Schritte 4 bis 8 |
| „Füge Markierungsrahmen zum bestehenden -Video hinzu“ (keine neue Aufnahme) | Markierungsrahmen in der laufenden App messen, annotations.json schreiben, <kit> annotate <demo-verzeichnis> ausführen (siehe Markierungsrahmen) |
| „… ohne Markierungsrahmen“ | Jede highlight-Option weglassen; nichts anderes ändert sich |
| „Stimme / Geschwindigkeit / Formulierung ändern“ | narration.json bearbeiten (voice, rate, Segment-text), dann Schritte 4 bis 8: die Cliplängen bestimmen das Timing auf dem Bildschirm, daher wird das Video immer neu aufgenommen |
Regeln (immer)
- Echt und frisch. Eine neue Browsersitzung; die echte Anwendung; echte Daten. Niemals ein Ergebnis, eine Zahl oder einen Status vortäuschen. Erfolg erst verkünden, nachdem die Anwendung ihn anzeigt.
- Sicher. Nur eine lokale oder Testumgebung: Jeder Durchlauf erzeugt wirklich alles, was der Ablauf erzeugt. Niemals Anmeldedaten ausdrucken oder speichern: Sie stammen aus der Umgebung (z.B.
DEMO_EMAIL,DEMO_PASSWORD); ein Passwort wird nur als Punkte angezeigt. Nach einem Testkonto fragen, wenn keins bekannt ist. - Bild. 1920x1080, 16:9, 30 fps, H.264 + AAC MP4, headless aufgenommen, sodass nichts abgeschnitten wird. Ein sichtbarer Mauszeiger, der zu jedem Ziel gleitet, und ein leichter Klickring; kein Zoom, keine Kamerabewegungen, keine ausgefallenen Übergänge. Vor jeder Aktion das Element vollständig in den sichtbaren Bereich scrollen; lange Seiten langsam genug von oben nach unten lesen, um sie zu lesen.
- Ton. Edge TTS, Standardstimme
en-IN-NeerjaNeural(weitere mit<kit> voices en-oder einem anderen Filter auflisten). Ein Clip pro Erzählsegment; Clips überschneiden sich nie; die Erzählung beschreibt nur, was auf dem Bildschirm zu sehen ist oder in dieser Szene gleich passieren wird. - Keine Lücke länger als 7 Sekunden. Kein Abschnitt ohne Erzählung darf 7 Sekunden erreichen. Wenn der Rekorder warnt oder
verify.pyfehlschlägt, die Erzählung dieses Segments verlängern oder aufteilen oder seine Aktionen beschleunigen und erneut aufnehmen. - Tempo. Keine Hektik: Jeder eingegebene Wert muss sichtbar sein, bevor fortgefahren wird.
- Der Anwendung folgen. Routen, Bezeichnungen und Schaltflächen aus der laufenden Anwendung ableiten (Probelauf), niemals aus dem Gedächtnis. Schaltflächen so benennen, wie die App es tut.
- Ausgaben bleiben außerhalb von Git. Generierte Audiodateien, Builds und MP4s werden nicht eingecheckt, es sei denn, es wird darum gebeten.
- Markierungsrahmen. Jede Demo markiert das, worauf die Erzählung zeigt, mit einem roten Rahmen, es sei denn, die Anfrage sagt „ohne Markierungsrahmen“.
Markierungsrahmen
Ein roter Rahmen (4 px, #E11D48, 6 px Abstand zum Element) wird live auf der Seite gezeichnet, während das Video aufgenommen wird, sodass er dem Scrollen folgt und Teil des Bildes ist. Standardmäßig rahmen: jedes Menüelement, das auf dem Weg zum Bildschirm angeklickt wird, die Schaltflächen, die den Prozess starten, jeder eingegebene Datenblock auf einer Überprüfungsseite und die endgültige Absenden-Schaltfläche. Nicht jedes Feld rahmen, das getippt wird: Der Cursor und das Tippen zeigen es bereits.
API in flow.cjs:
goto / click / type / pick (loc, ..., { highlight }):highlight: truerahmt das Element, zu dem der Cursor geht;highlight: <locator>rahmt stattdessen ein anderes Element (z.B. seine gesamte Zeile oder seinen Block).- Ein Rahmen bleibt, bis sich der Cursor zum nächsten Ziel bewegt.
{ keep: true }behält auch die aktuellen Rahmen;k.clearHighlights()entfernt sie;k.highlight(loc)fügt einen hinzu, ohne den Cursor zu bewegen. - Jeder Rahmen fügt einen
highlight-Eintrag zumarksinbuild/timeline-raw.jsonhinzu.
Für ein bereits ohne Rahmen aufgenommenes Video zeichnet <kit> annotate <demo-verzeichnis> diese nachträglich anhand von annotations.json. Dies ist ein Fallback; eine erneute Aufnahme ist einfacher und genauer.
Workflow
- Setup.
<kit> doctorausführen (siehe Setup) und prüfen, ob die App auf ihre URL antwortet. - Probelauf. Mit den Playwright-Browser-Tools den echten Ablauf ohne Aufnahme und ohne endgültiges Absenden durchgehen: Notieren der Route jedes Bildschirms, Bezeichnungen und
getByRole-Namen, Dropdown-Optionen, Dialoge, die endgültige Schaltfläche und was danach passiert. - Demo schreiben:
<kit> init <demo-verzeichnis> --url <app-url>erstellt einen Start, der bereits läuft; oderexamples/todomvc/kopieren. Dann bearbeiten:narration.json: Titel, Stimme, Geschwindigkeit,pronounce(geschrieben → gesprochen) und Segmente{id, scene, text}. Eine Szene in mehrere Segmente aufteilen, wenn ihre Aktionen lange dauern, sodass die Erzählung jedes Segments ungefähr so lange dauert wie seine Aktionen.flow.cjs:runDemo(__dirname, async (k) => { ... })mit einemk.segment(id, async (at, clip) => { ... })pro Erzählsegment.k.click / k.type / k.pick / k.goto / k.scrollByverwenden;at(Sekunden)wartet auf einen Punkt im Clip (z.B.clip.cues[0].end). Auf Elemente warten, niemals auf „network idle“. Zurückgeben, was verifiziert wurde.require(process.env.DEMO_KIT || 'demo-video-kit')beibehalten: Der Befehl setztDEMO_KIT.
- Erstellen.
<kit> run <demo-verzeichnis>(mitDEMO_BASE_URLund eventuellen Anmeldedaten in der Umgebung) führt Audio, Aufnahme, Rendern und Überprüfung aus. JedeWARNING-Zeile vom Rekorder und jedeFAIL-Zeile von verify verletzt Regel 5: Korrigieren und erneut ausführen. Nach Änderungen nur anflow.cjsüberspringt--from recorddas Audio; nach Änderungen nur an Untertiteln oder Kodierung überspringt--from renderdie Aufnahme. - Ansehen.
build/check/contact-sheet.png: Jeder Frame zeigt den gesamten Bildschirm, den Cursor, den richtigen Schritt, lesbare Untertitel und Rahmen auf den Elementen, die die Erzählung nennt. Das Video ist<demo-verzeichnis>/<Ordnername>.mp4. - Übergabe. Melden: MP4-Pfad, Dauer, Format, längste Lücke, was der Durchlauf erstellt hat und was sich von der Anfrage unterscheidet, weil die App anders funktioniert.
Setup
Node 18+ und Google Chrome (oder Microsoft Edge: Der Rekorder fällt darauf zurück, oder DEMO_BROWSER=msedge setzen). Einmal npm install in diesem Ordner ausführen; die Stimmen (msedge-tts), ffmpeg (ffmpeg-static) und playwright-core werden damit mitgeliefert, und der Rekorder holt sich Playwrights eigenes kleines ffmpeg beim ersten Start. <kit> doctor prüft alles, einschließlich des Internetzugangs, den die Stimmen benötigen.
Bekannte Fallstricke
| Falle | Vermeidung |
|---|---|
| Grauer Balken, Seite unten abgeschnitten | Niemals in einem sichtbaren Browserfenster aufnehmen; demo-kit.cjs zeichnet headless bei 1920x1080 auf |
waitForLoadState('networkidle') hängt bei Apps, die Polling nutzen | Stattdessen auf ein Element warten |
| Ein Headless-UI-Dialog meldet sich als verborgen | Auf ein Feld darin warten, nicht auf den Dialog |
| Escape innerhalb eines Dropdowns schließt den gesamten Dialog | Statt Escape zu drücken, eine Option auswählen |
| Stille Lücke während viele Felder getippt werden | Erzählung dieses Segments aufteilen; type() akzeptiert { delay } (Standard 32 ms) |
| Bezeichnungen, die durch CSS in Großbuchstaben dargestellt werden | Den echten Text abgleichen, nicht die dargestellte Großschreibung |
| Ein Rahmen nur um eine Bezeichnung, nicht um ihren Block | Rahmen auf das übergeordnete Element (xpath=..) oder das Großelternelement (xpath=../..) setzen |