# 指示書N — 対応機種レジストリと未対応機種の表示（現状は PUDU CC1 のみ）

| 項目 | 内容 |
|---|---|
| 実行環境 | ★**Cursor 内の Claude が実装します**★（`.claude/commands/d-watch.md` の役割分担表を参照） |
| 実行状態 | ★**完了**★（2026-09-09。D-087。段階0〜6すべて実施。`docs/decisions.md` D-087 / `docs/handoff.md` 追記23 参照） |
| 実行順 | ★**指示書M（D-086）の後**★。★指示書M が作った `scripts/seed-local-db.ts` / `lib/db/index.ts` の `prismaSource.facilities()` を**変更せずに**、その入力である fixtures と、出力を表示する画面だけを触る作業★です。★**ファイルが重なる指示書が1本あります**★ → ★**指示書K（未実施）が `lib/fixtures/clean-logs.ts` を触ります**★（実測。指示書K 内で5回参照）。★**指示書K と同時に走らせないこと**★。★指示書I（`lib/config/allowed-ips.ts` を**新規**で作る。`index.ts` には**足さない**と明記されている ─ 指示書I `:189-193`）／指示書J（`lib/settings/`）とは**重なりません**★。★ただし `docs/decisions.md` / `docs/handoff.md` は3本とも触ります★ |
| 取得する決定番号 | ★**着手直前に `grep -n "^### D-0" docs/decisions.md` で最終番号を確認すること**★。★作成時点の最終は `D-086`（`docs/decisions.md:2812`）なので、最初の空き番号は `D-087` の見込み★。★**2026-09-07 に `D-068` の二重採番が実際に起きています。必ず自分で確認してください**★ |
| 前提 | ★**D-077**★（エラー種別を変換しない＝★未知の値を潰さない★。本指示書の思想的な土台） / ★**D-085**★（`maker_id` の採番。`1`=PUDU / `2`=Keenon） / ★**D-086**★（ローカルDB第1歩。シードは fixtures から作られる） / **D-050**（通ってからコミット） / **D-044**（fixtures で開発する） / **D-043**（実在の住所・氏名を使わない） |
| 段階数 | ★**7段階**★（段階0〜段階6） |
| 着手前の基準 | `npm test` が ★**28ファイル / 572テスト 全パス**★、`npx tsc --noEmit` エラー0、`npm run lint` 0（★2026-09-09 実測。Vitest 4.1.11 / Node v24.14.0★）。**着手直前に自分で実行して実測値を基準にすること** |
| 作成 | 2026-09-09（作成時点の決定事項最終番号 **D-086** / `docs/handoff.md` の最新は **追記22**） |

> 実行後はこの表と、文書末尾の「実行状態」表を更新してください。

> ★**この指示書を読んでいるあなたへ**★
> `.claude/commands/d-watch.md` に「実装は Cursor 側、Claude Code は指示書作成まで」とありますが、
> これは**2つの環境の役割分担**です。**Cursor の中で動いている Claude は実装担当**です。
> この指示書を渡されたなら、**調査や改訂ではなく実装を行ってください。**

---

## ★★着手前にユーザーへ確認すること — ★なし★★★

★**2件あった論点はいずれも 2026-09-09 にユーザーから回答を得ています。**★
★段階0 からそのまま進めてください。★

| 論点 | ★決定★ |
|---|---|
| ★レジストリに無い機種が実データから来たらどうするか★ | ★**出す＋印を付ける**★。★**画面から消さない**★ |
| ★fixtures にある Keenon T5（境界ケース）をどうするか★ | ★**テストにだけ残す**★。★fixtures（＝画面に出るもの）からは外す★ |

---

## この指示書の位置づけ

### ★発端 — ユーザーからの新しい情報（2026-09-09）★

```
pudu製品は現状CC1のみ、将来的にMT1も表示するかもしれません
keenon製品はC30とC40を将来的にやる可能性があります
なので現状はCC1のみ表示されるように、他の3機種は追加が必要になったときに迅速に追加できるように土台だけ置いておいてください
```

### ★何が問題なのか★

| # | 事実 | 現物 |
|---|---|---|
| 1 | ★**`CLAUDE.md` §1 の P1 スコープは「ロボットメーカー: PUDU Roboticsのみ（KeenonはP2以降）」**★ | `CLAUDE.md` §1 |
| 2 | ★**なのに fixtures には Keenon T5 が1台、PUDU Phantas が1台いる**★（実測。CC1 は12台） | `lib/fixtures/facilities.ts:81` / `:99` |
| 3 | ★**機種（model）の対応表はどこにも存在しない**★（実測）。`devices.model_name` の文字列をそのまま画面に出しているだけ | `components/features/facilities/FacilitiesScreen.tsx:146` |
| 4 | ★**メーカー（maker）の対応表はある**★ | `lib/config/index.ts:95` の `MAKER_NAMES` |

★**つまり「対応している機種はどれか」という情報が、コードのどこにも書かれていません。**★
★将来 MT1 / C30 / C40 を足すとき、どこを触ればいいのかが分からない状態です。★

### ★★なぜ「未対応機種を隠す（フィルタする）」を選ばなかったのか★★

★**ユーザーの決定は「出す＋印を付ける」です。理由を必ず理解してから実装してください。**★

| 案 | 実データに MT1 が現れたときに起きること |
|---|---|
| ★**隠す（フィルタする）**★ | ★**そのロボットが画面から消える**★。★**例外もテスト失敗も出ない**★。★誰も気づかないまま「そんなロボットは無い」ことになる★ |
| ★**出す＋印を付ける（★採用★）**★ | ★画面に「未対応」バッジ付きで出る★。★**見た人が「レジストリに足す必要がある」と気づける**★ |

★**これは指示書K で避けた「静かに壊れる」パターンとまったく同じ構図です。**★
★また `MAKER_NAMES` のコメント（`lib/config/index.ts:91-93`）が既に同じ方針を宣言しています★ —
「★未知の番号は `String(maker_id)` をそのまま返し、"不明" 等に潰さないこと★」。
★**D-077（エラー種別を推測で7分類に変換しない）とも同じ思想です。**★

★**本指示書は「知らないものを黙って消さない」という既存方針を、機種にも適用する作業です。**★

---

## ★調査済みの事実（★現物で裏を取ってから使うこと★）★

★本節はすべて 2026-09-09 の実測です。★ ★**それでも、着手時にもう一度自分で確認してください**★（コードは動きます）。

### 1. 現状の対応表と機種

| 項目 | 実測 |
|---|---|
| ★メーカー対応表★ | ★`lib/config/index.ts:95-98`★ の `MAKER_NAMES: Record<number, string> = { 1: "PUDU", 2: "Keenon" }`。★未知の番号は潰さない方針が `:91-93` のコメントに明記されている★ |
| ★機種（model）の対応表★ | ★**存在しない**★（実測。`grep` で該当なし） |
| ★fixtures の機種★ | ★**CC1 が12台 / PUDU Phantas が1台 / Keenon T5 が1台 = 計14台**★ |
| Phantas の位置 | ★`lib/fixtures/facilities.ts:81`★ — `{ id: 106, maker: "PUDU", model: "Phantas", serial_number: "8222P4527080066" }`（施設5「メガバリューマート港南総本店」の**2台目**） |
| Keenon T5 の位置 | ★`lib/fixtures/facilities.ts:99`★ — `{ id: 109, maker: "Keenon", model: "T5", serial_number: "9333K7412050111" }`（施設7「メガバリューマート緑野店」の**3台目**） |
| fixtures 冒頭のコメント | ★`lib/fixtures/facilities.ts:16`★ に「メーカー混在（PUDU + Keenon。KeenonはP2だが表示が壊れないこと）」／★`:85`★ に「ロボット3台 + メーカー混在（PUDU × 2 + Keenon × 1）」★ → ★**段階3 で実態に合わせて直すこと**★ |
| ★`docs/fixtures.md`★ | ★`:172`★ に「メーカー混在 \| PUDU + Keenon（Keenonは P2 だが表示は壊れないこと）」★ → ★**段階3 で直すこと**★（★`CLAUDE.md` §5 が「`docs/fixtures.md` はサンプルデータの正」と定めているため、実態とズレたまま放置しない★） |

