From 08c410c28eeb3d7a4c41014d8926b765441546c4 Mon Sep 17 00:00:00 2001 From: yyamashita Date: Sat, 9 May 2026 11:12:46 +0900 Subject: Add Claude Code guidance to CLAUDE.md Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) (limited to 'CLAUDE.md') diff --git a/CLAUDE.md b/CLAUDE.md index 39fde4e..2468a45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,7 @@ # CLAUDE.md +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + ## 概要 バンドとアーティストの情報管理サイト。React Router v7 (SSR) + SQLite + Tailwind CSS v4。 @@ -14,17 +16,21 @@ npm run typecheck # react-router typegen + tsc ## 重要ファイル -- `app/lib/db.server.ts` — DB 接続・スキーマ初期化・全 CRUD 関数 +- `app/lib/db.server.ts` — DB 接続・スキーマ初期化・全 CRUD 関数 (すべての SQL はここに集約) +- `app/lib/constants.ts` — `LINK_TYPES` / `ARTIST_ROLES` など `as const` 定数・型 - `app/routes.ts` — ルート定義 -- `app/root.tsx` — ルートレイアウト (nav バー含む) +- `app/root.tsx` — ルートレイアウト (nav バー・グローバルエラーバウンダリ含む) - `app/routes/` — 各ページのルートファイル ## DB スキーマ SQLite (`whois.db`)。テーブル: `bands`, `band_links`, `artists`, `artist_links`, `band_artists`, `band_revisions`, `artist_revisions`。 -- バンドとアーティストは N:M 関係 (`band_artists` 中間テーブル) -- 編集のたびに `*_revisions` テーブルに JSON スナップショット + 更新メッセージ + IP を記録 +- バンドとアーティストは N:M 関係 (`band_artists` 中間テーブル、`order_index` で順序管理) +- `band_artists.role` はカンマ区切り文字列 (`"Vocal, Guitar"`) で複数ロールを保持。ロード時に `.split(", ").filter(Boolean)` で配列へ戻す +- 編集のたびに `*_revisions` テーブルへ完全な状態の JSON スナップショット + 更新メッセージ + IP を記録 (差分ではなく全量) +- 全 PK は `crypto.randomUUID()` で生成した UUID (TEXT 型) +- `foreign_keys = ON` / `journal_mode = WAL` を常に設定 ## ルートファイルの命名 @@ -39,13 +45,48 @@ SQLite (`whois.db`)。テーブル: `bands`, `band_links`, `artists`, `artist_li | `band-history.tsx` | `/bands/of/:uuid/history` | | artist 系も同様 | `/artists/...` | +slug-only ルート (`band-by-slug.tsx` 等) は `loader` だけを持ち、コンポーネントは `export default function() { return null; }` のみ。 + ## コーディング規約 - `loader` / `action` のみ `~/lib/db.server` をインポートする (クライアントバンドルに含めないため) -- フォームの動的フィールド (リンク・メンバー) は React `useState` で管理し、hidden input に JSON シリアライズして送信 -- slug は自動生成 (バンド名/アーティスト名から) かつ手動上書き可能 +- フォームの動的フィールド (リンク・メンバー) は React `useState` で管理し、hidden input に JSON シリアライズして送信。サーバー側は `JSON.parse((fd.get("field") as string) || "[]")` でデシリアライズ +- slug は自動生成 (バンド名/アーティスト名から `toSlug()` 関数) かつ手動上書き可能。日本語などの Unicode 文字を保持するため正規表現で ASCII 以外も許容 - IP アドレスは `x-forwarded-for` → `x-real-ip` の順で取得 (nginx リバースプロキシ環境) +### action のエラーハンドリングパターン + +```ts +// バリデーション失敗 → エラーオブジェクトを return (リダイレクトしない) +const errors: Record = {}; +if (!name) errors.name = "必須です"; +if (Object.keys(errors).length > 0) return { errors }; + +// UNIQUE 制約違反はキャッチして専用エラーを返す +try { + createBand(...); +} catch (e) { + if (e instanceof Error && e.message.includes("UNIQUE constraint failed")) { + return { errors: { slug: "既に使われています" } }; + } + throw e; +} + +// 成功時は UUID ルートへリダイレクト +return redirect(`/bands/of/${id}`); +``` + +コンポーネントは `useActionData()` でエラーを受け取り、各フィールド直下に表示する。 + +### 404 の throw パターン + +```ts +import { data } from "react-router"; +if (!band) throw data("Not found", { status: 404 }); +``` + +グローバルエラーバウンダリ (`root.tsx`) が `isRouteErrorResponse(error)` で処理する。 + ## デプロイ ```bash -- cgit v1.2.3