This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
- Primary Instruction File: This file (
CLAUDE.md) is the authoritative source of truth for this project. - Ignore GEMINI.md: Do not use
GEMINI.mdfor project instructions. - Language: Communicate in Japanese for all tasks unless asked otherwise.
- Artifacts: Write all artifacts (implementation_plan, walkthrough) in Japanese.
Visual Echo is an asynchronous, branching image association game powered by AI. Players describe an image in text, and the AI (Google Gemini) generates a new image from that description. This creates a tree structure of visual transformations, similar to Git branches.
Core concept: A single image can spawn multiple interpretations, forming a "tree of imagination" where each node represents a generation cycle (image → text → new image).
The entire game state is represented by a single generations table that uses the Adjacency List pattern for tree structures:
parent_idcreates parent-child relationships between generations- Root nodes (starting images) have
parent_id = NULL - Each node can have multiple children, enabling branching narratives
- Indexes on
parent_idenable efficient tree traversal queries
Critical architectural decision: The tree structure is NOT materialized or cached. Always query directly from the generations table. Use recursive CTEs for deep tree traversal if needed.
This project uses separate Supabase clients for different Next.js contexts:
-
Server-side (
lib/supabase/server.ts):- Used in Server Components, Server Actions, and Route Handlers
- Handles cookie-based session management via
@supabase/ssr - Must be imported with
awaitdue to asynccookies()call - Example:
const supabase = await createClient()
-
Client-side (
lib/supabase/client.ts):- Used in Client Components (components with
'use client') - Uses browser localStorage for sessions
- Synchronous initialization
- Example:
const supabase = createClient()
- Used in Client Components (components with
Do NOT mix these clients. Using the wrong client will cause session/auth issues. The middleware (middleware.ts) automatically refreshes sessions across requests.
All database operations are type-safe via types/database.ts:
Databaseinterface mirrors the Supabase schema exactly- Exported type aliases:
Generation,GenerationInsert,GenerationUpdate,GenerationStatus - Enables full IntelliSense for
.from('generations').select()queries - Status field is strictly typed as
'pending' | 'completed' | 'failed'
When modifying the database schema: Update supabase/schema.sql AND types/database.ts in sync.
Image generation uses Google Gemini 2.5 Flash Image model via @google/genai SDK (lib/gemini/client.ts).
Implementation details:
- Uses
gemini-2.5-flash-imagemodel for image generation - High temperature (1.5) and sampling parameters for creative, diverse outputs
- Generated images are saved locally in
public/images/generated/directory - Image URLs are stored as
/images/generated/{timestamp}-{random}.png - Images are generated asynchronously in background via Server Actions
- Generated images are excluded from git via
.gitignore
Background generation flow:
- Server Action creates a
pendinggeneration record - Returns immediately with the generation ID
- Background function generates image via Gemini API
- Downloads base64 image data and saves to local filesystem
- Updates generation record to
completedstatus with image URL
# Development
npm run dev # Start dev server at localhost:3000
# Production
npm run build # Build for production
npm start # Start production server
# Code quality
npm run lint # Run ESLint (pre-commit hookで自動実行)
# Testing
npm test # Run all tests (vitest run)
npm run test:watch # Watch mode (vitest)
npx vitest run lib/queries/tree.test.ts # 単一ファイル実行
npx vitest run -t "空配列" # テスト名でフィルタRequired environment variables (see .env.local.example):
NEXT_PUBLIC_SUPABASE_URL= # Supabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY= # Supabase anon/public key
GEMINI_API_KEY= # Google Gemini API key
GEMINI_MODEL=gemini-2.5-flash-image # Optional: model versionDatabase setup: Run supabase/schema.sql in Supabase SQL Editor before first use.
import { createClient } from '@/lib/supabase/server';
async function MyServerComponent() {
const supabase = await createClient();
const { data, error } = await supabase
.from('generations')
.select('*')
.eq('parent_id', parentId);
// Handle data...
}'use client';
import { createClient } from '@/lib/supabase/client';
import { useEffect, useState } from 'react';
function MyClientComponent() {
const [data, setData] = useState<Generation[]>([]);
const supabase = createClient();
useEffect(() => {
async function fetchData() {
const { data } = await supabase
.from('generations')
.select('*');
setData(data || []);
}
fetchData();
}, []);
// Render data...
}To get all children of a node:
const { data: children } = await supabase
.from('generations')
.select('*')
.eq('parent_id', nodeId);To get root nodes (starting images):
const { data: roots } = await supabase
.from('generations')
.select('*')
.is('parent_id', null);For deep tree traversal, consider using Postgres recursive CTEs via .rpc() for performance.
DB クエリロジックは lib/queries/ に集約。Supabase JS クライアントで表現できない複雑なクエリは PostgreSQL RPC 関数で実装:
lib/queries/tree.ts— ツリー構造取得 (get_tree_structureRPC) +buildTreeFromFlatData純粋関数lib/queries/leaves.ts— リーフノード取得 (get_leaf_nodesRPC,NOT EXISTSサブクエリ)lib/queries/lineage.ts— 系譜取得 (get_lineageRPC, 再帰CTE)
RPC ラッパーは SupabaseClient<Database> を引数に取り、Server/Client どちらからも使える設計。
Vitest を使用。設定は vitest.config.ts(@ エイリアス解決のみ)。
テスト構成:
- 純粋関数テスト:
lib/ui/status.test.ts,lib/queries/tree.test.ts— モック不要 - RPC ラッパーテスト:
lib/queries/leaves.test.ts,lib/queries/lineage.test.ts—createMockSupabase()でモック - Server Action テスト:
app/actions/generations.test.ts—vi.mockで外部依存をモック
Supabase モックパターン (lib/test-helpers.ts):
import { createMockSupabase } from '@/lib/test-helpers';
const supabase = createMockSupabase({
get_leaf_nodes: { data: [...], error: null },
});- Next.js 15: Uses App Router exclusively (no Pages Router)
- React 19: Uses latest React features
- TypeScript strict mode: All code must be type-safe
- Image optimization: next.config.ts allows remote image patterns for AI-generated images
- RLS is enabled: Currently permissive (all operations allowed) for development. Tighten policies before production deployment.
The intended game loop is:
- User views an image (without seeing its parent or prompt history)
- User enters a text description of what they see
- System creates a
pendinggeneration record with the prompt - User is redirected to generating page which polls for completion
- System calls Gemini API to generate a new image in background
- System saves image to local filesystem and updates record to
completed - User is redirected to result page showing the transformation chain
- User can now view the historical chain leading to their contribution
Status field usage:
pending: Generation request created but image not yet generatedcompleted: Image successfully generated and storedfailed: Image generation failed (store error details separately if needed)
- Gallery (
/gallery,/gallery/all): ランダム3枚表示 + 全件一覧 - Image Detail (
/gallery/[id]): 画像詳細、系譜タイムライン、子画像グリッド、プロンプト入力 - Play Mode (
/play/[id]): ブラインドモード(親画像を見ずにプロンプト入力) - Generation Flow: pending → generating ページ(2秒ポーリング) → result ページ
- Tree View (
/tree): ツリー構造の可視化