### 2. 画面の描画（★変更対象★）

★`components/features/facilities/FacilitiesScreen.tsx:142-150`★（★ロボット列。作成時点の実測★）:

```tsx
<td className={styles.tdRobots}>
  {facility.robots.map((robot) => (
    <div key={robot.id} className={styles.robotRow}>
      <Pill text={robot.maker} color={c.pr} />
      <span className={styles.robotModel}>{robot.model}</span>
      <span className={styles.robotSerial}>{robot.serial_number}</span>
    </div>
  ))}
</td>
```

| 項目 | 実測 |
|---|---|
| `t` と `theme` | ★`:31` で `const { t, theme } = useTheme();`★ / ★`:32` で `const c = THEME_TOKENS[theme];`★ → ★**新しく import するものは判定関数だけで済む**★ |
| `Pill` | ★`components/ui/Pill.tsx`★。★`{ text: string; color: string }` を受け取り、`background: ${color}22` / `color` を**インラインスタイル**で当てる★ → ★**必ず `THEME_TOKENS[theme]` 由来の値を渡すこと**★ |
| 警告色 | ★`lib/theme.ts` の `wn`★（dark `#f59e0b` / light `#d97706`）。★両テーマに存在することを実測済み★ |
| CSS | ★`components/features/facilities/FacilitiesScreen.module.css:136-156`★。`.robotRow` は `display: flex` / `gap: 6px` → ★**バッジを1つ挟むだけなら CSS の変更は不要**★（★実装後に目で確かめること★） |

### 3. ★既存テストの Keenon 依存（★消してはいけない★）★

| 行 | 内容 |
|---|---|
| ★`lib/facilities/__tests__/query.test.ts:158`★ | `robot({ id: 102, maker: "Keenon", model: "T5", serial_number: "9333K7412050111" })` — ★`filterFacilitiesByQ` の入力データ★ |
| ★`:188`★ | `expect(filterFacilitiesByQ(items, "Keenon").map((f) => f.id)).toEqual([2]);` — ★**メーカーの部分一致テスト**★ |
| ★`:225`★ | `robot({ id: 109, maker: "Keenon", model: "T5", serial_number: "9333K7412050111" })` — ★3台目のシリアルでヒットするかのテスト★ |
| ★`:229`★ | 上の期待値（`"9333K7412050111"` で施設7 がヒットすること） |
| ★`:308` / `:313`★ | ★`filterFacilitiesByFields` 側でも同じシリアルを使っている★（★調査で追加判明。作成依頼には含まれていなかった行★） |

★**これらはすべて `robot({...})` / `row({...})` ヘルパで作る「合成データ」であり、`lib/fixtures/` とは独立しています。**★
★**したがって fixtures から Keenon を外してもこのテストは1件も落ちません。**★
★**つまり「境界ケースはテストにだけ残す」という決定は、ここでは既にほぼ達成されています。**★
★段階4 でやることは「残すことを明示する」ことであって、書き足すことではありません。★

### 4. ★★調査で追加判明した事実（★作成依頼に含まれていなかったもの。読み飛ばさないこと★）★★

#### 4-1. ★`lib/fixtures/clean-logs.ts` にも非CC1 の機種名が入っている★

| 行 | 内容 |
|---|---|
| ★`lib/fixtures/clean-logs.ts:149`★ | `model_name: "Keenon T5"`（施設7・緑野店） |
| ★`lib/fixtures/clean-logs.ts:151`★ | `model_name: "PUDU Phantas"`（施設5・港南総本店） |
| `lib/fixtures/clean-logs.ts:50` | ★既定値は `input.model_name ?? "PUDU CC1"`★ — ★**他の行はすべて `"PUDU CC1"`**★ |

★**`model_name` は清掃詳細ログ（F-02）の画面に出る項目です。**★
★**「現状は CC1 のみ表示されるように」というユーザーの要望を満たすには、ここも直す必要があります。**★
★ただし ★**形が違います**★ — `facilities` の `RobotSummary` は `maker` と `model` が**別項目**なのに対し、
`CleanLogItem.model_name` は ★**「PUDU CC1」という1本の文字列**★ です。
★**そのため段階2 の「未対応」バッジは清掃ログ画面には付けません**★（★別項目・別画面。判定関数の入力の形が違う★）。
★段階3 では `model_name` を `"PUDU CC1"` に直すだけにとどめ、★清掃ログ画面へのバッジ表示は「やらないこと」に入れています★。

★**★重要★: `lib/fixtures/clean-logs.ts` は ★指示書K（未実施）も触ります★（実測。指示書K 内で5回参照）。
同時に走らせないこと。★**★

#### 4-2. ★シリアル番号は他の fixtures からも参照されている（★実測。全文検索済み★）★

```
grep -rn "8222P4527080066\|9333K7412050111" lib/ docs/ app/ components/ scripts/
```

| シリアル | 参照箇所 |
|---|---|
| ★`8222P4527080066`★ | `lib/fixtures/facilities.ts:81` / ★`lib/fixtures/clean-logs.ts:151`★ |
| ★`9333K7412050111`★ | `lib/fixtures/facilities.ts:99` / ★`lib/fixtures/clean-logs.ts:149`★ / ★`lib/fixtures/inspections.ts:137`★ / ★`lib/fixtures/schedules.ts:174`★ / `lib/facilities/__tests__/query.test.ts:158, 225, 229, 308, 313` |

★**fixtures 側は合計6箇所。テスト側（合成データ）は5箇所で、こちらは変えません。**★

#### 4-3. ★★シリアルを変え忘れると `npm run seed:local` が止まる（★自動で検知できる★）★★

★`scripts/seed-local-db.ts:116-149` を読んで確認したこと★:

| # | 事実 |
|---|---|
| 1 | シードは ★`facilities` fixtures のロボットから `serial_number → {facilityId, sectionId}` の逆引きを作る★（`:116-126`） |
| 2 | ★`schedules` fixtures のうち、その逆引きに**無いシリアル**の行をスキップする★（`:128-135`） |
| 3 | ★**スキップ件数が1件でなければ例外を投げて中断する**★（`:145-149`） |
| 4 | → ★**`facilities.ts:99` のシリアルだけ変えて `schedules.ts:174` を変え忘れると、スキップが2件になり、シードが必ず止まります**★ |

★**これは「変え忘れを黙って通さない仕組みが既にある」ということです。**★
★段階3 の検算はこれを利用します（★シードを流し直して1件スキップのままであること★）。★

#### 4-4. ★`scripts/seed-local-db.ts` のコード変更は不要（★確認済み★）★

| # | 事実 |
|---|---|
| 1 | ★`collectDevices()`（`:84-96`）が fixtures の `(maker, model)` の組み合わせを**動的に**集めて `devices` の行を作る★ |
| 2 | → ★**fixtures を CC1 のみにすれば `devices` は自動的に1行になります**★ |
| 3 | ★`makerId()`（`:69-75`）は `MAKER_NAMES` の逆引き★ → ★`MAKER_NAMES` から Keenon を消す必要はありません★（★消さないこと。D-085 の採番はそのまま★） |
| 4 | ★**帰結: 段階3 のシード検算の期待値は `d=3` → `d=1` に変わります**★（指示書M `2-9` の期待値 `g=1 / c=1 / d=3 / f=12 / s=14 / r=14 / cs=12` のうち `d` だけ変わる） |

★**それでも段階3 で必ずシードを流し直して実測すること。**★ ★コードを読んだだけで済ませないでください。★

---

## ★禁止事項（この指示書の全段階に効きます）★

