作成: 2026-09-17 / ブランチ: develop(HEAD 1726817)/ 前提: D-143 §3(UI差分 第3章の判断)
D-Watch の実装をお願いします。ブランチは develop です。
■ 最初に読むもの(この順で、全部)
1. CLAUDE.md(全文。特に §4.3 端数処理 / §4.8 エラー種別は変換しない /
§5 モックが正 / §7 禁止事項 / §9 実装規約)
2. docs/handoff/指示書AO_ダッシュボードと詳細ログの列を直す.md(全文)
3. components/features/detail-log/CleanLogTable.tsx(★今回の主役。1本で2画面を兼ねている★)
4. lib/format/raw-label.ts(★段階1で作る関数の手本。置き場所も作法もこれに倣う★)
5. lib/clean-logs/csv.ts と lib/clean-logs/__tests__/csv.test.ts
(★テストが列を**位置**で見ている。段階3の前に必ず読む★)
6. components/features/detail-log/DetailLogScreen.tsx の toggleRow() / openAreas() /
AreaModal の使い方(★段階5でダッシュボードへ移植する★)
■ やること
指示書AO の 段階1 から 段階7 までを、★番号順に★実行してください。
★段階1(清掃結果ラベルの純粋関数)は「期待値表をテストに落としてから実装する」
順序を崩さないでください★(CLAUDE.md §6-2 / §9.4)。
★段階の順序には依存関係があります★ — 列を足してから行展開を有効にします
(colSpan の値が変わるため。逆順だと展開行が詰まって壊れます)。
■ 段階ごとに必ずやること
1. npx tsc --noEmit → 0 であること
2. npm run lint → 0 であること
3. npm test → 全パス
4. コミットする(★段階ごとに1コミット★。まとめない)
■ コミットの決まり
・git add -A は使わないでください。必ずファイルパスを明示してください
・コミットメッセージは日本語。「なぜそうしたか」を1〜2行入れてください
・push はしないでください
■ 絶対に触らないファイル
README.md / docs/code-map.md /
docs/handoff/管理画面_設計検討_20260910.md / .claude/worktrees/** /
d-watch-mock.jsx / lib/judge/** / lib/db/** / prisma/**
■ 絶対にやらないこと
・★clean_error を7分類に変換すること★(D-077 / CLAUDE.md §4.8)
・★コード値(0/1/2/3/4)をコードに直書きすること★ — lib/config の CLEAN_ERROR を使う
・★未知のコードを「その他」「不明」に潰すこと★(D-089 項3 / D-104 項2)
・表示用に丸めた値で判定すること(CLAUDE.md §4.3)
・components/ に判定・整形ロジックを書くこと(★テストが走りません★。落とし穴1)
・生SQL($queryRaw / $executeRaw / *Unsafe / Prisma.raw)— ESLint が落とします
・メインDBのスキーマ変更・マイグレーション作成
・新しい CSS に font-family を書くこと
・仕様が不明なところを推測で実装すること
■ 動作確認
★dev server はポート3000です★。ログインが要るので目視が難しければ
「できなかった」と正直に書いてください。★推測で「動いているはず」と書かないこと★。
■ 終わったら
・段階ごとのコミットハッシュ
・tsc / lint / test の結果(★テスト本数も★。着手前は 96ファイル / 2,141テスト)
・指示書どおりにできなかったところと、その理由
・判断に迷って自分で決めたところ(あれば全部)
docs/handoff/UI差分第3章_調査_20260916.md(937行)で洗い出した8件のうち、 ★ユーザーが 2026-09-16 に「直す」と決めた4件★を実装します。
| 第3章 # | 件名 | ユーザー判断 | 本指示書 |
|---|---|---|---|
| #2 | 死んだ「判定 ?」ボタン | ★直す★ | ★段階6★ |
| #3 | ダッシュボードの清掃ログ表が4列 | ★★「出発」列だけ足す★★ | ★段階4★ |
| #6 | コンパクト表の行展開が無効 | ★直す★ | ★段階5★ |
| — | 清掃結果(clean_error)が画面にもCSVにも出ていない |
★出す★ | ★段階1〜3★ |
| #1 | 「未対応」の対訳キー衝突 | ★★直さない★★ | §2 |
| #4 | サイドバーの未対応件数バッジ | ★★足さない★★ | §2 |
| #5 | D-Secアラート「通信断」の色 | ★★アンバー維持★★ | §2 |
| #7 | D-Secアラートの期間フィルタ | ★★今は入れない★★ | §2 |
| #8 | バッテリー残量行 | ★意図した仕様★ | §2 |
★第3章の調査は「日付・1h前ST・30分前ST・出発の4列を戻す」を提案しましたが、 ユーザーは★★「出発」1列だけ★★と決めました。★提案のほうを実装しないこと★。★
lib/types/index.ts:238 に、こう書いてあります:
★★画面・CSV には出していない★★ — 2/3/4 の出し方が未決(D-107 の申し送り 項1)。
★本指示書でこの申し送りが片付きます★(段階2・段階3)。 ★★型のコメントと lib/config/index.ts:248-249 のコメントを、実装に合わせて直すこと★★ — 直さないと、次の担当者が「まだ出していない」と読んで二重に実装します。
★どれも 2026-09-17 に自分で開いて確かめた事実です。★ ★★行番号は着手時にズレている可能性があります。必ず自分で開いて確認してください。★★
components/features/detail-log/CleanLogTable.tsx ← ★これ1本★
├─ components/features/dashboard/DashboardScreen.tsx:152 compact={true}
└─ components/features/detail-log/DetailLogScreen.tsx:495 compact 無し
★★新規コンポーネントを作らないこと★★ — CleanLogTable.tsx:26-27 に 「表を分けると修正が2箇所に散るため、新規コンポーネントは作らずこのテーブルの prop で分岐する」 と明記されています。★この判断はそのまま維持します★。
| # | 列 | compact(ダッシュボード) | 非compact(詳細ログ) |
|---|---|---|---|
| 1 | 施設名 | ★出す★ | 出す |
| 2 | 機種 | 落とす | 出す |
| 3 | シリアル | 落とす | 出す |
| 4 | 日付 | 落とす | 出す |
| 5 | 予定 | ★出す★ | 出す |
| 6 | 1h前ST | 落とす | 出す |
| 7 | 30分前ST | 落とす | 出す |
| 8 | 出発 | ★★落とす → 段階4で出す★★ | 出す |
| 9 | 完了率 | ★出す★ | 出す |
| 10 | 判定 | ★出す★ | 出す |
| — | ★清掃結果★ | ★★足さない★★ | ★★段階2で足す★★ |
着手前: compact = 4列 / 非compact = 10列 段階2後: compact = 4列 / 非compact = 11列 段階4後: compact = 5列 / 非compact = 11列 ← ★最終形★
colSpan が2箇所ある★CleanLogTable.tsx:156 空状態の行 colSpan={compact ? 4 : 10}
CleanLogTable.tsx:205 アドバイス展開行 colSpan={compact ? 4 : 10}
★★列を足したら両方直すこと★★。★片方だけ直しても tsc も lint もテストも通ります★ (画面のテストが存在しないため)。★見た目が詰まるだけなので、目視でしか気づけません★。
最終的に★両方とも colSpan={compact ? 5 : 11}★になります。
:162-163★// CleanLogTable.tsx:162-163 const hasAdvice = !compact && (r.grade === "△" || r.grade === "×"); const isExpanded = !compact && expandedId === r.id;
根拠として CleanLogTable.tsx:23-27 の docstring がこう書いています:
ダッシュボードではアドバイスを一切出さない規約のため、compact 時は行クリック・ アドバイス展開も無効化する。
★★この「規約」はどの資料にも存在しません★★。
$ grep -n "アドバイスを一切\|ダッシュボードではアドバイス" \
docs/decisions.md docs/spec.md docs/handoff.md
(0件)
git blame では 47f2df4(2026-08-30「F-01 ダッシュボード画面を実装」)の初回実装で 書かれたもので、★実装者の自己申告★です。★段階5でこの2文を消してください★。
★★消すだけにして、別の「規約」を書かないこと★★ — 出所不明の根拠を足したのが この事故の原因です。★消す代わりに「ユーザー判断 2026-09-16(D-143 §3 #6)」と書きます★。
clean_error は API契約にも型にも既にある★d-watch-api.yaml:1621-1632 clean_error(integer / nullable / 0〜4 の説明つき) lib/types/index.ts:240 clean_error: number | null;
★★つまり YAML のスキーマ変更は要りません★★(CLAUDE.md §6-1 が要求する 「API契約を先に直す」は、今回は該当しません)。★着手時に自分で確認すること★。
★ただし d-watch-api.yaml:1632 の説明文★:
2/3/4 は未実施とは違う扱いで departed=true(画面への出し方は未決)
★この「未決」だけが古くなります★。★段階2で「詳細ログの『清掃結果』列とCSVに生値のラベルで出す」に 書き換えること★。★スキーマは1文字も変えません★(説明文だけ)。
lib/config/index.ts:262 CLEAN_ERROR 0〜4 のコード(★直書き禁止★) lib/config/index.ts:285 CLEAN_ERROR_LABELS コード → ラベル(★定義書の原文ママ★) lib/config/index.ts:301 cleanErrorLabel(code) 無いコードは String(code) を返す
| コード | ラベル |
|---|---|
| 0 | 正常 |
| 1 | 異常(清掃未実施) |
| 2 | 異常(セキュリティ有効) |
| 3 | 異常(D-S制御盤異常) |
| 4 | 異常(清掃ロボット異常) |
| ★5〜255★ | ★★String(code) = 生値がそのまま出る。それが正しい挙動★★(D-077) |
★★括弧は半角のまま。全角に直さないこと★★(lib/config/index.ts:277-278。 定義書の原文が半角)。★言い換え・要約もしないこと★。
★★cleanErrorLabel() は null を受け取れません★★(引数が number)。 ★だから段階1で null を扱う関数を1つ足します★。
lib/clean-logs/csv.ts:62-73 CLEAN_LOG_CSV_HEADER(10要素) lib/clean-logs/csv.ts:85-102 buildCleanLogCsv()。★行の組み立ては :90-99 の10セル★
★全列が例外なく csvCell() を通っています★(D-125)。 ★csv.ts:78-83 のコメントに「列の追加は『csvCell() で包む』だけで済む」とあります★。 ★★その約束を破らないこと★★。
$ npm test
Test Files 96 passed (96)
Tests 2141 passed (2141)
★この数字を基準にしてください★。
★★以下は「やり忘れ」ではありません。ユーザーが 2026-09-16 に判断した結果です(D-143 §3)。 親切心で直さないでください。★★
| 第3章 # | 件名 | ★理由★ |
|---|---|---|
| ★#1★ | ★「未対応」の対訳キー衝突★(lib/i18n.ts:160 の 未対応: "Unsupported"。★英語で「非対応」の意味になる★) |
★★英語を配布するか未定★★のためユーザーが「そのまま」と判断。★★★英語配布を決めた時点で必ず直す必要があります★★★ — ★D-Secアラートの「未対応」(人が対応していない)が Unsupported(機器が非対応)と出続けます★。★要対応 にも同型の地雷があります★(今は辞書に無く和文のままですが、素直に対訳を足した瞬間に同じ事故になります) |
| #4 | サイドバーの未対応件数バッジ | ★PATCH(対応状況更新)が未実装・DB更新用アカウントも未受領★。ユーザー判断で保留 |
| #5 | D-Secアラート「通信断」の色を赤に戻す | ★★アンバー維持で確定★★ |
| #7 | D-Secアラートに期間フィルタを足す | ★今は入れない★。入れるなら d-watch-api.yaml の変更から |
| #8 | バッテリー残量行を消す | ★意図した仕様★(D-105 項5)。★★空欄のまま残すのが正★★。★行ごと消さないこと★ |
★★#1 について、指示書に書いておく注記★★:
英語表示を配布すると決めた時点で、
lib/i18n.tsの未対応キーは必ず直す必要があります。 直し方は「キーを日本語原文のまま使う方式」自体を変えるか(1階層辞書をやめる)、 呼び出し側で別の日本語(例:未対応(対応状況))に分けるかの2択で、 ★どちらもlib/i18n.tsの作法を変える話なので、単独の指示書が要ります★。 ★本指示書では触りません。★
| 対象 | 理由 |
|---|---|
README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md / .claude/worktrees/** |
★別作業★ |
★d-watch-mock.jsx★ |
★★モックは原型として保存する★★(CLAUDE.md §7)。★1文字も書き換えない★ |
★lib/judge/**★ |
★判定ロジックは変えません★ |
★lib/db/** / prisma/**★ |
★今回はDB側を1行も変えません★(clean_error は既にAPIまで来ています) |
★d-watch-api.yaml のスキーマ★ |
★変更不要★(§1-5)。★★説明文1箇所だけ★★直します |
★テストDB(133.125.238.144)への書き込み★ |
★★読み取り専用★★ |
★★禁止★★: git add -A / 生SQL / スキーマ変更 / ★コード値の直書き★ / ★components/ への判定・整形ロジック★ / ★新しいCSSへの font-family★ / 推測での実装
★新規 lib/format/clean-result.ts★ と lib/format/__tests__/clean-result.test.ts。
★手本は lib/format/raw-label.ts★(27行。★D-077 を維持したまま「空である」事実だけを 差し替える純粋関数★)。★docstring の書き方もこれに倣ってください★。
★★components/ に書かないこと★★ — vitest.config.ts:14 の include は ["lib/**/*.test.ts", "app/**/*.test.ts"] で、★components/** は走りません★(落とし穴1)。
★lib/judge/ にも置かないこと★ — あちらは◎○△×等の★評価★の置き場所で、 こちらは★表示整形★です(lib/format/raw-label.ts:15-16 に同じ切り分けが書いてあります)。
export function formatCleanResult(cleanError: number | null): string
null → 「出発失敗」 0〜4 → CLEAN_ERROR_LABELS のラベル(★定義書の原文ママ★) 5以上 → String(code)(★生値。D-077★)
★★null を「出発失敗」と表示する根拠★★(★コメントに書くこと★):
d-watch-api.yaml:1629-1630 — 「null は『履歴の行が無い(=清掃結果が記録されていない)』 ことを表し、★出発失敗として扱う★(D-107 設計3-C)」lib/types/index.ts:219-221 — clean_error === 1 または null のとき departed === falsenull は『出発失敗』と表示する」★★★「(値なし)」にしないこと★★ — formatRawLabel() の EMPTY_VALUE_LABEL は 「DBのカラムが空」という事実だけを述べる文言で、★ここは「履歴の行が無い=出発していない」という 意味が確定している 場面★です。★ユーザーが文言を指定しています★。
★★文言は関数と同じファイルの定数に出すこと★★(EMPTY_VALUE_LABEL と同じ形):
/** `clean_error` が null(履歴の行が無い)のときの表示。★ユーザー確定 2026-09-16★ */ export const CLEAN_RESULT_NOT_DEPARTED_LABEL = "出発失敗";
★画面とCSVの2箇所から使うので、文字列を2回書かないためです★。
| # | 入力 | 期待 | なぜ入れるか | ||
|---|---|---|---|---|---|
| ★1-1★ | null |
★"出発失敗"★ |
★ユーザー確定の本体★ | ||
| 1-2 | CLEAN_ERROR.normal(0) |
"正常" |
|||
| ★1-3★ | CLEAN_ERROR.notExecuted(1) |
★"異常(清掃未実施)"★ |
★★null と別物であること★★。★同じ「出発失敗」に潰さない★ |
||
| 1-4 | CLEAN_ERROR.securityEnabled(2) |
"異常(セキュリティ有効)" |
★D-107 申し送りの本題★ | ||
| 1-5 | CLEAN_ERROR.controlBoardError(3) |
"異常(D-S制御盤異常)" |
同上 | ||
| 1-6 | CLEAN_ERROR.robotError(4) |
"異常(清掃ロボット異常)" |
同上 | ||
| ★1-7★ | 5 |
★"5"★ |
★★未知のコードは生値★★(D-077) | ||
| ★1-8★ | 255 |
★"255"★ |
★unsigned tinyint の上限★ |
||
| ★1-9★ | 255 |
★★「その他」「不明」を含まない★★ | ★D-089 項3 / D-104 項2★。★clean-error.test.ts:60-65 に同じ形のテストがあるので倣う★ |
||
| ★1-10★ | 0 |
★"出発失敗" ではない★ |
★null と 0 の取り違え(?? と `\ |
\ | ` の事故)を止める★ |
| 1-11 | 全コード | ★CLEAN_ERROR_LABELS と1文字も違わない★ |
★ラベルを画面側で言い換えていないことの担保★ |
★★1-10 を必ず入れること★★ — clean_error ?? ... を clean_error || ... と書くと ★0(正常)が「出発失敗」に化けます★。★例外もtscエラーも出ません★。
★コード値を直書きせず CLEAN_ERROR を使うこと★(テスト側も。 lib/db/__tests__/clean-log-mapping.test.ts:285 に同じ趣旨のテストがあります)。
lib/format/clean-result.ts と lib/format/__tests__/clean-result.test.ts があるnpx tsc --noEmit → ★0★npm run lint → ★0★npm test → 全パス。★★97ファイル(+1)/ 2,150前後(+8〜12)★★CLEAN_ERROR を import していること★(0〜4 の直書きが無いこと)コミットメッセージ案
feat(format): 清掃結果ラベルの整形を純粋関数にする(null は「出発失敗」) clean_error を画面とCSVの2箇所で使うため、null の扱いを1箇所に閉じた。 cleanErrorLabel() は null を受け取れず、?? と || の取り違えで 0(正常)が 「出発失敗」に化ける事故が静かに起きるため、期待値表で固定した(指示書AO 段階1)。
| ファイル | やること |
|---|---|
components/features/detail-log/CleanLogTable.tsx |
★!compact 付きの <th> / <td> を1組追加★・★colSpan を2箇所直す★ |
lib/i18n.ts |
★清掃結果 の対訳を追加★ |
lib/types/index.ts |
★clean_error の docstring から「画面・CSVには出していない」を消す★ |
lib/config/index.ts |
★CLEAN_ERROR の docstring から「画面への出し方は未決」を消す★ |
d-watch-api.yaml |
★clean_error の description 1行だけ★(★スキーマは変えない★) |
★★「出発」列(:102-106 の <th> / :188-192 の <td>)の直後、「完了率」の直前★★。
★理由★: ★出発(✓/✗)も 清掃結果 も出所は同じ clean_status_histories.clean_error です★ (d-watch-api.yaml:1617-1620)。★隣に並べると、✗ の理由がその場で読めます★。 ★離すと、2列の関係が読み取れません★。
… 30分前ST │ 出発 │ ★清掃結果★ │ 完了率 │ 判定
{!compact && (
<th className={styles.th} style={{ textAlign: "left" }}>
{t("清掃結果")}
</th>
)}
{!compact && <td className={styles.td}>{formatCleanResult(r.clean_error)}</td>}
★★セルの値を t() に通さないこと★★ — ★理由をコメントに書くこと★:
CLEAN_ERROR_LABELS は★定義書の原文★です(lib/config/index.ts:272-278)。 ★対訳を当てると「何が原文だったか」が分からなくなります★String(code)★ で、そもそも訳せませんstatus_1h / status_30m(Pill)も t() を通していません★。★同じ扱いに揃えます★清掃結果)だけ t() を通します★colSpan を直す★:156 colSpan={compact ? 4 : 10} → colSpan={compact ? 4 : ★11★}
:205 colSpan={compact ? 4 : 10} → colSpan={compact ? 4 : ★11★}
★★2箇所とも★★。★段階4で compact 側も 5 に変わります★。
lib/i18n.ts の EN に足します。★施設名 〜 判定 が並んでいる塊(:23-31)の 出発 と 完了率 の間★に置いてください(★画面の列順と辞書の並びを揃えるため★)。
清掃結果: "Clean Result",
★★衝突の確認(★必ずやること★)★★:
grep -n "清掃結果" lib/i18n.ts
★着手前の実測では 0件★です。★1件でも出たら止まって報告してください★ — lib/i18n.ts は★キーが日本語原文の1階層辞書★なので、 ★同じ日本語を別の意味で使うと必ず衝突します★(未対応 → Unsupported の事故が実在。§2 #1)。
★清掃結果 は docs/spec.md や app/api/dashboard/route.ts のコメントでは 「昨夜の清掃結果サマリ」という別の意味で使われています★が、 ★★t() のキーとしては未使用なので衝突しません★★。 ★ただし将来「清掃結果サマリー」を t() に通す人が出たら、そちらは別キーになります★ (DashboardScreen.tsx:81 の 昨夜の清掃結果サマリー — {today} は今も t() を通していません)。
★これを忘れると、次の担当者が「まだ出していない」と読んで二重に実装します。★
| 場所 | 現在 | 直す |
|---|---|---|
lib/types/index.ts:238 |
★「★★画面・CSV には出していない★★ — 2/3/4 の出し方が未決(D-107 の申し送り 項1)。」★ | ★「詳細ログの『清掃結果』列とCSVに formatCleanResult() で出す(指示書AO / D-1xx)」★ |
lib/config/index.ts:248-249 |
★「★★どの画面にどう出すかは未決★★(D-107 の申し送り)。現状は API まで生値を運ぶだけで、画面・CSV には出していない。」★ | 同上 |
d-watch-api.yaml:1632 |
「2/3/4 は未実施とは違う扱いで departed=true(画面への出し方は未決)」 | 「2/3/4 は未実施とは違う扱いで departed=true。詳細ログの『清掃結果』列とCSVに生値のラベルで出す(指示書AO)」 |
★★D-1xx は段階7で採番した実際の番号に置き換えること★★(先に書かない)。
colSpan が2箇所とも compact ? 4 : 11★grep -n "清掃結果" lib/i18n.ts → ★1件(追加した行だけ)★lib/types/index.ts / lib/config/index.ts / d-watch-api.yaml のコメントが実装と一致★d-watch-api.yaml のスキーマ(型・nullable・enum)が1文字も変わっていないこと★npx tsc --noEmit → 0 / npm run lint → 0 / npm test → 全パス(★本数は段階1のまま★)★★テストは増えません★★ — ★画面のテストは存在しません★(vitest.config.ts:14)。 ★「増えないこと」自体が正しい★ので、増やそうとして components/ にテストを置かないでください。
コミットメッセージ案
feat(detail-log): 清掃詳細ログに「清掃結果」列を足す(10列→11列) clean_error は API まで来ていたが画面に出しておらず、✗(出発失敗)の理由が 読めなかった。出発列の隣に置いて、同じ clean_error 由来であることを見せる。 D-107 の申し送り 項1(2/3/4 の出し方が未決)はこれで片付く(指示書AO 段階2)。
| ファイル | やること |
|---|---|
lib/clean-logs/csv.ts |
★ヘッダ(:62-73)と行(:90-99)の両方★ |
lib/clean-logs/__tests__/csv.test.ts |
★★位置で見ているテストの添字を直す★★ + ★新しい列のテストを足す★ |
★★画面と同じ位置★★ — 出発 と 完了率 の間。
★理由★: csv.ts:61 のコメントが★「ヘッダ行の見出し(モックの列順に合わせる)」★です。 ★画面とCSVで列順が違うと、突き合わせる人が毎回並べ替えることになります★。
export const CLEAN_LOG_CSV_HEADER = [ "施設名", "機種", "シリアル", "日付", "予定時刻", "1h前ST", "30分前ST", "出発", ★"清掃結果"★, "完了率", "判定", ] as const;
csvCell(r.departed ? "出発" : "未出発"), ★csvCell(formatCleanResult(r.clean_error)),★ csvCell(displayRate(r.completion_rate)),
★★必ず csvCell() を通すこと★★(D-125。csv.ts:78-83)。 ★「ラベルは固定文言だから安全」という判断をしないこと★ — ★未知コードは String(code) で、 - 始まりにはなりませんが、★列ごとに安全性を判断させないのがこの設計の要点★です。
lib/clean-logs/__tests__/csv.test.ts:112 cells(row)[8] 完了率
:126 cells(base)[8] 完了率
:131 cells(base)[7] 出発
:132 cells(base)[9] 判定
:134 cells(...)[7] 出発
:152 c[0] c[1] c[2] c[5] c[6] ★c[8]★
★清掃結果 を index 8 に挿入すると★:
| 旧 | 新 | 列 |
|---|---|---|
[7] |
[7] |
出発(★変わらない★) |
| — | ★[8]★ |
★清掃結果(新規)★ |
[8] |
★[9]★ |
★完了率★ |
[9] |
★[10]★ |
★判定★ |
★★[8] → [9] / [9] → [10] を、★5箇所★直すこと★★。
★★直し忘れると「テストが落ちて気づく」ので、そこは安全です★★。 ★危険なのは逆で、「落ちたから添字を合わせる」だけで済ませて、 ★新しい列そのもののテストを書かないこと★です。
★:164 の expect(cells(base)).toHaveLength(CLEAN_LOG_CSV_HEADER.length) は ★定数を見ているので自動で追随します★(直す必要なし)。
| # | 入力 | 期待 |
|---|---|---|
| ★3-1★ | clean_error: 0 |
★cells(base)[8] === "正常"★ |
| ★3-2★ | clean_error: null |
★"出発失敗"★ |
| ★3-3★ | clean_error: 2 |
★"異常(セキュリティ有効)"★(★括弧は半角★) |
| ★3-4★ | clean_error: 5 |
★"5"★(★生値★) |
| ★3-5★ | ★ヘッダの8番目★ | ★CLEAN_LOG_CSV_HEADER[8] === "清掃結果"★ |
★★3-3 を必ず入れること★★ — ★異常(セキュリティ有効) は ( ) を含みますが、 csvCell() の引用判定は /[",\n\r]/ なので★引用されません★。 ★「引用されないのが正しい」ことを固定します★(後から誰かが「括弧も引用しよう」として 既存CSVの互換を壊すのを止めるため)。
★:139-157 の「★どの列に危険な値が来ても素通ししない★」テストに ★清掃結果の列は追加できません★(値が clean_error から機械的に決まり、 ★危険な文字列を作れない★ため)。★その旨を1行コメントで書いてください★ — 書かないと「この列だけ漏れている」と読まれます。
CLEAN_LOG_CSV_HEADER が11要素★csvCell() を11回通る★)npx tsc --noEmit → 0 / npm run lint → 0 / npm test → 全パス。
★★97ファイル / 2,155前後(段階1から +5)★★
コミットメッセージ案
feat(csv): 清掃詳細ログのCSVに「清掃結果」列を足す(画面と同じ位置) 画面(段階2)とCSVで列順が違うと突き合わせのたびに並べ替えになるため、 出発と完了率の間に入れた。既存テストが列を位置で見ているので添字を直し、 新しい列が csvCell() を通っていることを固定した(指示書AO 段階3 / D-125)。
components/features/detail-log/CleanLogTable.tsx ★のみ★。
:102-106 の <th> と :188-192 の <td> から★!compact && を外す★だけです。
// before
{!compact && (
<th className={styles.th} style={{ textAlign: "center" }}>{t("出発")}</th>
)}
// after
<th className={styles.th} style={{ textAlign: "center" }}>{t("出発")}</th>
★★ソース上の位置を動かさないこと★★ — 出発 の <th> は 30分前ST と 完了率 の間にあり、 ★!compact を外すだけで compact 時の列順が「施設名 / 予定 / 出発 / 完了率 / 判定」になります★。 ★並べ替える必要はありません★。
colSpan を直す★(★2箇所★):156 colSpan={compact ? 4 : 11} → colSpan={compact ? ★5★ : 11}
:205 colSpan={compact ? 4 : 11} → colSpan={compact ? ★5★ : 11}
★★これが最終形です★★。
CleanLogTable.tsx:24 の「★4列表示にする(施設名/予定時刻/完了率/判定のみ)★」が ★事実と違うようになります★。
★5列表示にする(施設名 / 予定 / 出発 / 完了率 / 判定)★
★★「機種・シリアル・日付・1h前ST・30分前ST を落とす」と落とすほうを書くこと★★ — ★出す列を並べると、列を足すたびに書き換えが要ります★。
★★「ユーザー判断(2026-09-16。D-143 §3 #3)で、足すのは『出発』1列だけと決めた」と 書き添えること★★ — ★書かないと、次に docs/spec.md:32(「機種・シリアル列を省略」=8列)と 突き合わせた人が「日付・1h前ST・30分前ST も戻し忘れている」と判断して足します★。
出発 は★対訳済み★(lib/i18n.ts:29 の 出発: "Dep.")CleanLogTable.module.css:1-5 が .table { min-width: 1000px }、 DashboardScreen.module.css:75-77 が .tableWrap { overflow-x: auto }。 ★★横スクロールで収まるので CSS 変更は不要★★font-family を入れない。CLAUDE.md §0-3)colSpan が2箇所とも compact ? 5 : 11★npx tsc --noEmit → 0 / npm run lint → 0 / npm test → 全パス(★本数は段階3のまま★)コミットメッセージ案
feat(dashboard): ダッシュボードの清掃ログ表に「出発」列を足す(4列→5列) △×の行しか出ない表で、出発したのかどうかが読めなかった。 日付・1h前ST・30分前ST は足さない(ユーザー判断 2026-09-16 / D-143 §3 #3)。 colSpan は空状態と展開行の2箇所とも追随させた(指示書AO 段階4)。
★colSpan が compact ? 5 : 11 になっている必要があります★。 ★段階4より先にやると、展開したアドバイス行が4列ぶんしか広がらず、右端が詰まります★。 ★tsc も lint もテストも通るので、目視でしか気づけません★。
| ファイル | やること |
|---|---|
components/features/detail-log/CleanLogTable.tsx |
★:162-163 の !compact && を外す★ / ★:23-27 の存在しない「規約」のコメントを消す★ |
components/features/dashboard/DashboardScreen.tsx |
★state とアドバイス取得を足す★ / ★★エリアモーダルを足す★★(§8-4) |
CleanLogTable.tsx// before const hasAdvice = !compact && (r.grade === "△" || r.grade === "×"); const isExpanded = !compact && expandedId === r.id; // after const hasAdvice = r.grade === "△" || r.grade === "×"; const isExpanded = expandedId === r.id;
★★モックと同じ形になります★★(d-watch-mock.jsx:311 は var hasAdvice = r.g === "△" || r.g === "×"; で compact を見ていません)。
★docstring(:23-27)から★★この2文を消すこと★★:
ダッシュボードではアドバイスを一切出さない規約のため、compact 時は行クリック・ アドバイス展開も無効化する。
★★代わりに書くこと★★:
★compact でも行展開する★(モック `d-watch-mock.jsx:311` は compact を見ていない)。 ★かつて「ダッシュボードではアドバイスを一切出さない規約」と書かれていたが、 ★★その「規約」はどの資料にも存在しなかった★★(grep 0件。実装者の自己申告)。 ユーザー判断で展開を有効にした(2026-09-16 / D-143 §3 #6 / 指示書AO 段階5)。
★★「規約が無かった」という事実を残すこと★★ — ★消すだけだと、また誰かが同じ 思い込みを書き足します★。
DashboardScreen.tsx(ここがいちばん量が多い)★★DetailLogScreen.tsx から★そのまま移植できます★。
| 移す先 | 元 |
|---|---|
expandedId / adviceById / adviceLoadingId の state |
DetailLogScreen.tsx:84-88 |
toggleRow() |
DetailLogScreen.tsx:214-246 |
openAreas() |
DetailLogScreen.tsx:257-263 |
areaTarget / areaFilter / areaData / areaLoading / areaError |
DetailLogScreen.tsx:90-95 |
エリア取得の useEffect |
DetailLogScreen.tsx:265-297 |
<AreaModal ... /> |
DetailLogScreen.tsx:513-524 |
AreaTarget 型 |
DetailLogScreen.tsx:31-40 |
そして CleanLogTable への受け渡しを差し替えます(DashboardScreen.tsx:152-162):
expandedId={expandedId} // was null
adviceById={adviceById} // was {}
adviceLoadingId={adviceLoadingId}// was null
onToggleRow={toggleRow} // was () => {}
onOpenAreas={openAreas} // was () => {}
★★onOpenAreas を () => {} のまま残さないこと(★見落としやすい★)★★
★アドバイスが1件以上あると、展開部に★「エリア別詳細を見る →」ボタン★が出ます★ (CleanLogTable.tsx:235-244)。★★onOpenAreas が空関数のままだと、 このボタンは押しても何も起きません★★ — ★今回直している「死んだ『判定 ?』ボタン」と まったく同じ不具合を、その隣に作ることになります★。
★モックでは compact でもこのボタンからモーダルが開きます★ (d-watch-mock.jsx:358 は LogTable の中にあり、compact で分岐していません)。 ★CLAUDE.md §5 でモックが★遷移の正★です★。 ★したがって AreaModal をダッシュボードにも置きます★。
★★/detail-log へ遷移させて代用しないこと★★ — ★モックはモーダルを開きます★。 ★遷移にすると、開いていた行の情報が失われます★。
AreaModal は components/features/detail-log/ にあります★。 ★ダッシュボードから import して構いません★(CleanLogTable と同じ扱い。★複製しないこと★)openAreas() の中でやらないこと★ — DetailLogScreen.tsx:248-255 のコメントのとおり、 ★filter の切り替えでも取り直す必要がある★ので useEffect に寄せてあります。 ★2本に分けないこと★cancelled フラグを落とさないこと★(DetailLogScreen.tsx:269)— ★古い応答が後から 届いて新しい表示を上書きします★toggleRow() の catch 節をそのまま持ってくること★(:221-243)— ★取得に失敗しても行は開き、一覧が持っている値で最小限を埋めます★。 ★★row.consecutive_problem_days_capped ?? false の ?? を || にしないこと★★展開部の見出し(CleanLogTable.tsx:37-51 の「🔴 レベル3 — DFA介入を検討」等)は ★t() を通していません★。★EN では和文のまま出ます★。
★★これは詳細ログでも同じ既存の抜けなので、本段階では直しません★★ (§2 #1 と同じ理由 — ★英語を配布するか未定★)。★ダッシュボードでも和文が出ることを 報告に1行書いてください★。
▼ / ▲ が判定列に出る★CleanLogTable.tsx に「規約」という語が残っていないこと★
(`grep -n "規約" components/features/detail-log/CleanLogTable.tsx` → ★0件★)
npx tsc --noEmit → 0 / npm run lint → 0 / npm test → 全パス(★本数は段階3のまま★)コミットメッセージ案
feat(dashboard): コンパクト表の行展開アドバイスを有効にする 根拠とされていた「ダッシュボードではアドバイスを一切出さない規約」は どの資料にも存在せず(grep 0件)、初回実装の自己申告だった。 モックは compact でも展開する。エリア別詳細のモーダルも合わせて移植した (押しても何も起きないボタンを新たに作らないため)。 ユーザー判断 2026-09-16 / D-143 §3 #6 / 指示書AO 段階5。
ボタンは★compact でも表示されています★(CleanLogTable.tsx:110-120)。 凡例本体(:123-152)は showGradeInfo でしか開きません。 ダッシュボードは★showGradeInfo={false} / onToggleGradeInfo={() => {}}★を渡しています (DashboardScreen.tsx:158-159)。
★★押しても何も起きないのは仕様上の制約ではなく、状態を持たせていないだけです★★。
useState 1つ★)components/features/dashboard/DashboardScreen.tsx ★のみ★:
const [showGradeInfo, setShowGradeInfo] = useState(false);
...
showGradeInfo={showGradeInfo}
onToggleGradeInfo={() => setShowGradeInfo((v) => !v)}
★CleanLogTable.tsx は変更不要★です。
D-053 は★「固定の選択肢だけがあって何も起きないUIは、不具合と区別できない」★ため 絞り込み <select> を★消し★ました。
★本件は前提が違います★ — ★D-053 が「消す」を選んだのは、機能させるためのデータ (マスタ)が存在しなかったから★です。★こちらはデータも凡例本体も既にあり、 必要なのは useState 1つ★です。
同じ趣旨が lib/i18n.ts:320-321 にも明文化されています:
★★
disabledだけにして理由を出さないことはしない★★ — 押せないボタンは、理由が書いていないと不具合と区別がつかない(D-053 と同じ理由)
★★したがって D-053 と整合するのは「機能させる」です★★。 ★「ボタンを消す」を選ばないこと★ — ★モックには ? があり、押すと開きます★ (d-watch-mock.jsx:301)。★消すと CLAUDE.md §5 に反します★。
凡例の文言は★t() を通していません★:
CleanLogTable.tsx:126 判定基準(タスク完了率): CleanLogTable.tsx:142 (閾値はシステム設定で変更可能)
★開けるようにすると、EN表示で和文の塊が新たに1つ増えます★。
★★これは既存の抜けであって本段階が作る問題ではありません★★ (★同じ凡例は詳細ログでも開きます★)。★§2 #1 と同じ理由で今回は直しません★。 ★報告に1行書いてください★。
★★もし直すなら★★: 辞書に足すのは★2キーだけ★です。 ★ただし ◎ {GRADE_THRESHOLDS.excellent}%以上 のような★数値混じりの行★は、 lib/i18n.ts の★方式A(プレースホルダ + replace)★に乗せる必要があります ("{n}分前" などが前例)。★1文字キー(%以上 等)にしないこと★。 ★これは本指示書の範囲外です★。
? を押すと凡例が開く★✕ を押すと閉じる★CleanLogTable.tsx を変更していないこと★npx tsc --noEmit → 0 / npm run lint → 0 / npm test → 全パス(★本数は段階3のまま★)コミットメッセージ案
fix(dashboard): 「判定 ?」ボタンを機能させる(useState 1つ) 凡例本体もデータも既にあり、状態を渡していないだけだった。 D-053(何も起きないUIは消す)は「機能させるデータが無い」場合の話で、 本件は前提が違う。モックには ? があり押すと開く(CLAUDE.md §5)。 ユーザー判断 2026-09-16 / D-143 §3 #2 / 指示書AO 段階6。
grep -n "^### D-" docs/decisions.md | tail -3
★★^### D-0 では3桁台を拾えません★★(★二重採番の事故歴があります★)。
★着手前の実測では最終番号は D-143(docs/decisions.md:11255)★です。 ★★着手時点で増えている可能性があるので、必ず自分で確認してください★★。
★D-143 §3 の表と同じ形式で、採番の根拠を本文の冒頭に1行残すこと★:
★採番についての注記★:
grep -n "^### D-" docs/decisions.md | tail -3で最終番号が ★D-1xx★ であることを実測してからD-1yyを採った。
docs/decisions.md に書くことclean_error を画面とCSVに出したこと★と、★★D-107 の申し送り 項1 が片付いたこと★★null を「出発失敗」と表示すると決めたこと★(★ユーザー原文を転記★)と、 ★clean_error = 1(異常(清掃未実施))とは別の表示にしたこと★String(code) のまま画面に出ること★★ (D-077。★「その他」「不明」に潰さない★)未対応 の対訳を必ず直す必要があること★★docs/handoff.md に追記することlib/i18n.ts の 未対応 → Unsupported は直していない★★(D-143 §3 #1)。 ★英語配布を決めた時点で必ず直す★。★要対応 にも同型の地雷がある★判定基準(タスク完了率): / (閾値はシステム設定で変更可能))と アドバイス展開部の見出しが t() を通っていない★ — ★EN で和文が出る★。 ★段階6でダッシュボードでも出るようになった★1h前ST / 30分前ST はダッシュボードには出していない★(ユーザー判断)d-watch-api.yaml の変更から)lib/types/index.ts / lib/config/index.ts / d-watch-api.yaml に書いた D-1xx が
実際の番号に置き換わっている★
docs/handoff.md に5件の申し送りが入っているgit add にファイルパスを明示している★(★git add -A を使っていない★)コミットメッセージ案
docs: ダッシュボードと詳細ログの列修正を記録する(D-1xx) やったこと4件とやらないと決めた5件の両方を残す。存在しない「規約」が 根拠になっていた経緯と、英語配布時に「未対応」の対訳を直す必要があることを 申し送りに入れた(指示書AO 段階7)。
| # | 落とし穴 | どうなるか | ||
|---|---|---|---|---|
| ★1★ | ★判定・整形を components/ に書く★ |
★★テストが走らない★★(vitest.config.ts:14 の include は lib/** と app/** のみ)。★落ちないのでゼロ件成功と区別がつかない★ |
||
| ★2★ | ★colSpan の片方だけ直す★ |
★tsc も lint もテストも通る★。★空状態か展開行のどちらかが詰まる。目視でしか気づけない★ | ||
| ★3★ | ★段階5 を段階4 より先にやる★ | ★展開行が4列ぶんしか広がらない★。★同じく気づけない★ | ||
| ★4★ | ★clean_error ?? x を `\ |
\ | x` と書く★ | ★★0(正常)が「出発失敗」に化ける★★。★例外も tsc エラーも出ない★(段階1 期待値表 1-10) |
| ★5★ | ★clean_error === 1 と null を同じ表示に潰す★ |
★「履歴が無い」と「未実施として記録された」の区別が消える★(期待値表 1-3) | ||
| ★6★ | ★未知コードを「その他」「不明」にする★ | ★D-089 項3 / D-104 項2 に反する★。★破棄済み D-067 の方針と紛らわしい★。★String(code) が正★ |
||
| ★7★ | ★ラベルの半角括弧を全角に直す★ | ★定義書の原文が半角★(lib/config/index.ts:277-278)。★実データと突き合わせたときに原文が分からなくなる★ |
||
| ★8★ | ★CSVテストの添字を直し忘れる★ | ★テストが落ちるのでそこは安全★。★危険なのは「落ちたから添字を合わせる」だけで新しい列のテストを書かないこと★ | ||
| ★9★ | ★CSVで csvCell() を通さない★ |
★D-125 の関門が1つ増える★。★csv.ts:78-83 の約束が崩れる★ |
||
| ★10★ | ★CSVの列順を画面と変える★ | ★突き合わせのたびに並べ替えになる★ | ||
| ★11★ | ★onOpenAreas を () => {} のまま残す★ |
★★今直している「死んだボタン」と同じものを隣に作る★★(§8-4) | ||
| ★12★ | ★?.length / `\ |
\ | ` でモーダルの応答を分岐する★ | — ★cancelled フラグを落とすと古い応答が新しい表示を上書きする★ |
| ★13★ | ★t() に 清掃結果 と別の意味の日本語を足す★ |
★★lib/i18n.ts はキーが日本語原文の1階層辞書★★。★同じ日本語を別の意味で使うと必ず衝突する★(未対応 → Unsupported の事故が実在)。★足す前に必ず grep する★ |
||
| ★14★ | ★セルの値を t() に通す★ |
★CLEAN_ERROR_LABELS は定義書の原文★。★訳すと何が原文か分からなくなる★。★未知コードはそもそも訳せない★ |
||
| ★15★ | ★「規約」のコメントを消して別の根拠を書く★ | ★出所不明の根拠を足したのがこの事故の原因★。★「規約は存在しなかった」という事実を残す★ | ||
| ★16★ | ★モック(d-watch-mock.jsx)を直す★ |
★★原型として保存する★★(CLAUDE.md §7)。★1文字も書き換えない★ |
||
| ★17★ | ★d-watch-api.yaml のスキーマを変える★ |
★変更不要★(§1-5)。★直すのは説明文1行だけ★ | ||
| ★18★ | ★新しいCSSに font-family を書く★ |
★漢字が中華字形になる★(CLAUDE.md §0-3)。★そもそも今回CSSは要らない★ |
||
| ★19★ | ★git add -A★ |
★★禁止★★(CLAUDE.md §7。★別作業の途中ファイルを巻き込む事故歴あり★) |
||
| ★20★ | ★決定番号を先に決める★ | ★★二重採番の事故歴あり★★。★着手直前に `grep -n "^### D-" \ | tail -3` で実測する★ |
| 項目 | なぜ |
|---|---|
★未対応 の対訳キー衝突を直す★ |
★英語を配布するか未定★(§2 #1 / D-143)。★★英語配布を決めた時点で必ず直す★★ |
| ★サイドバーの未対応件数バッジ★ | ★PATCH 未実装・DB更新用アカウント未受領★(D-143) |
| ★D-Secアラート「通信断」の色★ | ★アンバー維持で確定★(D-143) |
| ★D-Secアラートの期間フィルタ★ | ★今は入れない★(D-143)。★入れるなら d-watch-api.yaml の変更から★ |
| ★アラート画面のバッテリー残量行★ | ★意図した仕様★(D-105 項5)。★空欄のまま残すのが正★ |
| ★ダッシュボードに日付 / 1h前ST / 30分前ST を足す★ | ★★ユーザーが「出発だけ」と決めた★★(D-143 §3 #3 の追加判断) |
| ★凡例・アドバイス見出しの対訳★ | ★既存の抜け★。★英語配布の判断とセットで別途★ |
★1h前ST / 30分前ST の対訳★ |
★モックも実装も t() を通していない生文字列★。★詳細ログでも同じなので今回は変えない★ |
| ★ダッシュボードの清掃ログをCSVに出す★ | ★ユーザーが求めていない★(CSVは詳細ログのみ) |
★clean_error による絞り込み★ |
★API契約に無い★。★要るなら d-watch-api.yaml の変更から★ |
| ★画面のテストを書けるようにする★ | ★vitest.config.ts の include を広げる話★。★別課題★ |
| ★スマートフォン幅のレイアウト★ | 別課題 |