セルフサービス予約キオスク
キオスクとは、信頼モデルが逆転した予約ウェブサイトです。クライアントは設計上未認証であり、物理的に共有されるため、すべての信頼はサーバー側(価格、キャパシティ、冪等性)にあり、すべての状態は使い捨て(インメモリ、自動リセット、永続化なし)です。このスキルのすべては、この2つの事実に基づいています。
使用するタイミング
- スロットベースのキャパシティ(ステーション×時間枠)にオプションの装備や消耗品アドオンを加えたウォークアップ予約端末を構築する場合。
- オンライン予約がすでにあるアプリにキオスクモードを追加する場合。
- 既存のキオスクを以下のハードルールに照らして監査する場合。provenance.mdには各ルールの理由が記録されています。
使用してはいけないタイミング
- オンプレミスのオフラインフェイルオーバーサーバー — それは姉妹スキル
island-mode-serverの担当です。このスキルはキオスクのクライアントサイドフェイルオーバー契約のみを定義します。 - デジタルサイネージ/表示画面 — 異なる機能(再生のみ、入力なし)であり、異なるスキルが必要です。
- スタッフ向けPOSまたは管理予約ツール — スタッフは認証され信頼されています。このスキルの信頼モデルはそれらには適していません。
- 通常の予約ウェブサイト — バックエンドのシームが有用であれば取得してください。ただし、キオスクの仕組み(キーボード、タイマー、タッチ操作の頑健化)は重荷になります。
アーキテクチャ
KioskProvider (純粋なリデューサー: ステップ、選択、カート、sessionId)
│ props: 辞書スライス hooks: pricing · equipment · availability
画面: service→date→time→participants→equipment→summary→confirmation
│ └─ サイドフロー: booking-lookup → booking-edit
▼ 1つのfetchラッパー(デバイスキー + オフラインのベースURLフェイルオーバー)
/api/kiosk/booking/{create,lookup,add-items,edit} ← すべての信頼判断
▼ KioskBackend シーム: サーバー価格設定 · プロモーション · 在庫ロック (TTL 15分)
トランザクションキャパシティ再チェック · カウンター/オンライン支払い · ウェブフック
リアルタイム: location.lastBookingChangeAt → サイレント再fetch → REFRESH_SLOT
重要な事実
- ユーザーのタップは遷移を引き起こします。バックグラウンドのリフレッシュは決して遷移を引き起こしません。
SET_SLOTとREFRESH_SLOTの違いは、リアルタイム空き状況を表示することと、購入途中のサマリー画面から顧客をテレポートさせてしまうことの違いです。 - サーバーがすべての価格を計算します。 お金に関するデータはインバウンドでネットワークを通過しません。クライアントの合計はプレビューです。
- カウンター支払いが回復力のパスです — 予約は
PENDING_COUNTER_PAYMENTで確定し、ループ内に支払い統合はありません。キオスクでのオンライン支払いは、顧客がスキャンするQRコードを意味し、キオスク自体をナビゲートさせることはありません。 - すべてのテキスト入力はオンスクリーンキーボードを開き、
inputMode="none"を設定します。一方が欠けると操作不能な画面になります。 - 在庫ロックはTTLで予約(15分)され、すべての障害パスで解放されます — スイープジョブはセーフティネットであり、メカニズムではありません。
- クライアントのsessionIdが冪等性キーです。 ダブルタップ、リトライ、応答喪失:最大1件の予約。
ハードルール
キオスクのセッション状態を永続化してはいけません。 次の顧客が前の顧客の名前を見てはいけません。インメモリ + 120秒の非アクティブリセット + 30秒の確認リセット、ハードウェア上で検証済み。
価格、キャパシティ、プロモーションの有効性についてクライアントを信頼してはいけません。 サーバーがすべての金額を計算し、無効なプロモーションには
PROMO_INVALIDで応答します。キオスクの顧客には金額を確認する方法がありません。
すべてのキオスクリクエストに対して1つのfetchラッパーを使用します。 ラッパーはデバイスヘッダー、エラーエンベロープ、フェイルオーバーベースURLを保持するため、認証、エラー、およびオフラインフェイルオーバーがすべてのリクエストに同時に適用されます。
デバイスキーが設定されている場合、ヘッダーがないと401になります。 キーが間違っている場合のみ拒否することは、認証なしと同じです。
在庫ロックを取得した後のすべての障害パスで解放します。 予約を保持したままの409は、TTL全体にわたって在庫を凍結します。
クイックスタート
- state-machine.mdを読み、リデューサーとプロバイダーを実装し、テストスイートを実行します。
- screens.mdの契約に基づいて、ホストのデザインシステムで画面を構築します — キーボード、タイマー、タッチ操作の頑健化。
- api-contract.mdのルートをbooking-backend.mdのシーム上に実装します。
- realtime-offline.mdに従って、鮮度とオフラインを配線します。
- operations.mdに従ってデプロイし、スモークテストを実施します。
アダプテーション契約
| シーム | このスキルが提供するもの | ホストが提供するもの |
|---|---|---|
| ドメインエンティティ | service/station/equipment/consumable + 名称変更テーブル | 独自の用語(court, kart, bay…) |
| テナントスコープ | キオスクURL内のlocationId、サーバー検証 | 独自のロケーション/サイトモデル |
| デバイス認証 | x-kiosk-api-key契約 | キー管理; オプションでデバイス別トークン |
| データアクセス | KioskBackendインターフェース; Firestoreリファレンス + SQLスケッチ | 独自のORM/SDK |
| 支払い | カウンターステータス + checkout-session/webhook契約 | Stripeまたは同等のもの |
| リアルタイム | SubscribeFreshnessインターフェース | Firestore/Supabase/SSE/ポーリング |
| オフライン | NetworkStatus契約 | LANサーバー(island-mode-server)またはなし |
| UIプリミティブ | 構造、状態、インタラクションルール | ボタン、ダイアログ、スピナー、ルック |
| スタイリング | レイアウト意図、タッチターゲット、1つの--primaryトークン | 独自のデザインシステム |
| 文字列 | 辞書キーツリー、すべてのキー必須 | 独自のi18nファイル、すべてのロケール |
| バリデーション | 依存関係フリーのガード | 独自のスキーマライブラリ(あれば) |
参考ディレクトリ
| シナリオ | トリガーキーワード | リファレンス |
|---|---|---|
| 状態、ステップ、リデューサー、セッション、テスト | step, reducer, GO_BACK, REFRESH_SLOT, sessionId, reset | state-machine.md |
| 画面、キーボード、タイマー、タッチ、i18nキー | オンスクリーンキーボード, 非アクティブ, カウントダウン, QR, タッチターゲット, 辞書 | screens.md |
| ルート、認証、バリデーション、冪等性、エラー | x-kiosk-api-key, 401, 409, 冪等性, PROMO_INVALID, lookup, PII | api-contract.md |
| キャパシティ、在庫、支払い、ウェブフック、cron | トランザクション, 在庫ロック, TTL, PENDING_COUNTER_PAYMENT, ウェブフック, 払い戻し | booking-backend.md |
| リアルタイム更新、キャッシュ、オフラインフェイルオーバー | lastBookingChangeAt, onSnapshot, s-maxage, オフラインバナー, LAN, ヘルスポーリング | realtime-offline.md |
| デプロイ、ゲーティング、オペレーター画面 | Edge --kiosk, 割り当てられたアクセス, ハートビート, フィーチャーゲート, スモークテスト | operations.md |
| テンプレートが以前の実装と異なる理由 | 監査, 台帳, 意図的に保持, 追加 | provenance.md |