| # | 禁止 | 理由 |
|---|---|---|
| 1 | ★★**未対応機種を画面から隠す（フィルタして消す）こと**★★ | ★**ユーザーの決定**★。★実データに MT1 が現れたときロボットが画面から消え、例外もテスト失敗も出ず、誰も気づかない★（「なぜ隠さないのか」の節） |
| 2 | ★**色だけで「未対応」を表すこと**★ | ★色覚特性のある人に伝わらない★／★スクリーンショットの白黒印刷で消える★。★**必ず文字を出すこと**★ |
| 3 | ★**fixtures のロボット台数を減らすこと**★ | ★施設5 の2台構成・施設7 の3台構成は「複数台の表示が崩れないこと」を確かめる境界ケース★。★**削除ではなく置換**★ |
| 4 | ★**`lib/facilities/__tests__/query.test.ts` の Keenon を使ったテストを消すこと**★ | ★これが「境界ケースをテストにだけ残す」の実体★（`:158` / `:188` / `:225` / `:229` / `:308` / `:313`）。★消して通すのは禁止★ |
| 5 | ★**生の色コードを直書きすること**★ | ★`Pill` は `color: string` をインラインスタイルに流し込むだけ★なので、★必ず `THEME_TOKENS[theme]` 由来の値（`c.wn`）を渡す★。★直書きするとテーマ切替で色が固定される★ |
| 6 | ★**`lib/db/` の `prismaSource` に手を付けること**★ | ★別作業（第2歩の指示書）★ |
| 7 | ★**`MAKER_NAMES` の番号を変えること／Keenon を消すこと**★ | ★D-085 の暫定決定★。★実データの `maker_id` 受領後に見直す★。★**「Keenon 製品は将来やる可能性がある」とユーザーが明言している**★ |
| 8 | ★**レジストリに無い機種を3状態目（「未知」など）として別扱いすること**★ | ★ユーザーの決定は「同じ『未対応』バッジ」★。★状態を増やすと運用が複雑になり、見た人が何をすべきか分からなくなる★ |
| 9 | ★**未対応機種の判定を画面（コンポーネント）の中に直書きすること**★ | ★純粋関数1箇所に閉じ込める★（`lib/format/contract.ts` と同じ形。`CLAUDE.md` §9.4 の趣旨） |
| 10 | ★**レジストリの機種をコメントアウトで表現すること**★ | ★段階1 の理由を読むこと★ |
| 11 | ★**清掃ログ画面（F-02）に「未対応」バッジを出すこと**★ | ★`CleanLogItem.model_name` は「PUDU CC1」という**1本の文字列**で、判定関数の入力の形が違う★（4-1）。★本指示書の範囲外★ |
| 12 | ★**機種ごとの挙動差（清掃面積の扱い・エラーコードの意味など）を実装すること**★ | ★**仕様がありません**★。`CLAUDE.md` §7「仕様が不明な箇所を推測で実装すること」の禁止 |
| 13 | ★**段階5 で DB に入れた検証用データを消さずに終わること**★ | ★次に触る人（あなた自身を含む）が「未対応バッジが出っぱなし」の画面に出くわします★ |
| 14 | ★**`git add -A` を使うこと**★ | 過去に**別作業の途中ファイルを巻き込む事故**が起きています。★**必ずファイルパスを明示**★ |
| 15 | ★**`.env.local` をコミットすること**★ | `CLAUDE.md` §9.3。★`.gitignore:34` の `.env*` で追跡外だが、`git add` でパスを明示すれば入ってしまいます★ |
| 16 | ★**`CLAUDE.md` の §1 以外を触ること**★ | ★段階6 で §1 の P1 スコープ表に追記するのは可★。★**末尾の「This is NOT the Next.js you know」ブロックは絶対に触らないこと**★（`next dev` が書き戻すブロック） |

---

## 作業の順序（★崩さないこと★）

| 段階 | 内容 | コミット |
|---|---|---|
| 段階0 | 前提の確認（★読むだけ★） | **コミットなし** |
| 段階1 | 対応機種レジストリと判定関数を追加する（★テストが先★） | 段階1で1コミット |
| 段階2 | 画面に「未対応」の印を付ける | 段階2で1コミット |
| 段階3 | fixtures を CC1 のみにする | 段階3で1コミット |
| 段階4 | 境界ケースをテスト側で担保する | 段階4で1コミット |
| 段階5 | ★**動作確認**★ | コミットなし |
| 段階6 | 記録と push | 段階6で1コミット + push |

★**段階ごとに `npx tsc --noEmit` / `npm run lint` / `npm test` を実行し、通ってからコミットすること**★（D-050。`docs/decisions.md:308`）。

---

## 段階0 — 前提の確認（★読むだけ・コミットなし★）

### 読むもの

| 資料 | 何を読むか |
|---|---|
| ★`lib/config/index.ts:85-98`★ | ★`MAKER_NAMES` と、その上のコメント★。★**「未知の番号は `String(maker_id)` をそのまま返し、"不明" 等に潰さないこと」という既存方針**★。★本指示書はこれを機種にも広げる作業です★ |
| ★`lib/config/index.ts:100-109`★ | `CONTRACT_DIGITS`。★**「設定値は `lib/config/`、それを使う純粋関数は `lib/format/`」という既存の分け方の実例**★ |
| ★`lib/format/contract.ts` 全文★ | ★**段階1 で作るものの雛形**★。★設定値を `lib/config` から読み、桁位置を直書きせず、例外を投げず、テストで固定している★ |
| ★`lib/fixtures/facilities.ts:1-20`★ | 冒頭のコメント（★網羅ケースの一覧。段階3 で直す★） |
| ★`components/features/facilities/FacilitiesScreen.tsx:30-32` / `:142-150`★ | ★`t` / `theme` / `c` の取り方★と★ロボット列の描画★ |
| ★`components/ui/Pill.tsx` 全文（17行）★ | ★`color` をインラインスタイルに流すだけ★ → ★`THEME_TOKENS` 由来の値を渡す必要がある理由★ |
| ★`lib/i18n.ts:114-133`★ | ★施設ロボットマスタ（F-08）の対訳ブロック★。★**キーは日本語原文。完全一致でしか引けない**★ |
| ★`lib/facilities/__tests__/query.test.ts:143-231` / `:290-320`★ | ★Keenon を使っているテスト（消さないもの）★ |
| ★`scripts/seed-local-db.ts:84-96` / `:116-149`★ | ★`collectDevices()` と、スキップ件数1件のガード★（4-3 / 4-4） |
| `docs/decisions.md` の **D-077** | ★エラー種別を変換しない＝未知の値を潰さない★。★**本指示書と同じ思想**★ |
| `docs/decisions.md` の **D-085** / **D-086** | `maker_id` の採番 / ローカルDB第1歩 |
| `CLAUDE.md` §1 / §7 / §9.4 / §9.6 | P1スコープ（★PUDU のみ★） / 禁止事項 / テストを先に書く / フロント |

### 段階0の完了条件

- 上の資料を読んだこと
- ★**「なぜ隠さないのか」の節を読み、隠す実装をしない理由を説明できること**★
- `git status` がクリーンであること
- ★**`grep -n "^### D-0" docs/decisions.md | tail -3` で最新の決定番号を確認し、控えたこと**★
- `npm test` / `npx tsc --noEmit` / `npm run lint` を実行し、★**実測値を控えたこと**★
  （作成時点の基準は **28ファイル / 572テスト / tsc 0 / lint 0**）
- ★**コミットしない**★

---

## 段階1 — 対応機種レジストリと判定関数を追加する

### 1-1. 作るもの

| ファイル | 内容 |
|---|---|
| ★変更★ `lib/config/index.ts` | ★`ROBOT_MODELS`（対応機種レジストリ）★。★**`MAKER_NAMES` の隣に置くこと**★ |
| ★新規★ `lib/format/robot-model.ts`（★置き場所は 1-3 で決める★） | ★判定の純粋関数★ |
| ★新規★ `lib/format/__tests__/robot-model.test.ts` | ★期待値表（1-5）★ |

### 1-2. ★★レジストリは `lib/config/index.ts` に置くこと★★

★**`lib/config/allowed-ips.ts` のような独立ファイルにしないでください。**★

