Visual Echo 開発で得た教訓・パターンを記録する。
getLeafNodesで子の有無を判定する際、pending/failedの子も含めると、失敗した生成があるだけで親がリーフから除外される- ルール: 子の存在判定には必ず
.eq('status', 'completed')を付ける
getLineageがgallery/[id]/page.tsxとgallery/[id]/result/page.tsxに重複していた- 新規ファイルで同じロジックを使う時点で
lib/queries/に共通化すべき - ルール: 2箇所で使うなら即抽出(code-quality ルール #6)
?from=playパラメータで generating → result のフローを分岐- generating ページで
useSearchParamsを使う場合、依存配列に忘れず追加する - パターン: フロー元の情報はクエリパラメータで伝播、Server Component では
searchParamsprop で受け取る
- リーフノード取得(子を持たないノード)は Supabase JS クライアントでは直接表現できない
- 2段階クエリ(parent_id 一覧取得 → Set で除外)で対応
- 今後: データ量が増えたら RPC (PostgreSQL関数) に移行する
- レビュー指摘でリーフノード取得(全件フェッチ→メモリフィルタ)と系譜取得(N+1ループ)のスケーラビリティ問題が発覚
NOT EXISTSサブクエリや再帰CTEはSupabase JSクライアントでは表現できないため、PostgreSQL関数(RPC)に移行- ルール: 「全件取得→JSでフィルタ」「ループ内クエリ」パターンが出たら即RPC化を検討する。
RETURNS SETOF テーブル名で既存の型定義をそのまま活用できる - 注意:
schema.sqlにRPC関数を追加したら、types/database.tsのFunctionsセクションも同一コミットで更新する(code-quality ルール #1)
- Codex: ロジックバグ(status フィルタ漏れ)を検出
- Gemini: パフォーマンス懸念(N+1、メモリ)とベストプラクティスを指摘
- ルール: 機能実装後は Codex + Gemini の2段レビューを実施する
vitest.config.tsに必要なのは@エイリアスの解決のみ。tsconfig.jsonのpathsを手動でマッピングするglobals: trueを設定すればimport { describe, it, expect }を省略可能だが、明示的 import の方がエディタ補完が安定する
- 純粋関数(
buildTreeFromFlatData,statusVariant): モック不要。入出力だけテスト - RPC ラッパー(
getLeafNodes,getLineage):SupabaseClientを引数に取る設計なので、軽量モック (createMockSupabase) を注入するだけで済む - Server Action(
createGeneration):vi.mockでnext/cache,next/server, Supabase クライアント、Gemini クライアントの4モジュールをモック。'use server'ファイルはawait import()で動的インポートする - ルール: 関数が
SupabaseClientを引数に取る設計にしておくと、テスト時のモック注入が格段に楽になる(DI パターン)
lib/queries/の関数はすべて.rpc()経由なので、.from().select().eq()のチェーンモックは不要createMockSupabase({ rpc名: { data, error } })の工場関数パターンで RPC 名ごとに戻り値を設定する.from()チェーンが必要な Server Action テストでは、個別にmockFrom/mockInsert/mockSelect/mockSingleを組み立てる
vi.mockは巻き上げ(hoisting)されるため、モック変数はvi.fn()で宣言してからvi.mock内で参照する'use server'ファイルのインポートはawait import()を使う(トップレベルimportだとモック適用前に実行される)- パターン:
vi.mock→const mockXxx = vi.fn()→await import()の順序を守る
- Vitest v4 は Node 20+ が必須だが、README には Node 18 以上と記載していた
- Codex レビューで指摘されるまで気づかなかった
- ルール: devDependencies 追加時に
engines要件を確認し、package.jsonのenginesフィールドと README を同時に更新する
setupInsertMockが親確認・挿入・更新の3操作を1関数にまとめており、どのテストがどのモックを使うか不明瞭だった- Gemini レビューで指摘され、
setupParentExistsMock/setupInsertChainMock/setupMocksForCreationに分離 - ルール: モックセットアップ関数は1操作1関数にし、組み合わせ用の上位関数で合成する