Newer
Older
client_dwatch / docs / handoff / UI差分第3章_調査_20260916.md

UI差分まとめ 第3章 — 8件の「不具合 / 仕様」振り分け調査

作成: 2026-09-16 / ブランチ: develop(2aaf606 時点) / 調査のみ。コードは1行も変更していない


0. この文書の位置づけ

docs/decisions.md の D-142 項8 の J-6(docs/decisions.md:11232 付近) 「UI差分まとめ 第3章8件が『仕様』か『不具合』か」に答えるための材料です。

  • 対象: docs/UI差分まとめ_20260915.md:206-222 の8件
  • ★本文書は決定ではありません★。決めるのはユーザーで、決めた内容は docs/decisions.md に D-143 として残してください
  • ★実装は Cursor 側★。§11 に指示書AO の段階分け案を置きました
  • ★根拠はすべて実測★(file:line / grep コマンド / git blame)。推測の箇所は「推測」と明記しました

1. 結論 — 3分類の一覧

# 内容 判定 一言
1 「未対応」の対訳キー衝突 ★不具合★ 2026-09-09 に別画面のために足したキーが、7日前から使われていた同じ日本語を巻き込んだ。記録は「登録済みであることを確認した」だけ
2 ダッシュボードの「判定 ?」ボタンが死んでいる ★不具合★ D-053 に真正面から反する。onToggleGradeInfo={() => {}} を渡しているだけで、直すのは2行
3 ダッシュボードの清掃ログ表が4列 ★不具合★ docs/spec.md:32 が「機種・シリアル列を省略」=8列と書いている。★落ちている4列のデータはAPIに全部入っている★
4 サイドバーの未対応件数バッジが無い ★判断が要る★ モックにはあるが docs/spec.md:140 は要求していない。件数は取れるが、共通レイアウトに初めてデータ取得を持ち込むことになる
5 D-Secアラート「通信断」の色が赤→アンバー ★判断が要る★ モックの赤は「トークン期限だけを橙にする」分岐の裏返し。その種別が D-030 で消えた今、単純に戻すのが正しいとは限らない
6 コンパクト表で行展開アドバイスが無効 ★判断が要る★ 実装が根拠にしている「規約」は★どの資料にも存在しない★(grep 0件)。一方 docs/spec.md の F-01 にも行展開は載っていない。資料同士が食い違う
7 D-Secアラートの期間フィルタが無い ★判断が要る★ モックの日付欄は★飾り★(onChange なし)。足すには先に d-watch-api.yaml を直す必要がある(新規パラメータ2つ)
8 アラート画面のバッテリー残量行 ★意図した仕様★ ★記録あり★。D-105 項5 が「画面の残量表示の経路を残す」と明記。第3章の「記録なし」は誤り

内訳: 不具合 3件 / 意図した仕様 1件 / 判断が要る 4件


2. #1 —「未対応」の対訳キーが衝突している

【現状】

lib/i18n.ts は★キーが日本語原文★の1階層の辞書です(lib/i18n.ts:363-366 の translate() は完全一致でしか引かない)。そのため★同じ日本語を別の意味で使うと必ず衝突します★。

// lib/i18n.ts:159-160
// 対応機種レジストリ(lib/config/index.ts の ROBOT_MODELS)に無い機種のバッジ(指示書N)
未対応: "Unsupported",

t("未対応") の呼び出しは★3箇所★(実測):

場所 意味 EN表示 正しい英語
components/features/ds-alerts/DsAlertsScreen.tsx:141 数値カード「未対応」=★未処理のアラート件数★ Unsupported Open
components/features/ds-alerts/DsAlertsScreen.tsx:289 ステータスバッジ「未対応」=★未処理★ Unsupported Open
components/features/facilities/FacilitiesScreen.tsx:180 機種バッジ「未対応」=★サポート外の機種★ Unsupported Unsupported(正しい)

実害: EN表示で、D-Secアラートの「未処理の警報が3件」が「非対応が3件」に化けます。 ★状態の意味が逆どころか別カテゴリになる★ので、英語で運用する担当者は件数の意味を取り違えます。

【モック】

d-watch-mock.jsx は自前の辞書(:129-147)を持ちます。★モック側に 未対応 の対訳エントリはありません★ (grep -n '"未対応"' d-watch-mock.jsx → 辞書行にヒットなし。:189 / :901 の状態文字列のみ)。 つまり★モックでは EN でも「未対応」と日本語のまま出ており、誤訳は実装側で新たに生まれた★ものです。

【記録の有無】 — ★衝突を認めた記録は無い★

$ grep -n "Unsupported" docs/decisions.md
11216:例: ★「未対応」の対訳キー衝突で英語表示が `Unsupported`(非対応)になる★ /

唯一のヒットは D-142(2026-09-16)が★本件を「これから振り分ける宿題」として引用している箇所★で、 決定ではありません。

代わりに、衝突が★見落とされた瞬間★の記録があります(D-087 = 指示書N の検証節):

docs/decisions.md:3084
  および`t("未対応")`が`lib/i18n.ts`に登録済みであることをコードレビューで確認したのみ

git blame で時系列が確定します:

コミット 日付 内容
d59fecb 2026-09-02 DsAlertsScreen.tsx:141 / :289 が t("未対応") を使い始める(当時は辞書に無く EN でも和文=実害なし)
7a68a4d 2026-09-09 指示書N が lib/i18n.ts:160 に 未対応: "Unsupported" を追加 → ★7日前からあった別画面を巻き込んだ★

D-087 の検証文は「登録済みであることを確認した」=★自分が足したキーを確認しただけ★で、 ★他画面が同じキーを使っていないかは見ていません★。

【仕様書】

docs/spec.md / UI仕様書に対訳の規定はありません。d-watch-api.yaml にも該当なし。

【判定】 ★不具合★

  • 意図した記録が無い(上記のとおり、見落としの記録しか無い)
  • 実害がある(EN表示で状態の意味が変わる)
  • CLAUDE.md §9.6「JP/EN切替の器を壊さない」に反する

【直す場合の影響範囲】

★キーを日本語原文にしたまま2つの意味を区別することは、いまの translate() では不可能★です。 どちらかの呼び出し側を変える必要があります。推奨は★D-Secアラート側の文言を変えない方針★ (モックの文言は「未対応」で、CLAUDE.md §5 でモックが文言の正)なので、 ★機種バッジ側のキーを変える★のが副作用が小さいです。