| # | 理由 |
|---|---|
| 1 | ★**`MAKER_NAMES` とまったく同じ性質の設定値**★（「DB から来た識別子を、DFA 側の対応表で解釈する」）。★分散させると片方だけ直す事故が起きます★ |
| 2 | ★`lib/config/allowed-ips.ts` を独立させたのは **proxy のバンドルに judge 系を引き込まないため**★（指示書I `:189-193`）。★**こちらは画面（クライアントコンポーネント）から使うもので、`FacilitiesScreen.tsx:7` が既に `@/lib/config` を import しています**★ → ★同じ事情は当てはまりません★ |

### 1-3. ★★推奨する形（★この形で書くこと★）★★

```ts
/**
 * 対応機種レジストリ
 *
 * P1 で画面に出る機種は PUDU CC1 のみ（CLAUDE.md §1「ロボットメーカーは PUDU のみ」）。
 * 将来の候補として PUDU MT1 / Keenon C30 / Keenon C40 がある（2026-09-09 ユーザー情報）。
 *
 * ★対応を開始するときは supported を true にするだけ★
 * ★ここに無い機種も画面からは消さず「未対応」バッジを付けて表示する★（D-0XX）
 */
export const ROBOT_MODELS = [
  { maker: "PUDU",   model: "CC1", supported: true  },
  { maker: "PUDU",   model: "MT1", supported: false },
  { maker: "Keenon", model: "C30", supported: false },
  { maker: "Keenon", model: "C40", supported: false },
] as const;
```

★**なぜコメントアウトではなくフラグにするのか（★コメントに書くこと★）**★:

| # | 理由 |
|---|---|
| 1 | ★**コメントアウトした行は型チェックもテストも通りません**★ → ★有効化したときに初めて綴りの誤り（`MT-1` / `mt1` など）に気づくことになります★ |
| 2 | ★**フラグなら「既知だが未対応」と「完全に未知」を区別できます**★ → ★実データに `MT1` が来たときに「想定内だが未対応」と分かる★ |
| 3 | ★**有効化が `false` → `true` の1文字で済みます**★ → ★ユーザーの「迅速に追加できるように土台だけ置いておいて」という要望そのもの★ |

### 1-4. ★判定の純粋関数★

- ★**置き場所は `lib/format/robot-model.ts` を推奨**★
  （★`lib/format/contract.ts` が「設定値は `lib/config`、それを読む純粋関数は `lib/format`」の前例★）
  ★`lib/config/robot-model.ts` に置いてもよい。★**どちらを選んだかと理由を段階6 で記録すること**★★
- ★**`lib/judge/` には置かないこと**★（★あそこは ◎○△× / L1〜L3 / KPI / 丸めの置き場所★。`CLAUDE.md` §9.1）
- シグネチャの例:

```ts
export function isSupportedModel(maker: string, model: string): boolean
```

- ★**DB アクセスを混ぜないこと。例外を投げないこと。**★（★`lib/format/contract.ts` と同じ作法★）

#### ★★大文字小文字と前後空白の扱いを決めること★★

★**実データの表記ゆれに備えます**★（★指示書K の B2 判定と同じ考え方★ — ★実データが何を返すか分からないので、
比較の前に正規化してから完全一致で判定する★）。

| 項目 | ★推奨★ |
|---|---|
| 前後の空白 | ★`trim()` してから比較する★（`" CC1 "` → 対応済み扱い） |
| 大文字小文字 | ★`toUpperCase()`（または `toLowerCase()`）で揃えてから比較する★（`"cc1"` → 対応済み扱い） |
| 空文字 | ★**`false`**★（★レジストリに空文字は無いので自然にこうなる。テストで固定すること★） |
| 部分一致 | ★**しないこと**★（★`"CC10"` が `"CC1"` にヒットしてはいけない★）。★正規化後の**完全一致**★ |

★**採った方針を関数のコメントに書くこと。**★ ★後任が「なぜ trim しているのか」を迷わないように。★

### 1-5. ★★テストを先に書く（★`CLAUDE.md` §9.4★）★★

★**期待値表を作ってから実装してください。**★ `lib/format/__tests__/` は既存です（`contract.test.ts` / `address.test.ts` / `schedule.test.ts` / `period.test.ts`）。★同じ場所に置くこと★。

| # | 入力（maker, model） | 期待 | 意図 |
|---|---|---|---|
| 1 | `("PUDU", "CC1")` | ★`true`★ | ★唯一の対応機種★ |
| 2 | `("PUDU", "MT1")` | ★`false`★ | ★既知だが未対応★ |
| 3 | `("Keenon", "C30")` | ★`false`★ | 同上 |
| 4 | `("Keenon", "C40")` | ★`false`★ | 同上 |
| 5 | ★`("PUDU", "ZZ9")`★ | ★`false`★ | ★**レジストリに無い組み合わせ（完全に未知）**★ |
| 6 | ★`("Keenon", "CC1")`★ | ★`false`★ | ★**メーカーだけ違う**★（★機種名だけで判定していないことを固定する★） |
| 7 | ★`("PUDU", "T5")`★ | ★`false`★ | ★**メーカーだけ一致して機種が違う**★ |
| 8 | ★`("", "")`★ | ★`false`★ | ★空文字で落ちないこと★ |
| 9 | ★`("PUDU", "")`★ | ★`false`★ | ★片方だけ空★ |
| 10 | ★`(" PUDU ", " CC1 ")`★ | ★`true`★ | ★前後空白を許容する（1-4 の方針）★ |
| 11 | ★`("pudu", "cc1")`★ | ★`true`★ | ★大文字小文字を区別しない（1-4 の方針）★ |
| 12 | ★`("PUDU", "CC10")`★ | ★`false`★ | ★**部分一致していないことの固定**★ |

★**10・11 は 1-4 で別の方針を選んだ場合、期待値もそれに合わせて変えること。**★
★ただし ★**「決めてテストで固定する」ことは必ず行う**★（★決めないまま放置しないこと★）。★

### 段階1の完了条件

- ★**`ROBOT_MODELS` が `lib/config/index.ts` の `MAKER_NAMES` の隣にあること**★
- ★**4機種すべてが行として存在し、`supported` が CC1 だけ `true` であること**★
- ★**コメントアウトされた機種が1つも無いこと**★
- ★**判定関数が純粋関数で、`lib/config` からレジストリを読んでいること**★（★機種名を関数内に直書きしていないこと★）
- ★**期待値表（1-5）の12ケースがテストにあること**★
- `npx tsc --noEmit` エラー0 / `npm run lint` 0 / `npm test` 全パス（★テスト数が増えていること★）
- ★**この段階では画面も fixtures も変わっていないこと**★（`git diff --stat` で確認）
- ★**ここで1コミット**★

---

## 段階2 — 画面に「未対応」の印を付ける

### 2-1. 変えるもの

| ファイル | 内容 |
|---|---|
| ★変更★ `components/features/facilities/FacilitiesScreen.tsx` | ★ロボット列（`:142-150`）★ |
| ★変更★ `lib/i18n.ts` | ★新しい文言の EN 対訳★ |

### 2-2. ★★推奨する見せ方★★

| 項目 | ★どうするか★ |
|---|---|
| メーカーバッジ | ★**そのまま**★（`<Pill text={robot.maker} color={c.pr} />` を変えない） |
| 機種名 | ★**そのまま**★（`<span className={styles.robotModel}>{robot.model}</span>`） |
| ★未対応の印★ | ★**機種名の横に「未対応」の `Pill` を出す**★。★色は `c.wn`（警告色）★ |
| ★対応済みの機種★ | ★**何も出さない**★（★現状の見た目を1ピクセルも変えない★） |
| ★完全に未知の機種★ | ★**同じ「未対応」バッジ**★（★3状態にしないこと。禁止事項8★） |

```tsx
{facility.robots.map((robot) => (
  <div key={robot.id} className={styles.robotRow}>
    <Pill text={robot.maker} color={c.pr} />
    <span className={styles.robotModel}>{robot.model}</span>
    {!isSupportedModel(robot.maker, robot.model) && (
      <Pill text={t("未対応")} color={c.wn} />
    )}
    <span className={styles.robotSerial}>{robot.serial_number}</span>
  </div>
))}
```

