demo-video は何をしますか?
bun run demo <feature>/<clip> は、リースしたiOSシミュレーターでテイクを録画し、1920x1080のビデオをレンダリングします。タイトルは左、各 warden コマンドを実際に入力しステップ終了時に出力を表示する端末は中央、電話画面は右(角丸、3pxのリング)に配置されます。キット: scripts/demo/(demo.ts CLI、record.ts、render.py、timeline.ts、demo.types.ts)。定義ファイル: apps/example/demos/<feature>/<clip>.demo.ts(name は <feature>/<clip> と一致する必要があります)。アセット: apps/docs/public/demos/<feature>/<clip>.mp4|jpg。フィーチャーごとに1フォルダ(biometrics、network、location、clock)、小文字、ハイフン区切り。内部用: changeset不要。
1. 作成するタイミング
- 画面上で効果が視覚的に確認でき、数秒で完了する機能(biometrics、アラート、ジェスチャー)。ログ、パフォーマンス数値、表で表示した方が良いものには利用しません。
- アプリUIやコマンドの動作が変更された後: mp4を編集せず、必ず再録画します。
- クリップあたり1つのストーリー、10〜25秒、3〜6のキャプション付きステップ。
ffmpeg、ffprobe、uv(brew install ffmpeg uv)、Xcode、および node_modules/@expo-google-fonts のフォント(bun install)が必要です。
2. 定義を書く
demo: DemoDefinition(scripts/demo/demo.types.ts)をエクスポートします。apps/example/demos/biometrics/faceid.demo.ts をコピーしてください。
| フィールド | 意味 |
|---|---|
title / subtitle / terminalTitle / footer | 静的テキスト;titleは主張、subtitleは1行 |
phoneCaption | 電話画面の下の行、デフォルトは real simulator · not an animation;"" で非表示 |
claim | { biometrics?, profile? } -> warden claim ios --json --label demo-<feature>-<clip> --ttl 30m [--biometrics] |
app | bundleId、kind(デフォルトは simulator)、project(リポジトリ相対のcwd)、deepLink(--settle 付きで開く)、appearance(light/dark、リースされたudidに simctl ui で設定し、後に復元) |
setup | アプリ起動後、ディープリンク前にカメラオフで実行される warden のargv;["ui","network","set","online","--relaunch"] はiOS dylib起動ネットワーク/クロックステップに必要なもの。エントリはargvまたは { argv, optional: true }:オプションのステップが非ゼロで終了してもログに記録されるだけで(optional setup warden … -> <code>)、テイクは失敗しません(表示されない可能性のある許可プロンプト) |
ready | ディープリンク後、ビデオ開始前に通過しなければならない warden ui wait セレクター(例: id=bio-screen);マウントされていない画面をステップが押すことはありません |
leadInMs / tailHoldMs | ステップ1前のアイドル時間 / 最終フレームの保持時間、レンダラーによってパディングされます(テイクの末尾は1秒) |
steps[] | argv(warden の後;レコーダーが --udid を追加)、output(ok はベクターチェックを描画、plain はミュート、lines は複数のミュート行を表示)、pauseAfterMs、shown?、hidden?、quote? |
outro | 最後の出力後にタイプされる # comment 行 |
output: { kind: "lines", text: [...] } は、ファイルから表示するコードやテキストのために、エントリごとに1行のミュート行を表示します(約6行までに抑えてください;最初の1行を超えるごとにレイアウトのエントリピッチの0.4を消費します)。quote: { file }(app.project からの相対パス)とペアにします:ステップ後、レコーダーは印刷された各行(トリム済み)がそのファイルに存在することを確認し、存在しない場合は quoted line not in <file>: <line> で例外をスローするため、引用行が捏造されることはありません。行は依然として maskSecrets を通過します。
時間は手動で記述しません:レコーダーが各ステップの開始と終了をビデオクロックに対して計測し、出力行はステップの終了時に表示されます。
3. ループ
bun run demo <feature>/<clip> --record-only # クレーム、9:41のステータスバー、起動、録画、IDで解放
bun run demo <feature>/<clip> --render-only --sheet # mp4 + jpgをビルド、<work>/sheet.jpg を書き込み
bun run demo <feature>/<clip> # 両方
作業ディレクトリ: $TMPDIR/warden-demo/<feature>/<clip>(--out dir):take.mp4、steps.json、plan.json、sheet.jpg;テイク失敗時は failure-snapshot.txt(エラー + ui snapshot -i)。ここにあるものはコミットしません。
- 録画。
sheet.jpg(4x4タイル、1秒あたり1枚、ptsが焼き付けられている)を読み取ります。ログと照合:各出力行は、電話画面が視覚的に反応するタイミングで配置されています;無関係なアラート、キーボード、赤枠、間違ったステータスバーを表示するフレームはありません;最終フレームは終了状態を示しています。- より詳細に確認するには:
ffmpeg -ss 7 -i take.mp4 -frames:v 1 f.pngでフレームを抽出し、Readします。 - 定義で修正し(
pauseAfterMs、ステップの削除/非表示、shown)、--render-onlyを実行;テイク自体が間違っていた場合のみ再録画します。バッチ修正:ラウンドごとに1回の録画。 apps/docs/public/demos/<feature>/に mp4 + jpg をコミットします。
4. 正直ルール
- 表示されるすべてのコマンドは、実際のアプリに対して実行されました。
argvと意味が異なるshownはありません(フラグ(--timeoutなど)のみをトリミングします)。 - 出力テキストは、電話画面が実行した内容であり、コマンドが出力した内容ではありません。プレス操作に視覚的な効果がない場合(シートの開始アニメーション中にドロップされた、何も変更されなかった)、キャプションを付けないでください:
hidden: true、または前のpauseAfterMsを長くしてタイミングを修正します。 - 初回実行時の許可アラートは、クリーンインストール時にのみ表示されます;クレームはウォームビルドを再利用するため、テイクでアラートが表示されない限り、キャプションを付けないでください。
- アプリ側のバイパスやモックプロンプトは禁止:タイトル内のクレームはテイク上で真実でなければなりません。
- シークレット:
maskSecretsは key=value、ghp_/sk-スタイルのトークン、および長いhex/base64を印刷行で隠します。それでも電話画面でトークン、メール、デバイス名がないか確認してください。 - 実際のシミュレーター、実際のiOS UI。決して偽のフレームを合成しないでください。
5. 注意点(キットにエンコード済み)
- simctl video はVFRで、アイドルテイルをドロップします:レンダラーは
fpsで正規化し、最後のフレームを計画長にパディングします(tpad)。 - ビデオ時間はコマンドログと約0.5秒以内で一致します;レコーダーは
video startの直後にクロックを開始します。 - Martian Mono にはチェックグリフがありません:
render.pyがベクターとして描画します。 - フレームはドキュメントの Device Hub ダークパレット(Mona Sans + Martian Mono)です;
app.appearance: "dark"を設定して電話画面を合わせます。レコーダーは後にシミュレーターの外観を復元します。 - 端末オーバーフロー:
computeTerminalLayoutはピッチとフォントを17pxに縮小し、最も古いエントリをスクロールアウトします。 - デバイスビデオ制限3分;それでもテイクは40秒未満に抑えてください。
- ステータスバー:リースされたudidでは
xcrun simctl status_bar <udid> overrideが許可されています;レコーダーは後にそれをクリアします。
6. X エクスポート
1920x1080、H.264 High、yuv420p、30 fps、+faststart、音声なし、2分20秒以下、小さなファイル(キットはCRFラダー24..36で2 MB以下を目標)。確認:
ffprobe -v error -show_entries stream=codec_name,profile,width,height,r_frame_rate -show_entries format=duration,size apps/docs/public/demos/<feature>/<clip>.mp4
7. アセットとドキュメントへの埋め込み
出力:apps/docs/public/demos/<feature>/<clip>.mp4 + <clip>.jpg(ポスター = 最終フレーム)、/demos/<feature>/<clip>.mp4(Vite public/)で提供。DemoClip MDXコンポーネント(apps/docs/src/components/demo-clip.tsx)で埋め込み:自動再生、ミュート、ループ、モーション軽減設定時はポスターで静止。src に拡張子はありません;label はクリップの内容を示します(キャプションと一致)。生の <video> は apps/docs/src/lib/source.test.ts に失敗します。
<DemoClip src="/demos/<feature>/<clip>" label="warden ui … がリースされたシミュレーターで … を実行中" />
コマンドを記載したキャプション文を追加します。バッチリール apps/docs/public/demos/e2e/batch.*(<DemoReel> で埋め込み)は別のパイプラインです(bun run demo:compose)。
8. warden dev リール(dev/hero)
3番目のパイプライン:wardenの実際の開発ダッシュボード(左、端末カード内)がシミュレーター(右)の横に表示されます。warden dev はそれ自身をクレームします。タイピングや偽装は一切ありません:dev-take.ts は PTY(Bun.spawn({ terminal }))で warden dev ios --label demo-dev-<clip> --headless --no-shutdown を生成し、@xterm/headless にミラーリング(フレーム読み取りで待機)、すべてのPTYチャンクを tui.cast に書き込みます;クロックの原点は生成時です。warden ls --json でラベル付きデバイスリースをポーリングし、ダーク + 9:41 を設定、アプリをアンインストールして ui screen video を開始します(それまでは電話画面は黒です)。開発子プロセスは独自の WARDEN_SESSION_ID を取得します:warden dev は呼び出し元のセッションが保持するデバイスリースを採用し、すべてのチームメイトエージェントがそれを共有するため、これがないとテイクが他の人のシミュレーターを操作する可能性があります。
bun run demo:dev dev/hero --record-only # クレーム、アプリをアンインストール、9:41 + ダーク、録画、IDで解放
bun run demo:dev dev/hero --render-only --sheet # agg + ffmpeg -> apps/docs/public/demos/dev/hero.{mp4,webm,jpg}、<work>/sheet.jpg
bun run demo:dev dev/hero # 両方
- キット:
dev-demo.ts(CLI)、dev-take.ts(レコーダー、runBeats)、dev-render.ts(agg + 合成、DEV_LAYOUT)、dev-frame.py(カード、リング、マスク)、dev-timeline.ts(キャストテキスト、ランプセグメント、キャプション)。型:DevDemoDefinition/DevBeatindemo.types.ts。定義:apps/example/demos/dev/<clip>.demo.ts;作業ディレクトリ$TMPDIR/warden-demo/dev/<clip>(take.mp4、tui.cast、dev.json、sheet.jpg、failure-frame.txt)。 - ビート =
waitScreen(TUIフレームの正規表現)+waitPhone(ui waitセレクター)-> アクション(TUIへのkeys、および/またはリポジトリファイルの実際のedit)->afterPhoneセレクター(ホットリロード着地)->pauseAfterMs。dev.jsonはビデオクロック上の各ビートの開始/準備/終了を保持し、キャプションはビートのアクションから次のビートのアクションまで実行されます。 - 編集は実際のソース編集です(
welcome.tsx本文文字列)、finallyでバイト単位で元に戻されます;テイク後にgit diff apps/example/srcが空であることを確認します。 uninstallFirstはリースされたシミュレーターからアプリを削除し、warden devがキャッシュから開発ビルドをカメラ上にインストールするようにします。指紋認証の最初のテイクはローカルビルド(数分、dev build行がコンパイル)です:キャッシュをウォームアップするために1回録画し、その後再録画します。warden app ensure --kind devはwarden devが読み取るキャッシュをウォームアップしません。- スピードランプ:
DevBeat.ramp: Nはそのビートの待機をN倍に高速化し(先頭1.2秒と末尾1秒は実速度を維持)、リールは× N speedタグを描画します。開発クライアントの待機が遅いウォームテイクはそれをランプします。キー操作とホットリロードは常にリアルタイムです。 - 正直さ:
<DemoReel>キャプション内の事実は、テイクのTUI行(dev build 3.5s、Metro 3.9s、dev client 161ms)およびdev.json(編集 ->ui waitが新しいテキストを確認:0.76秒)から取得されます。再録画のたびに再読み取りしてください;変更される可能性があります。 - 制限:ダッシュボードには個別のクレーム行はありません(
preparing the ios dev build内にあります);--headlessによりDevice Hubウィンドウは開きません;100x26 PTY(100列はHUDの最小値)。stopVideoFirstを持つビートは、アクションの前にビデオを停止します(qでリースが終了し、warden uiはその後拒否します)。qの後、テイクはwarden lsがセッションのリースを保持していないことを確認します(それ以外の場合は失敗)、キャストはダッシュボードがalt画面を離れる場所でカットされるため、リールは最後のフレームで終了します。Dev Launcherの白いローディング画面と青いRefreshing…バナーは実際のアプリです;Metroのリロードは別のエージェントがapps/example/srcを編集したことによる場合もあります。 - ランプ:
rampは、待機時間から先頭1.2秒と末尾1秒を差し引いた残りが少なくとも6秒(RAMP_MIN_S)の場合にのみ適用されます。ヒーローは開発クライアントの待機を3倍(タグ付き)でランプします。 devEnvはwarden dev子プロセスに環境変数を追加します;ヒーローはEXPO_NO_DEPENDENCY_VALIDATION=1(@expo/clicheckDependenciesOnStart)を設定して、MetroペインからExpoの「利用可能なアップデート」警告を削除します。ノイズ対策にのみ使用し、動作には決して使用しないでください。<DemoReel src="/demos/dev/hero" command="warden dev ios" facts="…" label="…" />で埋め込みます(ランディングヒーロー、sims/dev.mdx、getting-started/quickstart.mdx)。ポスター =posterBeatアクションフレーム + 1秒(HUD表示、すべての行完了、アプリ実行中)。
9. 端末中心のクリップ(VID-6)
すべて実際のコマンド;各リールは実行されたものだけを表示します。一度に1クリップ、ラベルで解放。
| クリップ | パイプライン | コマンド |
|---|---|---|
e2e/parallel | batch-take.ts:warden e2e <suite> --count N --record --headless -> <work>/rec、各リースされたシミュレーターを起動時にダーク + 9:41 にスタイリング、その後 compose.ts --height 1080 | bun run demo:batch e2e/parallel --suite example --specs devtools,gestures,onboarding,sign-in,todo-detail --count 2(--record-only / --compose-only;ヘッダーは仕様数を表示;カウンターは N/total shards done を読み取る、仕様がジョブと異なる場合) |
dev/worktrees | LanesDemoDefinition:recordDev 1つをレーンごとに並行、下にシェルストリップ(warden ls) | bun run demo:lanes dev/worktrees(lanes.ts、lanes-frame.py、lanes-demo.ts;レーンは DevTake.startedAtS を介して1つのクロックを共有) |
golden/clone | DevDemoDefinition.script:シェルスクリプトが warden dev の代わりにPTYで実行 | bun run demo:dev golden/clone |
debug/failure | script + rawVideo | bun run demo:dev debug/failure --sheet |
scriptモード:アンインストールなし、解放チェックなし;テイクはrelease --mine --label L --shutdownとsims delete <udid> --yesで終了します。waitBootを持つビートはsimctl io screenshotをポーリングし、PNGが100 KB(HOME_SCREEN_MIN_BYTES)を超えるまで待機します:新しくクローンされたヘッドレスシミュレーターはclaimが戻った後も10秒以上Appleロゴを表示します。その待機をランプします(3倍、タグ付き)。rawVideo:simctl io recordVideo(SIGINTで停止)で録画します。これは、wardenのui screen videoヘルパーがwarden e2eと1つのデバイスで競合するためです。--recordは開始時に存在するデバイスのみを録画します;後から参加するものにはビデオがありません。ホスト容量ゲートが収容可能数を設定するため、開始時に起動されているシミュレーター以上を要求しないでください(ここでは最大2)。debug/failureの使い捨て仕様はapps/example/e2e/debug-demo.e2e.tsに書き込まれ、シェルトラップによって削除されます;その後git statusを確認してください(強制終了されたテイクが残す可能性があります)。<DemoReel src="/demos/<feature>/<clip>" command="…" facts="…" label="…" />で埋め込みます;事実はテイクから取得され、再録画のたびに再読み取りされます。demo:batchはポーラー(styleWhenBooted:warden ls --jsonでラベルを検索、simctl listでBootedを検索、その後ui appearance dark+status_bar override 9:41)を使用してシミュレーターをスタイリングします。これはwarden e2eにデバイスごとのフックがないためです;finallyで--mine --label demo-<feature>-<clip> --shutdownを解放します。クリーンなシミュレーターで合格する仕様を使用してください(perf、settings、todosは確実に合格しません);compose --height 1080フラグは16:9のレイアウトを提供します(端末)
…
[翻訳のため一部省略]