verify-optimistic-update について
保存リクエストを遅延させて失敗させ、楽観的更新のロールバック、エラーメッセージ、再試行後の永続化を確認する Reticle スキルです。Reticle が観測できるブラウザーセッションで Web アプリを検証します。
主な機能
- 01保存リクエストの遅延と失敗
- 02楽観的な UI 状態の確認
- 03ロールバックとエラー表示の確認
- 04再試行後の永続化確認
活用シーン
- 01ToDo 編集の失敗テスト
- 02保存失敗時のエラー表示確認
- 03再読み込み後の変更確認
MCP 対応
このスキルは Reticle のブラウザーセッション用ツールで実行中のアプリを操作し、アサーションを行います。入力には MCP への公開方法は記載されていません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ reticlehq/ reticle --skill verify-optimistic-updateインストール後、Claude Code で/verify-optimistic-updateを入力して読み込まれたか確認します。
詳細
楽観的更新は、サーバーが覆せる約束です
サーバーの応答を待つ前に UI を更新すると、アプリが速く感じられます。その一方で、サーバーがまだ承認していないことを UI が表示していることにもなります。リクエストが失敗して UI がその表示を取り消す失敗経路は、誰も試しません。正常に動作するバックエンドでは起こらないからです。
Reticle を使えば、実行中のアプリでこの状況を起こせます。まだインストールしていない場合は、RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init を実行してから、install-and-verify スキルを使います。
始める前にお読みください:リクエストを失敗させるには、Reticle が管理するブラウザーが必要です
reticle_network_mock は、貸し出し中のタブ(reticle_run({ tool: "reticle_lease", args: { action: "acquire", url } }))または操作中のタブで動作します。常時接続の SDK 経由では動作せず、{ ok: false, reason: "no-cdp-provider" } が返されます。ブラウザーがない場合は、手順 4(成功経路)を実行し、ロールバックの結果は unknown と報告してください。成功経路を操作して、ロールバックが機能すると報告することだけは、決してしてはいけません。
まず、セッションを観察できることを確認します:reticle_session({ action: "list" })。非表示またはスロットリング中のタブでは、クリックを受け付けても何も描画されないことがあります。
1. 保存をゆっくり失敗させる
reticle_run({ tool: "reticle_network_mock", sessionId, args: {
mocks: [{ urlContains: "/api/todos", method: "PATCH", status: 500, delayMs: 1500 }],
}})
遅延を入れることで、楽観的な状態を観察できます。遅延がないと、画面に何か表示される前に失敗することがあり、「ロールバックされた」のか「最初から更新されなかった」のか判断できません。
2. 操作して、楽観的な状態が表示されることを確認する
reticle_look({ action: "page", sessionId, mode: "interactive" }) でコントロールの ref を取得し、クリックする前に楽観的な状態を指定します:
reticle_act_and_wait({ sessionId, ref: "<rename or toggle>", action: "click", until:
{ kind: "text", contains: "Buy oat milk" }
})
これにより、サーバーの応答前に UI が更新されたことを確認できます。次の手順では、この表示が取り消されることを確認します。
3. 元に戻り、その理由が表示されることを確認する
リクエストが失敗したら、新しい値が消え、古い値に戻り、ユーザーに通知される必要があります。ページ上に別のアラートがあっても単純なロールチェックを満たしてしまうため、アプリが実際に表示する失敗メッセージを、そのアラートの範囲内で指定します:
reticle_assert({ sessionId, timeout_ms: 5000, predicate: { kind: "allOf", predicates: [
{ kind: "net", urlContains: "/api/todos", method: "PATCH", status: 500 },
{ kind: "text", contains: "Buy oat milk", absent: true },
{ kind: "text", contains: "Buy milk" },
{ kind: "text", contains: "Could not save", scope: "[role=alert]" },
{ kind: "console", level: "error", absent: true },
]}})
アプリが状態を登録済みのストアに保持している場合は、ストアも確認します。画面上ではロールバックされていても、ストアには楽観的な値が残っていることがあるからです:
reticle_assert({ sessionId, predicate: { kind: "state", path: "todos.0.title", equals: "Buy milk" } })
4. モックを解除し、成功が維持されることを確認する
モックを解除してから、ロールバック後の状態を使って、同じ変更をもう一度行います。再レンダリング後は手順 2 の ref が古くなっていることがあります。また、名前の変更では新しい値を再入力する必要があるため、まずページを読み込みます:
reticle_run({ tool: "reticle_network_mock", sessionId, args: { clear: true } })
reticle_look({ action: "page", sessionId, mode: "interactive" })
編集の準備として行った操作をやり直し(名前を変更する場合は、新しい値を入力してフィールドに reticle_act を実行します)、保存して結果を指定します:
reticle_act_and_wait({ sessionId, ref: "<save or toggle>", action: "click", until: { kind: "allOf", predicates: [
{ kind: "net", urlContains: "/api/todos", method: "PATCH", ok: true },
{ kind: "text", contains: "Buy oat milk" },
]}})
続いて再読み込みし、サーバーが変更を保持したことを確認します。これが「保存された」という意味です。実際に再読み込みを行い、すでに表示中の URL へ移動するのではなく、新しいページを待ちます:
reticle_navigate({ sessionId, reload: true })
sessionId とともに confirmed: true が返される必要があります。再読み込み後のページである、そのセッションに対してアサーションを実行します:
reticle_assert({ sessionId: "<sessionId from the reload>", predicate: { kind: "text", contains: "Buy oat milk" } })
再読み込みが確認されない場合は、永続化の結果を unknown と報告してください。
誠実な報告
verified: "yes" のみが合格です。モック中の判定はモックした条件についてのものなので、どのエンドポイントをどのステータスで失敗させたかを伝えてください。引き渡す前にすべてのモックを解除してください。解除しないと、次のセッションでもすべての保存が失敗するバックエンドを使うことになります。
機能のリファレンス:curl https://docs.reticle.sh/capabilities.md。その他すべてについては、curl https://docs.reticle.sh/llms.txt を参照してください。