デモ動画の収録
アドホックなPlaywrightストーリーボードを作成し、自己完結型エンジンを任意のカレントディレクトリから実行します。
ユーザーが明示的に要求しない限り、ストーリーボードをアプリケーションリポジトリに追加しないでください。
ストーリーボードは自己完結型に保ってください。ランナーは、Playwrightが読み込む前にそのファイルをエンジン内部にステージングするため、ストーリーボードからの相対インポートは使用しないでください。
<skill-dir>は、このリポジトリがクローンされたディレクトリ(例: ~/.claude/skills/record-demo)を意味します。
ストーリーボードの作成
以下のポータブルインポートでDSLをインポートします:
import { demo } from "record-demo/storyboard";
ランナーはステージング時にこの指定子をエンジンの実際のパスに書き換えるため、ストーリーボードファイルはどこにでも配置できます。
record-demo/storyboard のエディタでの型解決は engine/tsconfig.json の paths に基づきます – リポジトリ外では、record-demo/storyboard を tsconfig の paths で絶対パス <skill-dir>/engine/lib/storyboard.ts にマッピングするか、ファイルを engine/scripts/ 内に保持してください。
engine/scripts/example-site.demo.ts をリファレンスとして使用してください。getByRole セレクタを推奨し、locator.click() の代わりに d.click(locator) を使用し、アクションの間隔は d.hold(...) で調整し、最終状態をアサートまたは待機してください。
demo("storyboard-title", async (d) => {
await d.seed(async (page) => {
await page.getByRole("heading", { name: "Example Domain" }).waitFor();
});
await d.step("ページを読む", async (page) => {
const heading = page.getByRole("heading", { name: "Example Domain" });
await d.zoomTo(heading, { padding: 48 });
await d.hold(2_000);
d.zoomOut();
await heading.waitFor({ state: "visible" });
});
});
エンジンはタイムライン名を --name またはスクリプトのベース名で上書きするため、demo(...) に渡す文字列は出力スラッグと一致する必要はありません。
事前フライト(キャプチャ前)
ローカルで提供されるチェックアウトに対して収録する場合: 提供チェックアウトで git fetch origin を実行し、HEAD == origin/main(またはユーザーが指定したブランチ)であることをアサートしてください。古いチェックアウトは古いデザインを収録することになります。古い場合は、最初にサーバーを再ビルド/再起動してください。提供SHAを収録ノートに出力し、すべての収録が追跡可能であることを確認してください。
収録
Node >= 22.18 (nvm またはシステム)、ffmpeg、Xvfb、xdpyinfo が必要です。engine/ 内で pnpm install を使用して一度依存関係をインストールしてください。
node <skill-dir>/engine/record.mjs \
--script /absolute/or/cwd-relative/demo.demo.ts \
--url https://example.org \
[--name output-slug] [--formats 16x9,1x1,9x16] \
[--chrome none|macos|browser] [--storage-state <path>] [--raw]
--formats を使用して1つ以上のコンポジションプリセットを選択し(デフォルトは 16x9)、--chrome でレンダリングされるウィンドウフレームを選択します(デフォルトは none)。フォーマットはリクエストされた順序でレンダリングされ、最初のフォーマットがGIFのエンコードに使用されます。
9x16 フォーマットはアグレッシブな中央クロップ(ワイドショットではページ幅の約44%のみ表示)であるため、ポートレート向けに作成する際はアクションを中央に配置してください。
--raw を使用して、クリーンキャプチャ後に停止します。それ以外の場合は、以下の成果物が期待されます:
engine/out/<name>-raw.mp4- リクエストされた各フォーマットの
engine/out/<name>-<format>.mp4 - プライマリフォーマットからエンコードされた
engine/out/<name>.gif
ランナーは終了時に絶対パスの成果物パスを出力します。ライブサイトの再現性はターゲットサイトの責任と見なし、ストーリーボードに実際の認証情報を埋め込まないでください。
認証が必要なサイト
認証フローの収録はステージングまたはテストアカウントでのみ行ってください。本番環境の認証情報は絶対に使用しないでください。エージェントはまず自身のツールを使用して、ヘッドレスなPlaywrightスクリプトで一度ログインする必要があります。サインインにメール送信されるOTPやマジックリンクが必要な場合、推奨される方法はエージェント自身のメールアクセス(Claude Codeの場合は連携されたGmailコネクタ/MCP、その他のランタイムの場合はメールCLI)を使用してコードを読み取ることです。それができない場合は、ユーザーにペーストしてもらってください。ログイン後、await context.storageState({ path: storageStatePath }) でセッションを保存します。ここで storageStatePath は、storageStatePath="$(mktemp -d)/<account>.storage-state.json" として作成される使い捨ての一時パスです。その後、--storage-state "$storageStatePath" を付けて収録コマンドを実行します。
Playwrightの storageState はクッキーとlocalStorageのみを復元します。IndexedDBやsessionStorageに認証を保持するアプリは、サイレントにログアウト状態の画面を収録するため、キャプチャ前に認証済みページを確認してください。storage-stateファイルは機密情報です。絶対に印刷したりコミットしたりしないでください。レコーダーは読み込み後にすぐに削除します。これは使い捨ての受け渡しであり、再収録には新たなログインが必要で、認証関連のデータはディスクに残りません。一時的なログインヘルパースクリプトも認証情報を保持するため、一時的に保ち、絶対にコミットしないでください。
Remotion Studioでの収録済みデモの編集
収録後に自己完結型プロジェクトをエクスポートし、Studioをインストールして開きます:
node <skill-dir>/engine/scripts/export-project.mjs <name>
cd <skill-dir>/engine/out/<name>-project
pnpm install
pnpm studio
Studioのタイムラインパネルには、エディタースタイルのトラック(スクリーンフィルムストリップ、キャプションごとに1つの名前付きクリップ、スポットライト、ズーム移動、アウトロ)が表示されます – 再生ヘッドをドラッグしてスクラブし、クリップの行をクリックしてイベントを見つけてください。プロップスパネルまたは fixtures/timeline.json を使用して、キャプション、ズームのタイミング(ズーム移動は1650msのフロア以上に保つ)、スポットライトのタイミング、アウトロタイトルを編集してください。色、タイポグラフィ、間隔、モーションについては remotion/theme.ts を編集してください。完成した16:9動画は pnpm render でレンダリングします。
モーションとストーリーボードのルール(ポスト可能なクリップにするために)
- シーンごとに1回のカメラ移動。フィールドごとではありません。 セクションに一度ズームインし、その中でアクションを行います。入力ごとのズームは目障りなカットになります。
- ページ遷移時はワイドに戻り、まばらなページではワイドを維持します。 ナビゲートするクリックの前に
d.zoomOut()を実行します。ほぼ空のページでステータスピルにズームしないでください。 - ページ下部付近のセクション:
d.zoomTo([...locators], { scroll: "center", padding: 48 })– カメラクランプはページ端の矩形を中央に配置できません。スクロール中央揃えはルートでそれを修正します。Locator[]はユニオンbboxを介してセクション全体をフレーミングします。 d.spotlight([...locators])= Appleスタイルのフォーカスディム(カットアウト+リング)。holdMsは十分に取り、ステップ終了前にd.spotlightOff()+hold(600)で明示的に終了します。スポットライト矩形はビューポートに収まる必要があります(強制されます)。ズーム矩形は設計上オーバーフローする場合があります。- Seedは実際のデータを待機します。トリガー要素だけではありません。 そうしないと、動画はスケルトン状態で開きます。既知のコンテンツノード + 約600ms待機します。
- 結果キャプションの前に
d.settle(locator)を使用します。 送信後に再フェッチするビューは一時的に空白になります。単純なwaitForは最初のペイントで解決され、結果キャプションが白いページの上に表示されます。 - キャプションは命令形で、カーソルアクションに関連付けます。 クロージングシーンには結果キャプション(
d.caption(...)をステップの後半に)を配置します。ステップキャプションをトリミングして、命令形が完了したアクションの上に残らないようにします。 d.outro(title)はポスト可能なクリップを閉じます。 最終シーンの後に落ち着いたエンドスレートを表示します。- 常に終了状態をアサートします。 これがないと、パイプラインは壊れたフローの洗練された動画をレンダリングします。
- localhost URL、内部ホスト名、プロバイダー名、実際の認証情報を画面に表示したクリップを決して出荷しないでください。Seed内でプレゼンテーション専用の文字列をマスクします(window.openパッチとMutationObserverによる入力書き換え)。
報告前に確認する項目
engine/でpnpm typecheck && pnpm test。node scripts/score-motion.mjs out/timeline.json– FAILはブロックします。WARNのズームレート ~0.78/s は許容される定常状態です。不連続性の判定はカメラバグです。デッドエアのWARNは、カーソル移動が40px未満の静的ストレッチが3.5秒以上続くことを意味します – ペーシングを調整するか、キャプションを追加してください。- エンジン変更後は
node scripts/prove-composition.mjsを実行。 - 実際のレンダリングを確認:
ffmpeg -i out/<name>-16x9.mp4 -vf fps=1 out/frames/%03d.pngを実行し、フレームを読み取ってください。ズームのクリッピング、キャプションの遅延、カットアウトの非同期、画面上の開発URLなどは、グリーンテスト実行では見えません。
よくある問題
| 症状 | 原因 / 修正 |
|---|---|
Invalid demo viewport ... 中止 | ジオメトリがずれています – 起動フラグを修正し、アサーションを緩めないでください。 |
| カーソルオフセットが約174px垂直方向にずれる | Chromeが --app ではなくタブモードで実行されました。 |
| カチンコフレームが見つからない | 検出器のトレランスは64です(外部キャプチャは平均して不純です)。それでも見つからない場合は、キャプチャが黒でないか確認してください(Chromeが間違ったDISPLAYを使用)。 |
| キャプション/カーソルがクリップ上でずれる | カチンコ同期 – 壁掛け時計の引き算をしないでください。 |
| スポットライトのカットアウトがターゲットから外れる | 矩形がスムーズスクロールと競合しました – measureTargetの安定性ループが保持するはずです。再発する場合は、タイムアウトを上げてください。 |
| クロップ端で単語が途中で切れる | モーダルには zoomTo(locator, { padding: 120 }) を使用します。 |
| ズームでコンテンツがフレームからはみ出す | maximumScale の好みの問題(1.45で動作)。クランプバグではありません。 |
エンジン変更の確認
engine/ から実行:
pnpm typecheck && pnpm test
node scripts/prove-composition.mjs