作成: 2026-09-15 / ブランチ: develop / 前提: 指示書AI 完了(D-135 / D-136)
D-Watch の実装をお願いします。ブランチは develop です。
■ 最初に読むもの(この順で、全部)
1. CLAUDE.md(全文。特に §0-3 フォント / §5 モックが正 / §7 禁止事項 / §9 実装規約)
2. docs/handoff/指示書AJ_要レスキューの見せ方とサイドバーのスクロール.md(全文)
3. docs/handoff/指示書AI_要レスキュー店舗と詳細ログの期間上限.md の §6-5
(今回直すのは、ここで私が指定を誤ったところです)
■ やること
指示書AJ の 段階1 から 段階4 までを、★番号順に★実行してください。
前回、段階の順序が前後しました。今回は番号順でお願いします
(段階2 はサイドバーの見た目に関わるので、段階1 と混ざると切り分けができません)。
■ 段階ごとに必ずやること
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/dashboard/aggregate.ts / lib/judge/advice.ts / lib/db/prisma.ts
■ 絶対にやらないこと
・閾値・件数のハードコード — 必ず lib/config/index.ts に置いてください
・新しい CSS に font-family を書くこと
(日本語フォントが落ちて漢字が中華字形になります)
・判定ロジック(lib/judge/rescue.ts)を変えること
★今回は「判定」ではなく「見せ方」だけを直します★
・仕様が不明なところを推測で実装すること
■ 動作確認
★テストDBのデータは 2026-09-13 で終わっています★。
/api/dashboard?date=2026-09-13 と ?date=2026-09-12 で確かめてください。
期待される結果は指示書AJ の §6 に書いてあります。
★段階2(サイドバー)は画面を見ないと確かめられません★。
§6-3 に、目視以外の確かめ方(getComputedStyle / scrollTop)を書いてあります。
■ 終わったら
・段階ごとのコミットハッシュ
・tsc / lint / test の結果(テスト本数も)
・★§6 の動作確認を実際に行った結果★(前回はここが素通りになりました)
・指示書どおりにできなかったところと、その理由
・判断に迷って自分で決めたところ(あれば全部)
★指示書AI の実装は合格でした。★ 3本の査読が独立に検算し、★実装の誤りは1件も出ていません★。
★直すのは、私(監督)が指示書AI §6-5 で指定を誤った1箇所です。★
§6-5: 「並び=連続問題日数の降順」「件数=上限を設ける。詳細ログに揃えて5件」
★この2つの組み合わせで、急落だけで該当する施設が構造的に一覧から落ちていました。★
★加えて、ユーザーから新しい要望が1件★来ています(段階2)。
★★実装者に落ち度はありません。指示書AI §6-5 のとおりに作られています。★★
テストDB(133.125.238.144:13306 / clean_watch)で確認した事実です。
| 当該日 | 3条件に当たる施設 | APIが返す件数 | ★消えた施設★ |
|---|---|---|---|
| 2026-09-13 | ★7件★ | 5件 | ★施設1(46.0%急落)/ 施設10(46.5%急落)★ |
| 2026-09-12 | ★7件★ | 5件 | ★施設8(91%急落)/ 施設10(28%急落)★ |
★消えた4件は、すべて completion_drop 単独★でした。
急落 = 「昨日まで正常 → 今日落ちた」
→ consecutive_problem_days は 1〜2 にしかならない
→ 連続日数の降順で並べると、必ず L3(3以上)の後ろ
→ L3 が5施設ある日は、急落が1件も出ない
★★ユーザーがわざわざ新設を指示した条件が、実データでほぼ機能していません。★★
components/features/dashboard/RescueRequiredCard.tsx:85
{t("{n}件").replace("{n}", String(items.length))}
items は切ったあとの配列です。★7件該当していても「5件」と出ます。★ ★運用担当は5件を全件として読みます。★ ★例外もテスト失敗も出ません。★
①上限は設けずに、表示は5件、もっと表示のようなのもので全件
★確定:★
| 層 | どうするか |
|---|---|
| ★API★ | ★上限を設けない★。該当する施設を全部返す |
| ★画面★ | ★最初は5件★。「もっと表示」で★全件★を開く |
★★API 側の上限(RESCUE_LIST_LIMIT)は撤廃します。★★ ★これで「切ったのに気づけない」問題が根から消えます★ — ★切らないからです。★
★件数が青天井にならないか★: なりません。★要レスキューは施設ごとに最大1行★なので、 ★施設数(P1 の想定で200件程度)が上限★です。★ページ送りは要りません。★
②これも上限無し
★確定: 評価日が何日前であっても出す。足切りをしない。★
★背景★: 「当該日以前で最も新しいスケジュール存在日」で判定するため(指示書AI §1-5)、 ★清掃予定が止まった施設は、遡り幅いっぱい(既定217カレンダー日 ≒ 7か月)出続けます★。
実測(今日 2026-09-15、日付なしで /api/dashboard):
summary success 0 / failed 0 / not_executed 0 / success_rate null rescue_required 5件(evaluated_on は 2026-09-12 と 2026-09-13)
★★ユーザーはこの挙動を承知のうえで「上限無し」と決めました。★★ ★足切りの実装はしません。★
★★判断の根拠(記録に残すこと)★★: ★困っている店舗が、時間が経ったという理由だけで 一覧から消えるほうが危ない★。★カードには既に「判定日」が出ている★ので (RescueRequiredCard.tsx:116)、★いつの状態かは読み手が判断できます★。
| 対象 | 理由 |
|---|---|
README.md / docs/code-map.md / docs/handoff/管理画面_設計検討_20260910.md / .claude/worktrees/** |
★別作業★ |
d-watch-mock.jsx |
★モックは原型として保存する★(段階2 で外れるが、書き換えない。§5) |
lib/dashboard/aggregate.ts |
★数値カードを変えない★ |
lib/judge/advice.ts |
R1〜R4。要レスキューとは別物 |
lib/db/prisma.ts |
★未コミットの変更が乗っている★ |
★lib/judge/rescue.ts の判定ロジック★ |
★★今回は「見せ方」だけを直します★★。3条件の判定は査読で合格しています |
★★禁止★★: git add -A / 生SQL / 閾値・件数のハードコード / スキーマ変更 / ★新しいCSSへの font-family★ / 推測での実装
lib/config/index.ts:81
export const RESCUE_LIST_LIMIT = 5 as const;
★この定数は「APIが返す上限」ではなくなります。★ ★画面の初期表示件数に役割を変え、名前もそれに合わせること★(例: RESCUE_INITIAL_VISIBLE)。
★★名前を変えずに意味だけ変えないこと★★ — ★次に読む人が「APIの上限」だと思い込みます。★
docstring に書くこと:
★これは API が返す件数の上限ではない★(上限は設けない。ユーザー指示 2026-09-15)。 ★画面で最初に見せる件数であり、「もっと表示」で全件が開く★。 ★API 側で切らない理由★: 切ると「切られたこと」に運用担当が気づけないため (指示書AJ §1-2。実データで7件中2件が黙って消えていた)。 ★件数が青天井にならない理由★: 要レスキューは施設ごとに最大1行なので、 施設数が自然な上限になる。
lib/db/rescue-mapping.ts:156-161
.slice(0, limit) を外す★buildRescueRequired() の limit 引数を外す★(★呼び出し側から渡さない★)sort は残す★(§4-3 で並び順を変える)★総数を別キーで返す必要はありません★ — ★配列の長さがそのまま総数★になります。 ★rescue_required_total のようなキーを足さないこと。★
★現在★: consecutive_problem_days の降順 → facility_id の昇順 ★変更後★:
1) reasons.length の降順 ← ★複数の条件に当たる施設ほど重い★ 2) consecutive_problem_days の降順 3) facility_id の昇順 ← ★安定した順序にするため★
★なぜ理由の数を最優先にするか★を docstring に書くこと:
★連続日数だけで並べると、急落(連続日数が浅いのが当たり前)が下に沈む★ (指示書AJ §1-1)。理由の数を先に見ることで、 「L3 かつ急落」の施設が「L3 だけ」の施設より上に来る。 ★★これでも「急落だけ」の施設は下のほうに来る★★ — そこは上限を撤廃し、画面で全件を開けるようにしたことで補っている。
★★「急落だけ」の施設が上位に来るわけではありません。正直に書くこと。★★
components/features/dashboard/RescueRequiredCard.tsx
要レスキュー店舗 7件 ← ★受け取った配列の長さ(=総数)★ (… 5件ぶんの行 …) もっと表示(あと2件) ← ★押すと全件が開く★
items.length(=総数)のまま★でよい。★上限を外したので、これが正しい数になります★RESCUE_INITIAL_VISIBLE 件★items.length > RESCUE_INITIAL_VISIBLE のときだけ「もっと表示」を出す★t() を通すこと★(JP/EN の器。§9.6)。★数値は画面側で差し込む★(D-111)lib/i18n.ts に新しい語を足すこと★(「もっと表示」「閉じる」など)★★0件のときカードごと消さない★★ — 指示書AI の実装者判断(D-135)を維持すること。 ★「確認した結果0件だった」と「表示が出ていない/壊れている」を区別できる必要があります。★
★データ層(上限が無いこと)★
| # | 該当施設 | APIが返す件数 |
|---|---|---|
| 1-1 | 0 | ★0★ |
| 1-2 | 7 | ★★7★★(★5ではない★) |
| 1-3 | 50 | ★50★ |
★1-2 が今回の眼目★です。★実データで確かめられます(§6)。★
★並び順★
| # | 入力 | 期待 |
|---|---|---|
| 1-4 | A(理由2・連続3) / B(理由1・連続6) | ★A が先★(理由の数が優先) |
| 1-5 | A(理由1・連続3) / B(理由1・連続6) | ★B が先★(同数なら連続日数) |
| 1-6 | A(id=9・理由1・連続3) / B(id=3・理由1・連続3) | ★B が先★(同値なら facility_id 昇順) |
★画面★
| # | 該当件数 | 初期表示 | 「もっと表示」 |
|---|---|---|---|
| 1-7 | 0 | 0行 | ★出さない★(カードは残す) |
| 1-8 | 3 | 3行 | ★出さない★ |
| 1-9 | ★ちょうど5★ | ★5行★ | ★★出さない★★ |
| 1-10 | ★6★ | ★5行★ | ★★出す(あと1件)★★ |
| 1-11 | 6 → 押したあと | ★6行★ | ★「閉じる」に変わる★ |
★1-9 と 1-10 の両方をテストに入れること★(> と >= の取り違えが通らないように)。
★★画面のテストは vitest.config.ts の include が lib/** と app/** なので走りません★★(D-120 項2)。 ★「何件表示するか」を決める部分は lib/ 側の純粋関数に切り出せば、テストできます。★ ★切り出すかどうかは実装者判断★ですが、★切り出さないならその旨を記録に残すこと★。
サイドパネルはメインパネルとスクロールは独立させてください
★ユーザー確定(2026-09-15): 案A(シェルを画面高に固定し、左右それぞれを独立させる)★
.shell { min-height: 100vh } ← 高さが内容で決まる
.main { overflow: auto } ← ★書いてあるが効いていない★
.shell が min-height なので flex 子の .main が内容分まで伸び、 scrollHeight === clientHeight になってスクロールコンテナになりません。
実測(components/layout/AppShell.module.css の宣言を実ページ上で再現して測定):
| 要素 | clientHeight | scrollHeight | スクロールするか |
|---|---|---|---|
html |
1268 | 3088 | ★する★ |
.sidebar |
3088 | 3088 | しない(position: static) |
.main |
3088 | 3088 | ★しない★ |
★下にスクロールすると、サイドバーは色のついた空の柱になります。★ ★メニュー・テーマ切替ボタン・フッター「DFA運用担当」が全部画面外へ流れます。★
components/layout/AppShell.module.css のみ。★JSX の構造変更は不要★です (aside と main は既に兄弟です)。
.shell {
display: flex;
height: 100vh; /* ★min-height から height へ★ */
height: 100dvh; /* ★dvh 対応ブラウザではこちらが勝つ(モバイルのURLバー分)★ */
...
}
.sidebar {
...
overflow-y: auto; /* ★1行追加★ */
}
/* .main の overflow: auto はそのまま。これで初めて効く */
★height: 100vh を先に、height: 100dvh を後に書くこと★(2段指定)。 ★100dvh を知らないブラウザは後者を無視して前者が残ります。★
★これは将来の話ではなく、もうすぐ起きます。★
今のメニューの高さ 約591px 点検記録を戻すと 約628px 総清掃面積の画面を足すと 約665〜699px
★ノートPCのブラウザ内寸は600〜700px★です。★overflow-y: auto が無いと、 下のメニューに手が届かなくなります。★
★height: 100vh にすると、ブラウザの「印刷」で1画面分しか出ない可能性があります。★
@media print {
.shell { height: auto; }
.main { overflow: visible; }
}
★これを入れ忘れると、印刷したときに初めて気づきます。★ ★★画面では絶対に分かりません。★★
Next.js は遷移時に document.documentElement.scrollTop = 0 を試し、 ダメなら scrollIntoView() に落ちます。 ★.shell を height: 100vh にすると前者が効かなくなり、後者頼みになります。★
★実機で確かめること★(§6-3 の (5))。★動かなければ報告してください。★ ★勝手に scrollTo を画面に足さないこと★ — ★アプリコードに scrollTo / scrollIntoView / scrollY は現在1件もありません★。1つ足すと、全画面の遷移の挙動を1箇所で背負うことになります。
★実測: 375px 幅でサイドバーは 220px のままで、本文は 155px に潰れます。★ ★横スクロールは出ません。★
★★今回の変更は、これを改善も悪化もさせません。★★ ★直したくなっても、今回は触らないこと★ — ユーザー指示はスクロールのみで、 ★レイアウトを変えるとモックからさらに外れるため、別の判断が要ります★。 ★申し送りに書くこと。★
d-watch-mock.jsx:426 minHeight: "100vh" / :429 サイドバー(position 指定なし)/ :468 overflow: "auto"。
★★モックにも同じ overflow: "auto" が、同じ理由で効かないまま置かれています。★★ ★実装はモックの 1:1 移植で、外れていません。★ ★今回が「外れる」最初の変更です。★
CLAUDE.md §5 は「モックが画面レイアウト・文言・遷移の正」と定めています。★前例が2件★:
| 決定 | 何をしたか |
|---|---|
| ★D-120★ | モックに無いページ送りUIを新規に作った。「§5 の例外」と明記し、★ユーザー原文を転記★ |
| ★D-121★ | モックと違う色にした。同上+★コード側にも理由コメントを残した★ |
★同じ作法を踏襲すること:★
d-watch-mock.jsx は1文字も書き換えない★docs/decisions.md に ★「CLAUDE.md §5 の例外。ユーザーの明示指示(2026-09-15)に基づく」★ と記録し、★指示の原文を引用する★AppShell.module.css の該当箇所の直上に、理由コメントを残す★ — ★★書かないと、次にモックと突き合わせた人が「モックと違う」と判断して戻します★★★★前回の実装では、実データでの確認が行われませんでした。★★ ★記録に「本作業では実施していない」と正直に書かれていたので分かりましたが、 その結果、上限で2件消えている問題は査読まで見つかりませんでした。★
★今回は必ず行い、結果を報告してください。★
.env.local は既にテストDB(133.125.238.144:13306 / clean_watch)を向き、USE_FIXTURES=false です。 ★★テストDBのデータは 2026-09-13 で終わっています。今日の日付では数値カードが全部0になります。★★
GET /api/dashboard?date=2026-09-13
| # | 期待 |
|---|---|
| ★6-1★ | ★★rescue_required が 7件 返ること★★(★前回は5件だった★) |
| ★6-2★ | ★★施設1 と 施設10 が含まれること★★(★どちらも急落46%。前回は消えていた★) |
| ★6-3★ | ★施設7 が含まれ、evaluated_on が 2026-09-12 であること★ |
| 6-4 | ★並びの先頭が施設9★(理由2つ・連続6日) |
| 6-5 | ★数値カードが success 8 / failed 9 / not_executed 1 のままであること★ |
GET /api/dashboard?date=2026-09-12
| # | 期待 |
|---|---|
| 6-6 | ★rescue_required が 7件★ |
| 6-7 | ★施設8 が含まれること★(★91%急落。前回は消えていた★) |
| 6-8 | 施設6 の reasons に not_executed_streak と escalation_l3 が両方入る |
GET /api/dashboard (日付なし=今日 2026-09-15)
| # | 期待 |
|---|---|
| ★6-9★ | ★★数値カードは全部0のまま、rescue_required は件数が返ること★★ |
★6-9 はユーザーが承知のうえで決めた挙動★です(§2-2)。 ★★「0件になるべきでは」と考えて足切りを入れないこと。★★
★★画面のログインにはアクセストークンが要ります。★★ ★ログインできる場合は、縦に長い画面(/detail-log で期間を広げる)で確かめてください。★
ブラウザのコンソールで:
// (1) スクロールコンテナがどれか
const info = e => { const c = getComputedStyle(e); return {
overflowY: c.overflowY, position: c.position, height: c.height,
clientH: e.clientHeight, scrollH: e.scrollHeight,
scrollable: e.scrollHeight > e.clientHeight + 1 }; };
({ html: info(document.documentElement),
sidebar: info(document.querySelector('aside')),
main: info(document.querySelector('main')) })
★合格条件★: main.scrollable === true ★かつ★ html.scrollable === false
// (2) 独立していることの証明
const m = document.querySelector('main'), a = document.querySelector('aside');
m.scrollTop = 500;
({ mainTop: m.scrollTop, docTop: document.documentElement.scrollTop,
brandVisible: a.querySelector('div').getBoundingClientRect().top >= 0 })
★合格条件★: mainTop === 500 ★かつ★ docTop === 0 ★かつ★ brandVisible === true (=★本文を動かしてもロゴが画面内に残っている★)
// (3) メニューが溢れたときサイドバー内だけが動く
// 先にブラウザの内寸を 1280×600 程度にすること
const a = document.querySelector('aside');
a.scrollTop = 9999;
({ asideScrollable: a.scrollHeight > a.clientHeight + 1, asideTop: a.scrollTop,
mainTop: document.querySelector('main').scrollTop })
★合格条件★: asideTop > 0 ★かつ★ mainTop が変化しない
// (4) テーマ切替が壊れていない(🌙 を押してから)
({ theme: document.documentElement.dataset.theme,
asideBg: getComputedStyle(document.querySelector('aside')).backgroundColor })
★合格条件★: ダーク rgb(26,35,50) ⇄ ライト rgb(255,255,255) で切り替わる
★(5) 画面遷移★: 本文を下までスクロール → サイドバーのリンクで別画面へ → ★document.querySelector('main').scrollTop === 0 になるか★
★(6) スマホ幅★: 375px にして ★横スクロールが出ていないこと★ (document.documentElement.scrollWidth === clientWidth)
★(7) 「もっと表示」★: 要レスキューが6件以上ある日で、★押すと全件出て、「閉じる」に変わるか★
★★ログインできない場合は、そう報告してください。★★ ★推測で「動いているはず」と書かないこと。★
★私が書いた期待値が間違っていました。★ ★直さないと、次に読む人が「実装が壊れている」と誤判定します。★
docs/handoff/指示書AI_要レスキュー店舗と詳細ログの期間上限.md の §12:
| 現在の記述 | 正しい値 |
|---|---|
「?date=2026-09-13 → R-2 が4件」 |
★★5件★★(★施設7 を数え漏らしていた★。09-13 に予定が無いが 09-12 評価で出る) |
| 「R-3 が4件」 | ★4件で正しい★。★ただし当時は上限5件のため、APIには2件しか出ていなかった★ |
★★訂正であることが分かるように書くこと。★★ ★元の数字を消して上書きしないこと★ — ★「4件と書いていたが誤り。正しくは5件」という形にする★。 ★次に読む人が、この指示書を信じて数を合わせにいく可能性があります。★
★あわせて §6-5(並びと件数の指定)にも、★★指示書AJ で撤回した旨★★を1行足すこと。★
docs/decisions.md★★採番の前に必ず実測すること★★:
grep -nE "^### D-(0|1)[0-9][0-9]" docs/decisions.md | tail -3
★docs/decisions.md は番号順に並んでいません。★ 末尾を見て最大値を判断しないこと。
書くこと:
CLAUDE.md §5 の例外であること。★ユーザー原文を転記★docs/handoff.md に追記scrollIntoView 経路に依存するようになったこと★| # | 落とし穴 | どうなるか |
|---|---|---|
| ★1★ | ★RESCUE_LIST_LIMIT の名前を変えずに意味だけ変える★ |
次の人が「APIの上限」だと思い込む |
| ★2★ | ★API 側にも上限を残す★ | ★今回直したかったことが直らない★ |
| ★3★ | ★評価日の足切りを「親切のつもりで」入れる★ | ★ユーザーが上限無しと決めている★。入れると古い問題店舗が消える |
| ★4★ | ★「もっと表示」の状態を URL に持たせる★ | 別課題(D-131 申し送り⑤)と混ざる |
| ★5★ | ★開いたあと閉じられない★ | 一度押すと戻せない |
| ★6★ | ★height: 100vh だけ書いて 100dvh を書かない★ |
モバイルでURLバーの分だけ画面外に出る |
| ★7★ | ★@media print を忘れる★ |
★印刷で1画面分しか出ない。画面では絶対に気づけない★ |
| ★8★ | ★サイドバーに overflow-y: auto を入れ忘れる★ |
メニューが増えた時点で下に手が届かなくなる |
| ★9★ | ★lib/judge/rescue.ts の判定を触る★ |
★査読で合格した判定を壊す。今回は見せ方だけ★ |
| ★10★ | ★新しいCSSに font-family を書く★ |
★漢字が中華字形になる★ |
| ★11★ | ★段階2 を段階1 と同じコミットにする★ | 壊れたときに切り分けられない |
| 項目 | なぜ |
|---|---|
| ★評価日の足切り★ | ★ユーザーが「上限無し」と決めた★(§2-2) |
| ★要レスキューのページ送り★ | ★施設ごとに最大1行なので要らない★ |
| ★スマートフォン幅のレイアウト★ | ユーザー指示はスクロールのみ。★モックからさらに外れるので別の判断が要る★ |
| ★「急落だけ」の施設を上位に出す仕組み★ | ★上限撤廃と「もっと表示」で足りるかを、まず実運用で見る★ |
| ★分類(ホールディングス/屋号/店舗)の復活★ | TeamWorks 確認待ち |
| ★総清掃面積の新画面★ | 分類が先 |
★LIMIT の SQL 押し下げ / ORDER BY の向き揃え★ |
別課題(D-136 の申し送り) |
| ★出発失敗を未実施と別に判定する★ | ★データがDBに無い★(第9回 11章で照会中) |