★**色だけで区別しないこと（禁止事項2）。必ず「未対応」という文字を出してください。**★
★理由: ★色覚特性のある人に伝わらない★／★スクリーンショットを白黒で印刷すると区別が消える★。★
★運用は「翌朝 DFA 担当が確認する」もので、画面を印刷・共有する場面があります（`CLAUDE.md` §1）。★

★**判定はフロントで再計算しているのではなく、設定値を引いているだけです**★ — ★`CLAUDE.md` §9.6 の
「判定結果（◎○△×、L1〜L3、ソート順）はAPIから受け取る。フロントで再計算しない」に抵触しません★
（★あれは**データに依存する判定**の話。こちらは**静的な対応表の参照**であり、API のレスポンスに
`supported` を足すと `d-watch-api.yaml` の変更が必要になり、範囲が広がります★）。
★**この判断と理由をコンポーネントのコメントに書くこと。**★

### 2-3. ★i18n★

- ★**`lib/i18n.ts` の `EN` に新しい文言を足すこと**★
- ★**キーは日本語原文。`translate()` は完全一致でしか引けません**★（★`lib/i18n.ts:3-6` のコメント★）
- ★置き場所は `:114` の「`// ---- 施設ロボットマスタ（F-08）----`」ブロックの中★
- 例: `未対応: "Unsupported",`
- ★**JP/EN を両方で目視確認すること**★（段階5）

### 2-4. ★CSS★

★`.robotRow` は `display: flex; gap: 6px`（`FacilitiesScreen.module.css:136-140`）なので、
**バッジを1つ挟むだけなら CSS の変更は不要のはず**です。★
★**ただし実装後に必ず目で確かめること**★ — ★施設7 のような3台構成で行が折り返して崩れないか★。
★崩れる場合のみ CSS を触り、★触った理由をコメントに書くこと★。★

### 段階2の完了条件

- ★**未対応機種に「未対応」バッジが出て、対応済み機種には何も出ないこと**★
- ★**バッジの色が `THEME_TOKENS[theme]` 由来（`c.wn`）であること**★（★生の色コードが1つも無いこと★）
- ★**「未対応」という文字が出ていること**★（★色だけで表していないこと★）
- ★**`lib/i18n.ts` に対訳が入っていること**★
- ★**判定が `isSupportedModel()` の呼び出し1箇所で、コンポーネント内に機種名の直書きが無いこと**★
- ★**この段階では fixtures がまだ Keenon / Phantas を持っているので、画面にバッジが2つ出るはず**★
  （★段階3 でそれが0個になります。**先にバッジが出ることを確認しておくこと**★ — ★段階3 の後だと
  「バッジが出ないのは実装が効いていないからかもしれない」と区別がつかなくなります★）
- `npx tsc --noEmit` エラー0 / `npm run lint` 0 / `npm test` 全パス
- ★**ここで1コミット**★

---

## 段階3 — fixtures を CC1 のみにする

### 3-1. 変えるもの

| ファイル | 行 | 内容 |
|---|---|---|
| ★変更★ `lib/fixtures/facilities.ts` | ★`:81`★ | Phantas → ★`model: "CC1"`★ |
| ★変更★ `lib/fixtures/facilities.ts` | ★`:99`★ | Keenon T5 → ★`maker: "PUDU"` / `model: "CC1"`★ |
| ★変更★ `lib/fixtures/facilities.ts` | ★`:13-20` / `:85`★ | ★コメントを実態に合わせる★ |
| ★変更★ `lib/fixtures/clean-logs.ts` | ★`:149` / `:151`★ | ★`model_name` を `"PUDU CC1"` に★（4-1） |
| ★変更（シリアルを変える場合のみ）★ | `lib/fixtures/inspections.ts:137` / `lib/fixtures/schedules.ts:174` | ★3-3★ |
| ★変更★ `docs/fixtures.md` | ★`:172`★ | ★「メーカー混在」の行を実態に合わせる★ |

### 3-2. ★★台数を減らさないこと★★

★**削除ではなく置換です。**★

| # | 理由 |
|---|---|
| 1 | ★施設5（港南総本店）の**2台構成**、施設7（緑野店）の**3台構成**は、「複数台の表示が崩れないこと」を確かめる境界ケース★ |
| 2 | ★`lib/facilities/__tests__/query.test.ts:216-230`「複数台の施設は3台目のシリアルでもヒットする」が、**先頭1台しか見ない実装を検出するためのテスト**★。★画面側にも同じ境界が必要★ |
| 3 | ★指示書M 段階5 の確認1「ロボット3台の施設が3行で出ること」がそのまま使えなくなります★ |

★**結果として、fixtures のロボットは「PUDU CC1 が14台」になります。**★

### 3-3. ★★シリアル番号をどうするか（★決めて、理由を記録すること★）★★

★**まず全文検索の結果（4-2）を自分で再現してください。**★

```bash
grep -rn "8222P4527080066\|9333K7412050111" lib/ docs/ app/ components/ scripts/
```

★**問題**★: `8222P...`（P は Phantas 由来と思われる）と `9333K...`（K は Keenon 由来と思われる）が残ると、
★機種を CC1 に変えたあとに**シリアルと機種が食い違って見えます**★。

★**当方の推奨: 揃える**★ — ★他と同じ `8111R...` 系の連番にする★（例: `8111R5628060166` / `8111R5628060177`。
★**既存のどのシリアルとも重複しない値を選ぶこと**★。★`grep` で重複が無いことを確かめる★）。

| # | 揃える場合にやること |
|---|---|
| 1 | ★**fixtures 側の6箇所すべてを同じコミットで直す**★（`facilities.ts:81, 99` / `clean-logs.ts:149, 151` / `inspections.ts:137` / `schedules.ts:174`） |
| 2 | ★**テスト側（`query.test.ts` の5箇所）は変えない**★ — ★合成データなので独立しています（禁止事項4）★ |
| 3 | ★**変え忘れは `npm run seed:local` が検知します**★（4-3）。★`facilities.ts` と `schedules.ts` がズレるとスキップが2件になり、シードが例外で止まります★ |
| 4 | ★**`grep -rn "8222P4527080066\|9333K7412050111" lib/fixtures/ docs/` が0件になること**★を確認する |

★**揃えない（シリアルを変えない）という判断も許容します。**★
★その場合は ★**`lib/fixtures/facilities.ts` の該当行に「シリアルの英字は旧機種名の名残で、機種とは無関係」
というコメントを必ず書くこと**★（★後任が「シリアルから機種が分かる」と誤解するのを防ぐため★）。

★**どちらを選んだかと理由を、段階6 で必ず記録すること。**★

### 3-4. ★コメントの修正（★忘れやすい★）★

| 場所 | 現状 | どうするか |
|---|---|---|
| ★`lib/fixtures/facilities.ts:16`★ | `- メーカー混在（PUDU + Keenon。KeenonはP2だが表示が壊れないこと）` | ★実態に合わせて書き換える★。★推奨: 「**メーカー・機種は PUDU CC1 のみ（P1 の対象機種。CLAUDE.md §1）。未対応機種の表示は `lib/facilities/__tests__/query.test.ts` の合成データで担保する**」★ |
| ★`lib/fixtures/facilities.ts:85`★ | `// ロボット3台 + メーカー混在（PUDU × 2 + Keenon × 1）` | ★「ロボット3台のケース」に直す★ |
| ★`docs/fixtures.md:172`★ | `| メーカー混在 | PUDU + Keenon（Keenonは P2 だが表示は壊れないこと） |` | ★同様に直す★。★`CLAUDE.md` §5 が「`docs/fixtures.md` はサンプルデータの正」と定めているため、放置しない★ |

★**コメントを直さないと、次に読む人が「Keenon がいるはずなのに見当たらない」と混乱します。**★
★これは指示書M が問題にした「fixtures の食い違い」と同じ種類の負債です。★

### 3-5. ★`scripts/seed-local-db.ts` は変更不要（★ただし必ず実測すること★）★

★コードを読んだ限り変更は不要です（4-4）。★ ★**それでも、シードを流し直して自分の目で確かめてください。**★

```bash
npm run seed:local
```

★**確認すること**★:

