店名・地名・スタッフ名・料金は、すべて架空です。同名の実在するお店とは関係ありません。
予約は保存も送信もされません。入力した名前や電話番号が、どこかへ送られることもありません。
スマホでは、画面の中を指でスクロールできます。
AIが返してきた文章(そのまま)
演出ではない証拠として、画面と一緒に返ってきた説明文を載せています。リポジトリの取り扱いなど、こちらの内部の話だけ省きました。
「このデモが引き受けていないこと … DB の排他制約。同時実行の最終防衛線はアプリ側の再判定では作れません。本番は exclusion constraint を置き、起動時に制約が効いているかを SQL で確認する必要があります」
b612 ON の回答より — 聞かれていないのに、自分から限界を書いている
b612 OFF の回答を読む
美容室「陽和(ひより)/中目黒」の予約画面を1枚。そのまま触れます。
この画面でできること
1 スタイリスト — おまかせ+3名。指名料(¥550 / ¥1,100)が合計に乗ります
2 メニュー — 基本1つ+追加は任意。選ぶたびに所要時間と金額が積み上がります
3 日時 — 7日×30分刻みのタイムテーブル。前後の週にも移動できます
作りの中心は所要時間の扱いです。カット60分なら2枠、カット+カラー120分なら4枠。連続で空いている枠だけが「○」になり、押せます。枠にカーソルを合わせると、その施術で埋まる範囲が先に色づきます。19:00の閉店から逆算した最終受付もメニューごとに変わります(120分なら17:00、縮毛矯正180分なら16:00)。火曜は定休で斜線。
右の「カルテ」に担当・日時・内訳・税込合計が集まり、氏名と電話番号を入れて確定すると予約番号つきの控えに切り替わります。送信は行わないデモである旨は控えに明記しています。空き状況は日付とスタイリストから決まる擬似データなので、リロードしても同じ画面が出ます。
b612 ON の回答を読む
架空の美容室「凪(中目黒)」の予約フロー1枚。メニュー → 指名 → 日時 → 確認・入力 → 完了まで実際に通ります。
b612 から引いた観点
principles / domain_preset(booking, beauty) / review_checklist(form) / patterns(予約, サロン) / rules の5系統。効いたのは主にこの6つです。
・空き確認と INSERT の間に他人が入る
→ 枠選択で HOLD を作り、HOLD も重複判定の対象に含める。確定の直前にもう一度判定し直し、埋まっていれば止める
・前後の片付け枠(beauty の典型ミス)
→ 予約は「施術時間 + 片付け10分」を占有。次の枠は片付けが明けてから
・所要時間の個人差
→ スタイリストごとのペース係数。ジュニア指名だと同じメニューが145分→175分になり、最終受付も空き枠数も変わる
・JST の暦日
→ setHours を使わず UTC+9h → getUTC*。「当日キャンセル」の判定が閲覧者のタイムゾーンに揺れない
・キャンセル料の事前明示(特商法)
→ 確定ボタンの手前に、選んだ日付の実際の料率と金額。同意なしでは確定不可
・「成功表示」を証拠にしない
→ 保存は書き込み→読み戻しの2段。読み戻せたレコードだけを完了画面の材料にする
確認画面に検証パネルを置いてあります。「この枠を他のお客様に取られる」で競合を実際に起こすと、確定の直前判定で止まって日時選択に戻ります。「送信を失敗させる」で失敗状態も踏めます。
実測
見た目で判断せず、Chromium で操作して27項目を確認しました(全PASS)。競合で止まること、仮押さえのタイマーが多重起動していないこと(3秒で3秒減)、フリガナの自動変換、電話番号の全角→半角、390px で横スクロールしないこと、ダークテーマ、JSエラーなし。
途中2件 FAIL が出ましたが、どちらもテスト側の誤りでした(初期値 05:00 を弾く正規表現/サンドボックスが Google Fonts を遮断したことによる読み込みエラー)。判定を直して取り直しています。
このデモが引き受けていないこと
・DB の排他制約。同時実行の最終防衛線はアプリ側の再判定では作れません。本番は tstzrange の exclusion constraint を置き、起動時に制約が効いているかを SQL で確認する必要があります
・通知(前日リマインド・LINE/SMS)は文言だけで送信経路なし。保存先はブラウザのメモリで、リロードすると消えます
・決済を載せるなら、決済代行の審査は商材(誰が施術するか)で可否が反転するので、実装より先に可否を確定させてください