触るファイル 変更
lib/i18n.ts:159-160 未対応 を削除し、"未対応(機種)": "Unsupported" 等の★一意なキー★に置き換え。さらに 未対応: "Open" を D-Secアラート用に登録
components/features/facilities/FacilitiesScreen.tsx:180 t("未対応") → t("未対応(機種)")。★Pill に渡すのは t() の戻り値なので、日本語表示が「未対応(機種)」に変わってしまう★ → ★方式A(t("...").replace())ではなく、表示用の日本語を別に持つ必要がある★(§10 の質問Q1)
components/features/ds-alerts/DsAlertsScreen.tsx:141,289 変更不要(キーはそのまま、対訳が Open になる)
  • 既存テストへの影響: lib/i18n.ts を直接検証するテストは★0件★ (grep -l "i18n\|translate" **/*.test.ts → lib/prechecks/__tests__/label.test.ts と lib/db/__tests__/report-mapping.test.ts のみで、どちらも 未対応 を扱わない)。 lib/facilities/__tests__/query.test.ts:677-694 は isSupportedModel() の判定だけを見ており★文言に依存しない★
  • i18n 両言語: JA は現状維持が必須(モック準拠)。EN は2語に分かれる
  • ダーク/ライト: 影響なし(色は c.wn / THEME_TOKENS 由来で文言と独立)

3. #2 — ダッシュボードの「判定 ?」ボタンが押しても何も起きない

【現状】

ボタンは★compact で隠されていません★(CleanLogTable.tsx:110-120):

<th className={styles.th} style={{ textAlign: "center" }}>
  {t("判定")}{" "}
  <button type="button" className={styles.helpBtn} onClick={onToggleGradeInfo} aria-label="判定基準">
    ?
  </button>
</th>

一方ダッシュボードは★空の関数と false を渡しています★(DashboardScreen.tsx:152-162):

<CleanLogTable
  rows={logs}
  compact
  ...
  showGradeInfo={false}
  onToggleGradeInfo={() => {}}
  onToggleRow={() => {}}
  onOpenAreas={() => {}}
/>

凡例本体は CleanLogTable.tsx:123-152 にあり showGradeInfo でしか開きません。 ★つまり「押しても何も起きない」のは仕様上の制約ではなく、状態を持たせていないだけ★です。

【モック】

d-watch-mock.jsx:301 の ? は★トップレベルの showGradeInfo(:171)★を切り替えます。 LogTable は compact の有無で分岐しないので、★ダッシュボード(:508)でも詳細ログ(:573)でも同じように開きます★。

【記録の有無】 — ★無い★

$ grep -n "showGradeInfo\|凡例" docs/decisions.md
10864:- 画面の凡例(`CleanLogTable.tsx` / `SystemScreen.tsx`)が「◎90%超」「○70%超」のままだった

唯一のヒットは D-139 項8(文言を「超」→「以上」に直した話)で、★ボタンの有効/無効には触れていません★。 docs/spec.md にも記述なし。

【D-053 との整合 — 質問に対する回答】

D-053(docs/decisions.md:333-337)の本文:

  • 決定: グループ・会社の絞り込み <select> は、マスタが揃うまで画面から削除する
  • 理由: 固定の選択肢だけがあって何も起きないUIは、不具合と区別できないため

D-053 が「消す」を選んだのは★機能させるためのデータ(マスタ)が存在しなかったから★です。 本件は★データも凡例本体も既にあり、必要なのは useState 1つ★なので、前提が違います。

同じ趣旨がもう1箇所、lib/i18n.ts:320-321 に明文化されています:

★★disabled だけにして理由を出さないことはしない★★ — 押せないボタンは、理由が書いていないと不具合と区別がつかない(D-053 と同じ理由)

したがって D-053 と整合するのは★「機能させる」★です。 「消す」も D-053 の文言だけ見れば整合しますが、CLAUDE.md §5(モックが画面レイアウト・ 文言・遷移の正)に反します。モックには ? があり、押すと開きます。

【判定】 ★不具合★(直し方は「機能させる」)

【直す場合の影響範囲】

触るファイル 変更
components/features/dashboard/DashboardScreen.tsx const [showGradeInfo, setShowGradeInfo] = useState(false) を足し、:158-159 の false / () => {} を差し替える(★実質2行★)
components/features/detail-log/CleanLogTable.tsx 変更不要
  • 既存テストへの影響: なし(画面のテストは存在しない)
  • ★i18n の落とし穴★: 凡例の文言は★t() を通していません★ (CleanLogTable.tsx:126 判定基準(タスク完了率): / :142 (閾値はシステム設定で変更可能))。 ★開けるようにすると、EN表示で和文の塊が新たに1つ増えます★。 同じ凡例が詳細ログでも開くので★これは既存の抜けであって本件が作る問題ではありません★が、 直すなら同じコミットで対訳を足すのが自然です(辞書に足すのは2キー)
  • ダーク/ライト: 凡例は CleanLogTable.module.css の .legend* を使い、色は CSS 変数。追加作業なし

4. #3 — ダッシュボードの清掃ログ表が4列

【現状】

CleanLogTable.tsx:74-106 が !compact && で★6列を落としています★:

列 compact
施設名 出す
機種 ★落とす★
シリアル ★落とす★
日付 ★落とす★
予定 出す
1h前ST ★落とす★
30分前ST ★落とす★
出発 ★落とす★
完了率 出す
判定 出す

CleanLogTable.tsx:24 の docstring も「4列表示にする(施設名/予定時刻/完了率/判定のみ)」と 自認しています。

【データはあるのか — ★実測した★】

★あります。表示していないだけです。★

ダッシュボードは★詳細ログとまったく同じ API・同じ型★を使っています:

  • DashboardScreen.tsx:38-46 — apiFetch<ListResponse<CleanLogItem>>("/api/clean-logs?result=problem&date_from=…&per_page=5")
  • 型は lib/types/index.ts:202 の CleanLogItem。落ちている列に対応するフィールドが★全部入っています★:
落ちている列 CleanLogItem のフィールド 定義位置
日付 scheduled_at(ISO8601 +09:00) lib/types/index.ts:209
1h前ST status_1h lib/types/index.ts:216
30分前ST status_30m lib/types/index.ts:217
出発 departed lib/types/index.ts:223
(機種) model_name lib/types/index.ts:206
(シリアル) serial_number lib/types/index.ts:207

app/api/dashboard/route.ts は関係ありません(コンパクト版ログは GET /dashboard に含まれない 契約です。d-watch-api.yaml:219 / docs/spec.md:36)。★APIを1本も変えずに列を戻せます。★

【モック】

d-watch-mock.jsx:285-303 の LogTable は★compact で 機種(:290)と シリアル(:291)の 2列だけ★を落とします。:508 がダッシュボードで compact={true}。→ ★8列★。

【仕様書】 ★ここが決定的★

docs/spec.md:32
- 清掃詳細ログ(コンパクト版。機種・シリアル列を省略)

CLAUDE.md §5 で docs/spec.md は★機能一覧の正★です。モックと spec.md が★一致して8列★と 言っており、実装だけが4列です。

【記録の有無】 — ★無い★

$ grep -n "コンパクト版" docs/decisions.md docs/spec.md d-watch-api.yaml
docs/decisions.md:10191  (D-137。「コンパクト版ログを二重に作るな」という API 契約の話)
docs/spec.md:32          (8列=機種・シリアルのみ省略)
docs/spec.md:36          (API再利用の話)
d-watch-api.yaml:219     (API契約の話)
d-watch-api.yaml:313     (D-137 が上に抵触しない旨)

★列数を減らした決定は1件もありません。★ git blame では 47f2df4(2026-08-30「F-01 ダッシュボード画面を実装」)の初回実装から4列で、 docstring の理由(「ダッシュボードではアドバイスを一切出さない規約のため」)は★#6 と同じ 出所不明の「規約」★です(§7 参照)。

【判定】 ★不具合★

モックと docs/spec.md が一致し、記録が無く、データは揃っている。3条件そろっています。

【直す場合の影響範囲】

触るファイル 変更
components/features/detail-log/CleanLogTable.tsx !compact && を★日付・1h前ST・30分前ST・出発の4列から外す★(機種・シリアルは落としたまま)。colSpan={compact ? 4 : 10}(:156 / :205)を★compact ? 8 : 10 に直す★ — ★これを忘れると空表示の行が詰まる★
  • レイアウト: CleanLogTable.module.css:1-5 が .table { min-width: 1000px }、 DashboardScreen.module.css:75-77 が .tableWrap { overflow-x: auto }。 ★横スクロールで収まるので CSS 変更は不要★(モックも minWidth: compact ? 700 : 1000 で同じ考え方)
  • 既存テストへの影響: なし(画面のテストは無い)
  • i18n 両言語: 追加する4列のうち 日付(lib/i18n.ts:27)・予定(:28)・出発(:29)は ★対訳済み★。1h前ST / 30分前ST は★モックも実装も t() を通していない生文字列★ (CleanLogTable.tsx:94 / :99)= EN でも和文。★詳細ログでも同じなので本件では変えない★
  • ★ダーク/ライトと D-077 の副作用★: 1h前ST / 30分前ST は Pill + statusColor()。 D-077(生値表示)により実DBでは英語識別子が入り、lib/theme-status.ts:26-30 の仕様どおり ★全部グレーにフォールバック★します。★ダッシュボードにグレーのPillが2列並ぶ★のは 「意図した挙動」(CLAUDE.md §4.8)ですが、見え方が変わるので §10 の質問Q3に出しました

5. #4 — サイドバーの未対応件数バッジが丸ごと無い

【現状】

components/layout/AppShell.tsx に★バッジの受け口自体がありません★:

  • メニュー定義 :8-34 は { href, id, label } の3キーのみ(badge に相当するものが無い)
  • NavItem(:36-53)は href / label / active だけを受け取る
  • AppShell は★1本もデータ取得をしていません★(useTheme() と usePathname() のみ)

【モック】

d-watch-mock.jsx:188  const unresPre = PRECHK.filter(function (a) { return !a.ok; }).length;
d-watch-mock.jsx:189  const unresDsa = dsaState.filter(function (a) { return a.st === "未対応" || a.st === "対応中"; }).length;
d-watch-mock.jsx:372  { id: "alert", label: t("🔔 アラート"), badge: unresPre },
d-watch-mock.jsx:378  { id: "dsalert", label: t("⚠ D-Secアラート"), badge: unresDsa },
d-watch-mock.jsx:391  {props.badge > 0 && <span style={{ ... background: c.ng, ... }}>{props.badge}</span>}

赤いバッジが2箇所(アラート/D-Secアラート)。0件のときは出しません。

【件数は取れるのか — ★取れる★】

バッジ 取得元 根拠
アラート GET /prechecks の summary.unresolved(未解消+対応中) d-watch-api.yaml:1754-1757、D-054(docs/decisions.md:339)
D-Secアラート GET /ds-alerts の summary.pending + summary.in_progress app/api/ds-alerts/route.ts:69、DsAlertsScreen.tsx:141-142

★モックの unresDsa(未対応+対応中)とちょうど同じ定義★です。値の意味合わせは不要。

【記録の有無】 — ★無い★

$ grep -n "未対応件数\|件数バッジ" docs/decisions.md
(0件)
$ grep -n "サイドバー" docs/decisions.md
10367, 10374, 10488, 10506, 10519, 10522, 10566, 10591   ← すべて D-137 の「スクロール」の話

【仕様書】

docs/spec.md:140
* task: : 共通レイアウト(サイドバー10画面ナビ、ダーク/ライト、JP/EN切替の器)

★バッジは要求されていません★。モックにはあるが spec.md には無い、という状態です。

【判定】 ★判断が要る★

  • 「モックが正」(CLAUDE.md §5)に従えば足すべき
  • docs/spec.md は要求していない
  • ★足すと共通レイアウトに初めてデータ取得が入る★ — これは見た目の差分ではなく★設計上の新要素★です

★実装コストの実態★(これがユーザー判断の材料):

  1. AppShell は app/(dashboard)/layout.tsx 配下の★全10画面で常に描画される★
  2. バッジを出すには★全画面で2本の API を叩く★ことになる(/prechecks と /ds-alerts)
  3. ★件数だけ返す軽いエンドポイントは d-watch-api.yaml に存在しない★ → 既存2本を per_page=1 で叩いて summary だけ使う(items は捨てる)か、 契約に GET /badges のような1本を足すか
  4. ★更新のタイミングが未定義★ — 画面遷移のたびに取り直すのか、一定間隔で更新するのか。 モックはローカル state なので参考にならない
  5. ★D-142 の「対応状況の更新(PATCH)は更新用アカウント未発行のため未実装」★との関係: 件数は★誰も操作できないので減りません★。「未対応 27」が固定表示され続けます

【直す場合の影響範囲】

触るファイル 変更
components/layout/AppShell.tsx メニュー定義に badge を足し、NavItem に受け口を足し、useEffect で2本フェッチ
components/layout/AppShell.module.css .navBadge を追加(モック :391 相当。background: var(--ng))
d-watch-api.yaml ★1本足す案を採る場合のみ★(CLAUDE.md §6-1: 先にYAMLを直す)
lib/i18n.ts 不要(数字だけ)
  • 既存テストへの影響: なし
  • ダーク/ライト: var(--ng) を使えば両対応(lib/theme.ts:16 dark #ef4444 / :35 light #dc2626)

6. #5 — D-Secアラート「通信断」の色が赤→アンバー

【現状】

// components/features/ds-alerts/DsAlertsScreen.tsx:36-38
function typeColor(c: ThemeTokens, type: DsAlertType): string {
  return type === "disconnected" ? c.wn : c.ng;
}

c.wn はアンバー(lib/theme.ts:14 dark #f59e0b / :33 light #d97706)。 この色は★Pill(:264)とカード左端の帯(:259 borderLeftColor)の2箇所★で使われます。

【モック】

// d-watch-mock.jsx:946
var tpCol = a.type === "トークン期限" ? c.wn : c.ng;

★モックでアンバーだったのは「トークン期限」だけ★で、「通信断」と「セキュリティ無効」は どちらも c.ng(赤)でした。

つまり実装は、★D-030 で消えた「トークン期限」の色を、残った「通信断」に付け替えた★形です。 意図的な付け替えなのか、条件式を機械的に書き換えた結果なのかは★コミットからは読み取れません★ (d59fecb 2026-09-02「feat: F-07 D-Secアラート一覧を実装 (D-050)」の初回実装から現在の形)。

【記録の有無】 — ★無い★

$ grep -n "バッジの色" docs/decisions.md
146:  (アドバイスの行バッジの色。無関係)
237:  (判定バッジの色分け。無関係)
$ grep -n "通信断" docs/decisions.md
351, 497, 498, 505, 1138, 1168, 1170, 1778, 1795, 1925, 9720, 9721, 9875
  → すべて「2状態化」「判定ロジック」「ソート順」の話。★色に触れた行は1つも無い★

【★この色が効いてくる事情★】

lib/db/index.ts:1533 の dsAlerts() は★alert_type に常に "disconnected" を入れます★ (:1520-1531 の docstring: security_off に対応するカラムが DB に無い。D-090 項3-9)。

★帰結: 実DB接続時、この画面のカードは★★全部アンバー★★になり、赤は1件も出ません。★ 「セキュリティ無効(=赤=重大)」という対比が画面から消えているため、 ★色で重要度を判断する運用が成立していません★。

【判定】 ★判断が要る★

  • 記録が無い=★不具合の条件の1つは満たす★
  • しかし「モックの赤に戻す」ことが正しいとは限らない:
    • モックの赤は「トークン期限だけを橙にする」ための消極的な色分けで、 ★「通信断は重大」という積極的な意味づけの記録はどこにも無い★
    • 一方 docs/decisions.md:497-498(D-065)は★「異常=通信断は同じもの」★と確定しており、 D-Secログ画面では★異常=ng(赤)★で描かれています(DsLogScreen.tsx:308 の tone="ng")。 ★同じ事象が2画面で違う色★になっているのは、どちらかに揃えるべき論点です

【直す場合の影響範囲】

触るファイル 変更
components/features/ds-alerts/DsAlertsScreen.tsx:36-38 1行(c.wn → c.ng、または typeColor() ごと削除して常に c.ng)
  • 既存テストへの影響: なし(lib/ds-alerts/__tests__/query.test.ts は絞り込み・ソートのみ検証)
  • i18n: 影響なし
  • ダーク/ライト: THEME_TOKENS 経由なので両対応のまま
  • ★どちらを選んでも docs/decisions.md に記録を残すこと★(次に突き合わせた人が同じ問いを立てるため)

7. #6 — コンパクト表で行展開アドバイスが無効

【現状】

// components/features/detail-log/CleanLogTable.tsx:162
const hasAdvice = !compact && (r.grade === "△" || r.grade === "×");

hasAdvice が false なので、行の onClick(:171)も ▼ の表示(:197-199)も出ません。 ダッシュボード側は onToggleRow={() => {}} / expandedId={null} / adviceById={{}} を 渡しています(DashboardScreen.tsx:155-161)。

根拠として docstring(CleanLogTable.tsx:25)にこう書かれています:

ダッシュボードではアドバイスを一切出さない規約のため、compact 時は行クリック・アドバイス展開も無効化する。

【★この「規約」は存在しない★】

$ grep -n "アドバイスを一切\|ダッシュボードではアドバイス" docs/decisions.md docs/spec.md docs/handoff.md docs/変更点まとめ_20260914.md
(0件)
$ grep -n "行展開" docs/decisions.md
7973:(★ダーク/ライトの見た目・JP/EN の切替・CSVの中身・行展開・備考編集★)
   → D-120 の「目視確認が要る項目」の列挙。方針ではない

git blame によれば docstring は 47f2df4(2026-08-30、F-01 初回実装)で書かれたもので、 ★実装者の自己申告であり、どの資料にも裏付けがありません★。

【モック】

d-watch-mock.jsx:311(LogTable 内):

var hasAdvice = r.g === "△" || r.g === "×";

★compact で分岐していません★。展開行の colSpan にだけ compact ? 8 : 10 が入っており (:331)、★compact でも展開する前提で書かれています★。

【仕様書】

docs/spec.md:29-36   F-01 ダッシュボード
  - 清掃詳細ログ(コンパクト版。機種・シリアル列を省略)   ← 行展開の記述なし
docs/spec.md:43-52   F-02 清掃詳細ログ
  - **行展開アドバイス(Phase A)**: △×行クリックで R1〜R4 の推定原因とアクションを表示

★行展開は F-02 の機能として書かれており、F-01 の箇条書きには出てきません。★ ただしこれは「F-01 では禁止」と書いてあるわけではなく、★ブロックの列挙に含まれていない★だけです。

【判定】 ★判断が要る★

  • ★モック(レイアウト・遷移の正)は「展開できる」★
  • ★docs/spec.md(機能一覧の正)の F-01 には載っていない★
  • 実装が挙げた根拠(「規約」)は★存在しない★

資料が正面から食い違うので、CLAUDE.md §5 の最後の指示「それでも解決しない場合は ★実装を止めて確認を求める★」に該当します。

★運用上の意味★: ダッシュボードは「昨夜の結果を10分で把握」する画面(docs/spec.md:12)で、 表示されるのは★当日の △× 上位5件のみ★(DashboardScreen.tsx:38-43 の result=problem / per_page=5)。★問題行しか出ない表で理由が見られない★のは導線として不自然、というのが 「直す」側の論拠です。「直さない」側の論拠は「ダッシュボードは要約に徹し、 理由は『全項目 →』で F-02 に飛ぶ」という切り分け(DashboardScreen.tsx:36-37 のコメント)。

【直す場合の影響範囲】

触るファイル 変更
components/features/detail-log/CleanLogTable.tsx:162 !compact && を外す
components/features/dashboard/DashboardScreen.tsx expandedId / adviceById / adviceLoadingId の state と、GET /api/clean-logs/{id}/advice の取得を足す(★DetailLogScreen.tsx の実装をそのまま移植できる★)
  • ★colSpan を compact ? 4 : 10 から直すこと★(:205)。#3 を先に直すなら compact ? 8 : 10
  • 既存テストへの影響: なし。app/api/clean-logs/[id]/__tests__/route.test.ts は既にあり、 ★APIは変更不要★
  • i18n: 展開部の見出し(CleanLogTable.tsx:37-51 の「🔴 レベル3 — DFA介入を検討」等)は ★t() を通していない★ため EN で和文。詳細ログでも同じ既存の抜け
  • ★アドバイス0件時の分岐★: :210-214 に「該当するアドバイスはありません」があり、 ダッシュボードでもこの経路が出ます

8. #7 — D-Secアラートの期間フィルタが無い

【現状】

DsAlertsScreen.tsx:86-97 が組み立てるクエリは★日付を含みません★:

type / status / group_id・customer_id(分類) / per_page / page

サーバ側 lib/ds-alerts/query.ts:46-82 の parseDsAlertQuery() も date_from / date_to を ★読みません★(TYPE_FILTERS / STATUS_FILTERS / parseMasterIdFilters / page / per_page のみ)。

【モック】

d-watch-mock.jsx:916-921 に日付入力が2つあります。ただし★飾りです★:

<input type="date" defaultValue="2026-03-20" ... />   // :918 — onChange なし
<span>〜</span>
<input type="date" defaultValue="2026-03-27" ... />   // :920 — onChange なし

★絞り込みロジック(:937-945)は日付を一切参照していません。★ 第3章の「期間の絞り込みが無い」は正しい指摘ですが、★モック側も動いてはいなかった★点は 記録しておく価値があります(docs/UI差分まとめ_20260915.md:138 は「あり」としか書いていません)。

【記録の有無】 — ★無い★

$ grep -n "D-Secアラート.*期間\|ds-alerts.*date\|期間の絞り込み\|期間フィルタ" docs/decisions.md
(0件)

【API契約】 ★足りない★

d-watch-api.yaml:1131-1149(/ds-alerts の parameters):

GroupId / CustomerId / type / status / Page / PerPage

★date_from / date_to がありません。★ CLAUDE.md §6-1「API契約を変えたくなったら、先に d-watch-api.yaml を直す」に従い、 ★実装より先に YAML を直す必要があります★(= 1行の修正では済まない)。

比較: GET /prechecks は date_from / date_to を持ち、フィルタの前例もあります (lib/prechecks/query.ts:65-69 の検証、:119-127 の filterPrechecksByDate())。 ★同じ形をそのまま写せます。★

【実装方式】 — ★SQLに降ろさなくてよい★

docs/decisions.md:9550-9560(D-132 項7)が★実測で結論を出しています★:

★結論: /facilities /schedules /ds-devices /ds-alerts は降ろす価値がない。★ /ds-alerts | ★20行★

app/api/ds-alerts/route.ts:64-74 と同じくメモリ内で絞る形で足せます。 (lib/db/index.ts:1533 の dsAlerts() は毎回全件読みですが、★D-132 で「そのままでよい」と 判断済み★なので本件の論点ではありません)

【判定】 ★判断が要る★

  • 記録は無い
  • が、モックの当該UIは★動いていなかった飾り★であり、「モック準拠で戻す」という単純な話ではない
  • API契約の変更を伴う=★新規の作業★であって「戻す」ではない
  • ★実害の程度が論点★: 並び順は detected_at 降順固定(lib/ds-alerts/query.ts:110-117)+ ページ送りがあるので、直近のものは1ページ目に出ます。一方で★対応状況の更新(PATCH)が 未実装(更新用アカウント未発行)★なので、★status=open の既定表示に古いアラートが 永遠に溜まり続けます★ — ここが「期間で絞りたい」の実需です

【直す場合の影響範囲】

触るファイル 変更
d-watch-api.yaml(:1131 付近) date_from / date_to を parameters に追加(/prechecks の定義をコピー)
lib/ds-alerts/query.ts DsAlertQuery に dateFrom / dateTo、検証(422)、filterDsAlertsByDate() を追加
app/api/ds-alerts/route.ts:70-73 日付フィルタを type/status と同じ層に差し込む。★summary より後(分類より後)★に置くこと — ★summary の母集団を変えるかどうかが設計判断★(§10 の質問Q5)
components/features/ds-alerts/DsAlertsScreen.tsx 日付入力2つ。★既定値は空=全期間★(AlertsScreen と揃える。docs/UI差分まとめ_20260915.md:87)。★変更時に setPage(1)★(D-120 の規則)
lib/ds-alerts/__tests__/query.test.ts ★テスト追加が要る★(filterPrechecksByDate のテストが手本)
lib/i18n.ts 開始日 / 終了日 は★既存★(:347-348)。追加不要
  • 既存テストへの影響: lib/ds-alerts/__tests__/query.test.ts は既存分が★壊れません★ (既定値が「絞らない」なので)
  • ダーク/ライト: 日付入力の CSS は AlertsScreen.module.css の .dateGroup を写す

9. #8 — アラート画面のバッテリー残量行

【現状】

// components/features/alerts/AlertsScreen.tsx:285-290
{item.battery_level !== null && (
  <div className={styles.batteryLine}>
    {t("残量")} {item.battery_level}%
  </div>
)}

★null ガードがあるので、実DBでは1件も描画されません★(DOMにも出ません)。 USE_FIXTURES=true のときだけ表示されます。

【モック】

★モックにこの行はありません★(d-watch-mock.jsx の PRECHK カードに残量の表示なし)。 第3章の分類どおり「モックに無いが追加した」項目です。

【記録の有無】 — ★★ある★★(第3章・第4章の「記録なし」は誤り)

★記録は2件、どちらも明示的です。★

(1) D-105 項5(docs/decisions.md:5596-5604) — 「なぜ null にしたか」と★「なぜ画面の行は残すか」★:

docs/decisions.md:5596  #### 5. ★★`battery_level` は常に `null`(D-090 項8 の解決)★★
docs/decisions.md:5597  - ★★実DB経由では全件 `null`★★(実測。27件すべて `null`)
docs/decisions.md:5599  - ★理由★: `robots.battery` は現在値のみで履歴が無いため…
docs/decisions.md:5602  - ★fixtures には値が入ったまま★(画面の残量表示の経路を残すため)。
docs/decisions.md:5603    ★`USE_FIXTURES` の true / false で差が出るのは**想定内**★

(2) D-114(docs/decisions.md:7138) — TeamWorks 第8回の回答で★恒久化★:

docs/decisions.md:7138  - ★A9-1: バッテリー残量の履歴は残さない★ → ★★`battery_level` は恒久的に `null` で確定★★

【「行ごと消す」か「空欄のまま残す」か — ★決定の文面から読み取れる答え★】

★★残す★★です。根拠は D-105 項5 の一文:

★fixtures には値が入ったまま★(★画面の残量表示の経路を残すため★)

★「経路を残す」と明示的に書かれている★ので、行を消すことは決定の意図に反します。 補強材料:

  • CLAUDE.md §9.5 — ★「fixtures はDB接続後も削除しない(テスト・デモ用)」★
  • D-105 項5 — ★「USE_FIXTURES の true / false で差が出るのは想定内」★
  • D-105 項5 末尾 — ★「事前チェック時点の残量を取るには TeamWorks 側のスキーマ変更が要る」★= ★将来復活しうる★。消すと、そのときに画面を書き直すことになる
  • d-watch-api.yaml の PrecheckItem にも battery_level を★意図的に足してある★(D-105 項5)

【判定】 ★意図した仕様★

★ただし docs/UI差分まとめ_20260915.md:89 / :244 の「★記録なし★」は事実に反します。★ この2行の訂正が必要です(本文書に基づき「D-105 項5 / D-114」と書き換える)。

【影響範囲】

★コード変更は不要★。文書のみ:

触るファイル 変更
docs/UI差分まとめ_20260915.md:89 根拠欄を「★記録なし★」→「D-105 項5(経路を残すと明記)/D-114(null で恒久確定)」
docs/UI差分まとめ_20260915.md:244 第4章の行を削除(記録が存在するため)

10. ★調査中に見つかった、第3章に載っていない問題★

追加1 — ★英語表示の抜けは第2章の記載より広い(★実測37キー★)★

第2章(docs/UI差分まとめ_20260915.md:196-203)は EN の抜けを 「D-Secログ/D-Secアラート/読み込み中/ログイン画面」と書いていますが、 ★components/** の t() 呼び出しを全部拾って辞書と突き合わせた★ところ、 ★ダッシュボードとアラート画面にも抜けがあります★(第2章に記載なし):

画面 辞書に無いキー 位置
★ダッシュボード★ 成功 / 失敗 / 未実施 / 成功率 / 予定時刻経過・実績なし / 全項目 → DashboardScreen.tsx:101,107,113,114,122,148
★アラート(F-03)★ 未解消 / 解消済み / 合計 / 要対応 / 対応完了 / 全アラート / 未解消のみ / 解消 / 対応中 / スケジュール開始前のエラー検知と出発失敗 / 該当するアラートはありません AlertsScreen.tsx:138,142,143,144,231,251,260,298,303,308

★ダッシュボードは「ログイン後の最初の画面」(docs/spec.md:12)★なので、 EN に切り替えると★最初に見る4枚の数値カードが全部和文★になります。

追加2 — ★#1 と同じ衝突が、次に対訳を足したときに起きるキーが1つある★

要対応 が★意味の違う2箇所★で使われています(どちらも現在は辞書に無く和文のまま):

位置 意味
components/features/alerts/AlertsScreen.tsx:142 未解消アラートのカードの補助文(★人が対応すべき件数★)
components/features/ds-logs/DsLogScreen.tsx:308 異常デバイスのカードの補助文(★機器が異常★)

★追加1 を直すときに 要対応: "Action Required" のように素直に足すと、#1 とまったく同じ構造で 2画面を巻き込みます。★ 先に方針(§11 段階1)を決めてから足すこと。

なお 注意 は ReportsScreen.tsx:394(施設ステータス)と SystemScreen.tsx:67,446 (アドバイスのレベル1)の2系統で共用されています。今は両方 "Caution" で成立していますが、 ★同じ構造のリスクを抱えています★。

追加3 — ★未使用のまま残っている危険な1文字キーが4つ★

lib/i18n.ts:126-129:

他: "View ",
"件を表示 →": " more →",
全: "View all ",
"施設を表示 →": " facilities →",

★4つとも使用箇所0件★(実測。grep -rn 't("他")' components/ app/ lib/ 等はいずれも lib/i18n.ts:120 のコメント行しかヒットしない)。lib/i18n.ts:122-123 が ★自分で警告しています★:

★注意★ 他 / 全 のような1文字キーは他画面の文言と衝突しやすい

★全 は特に危険★で、誰かが t("全") と書いた瞬間に EN で View all になります。 lib/i18n.ts:111-112 に「使わなかった方は画面実装が固まった時点で削除してよい」とあり、 ★方式A(全{n}施設を表示 →)が確定しているので削除条件は満たしています★。

追加4 — ★孤児になった対訳キー D-Sec ログ★

lib/i18n.ts:36 の "D-Sec ログ": "D-Sec Logs" は★使用箇所0件★。 h1 が「D-Secログ」(半角スペースなし。DsLogScreen.tsx:301)に変わったためです。 第1章 1-6(docs/UI差分まとめ_20260915.md:121)が「副作用で英語表示の対訳が当たらない」と 書いているのと同じ事象ですが、★辞書側にも死んだキーが残っている★点は未記載でした。


11. ★ユーザーに聞くべきこと(実例つき)★

Q1 — #1 の直し方: 日本語表示はどちらも「未対応」のままにするか

translate() はキー=日本語原文なので、2つの意味を区別するには★片方のキーを変える★しかありません。 キーを変えると★日本語表示もそのキーの文字列になります★(Pill text={t(...)} に渡すため)。

案 JA表示(施設マスタの機種バッジ) JA表示(D-Secアラート) EN 備考
★案A★ 「未対応」のまま 「未対応」のまま 機種=Unsupported / アラート=Open ★translate() に「画面ID」等の第2キーを足す必要がある★(i18n の仕組みを変える)
案B ★「未対応機種」★ 「未対応」のまま 機種=Unsupported model / アラート=Open ★仕組みは変えない★。JA表示が1語増える(D-087 の「色だけでなく必ず文字を出す」には反しない)
案C 「未対応」のまま ★「未処理」★ 機種=Unsupported / アラート=Open ★モックの文言(「未対応」)から外れる★。CLAUDE.md §5 に抵触

★この選択をするとこうなる★:

  • 案Aを選ぶと lib/i18n.ts の translate() の引数が増え、★t() の呼び出し約180箇所すべてが 影響範囲の候補★になります(実際に直すのは2箇所でも、レビュー範囲が全画面になる)
  • 案Bを選ぶと施設ロボットマスタの機種バッジが「未対応」→「★未対応機種★」に見た目が変わります。 機種名の右隣に出るので「PUDU MT1 ★未対応機種★」となり、むしろ読みやすくなる可能性があります
  • 案Cを選ぶとモックとの文言差分が1件増えます(第1章に追記が必要)

★推奨は案B★(仕組みを変えず、モックの文言から外れるのは機種バッジ側=そもそもモックに無い要素)。

Q2 — #2 の直し方: 「機能させる」で確定してよいか

§3 のとおり D-053 とも CLAUDE.md §5 とも整合するのは「機能させる」です。

★この選択をするとこうなる★: ダッシュボードで ? を押すと表の下に凡例が開きます。 凡例は t() を通していないので★EN でも「判定基準(タスク完了率): ◎ 90%以上 …」と和文★で出ます。 同じコミットで対訳を足すか(2キー)、和文のままにするかを決めてください。

Q3 — #3 で「1h前ST / 30分前ST」の2列を戻すか

docs/spec.md:32 は「機種・シリアル列を省略」なので★文面どおりなら戻す★のですが、 D-077(エラー種別を変換しない)の帰結として★実DBでは英語識別子がグレーのPillで並びます★。

★この選択をするとこうなる★:

案 ダッシュボードの見え方
★全4列戻す(spec.md どおり)★ 「日付 / 予定 / ★Standby. / EmergencyStop★ / ✓✗ / 完了率 / 判定」。★ST2列がグレー一色★(lib/theme-status.ts:26-30 の仕様どおり)
ST 2列だけ落とす 「日付 / 予定 / 出発 / 完了率 / 判定」の★6列★。spec.md から1歩外れるので★記録が要る★

★D-098(docs/decisions.md:4349)でエラーが無いときの status_1h は Standby.(原文どおり)と 確定しています★ので、正常な行は「Standby.」がずらりと並びます。

Q4 — #4 のバッジを足すか。足すなら更新タイミングは

★この選択をするとこうなる★:

  • 足す場合、★全10画面で毎回2本のAPIが余分に走ります★(/prechecks と /ds-alerts)
  • ★PATCH(対応状況の更新)が未実装なので件数は誰にも減らせません★。 「🔔 アラート ★27★」が★毎日固定で表示され続けます★ → ★かえって無視されるようになる★恐れ
  • 足さない場合、モックとの差分1件が「意図した仕様」として docs/decisions.md に残ります

★選択肢★: (a) 今すぐ足す / (b) ★PATCH が実装できるようになってから足す★(そのとき D-143 に条件付きで記録) / (c) 足さない

Q5 — #7 を足すか。足すなら日付はサマリにも効かせるか

D-054 と指示書AK の設計では、★type / status は「見え方」なので summary に効かせない★、 ★分類(group/customer)は「母集団」なので summary に効かせる★、という区別があります (app/api/ds-alerts/route.ts:58-69)。★期間はどちらなのかが未定義★です。

★この選択をするとこうなる★:

案 「2026-09-01〜09-10」を指定したとき
★母集団扱い(分類と同じ)★ 数値カードが★その期間の未対応/対応中/対応済み★になる。一覧と数字が一致する
見え方扱い(type/status と同じ) 数値カードは★全期間のまま★。一覧は10日分だけ。★「未対応 27」と書いてあるのに3件しか出ない★

★母集団扱いを推奨★(「期間を選ぶ」は明らかに対象を選ぶ操作なので、見え方扱いは誤読を招く)。

Q6 — #5 の色をどちらにするか

★この選択をするとこうなる★:

  • ★赤に戻す★: D-Secアラート画面のカードが★全部赤★になります(実DBでは disconnected しか 出ないため)。D-Secログの「異常=赤」と揃います
  • ★アンバーのまま★: 画面が★全部アンバー★のままです。「重大」に相当する赤が1色も出ません
  • いずれにせよ★docs/decisions.md に理由を残すこと★

Q7 — #6 をどちらに寄せるか

★この選択をするとこうなる★:

  • ★展開できるようにする(モック準拠)★: ダッシュボードの5行が展開でき、 ★そのたびに GET /api/clean-logs/{id}/advice が1本走ります★。エリア別モーダルも開けるようになります
  • ★展開させない(現状維持)★: CleanLogTable.tsx:25 の docstring から★「規約のため」を削除★し、 「D-143 で確定」と書き換えてください(★存在しない根拠を引用したままにしない★)

12. ★指示書AO に載せるべき段階分け案★

docs/handoff/指示書AN_総清掃面積の画面を作る.md の書式に合わせ、 ★段階ごとに1コミット★、★番号順に実行★、各段階で npx tsc --noEmit / npm run lint / npm test を通す前提です。

前提: ★着手前にユーザー判断が要る★

★段階1・3・5・6・7 は Q1〜Q7 の回答が無いと着手できません★(CLAUDE.md §0-1「仕様が不明なら止まる」)。 ★段階0 と段階2 だけは判断不要で先に進められます★。

段階 内容 前提となる判断 コミット単位の理由
★段階0★ ★文書の訂正のみ★。docs/UI差分まとめ_20260915.md:89 / :244 の「記録なし」を「D-105 項5 / D-114」に直す(#8) ★不要★ ★コード0行★。混ぜると「文書だけ直したコミット」が追えなくなる
★段階1★ ★#1 対訳キー衝突の解消★(lib/i18n.ts + FacilitiesScreen.tsx 1行) ★Q1★ ★実害のある不具合を単独で追えるようにする★。revert したくなったときに他と混ざらない
★段階2★ ★#2 死んだボタンを機能させる★(DashboardScreen.tsx に useState 1つ) ★Q2(推奨で確定なら不要)★ 2行。★D-053 違反の解消という単一の理由★
★段階3★ ★#3 コンパクト表の列を戻す★(CleanLogTable.tsx の !compact と colSpan) ★Q3★ ★colSpan の直し漏れが起きやすい★ので、他と混ぜず単独で目視確認する
★段階4★ ★追加1・追加2 の EN 抜けを埋める★(ダッシュボード6キー・アラート11キー。★要対応 は段階1 の方針に従う★) ★Q1(要対応 の扱いが同型)★ ★辞書だけの変更★。段階1 の方式が決まっていないと同じ地雷を踏む
★段階5★ ★#5 「通信断」の色★(1行)+ docs/decisions.md への記録 ★Q6★ ★1行だが決定の記録が本体★。混ぜると記録が埋もれる
★段階6★ ★#6 行展開★(実装する場合のみ。しない場合は★docstring の訂正だけ★) ★Q7★ ★段階3 の colSpan に依存する★ので必ず後
★段階7★ ★#7 期間フィルタ★。★7-1 YAML → 7-2 query.ts + テスト → 7-3 ルート → 7-4 画面★の4コミットに割る ★Q5★ ★CLAUDE.md §6-1(YAMLが先)★。テストを実装より先に置く(§6-2)
★段階8★ ★#4 サイドバーのバッジ★(足す場合のみ) ★Q4★ ★共通レイアウトに初めてフェッチを入れる★ので、他と混ぜると全画面の回帰が切り分けられない
★段階9★ ★追加3・追加4 の死んだ辞書キー4+1件を削除★ ★不要★ 掃除。★段階4 の後★(足す作業と消す作業を同じコミットに入れない)
★段階10★ ★docs/decisions.md に D-143 として8件の振り分けを記録★+ docs/UI差分まとめ_20260915.md 第3章・第4章の更新 — ★記録が最後★(実際にやったことを書くため)

★段階分けの原則(この指示書で守ってほしいこと)★

  1. ★判断不要の段階(0・2・9)と判断待ちの段階を混ぜない★ — 回答待ちで全部止まるのを避ける
  2. ★#3 → #6 の順序は固定★(colSpan の依存)
  3. ★#7 は YAML から★(CLAUDE.md §6-1)
  4. ★色・文言の変更は必ず docs/decisions.md への記録とセット★ — 記録が無いから今回の調査が発生した
  5. ★git add -A を使わない★(CLAUDE.md §7)

★指示書AO で「絶対に触らない」と書くべきファイル★

d-watch-mock.jsx(資料。docs/UI差分まとめ_20260915.md:262)/ lib/judge/** / prisma/** / docs/handoff/TeamWorks確認事項_第9回_20260915.md / docs/handoff/セキュリティ申し送り_20260916.md / docs/handoff/pdf/** / README.md / docs/code-map.md


13. 使った検証コマンド(再現用)

# 決定の位置(★番号順に並んでいないので必ずこれで取る★。最新は D-142)
grep -n "^### D-" docs/decisions.md

# #1
grep -rn "未対応" components/ lib/ app/ --include=*.tsx --include=*.ts
grep -n "Unsupported" docs/decisions.md            # → 11216(D-142 の引用のみ)
git blame -L 158,161 --date=short lib/i18n.ts
git blame -L 141,141 --date=short components/features/ds-alerts/DsAlertsScreen.tsx

# #2
grep -n "showGradeInfo\|凡例" docs/decisions.md    # → 10864 のみ(文言の話)

# #3
grep -n "コンパクト版" docs/decisions.md docs/spec.md d-watch-api.yaml
sed -n '202,260p' lib/types/index.ts               # CleanLogItem に全フィールドがある

# #4
grep -n "未対応件数\|件数バッジ" docs/decisions.md  # → 0件
grep -n "サイドバー" docs/decisions.md              # → すべて D-137(スクロール)

# #5
grep -n "バッジの色" docs/decisions.md              # → 146 / 237(どちらも別物)
sed -n '946,947p' d-watch-mock.jsx

# #6
grep -n "アドバイスを一切\|ダッシュボードではアドバイス" docs/decisions.md docs/spec.md docs/handoff.md docs/変更点まとめ_20260914.md   # → 0件
grep -n "行展開" docs/decisions.md                  # → 7973 のみ(目視項目の列挙)

# #7
grep -n "D-Secアラート.*期間\|ds-alerts.*date\|期間フィルタ" docs/decisions.md   # → 0件
sed -n '1131,1149p' d-watch-api.yaml                # parameters に日付が無い

# #8
grep -n "battery_level" docs/decisions.md           # → D-105 項5(5596-5604)/ D-114(7138)

# 追加1(EN の抜け)— t() のキーを全部拾って辞書と突き合わせる
grep -rno 't("[^"]*")' components/ --include=*.tsx