| # | 期待 |
|---|---|
| 1 | ★**スキップが1件だけ**★（`skip: schedule id=33 ...`）。★2件以上なら 3-3 のシリアル変更を1箇所やり残しています★ |
| 2 | ★**例外を投げずに完了すること**★ |

```bash
# ★mysql.exe は PATH に無い。フルパスで呼ぶこと★
# ★接続アカウントは `.env.local` の DATABASE_URL に書いてあるものを使う★
#   （★この文書にユーザー名・パスワードを書かないこと★。CLAUDE.md §9.3）
"C:\Program Files\MySQL\MySQL Server 8.4\bin\mysql.exe" -u <ユーザー> -D dwatch_local -e "
  SELECT id, maker_id, model_name FROM devices;"
```

★期待値★: ★**1行だけ**★（`maker_id=1` / `model_name='CC1'`）。
★**指示書M `2-9` の期待値 `d=3` は、本作業で `d=1` に変わります**★（★段階6 で記録すること★）。

### 段階3の完了条件

- ★**`grep -rn "Phantas\|Keenon\|\"T5\"" lib/fixtures/` が0件であること**★
  （★`lib/config/index.ts` の `MAKER_NAMES` の Keenon は**残す**。禁止事項7★）
- ★**施設5 が2台、施設7 が3台のままであること**★（★台数を減らしていないこと★）
- ★**fixtures のロボットが14台のままであること**★
- ★**`lib/fixtures/facilities.ts:13-20` / `:85` と `docs/fixtures.md:172` のコメントが実態に合っていること**★
- ★**シリアルを変えたなら fixtures 側6箇所すべてを変え、`query.test.ts` は変えていないこと**★
- ★**`npm run seed:local` がスキップ1件で完了し、`devices` が1行になること**★
- `npx tsc --noEmit` エラー0 / `npm run lint` 0 / `npm test` 全パス
- ★**ここで1コミット**★

---

## 段階4 — 境界ケースをテスト側で担保する

### 4-1. ★★既存の Keenon テストは「残す」のが仕事★★

★**書き足す必要はありません。消さないことを確認し、そう明示するだけです。**★

| 行 | 内容 | ★やること★ |
|---|---|---|
| `lib/facilities/__tests__/query.test.ts:158` | Keenon T5 の合成ロボット | ★**残す**★ |
| `:188` | メーカーの部分一致テスト | ★**残す**★ |
| `:225` / `:229` | 3台目のシリアルでヒットするテスト | ★**残す**★ |
| `:308` / `:313` | `filterFacilitiesByFields` のシリアル一致テスト | ★**残す**★ |

★**これが「境界ケースはテストにだけ残す」という決定の実体です。**★
★`describe` の冒頭か該当行に、★**「fixtures からは Keenon を外したが、メーカー混在・複数台の
境界ケースはここの合成データで担保している（D-0XX）」というコメントを1つ足すこと**★。
★コメントが無いと、後任が「fixtures に無いメーカーのテストは要らない」と考えて消してしまいます。★

### 4-2. ★新しく足すテスト★

| # | テスト | 必須か |
|---|---|---|
| 1 | ★**未対応機種を持つ施設を合成し、`isSupportedModel()` が `false` を返すこと**★ | ★**必須**★ |
| 2 | ★**fixtures の全ロボットが対応済み機種であること**★ | ★**実装者の判断でよい**★（4-3） |

★1 の置き場所★: ★`lib/format/__tests__/robot-model.test.ts`（段階1 で作ったもの）に足してよい★。
★施設オブジェクトを合成する形にするなら `lib/facilities/__tests__/query.test.ts` の
`row({...})` / `robot({...})` ヘルパを使うこと★（★新しいヘルパを作らない★）。

### 4-3. ★★「fixtures の全ロボットが対応済み」テストを入れるかどうか★★

★**入れても入れなくてもよい。ただし判断と理由を段階6 で記録すること。**★

| | 入れる | 入れない |
|---|---|---|
| ★利点★ | ★fixtures に未対応機種が紛れ込んだ瞬間に落ちる★ | ★将来 fixtures に境界ケースを足したいときに邪魔にならない★ |
| ★欠点★ | ★**将来 fixtures に境界ケースを足したい日に落ちます**★ | ★紛れ込みに気づけない★ |

★**入れる場合の必須条件**★: ★**なぜ落ちたかがテスト名から分かるようにすること**★。

```ts
// ✅ 落ちた人が何をすべきか分かる
it("fixtures のロボットはすべて対応機種（ROBOT_MODELS の supported）である — 意図的に未対応機種を足したい場合はこのテストごと見直すこと", ...)

// ❌ 落ちた人が「バグか、意図した変更か」を判断できない
it("全ロボットが対応機種", ...)
```

### 段階4の完了条件

- ★**`lib/facilities/__tests__/query.test.ts` の Keenon を使ったテストが1件も減っていないこと**★
  （★`git diff` で削除行が無いことを確認する★）
- ★**「なぜここに Keenon が残っているのか」のコメントが入っていること**★
- ★**未対応機種の判定テスト（4-2 の1）が入っていること**★
- `npm test` が ★**全件パス**★。★**既存テストを消して通すのは禁止**★
- `npx tsc --noEmit` エラー0 / `npm run lint` 0
- ★**ここで1コミット**★

---

## 段階5 — ★★動作確認★★

### 5-1. 起動

- ★**`npm run dev` を Bash で起動しないこと。Browser pane の `preview_start` を使う**★
  （★`.claude/launch.json` に `d-watch`（`npm run dev` / port 3000）が既にあります★ — 実測）
- ★**環境変数を変えたら dev サーバを再起動し、反映されていることを確認すること**★

### 5-2. ★確認1 — `USE_FIXTURES=true`（既定）★

| # | 項目 |
|---|---|
| 1 | ★`/facilities` の全ロボットの機種が **`CC1`** であること★ |
| 2 | ★**「未対応」バッジが1つも出ていないこと**★ |
| 3 | ★施設5「メガバリューマート港南総本店」が**2台**であること★ |
| 4 | ★施設7「メガバリューマート緑野店」が**3台**であること★ |
| 5 | ★フッタが `全12施設` であること★ |
| 6 | ★**ダークとライトの両方**★で崩れないこと |
| 7 | ★**JP と EN の両方**★で崩れないこと（★EN で「未対応」が出る場面は無いはずだが、器を確認する★） |
| 8 | ★`/detail-log` の機種列に `Keenon T5` / `PUDU Phantas` が出ていないこと★（段階3 の 4-1 の確認） |

### 5-3. ★★確認2 — `USE_FIXTURES=false` で「未対応」バッジが出ること★★

★**バッジが出ることを一度も確認しないまま終わらせないでください。**★
★段階2 の実装が本当に効いているかは、これでしか確かめられません。★
（★段階2 の完了条件で一度確認しているはずですが、そこでは fixtures 経由でした。
ここでは**実DB経由**で確認します — 実データが来たときと同じ経路です★）

| # | 手順 |
|---|---|
| 1 | ★`.env.local` の `USE_FIXTURES` を `false` にする★（★判定は **`lib/db/use-fixtures.ts:17`** の `process.env.USE_FIXTURES !== "false"`★ — ★**指示書M が書いていた `lib/db/index.ts:48` からは移動しています**★。指示書M 段階5 で Edge Runtime のバンドル問題を回避するため独立ファイルに切り出された。`lib/db/index.ts:56` が re-export しているだけ。★**文字列 `false` 以外はすべて fixtures 扱い**★で、`FALSE` や `0` では切り替わりません） |
| 2 | ★dev サーバを再起動する★ |
| 3 | ★`dwatch_local` に**未対応機種のロボットを1台だけ**手で入れる★（下記 SQL） |
| 4 | ★`/facilities` を開き、★**その1台にだけ「未対応」バッジが出ること**★を確認する★ |
| 5 | ★**入れたデータを削除し、シードを流し直して元に戻す**★（5-4） |
| 6 | ★**`USE_FIXTURES` を `true` に戻す**★ |

