このファイルは AI Agents がこのリポジトリで作業する際の具体的なガイダンスを提供します。
ユーザーとのコミュニケーションやコミットメッセージ、コメント、ログ、ドキュメントはすべて日本語で記述してください。
機能や実装について少しでも不明点があれば必ず質問してください。
実装後の必須作業として、以下のコマンドを実行してください。
npx tsc --noEmit && pnpm run lint:fix型エラーやリントエラーが出た場合は、コミット前に必ず修正してください。
適切な粒度でコミットを行ってください。コミットメッセージはprefix: messageの形式で記述してください。1行で完結するようにしてください。
ユーザーとの会話で新しくプロジェクト全体に共通するルールが指示された場合は、まずAGENTS.mdを更新してください。
ドキュメントを追加するよう指示があった場合はdocs以下にMarkdownファイルを作成して記述してください。
プレイデータがIndexedDBに保存されるローカル版と、プレイデータがサーバーに保存されるオンライン版があります。ローカル版はブラウザ内で完結するオフライン運用と、ログインして利用するオンライン運用の両方に対応しており、現場での迅速なスコアリングから大規模なイベントでの公開・観戦まで幅広い用途をサポートします。オンライン版はGoogleアカウントでログインして利用します。
オンライン機能のURLは/online/...で統一してください。オンライン機能の実装にあたり、ローカル版の実装にデグレードが発生しないようにしてください。
ローカル版の実装は一切変更しないでください。
Score Watcher は競技クイズのスコア可視化Webアプリケーションです。Next.jsのApp Routerアーキテクチャを使用しています。
Next.js v15を使用し、TypeScriptで記述します。App Routerを使用し、なるべくサーバーコンポーネントで実装してください。ユーザーとのインタラクションが必要な部分に限りuse clientの使用を許可しますが、その領域は最低限にしてください。
データ取得とuseEffectについて:
- 初期データ取得: page.tsxでサーバーコンポーネントとして実装し、propsでコンポーネントに渡してください
- useEffect: データ取得には使用しないでください。ブラウザAPIアクセスやイベントリスナー登録など、真に必要な場合のみ使用を許可します
- API Routes経由でのデータアクセス: コンポーネントから
repositories以下の関数を直接呼び出さないでください。必ずAPI Routes経由で取得してください - 型アサーションの使用禁止: 安易に型アサーションを使用しないでください。APIレスポンスは型を信頼し、そのまま受け入れてください
オンライン機能はServer Actionsへ移行予定です。 現在は以下の実装が併存しています:
- API Routes (
/api/...) + Honoクライアント(既存実装) - Server Actions (
src/actions/...)(新規実装、推奨)
ローカル機能については引き続きAPI Routesを使用してください。
Reactのガイドも参考にしてください: https://react.dev/learn/you-might-not-need-an-effect
UIコンポーネントライブラリの一つであるMantineを使用します。新しいUIを実装する際はまずMantineのコンポーネントの使用を検討してください。
デザインのカスタマイズはCSS Modulesを使用してください。Tailwind CSSは使用禁止とします。クラス名はkebab-caseで命名してください。
ユーザーのデータはIndexedDBに保存しています。データを操作する場合はDexie.jsで生成したクライアントがあるsrc/utils/db.tsを使用してください。テーブルは以下のようなものがあります。
users- ユーザー情報games- ゲーム情報players- プレイヤー情報logs- ゲーム操作ログ(元に戻す/やり直し用)quizes- クイズ問題
オフラインでの動作に対応させるため、サービスワーカーを使用しています。
pnpm install # 依存関係のインストール
pnpm run dev # Turbo使用での開発サーバー起動 (localhost:3000)
pnpm run build # プロダクションビルド
pnpm run start # プロダクションサーバー起動pnpm run formatt
pnpm run formatt:fix
pnpm run lint
pnpm run lint:fix
pnpm run stylelint
pnpm run stylelint:fix
pnpm run gen # CSS Modules Kitによる型生成pnpm run db:generate # データベーススキーマ生成
pnpm run db:migrate # データベースマイグレーション
pnpm run db:studio # データベーススキーマ確認
pnpm run db:push # データベーススキーマをデプロイpnpm run playwright # Playwright E2E テスト
pnpm run vitest # Vitest ユニットテストsrc/
├── app/
│ ├── (board)/ # スコアボード表示ページ群
│ ├── (default)/ # メイン管理ページ群
│ ├── _components/ # アプリ全体で使用する共有コンポーネント
│ ├── api/ # API Routes(Honoでルーティング)
│ ├── globals.css # グローバルスタイル
│ └── layout.tsx # ルートレイアウト
├── assets/ # 画像などの静的アセット
├── models/ # Zodスキーマと型定義(機能ごと)
│ ├── game.ts # ゲーム関連
│ ├── player.ts # プレイヤー関連
│ └── user-preference.ts # ユーザー設定関連
├── server/ # サーバーサイド実装
│ ├── controllers/ # APIハンドラー(機能別ディレクトリ)
│ │ ├── game/ # ゲーム関連エンドポイント
│ │ ├── player/ # プレイヤー関連エンドポイント
│ │ ├── quiz/ # クイズ関連エンドポイント
│ │ ├── user/ # ユーザー関連エンドポイント
│ │ └── viewer/ # 観戦者関連エンドポイント
│ ├── repositories/ # データベース操作層
│ ├── utils/ # サーバー専用ユーティリティ
│ └── index.ts # Honoアプリのエントリーポイント
├── utils/
│ ├── auth/ # 認証関連ユーティリティ
│ ├── cache/ # キャッシュ管理
│ ├── computeScore/ # 17種類のゲーム形式の計算ロジック
│ ├── drizzle/ # Drizzle ORM設定
│ ├── hono/ # Honoクライアント設定
│ ├── online/ # オンライン機能関連
│ ├── db.ts # IndexedDB操作・スキーマ管理
│ ├── types.ts # TypeScript型定義
│ ├── functions.ts # 共通ユーティリティ関数
│ ├── rules.ts # ゲームルール定義
│ └── theme.ts # Mantineテーマ設定
├── middleware.ts # Next.js ミドルウェア
└── instrumentation.ts # Next.js 計測設定IndexedDB テーブル(Dexie.js使用):
games- ゲーム情報players- プレイヤー情報logs- ゲーム操作ログ(元に戻す/やり直し用)quizes- クイズ問題
操作場所:
- データベース初期化:
src/utils/db.ts - スキーマバージョニング:
src/utils/db.ts - 型定義:
src/utils/types.ts
各形式のロジックはdocs/rules以下に仕様書があります。
ファイル場所: src/utils/computeScore/
対応ゲーム形式:
- normal, nomx, ny, swedish, backstream, z, aql, linear等
- 各形式は独立したファイルで実装
- 共通インターフェースを使用して統一的に処理
- 原則としてアロー関数を使用してください。
- コンポーネントは原則として
defaultでexportしてください。 - 決して
anyを使用しないでください。 - 型定義には
typeを使用してください。interfaceは禁止です。 - 原則として型アサーションを使用しないでください。使用する場合は明確な理由が必要です。
- パスエイリアスは
@/でsrc/を参照してください。 - CSS Modulesは自動生成されたTypeScript定義を使用してください。
- テストは原則としてVitestを使用してください。
- 関数を定義する際は必ずJSDocを記述してください。また、関数の返り値やAPIの返り値は必ず
as constを使用してください。 - エラーを解消するためにESLintのルールを変更するのは禁止です。
- CSS ModulesはPostCSS + Mantine プリセット使用してください。
- プロパティ順序はStylelintのrecess-orderに従ってください。
- レスポンシブはモバイルファーストで実装してください。
- 命名はkebab-caseで命名してください。
-
新しいコンポーネント作成時:
- 既存コンポーネントのパターンを確認
- 同じディレクトリ内の命名規則に従う
- CSS Modulesファイルも合わせて作成
-
ユーティリティ関数追加時:
src/utils/functions.tsに追加するか検討- 型定義は
src/utils/types.tsで管理
-
ゲームロジック変更時:
src/utils/computeScore/内の対応ファイルを編集- テストファイルも合わせて更新
オンライン機能はServer Actionsへ移行予定です。
- ローカル機能: API Routesで実装してください
- オンライン機能: Server Actionsで実装してください(
src/actions/以下)
- データベースとのやり取りが必要な場合は
src/server以下に新たなエンドポイントを実装してください - API RoutesのルートはすべてHonoで管理します
- エントリーポイントは
src/server/index.tsで管理します - ルートを追加する際は
src/server/index.tsに追加してください - コントローラーの実装は
src/server/controllers/に追加してください - バリデーションには必ず
zValidatorを使用してください(リクエストボディ、クエリパラメータ両方) - バリデーションスキーマは
src/models/で定義し、UpperCamelCaseで命名してください - データベースとのやり取りは
src/utils/cloud-db.tsやrepositories以下で行ってください - APIクライアント: クライアントサイドでは
apiClient、サーバーサイドではcreateApiClientOnServer()を使用してください。生のfetchは使用しないでください
バリデーション実装例:
import { zValidator } from "@hono/zod-validator";
import { GetPlayersQuerySchema } from "@/models/players";
const handler = factory.createHandlers(
zValidator("query", GetPlayersQuerySchema), // クエリパラメータ
zValidator("json", CreatePlayerSchema), // リクエストボディ
async (c) => {
const query = c.req.valid("query");
const body = c.req.valid("json");
// 処理...
}
);Controllers構成ルール:
src/server/controllers/以下のハンドラーは1ファイルにつき1個とします- 機能ごとにディレクトリを分割してください(
game/,player/,user/,auth/など) - ファイル名はメソッドタイプ-機能名の形式で命名してください
- 例:
game/get-list.ts,game/post-create.ts,game/get-detail.ts,game/patch-update.ts - 例:
player/get-list.ts,player/post-create.ts - 例:
user/get-preferences.ts,user/update-preferences.ts
- 例:
- 各ファイルでは
default exportでハンドラーをエクスポートしてください src/server/index.tsでimportして使用してください- 必ず
factory.createHandlersを使用してください: 既存の実装パターンに合わせて、すべてのハンドラーでcreateFactory()から生成したfactoryのcreateHandlersメソッドを使用してください - 既存ファイルとの統合を優先: 新しい機能を実装する際は、新しいファイルを作成するのではなく、既存の同種ファイル(例:
models/player.ts、repositories/player.ts)に機能を追加してください
オンライン機能はsrc/actions/以下にServer Actionsとして実装してください。
ディレクトリ構成:
src/actions/
├── game/ # ゲーム関連アクション
├── player/ # プレイヤー関連アクション
├── quiz/ # クイズ関連アクション
├── user/ # ユーザー関連アクション
├── viewer/ # 観戦者関連アクション
└── e2e/ # E2Eテスト用アクション
実装ルール:
- 各ファイルの先頭に
"use server"ディレクティブを記述してください - ファイル名はメソッドタイプ-機能名の形式で命名してください(例:
create-game.ts,update-player.ts) - バリデーションには必ずZodスキーマを使用してください(
src/models/で定義) - 認証が必要な場合は
getUser()で認証確認してください - データベースとのやり取りは
repositories以下の関数を使用してください - エラーハンドリングを適切に行い、エラーメッセージは日本語で返してください
- 返り値の型は明示的に定義してください
実装例:
"use server";
import { getUser } from "@/utils/auth/get-user";
import { CreatePlayerSchema } from "@/models/player";
import { createPlayer } from "@/server/repositories/player";
/**
* プレイヤーを作成する
*/
export const createPlayerAction = async (data: unknown) => {
// 認証確認
const user = await getUser();
if (!user) {
return { success: false, error: "認証が必要です" } as const;
}
// バリデーション
const result = CreatePlayerSchema.safeParse(data);
if (!result.success) {
return { success: false, error: "入力データが不正です" } as const;
}
try {
// データベース操作
const player = await createPlayer(user.id, result.data);
return { success: true, data: player } as const;
} catch (error) {
console.error("Failed to create player:", error);
return { success: false, error: "プレイヤーの作成に失敗しました" } as const;
}
};サーバーサイドの型定義とスキーマ定義はsrc/models/で機能ごとに管理してください。
ファイル構成:
src/models/
├── game.ts # ゲーム関連の型定義・スキーマ
├── player.ts # プレイヤー関連の型定義・スキーマ
└── user-preference.ts # ユーザー設定関連の型定義・スキーマ
使用ルール:
- 各機能のZodスキーマは対応するmodelsファイルで定義してください
- スキーマ名はUpperCamelCaseで命名してください(例:
CreateGameSchema,UpdateUserPreferencesSchema) - 各エンドポイントのリクエストを定義するスキーマや型名は、先頭をCRUDの動詞にしたうえで、リクエストのスキーマなのか、レスポンスのスキーマなのかを明示してください(例:
CreateGameRequestSchema,UpdateUserPreferencesResponseType) - TypeScriptの型定義もmodelsファイルで管理してください
- Controllers・Repositoriesからmodelsファイルを
@/models/でimportして使用してください - バリデーションスキーマと型定義を同じファイルで管理することで、保守性を向上させてください
- 新しい機能を追加する際は、対応するmodelsファイルを作成してください
- データ変換関数の禁止: modelsディレクトリには型とスキーマのみ配置してください。レスポンスデータを変換する関数は作成しないでください。APIレスポンスはそのまま受け入れてください
- APIリクエストを行う際は
useTransitionを使用してローディング表示を行ってください。 - ボタンを連打できないように
disabledを設定してください。
オンライン機能(Turso DBとの連携)を実装する際の標準パターン:
- サーバーサイド データ取得パターン:
const OnlinePlayerPage = async () => {
const user = await getUser();
if (!user) {
redirect("/sign-in");
}
const apiClient = await createApiClientOnServer();
let initialData: ApiDataType[] = [];
try {
const response = await apiClient.endpoint.$get({ query: {} });
if (response.ok) {
const data = await response.json();
initialData = data.data.items || [];
}
} catch (error) {
console.error("Failed to fetch initial data:", error);
}
return <OnlineComponent initialData={initialData} />;
};- クライアント側API通信パターン:
// フロントエンド側ではapiClientを使用
import apiClient from "@/utils/hono/browser";
const response = await apiClient.endpoint.$get({ query: {} });
const data = await response.json();- 型定義パターン:
// APIレスポンス専用の型を定義(createdAt/updatedAtはstring)
export type ApiDataType = {
id: string;
name: string;
createdAt?: string; // API responseは常にstring
updatedAt?: string;
};プロジェクト全体に影響する新しいルールや設定が決まった場合:
- このファイルの該当セクションに具体的に記載
- 抽象的表現は避け、実行可能な形で記述
- 将来のセッションで再現可能な内容にする