本文中の印の意味。
エグゼクティブサマリ
推奨は「案B: 静的アプリ + Supabase 最小 + 開発時 AI」。根拠は、過去3回の止まった機構がいずれも「自作するサーバー側の量」か「実行時に AI を信じたこと」か「自分で課した目標」にあり、案Bはその3つを構造的に持たないから。
- 推奨構成: Next.js(静的 export)の SPA を Vercel Hobby に置き、Supabase は Auth と
user_material1テーブル(+読み取り用 RPC 1本)だけに使う。サーバーコードは書かない。決定 - カクテルマスタはデータベースではなくリポジトリ内のコンテンツ(型付き TypeScript ファイル)として持つ。追加は Claude Code に候補を生成させ、人が PR でレビューしてマージする。管理画面もマスタ CRUD API も作らない。決定
- AI は実行時に呼ばない。v3 が実行時 AI に委ねた4責務(列挙・材料照合・画像 URL・文章生成)のうち、前3つは検証可能な事実問題であり DB マスタと集合演算で決定的に解ける(調査報告 findings-v3 の 7.3 節「architect への事実ベースの示唆」)。文章生成だけを開発時に使う。やらない
- 「作れるカクテル」の判定は v1 から一貫する「レシピの必要材料集合 ⊆ 手持ち材料集合」をブラウザ内で計算する。マスタが数百件でも即時・無料。
- 3回の頓挫の本人認識は、v2 について反証された。v2 にバックエンドコードは存在せず、止まったのは「単体テストカバレッジ 100%」という自分で課した目標だった(findings-v2 の 4.3 節「本人の記憶との照合」)。完成版では非機能目標を「完成の定義」に固定し、後から足さない。
- 完成の定義を先に固定する。公開 URL で、オペレーターがログインして材料を登録し、作れるカクテルが見え、ゲストがログインなしで同じ一覧を見られる。これで 1.0。「あと1材料で作れるカクテル」は 1.1。画像は 2026-09-16 に 1.0 へ移した(当初は 1.1 送り。完成の定義の項目 8)。
- 初日にデプロイする。3世代とも main にリリースされたことがない(findings-v1 の 5 節、findings-v2 の 5 節、findings-v3 の 6 節、いずれも「頓挫の兆候」)。空ページでも本番 URL を先に持つ。
- 工数の測り方は「セッション」(Claude Code に指示→レビュー→main マージまでの1まとまり)。1.0 まで 9 セッションを仮置きし、最初の3セッションで実測して更新する(作業分解と概算工数)。
- 代償: Supabase Free は「7日間の低活動でプロジェクトが停止されうる」(採用コンポーネントの代償)。このアプリの使われ方(来客時だけ)と正面から衝突するため、残課題の検討事項「Supabase Free の7日停止をどう扱うか」で判断する。
- セキュリティ: 3リポジトリとも認証情報がコミット済み(各調査報告)。完成版に着手する前にローテーションする(実測タスク「3リポジトリにコミットされた認証情報の失効」)。値は本文書に転記しない。
3回の頓挫の構造分析
本人の認識と調査事実を並べ、各回で「止まった機構」を特定する。3回に共通する根本原因はこの節の末尾。
本人の認識 vs 調査事実
| 世代 | 本人の認識(依頼原文) | 調査事実 | 判定 |
|---|---|---|---|
| v1 無印 2020-11〜2021-11 |
バックエンドと API を両方、バックエンドとフロントを両方、自分で全部手で実装した作業負荷 | 「手で全部」は半分正しい。API スタブ・モデル・EF エンティティは openapi-generator と scaffold で生成していた。負荷の正体は 1人で 2 サービス+共有ライブラリ 4 個+NuGet/npm 自前配布+DB+nginx+k8s を回す運用コストと生成物と手書きの二重管理。1機能(手持ち材料の登録)に 4 PR・25 日。フロントは生成クライアントを 2021-08 に捨てて手書きに戻した。12か月で活動 66 日、月 0 コミットが 4 回(findings-v1 の 4.3 節「手で全部書いたのが重かった説明の検証」) | 部分的に正しい |
| v2 2022-04〜2023-09 |
「確か同様かな」(v1 と同じ負荷) | 反証される。v2 は Supabase 化でバックエンドコードが 1 行も無い。止まったのは issue #8「UT カバレッジを 100% にする」。難しくて NGXS を廃止(issue #15 本文、354 行書いて 354 行消した)、最後は Observable 系テストを xdescribe にして services/** をカバレッジ除外し、「除外で 100%」の状態で 4 か月停止。2023-02 の実働の後半 9 コミットに画面機能が増えたものは 0(findings-v2 の 4.2 節「層別の偏り」と 4.3 節「本人の記憶との照合」) |
反証 |
| v3 2024-11〜2025-12 |
バックエンドを AI で代替したが、精度が安定せず、使用トークンが大きい | 概ね正しいが、原因は構造にある。AI が担ったのは CRUD ではなく「カクテルの列挙・材料照合・画像 URL・説明文」の生成。上限 100 件の仕様に対し max_output_tokens: 2048 で、切れた JSON は incomplete 未検知のまま 502。結果は永続化されず、無認証の公開ページで閲覧のたびに全額再生成。実在性・材料名の照合はアプリ側で検証していない。AI 版を動かして評価した痕跡はリポジトリに無い(findings-v3 の 3.2 節「構造化出力・リトライ・バリデーション・キャッシュ」、3.3 節「トークンが膨らむ構造的理由」、5 節「作業負荷の実態」) |
概ね正しい(原因は構造) |
各回の「止まった機構」
v1: 1機能あたりの工程数 × 再開コスト
YAML → 生成 → pack → Artifacts push → csproj バージョン上げ → ロジック → フロントのモデル手書き、を 1 エンドポイントごとに回した。着手のたびに環境とパイプラインの再立ち上げが要り、5 か月の空白ののち Okteto マニフェスト(DB のみ)を足して終了。認証・ユーザー管理・マスタ CRUD という「面倒だが差別化にならない部分」が丸ごと未着手(findings-v1 の 4.3 節と 5 節「頓挫の兆候」)。
v2: 自分で課した非機能目標 → アーキテクチャ往復
README に「テストは最初に書かないと一生書かないので…まずカバレッジ 100%」と書き(README の 126 行目)、それが状態管理ライブラリの導入→削除、サービス分割、テスト無効化に実働の半分以上を吸った。機能は読み取り専用の 1 画面から増えないまま燃え尽きた。設計文書は v1 のものをコピーせず、要件は文書化されないまま着手範囲だけが縮んだ(findings-v2 の 1.3 節「初代からの要件の変化」と 4.3 節)。
v3: 検証可能な事実問題を生成に委ねた + 未決事項がゲートになっていない
計画書の「未決事項」3 件(公開範囲・キャッシュ・トークン対策)が判断されないまま Codex が 1 日で実装し、12 分以内の 3 コミットでマージして停止。DB のカクテルマスタと決定的アルゴリズム(コミット 89c47e6)は残っているのに AI 経路からは未使用で、正確さと生成力が排他になった(findings-v3 の 5 節「作業負荷の実態」、6 節「頓挫の兆候」、7.2 節「AI に任せてうまくいった部分/いかなかった部分」)。10 か月の空白で Supabase プロジェクトが失われ再作成している(findings-v3 の 2 節「技術スタック」)。
3世代の指標
| 指標 | v1 | v2 | v3 | 測り方 |
|---|---|---|---|---|
| 活動期間 | 12 か月 | 17 か月(実働は 2 バースト) | 13 か月 | 初回〜最終コミット |
| コミットのあった日 | 66 | 約 15 | 10 | git log --format=%ad --date=short | sort -u | wc -l(各調査報告) |
| 最長の空白 | 5 か月 | 10 か月 | 10 か月 | 月別コミット数 |
| 手書きコード行数(概算) | 2,000〜2,500(推測) | 750(+テスト 424) | 1,135(うち AI 連携 381) | wc -l、生成物・lock 除外(各調査報告) |
| main へのリリース | 0(Okteto は DB のみ) | 0(master は init のみ) | 0(main は 2024-12 で停止) | ブランチ差分 |
| デプロイ設定 | DB の k8s マニフェストのみ | 無し | 無し | find(各調査報告) |
| カクテルマスタ件数 | 11 | 11(同一銘柄) | 11(同一銘柄、シードは旧テーブル名で流せない) | シード SQL |
| 認証 | 未実装(ID ハードコード) | 未実装(ID ハードコード) | Supabase Auth 実装済み | コード |
| テスト | 雛形のみ | 424 行、大半を無効化 | 0 件 | spec ファイル |
共通する根本原因
- 「完成」が定義されておらず、一度もリリースされていない。3世代とも main/master にリリースが無く、フロント+API のデプロイ設定も無い。完成の判定基準が無いので、どこまでやっても終わらない。
- 長い空白と高い再開コスト。5・10・10 か月の空白。再開時に環境再整備(v2 2023-02)、Supabase 再作成(v3 2025-12)が必要になる。自作した運用面が広いほど再開コストが上がる。
- カクテルマスタの問題が 5 年間未解決。v1/v2 は人手 CRUD 画面で解こうとして未着手、v3 は実行時 AI で解こうとして失敗。「AI が候補を作り、人が承認して固定する」という中間が空白のまま(findings-v3 の 7.3 節)。
- スコープの付着。ユーザー管理・ロール・管理画面が毎回計画に入り、毎回未着手(v1 の機能一覧のユーザー系 6 機能、v3 の管理者画面)。コア価値「材料を登録すると作れるカクテルが分かる」は 3 世代ぶれていない(findings-v2 の 7 節「初代との関係」)。
完成版の設計は「負荷を下げる」だけでは足りない。v2 は負荷を下げた上で別の機構で止まった。自作範囲の最小化(v1)、非機能目標の固定(v2)、AI を実行時から外す(v3)、初日リリースと再開手順(共通)の4つを同時に満たす必要がある。リスクと頓挫防止策で機構ごとに対策を対応づける。
要件定義
3リポジトリの README・設計文書・issue を統合し、完成版(1.0)に含めるもの、1.1 に送るもの、やらないものを分ける。v1 の「夢」と「現実」の区分、v3 のロール定義を土台にする。
目的とコア価値
自宅バーの持ち主が、今ある材料を登録すると作れるカクテルが分かり、来客がその一覧をスマホで見て注文できる。この 1 文が 3 世代を通じて変わらなかった価値であり(findings-v2 の 7 節、findings-v3 の issue #8)、完成版はこれ以外を作らない。
ロール
| ロール | できること | ログイン | 由来 |
|---|---|---|---|
| オペレーター(自宅バーの持ち主) | 手持ち材料の登録・解除、作れるカクテルの閲覧、共有 URL の取得 | 要 | v3 document/note.md「運用者」 |
| ゲスト(来客) | 共有 URL で、そのオペレーターの作れるカクテル一覧と詳細を閲覧 | 不要 | v3 note.md「ユーザー」、issue #8 |
| 管理者 | やらない マスタはリポジトリ内のコンテンツなので、管理者ロールも管理画面も存在しない。v3 note.md も「DB 書き換えで対応可能のため実装優先度は低め」としていた。※ 管理者画面を作る方式は 3 世代とも未着手で終わったため採らない | ||
機能要件
ID 列はこの表の中で行を区別するためだけのもので、表の外からは機能名で呼ぶ(v1 の ID 体系は重複があるため引き継がない。findings-v1 の 1.1 節「機能要件」)。「由来」列は v1 の機能一覧・v3 の issue に対応づける。
| ID | 機能 | 範囲 | 由来 | 仕様の要点 |
|---|---|---|---|---|
| F-01 | ログイン / ログアウト | 1.0 | v1 機能一覧のサインイン・サインアウト、v3 issue #2 | Supabase Auth のメール OTP(マジックリンク)。パスワードは存在しない。プロバイダの選択は検討事項「認証プロバイダ」判断 |
| F-02 | 手持ち材料の登録・解除 | 1.0 | v1 機能一覧の「現在の材料」登録・表示・削除、v3 pages/userMaterial.vue | カテゴリごとに材料チップを並べ、タップでトグル。即時保存。v3 の動作している最小 CRUD を踏襲 |
| F-03 | 作れるカクテル一覧(自分) | 1.0 | v1 機能一覧の「作成可能カクテル検索」、v2/v3 トップ | 「レシピの必須材料集合 ⊆ 手持ち材料集合」をブラウザ内で判定。材料を変えた瞬間に一覧が更新される。0 件のときは「0 件」と表示し、取得失敗と区別する(静かに壊れる箇所「Supabase の停止・ネットワーク失敗」) |
| F-04 | カクテル詳細 | 1.0 | v1 のカクテル詳細ダイアログ、v3 types/user-cocktails.ts | 名前・説明(200 字以内)・レシピ(材料と分量)・手順・難易度・アルコール度。v3 の型定義を踏襲(データモデル) |
| F-05 | ゲスト用共有 URL | 1.0 | v3 issue #8、note.md | /s?u=<オペレーターのユーザーID> をログインなしで開くと、作れるカクテル一覧とカクテル詳細が見える。URL はオペレーター画面からコピーできる。公開範囲は検討事項「共有ページの公開範囲」 |
| F-06 | マスタの追加(開発時の運用) | 1.0 | 新規(v1/v2 のマスタ CRUD、v3 の実行時 AI を置換) | Claude Code に「材料 X を使うカクテル候補を N 件、スキーマに従って生成」と指示 → 検証スクリプト → PR レビュー → マージ → デプロイ。手順を README に書くところまでが 1.0 |
| F-07 | あと 1 材料で作れるカクテル | 1.1 | 新規 | 集合の差が 1 のカクテルを別枠で表示。作れるカクテル一覧の判定関数の差分で実装できる。「次に何を買うか」に答える |
| F-08 | カクテル画像と帰属表示 | 1.1 | v2 issue #9、v3 spec の「Wikipedia 画像取得ポリシー」 | Wikimedia Commons の画像をビルド時にダウンロードして同梱し、作者・ライセンスを表示する。ホットリンクは Wikimedia 側が「推奨されない」としているため採らない(Commons: Reusing content outside Wikimedia)。1.0 に入れるかは検討事項「画像を 1.0 に入れるか」判断 |
| F-09 | カクテル名検索、材料 AND/OR 検索 | やらない | v1 機能一覧の「カクテル検索」 | 一覧はブラウザ内の配列なので、必要ならテキストフィルタ 1 つで足りる。※ 検索 API を立てる方式は v1 で search 画面ごとコメントアウトされて終わったため採らない |
| F-10 | サインアップ、パスワード再設定、プロフィール表示・編集 | やらない | v1 機能一覧のユーザー系(サインアップ・パスワード再設定・ユーザ情報表示・更新) | メール OTP ではサインアップとログインが同じ操作で、パスワードが存在しないため再設定も無い。プロフィールに表示する属性が無い。※ 自前の m_user/m_role は v1・v2 で設計だけ存在し一度も実装されなかったため採らない |
| F-11 | 材料マスタ・カクテルマスタの CRUD 画面 | やらない | v1 機能一覧の材料登録・表示・更新・削除とカクテル登録・表示・編集・削除、v2 issue #6/#7、v3 管理者画面 | 「マスタの追加(開発時の運用)」で置換。※ 3 世代とも「ボタンは置いたが // TODO: implement」で終わった機能群であり、完成版に持ち込まない |
| F-12 | レシピ共有・SNS・コメント・いいね | やらない | v1「夢」 | 複数オペレーター間の交流機能は作らない。共有はゲスト用共有 URL まで |
| F-13 | 実行時の AI 生成 | やらない | v3 server/api/user-cocktails | ※ 実行時に LLM でカクテルを列挙する方式は、出力上限・無キャッシュ・無検証・閲覧ごとの課金により v3 で頓挫したため採らない。AI の出番は「マスタの追加(開発時の運用)」のみ |
| F-14 | お気に入りカクテル | やらない | v1 m_user.favo_cocktail_id | v3 で既に廃止済み。復活させない |
| F-15 | 画像のアップロード | やらない | v1 m_cocktail.image bytea | 画像はコンテンツ(「カクテル画像と帰属表示」、1.1)。ユーザーがアップロードする経路は作らない |
非機能要件
| 項目 | 要件 | 前提・出典 |
|---|---|---|
| ランニングコスト | 月額 0 円を目標にする。超えるのは検討事項「Supabase Free の7日停止をどう扱うか」で判断したときのみ | Vercel Hobby は無料だが「非商用・個人利用に限る」(Vercel Hobby Plan)。Supabase Free の枠は採用コンポーネントの代償 |
| サーバーコード | 書かない。アプリは静的ファイルのみで、ブラウザから Supabase を直接呼ぶ | Next.js の output: 'export'(Next.js Static Exports)。サーバーが無いので秘密鍵をサーバーに置く必要も無い |
| 秘匿情報 | リポジトリに置かない。anon key と Supabase URL はビルド時の環境変数で渡す。service_role キーと DB パスワードはアプリから一切使わない | anon key はブラウザに埋まる前提の公開鍵で、RLS が唯一の防御線(静かに壊れる箇所「RLS ポリシーの欠落・誤り」)。3 世代の教訓 |
| テスト | (1) コンテンツ検証スクリプト、(2) 集合判定のユニットテスト 1 ファイル。カバレッジ目標は設けない | ※ 「カバレッジ 100%」は v2 を止めた目標であり、完成版では数値目標として採らない。テストは「落ちるべきときに落ちる」2 本に絞る(spec-guard 原則 9) |
| 状態管理 | ライブラリを入れない。React の状態と Supabase クライアントのみ | ※ NGXS/NgRx は v1 で二重登録、v2 で導入→削除(354 行往復)となったため採らない(v2 issue #15 の結論を継承) |
| 対応端末 | ゲストはスマホ想定。オペレーターも同じ画面で足りるようにする | v3 note.md のロール定義 |
| 可用性 | Supabase が停止中は共有ページが機能しない。これを許容するかを検討事項「Supabase Free の7日停止をどう扱うか」で決める。停止中でも「取得できません」と表示し、「0 件」と混同させない | 採用コンポーネントの代償、静かに壊れる箇所 |
| 再開手順 | README に「再開手順」節を置き、環境変数 2 つとコマンド 3 つで手元が動く状態を保つ | 共通する根本原因「長い空白と高い再開コスト」への対策 |
データモデル
v1 から 4 年間ぶれていない 3 層モデル(カテゴリ → 材料 → レシピ、ユーザー × 材料。findings-v3 の 7.1 節「そのまま持ち越せるもの」)を基礎に、「マスタ(リポジトリ内のコンテンツ)」と「ユーザー状態(Supabase)」の 2 領域に分ける。境界は材料 ID の文字列だけで、DB 側に外部キーは無い。
不変条件(検証スクリプトが落とすもの)
Material.idとCocktail.idは一度公開したら改名・削除しない。使わなくなった材料はdeprecated: trueにする。静かに壊れる 改名するとuser_materialの行が孤児化し、オペレーターの手持ちが黙って消える(静かに壊れる箇所「材料 ID の改名・削除」)- すべての
RecipeLine.materialIdは存在するMaterial.idを指す Cocktail.recipeはoptionalでない行を 1 件以上含む。静かに壊れる 空集合は任意の集合に含まれるので、必須材料 0 件のカクテルは「誰でも作れる」と判定される(静かに壊れる箇所「必須材料 0 件のレシピ」)Cocktail.stepsは 1 件以上、descriptionは 200 字以下(v3 spec の「レスポンス仕様」を継承)- ID の重複無し。
Material.nameの重複無し(aliases との衝突も含む)
v1〜v3 からの差分
| v3 まで | 完成版 | 理由 |
|---|---|---|
m_material_category / m_material / m_cocktail / m_cocktail_recipe(DB テーブル) | リポジトリ内の TypeScript コンテンツ | マスタは「データ」ではなく「コンテンツ」。git 履歴・PR レビュー・ビルド時検証がそのまま使え、管理画面が不要になる |
| ID は integer(v1/v2)→ uuid(v3) | 人が読める slug(例 gin、negroni) | Claude Code が生成する PR を人がレビューできる。v3 の AI レスポンス側 id も英名スラッグだった |
quantity integer(ml) | amount: string | 「1 dash」「適量」「レモン 1/2 個」が表現できる。集合判定に分量は使わない |
t_user_material.delete_at(論理削除、未使用) | 物理削除 | 3 世代で一度も使われなかった列を持ち込まない |
m_user / m_role | 無し(Supabase auth.users のみ) | v3 で廃止済み |
v_material ビュー | 無し | 結合はブラウザ内で済む |
| v3 の AI レスポンス型(difficulty / alcohol_level / steps / description) | Cocktail の属性として継承 | 表示に必要な属性の定義として有用(findings-v3 の 7.1 節)。missing_ingredients と is_user_material は判定結果なので型から外す |
画面一覧
ID 列はこの表の中だけのもので、表の外からは画面名(ログイン画面・マイバー画面・カクテル詳細・共有ページ)で呼ぶ。
| ID | 画面 | パス | ログイン | 内容 | 由来 |
|---|---|---|---|---|---|
| S-01 | ログイン画面 | /login | 不要 | メールアドレス入力 → OTP / マジックリンク送信 → 戻ってきたらマイバー画面へ | v1 のログイン画面、v3 app.vue |
| S-02 | マイバー画面 | / | 要 | 上段: カテゴリ別の材料チップ(トグル)。下段: 作れるカクテル一覧(件数付き)。「共有 URL をコピー」ボタン。1 画面完結は v1 のトップ画面の構成を継承 | v1 のトップ画面、v3 /userMaterial + /cocktails |
| S-03 | カクテル詳細 | マイバー画面・共有ページ内のモーダル | 画面に従う | カクテル詳細の属性(名前・説明・レシピ・手順・難易度・アルコール度)を表示。1.1 で画像 | v1 のカクテル詳細ダイアログ |
| S-04 | 共有ページ | /s?u=<uuid> | 不要 | そのオペレーターの作れるカクテル一覧と詳細のみ。材料チップは表示しない(ゲストに在庫を見せる必要は無い)。取得失敗時は「取得できません」を表示 | v3 /cocktails/[userId] |
| S-05 | 見つからない / エラー | /404 | 不要 | 静的 export の 404 | 新規 |
画面は 5 つ。v1 の 12 画面のうち 7 つ(ユーザー系 5・マスタ管理 2)は、機能要件で「やらない」としたサインアップ・プロフィール系とマスタ CRUD 画面に従いやらない。パスに動的セグメント(/s/[uuid])を使わないのは、静的 export が generateStaticParams() の無い動的ルートを「サポートしない」と明記しているため(Next.js Static Exports)。
完成の定義(Definition of Done)
次の 8 項目がすべて満たされた時点で 1.0 とし、git タグ v1.0.0 を打つ(2026-09-16 に 7 → 8 へ改定。画像を 1.1 から 1.0 へ移した)。それ以降の追加は 1.1 の課題として起票する。各項目は「落ちるべきときに落ちる」形で書く(spec-guard 原則 9)。<本番URL> は最初のセッション(リポジトリ作成と初日デプロイ)で確定した時点で README のコマンドに実値を埋め、この文書にも転記する。以下、表の外からは各項目を見出し文(「公開されている」など)で呼ぶ。
- 公開されている。
curl -s -o /dev/null -w '%{http_code}\n' https://<本番URL>/s?u=00000000-0000-0000-0000-000000000000の出力が200。落ちたら: Vercel のデプロイ一覧で最新ビルドの状態を見る。ロールバック: 前のデプロイを Promote(取り返しがつく) - コンテンツが検証を通る。
npm run content:checkが 1 行categories=<n> materials=<n> cocktails=<n> recipe_lines=<n> unknown_material_refs=0 empty_required_recipes=0を出力し、echo "exit=$?"がexit=0。cocktails の値が、検討事項「1.0 時点のカクテルマスタの目標件数」で決めた件数(既定 50 件)以上(数字は合格条件ではなく、桁の確認として併記する)。壊し検査(spec-guard 原則 9「検査は壊した状態で 1 回落ちることを確かめてから合格基準にする」): 存在しないmaterialIdを 1 行入れてunknown_material_refs=1かつexit=1になることを、1.0 判定の前に 1 回見る - 判定ロジックが落ちる。
npm test -- makeableが、データ非依存の最小入力で次の 3 ケースを通す: 手持ち {a,b}・必須 {a,b} → 含む/手持ち {a,b}・必須 {a,b,c} → 含まない/手持ち {a}・必須 {a}+optional {z} → 含む。壊し検査:everyをsomeに変えて 2 ケース目が落ちることを 1 回見る(判別力はコンテンツに預けない。spec-guard 原則 9「判別力をデータに預けない」) - オペレーターの縦串が動く。本番 URL でログイン → 材料を 1 つトグル → 一覧の件数表示が変わる → リロード後も材料が保持されている。合格の証拠: リロード後の画面のスクリーンショット 1 枚を
docs/dod/に置く - ゲストの縦串が動く。ログインしていない別ブラウザ(シークレットウィンドウ)で共有 URL を開き、オペレーターの縦串と同じ件数が「
N 件」の形で表示される。0 件でも「0 件」の文字列が出る(spec-guard 原則 9「何も出ないことが合格の基準を作らない」)。Supabase を一時停止した状態で同じ URL を開くと「取得できません」が出て「0 件」は出ない(壊し検査、1 回) - リポジトリに秘密が無い。
git grep -c -E 'service_role|postgres(ql)?://' -- . ':!requirements*' ; echo "exit=$?"の出力が空行+exit=1(grep の一致なし)。anon key はブラウザに埋まる前提の公開鍵なので対象外(静かに壊れる箇所「RLS ポリシーの欠落・誤り」の RLS が前提) - 再開できる。README に
## 再開手順見出しがあり(grep -c '^## 再開手順' README.md→1)、その節の手順だけでnpm run devがローカルで立ち上がることを、別ディレクトリへの fresh clone で 1 回確かめる - 画像が登録でき、来客にも見える。マイバー画面で作れるカクテルを 1 つ開き、「画像を追加する」から画像を選ぶと、カードの上部・詳細モーダル・共有ページのカードの 3 か所に同じ画像が出る。ログインしていない共有ページのモーダルには「画像を追加する」ボタンが出ない。壊し検査: 画像を登録していないカクテルには画像の枠ごと何も出ないことを、同じ 1 回の確認の中で見る(本番の権限を緩める検査は実施しない。2026-09-08 の判断を画像にも同じく当てる)。2026-09-16 に足した項目で、制約「1.0 の完成の定義に項目を足さない」を承知のうえで覆している
やらないことリスト
機能要件の「やらない」行と非機能要件を 1 か所に集める。追加したくなったら、残課題の検討事項として起票してから着手する。
| やらないこと | なぜ採らないか(否定形注記) |
|---|---|
| 自作の API サーバー(REST / GraphQL / Route Handler / Server Action) | v1 の頓挫機構そのもの。BaaS 直叩きで足りない要件が無い |
| 実行時の LLM 呼び出し | v3 の頓挫機構。事実問題を生成に委ねない |
| マスタの CRUD 画面・管理者ロール | 3 世代とも未着手で終わった。マスタはコンテンツとして git で管理する |
| サインアップ画面・パスワード再設定・プロフィール | メール OTP には該当する概念が無い |
| テストカバレッジの数値目標 | v2 の頓挫機構。テストは「コンテンツが検証を通る」「判定ロジックが落ちる」の 2 本 |
| 状態管理ライブラリ | v1 二重登録、v2 導入→削除。React の状態で足りる |
| コード生成パイプライン(OpenAPI → クライアント/サーバー) | v1 で生成物と手書きの二重管理を生んだ。型は Supabase CLI の型生成 1 回と zod で足りる |
| 複数サービス・コンテナ・k8s・リバースプロキシ | v1 の運用コストの正体。静的ファイル+BaaS に運用面は無い |
2026-09-16 に一部を解禁。今もやらないのは DB 内画像(bytea)とバケットを 2 つ以上に増やすこと | 当初の理由は「画像はコンテンツ(1.1)。ユーザー起点の画像経路は作らない」。2026-09-16 に shika の判断でユーザーごとの画像アップロードを 1.0 に入れた。増えたのは公開バケット cocktail-images 1 つだけで、テーブルも RPC も増やしていない。画像を src/content/ のコンテンツとして持つ案は今も採らない(画像は持ち主ごとの持ち物で、カクテルの属性ではないため) |
| SNS 機能(共有・コメント・いいね)、お気に入り | v1 の「夢」。コア価値の外 |
| 多言語対応、ネイティブアプリ、PWA のオフライン対応 | 1.0 のコア価値の外。1.x でも起票してから |
| Google / GitHub OAuth の設定 | 外部コンソールの UI 手順は陳腐化が速く、再開コストになる。メール OTP を主にする(検討事項「認証プロバイダ」で覆る余地あり) |
アーキテクチャ案の比較
4 案を「自作範囲」「運用面」「過去 3 回の機構が再発するか」「要件充足」で比べる。AI を置く位置が案ごとに違う点に注目する。
案A: 完全静的(ゼロバックエンド)
マスタも判定もブラウザ内。手持ち材料は URL のクエリ(材料 ID の列)に載せ、共有 URL がそのまま状態になる。ログイン無し、DB 無し、秘密無し。
AI の位置: 開発時(マスタ生成)のみ。
案B: 静的アプリ + Supabase 最小 + 開発時 AI 推奨
案A に「オペレーターの手持ちを Supabase の 1 テーブルに保存する」だけを足す。共有 URL が安定し、端末をまたいで手持ちが保たれる。サーバーコードは書かない。
AI の位置: 開発時(マスタ生成)のみ。
案C: v3 継続(実行時 AI にキャッシュと検証を足す)
Nuxt + Supabase + OpenAI の現行構成を維持し、結果を DB に永続化、incomplete 検知とリトライ、材料名の突合を追加する。
AI の位置: 実行時(初回生成)+キャッシュ。
案D: 自作 API(v1 型)を Claude Code に書かせる
Next.js の Route Handler または別サービスで API を立て、DB マスタ・認証・判定をサーバーに置く。実装は Claude Code。
AI の位置: コードを書く役のみ。
| 軸 | 案A 完全静的 | 案B 静的 + Supabase 最小 | 案C v3 継続 | 案D 自作 API |
|---|---|---|---|---|
| 自作するもの | SPA 1 つ、コンテンツ、検証スクリプト | 案A + テーブル 1・RLS 2 本・RPC 1 本・ログイン画面 | 案B の DB 側 + API 1 本 + キャッシュ表 + 検証・リトライ + 突合ロジック | SPA + API(認証・CRUD・判定)+ マイグレーション + デプロイ |
| 運用面(止まると壊れるもの) | 静的ホスティングのみ | 静的ホスティング + Supabase Free(7 日低活動で停止されうる。採用コンポーネントの代償) | 案B + OpenAI の課金・鍵・モデル廃止 | 案B + API のランタイム(Vercel Functions か コンテナ) |
| ランニングコスト(前提: 個人利用、月間アクセス数百) | 0 円 | 0 円(Supabase Free / Vercel Hobby。検討事項「Supabase Free の7日停止をどう扱うか」で Pro にすれば Supabase 料金ページの記載で月額 25 ドルから) | OpenAI 従量。生成 1 回あたりの単価は 未確認(本文書では数字を書かない) | 0 円〜(Vercel Functions の Hobby 枠内なら)。枠の数値は 未確認(Vercel Hobby Plan の取得結果に数値が含まれなかった) |
| v1 の機構(工程数 × 再開コスト) | 再発しない | 再発しない(Supabase 停止からの復帰は Studio のボタン 1 つ。Supabase docs: Pausing projects) | 一部再発(キャッシュ表・突合・API の 3 層を保守) | 再発する(API・DB・デプロイの多段工程。Claude Code が書いても運用は人) |
| v2 の機構(自分で課した目標・アーキ往復) | 設計で防ぐ(非機能要件、やらないことリスト) | 設計で防ぐ(同左) | 設計で防ぐ(同左) | 設計で防ぐ(同左)。ただし層が多いほど「整えたくなる」面が広い |
| v3 の機構(事実問題を生成に委ねる・未決のまま実装) | 再発しない | 再発しない | 構造は残る。キャッシュで回数は減るが、実在性・材料照合を AI の記憶に頼る点は変わらない。「Wikipedia に画像がある実在カクテル」の検証はアプリ側に無い(findings-v3 の 3.2 節) | 再発しない |
| 要件充足(機能要件の 1.0 範囲) | ゲスト用共有 URL が弱い: 手持ちを変えるたび共有 URL が変わる。壁の QR コードのように「安定した URL」にならない。ログインは存在しない | 全部満たす | 全部満たす(ただし「実行時の AI 生成をやらない」に反する) | 全部満たす |
| マスタ問題(5 年間 11 件)への答え | 開発時 AI + PR レビュー | 開発時 AI + PR レビュー | 実行時 AI(マスタは持たない) | 管理画面を作る(3 世代未着手)か開発時 AI |
| 秘密の置き場 | 無し | anon key(公開前提)のみ。service_role は使わない | OpenAI キーと service_role をサーバーに置く | DB 接続文字列とセッション鍵をサーバーに置く |
| 2026-10 以降(月 200h 稼働)の継続性 | 最も高い | 高い(Supabase 停止の判断次第) | 低い(課金と鍵の面倒が残る) | 低い |
案B を推奨する。案A との差は「テーブル 1 つとログイン画面」だけで、その代わりに共有 URL が安定し、v3 が定義したロール(オペレーター/ゲスト)をそのまま満たす。案B のコンテンツ層と判定ロジックは案A と同一なので、Supabase の停止問題(検討事項「Supabase Free の7日停止をどう扱うか」)が受け入れられない場合は案A に縮退できる。この縮退経路を設計に残しておく(技術スタックの「縮退経路」行)。
案C を採らない理由は、v3 の頓挫要因が「実装の粗さ」ではなく「事実問題を生成に委ねた構造」にあり、キャッシュを足しても構造が残るから。案D を採らない理由は、Claude Code がコードを書いても運用面の広さは v1 と同じだから。
推奨アーキテクチャの詳細
案B の構成、技術スタック、責務分担、Claude Code に書かせる前提の作業分解と概算。
構成図
技術スタック
| 層 | 採用 | 理由 | 代替(決定ログ参照) |
|---|---|---|---|
| フロント | Next.js(App Router、output: 'export')+ TypeScript + React | shika の主スタック(プロファイル)。静的 export は「HTML/CSS/JS を配れるどのサーバーにも置ける」と公式が明記(Next.js Static Exports)。サーバー機能を後から足せる余地は残るが、1.0 では使わない | Nuxt 3(v3 資産)、Vite + React。検討事項「フロントのフレームワーク」判断 |
| UI | Tailwind CSS(コンポーネントライブラリは入れない) | 画面 5 つ・部品はチップとカードとモーダルのみ。ライブラリの更新追従を負わない | shadcn/ui(必要になったら) |
| コンテンツ | content/*.ts を zod スキーマで定義。npm run content:check で検証 | 型と検証を 1 か所に。Claude Code の生成物をスクリプトで機械的に弾ける | JSON/YAML(型が離れる) |
| 判定 | ブラウザ内の純関数 makeable(owned: Set<string>, cocktails) | v1〜v3 の集合演算を 1 関数に。完成の定義「判定ロジックが落ちる」のテスト対象 | Postgres 関数(マスタを DB に置かないので不要) |
| BaaS | Supabase(Auth + Postgres 1 表 + RPC 1 本)。supabase/migrations/ に SQL を置き、CLI で適用 | v2・v3 の選択を継承。RLS は有効にしてポリシーを書くまで API から何も読めない既定拒否(Supabase docs: Row Level Security) | Firebase(shika は経験あり。ただし v2/v3 の SQL 資産と型生成を捨てることになる) |
| 認証 | Supabase Auth メール OTP | 外部コンソール設定が不要。パスワードが存在しない | GitHub OAuth。検討事項「認証プロバイダ」 |
| ホスティング | Vercel Hobby(GitHub 連携で push 即デプロイ) | 「非商用・個人利用」の条件を満たす(Vercel Hobby Plan)。静的なので GitHub Pages / Cloudflare Pages に差し替え可 | GitHub Pages(Next.js 公式テンプレートあり) |
| テスト | Vitest(「判定ロジックが落ちる」の 1 ファイル)+ content:check(「コンテンツが検証を通る」) | 2 本だけ。カバレッジ計測は入れない | — |
| AI(開発時) | Claude Code。CLAUDE.md にコンテンツの生成規約(スキーマ・「実在するカクテルのみ」・出典 URL 必須・1 回 20 件まで)を書く | v3 の AGENTS.md → spec → plan → impl の運用型を継承。plan の未決事項をゲートにする(リスクと頓挫防止策) | — |
| 縮退経路 | マイバー画面の判定と共有ページの表示は Supabase を介さずに動く設計にする(手持ち集合を引数に取る純関数)。Supabase の停止問題で案A に落とす場合、URL のクエリを手持ち集合の供給源に差し替えるだけで済む | アーキテクチャ案の比較の結論 | — |
責務分担
| 責務 | 人(shika) | Claude Code | Supabase | ビルド/検証スクリプト |
|---|---|---|---|---|
| 要件・完成の定義・やらないこと | 決める | — | — | — |
| アプリコード(画面 5・判定 1・クライアント 1) | レビュー・マージ | 書く | — | 型チェック・判定テスト |
| SQL(テーブル・RLS・RPC) | レビュー。データモデリングは本人の得意領域なのでここは自分で書いてもよい | 書く | 実行する | — |
| カクテルマスタの候補 | 採否をレビュー(味・実在性・材料の対応) | 生成(出典 URL 付き) | — | コンテンツ検証で機械検証 |
| 材料の追加 | 手持ちに新しい酒が増えたら 1 行足す | 関連カクテルの候補を出す | — | コンテンツ検証 |
| 認証・セッション | — | — | 担う | — |
| アクセス制御 | RLS の意図を決める | ポリシーを書く | 強制する | 壊し検査(静かに壊れる箇所「RLS ポリシーの欠落・誤り」) |
| デプロイ | push する | — | — | Vercel が自動 |
| 停止からの復帰 | Studio で Restore を押す | — | 停止から 1 年以内なら復元可(Supabase docs: Pausing projects) | — |
コンテンツ生成の手順(「マスタの追加(開発時の運用)」の中身)
- shika が Claude Code に指示: 「
content/materials.tsにある材料だけで作れる、Wikipedia に記事のある実在カクテルを 20 件、content/cocktails.tsのスキーマで追記。各件に出典 URL を付ける。既存 ID と重複しない」 - Claude Code が追記し、
npm run content:checkを自分で回してunknown_material_refs=0を確認してから PR を出す - shika が PR で 20 件をレビュー。知らないカクテル・怪しいレシピは落とす。採用率を PR 本文に書く(実測タスク「Claude Code が生成するカクテル候補の採用率」)
- マージ → Vercel が自動デプロイ → 一覧に増える
v3 は同じ問いを「閲覧のたびに」「上限 2,048 トークンで」「検証なしに」AI に投げていた。完成版は同じ問いを「開発時に 1 回」「件数を絞って」「スクリプトと人の 2 段で検証して」git に固定する。生成の単価が何であれ、閲覧回数に比例して増えない。
作業分解と概算工数
測り方: 単位は「セッション」= shika が Claude Code に 1 まとまりの指示を出し、成果物をレビューして main にマージするまで。前提: 1 セッションの人間の拘束時間を 1〜2 時間と仮置きする。この数字は実測値ではない。最初の 3 セッション(初日デプロイ・コンテンツ移植・判定とマイバー画面)を終えた時点で実測し(実測タスク「1 セッションの実所要時間」)、残りの見積もりを更新する。表の外からは各セッションを名前(「初日デプロイのセッション」など)で呼ぶ。
| 順 | セッション名 | 成果物 | マージ時の確認 | 備考 |
|---|---|---|---|---|
| 1 | 初日デプロイ | リポジトリ作成、Next.js 静的 export の空ページ、Vercel 連携、本番 URL 確定、README に再開手順の雛形 | 完成の定義「公開されている」が通る(空ページで) | 初日にやる。3 世代で一度も到達しなかった地点 |
| 2 | コンテンツ移植 | zod スキーマ、content/ に v3 のシード(カテゴリ 7・材料 24・カクテル 11)を移植、content:check | 「コンテンツが検証を通る」が通る。壊し検査 1 回 | v3 のシードは旧テーブル名のままなので SQL ではなく値を移す(findings-v3 の 1.5 節「データモデル」) |
| 3 | 判定とマイバー画面 | 判定関数 + テスト、マイバー画面(手持ちはまだブラウザ内の状態)、カクテル詳細モーダル | 「判定ロジックが落ちる」が通る。本番 URL で材料をトグルして一覧が変わる | この時点で案A 相当が動く。Supabase 停止の判断材料になる |
| 4 | Supabase とログイン | Supabase プロジェクト、user_material、RLS 2 本、RPC 1 本、メール OTP ログイン画面、マイバー画面を Supabase 保存に切替 | 「オペレーターの縦串が動く」が通る。RLS の壊し検査(静かに壊れる箇所「RLS ポリシーの欠落・誤り」) | 秘密は .env.local と Vercel の環境変数のみ。実測タスク「メールのレート制限が OTP にも適用されるか」をここで実測 |
| 5 | 共有ページ | 共有ページ、共有 URL コピー、「取得できません」の状態 | 「ゲストの縦串が動く」が通る。Supabase を一時停止して壊し検査 | 実測タスク「定期的な読み取りで Supabase の停止を回避できるか」をここから観測開始 |
| 6 | スマホ表示の仕上げ | スマホでの見た目、空状態、ローディング(v3 issue #5 相当) | 実機でマイバー画面・共有ページを開く | デザインに凝らない。Tailwind の既定で済ませる |
| 7〜8 | コンテンツ拡充 | コンテンツ生成の手順を 2〜3 回 | 「コンテンツが検証を通る」の cocktails= が目標件数(既定 50 件)に届く | 1 回 20 件、採用率を記録(実測タスク「Claude Code が生成するカクテル候補の採用率」) |
| 9 | 完成判定とタグ | 完成の定義 8 項目の一括確認、README 仕上げ、v1.0.0 タグ(2026-09-16 に画像を 1.0 へ移したため 7 → 8) | 全項目 | ここで止める。1.1 は別起票 |
合計: 9 セッション。前提の 1〜2 時間/セッションを掛けると人間の拘束は 9〜18 時間。2026-10 以降の月 200h 稼働の中では、週 1 セッションで 2〜3 か月の計算になる。10 月前の 9 月中に「共有ページ」のセッションまで(5 セッション)を終えれば、稼働が上がってからはコンテンツ拡充だけが残る。コンテンツ拡充は 1 回 30 分程度のレビューで進められる設計にしてある。
1.1(「あと 1 材料で作れるカクテル」・「カクテル画像と帰属表示」)は上記に含めない。前者は判定関数の差分で 1 セッション、後者は画像取得・縮小・帰属表示のスクリプトで 1〜2 セッションと見込むが、いずれも 1.0 の後に見積もり直す。
制約と代償
採用したコンポーネントの回避できない性質を、採用理由と同じ粒度で書く(spec-guard 原則 7)。数字はすべて 2026-09-06 取得の公式ページの引用で、引用元はその場にリンクする。
採用コンポーネントの代償
| コンポーネント | 代償 | 出典 | 本設計での扱い |
|---|---|---|---|
| Supabase Free | 「Free projects are paused after 1 week of inactivity」(料金ページ)。本番チェックリストでは「We may pause applications on the Free Plan that exhibit low activity in a 7-day period」。来客時にしか使わないアプリの使われ方と正面から衝突する。停止中はマイバー画面のログインも共有ページも動かない | Supabase 料金ページ、Supabase docs: Production Checklist | 検討事項「Supabase Free の7日停止をどう扱うか」判断。停止後の復元は「1 年の猶予」(Supabase docs: Pausing projects)。「activity」の定義と、定期的な読み取りで回避できるかは 未確認(実測タスク「定期的な読み取りで Supabase の停止を回避できるか」) |
| Supabase Free | 「2 active projects」「500 MB database size」「50,000 monthly active users」「1 GB file storage」「5 GB egress」 | Supabase 料金ページ | 容量・MAU は本設計の規模(テーブル 1・利用者 1〜数十人)では届かない。プロジェクト数 2 の枠は v3 のプロジェクトが現存すれば 1 つ消費している(実測タスク「v3 の Supabase プロジェクトが枠を消費しているか」) |
| Supabase Auth 組み込みメール | 「2 emails per hour with the built-in email provider. You can only change this with a custom SMTP setup」 | Supabase docs: Auth Rate Limits、Production Checklist | オペレーターは 1 人なので通常は届かないが、ログインを 1 時間に 3 回試すと 3 回目は届かない。この制限が OTP/マジックリンクの経路にも適用されるかは 未確認(実測タスク「メールのレート制限が OTP にも適用されるか」)。カスタム SMTP は入れない(作らない範囲) |
| Vercel Hobby | 「the Hobby plan restricts users to non-commercial, personal use only」。枠を超えると「30 日待つ」 | Vercel Hobby Plan | 個人利用なので満たす。将来収益化するなら Pro。帯域などの数値は取得結果に含まれなかったため書かない(未確認) |
| Next.js 静的 export | 動的ルート(generateStaticParams 無し)、Route Handler(Request 依存)、Cookies、Redirects、Server Actions、既定の Image Optimization が「サポートされない」 | Next.js Static Exports | 共有ページはクエリ /s?u= にする。将来サーバー機能が欲しくなったら export をやめる判断が要る(1.x の検討事項) |
| マスタをリポジトリに置く | カクテルを 1 件足すのにビルドとデプロイが要る。エンジニアでない人はマスタを触れない | — | オペレーターは shika 1 人で、追加は Claude Code 経由(コンテンツ生成の手順)。受け入れる |
| DB 側に外部キーが無い | user_material.material_id はコンテンツの Material.id と値が一致することだけが前提。DB は不整合を検出しない | — | データモデルの不変条件(ID 不変)と静かに壊れる箇所の検査で補う |
| Wikimedia Commons の画像(1.1) | 「Directly using a Commons file via embedding its URL ('hotlinking') is also possible, but is not recommended」「If attribution is required, provide attribution」 | Commons: Reusing content outside Wikimedia | 同梱+帰属表示。ライセンスごとの条件(CC BY-SA の継承など)は画像単位で 未確認(実測タスク「画像ごとのライセンスと帰属文言を API から取れるか」) |
制約(最上位の目的を守るために立てるもの)
- 実行時に外部の生成 AI を呼ばない。コア価値(作れるカクテルが分かる)は決定的に計算できる。生成に委ねると v3 の機構が戻る
service_roleキーをアプリのどの経路にも置かない。サーバーが無い設計では、置いた瞬間にブラウザへ漏れる- anon ロールに
user_materialの直接 SELECT を許さない。ゲストの読み取りは RPC 経由に限定し、全ユーザーの手持ちを列挙できる経路を作らない - 公開済みの
Material.id/Cocktail.idを改名・削除しない。DB 側の行が孤児化する - 1.0 の完成の定義に項目を足さない。足したくなったら残課題の検討事項として起票し、1.1 に送る
2026-09-16: この制約は 1 度だけ覆した。shika の指示でカクテル画像を 1.0 に含め、完成の定義を 7 項目 → 8 項目にした。覆したこと自体を記録に残す(この制約が守られなかった回数は 1 回)。
静かに壊れる箇所
エラーも警告も出ずに失敗する箇所。「何が起きるか」ではなく「気づけないこと」を書く(spec-guard 原則 6)。表の外からは各行を見出し文(「RLS ポリシーの欠落・誤り」など)で呼ぶ。
| 箇所 | 気づけないこと | 検査(壊して 1 回落とす) |
|---|---|---|
| RLS ポリシーの欠落・誤り | RLS を有効にしてポリシーを書き忘れると「no data is accessible」(Supabase docs: Row Level Security)で本人の手持ちが空に見えるだけで、エラーは出ない。逆にポリシーが緩いと他人の手持ちを書き換えられるが、UI 上は何も起きない | 「Supabase とログイン」のセッションで: 別ユーザーの JWT(または anon)で他人の user_id に INSERT / SELECT を投げ、行数 0・エラー行 1 が返ることを見る。ポリシーを一時的に using (true) に変えて同じ検査が通ってしまうことも 1 回見る(spec-guard 原則 9「検査は壊した状態で 1 回落ちることを確かめてから合格基準にする」) |
| 材料 ID の改名・削除 | コンテンツ側で gin を dry-gin に変えると、DB の gin 行は残ったまま参照されず、オペレーターの画面ではジンが「持っていない」状態に戻る。ジンのカクテルが一覧から消えるだけで警告は無い | マイバー画面の読み込み時に「DB にあってコンテンツに無い ID」を数え、0 件でも orphan_ids=0 をコンソールに 1 行出す(spec-guard 原則 9「何も出ないことが合格の基準を作らない」)。コンテンツ側は deprecated フラグで対応し、削除はレビューで弾く |
| 必須材料 0 件のレシピ | 空集合は任意の集合の部分集合なので、optional しか無いカクテルは全員に「作れる」と表示される。件数が増えるだけなので気づけない | 完成の定義「コンテンツが検証を通る」の empty_required_recipes=0。壊し検査: 全行 optional: true のカクテルを 1 件足して =1・exit=1 になることを見る |
| Supabase の停止・ネットワーク失敗 | 取得失敗を空配列として扱うと、共有ページが「0 件」を表示して正常に見える。ゲストは「このバーには何も無い」と受け取る | 完成の定義「ゲストの縦串が動く」の壊し検査。失敗は「取得できません」、成功の 0 件は「0 件」と別文言にする |
| ガーニッシュや氷を必須材料にする | 「レモンピール」「氷」を必須にすると、手持ちに登録していない限りほぼ全カクテルが作れない判定になる。「0 件」は正しく表示されるので気づけない | コンテンツ規約(CLAUDE.md)で、ガーニッシュ・氷・水は optional: true または材料に含めない。レビュー項目にする。機械検査にできるかは 未確認(実測タスク「ガーニッシュを必須にする誤りを機械検査にできるか」) |
| 環境変数の未設定ビルド | Vercel に NEXT_PUBLIC_SUPABASE_URL を入れ忘れてもビルドは通り、本番だけ Supabase に届かない(v2 の environment.prod.ts に URL が無かったのと同じ形。findings-v2 の付記) | ビルド時に環境変数 2 つの存在を検査して無ければビルドを落とす(next.config で throw)。「初日デプロイ」のセッションで一度、変数を消してビルドが落ちることを見る |
| OTP メールのレート制限 | 1 時間に 3 通目は送られないが UI は「送信しました」を出しうる | Supabase のエラー応答を画面に出す。実測タスク「メールのレート制限が OTP にも適用されるか」で実測 |
リスクと頓挫防止策
各回の「止まった機構」それぞれに「完成版ではこう再発を防ぐ」を対応づける。対策は設計(構造で防ぐ)と運用(行動で防ぐ)に分けて書く。
| 機構 | 再発の形 | 設計での対策 | 運用での対策 | 再発を検知する指標 |
|---|---|---|---|---|
| v1 1 機能あたりの工程数 × 再開コスト | 「ちゃんとした API を立てたい」「型を生成したい」と層を足す | サーバーコード 0、リポジトリ 1、生成パイプライン無し(やらないことリスト)。1 機能 = 1 PR で main に入る | README の再開手順を初日から維持。.env.example を置く | 1 つの機能が 2 PR 以上にまたがったら、この文書の検討事項として理由を書く |
| v2 自分で課した非機能目標 → アーキ往復 | 「テストを整えてから」「状態管理を入れ替えたい」「ディレクトリ構成を直したい」 | テストは「コンテンツが検証を通る」「判定ロジックが落ちる」の 2 本に固定、カバレッジ計測を入れない、状態管理ライブラリ無し(非機能要件) | 完成の定義を変えるには検討事項を起票する(制約「1.0 の完成の定義に項目を足さない」)。「機能が増えないコミット」が 3 回続いたら止まって検討事項を書く | 直近 5 コミットに画面・コンテンツの変更が 1 つも無い(findings-v2 の 4.2 節の兆候そのもの) |
| v3 事実問題を生成に委ねた | 「Claude に実行時に説明文を作らせたい」「画像 URL を AI に聞きたい」 | 制約「実行時に外部の生成 AI を呼ばない」。AI の出番は開発時の「マスタの追加」のみ。生成物は content:check と PR レビューの 2 段を通る | CLAUDE.md に「実行時に外部 AI を呼ぶコードを書かない」を明記 | package.json に LLM SDK の依存が入る |
| v3 未決事項がゲートになっていない | 計画書の「要判断」を残したまま実装に入る | 残課題の検討事項は「推奨」と「既定値」を必ず持つ。判断が無くても既定値で進み、既定値であることを README に書く | Claude Code の plan に未決が残っていたら実装に入らず、検討事項を起票する | 「要判断」「TBD」の文字列が CLAUDE.md/README に残ったまま PR がマージされる |
| 共通 一度もリリースされない | 「もう少し整ってから公開する」 | 「初日デプロイ」のセッションで本番 URL を確定(完成の定義「公開されている」)。main = 本番。develop ブランチを作らない | 毎セッション main にマージする。タグ v1.0.0 で止める | main が 2 週間更新されないのに未マージブランチがある |
| 共通 空白 → 再開コスト | 10 か月空いて環境が消えている | 自作した運用面が無い(静的 + BaaS)。Supabase の復元は 1 年以内なら Studio から(Supabase docs: Pausing projects) | 検討事項「Supabase Free の7日停止をどう扱うか」の判断。定期的な読み取りが効くなら cron で維持 | Supabase ダッシュボードの「Paused」表示 |
| 共通 マスタ問題 | 11 件のまま | マスタ = コンテンツ、生成 = 開発時 AI、承認 = PR(コンテンツ生成の手順) | コンテンツ拡充のセッションで採用率を記録し、生成規約を改善する(実測タスク「Claude Code が生成するカクテル候補の採用率」) | コンテンツ検証の cocktails= が目標件数(既定 50 件)に届かないまま 1.0 を打ちたくなる |
| 共通 スコープの付着 | ユーザー管理・管理画面を「ついでに」足す | やらないことリスト。ロールは 2 つ | 追加要望は 1.1 の課題に書く。1.0 の完成の定義には触らない | 「管理」「admin」という語が画面名に現れる |
Supabase Free の停止が最大の残リスク。使われ方(来客時だけ)と料金プランの前提(7 日以内の活動)が合っていない。実測タスク「定期的な読み取りで Supabase の停止を回避できるか」の結果次第で、定期的な読み取りで維持する/Pro に上げる/案A(完全静的)に縮退する、のどれかを選ぶ。設計は縮退が可能なように作る(技術スタックの「縮退経路」行)。
2026-10 以降の時間。9 セッションの見積もりは未実測。最初の 3 セッションの実測で 2 倍以上ずれたら、目標件数を下げるか、「スマホ表示の仕上げ」のセッションを 1.1 に送る判断をする。
残課題
「検討事項(判断が必要)」と「実測タスク(叩けば答えが出る)」を分ける。検討事項には推奨と既定値を付け、判断が無くても既定値で進める。表の外からは各行を「問い」の文で呼ぶ。
検討事項 判断
| 問い | 選択肢 | 推奨(既定値) | 判断のタイミング |
|---|---|---|---|
| Supabase Free の7日停止をどう扱うか | 「停止を許容し、GitHub Actions の cron から 1 日 1 回 RPC を読み取って停止を回避できるか実測し、効けばそのまま無料で運用する」/「Supabase Pro(公式料金ページで月額 25 ドルから)に上げる」/「ログインとデータベースを持たず、手持ち材料を URL のクエリに載せる完全静的の案A に縮退する」 | 停止を許容して定期的な読み取りを実測する。効かなければ完全静的の案A に縮退する。Pro に上げる案は月額が「0 円目標」を破るため、収益化しない限り採らない | 「共有ページ」のセッションの後、定期的な読み取りの観測(2 週間)が終わった時点 |
| 画像を 1.0 に入れるか | 「1.0 に入れる」/「1.1 に送る」 | 1.1 に送る。画像は v2(issue #9)・v3(Wikipedia 方針)でスコープを膨らませた実績があり、帰属表示とライセンス確認(実測タスク「画像ごとのライセンスと帰属文言を API から取れるか」)が付く | 今 |
| 1.0 時点のカクテルマスタの目標件数 | 30 件/50 件/100 件 | 50 件(測り方: コンテンツ検証コマンド npm run content:check が出す cocktails= の値。合格条件ではなく桁の目安)。前提: 1 回 20 件生成で採用率 5 割なら 4〜5 回のレビュー | 「コンテンツ拡充」のセッションの前 |
| 共有ページの公開範囲 | 「オペレーターのユーザー UUID を知っていれば誰でも閲覧できる(v3 note.md の方針)」/「共有用スラッグと公開/非公開の切替を持つ」 | UUID を知っていれば閲覧できる方式。制約「anon ロールに user_material の直接 SELECT を許さない」により UUID 無しでは列挙できない。スラッグ方式はテーブルが 1 つ増えるので 1.x | 「共有ページ」のセッションの前 |
| 認証プロバイダ | 「メール OTP(マジックリンク)」/「GitHub OAuth」/「Google OAuth」 | メール OTP。外部コンソール設定が無い。実測タスク「メールのレート制限が OTP にも適用されるか」で制限が掛かり、かつ不便なら GitHub OAuth | 「Supabase とログイン」のセッションの前 |
| フロントのフレームワーク | 「Next.js 静的 export」/「Nuxt 3(v3 資産)」/「Vite + React」 | Next.js。shika の主スタック。v3 の Vue 資産は約 750 行で、うち AI 経路 381 行は捨てるので再利用の利得が小さい | 「初日デプロイ」のセッションの前(今) |
| 既存 3 リポジトリの扱い | 「アーカイブし、README に完成版への導線を置く」/「そのまま」 | アーカイブ。実測タスク「3リポジトリにコミットされた認証情報の失効」の後に | 「初日デプロイ」のセッションの後 |
| 1.1 の優先順位 | 「あと 1 材料で作れるカクテル」/「カクテル画像と帰属表示」 | 「あと 1 材料で作れるカクテル」。判定関数の差分で 1 セッション。画像はライセンス取得の実測が先 | 1.0 の後 |
実測タスク 未確認
| 確認すること | 確認方法 | タイミング | 結果の意味 |
|---|---|---|---|
| 定期的な読み取りで Supabase の停止を回避できるか(Supabase Free の「activity」に、GitHub Actions の cron から 1 日 1 回 RPC を叩く読み取りが数えられるか) | 「共有ページ」のセッションの後、cron を仕込んで 14 日放置し、ダッシュボードが Paused にならないことを見る。対照として cron 無しのプロジェクトは作らない(枠が 2 つしか無い。実測タスク「v3 の Supabase プロジェクトが枠を消費しているか」) | 「共有ページ」のセッション直後から 2 週間 | 効く → 検討事項「Supabase Free の7日停止をどう扱うか」は「停止を許容して定期的な読み取りで維持」で確定。効かない → 完全静的の案A への縮退を検討。GitHub Actions の無料枠は 未確認(本文書では数字を書かない) |
メールのレート制限が OTP にも適用されるか(「2 emails per hour」(Supabase docs: Auth Rate Limits)がマジックリンク/OTP のパス /auth/v1/otp にも掛かるか) | 「Supabase とログイン」のセッションで同じアドレスに 3 回連続で送信し、3 回目の HTTP 応答コードと本文を記録する | 「Supabase とログイン」のセッション | 適用される → 静かに壊れる箇所「OTP メールのレート制限」の文言をエラー応答から出す。不便なら検討事項「認証プロバイダ」で GitHub OAuth |
静的 export でクエリ付き直リンクが 200 を返すか(Next.js output: 'export' で /s?u= の直リンクが Vercel 上で 200 を返すか) | 「初日デプロイ」のセッションで空の /s ページを置き、完成の定義「公開されている」の curl コマンドを叩く | 「初日デプロイ」のセッション | 返らない → trailingSlash か 404 のフォールバック設定を調整(Next.js Static Exports のオプション) |
| Claude Code が生成するカクテル候補の採用率 | 「コンテンツ拡充」のセッションで 20 件生成させ、PR レビューで採用した件数 / 20 を PR 本文に書く。落とした理由(実在しない/材料が無い/レシピが怪しい)を 3 分類で数える | 「コンテンツ拡充」のセッション | 採用率が 5 割未満なら生成規約(CLAUDE.md)に「出典 URL 必須」「有名どころ優先」などを追記して再測。目標件数(既定 50 件)の妥当性もここで決まる |
| 1 セッションの実所要時間(人間の拘束) | 最初の 3 セッション(初日デプロイ・コンテンツ移植・判定とマイバー画面)で開始・終了時刻を README の作業ログに書く | 最初の 3 セッション | 仮置き 1〜2 時間の 2 倍を超えたら作業分解と概算工数の残りを見直す(リスクと頓挫防止策) |
| 3リポジトリにコミットされた認証情報の失効(有効性の確認とローテーション) | Supabase ダッシュボードで v2/v3 のプロジェクト存否と anon key のローテーション、DB パスワードの変更。Azure DevOps の PAT は設定画面で失効(有効期限切れなら記録だけ)。値は本文書にもログにも書かない | 「初日デプロイ」のセッションの前 | 完成版の Supabase プロジェクトを作る前に済ませる。公開リポジトリのため優先 |
| 画像ごとのライセンスと帰属文言を API から取れるか(Wikimedia Commons、1.1) | Commons API の imageinfo(extmetadata)を 1 画像で叩き、LicenseShortName と Artist が返ることを見る。API の仕様は 未確認 | 1.1 の前 | 取れる → 「カクテル画像と帰属表示」のスクリプトで自動化。取れない → 画像は手動で 1 件ずつ、件数を絞る |
| v3 の Supabase プロジェクトが枠を消費しているか(Free の「2 active projects」枠) | ダッシュボードのプロジェクト一覧を見る | 「Supabase とログイン」のセッションの前 | 消費していれば認証情報の失効と合わせて削除または一時停止 |
| ガーニッシュを必須にする誤りを機械検査にできるか | 材料カテゴリに garnish を設け、そのカテゴリの材料が optional 無しでレシピに現れたら content:check を落とす。壊し検査を 1 回 | 「コンテンツ移植」のセッション | できれば完成の定義「コンテンツが検証を通る」の出力に required_garnish=0 を足す |
セキュリティ注意
- v1:
.npmrcとnuget.configに Azure DevOps の PAT、DML に管理ユーザーのパスワードハッシュ(findings-v1 の 2 節「技術スタック」の注記) - v2:
environment.tsに Supabase URL と anon key、README 末尾に DB パスワード(findings-v2 の付記) - v3: README に DB パスワード、
plugins/supabase.tsに anon key。2025-12 に再作成したプロジェクトの値もコミット済み(findings-v3 の 2 節と要約 10) - いずれも値は本文書に転記しない。実測タスク「3リポジトリにコミットされた認証情報の失効」で失効させ、完成版では完成の定義「リポジトリに秘密が無い」で再発を検査する
出典・参照
本文で数字や「〜できない」を書いた箇所は、その場にリンクした一次情報に対応する。ここに一覧する。取得日はすべて 2026-09-06。
調査報告(本文書の事実の出典)
- findings-v1:
/Users/kazuki.okahashi/life/sessions/2026-09-06_213000_mycocktails-architecture/findings-v1.md(repo-v1 作成) - findings-v2:
/Users/kazuki.okahashi/life/sessions/2026-09-06_213000_mycocktails-architecture/findings-v2.md(repo-v2 作成) - findings-v3:
/Users/kazuki.okahashi/life/sessions/2026-09-06_213000_mycocktails-architecture/findings-v3.md(repo-v3 作成) - 決定ログ(棄却した案・検討過程):
/Users/kazuki.okahashi/life/sessions/2026-09-06_213000_mycocktails-architecture/findings-architect.md
リポジトリ
- v1 無印: github.com/OkahashiKa/mycocktails(ブランチ develop、HEAD
28cbc93) - v2: github.com/OkahashiKa/mycocktails-v2(develop)
- v3: github.com/OkahashiKa/mycocktails-v3(develop、HEAD
7450f94)
外部サービスの公式ページ(引用箇所)
| ページ | 引用した内容 |
|---|---|
| Supabase 料金ページ | Free: 「500 MB database size」「1 GB file storage」「5 GB egress」「50,000 monthly active users」「2 active projects」「Free projects are paused after 1 week of inactivity」。Pro: 「from $25/month」 |
| Supabase docs: Pausing projects | 「When a project is paused, users have a 1-year window to restore the project on the platform from within Supabase Studio」 |
| Supabase docs: Row Level Security | 「Once RLS is enabled, no data is accessible through the API when using a publishable key, until you create policies」「A table in an exposed schema without RLS is readable and writable by any role with a grant on it. Enable RLS on every table in an exposed schema」 |
| Supabase docs: Production Checklist | 「We may pause applications on the Free Plan that exhibit low activity in a 7-day period to save on server resources」「As of 3 Sep 2024, this has been updated to 2 emails per hour. You can only change this with your own custom SMTP setup」 |
| Supabase docs: Auth Rate Limits | 「2 emails per hour with the built-in email provider. You can only change this with a custom SMTP setup」(対象パスに /auth/v1/signup、/auth/v1/recover、/auth/v1/user。OTP パスへの適用は実測タスク「メールのレート制限が OTP にも適用されるか」) |
| Vercel Hobby Plan | 「The Hobby plan is free」「the Hobby plan restricts users to non-commercial, personal use only」「if you exceed your usage limits on the Hobby plan, you will have to wait until 30 days have passed」。帯域・関数実行回数の数値は取得結果に含まれなかったため本文書では書かない |
| Next.js Static Exports | 「Since Next.js supports this static export, it can be deployed and hosted on any web server that can serve HTML/CSS/JS static assets」。Unsupported: 「Dynamic Routes without generateStaticParams()」「Route Handlers that rely on Request」「Cookies」「Redirects」「Server Actions」「Image Optimization with the default loader」ほか。GitHub Pages 用テンプレートあり |
| Commons: Reusing content outside Wikimedia | 「If attribution is required, provide attribution」「Directly using a Commons file via embedding its URL ('hotlinking') is also possible, but is not recommended」 |
本文書で意図的に数字を書かなかったもの: OpenAI / Anthropic の API 単価(実行時 AI を採らないため不要)、Vercel Hobby の帯域・関数枠、GitHub Actions の無料枠、Wikimedia Commons API の仕様。必要になった時点で一次情報を取得して追記する。
作法
- spec-guard:
/Users/kazuki.okahashi/life/.claude/skills/spec-guard/SKILL.md(断定には出典/未確認は実測タスクへ/撤回した設計は否定形注記/数字は前提とセット/静かに壊れる箇所/採用の代償/受け入れ基準は落ちるべきときに落ちる)