```sql
-- ★検証用。段階5 の最後に必ず消すこと★
-- ★日本語を含む SQL を流すときは --default-character-set=utf8mb4 を付けること★
--   （付けないと文字化けでエラーになる）
INSERT INTO devices (id, maker_id, model_name) VALUES (99, 1, 'MT1');
INSERT INTO sections (id, facility_id, section_name) VALUES (999, 1, '検証用エリア');
INSERT INTO robots (id, facility_id, section_id, device_id, serial_number)
  VALUES (999, 1, 999, 99, '9999TEST0000001');
```

```bash
# ★mysql.exe は PATH に無い。フルパスで呼ぶこと★
# ★接続アカウントは `.env.local` の DATABASE_URL に書いてあるものを使う★
#   （★この文書にユーザー名・パスワードを書かないこと★）
"C:\Program Files\MySQL\MySQL Server 8.4\bin\mysql.exe" --default-character-set=utf8mb4 \
  -u <ユーザー> -D dwatch_local -e "<上のSQL>"
```

★**列名と NOT NULL 制約は着手時に自分で確認すること**★（★指示書M「調査済みの事実」5 に一覧があります★）。
★上の SQL は既定値に頼っています。通らなければ足りない列を足してください。★

★期待する見え方★: 施設1「メガバリューマート中央本店」に2台目が現れ、
★`PUDU` バッジ + `MT1` + ★**`未対応` バッジ（警告色）**★ + シリアル★ が並ぶこと。

### 5-4. ★★後始末（★忘れないこと。禁止事項13★）★★

```bash
# ★削除は挿入の逆順（外部キーの依存。指示書M「調査済みの事実」4）★
# robots → sections → devices の順
```

★**削除したあと `npm run seed:local` を流し直して、DB を元の状態に戻すこと。**★
★**そのうえで `USE_FIXTURES` を `true` に戻すこと。**★

★確認★: ★`USE_FIXTURES=false` のまま `/facilities` を開き、★**「未対応」バッジが0個に戻っていること**★を見てから、
`true` に戻す★（★消し忘れをここで検知できます★）。

### 5-5. ★ログの確認★

- ★`read_console_messages` でブラウザのコンソールエラーを確認する★ → ★**0件であること**★
- ★`preview_logs` でサーバ側のエラーを確認する★ → ★**0件であること**★

### 段階5の完了条件

- ★**確認1・確認2 をすべて実施したこと**★
- ★**`USE_FIXTURES=true` でバッジが0個、検証用データを入れた `USE_FIXTURES=false` でバッジが1個出たこと**★
- ★**ダーク/ライト、JP/EN の4通りを目で見たこと**★
- ★**検証用データを削除し、シードを流し直したこと**★
- ★**`USE_FIXTURES` が `true` に戻っていること**★
- ★**`read_console_messages` / `preview_logs` のエラーが0件であること**★
- ★**コミットしない**★

---

## 段階6 — 記録と push

### 6-1. `docs/decisions.md` に新しい決定を追記する

★**着手直前に `grep -n "^### D-0" docs/decisions.md` で最終番号を確認すること**★
（★作成時点の最終は `D-086`（`docs/decisions.md:2812`）なので、最初の空き番号は `D-087` の見込み★）。

**記録すべきもの**:

| # | 内容 |
|---|---|
| 1 | ★**対応機種は現状 PUDU CC1 のみ**★。★将来 PUDU MT1 / Keenon C30 / Keenon C40 の可能性がある★（★2026-09-09 ユーザー情報。原文を引用すること★） |
| 2 | ★★**未登録機種は隠さず「未対応」バッジを付ける**★★。★理由: 隠すと実データで**静かにロボットが画面から消え、例外もテスト失敗も出ない**★。★指示書K で避けた構図と同じ★。★D-077 / `MAKER_NAMES` のコメントと同じ思想★ |
| 3 | ★**`supported` フラグ方式を採った理由**★ — ★コメントアウトだと型チェックもテストも通らず、有効化するまで綴りの誤りに気づけない★／★「既知だが未対応」と「完全に未知」を区別できる★／★有効化が1文字で済む★ |
| 4 | ★**レジストリの置き場所を `lib/config/index.ts`（`MAKER_NAMES` の隣）にした理由**★と、★判定関数の置き場所（`lib/format/` か `lib/config/`）をどちらにしたか★ |
| 5 | ★**大文字小文字・前後空白の扱いをどう決めたか**★（1-4） |
| 6 | ★**fixtures から Keenon / Phantas を外し、CC1 のみにしたこと**★。★台数（14台）は変えていないこと★ |
| 7 | ★**境界ケースは `lib/facilities/__tests__/query.test.ts` の合成データで担保すること**★（★`:158` / `:188` / `:225` / `:229` / `:308` / `:313`★） |
| 8 | ★★**`CLAUDE.md` §1「P1 は PUDU のみ」と fixtures が食い違っていたのを解消したこと**★★ |
| 9 | ★**シリアル番号を変えたか変えなかったか、およびその理由**★（3-3）。★変えた場合は fixtures 側6箇所を同時に直したこと★ |
| 10 | ★**`lib/fixtures/clean-logs.ts:149, 151` の `model_name` も直したこと**★（★調査で追加判明。`"Keenon T5"` / `"PUDU Phantas"` → `"PUDU CC1"`★） |
| 11 | ★**清掃ログ画面（F-02）には「未対応」バッジを出していないこと**★と理由（★`model_name` が「PUDU CC1」という1本の文字列で、判定関数の入力の形が違う★） |
| 12 | ★**「fixtures の全ロボットが対応済み」テストを入れたか入れなかったか**★（4-3） |
| 13 | ★**シードの `devices` が3行 → 1行になったこと**★（★指示書M `2-9` の検算期待値 `d=3` は `d=1` に更新される★） |
| 14 | ★**`MAKER_NAMES` の Keenon は残したこと**★（★D-085 の暫定採番はそのまま。ユーザーが「Keenon 製品は将来やる可能性がある」と明言している★） |
| 15 | ★**API（`d-watch-api.yaml`）は変更していないこと**★と理由（★`supported` はサーバのデータではなく DFA 側の静的な対応表。レスポンスに足すと契約変更になる★） |
| 16 | 実測値（`npm test` のファイル数・テスト数 / `npx tsc --noEmit` / `npm run lint`） |

### 6-2. ★`CLAUDE.md` に追記するかを検討する★

★**§1 の P1 スコープ表に機種の情報を足すのが自然です。**★

| 現状（`CLAUDE.md` §1） | 追記の例 |
|---|---|
| `| ロボットメーカー | PUDU Roboticsのみ（KeenonはP2以降。Whiz iは機能的に劣るため取り扱わない） |` | ★機種の行を1行足す★: 「対応機種 \| **PUDU CC1 のみ**（将来候補: PUDU MT1 / Keenon C30 / C40。`lib/config/index.ts` の `ROBOT_MODELS` が正）」 |

| # | 注意 |
|---|---|
| 1 | ★**§1 以外は触らないこと**★ |
| 2 | ★★**末尾の「This is NOT the Next.js you know」ブロックは絶対に触らないこと**★★（★`next dev` が書き戻すブロック。消しても再生成され、差分が汚れます★） |
| 3 | ★追記するかしないかを判断し、**判断と理由を段階6 の記録に書くこと**★ |

### 6-3. `docs/handoff.md` に追記する

- ★**末尾に追記を1つ追加**★（★作成時点の最新は「**追記22**」（`docs/handoff.md:1860`）。着手時に確認すること★）
- ★**冒頭の「最新の追記」ポインタも更新すること**★
- ★**次にやること**★を書く:
  - ★実データの `maker_id` / `model_name` を受領したら `MAKER_NAMES` と `ROBOT_MODELS` を突き合わせる★（D-085）
  - ★MT1 / C30 / C40 の対応を開始するときは `supported` を `true` にするだけでよいこと★
  - ★清掃ログ画面（F-02）の `model_name` に印を付けるかは未決であること★
  - ★残り10メソッドの `prismaSource` 実装（第2歩）★

### 6-4. コミットと push

