# アーキテクチャガイド

## 1. 3層ディレクトリモデル

コードの配置先を **routes / features / lib** の3層で判断する。

```text
src/
├── routes/      # ルート定義。薄く保つ
├── features/    # 機能スコープのコード（1つの機能ドメインに閉じるもの）
└── lib/         # 真に共通のコード（2つ以上の機能から使われるもの）
```

### 判定基準

```text
このコードはどの機能ドメインで使われるか？
│
├── 1つの機能ドメインに属する
│   → features/{feature}/ に配置
│
└── 複数の機能ドメインで使う
    → lib/ に配置
```

`routes/` にはルーティングファイル（`+page.svelte`, `+page.server.ts`, `+layout.svelte` 等）のみ置く。

**例:**

- `WorkBookForm.svelte` → workbooks ドメイン → `features/workbooks/`
- `TaskTable.svelte` → tasks ドメイン（problems ページで表示） → `features/tasks/`
- `GradeLabel.svelte` → problems・workbooks・admin で使う → `lib/components/`
- `UserService` → auth・admin・users で使う → `lib/server/services/`

---

## 2. src/features/ の構造

feature ディレクトリの内部は技術スタックベースで整理する。`components/` はページ単位でサブディレクトリに分割し、テストはファイル隣接で配置する。

```text
src/features/
├── workbooks/
│   ├── components/
│   │   ├── list/                    # 一覧ページ
│   │   │   ├── WorkBookList.svelte
│   │   │   ├── WorkBookList.test.ts
│   │   │   └── WorkBookBaseTable.svelte
│   │   ├── detail/                  # 詳細ページ
│   │   │   ├── CommentAndHint.svelte
│   │   │   └── PublicationStatusLabel.svelte
│   │   └── shared/                  # feature 内で複数ページから使うもの
│   │       ├── WorkBookForm.svelte
│   │       ├── WorkBookForm.test.ts
│   │       └── WorkBookInputFields.svelte
│   ├── fixtures/                    # テスト用データ
│   ├── services/
│   │   ├── workbooks.ts
│   │   ├── workbooks.test.ts
│   │   ├── workbook_placements.ts   # WorkBookPlacement の取得・更新・新規の問題集を追加
│   │   └── workbook_placements.test.ts
│   ├── stores/
│   │   └── active_workbook_tab.ts
│   ├── types/
│   │   └── workbook.ts
│   └── utils/
│       ├── workbook.ts
│       └── workbook.test.ts
├── tasks/
│   ├── components/
│   │   ├── contest-table/           # コンテスト別
│   │   ├── grade-list/              # グレード別
│   │   ├── detail/
│   │   │   ├── statistics/          # AtCoder Problems の difficulty、JOI 難易度、AC人数 など
│   │   │   └── comments/            # (要確認) 管理者: 解説、一般ユーザ: コメント
│   │   └── shared/
├── votes/                           # AtCoder NoviSteps における難易度、タグを投票
├── admin/
│   ├── components/
│   │   ├── account-transfer/
│   │   ├── tasks-for-import/
│   │   └── shared/
│   ...
├── auth/
└── user-profile/
```

**components/ のサブディレクトリ規約:**

- `list/` — 一覧ページ用コンポーネント
- `detail/` — 詳細ページ用コンポーネント
- `shared/` — feature 内で複数ページから使うコンポーネント

**`server/` サブディレクトリ規約:**

feature 内のサーバ専用・非サービスコード(リクエストスコープのハンドル、cookie 書き込み、
crypto ユーティリティなど、`services/` の「純粋な値/`null` を返す framework 非依存」規約に
収まらないもの)は `features/{feature}/server/` に置く(`votes/server/`・`workbooks/server/`・
`auth/server/` が前例)。

- `$lib/server/` を出るため SvelteKit のクライアント import 禁止(ビルド時エラー)は**効かない**。
  保護は規約ベース + 推移的依存(`$lib/server/database` の import や `node:crypto` 依存)に委ねる
- 2つ以上の feature で使うサーバコードは `src/lib/server/` に昇格させる(例: `database.ts`)

### ルート固有の `_types/` / `_utils/` ディレクトリ

SvelteKit のルートディレクトリ内で、そのページ専用の型やユーティリティを colocate するために `_types/` と `_utils/` を使う。アンダースコアプレフィックスにより SvelteKit のルーティング対象外となる。

```text
src/routes/(admin)/workbooks/order/
├── +page.svelte
├── +page.server.ts
├── +server.ts
├── _components/        # ページ専用コンポーネント
│   ├── KanbanBoard.svelte
│   └── ...
├── _types/             # ページ専用の型定義
│   └── kanban.ts
└── _utils/             # ページ専用のユーティリティ（純粋関数）
    ├── kanban.ts
    └── kanban.test.ts  # ユーティリティにはテストを隣接配置
```

