visual-feedback-loop について
Visual Feedback Loop は、開発中に Web アプリの表示を確認するスキルです。サーバーがブラウザーと連携してスクリーンショットを取得し、メタデータやピクセル差分でコード状態ごとの描画変化を追跡できます。
主な機能
- 01ブラウザー経由の画面キャプチャ
- 02タイムスタンプ付き画像とメタデータの保存
- 03ピクセル差分による画像比較
- 04コードコミットとページパラメーターの記録
- 05SSE によるキャプチャ要求の調整
活用シーン
- 01Web アプリの現在の表示確認
- 02コード変更前後の画面比較
- 03画像に対応するコード状態の追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vladmdgolam/ agent-skills --skill visual-feedback-loopインストール後、Claude Code で/visual-feedback-loopを入力して読み込まれたか確認します。
同梱ファイルreferences/errors.mdreferences/setup-nextjs.md
詳細
ビジュアルフィードバックループ
反復的な開発中に、実行中のウェブアプリのビジュアル出力をキャプチャし、確認して比較します。
仕組み
Agent (CLI) Server Browser
|--- GET /api/dev-screenshot -->|--- SSE "capture" ----------->|
| ?param=value | |-- capture canvas
| |<-- POST { dataUrl } ---------|
| | writes:
| | .screenshots/{UTC}.webp
| | .screenshots/{UTC}.json (metadata)
| | .screenshots/latest.webp (convenience copy)
|<-- { ok, path, latest } ------|
エージェントはブラウザーに直接アクセスできません。サーバーが中継します。GET がトリガーとなり、SSE がブラウザーに通知し、ブラウザーがキャプチャして POST で返すと、GET が完了します。
使い方
現在の表示をキャプチャします:
curl http://localhost:3000/api/dev-screenshot
パラメーターを指定してキャプチャします(アプリ固有のもので、ブラウザークライアントが解釈します):
curl 'http://localhost:3000/api/dev-screenshot?component=header&theme=dark'
curl 'http://localhost:3000/api/dev-screenshot?letter=б&depth=0.8'
その後、結果を読み取ります。レスポンスには path(タイムスタンプ付きファイル)と latest(便利なコピー)が含まれます:
Read .screenshots/latest.webp
ビジュアル回帰(ピクセル差分)
ImageMagick を使って、正解のスクリーンショットと比較します:
magick compare -metric RMSE .screenshots/ground-truth.webp .screenshots/latest.webp .screenshots/diff.webp
RMSE の出力:0.01 は約 1% の差(レンダリングノイズ)、0.03+ は目に見える変化です。確認用に差分画像を保存してください。
メタデータサイドカー
各スクリーンショットには、同じ UTC タイムスタンプの JSON サイドカーが付与されます:
{
"timestamp": "2026-02-25T08:22:12.655Z",
"git": { "commit": "0a5726f", "dirty": true },
"params": { "letter": "о", "chamferModel": "membrane", "cameraView": "front" },
"format": "webp",
"file": "2026-02-25T08-22-12-655Z.webp"
}
これを使うと、どのコードの状態とパラメーターでスクリーンショットが生成されたかを追跡できます。複数のコード変更をまたいで反復する場合に欠かせません。
A/B 比較ワークフロー
- 正解画像をキャプチャします:
curl '...?letter=о'→{UTC}.webpのファイル名を控えます - コードを変更し、ブラウザーを更新します
- もう一度キャプチャします:
curl '...?letter=о' - 差分を取ります:
magick compare -metric RMSE .screenshots/{ground-truth}.webp .screenshots/latest.webp .screenshots/diff.webp - 差分画像を読み込み、RMSE の値を確認します
コンソールから実行する場合:await window.__takeDevScreenshot()
ルール
- キャプチャ後は必ず
.screenshots/latest.webpを読み込んでください。 レンダリングが正しいと思い込んではいけません。 - アプリの URL を開いたブラウザータブが必要です。 タイムアウトは SSE 接続がないことを意味します。ユーザーに更新を依頼してください。
- リクエストは一度に 1 件だけです。 2 件目の GET は 409 を返します。最初のリクエストが完了(成功/エラー/10 秒のタイムアウト)するまで待ってください。
- エラーはタイムアウトではなく、即座に返されます。 クライアントはエラーを POST で返します:
{ ok: false, error: "..." }。 - HMR への耐性。 モジュールの再読み込み後も状態を維持できるよう、サーバー側の SSE 状態を
globalThisに保存してください。クライアントの EventSource は自動的に再接続します。コード変更後もスクリーンショットを撮れない場合は、ユーザーに更新を依頼してください。 - HMR は画面外のレンダリングパスを更新しません。
useEffect内のクロージャは古いモジュール参照を保持します。レンダリングロジックを変更した後は、必ずユーザーにハードリフレッシュを依頼してください(macOS では Cmd+Shift+R、Windows/Linux では Ctrl+Shift+R)。
トラブルシューティング
| 症状 | 対処方法 |
|---|---|
| タイムアウト:ブラウザーから応答がありません | ブラウザーでアプリを開く/更新する |
| 409:すでに処理中です | タイムアウト(10 秒)まで待つ |
| 黒い画像/空の画像 | ブラウザーのタブを更新する |
| 一度は動作するが、その後タイムアウトする | HMR により SSE が機能しなくなりました。タブを更新してください(globalThis パターンを使っている場合はまれです) |
| コードの変更がスクリーンショットに反映されない | HMR は画面外のレンダラーを更新しません。ハードリフレッシュしてください(macOS では Cmd+Shift+R、Windows/Linux では Ctrl+Shift+R) |
| 異なるコードの状態のスクリーンショットを比較している | JSON サイドカーで git コミットと dirty フラグを確認する |
すべてのエラーについては、references/errors.md を参照してください。
セットアップ
このパターンはフレームワークに依存しません。GET/POST ルートと SSE を備えた HTTP サーバーがあれば使用できます。参照実装では Next.js を使っていますが、同じ方法は Express、Fastify、Hono、Vite 開発サーバープラグイン、その他の Node.js HTTP サーバーでも動作します。
Next.js の完全な実装(API ルート、SSE リスナー、WebMCP 登録)については、references/setup-nextjs.md を参照してください。ルートハンドラーを使用するフレームワークに合わせて調整してください。クライアント側の SSE リスナーとキャプチャロジックは、サーバーフレームワークにかかわらず同一です。