/errors)を作る(robot_error_reports の未対応エラーを一覧し、対応状況と備考を更新する)★前提: 指示書AV(段階5b を含む)は実装済み(HEAD 79485ce 時点)。★ (AV の実装コミットは 1501173〜939a2c5、段階5b(清掃詳細ログの既定を全期間に)は 137a94b。本書はその上に足す)
日付: 2026-10-06 / 担当: Cursor(★実装は Claude Code(2026-10-06。ユーザー指示「ここで実装する」)。§13★)/ 基点: develop 79485ce 前提: ★D-160(本書の根拠。docs/decisions.md:12612-12622)★ / D-152(ステージングへの4カラム書き込みを解禁。:12325-12356)/ D-156 項3・4(AT の @updatedAt 例外・書き込みの規則。:12475-12489)/ D-158 P-8〜P-10(エラーの3区分・robot_error_reports を正とする。:12523-12581)/ D-105(PrecheckItem.id は string・error_id を Number() に通さない。:5442 以降)/ 指示書AT(4カラム UPDATE)/ 指示書AU 段階0-4(robot_error_reports は select で読む)・段階3b(予定は2表から)/ CLAUDE.md §3・§4.8・§6・§7・§9.3・§9.4・§9.6
★期待値表(§3 の表1)は監督が確定した正。変えないこと★(CLAUDE.md §0-2・§6-2・§9.4)。 ★表2・表3 は本書で表1 と既存のコードから導いたもの★。★監督が確認し正とした(2026-10-06。§11 Q14)★。 ★§11 の質問に回答が来て本書の暫定が変わったら、§11 の各行に書いた「直す場所」を本文と同時に直すこと★ (本文と末尾の決定が食い違ったまま渡さない)。
★行番号はすべて 2026-10-06 の develop 79485ce(AV 実装後)で確かめたもの★ (f424689 以降のコミットは資料だけで、app/ lib/ components/ d-watch-api.yaml prisma/ に差分が無いことを git diff で確かめた)。 ★着手時に develop が進んでいたら、関数名・文で探し直すこと★(特に lib/db/index.ts・lib/types/index.ts・d-watch-api.yaml・lib/i18n.ts)。
| # | やること | 段階 |
|---|---|---|
| 1 | 新しいページ ★/errors「エラー一覧」★(EN 暫定 "Errors")をメニューの「モニタリング」の ★「アラート」の直後★ に足す |
段階5 |
| 2 | ★robot_error_reports の全行のうち、状態が「未解消」「対応中」のもの★を一覧する。★「解消済みも表示」★ で解消も出す |
段階2〜5 |
| 3 | 一覧から ★対応状況(未解消 → 対応中 → 解消 → 未解消)と備考★ を更新できる。★書くのは4カラムだけ★(指示書AT の書き込みを、事前チェックの行だけから ★全行★ に広げる) | 段階3〜5 |
| 4 | ★エラーの種類(error_type)・レベル(error_level)・シリアルは生値のまま★出す(区分を作らない。D-077 / D-160 項3) |
段階2・5 |
★変えないもの★(D-160 末尾): ★アラート画面(/alerts。事前チェック+出発失敗)の挙動★/★PATCH /prechecks/{id}/status の挙動(事前チェックの行だけ・他は404)★/ ★D-Sec アラート画面とその取得元(control_error_histories。次フェーズで切り替え)★/★AT の @updatedAt 例外とガードテスト★。
/alerts が拾うのは ★事前チェック行(precheck ≠ 0)に紐づくエラー行だけ★(lib/db/index.ts:1366 の clean_timetable_id: { in: 事前チェック行の id }、:1393-1405)。 ★本清掃行に紐づくエラー・予定 ID が NULL のエラーは出ない★(:1387-1388 / :1402-1405。D-104)robot_error_reports は ★エラー全般の表★ になった(第11回 P-8〜P-10。D-158): 制御盤エラー=robot_id NULL・serial_number が制御盤の SN/★セキュリティエラーと「清掃を開始できなかった」=robot_id NULL・serial_number = '0'★(error_type で見分ける)。 ★先方の DDL では robot_id が NULL 可、prisma/schema.prisma:318 は NOT NULL のまま★(db pull 待ち)→ ★robot_id / robot を読むと落ちる★(指示書AU 段階0-4。lib/db/robot-error-select.ts:4-12)| # | 決定 | 理由 |
|---|---|---|
| D1 | ★対象は robot_error_reports の全行★(error_type を問わない。事前チェック・本清掃・予定 ID が NULL・robot_id が NULL の行すべて)。★既定は status が 未解消(0)・対応中(1)★、include_resolved=true で 解消(2) を足す |
D-160 項1 |
| D2 | ★状態コード ↔ enum は既存の lib/db/precheck-mapping.ts の2関数だけ★(resolutionStatusFromRobotErrorStatus() :33 / robotErrorStatusFromResolutionStatus() :53)。★0/1/2 を直書きしない★ — 絞り込みのコードも ["unresolved", "in_progress"].map(robotErrorStatusFromResolutionStatus) で作る |
変換を1箇所に(AT §1 #7) |
| D3 | ★検知時刻 = errored_at ?? created_at★。★既存の detectedAtFromError()(lib/db/precheck-mapping.ts:171)を使い、jstIsoFromDb() に通す★ |
/alerts と同じ規則(D-105。指示書S 3-4) |
| D4 | ★紐づく予定の4区分★: clean_timetable_id が NULL →「予定なし」(none)/★本表にも履歴にも無い id★ →「予定不明」(unknown)/precheckMinutesFromCode(precheck) が null →「本清掃」(main)/それ以外 →「事前チェック(N分前)」(precheck。N は ★生の分数★) |
D-160 / 監督の既定。分数は D-105 項1 と同じく生値(設定の 60/30 に無い値でも出す) |
| D5 | ★予定は2表から引く★: 一覧は findTimetablesByChunks()(lib/db/timetable-source.ts:255)、1件は findTimetableById()(:285)。★同じ id は本表が勝つ★ |
指示書AU 段階3b。表1 E6 |
| D6 | ★robot_error_reports の読み取りは select で列を絞る。robot_id / robot を読まない★。新しい select は lib/db/robot-error-select.ts に定数で置き、lib/db/__tests__/robot-error-select.test.ts:18-23 の一覧に足す |
指示書AU 段階0-4(NULL の行で落ちる) |
| D7 | ★並びは検知時刻の降順、同時刻は error_id の昇順(文字列の辞書順)★ |
監督の既定。sortPrechecks() と同じ(D-105 項2 #4。error_id が数値である保証が無い) |
| D8 | ★SQL に降ろすのは status IN・facility_id IN・検知時刻の期間★。★並べ替えとページ分けは「キーだけの一覧」をアプリで行う★(2段階で読む。下の「D8 の理由」) |
★errored_at ?? created_at の順は Prisma のクエリビルダで書けない★(COALESCE の orderBy が無い。生SQL は禁止)。★§11 Q1★ |
| D9 | ★種類(error_type)の絞り込みは部分一致・大小文字を区別しない★(includesFoldPlain()。lib/format/search-text.ts:190)。★SQL の LIKE にしない(アプリ側で行う)★ |
機種・シリアルと同じ照合(指示書AG D-2)。LIKE は % _ のエスケープと DB の照合順序で fixtures と結果が割れる。★§11 Q4★ |
| D10 | ★施設名の絞り込みは既存の resolveFacilityIdsByName()(lib/facilities/resolve-by-name.ts:78)★、分類は resolveFacilityIdsByMasters()(lib/facilities/resolve-by-master.ts:88)、★2本は intersectFacilityIds()(lib/facilities/intersect-ids.ts:45)で AND★ |
/clean-logs と同じ(app/api/clean-logs/route.ts の取得部分)。★null = 絞らない / [] = 0件★(罠1) |
| D11 | ★更新は新しい PATCH /errors/{error_id}/status★。★robot_error_reports に行があれば書ける(無ければ404)★。★規則は AT のまま★: パッチは planPrecheckStatusUpdate()(lib/prechecks/status-update.ts:50)、本文の検証は parsePrecheckStatusUpdateBody()(:100)、書き込みは writeRobotErrorColumns()(lib/db/robot-error-write.ts:121)を ★そのまま使う★。★改名しない★(§11 Q7) |
D-160 項2。★同じ列を2つの規則で書かない★ |
| D12 | ★PATCH /prechecks/{id}/status は変えない★(事前チェックの行だけ・他は404)。★ルートの共通化はしない★(新しいルートを並べて置く) |
D-160「変えないもの」。AT のルートテストを巻き込まない |
| D13 | ★fixtures は「DB の行の形」で持つ★(日時は toDbJst() を通した壁時計値の Date、id は bigint)。★fixtures と Prisma は同じ純粋関数(行 → API の値・絞り込み・並べ替え)を通す★ |
CLAUDE.md §9.5(判定ロジックは切替の外側)。USE_FIXTURES で件数が変わらない(指示書AC 禁止事項6) |
| D14 | ★fixtures の書き込みは新しい上書き表★(lib/db/fixture-error-report-overrides.ts)。★パッチは robotErrorColumnsFromPatch()(lib/db/robot-error-write.ts:70)で DB の列の値に写してから上書きする★ |
Prisma 版と同じ歯止め・同じ値(toDbJst() 済みの日時)。★AT の fixture-precheck-overrides.ts は触らない★(§11 Q8) |
| D15 | ★メニューは常に出す★(/alerts と同じ)。★AppShell.tsx には契約(facilities.contract)でメニューを出し分ける仕組みが無い★(components/layout/AppShell.tsx:8-34・:126-158。全メニューを無条件に描く)。★本書でも作らない★ |
監督の既定「/alerts と同じ規則」。実測した規則は「無条件」。★§11 Q2★ |
| D16 | ★列の見出しは「施設名」★(監督の依頼文は「店舗」) | CLAUDE.md §8「施設(旧称店舗)」。★既存キー 店舗 は EN が "Stores"(複数形。lib/i18n.ts:390 のレポートの粒度タブ)で列見出しに合わない★。★§11 Q5★ |
| D17 | ★対応状況の巡回順(未解消 → 対応中 → 解消 → 未解消)は純粋関数1本★: AlertsScreen.tsx:80-89 の nextResolutionStatus() を lib/prechecks/status-cycle.ts(新規)へ移し、★/alerts と /errors の両方がそれを import する★ |
★2画面で巡回順が食い違わないため★。/alerts の挙動は変わらない(関数を移すだけ)。★§11 Q10★ |
| D18 | ★レスポンスに clean_timetable_id を載せる(画面には出さない)★ |
「予定不明」の行を調べるときに要る。数値(Number())で返す — clean_timetables.id は BigInt(PrecheckItem の facility_id と同じ作法)。★§11 Q9★ |
errored_at ?? created_at★(D3)。Prisma の orderBy は ★列ごとの並べ替えしか書けず COALESCE を書けない★。 [{ errored_at: { sort: "desc", nulls: "last" } }, { created_at: "desc" }] にすると ★errored_at が NULL の行(表1 E7)は検知時刻に関係なく最後に回る★ — 表1 の並びと食い違う(例外もテスト失敗も出ない)。$queryRaw)は禁止★(CLAUDE.md §3。ESLint でも禁止。D-122)。error_id / clean_timetable_id / facility_id / error_type / errored_at / created_at / status)を、★SQL で status IN・facility_id IN・期間を絞って★ 全件読むerror_id だけ★ 列を足して読み直す(最大 PAGINATION.maxPerPage = 200件。lib/config/index.ts:421-424)take / skip を SQL に降ろしていない★(lib/types/index.ts:588 付近「page / per_page … ★take / skip は SQL に降ろさない★(指示書AC D-3)」)。本書の形はそれより軽い(1段目はキーだけ)。| # | 行 | 既定(解消を含めない)で出るか | 「解消済みも表示」で出るか | 紐づく予定の表示 | 検知時刻 |
|---|---|---|---|---|---|
| E1 | status 0・本清掃の予定に紐づく | 出る | 出る | 本清掃 | errored_at |
| E2 | status 1・事前チェック(precheck=30)に紐づく | 出る | 出る | 事前チェック(30分前) | errored_at |
| E3 | status 2・本清掃 | 出ない | 出る | 本清掃 | errored_at |
| E4 | status 0・clean_timetable_id NULL | 出る | 出る | 予定なし | errored_at |
| E5 | status 0・clean_timetable_id が両方の予定表に無い id | 出る | 出る | 予定不明 | errored_at |
| E6 | status 0・予定が clean_timetable_histories にだけある | 出る | 出る | 本清掃(履歴の予定も引く) | errored_at |
| E7 | status 0・errored_at NULL | 出る | 出る | (予定どおり) | created_at |
| E8 | status 0・robot_id NULL・serial_number '0'・error_type SECURITY_ON | 出る | 出る | (予定どおり) | errored_at。シリアル欄は「0」、種類は「SECURITY_ON」のまま |
| E9 | status 0・precheck=10 の予定に紐づく | 出る | 出る | 事前チェック(10分前) | errored_at(設定の 60/30 に無い値でも生の分数で出す) |
並び順: 検知時刻の降順、同時刻は error_id の昇順。
PATCH: AT §3-4 の状態遷移と日時の規則(12行の表)をそのまま適用する。★事前チェック以外の行(E1・E4・E5・E8)にも書ける★こと、★存在しない error_id は404★、★書く列は4列以内(ガードテスト)★。
★テストの書き方の注意(表1 の値を変えずに、テストを確かにするための指定)★:
created_at は9行の中で一番新しく、E7 の errored_at 以外の値より後★にする(例: 他は 10/5 19:00〜22:00、E7 は created_at = 10/6 08:00)。 → ★E7 が先頭に来ること★を確かめる。nulls: "last" の並べ替え(D8 の理由)だと最後に来るので、取り違えを検出できるerror_id の昇順★を確かめる(例: E4 = "E-10"・E5 = "E-9" を同時刻にし、★"E-10" が先★ — 辞書順。数値順なら逆になる)PRECHECK_MINUTES_BEFORE[1](lib/config/index.ts:244)から読む★。★E9 の 10 は設定に無い値であることが本題なので直書きする★(lib/fixtures/prechecks.ts の 20 と同じ扱い。コメントで理由を書く)cleanTimetableHistory)にだけ★ 予定を置く。★本表の findMany が E6 の id を返さない★ことをスタブで確かめるrobot_id: null★ を持たせ、guardNullRobotId()(lib/db/__tests__/robot-error-null-guard.ts)をスタブに入れる(robot_id を読むと落ちる)+09:00 付きの ISO 文字列から toDbJst(new Date(...))★ で DB の壁時計値を作る(スタブが返す Date は DB と同じ形にする)入力は表1 の9行(facility_id は E1〜E5 = 1、E6〜E9 = 2 とする)。
| # | 入力 | 期待 | 確かめること |
|---|---|---|---|
| F1 | 既定(パラメータなし) | E3 以外の8行・meta.total = 8 |
D1 |
| F2 | include_resolved=true |
9行 | D1 |
| F3 | include_resolved=yes(true / false 以外) |
422 | 入力の検証(黙って false に倒さない) |
| F4 | error_type=secur(大小文字違い・部分) |
E8 だけ | D9 |
| F5 | error_type=(空)/空白だけ |
F1 と同じ | 空は「絞らない」 |
| F6 | date_from = date_to = E7 の created_at の日付 |
E7 が入る(errored_at が NULL でも created_at の日付で当たる) |
D3。★SQL 側も errored_at が NULL の行を created_at で絞る★ |
| F7 | date_from > date_to |
422 | parseCleanLogQuery() と同じ文言(lib/clean-logs/query.ts の「date_fromはdate_to以前の日付を指定してください」) |
| F8 | 分類で絞って施設が0件(facility_ids = []) |
0件(★全件にならない★) | 罠1 |
| F9 | 施設名が当たり分類も当たるが積集合が空 | 0件 | D10(AND) |
| F10 | per_page=3&page=2(既定の8行) |
並び順の4〜6行目・meta.total = 8 |
D8 |
| F11 | page=99 |
items: []・meta.total = 8 |
404 にしない(一覧の作法) |
| F12 | status = 3 の行が混ざる |
★既定でも「解消済みも表示」でも出ない・例外にならない★ | SQL の status IN で外れる。純粋関数も生の数値で比べるので resolutionStatusFromRobotErrorStatus() の例外は起きない。★§11 Q3★ |
errorScheduleLabel()。★本書で導いたもの。監督確認済み・正★)| # | schedule_link / precheck_minutes |
JP | EN(暫定) |
|---|---|---|---|
| S1 | main / null |
本清掃 | Main run |
| S2 | precheck / 30 |
事前チェック(30分前) | Pre-check (30 min before) |
| S3 | precheck / 10 |
事前チェック(10分前) | Pre-check (10 min before) |
| S4 | none / null |
予定なし | No schedule |
| S5 | unknown / null |
予定不明 | Unknown schedule |
| S6 | precheck / ★null★(API 契約では起きない) |
事前チェック | Pre-check |
S6 は precheckSeverityLabel()(lib/prechecks/label.ts)と同じ考え方 — ★黙って「null分前」を出さない★。
| # | 罠 | どう避けるか |
|---|---|---|
| 4-1 | ★robot_error_reports を select なし・include で読む/robot_id・robot を選ぶ★ |
★robot_id が NULL の行(E8・NOT_STARTED・制御盤)で読み取りが落ちる★(指示書AU 段階0-4)。★robot-error-select.ts の定数だけを使う★。findUnique も同じ |
| 4-2 | ★facility_ids の null と [] を混同する★ |
!== null で分岐する(罠1。lib/db/index.ts:1320-1328 のコメントと同じ)。★?.length で書かない★ |
| 4-3 | ★期間の条件を errored_at だけにかける★ |
★errored_at が NULL の行は created_at で絞る★(OR: [{ errored_at: 範囲 }, { errored_at: null, created_at: 範囲 }])。片方だけだと E7 が期間指定で消える(表2 F6) |
| 4-4 | ★期間の Date を自分で作る★ |
★dbDateRange()(lib/db/date-range.ts:50)の1箇所★。DB の値は JST の壁時計値(CLAUDE.md §3)。★9時間を足さない・引かない★ |
| 4-5 | ★errored_at ?? created_at を SQL の orderBy で近似する★ |
★しない★(D8 の理由)。並べ替えは純粋関数1本 |
| 4-6 | ★take / skip を findTimetables*() に渡す★ |
型で禁止されている(lib/db/timetable-source.ts:57-70)。予定は id で引くだけ |
| 4-7 | ★error_id を Number() に通す★ |
★文字列のまま★(varchar(50)。D-105 項2)。React の key も文字列のまま |
| 4-8 | ★findUnique({ error_id }) の結果をそのまま採る★ |
★DB の照合順序は大小文字を区別しない(ci)★。rer-1 で RER-1 が当たる → ★report.error_id !== id なら null(404)★(precheckItemById() lib/db/index.ts:1517-1521 と同じ。D-4) |
| 4-9 | ★clean_timetables.id の照合を bigint の === で書く★ |
bigint は === で比べられるが Map のキーにはならない → ★.toString() をキーにする★(lib/db/index.ts:1331 の key() と同じ) |
| 4-10 | ★予定を本表だけから引く★ | ★E6 が「予定不明」になる★。findTimetablesByChunks() / findTimetableById() を使う(D5) |
| 4-11 | ★状態コードを直書きする(status: { in: [0, 1] })★ |
D2。robotErrorStatusFromResolutionStatus() から作る |
| 4-12 | ★planPrecheckStatusUpdate() を通さずにパッチを作る/本文を data に渡す★ |
★パッチを作るのは planPrecheckStatusUpdate() だけ★(AT §3-6)。本文の updated_at・error_type が DB に届く経路を作らない |
| 4-13 | ★update(1件更新)を使う★ |
★writeRobotErrorColumns()(updateMany の1文)だけ★(AT §3-2)。★書き込み用の接続(getWritePrisma())で書く★ |
| 4-14 | ★prisma/schema.prisma の RobotErrorReport.updated_at に @updatedAt を付ける★ |
★付けない★(AT §3-1・D-156 項3)。lib/db/__tests__/robot-error-write-sql.test.ts が見張っている |
| 4-15 | ★1段目と2段目の間に行が消える★(ステージングのデータ入れ替え。第10回 N-6) | 2段目で見つからない error_id は ★飛ばす★(★例外にしない★ — 一覧全体が500になる)。meta.total は1段目の件数のまま(1件ずれうる。承知のうえ)。★コメントで理由を書く★ |
| 4-16 | ★/alerts の挙動を変える★ |
AlertsScreen.tsx で変えてよいのは ★nextResolutionStatus() を import に置き換えることだけ★(D17)。★prechecks() / precheckItemById() / /prechecks のルートは触らない★ |
| 4-17 | ★JP/EN の切替で列幅や位置が動く★ | ★table-layout: fixed + <colgroup>★(components/features/detail-log/CleanLogTable.module.css:45・CleanLogTable.tsx:80)。★状態ボタンは固定幅★(「対応中」と "In progress" で幅が違う)。ツールバーの入力欄も固定幅 |
| 4-18 | ★画面の文字列を t() に通さない/キーの全角括弧がずれる★ |
新しい文言はすべて t()(lib/i18n.ts)。★{n} は replace★("{n}分前" の前例)。★既存キーの EN と衝突しないか確かめる★("{n}件" は "{n} facilities"(lib/i18n.ts:274)なので件数表示に使えない。店舗 は "Stores") |
| 4-19 | ★生SQL($queryRaw / $executeRaw)・eslint-disable を足す★ |
★禁止★(CLAUDE.md §3) |
| 4-20 | ★git add -A★ |
★禁止★(CLAUDE.md §7)。ファイルパスを明示する。★作業ツリーに自分の作業でないファイルがあっても含めない★ |
develop で作業する★(.claude/worktrees/ の中ではない)。git pull で最新にし、★AV の実装(段階5b を含む)が入っていること★を git log で確かめる(79485ce 以降・lib/dashboard/latest-run.ts と lib/clean-logs/detail-log-date-range.ts があること)。git status で控える)。npx prisma generate → npx tsc --noEmit -p . → npm test が通ることを確かめ、★テストの件数を記録する★(以後の増分の基準)。通らなければ着手前に報告。node_modules/next/dist/docs/ の Route Handler の項を読む(CLAUDE.md 末尾)。★動的セグメントの params は Promise★(app/api/prechecks/[id]/status/route.ts:88-95 に合わせる)。robot_error_reports の読み取りがすべて定数の select を使っている★ことを確かめる(本書で足す読み取りも同じにする):
grep -rn "robotErrorReport\." lib app --include=*.ts | grep -v __tests__
★この時点ではテストが落ちる(関数・型が無い)のが正しい★。コミットはテストだけで1つ(「テストだけ。この時点では落ちる」と明記)。
lib/db/__tests__/error-report-mapping.test.ts(新規)★:
errorReportItemFromRow(): E1〜E9 の各行 → schedule_link / precheck_minutes / detected_at / serial_number / error_type / error_level が表1 どおり(it.each)errorScheduleLink(): NULL → none/予定が undefined → unknown/本清掃 → main/PRECHECK_MINUTES_BEFORE[1] → precheck・その分数/10 → precheck・10/★予定の id と clean_timetable_id が違う → 例外★(呼び出しの誤り)errorReportKeyFromRow(): detected_at が E7 で created_at、それ以外で errored_at(+09:00 の ISO。★壁時計値のまま★ — toDbJst(new Date("2026-10-05T19:05:00+09:00")) → "2026-10-05T19:05:00+09:00")status に 3 が入った行でも errorReportKeyFromRow() は例外にならない★(キーは生の数値を持つ。表2 F12)lib/error-reports/__tests__/query.test.ts(新規)★:
filterSortErrorReportKeys(): 表2 F1・F2・F4・F5・F6・F8・F12 と、★表1 の並び順★(E7 が先頭・同時刻は "E-10" が "E-9" より先)errorReportStatusCodes(false) = 未解消・対応中のコード、(true) = それに解消を足したもの(★期待値も robotErrorStatusFromResolutionStatus() から作る★。0/1/2 を直書きしない)parseErrorReportQuery(): F3(422)・F7(422)・日付の形式(422)・group_id=0(422。parseMasterIdFilters() に任せる)・page / per_page の丸め(parsePrecheckQuery() と同じ)・error_type / facility_name は前後の空白を除くlib/error-reports/__tests__/label.test.ts(新規)★: 表3 S1〜S6(t は恒等関数と EN 辞書の2通り)lib/prechecks/__tests__/status-cycle.test.ts(新規)★: 未解消 → 対応中 → 解消 → 未解消(3件)lib/db/__tests__/robot-error-select.test.ts: SELECTS(:18-23)に段階2-3 の2つの定数を足す(★robot_id / robot を含まない・clean_timetable_id を含む★がそのまま効く)lib/types/index.tsPrecheckDbQuery(:592 付近)の後に足す。★YAML のスキーマ名と揃える★(CLAUDE.md §9.2):
/** エラー一覧の「紐づく予定」(指示書AW D4)。`d-watch-api.yaml` の `ErrorReportItem.schedule_link` */
export type ErrorScheduleLink = "main" | "precheck" | "none" | "unknown";
/** `GET /errors` の1行(`robot_error_reports` の1行。指示書AW)。`d-watch-api.yaml` の `ErrorReportItem` */
export interface ErrorReportItem {
/** `robot_error_reports.error_id`(★string のまま。`Number()` を通さない。D-105 項2★) */
id: string;
/** `errored_at ?? created_at`(`detectedAtFromError()`)。ISO8601(+09:00) */
detected_at: string;
facility_id: number;
facility_name: string;
/** ★生値★(`'0'` もそのまま。D-077) */
serial_number: string;
/** ★生値★(区分を作らない。D-077 / D-160 項3) */
error_type: string;
/** ★生値★ */
error_level: string;
/** ★画面には出さない★(「予定不明」の調査用。D18) */
clean_timetable_id: number | null;
schedule_link: ErrorScheduleLink;
/** `schedule_link === "precheck"` のときだけ数値(★生の分数★)。それ以外は null */
precheck_minutes: number | null;
/** ★`PrecheckStatus` を使い回す★(名前は歴史的なもの。§11 Q7) */
resolution_status: PrecheckStatus;
responded_at: string | null;
resolved_at: string | null;
note: string | null;
}
/** `DataSource.errorReports()` に渡す取得条件(指示書AW)。★引数は必須★ */
export interface ErrorReportDbQuery {
/** false = 未解消・対応中だけ / true = 解消も含める */
include_resolved: boolean;
/** ★`null` = 施設で絞らない / `[]` = 0件★(罠1) */
facility_ids: number[] | null;
/** JST の `YYYY-MM-DD`(検知時刻の日付)。★下限なしは `null`★ */
date_from: string | null;
/** JST の `YYYY-MM-DD`(★その日を含む★)。★上限なしは `null`★ */
date_to: string | null;
/** 種類の部分一致(大小文字を区別しない)。★`""` = 絞らない★ */
error_type: string;
page: number;
per_page: number;
}
★PrecheckStatusUpdateRequest / PrecheckStatusPatch(:542 / :556)は ★そのまま使う★(新しい型を作らない。D11)。
lib/prechecks/status-cycle.ts(新規)components/features/alerts/AlertsScreen.tsx:76-89 の nextResolutionStatus() を ★docstring ごと★ 移し、export する。★中身は1文字も変えない★。
lib/db/robot-error-select.ts — 定数を2つ足す(:51-64 の後)/**
* エラー一覧の1段目(キーだけ。指示書AW D8)。絞り込み・並べ替えに要る列だけ。
* ★`clean_timetable_id` は並べ替えには使わないが、select の共通テスト(紐づけのキーを含む)のために読む★
*/
export const ROBOT_ERROR_LIST_KEY_SELECT = {
error_id: true,
clean_timetable_id: true,
facility_id: true,
error_type: true,
errored_at: true,
created_at: true,
status: true,
} as const satisfies Prisma.RobotErrorReportSelect;
/**
* エラー一覧の2段目・PATCH の前後の読み取り(`errorReportItemFromRow()`)に要る列。
* ★`errorReports()` と `errorReportById()` が同じ定数を使う★(行の作り方を1本にする。AT 段階4-1 と同じ理由)
*/
export const ROBOT_ERROR_LIST_ROW_SELECT = {
error_id: true,
clean_timetable_id: true,
serial_number: true,
facility_id: true,
facility: { select: { facility_name: true } },
error_type: true,
error_level: true,
errored_at: true,
created_at: true,
status: true,
responded_at: true,
resolved_at: true,
comment: true,
} as const satisfies Prisma.RobotErrorReportSelect;
★robot_id / robot / section を足さない★(4-1)。
lib/db/error-report-mapping.ts(新規)— ★純粋関数のみ★(lib/db/precheck-mapping.ts と同じ作法)/** 1段目の行(`ROBOT_ERROR_LIST_KEY_SELECT` の結果がそのまま入る形) */
export interface ErrorReportKeySource extends PrecheckDetectedAtSource {
error_id: string;
facility_id: bigint;
error_type: string;
status: number;
}
/** 絞り込み・並べ替えに使うキー(★`status` は生の数値★。表2 F12) */
export interface ErrorReportKey {
error_id: string;
facility_id: number;
error_type: string;
/** `jstIsoFromDb(detectedAtFromError(row))` */
detected_at: string;
status: number;
}
/** 2段目の行(`ROBOT_ERROR_LIST_ROW_SELECT` の結果がそのまま入る形) */
export interface ErrorReportRowSource extends PrecheckDetectedAtSource {
error_id: string;
clean_timetable_id: bigint | null;
serial_number: string;
facility_id: bigint;
facility: { facility_name: string };
error_type: string;
error_level: string;
status: number;
responded_at: Date | null;
resolved_at: Date | null;
comment: string | null;
}
/** 予定(本表・履歴のどちらか。`findTimetables*()` の `select: { id: true, precheck: true }`) */
export interface ErrorTimetableRef { id: bigint; precheck: number }
export function errorReportKeyFromRow(row: ErrorReportKeySource): ErrorReportKey
export function errorScheduleLink(
cleanTimetableId: bigint | null,
timetable: ErrorTimetableRef | undefined
): { schedule_link: ErrorScheduleLink; precheck_minutes: number | null }
export function errorReportItemFromRow(
row: ErrorReportRowSource,
timetable: ErrorTimetableRef | undefined
): ErrorReportItem
export function errorReportListWhere(query: ErrorReportDbQuery): Prisma.RobotErrorReportWhereInput
errorScheduleLink(): ①cleanTimetableId === null → none ②timetable === undefined → unknown ③★timetable.id !== cleanTimetableId → 例外★(呼び出しの誤り)④precheckMinutesFromCode(timetable.precheck)(lib/db/precheck-mapping.ts:97)が null → main ⑤それ以外 → precheck・その値。 ★CLEAN_TIMETABLE_PRECHECK.main を自分で比べない★(precheckMinutesFromCode() の1本)errorReportItemFromRow():
id: row.error_id(★string のまま★)/detected_at: jstIsoFromDb(detectedAtFromError(row))facility_id: Number(row.facility_id)/clean_timetable_id: row.clean_timetable_id === null ? null : Number(row.clean_timetable_id)(★BigInt を残すと NextResponse.json が例外★)serial_number / error_type / error_level は ★そのまま★resolution_status: resolutionStatusFromRobotErrorStatus(row.status)(★3以上は例外★ — 1段目で外れているので一覧では来ない。PATCH の1件読みで来たら500。fail-loud)responded_at / resolved_at は jstIsoFromDb()(null はそのまま)/note: row.commentprecheckItemFromErrorRow()(:202-238)と同じ書き方で揃える★(日時・BigInt の扱い)errorReportListWhere()(★SQL 側の写し。正は 2-5 の純粋関数★):
const range = dbDateRange(query.date_from, query.date_to);
return {
status: { in: errorReportStatusCodes(query.include_resolved) },
...(query.facility_ids !== null
? { facility_id: { in: query.facility_ids.map((id) => BigInt(id)) } }
: {}),
...(range.gte || range.lt
? { OR: [{ errored_at: range }, { errored_at: null, created_at: range }] }
: {}),
};★error_type は SQL に降ろさない★(D9)。★page / per_page も降ろさない★(D8)lib/error-reports/query.ts(新規)export interface ErrorReportQuery {
dateFrom: string | null; // ★空文字は null に正規化★(`parsePrecheckQuery()` の注記と同じ理由)
dateTo: string | null;
includeResolved: boolean;
facilityName: string; // trim 済み。"" = 絞らない
errorType: string; // trim 済み。"" = 絞らない
groupId: number | null;
customerId: number | null;
page: number;
perPage: number;
}
export function parseErrorReportQuery(searchParams: URLSearchParams): QueryParseResult<ErrorReportQuery>
export function errorReportStatusCodes(includeResolved: boolean): number[]
export function compareErrorReportKeys(a: ErrorReportKey, b: ErrorReportKey): number
export function filterSortErrorReportKeys(keys: readonly ErrorReportKey[], query: ErrorReportDbQuery): ErrorReportKey[]
parseErrorReportQuery(): 日付の形式は parseDate()(lib/clean-logs/query.ts)、★date_from > date_to は 422(表2 F7)★、★期間の日数の上限は設けない★(/prechecks と同じ。§11 Q13)/ include_resolved は ★無い・空・"false" → false、"true" → true、それ以外 → 422★(表2 F3)/分類は parseMasterIdFilters()(lib/api/master-id.ts:93)/ page / per_page は parsePrecheckQuery()(lib/prechecks/query.ts)と同じ丸め(PAGINATION)。★エラーに入力値をエコーしない★errorReportStatusCodes(): (includeResolved ? ["unresolved", "in_progress", "resolved"] : ["unresolved", "in_progress"]).map(robotErrorStatusFromResolutionStatus)(D2)compareErrorReportKeys(): ★Date.parse(b.detected_at) - Date.parse(a.detected_at)★、0 なら ★a.error_id < b.error_id ? -1 : a.error_id > b.error_id ? 1 : 0★(localeCompare を使わない — 環境で順が変わる)filterSortErrorReportKeys() — ★絞り込みの「正」★(SQL はその写し。指示書AC C-5 と同じ考え方):
status が errorReportStatusCodes(query.include_resolved) に入る(★生の数値で比べる★)query.facility_ids !== null なら facility_id が入る(罠1)detected_at.slice(0, 10)★ が date_from 以上・date_to 以下(★文字列で比べる★。filterFixturePrechecks() lib/db/fixture-prechecks.ts:33-38 と同じ)query.error_type !== "" なら includesFoldPlain(key.error_type, query.error_type)compareErrorReportKeys() で並べる(★入力の配列を書き換えない★。コピーしてから sort)lib/error-reports/label.ts(新規)errorScheduleLabel(t: Translate, item: Pick<ErrorReportItem, "schedule_link" | "precheck_minutes">): string — 表3。 Translate は lib/prechecks/label.ts の型を import する。{n} は replace(precheckSeverityLabel() と同じ方式A)。
段階1 のテストが通ること。コミット。
DataSource(lib/db/index.ts:205。updatePrecheckErrorColumns() の宣言 :300-308 の直後)/**
* エラー一覧(`GET /errors`。指示書AW)。★`robot_error_reports` の全行★が対象(事前チェック・本清掃・予定なし・`robot_id` NULL を問わない)。
* ★2段階で読む★(D8): キーだけを SQL で `status IN`・`facility_id IN`・期間で絞って読み、
* `filterSortErrorReportKeys()` で絞り込み・並べ替え → ページの `error_id` だけ列を足して読む。
* ★`total` は絞り込み後・ページ分け前の件数★(`meta.total`)。
*/
errorReports(query: ErrorReportDbQuery): Promise<{ items: ErrorReportItem[]; total: number }>;
/**
* `PATCH /errors/{error_id}/status` の前後の読み取り。★`error_id === id` の行があれば返す(事前チェックかどうかを問わない)★。
* ★大小文字は JS の `===` で決める★(D-4。`precheckItemById()` と同じ)。★期間・施設・状態では絞らない★
*/
errorReportById(id: string): Promise<ErrorReportItem | null>;
/**
* 4カラムを書く(`writeRobotErrorColumns()` の1本。★書き込み用の接続★)。
* ★`matched` で存在を判定しないこと★ — 存在確認は書く前に `errorReportById()` で行う
*/
updateErrorReportColumns(errorId: string, patch: PrecheckStatusPatch): Promise<{ matched: number }>;
lib/fixtures/error-reports.ts(新規)FixtureErrorReportRow(= ErrorReportRowSource + ★robot_id: bigint | null(読まない。実データの形を示すためだけ)★)。 日時は ★toDbJst(new Date(at(n, "HH:MM")))★(at() は lib/fixtures/_helpers.ts)。施設名は facilities(lib/fixtures/facilities.ts)から id で引いて入れる(★施設名を直書きしない★)errorReportTimetables: ErrorTimetableRef[](★fixtures では本表・履歴を分けない★。E6 相当の行にはコメントで「実DBでは履歴にだけある想定」と書く)"FX-ERR-01" 〜(★lib/fixtures/prechecks.ts の id と重ならない★)。値は ★ダミー★(実在の店名・住所を入れない。CLAUDE.md §7):| id | 内容 | status | 予定 | 備考 | |---|---|---|---|---| | FX-ERR-01 | ロボットのエラー(例 EmergencyStop / ERROR)・本清掃に紐づく | 0 | 本清掃 | E1 | | FX-ERR-02 | ロボットのエラー・事前チェック(PRECHECK_MINUTES_BEFORE[1])に紐づく | 1(responded_at あり) | 事前チェック | E2 | | FX-ERR-03 | 解消済み・本清掃(responded_at / resolved_at / 備考あり) | 2 | 本清掃 | E3 | | FX-ERR-04 | clean_timetable_id NULL | 0 | なし | E4 | | FX-ERR-05 | clean_timetable_id が予定に無い id | 0 | 不明 | E5 | | FX-ERR-06 | 予定が履歴にだけある想定・本清掃 | 0 | 本清掃 | E6 | | FX-ERR-07 | errored_at NULL(created_at だけ) | 0 | 本清掃 | E7 | | FX-ERR-08 | ★robot_id NULL・serial_number '0'・error_type SECURITY_ON★ | 0 | 本清掃 | E8 | | FX-ERR-09 | 事前チェック(★10 を直書き。設定に無い値★) | 0 | 事前チェック | E9 | | FX-ERR-10 | ★robot_id NULL・serial_number = 制御盤の SN(lib/fixtures/ds-devices.ts の DS-001)・error_type D-S_CONTROL_FAILURE★ | 0 | 本清掃 | P-8 a | | FX-ERR-11 | ★robot_id NULL・serial_number '0'・error_type NOT_STARTED・error_level CRITICAL★ | 0 | 本清掃 | ステージング実測の形(D-158 2026-10-06 実測・先方投入) |★error_level の値(CRITICAL 以外)と SECURITY_ON / D-S_CONTROL_FAILURE のレベルは実データ未受領のダミー★ — ファイル冒頭のコメントに書く。lib/fixtures/index.ts に export * from "./error-reports"; を1行足すlib/db/fixture-error-report-overrides.ts(新規)lib/db/fixture-precheck-overrides.ts と同じ形で、★値は DB の列の形★(D14):
const overrides = new Map<string, RobotErrorColumnsData>();
export function resetFixtureErrorReportOverrides(): void
/** 上書きを反映した行(★元の配列の要素は書き換えない★)。`comment` は `status` と同じく上書きのキーがあるときだけ差し替える */
export function withFixtureErrorReportOverride<T extends FixtureErrorReportRow>(row: T): T
/** `robotErrorColumnsFromPatch(patch)` で DB の列に写し、既存の上書きに重ねる。★`error_id === id`★ が無ければ `matched: 0` */
export function applyFixtureErrorReportPatch(rows: readonly FixtureErrorReportRow[], id: string, patch: PrecheckStatusPatch): { matched: number }
robotErrorColumnsFromPatch() を通す★ので、4つ以外のキー・空パッチは Prisma 版と同じく例外になり、日時は ★toDbJst() 済み★(読み出しは jstIsoFromDb() で戻る)fixture-precheck-overrides.ts とは状態を共有しない★(id が重ならないので食い違いは起きない。§11 Q8)fixtureSource。lib/db/index.ts:548-563 の事前チェックの3メソッドの後)async errorReports(query: ErrorReportDbQuery) {
const rows = errorReportRows.map(withFixtureErrorReportOverride);
const keys = filterSortErrorReportKeys(rows.map(errorReportKeyFromRow), query);
const start = (query.page - 1) * query.per_page;
const pageIds = keys.slice(start, start + query.per_page).map((k) => k.error_id);
// 以下、Prisma 版と同じ: id で行を引き、予定を id で引いて errorReportItemFromRow()
...
return { items, total: keys.length };
},
async errorReportById(id) { /* `===` で探し、上書きを反映して errorReportItemFromRow() */ },
async updateErrorReportColumns(errorId, patch) { return applyFixtureErrorReportPatch(errorReportRows, errorId, patch); },
★予定の引き当て(clean_timetable_id → ErrorTimetableRef)は .toString() をキーにした Map★(4-9)。 ★ページの切り出し・行の組み立てを Prisma 版と同じ関数にしたければ、非公開の関数(例 pageOfErrorReports())に切り出して2本で共有してよい★(★規則を2箇所に書かない★)。
prismaSource。updatePrecheckErrorColumns() lib/db/index.ts:1556-1562 の後)async errorReports(query: ErrorReportDbQuery) {
const { getPrisma } = await import("./prisma");
const prisma = await getPrisma();
// 1段目: キーだけ(★SQL で status / 施設 / 期間を絞る。並べ替え・ページ分けはしない★。D8)
const keyRows = await prisma.robotErrorReport.findMany({
where: errorReportListWhere(query),
select: ROBOT_ERROR_LIST_KEY_SELECT,
});
const keys = filterSortErrorReportKeys(keyRows.map(errorReportKeyFromRow), query);
const start = (query.page - 1) * query.per_page;
const pageIds = keys.slice(start, start + query.per_page).map((k) => k.error_id);
if (pageIds.length === 0) return { items: [], total: keys.length }; // ★0件なら2段目を撃たない★
// 2段目: そのページの行だけ列を足して読む
const rows = (await Promise.all(
chunkForInClause(pageIds).map((ids) =>
prisma.robotErrorReport.findMany({ where: { error_id: { in: ids } }, select: ROBOT_ERROR_LIST_ROW_SELECT })
)
)).flat();
// 予定は2表から id で(D5)。★id が0件ならクエリを撃たない★(findTimetablesByChunks が塊0個で返す)
const timetableIds = [...new Set(rows.flatMap((r) => (r.clean_timetable_id === null ? [] : [r.clean_timetable_id.toString()])))].map(BigInt);
const timetables = await findTimetablesByChunks(prisma, chunkForInClause(timetableIds), (ids) => ({
where: { id: { in: ids } },
select: { id: true, precheck: true },
}));
...(rowById / timetableById を `.toString()` / `error_id` で引き、★pageIds の順★に並べて errorReportItemFromRow()。見つからない error_id は飛ばす(4-15))
return { items, total: keys.length };
},
errorReportById()★: findUnique({ where: { error_id: id }, select: ROBOT_ERROR_LIST_ROW_SELECT }) → ★report.error_id !== id なら null★(4-8)→ clean_timetable_id があれば findTimetableById(prisma, report.clean_timetable_id, { id: true, precheck: true })(lib/db/timetable-source.ts:285)→ errorReportItemFromRow()updateErrorReportColumns()★: updatePrecheckErrorColumns()(:1556-1562)と ★同じ中身★(getWritePrisma() → writeRobotErrorColumns())。 ★同じ3行を2回書きたくなければ、非公開の関数に切り出して2本から呼んでよい★(updatePrecheckErrorColumns() の挙動は変えない)lib/db/index.ts:97-127 の並びに足す(ROBOT_ERROR_LIST_*_SELECT・error-report-mapping・fixture-error-report-overrides・errorReportRows / errorReportTimetables)lib/db/__tests__/error-report-mapping.test.ts★ に errorReportListWhere() を足す: 既定 → status: { in: [未解消, 対応中のコード] } だけ/facility_ids: [] → facility_id: { in: [] }(★キーが消えない★)/null → facility_id のキーが無い/ 期間あり → OR の2要素で、gte / lt が ★壁時計値★(date_from = "2026-10-05" → gte = 2026-10-05T00:00:00.000Z、date_to = "2026-10-05" → lt = 2026-10-06T00:00:00.000Z)/期間なし → OR のキーが無いlib/db/__tests__/fixture-error-reports.test.ts(新規)★: fixtures の11行が ★errorReportKeyFromRow() / errorReportItemFromRow() を例外なく通る★/FX-ERR-03 は既定で出ない/ applyFixtureErrorReportPatch() → errorReports() に反映される/resetFixtureErrorReportOverrides() で戻る/存在しない id・大小文字違いは matched: 0/★4つ以外のキーは例外★コミット。
d-watch-api.yaml(★先に直す。CLAUDE.md §6-1★)tags(:39-62)の prechecks(:46-47)の後に - name: errors / description: エラー一覧(robot_error_reports) を足す/prechecks/{id}/status(:690-755)の後・# ---- reports(:757)の前に ★/errors★ と ★/errors/{error_id}/status★ を足す:
GET /errors: description に ★対象は robot_error_reports の全行(事前チェック・本清掃・予定なし・制御盤・セキュリティ・清掃開始失敗を問わない)★/既定は未解消・対応中、include_resolved=true で解消も/ ★並び順は detected_at(= errored_at、無ければ created_at)の降順、同値は id(error_id)の昇順(文字列の辞書順)★/★error_type・error_level・serial_number は DB の値のまま(D-077)★//alerts との違い(あちらは事前チェック+出発失敗だけ)。 パラメータ: date_from / date_to(★検知時刻の日付★。date_from > date_to は422)/ $ref GroupId / $ref CustomerId / facility_name(部分一致。記号を無視)/ error_type(部分一致・大小文字を区別しない)/ include_resolved(type: boolean、default: false。true / false 以外は422)/ $ref Page / $ref PerPage。 200 は ErrorReportListResponse、422 は ValidationErrorPATCH /errors/{error_id}/status: ★/prechecks/{id}/status の description(:694-713)の日時・備考の規則をそのまま写す★。違いは ★「robot_error_reports に error_id の行があれば書ける(事前チェックかどうかを問わない)。無ければ404」★だけ。 パスパラメータは /prechecks/{id}/status と同じくインラインの { type: string, maxLength: 50 }(★共有の PathId(integer)を参照しない★。:715-717 のコメントと同じ理由)。 リクエストは /prechecks/{id}/status と同じ(status 必須・note は maxLength: 400)。200 は ErrorReportItem、404 は Error、422 は ValidationErrorcomponents/schemas の PrecheckListResponse(:1882 付近)の後に ★ErrorReportItem★(2-1 の型と同じ。schedule_link は enum: [main, precheck, none, unknown] と4区分の説明・precheck_minutes は「schedule_link=precheck のときだけ。生の分数」)と ★ErrorReportListResponse★(items + meta: $ref Meta。★summary は無い★)を足すGET /errors — app/api/errors/route.ts(新規)形は app/api/clean-logs/route.ts(動く見本)と app/api/prechecks/route.ts に揃える:
parseErrorReportQuery() → 失敗は 422try の中で★ Promise.all([resolveFacilityIdsByName(q.facilityName), resolveFacilityIdsByMasters(q)]) → intersectFacilityIds()(D10)→ db.errorReports({ include_resolved, facility_ids, date_from, date_to, error_type, page, per_page })logApiError。文言「データの取得に失敗しました」)ListResponse<ErrorReportItem>({ items, meta: { total, page, per_page } })を返す。withRequestId() で包むPATCH /errors/{error_id}/status — app/api/errors/[error_id]/status/route.ts(新規)★app/api/prechecks/[id]/status/route.ts(全173行)と同じ順序・同じ部品★(形式 → 本文 → 実在 → 計画 → 書き込み → 読み直し)。違いだけ書く:
| 箇所 | /prechecks |
/errors |
|---|---|---|
ROUTE |
/api/prechecks/[id]/status |
★/api/errors/[error_id]/status★ |
params |
{ id } |
★{ error_id }★ |
| 実在・読み直し | db.precheckItemById() |
★db.errorReportById()★ |
| 書き込み | db.updatePrecheckErrorColumns() |
★db.updateErrorReportColumns()★ |
| 404 の文言 | 対応状況を記録できるアラートが見つかりません(出発失敗には記録先がありません) | ★「エラーが見つかりません」★ |
監査ログの before の型 |
PrecheckItem |
★ErrorReportItem★(4項目の名前は同じ) |
parsePathErrorId()(lib/api/path-params.ts:89)/parsePrecheckStatusUpdateBody()/planPrecheckStatusUpdate()/isEmptyPatch()/columnsForPatch()(lib/db/robot-error-write.ts:135)/logDbWrite() / dbErrorCode()(lib/logger/db-write.ts:53 / :22)const now = new Date() は1回だけ★。★空パッチなら書かずに 200 で before・db.write も出さない★。★matched === 0 は 404・db.write(success: false, db_error_code: "no_matching_row")★。★書き込みの例外は 500・db.write(success: false)★。★成功時の db.write は書き込み直後に1件★comment_changed: boolean だけ。docs/logging-design.md の禁止キー)auditBase() 等を /prechecks のルートと共有するための書き換えはしない★(D12)。同じ形の小さな関数を新しいルートに置くapp/api/errors/__tests__/route.test.ts(新規。Prisma 経路)★ — app/api/prechecks/[id]/status/__tests__/route.test.ts:1-110 の作法(vi.mock("@/lib/db/use-fixtures", () => ({ USE_FIXTURES: false }))・@/lib/db/prisma をスタブ):
robotErrorReport.findMany は ★guardNullRobotId() を通し★、★select を見て返す列を絞る★(1段目と2段目で返す形が違う)。where は ★status.in / facility_id.in / OR(期間)/ error_id.in を評価★するcleanTimetable.findMany / cleanTimetableHistory.findMany を別の配列から返す(E6 は履歴だけ)。facilityNameIndex 等、施設名・分類の解決に要るものは既存の app/api/__tests__/master-filter.test.ts の作法を見て用意する"E-10" が先)・各行の schedule_link / precheck_minutes / detected_at / serial_number / error_type が表1 どおり。include_resolved=true で9行where が errorReportListWhere() の戻りと toEqual★(ルートが別の条件を組み立てていない)select が ROBOT_ERROR_LIST_KEY_SELECT、2段目が ROBOT_ERROR_LIST_ROW_SELECT★/★0件のとき2段目と予定のクエリを撃たない★app/api/errors/[error_id]/status/__tests__/route.test.ts(新規。Prisma 経路)★ — /prechecks の同名テストの describe を写し、行を表1 にする:
robot_id NULL)・E2(事前チェック)★ に書ける。updateMany の data が該当の列だけupdateMany を呼ばない・db.write も出さない★updated_at・error_type・memo を混ぜても data に出ない★e-1 で E-1)★/matched === 0★PRECHECK_NOTE_MAX_LENGTH + 1)db.write(success: false)と api.error が1件ずつ・コードだけ★db.write: route が /api/errors/[error_id]/status・record_id が文字列・columns が実際に SET した列だけ・備考の中身を含まない★updateMany を呼ばない★(スタブの参照用は updateMany で例外。/prechecks のテスト :96-98 と同じ)app/api/errors/__tests__/route.fixtures.test.ts(新規)★: fixtures で GET(既定で FX-ERR-03 が出ない・include_resolved=true で11行)→ PATCH → GET に反映される/存在しない id は404。beforeEach で resetFixtureErrorReportOverrides()app/api/prechecks/__tests__/route.test.ts・app/api/prechecks/[id]/status/__tests__/*.test.ts・lib/db/__tests__/robot-error-write-sql.test.ts(★/prechecks の挙動と書き込みの SQL が変わっていない証拠★)YAML → ルート → テストの順でコミット(1つでも分けてもよい)。
components/layout/AppShell.tsxMENU_MONITORING(:8-23)の ★/alerts の行(:10)の直後★ に足す:
// ★エラー一覧(D-160 / 指示書AW)★ — robot_error_reports の未対応エラーを全部出す(/alerts は事前チェック+出発失敗だけ)。
// ★契約によるメニューの出し分けは無い(このファイルの全メニューと同じ。指示書AW D15)★
{ href: "/errors", id: "errors", label: "🚨 エラー一覧" },
★ラベルとアイコンは暫定(§11 Q6)★。モックにこのページは無い(★モックは書き換えない★)。
app/(dashboard)/errors/page.tsx(新規)app/(dashboard)/alerts/page.tsx(5行)と同じ形で <ErrorsScreen /> を返す。
components/features/errors/ErrorsScreen.tsx + .module.css(新規)★見た目の作法は既存の画面から借りる。新しい共通部品は作らない★(AT §3-8 と同じ考え方)。
t("エラー一覧")、副題 t("未対応のエラー(ロボット・制御盤・セキュリティ・清掃開始失敗)")。件数は t("全{n}件").replace("{n}", String(total))(★"{n}件" は使わない★。4-18)AlertsScreen.tsx:146-155 の判断と同じ):
<input type="date">。★初期値は空=期間なし★。空の側は URL に付けない。両方空のあいだは t("指定なし(全期間)") を出す — 清掃詳細ログ(AV 段階5b。137a94b)と同じ見せ方。lib/i18n.ts:363 の既存キー)AlertsScreen.tsx:358-410 をそのまま写す。useClassificationFilter(t)・appendClassificationParams()(lib/masters/options.ts:218)。★option のラベルを t() に通さない★)DetailLogScreen.tsx:110-117 の作法。placeholder は t("施設名") / t("種類"))t("解消済みも表示")(既定オフ)setPage(1) をその場で★(AlertsScreen.tsx:162-170 の注記)table-layout: fixed + <colgroup>★。CleanLogTable.module.css:30-50 の作法で、列幅を CSS 変数にし min-width を合計にする。★狭い画面では表を横スクロールの箱に入れる★)。列と幅の目安(★幅は実装者が調整してよい。ただし固定にする★):| 列 | 中身 | 幅の目安 | |---|---|---| | t("検知時刻") | formatMd + formatHm(例 10/5 19:05。AlertsScreen.tsx:42-44) | 96px | | t("施設名") | facility_name | 残り(最小 140px) | | t("シリアル") | serial_number(★0 もそのまま★) | 140px | | t("種類") | error_type(★生値。t() に通さない★。長い値は折り返す) | 170px | | t("レベル") | error_level(★生値。t() に通さない★) | 90px | | t("紐づく予定") | errorScheduleLabel(t, item) | 170px | | t("対応状況") | ★ボタン★(下) | 150px | | t("備考") | ★インライン編集★(下) | 220px |★error_type / error_level / serial_number を t() に通さない理由★: 辞書に同じ綴りのキーがあると誤って訳される(AlertsScreen.tsx:363-364 の「屋号名まで通すと辞書の対訳に誤って当たる」と同じ)AlertsScreen.tsx:46-72(statusBadgeClass() / StatusBadgeText)と :503-511 の見た目を写す。クリックで ★nextResolutionStatus()(lib/prechecks/status-cycle.ts)★ の状態を送る(★備考は送らない★)。★ボタンは固定幅★(4-17)AlertsScreen.tsx:97-140(PrecheckNote)の作法を写す — クリックで textarea・★onBlur で確定★・★変えていなければ送らない★・★失敗時は編集状態のまま★・maxLength={PRECHECK_NOTE_MAX_LENGTH}(lib/config/index.ts:613)・★備考だけ変えるときも現在の status を送る★PATCH /api/errors/${encodeURIComponent(item.id)}/status。★成功したらレスポンスでその1行だけ差し替える★(一覧を取り直さない)。★「解消済みも表示」がオフでも、解消にした行は次に取り直すまで消さない★(押し間違いをその場で戻せる。AlertsScreen.tsx:242-251 と同じ)。 ★保存中はその行のボタン・入力を無効★。保存中・失敗の表示は ★その行の直後に <tr><td colSpan={8}> で★ 出す(t(rowError.message)。サーバーの文言は辞書に無ければ原文)t("該当するエラーはありません") / t("読み込み中…") / サーバーの文言(AlertsScreen.tsx:445-450 の作法。★「読み込み中…」は t() に通す★)Pagination(AlertsScreen.tsx:532-537)。★meta.total を使う★。存在しないページに取り残されたら最終ページへ(AlertsScreen.tsx:219-223。★pages > 0 の判定を落とさない★)/alerts の巡回順を共有する — components/features/alerts/AlertsScreen.tsx★変えてよいのはここだけ★(D17・4-16): :76-89 の nextResolutionStatus() を削り、lib/prechecks/status-cycle.ts から import する。★他は1文字も変えない★。
lib/i18n.ts足すキー(★EN は暫定。ユーザーが後でまとめて見直す★)。★キーは画面の原文と1文字も違わないこと(全角括弧・中点)★。重複は lint が落とす:
// ---- エラー一覧(D-160 / 指示書AW)----
"🚨 エラー一覧": "🚨 Errors",
エラー一覧: "Errors",
"未対応のエラー(ロボット・制御盤・セキュリティ・清掃開始失敗)": "Open errors (robots, control panels, security, start failures)",
"全{n}件": "{n} total",
検知時刻: "Detected at",
種類: "Type",
レベル: "Level",
紐づく予定: "Schedule",
対応状況: "Status",
備考: "Note",
本清掃: "Main run",
"事前チェック({n}分前)": "Pre-check ({n} min before)",
事前チェック: "Pre-check",
予定なし: "No schedule",
予定不明: "Unknown schedule",
解消済みも表示: "Show resolved",
該当するエラーはありません: "No matching errors",
エラーが見つかりません: "Error not found",
★既にあるキーは足さない★: 施設名・シリアル・未解消・対応中・解消・クリックして備考を編集・備考を入力・保存中…・保存に失敗しました・対応状況の更新に失敗しました・読み込み中…・指定なし(全期間)・ホールディングス・顧客(lib/i18n.ts で確かめてから足す。AV の実装でキーが増えていないかも見る)。
★画面のテストは vitest で走らない★(vitest.config.ts:14)ので段階7 の画面確認で見る。★巡回順・ラベルは純粋関数のテスト(段階1)で固定してある★。
docs/fixtures.md: ★§13「エラー一覧(error-reports)」を新設★(§12.3 :439- の後)。3-2 の表(11行・表1 との対応)と、 ★既定で10行(FX-ERR-03 以外)・「解消済みも表示」で11行★、★robot_id は読まない(select で絞る)ので fixtures でも値は使わない★、★error_level 等はダミー★ を書くd-watch-api.yaml / lib/types/index.ts / ルートの冒頭コメント★(段階4 で書いたもの)★CLAUDE.md / docs/decisions.md / docs/code-map.md / docs/spec.md は監督が直す★。
npx tsc --noEmit -p . — 0件npm run lint — 0件(★eslint-disable を1つも足していない★・i18n の重複キー・$queryRaw・any・未使用の import が無い)npm test — ★全件通過★(段階0 の件数から増えていること)USE_FIXTURES=true で npm run build★ — 成功(PowerShell なら $env:USE_FIXTURES='true'; npm run build)。★ワークツリーではなく本体で行う★(AV §13-4。Turbopack が node_modules のジャンクションを拒否する)USE_FIXTURES=true)★: .claude/launch.json の ★d-watch-fixtures(ポート 3100)★ で起動する。★確かめ終わったら自分で起動した dev サーバーは必ず止める★
FX-ERR-08 のシリアルが「0」、種類が「SECURITY_ON」。FX-ERR-09 が「事前チェック(10分前)」、FX-ERR-04 が「予定なし」、FX-ERR-05 が「予定不明」secur・期間・分類)で件数が変わり、1ページ目に戻る/alerts★ の状態ボタンの巡回順が今までと同じ(D17)dwatch_local)で確かめる★(USE_FIXTURES=false。npm run seed:local は ★lib/db/local-guard.ts がローカル以外を拒否する★):
/errors が出る。★シードの事前チェックのエラー行★が「事前チェック(N分前)」で出るdb.write が success: true で出て、route が /api/errors/[error_id]/status・columns が実際に変えた列だけ・備考の中身が無いgrep -rn "\$queryRaw\|\$executeRaw" app lib components
grep -rn "status: { in: \[" lib app --include=*.ts | grep -v __tests__
grep -rn "robot_id: true\|robot: true\|robot: {" lib/db/robot-error-select.ts着手の条件(すべて): ステージングに接続できる(★別の認証情報を探したり試したりしない★)//errors に ★未解消・responded_at / resolved_at / 備考がすべて空★ の行がある。
手順(AT 段階9 と同じ。★ステージングへの4カラムの書き込みはユーザーが許可済み。D-152★):
robot_id NULL の行など)★から選ぶ(★本書で新しく書けるようになった行で確かめる★)error_id と4列(画面・API の値)。★mysql で読むだけ★(★UPDATE / DELETE / INSERT を手で打たない★)updated_at は DB が更新するので戻らない — 値を記録して報告。AT §10 #8 で受け入れ済み)robot_id が NULL の行が一覧に出て、読み取りで落ちない★ことを確かめる(D-158 時点で2行)npm run seed:local をステージングに向けない★(テーブルを消して入れ直すので実データが消える)| ファイル | 内容 | 段階 |
|---|---|---|
lib/db/__tests__/error-report-mapping.test.ts(新規) |
表1 E1〜E9 の行の組み立て・errorScheduleLink()・errorReportKeyFromRow()・errorReportListWhere() |
1・3 |
lib/error-reports/__tests__/query.test.ts(新規) |
表2・表1 の並び順・parseErrorReportQuery()・errorReportStatusCodes() |
1 |
lib/error-reports/__tests__/label.test.ts(新規) |
表3 | 1 |
lib/prechecks/__tests__/status-cycle.test.ts(新規) |
巡回順 | 1 |
lib/db/__tests__/robot-error-select.test.ts |
SELECTS に2つ足す |
1 |
lib/db/__tests__/fixture-error-reports.test.ts(新規) |
fixtures の11行・上書き表 | 3 |
app/api/errors/__tests__/route.test.ts(新規) |
GET(Prisma 経路。表1・表2・2段階の読み方) | 4 |
app/api/errors/[error_id]/status/__tests__/route.test.ts(新規) |
PATCH(Prisma 経路。全行に書ける・404・422・500・db.write) |
4 |
app/api/errors/__tests__/route.fixtures.test.ts(新規) |
fixtures で GET → PATCH → GET | 4 |
| ★無修正で通ること★ | app/api/prechecks/**・lib/prechecks/__tests__/status-update.test.ts・lib/db/__tests__/robot-error-write-sql.test.ts・lib/db/__tests__/precheck-mapping.test.ts |
— |
CLAUDE.md / docs/decisions.md / docs/code-map.md / docs/spec.md(監督が直す)d-watch-mock.jsx(このページはモックに無い。モックは書き換えない)prisma/schema.prisma / lib/generated/prisma/**★(スキーマは変えない。★RobotErrorReport.updated_at に @updatedAt を付けない★。db pull は実行しない)control_error_histories への書き込み・D-Sec アラート(/ds-alerts・PATCH /ds-alerts/{id}/status・DsAlertsScreen.tsx・lib/ds-alerts/**)★(取得元の切り替えは次フェーズ。D-158 項5 / D-160)/alerts の挙動★: prismaSource.prechecks() / precheckItemById() / updatePrecheckErrorColumns() の中身、app/api/prechecks/**、lib/db/fixture-precheck-overrides.ts、lib/fixtures/prechecks.ts。★AlertsScreen.tsx は 5-4 の置き換えだけ★planPrecheckStatusUpdate() / parsePrecheckStatusUpdateBody() / isEmptyPatch() の中身(lib/prechecks/status-update.ts)、lib/db/robot-error-write.ts の中身(★書いてよい列を増やさない★)detectedAtFromError() / precheckMinutesFromCode() / resolutionStatusFromRobotErrorStatus() / robotErrorStatusFromResolutionStatus() の中身(lib/db/precheck-mapping.ts)findTimetables() / findTimetablesByChunks() / findTimetableById() / mergeTimetableRows() の中身(lib/db/timetable-source.ts)lib/dashboard/latest-run.ts・/api/dashboard・DashboardScreen.tsx)scripts/seed-local-db.ts とガード(lib/db/local-guard.ts)eslint-disable の追加★git add -A★error_type にコードを付けるのは次フェーズ★robot_error_reports に切り替えること★(P-10。次フェーズ)NOT_STARTED との関係は次フェーズで整理)/alerts にある未解消件数のバッジ。d-watch-mock.jsx:372)・CSV 出力facilities.contract)によるメニュー・データの出し分け★(D15。§11 Q2)planPrecheckStatusUpdate → 例 planResolutionUpdate、PRECHECK_NOTE_MAX_LENGTH → 例 ERROR_NOTE_MAX_LENGTH、PrecheckStatus → 例 ResolutionStatus)(§11 Q7)/alerts の備考・状態ボタンの部品を /errors と共通の部品にすること(本書は作法を写すだけ)★監督の決定(2026-10-06): Q1〜Q14 はすべて下の表の「本書の暫定」どおりで確定★。本文は暫定のとおり書いてあるので、直す場所は無い。 ★Q1 の補足★: 依頼文の「SQL の take/skip でページ分け」は、errored_at ?? created_at の順を Prisma で書けないため取り下げる(監督の依頼文の誤り)。 ★Q2 の補足★: 契約によるメニューの出し分けが未実装であることは、本書の範囲外として監督が別に扱う(docs/decisions.md D-160 追記)。
| # | 論点 | 本書の暫定 | 直す場所 |
|---|---|---|---|
| Q1 | ★依頼文の「ページ分けは SQL(take/skip)で」と食い違う★。検知時刻(errored_at ?? created_at)の順は Prisma のクエリビルダで書けない |
★キーだけを SQL で絞って全件読み、アプリで並べてページ分け → ページの行だけ読み直す★(D8)。対案: orderBy: errored_at desc nulls last, created_at desc で SQL ページ分け(★E7 の並びが表1 と食い違う★ので採らない) |
§2 D8 / 2-4 / 3-4 / 3-5 / 4-4 |
| Q2 | ★依頼文の「メニューは /alerts と同じ規則(D-Watch の契約)」★ — ★実測では AppShell.tsx に契約による出し分けが無い★(全メニューを無条件に描く。CLAUDE.md §4.6 の「契約のないセクションはメニューごと非表示」は未実装) |
★/alerts と同じく常に出す★。データも契約で絞らない(/prechecks も絞っていない) |
§2 D15 / 5-1 / §10 |
| Q3 | status が 0/1/2 以外(UnsignedTinyInt なので3以上が入りうる)の行 |
★一覧には出さない(SQL の IN で外れる)・例外にもしない★(表2 F12)。PATCH の1件読みで来たら500(fail-loud) |
表2 F12 / 2-4 / 2-5 |
| Q4 | 種類の絞り込み | ★部分一致・大小文字を区別しない(includesFoldPlain())をアプリ側で★。依頼文の候補「存在する値から選ぶセレクト」は採らない |
§2 D9 / 2-5 / 5-3 |
| Q5 | 列見出し | ★「施設名」★(依頼文は「店舗」。CLAUDE.md §8 の用語と、既存キー 店舗 の EN が "Stores" のため) |
§2 D16 / 5-3 / 5-5 |
| Q6 | メニューのアイコンと EN | ★🚨 エラー一覧 / 🚨 Errors(暫定)★ |
5-1 / 5-5 |
| Q7 | planPrecheckStatusUpdate / PRECHECK_NOTE_MAX_LENGTH / PrecheckStatus / PrecheckStatusPatch の名前 |
★改名しない(そのまま使う)★。改名は AT のテスト・ルートを巻き込むので別作業(§10) | §2 D11 / 2-1 / §10 |
| Q8 | fixtures の上書き表を AT と共有するか | ★別のモジュール(状態を共有しない)★。fixtures の id が重ならないので /alerts と /errors の食い違いは起きない |
§2 D14 / 3-3 |
| Q9 | レスポンスに clean_timetable_id を載せるか |
★載せる(画面には出さない)★ | §2 D18 / 2-1 / 4-1 |
| Q10 | /alerts の nextResolutionStatus() を共有の純粋関数に移すか(AlertsScreen.tsx に import の置き換えだけが入る) |
★移す★(2画面の巡回順が食い違わないため。挙動は変わらない) | §2 D17 / 2-2 / 5-4 / 4-16 |
| Q11 | 段階8(ステージングで1行書いて戻す)を行うか | ★条件が揃えば行う★(D-152 で許可済み。事前チェック以外の行で確かめる) | 段階8 |
| Q12 | サマリのカード(未解消◯件)・メニューのバッジ | ★作らない★(件数は「全◯件」だけ) | §10 / 5-3 |
| Q13 | 期間の日数の上限(MAX_DATE_RANGE_DAYS) |
★設けない★(/prechecks と同じ。date_from > date_to だけ422) |
2-5 / 4-1 |
| Q14 | 表2(F1〜F12)・表3(S1〜S6)・fixtures の11行と「既定10行・全11行」を正にしてよいか | 本書で導いた値 | §3 / 3-2 / 段階6 / 段階7 |
段階ごとに分ける(段階1 / 段階2 / 段階3 / 段階4 / 段階5 / 段階6)。★git add はファイルパスを明示する★(git add -A 禁止)。 メッセージには ★何を変えたか★ と ★なぜ(D-160。未対応のエラーを一覧し、対応状況と備考を更新できるようにする)★ を書く。 ★段階1 のコミットは「テストだけ(この時点では落ちる)」と明記する★。 ★D8(2段階で読む)を入れるコミットには、COALESCE の並べ替えを Prisma で書けないことと、nulls last を採らない理由(表1 E7)を書く★。 ★PR の作成とマージは daichi がレビューしてから行う★(D-154)。
★実装は Cursor ではなく Claude Code★(ユーザー指示 2026-10-06。本書の扱いを聞いた質問に「ここで実装する」と回答。段階8 も条件がそろえば行うと確認済み)。 監督がサブエージェント2本(★バックエンド: 段階1〜2 → 段階3・4・6★ / ★画面: 段階5★)に分け、ワークツリーで実装 → develop に cherry-pick。 ★期待値表(§3 表1)は変えていない★。記録の正は docs/decisions.md D-160 の「実装の記録」と docs/handoff.md 追記62。
19dbe38)| 段階 | コミット |
|---|---|
| 1(テストだけ。この時点では落ちる) | fc9b3cf |
| 2(型・純粋関数) | 499b23f |
5-4(/alerts の巡回順を lib/prechecks/status-cycle.ts から import) |
5ceb30f |
| 5(画面・メニュー・i18n) | 388172f |
| 3(データ層。fixtures と Prisma、2段階で読む) | e56b734 |
| 4-1(YAML) | 75a0115 |
| 4(ルート・テスト) | 9bdb8e2 |
6(docs/fixtures.md §13) |
f02eb1d |
f02eb1d 時点)npx tsc --noEmit -p . 0件 / npm run lint 0件 / vitest ★119ファイル・2,790件 通過★(着手前 111・2,601)/ USE_FIXTURES=true npm run build 成功(/errors・/api/errors・/api/errors/[error_id]/status が出る)status: { in: [ と select 定数の robot_id / robot は ★0件★。$queryRaw|$executeRaw は既存のコメント3か所(「使わない」と書いたもの)だけnext start -p 3100、2026-10-06 16:0x JST。 既定 ★10件★・include_resolved=true で ★11件★/並びは検知時刻の降順(先頭は errored_at が NULL の FX-ERR-07=created_at 10/6 06:40)/ error_type=secur で FX-ERR-08 の1件/include_resolved=yes は422/FX-ERR-08 はシリアル「0」・種類「SECURITY_ON」/ PATCH で 対応中 → 解消 → 未解消 の日時の入り方と消え方が §3(AT §3-4)どおり/大小文字違い・存在しない id は404/日本語の備考が UTF-8 で往復一致//prechecks は200dwatch_local。当方のシード由来)★: next start を環境変数でローカルに向けて起動、2026-10-06 16:10 JST。 ★49行★(本清掃・事前チェック30/60・予定なし・予定不明がすべて出る)。RER-1008-main(本清掃に紐づく)で 対応中 → 解消 → 未解消・備考を入れて空に。 db.write は route が /api/errors/[error_id]/status・columns は変えた列だけ・備考の中身なし(comment_changed のみ)。最後は元の値に戻した。 ★段階7-6 の列単位 GRANT のローカル専用ユーザーでの確認は、していない★(.env.local に書き込み用アカウントの設定が無い)mysql の SELECT だけで読んだ(★UPDATE を手で打っていない★)robot_id が NULL の行。§8-5)1000013(SECURITY_ON・シリアル0・本清掃)の1行だけ★: 対応中 → 解消 → 未解消・備考を入れて空に。★権限エラーなし★comment / responded_at / resolved_at は NULL)。updated_at は 15:09:52 → 16:11:10(DB が更新。AT §10 #8 で受け入れ済み)| # | 判断 | 理由・場所 |
|---|---|---|
| 1 | ★include_resolved は小文字の true / false だけ★(TRUE も422) |
黙って倒さない(表2 F3 の延長)。d-watch-api.yaml にも明記 |
| 2 | GET のルートテストは db.facilityNameIndex()・facilityGroupIndex() を vi.spyOn で差し替えた |
★vitest でモックした @/lib/db/prisma を同時に動的 import すると、片方に本物が返る罠★(テスト環境だけ)。★/clean-logs の Prisma 経路テストで施設名と分類を同時に試すと同じ罠を踏む★ |
| 3 | ページの切り出しと行の組み立ては lib/db/index.ts の非公開2関数(errorReportPageIds() / errorReportItemsInPageOrder())で fixtures と Prisma が共有 |
3-4 の「規則を2箇所に書かない」 |
| 4 | updateErrorReportColumns() は updatePrecheckErrorColumns() と同じ3行を ★共通化せず並べた★ |
updatePrecheckErrorColumns() の挙動を変えないため(D12 と同じ考え方) |
| 5 | fixtures の error_type は LowBattery 等のダミー |
3-2 の表の例以外は実データ未受領(docs/fixtures.md §13) |
| 6 | 画面は eslint-disable を足さず、一覧の結果を {query, items, total, error} で持ち、読み込み中を result.query !== queryString で判定 |
4-19。effect の中で同期的に setState しない(react-hooks/set-state-in-effect の抑止コメントを足さずに済ませる。ErrorsScreen.tsx:230-231) |
| 7 | 列幅は §5-3 の目安から調整(★表の最小幅 1156px★) | ErrorsScreen.module.css。固定幅は守っている(4-17) |
| 8 | 期間の検証は dateRangeError(from, to, Infinity) |
上限を設けない(§11 Q13)。lib/clean-logs/detail-log-date-range.ts を流用 |
| 9 | 一覧の取得失敗の文言を t() に通した |
4-18 |
DsLogScreen.tsx の t("備考"))の備考列の見出しが EN で "NOTE" になる★(これまでは EN でも「備考」のまま)。直していない(誤訳ではない)\\ を \ に縮める★(スクリプトに正規表現の \S 等を書くときは Write ツールで書くか、バックスラッシュを使わない)