**ルール:**

- `_types/` には型定義のみ置く。ランタイムコードは含めない
- `_utils/` には純粋関数のみ置く。副作用やサーバー専用コードは含めない
- `_utils/` の関数には単体テストを隣接配置する
- 他のルートから参照する場合は `features/` または `lib/` に昇格させる

### Feature 分割案

現在の `src/lib/` からの抽出候補:

| Feature          | 抽出対象                                                                                                                             |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **tasks**        | `TaskGrades/*`, `stores/active_problem_list_tab.svelte.ts`（※ `TaskGradeList`, `TaskList` 等は複数ドメインで使うため `lib/` に残す） |
| **admin**        | `TagForm`, `TagListForEdit`, `TaskForm`, `TaskListForEdit`, `services/tags.ts`, `services/task_tags.ts`                              |
| **auth**         | `AuthForm`, `utils/auth_forms.ts`, `types/auth_forms.ts`                                                                             |
| **user-profile** | `UserProfile`, `UserAccountDeletionForm`                                                                                             |

### Feature 間の依存ルール

- `features/` → `lib/` の参照: OK
- `features/A/` → `features/B/` の参照: NG（共通化が必要なら `lib/` に移す）
- `routes/` → `features/` の参照: OK
- `routes/` → `lib/` の参照: OK

---

## 3. src/lib/ に残すもの

2つ以上の feature から使われるコードのみ配置する。

```text
src/lib/
├── actions/        # SvelteKit アクション
├── clients/        # 外部 API クライアント（AtCoder Problems, AOJ）
├── components/     # 共通 UI コンポーネント（GradeLabel, TaskGradeList, TaskList, FormWrapper 等）
├── constants/      # アプリ定数
├── server/         # サーバー専用の共有インフラ
│   ├── database.ts # Prisma クライアント（14+ サービスが依存）
│   ├── tasks/      # cache.ts など複数 feature 共有のサーバ処理
│   └── services/   # 複数 feature で使うビジネスロジック
├── stores/         # 共通ストア（error_message 等）
├── types/          # 共通型定義
├── utils/          # 共通ユーティリティ
└── zod/            # バリデーションスキーマ
```

`lib/server/` 内のコードはクライアントからインポートできない（SvelteKit が自動でビルド時エラーにする）。

---

## 4. テストコロケーション

### 方針: ファイル隣接配置

テストは対象ファイルの隣に置く。変更時にテストが目に入り、更新忘れを防ぐ。

```text
features/workbooks/services/
├── workbooks.ts
└── workbooks.test.ts       ← 隣接
```

### 共通テスト資産は src/test/ に残す

- テストファクトリ（`@quramy/prisma-fabbrica`）
- テストヘルパー・フィクスチャ
- E2E テスト（`e2e/`）

### Vitest 設定

`vite.config.ts` の `include` パターンを更新する:

```ts
include: [
  'src/test/**/*.test.ts', // 既存テスト（段階移行）
  'src/features/**/*.test.ts', // feature 内テスト
];
```

---

## 5. パスエイリアス

`svelte.config.js` でカスタムエイリアスを設定する:

```js
kit: {
  alias: {
    $lib:      './src/lib',
    $features: './src/features',
  },
}
```

```ts
// インポート例
import WorkBookForm from '$features/workbooks/components/WorkBookForm.svelte';
import { formatDate } from '$lib/utils/format';
import type { PageServerLoad } from './$types'; // フレームワーク型は ./$types から
```

`$types` は SvelteKit が `.svelte-kit/types/` に自動生成するフレームワーク型（`PageServerLoad` 等）。ビジネスドメイン型は `$lib/types/` に置く。

---

## 6. 参考資料

### SvelteKit 公式

- [プロジェクト構造](https://svelte.dev/docs/kit/project-structure)
- [ルーティング](https://svelte.dev/docs/kit/routing)
- [高度なルーティング](https://svelte.dev/docs/kit/advanced-routing)
- [サーバー専用モジュール](https://svelte.dev/docs/kit/server-only-modules)

### 設計の参考

- [Next.js App Router アーキテクチャ設計ガイド](https://zenn.dev/yukionishi/articles/cd79e39ea6c172) — `src/features/` パターン、ESLint によるアーキテクチャ境界の自動強制
- [サンプルリポジトリ](https://github.com/YukiOnishi1129/next-app-router-architecture) — 上記記事の実装例
- [Immich](https://github.com/immich-app/immich/tree/main/web/src) — 大規模 SvelteKit アプリの実例（`lib/components/{domain}/` パターン）

### 関連 Issue

- [#601: ディレクトリ構造＆コロケーション](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/601)