★**`git add` は明示的にファイルパスを指定すること。`git add -A` は禁止**★
（**過去に別作業の途中ファイルを巻き込む事故が起きています**）。

```bash
git status                       # 何が変わったか目で確認する
git add docs/decisions.md docs/handoff.md docs/fixtures.md CLAUDE.md
git add "docs/handoff/指示書N_対応機種レジストリと未対応機種の表示.md"
git commit -m "docs: 指示書N完了(D-0XX)を記録する"
git push origin develop
```

- ★**`.env.local` を `git add` しないこと**★（禁止事項15）
- ★**`main` へは push しないこと**★。`CLAUDE.md` §6-5 のとおり
  **main へのマージは PR 経由で、もう1人のレビューを通してから**行います
- **`--force` は使わないこと**
- ★**403 が出たら、無理に回避せずユーザーへ報告してください**★
  （認証情報を画面に出すコマンドを実行しないこと。`git credential fill` は**禁止**）

**push 後に確認すること**:

```bash
git log --oneline -1 origin/develop   # ローカルの HEAD と同じコミットか
git status -sb                        # 1行目に ahead/behind が無いか
```

### 段階6の完了条件

- `docs/decisions.md` に新しい決定（★着手直前に確認した番号★）が追記されていること
- ★**記録すべきもの16項目がすべて書かれていること**★（★特に #2 / #3 / #8 の3件は「なぜこうしたか」の核心★）
- ★**`CLAUDE.md` §1 に追記したか、しない判断をしたかが記録されていること**★
- ★**`CLAUDE.md` の §1 以外に差分が無いこと**★（★`git diff CLAUDE.md` で確認。末尾のブロックが無傷であること★）
- `docs/handoff.md` の**末尾に追記**があり、**冒頭のポインタも更新**されていること
- ★**`origin/develop` へ push 済みで、`git status -sb` に ahead/behind が無いこと**★
- 文書末尾の「実行状態」表が埋まっていること

---

## ★やらないこと（別作業）★

| 対象 | 理由 |
|---|---|
| ★**残り10メソッドの `prismaSource` 実装**★（`cleanLogs` / `prechecks` / `dsDevices` / `dsAlerts` / `inspections` / `inspectionDetail` / `reportKpis` / `facilityPerformance` / `continuousProblems` / `errorBreakdown`） | ★第2歩。別の指示書★（D-086 の申し送り）。★**`lib/db/` には手を付けないこと**★（禁止事項6） |
| ★**`MAKER_NAMES` の番号の変更**★ | ★実データの `maker_id` を受領してから★（D-085）。★Keenon を消さないこと★（禁止事項7） |
| ★**機種ごとの挙動差**★（清掃面積の扱い、エラーコードの意味、機種別の判定閾値など） | ★**仕様がありません**★。`CLAUDE.md` §7「仕様が不明な箇所を推測で実装すること」の禁止 |
| ★**清掃ログ画面（F-02）への「未対応」バッジ**★ | ★`CleanLogItem.model_name` は「PUDU CC1」という**1本の文字列**で、`maker` と `model` が分かれていない★（4-1）。★分解して判定するのは推測になります★。★fixtures の値を直すだけにとどめる★ |
| ★**`d-watch-api.yaml` の変更**★（レスポンスに `supported` を足す等） | ★`supported` はサーバのデータではなく DFA 側の静的な対応表★。★API 契約を変えると TeamWorks 側との調整が発生します★（`CLAUDE.md` §6-1） |
| ★**機種の登録・編集画面**★ | ★マスタの更新は D-Security 側で行い、D-Watch は GET のみ★（`FacilitiesScreen.tsx:15-19` / D-053） |
| ★**`lib/theme-status.ts` の色分け**★ | ★指示書K（未実施）の範囲★ |

---

## 完了条件（全体）

1. ★**着手前確認は「なし」**★（2件とも回答済み。段階0 から進めた）
2. 段階0〜6 をすべて実施したこと（★途中で止めた場合は、どこで止めたかを実行状態表に書くこと★）
3. `npx tsc --noEmit` エラー0 / `npm run lint` 0
4. `npm test` 全パス。★**テスト数が着手前の実測値から増えていること**★（作成時点の基準: 28ファイル / 572テスト）
5. ★**`ROBOT_MODELS` が `lib/config/index.ts` にあり、4機種すべてが行として存在すること**★（★コメントアウト0件★）
6. ★**判定が純粋関数1箇所にあり、画面・fixtures・シードのどこにも機種名の直書きが無いこと**★
7. ★**`grep -rn "Phantas\|Keenon\|\"T5\"" lib/fixtures/` が0件であること**★
8. ★**`grep -rn "Keenon" lib/config/index.ts` が1件（`MAKER_NAMES`）であること**★（★消していないこと★）
9. ★**`lib/facilities/__tests__/query.test.ts` の Keenon を使ったテストが1件も減っていないこと**★
10. ★**fixtures のロボットが14台のままで、施設5 が2台・施設7 が3台であること**★
11. ★**`npm run seed:local` がスキップ1件で完了し、`devices` が1行（`maker_id=1` / `CC1`）になること**★
12. ★**`USE_FIXTURES=true` の `/facilities` で「未対応」バッジが0個であること**★
13. ★**`USE_FIXTURES=false` + 検証用データで「未対応」バッジが1個出ることを実際に見たこと**★
14. ★**検証用データを削除し、シードを流し直したこと**★
15. ★**`USE_FIXTURES` が `true` に戻っていること**★
16. ★**バッジの色が `THEME_TOKENS` 由来で、生の色コードの直書きが無いこと**★
17. ★**「未対応」という文字が出ていること**★（色だけで表していないこと）
18. ★**`lib/i18n.ts` に対訳が入っていること**★
19. ★**`lib/db/` / `d-watch-api.yaml` / `prisma/schema.prisma` に差分が無いこと**★
20. ★**`CLAUDE.md` の差分が §1 だけであること**★（★末尾のブロックが無傷★）
21. `docs/decisions.md` / `docs/handoff.md` / `docs/fixtures.md` の更新
22. ★**`origin/develop` へ push 済み**★（★`main` へは push しない★）
23. **文書末尾の「実行状態」表を更新した**

---

## 実行状態

| 段階 | 状態 | コミット |
|---|---|---|
| ★**着手前の確認**★ | ★**なし（2026-09-09 に2件とも回答済み）**★ | — |
| 段階0 前提の確認 | 完了 | （コミットなし） |
| 段階1 対応機種レジストリと判定関数 | 完了 | `d3f95bd` |
| 段階2 画面に「未対応」の印を付ける | 完了 | `7a68a4d` |
| 段階3 fixtures を CC1 のみにする | 完了 | `6f7eb92` |
| 段階4 境界ケースをテスト側で担保する | 完了 | `57a22f3` |
| 段階5 動作確認 | 完了（★ブラウザ操作手段が無く、curl + API JSON + 本番関数呼び出しで代替。
  ダーク/ライト・JP/EN の目視確認はできていない。詳細はD-087参照★） | （コミットなし） |
| 段階6 記録と push | 完了 | 本コミット |

**実行日**: 2026-09-09
**取得した決定番号**: D-087
**着手前の実測値**: 28ファイル / 572テスト / `tsc` 0 / `lint` 0（2026-09-09 実測。Vitest 4.1.11 / Node v24.14.0）
**判定関数の置き場所**: `lib/format/robot-model.ts`（`lib/format/contract.ts` と同じ「設定値は`lib/config`、
それを読む純粋関数は`lib/format`」という既存の分け方を踏襲）
**シリアル番号**: 揃えた（`8111R5628060088` / `8111R5628060177`。既存のどのシリアルとも重複しないことを
grepで確認済み。fixtures側6箇所を同時に変更、`query.test.ts`の合成データ5箇所は変えていない）
**`CLAUDE.md` §1 への追記**: した（「対応機種」の行を1行追加。§1以外は無変更）
**完了後の実測値**: 29ファイル / 587テスト 全パス / `tsc` 0 / `lint` 0
**詳細**: `docs/decisions.md` D-087 / `docs/handoff.md` 追記23 を参照
