開発eurekapu-nuxt4

ESLint no-dynamic-delete Phase 2解消と全体lint整理(1637→717)

前日のセッションで no-dynamic-delete エラーの Phase 1 を popup 型限定で潰したあと、計画書には Phase 2 が残っていた。今日はその Phase 2 を片付けて、勢いで lint 全体(1637件)の整理計画まで立て、自動修正で 717 件まで削った。

朝、計画書 memo/2026-05-11/lint-no-dynamic-delete-fix.md を開いて Phase 2 の対象7件をもう一度数え直すところから始まった。

1. Phase 1 と Phase 2 の方針の違い

前回のコミット 50e2a39(Phase 1)では、popup 型のフィールドだけに絞って Reflect.deleteProperty に置換していた。短期決戦で型衝突を避けたかったので、対象を狭く取った。

Phase 2 ではもっと広く、delete の用途を3つに分類して使い分ける方針に切り替えた。

分類用途対応
Apopup の cleanup(型に | undefined が入っている)= undefined で代入
Bwatch・関数内で動的キーを落とす(型変更したくない)Reflect.deleteProperty(obj, key)
CReactiveなオブジェクトのキー削除(リアクティビティ維持)Reflect.deleteProperty

Phase 1 は「とにかく lint を黙らせる」だったが、Phase 2 は「delete のセマンティクスを保ちながら lint も通す」に踏み込んだ。

2. 7件まとめて修正、テストも全パス

修正対象は3コンポーネント(Cash3TopicsExample / JournalExample / ClosingTransferExample)にまたがる7箇所。型定義(animating?: ... | undefined)も先に確認して、popup 型は型変更不要だと判明した。

7件の修正後、pnpm exec eslint .no-dynamic-delete0件 になっているのを確認。テストも 15 件 pass。Phase 2 のチェックリストに x を打って計画書を更新した。

3. Chrome DevTools で実走確認

「動作確認は Chrome DevTools で自分でやってください」とユーザーから指示が来た。各コンポーネントが使われているページを特定して、dev server を立ち上げた。

途中で Nitro の依存ファイルが消えて 500 エラーを吐いたので、dev server を再起動。ポート開放を確認してから再ロードして検証を再開した。

確認できた経路は以下の3つ:

  • Cash3TopicsExample: FY1 ↔ FY2 の切替で initState() が走り、popup の = undefined クリアと entries / balance の再構築が正しく動いた
  • JournalExample: push 直後の即時 reset(cancelAll 中断)と、一括取消の cancelPendingAnims(Reflect.deleteProperty 経路)。popup cleanup は5秒後に全て0に収束した
  • ClosingTransferExample: watch 経由の Reflect.deleteProperty(displayedAmts, k) 経路。Vue の props 変更を経由するため、ルートの example props を別データに差し替えて発火させた

3コンポーネントとも Vueエラー0件で通過した。

4. コミット2分割(fix + memo)

無関係な変更(courses/[slug].vue, quiz/practice.vue, pnpm-lock.yaml, settings.local.json)が作業ツリーに混ざっていたので、Phase 2 関連のみを選択的にステージした。コミットは2つに分割。

  • 3f62864 fix(lint): no-dynamic-delete を簿記ノート系で Reflect.deleteProperty に置換(4ファイル)
  • もう1コミットで memo の進捗マーカーと計画書チェックリストを更新

「ステージする前に必ず差分を読んで、無関係な変更が混ざっていないか確認する」を毎回守れた回だった。

5. Phase 1 の積み残し: delta popup の自動テスト追加

「未テストならテストを追加できますか」とユーザーから指示が来た。Phase 1 で popup の Next/Back クリア挙動を手動で確認したきりだったので、これを vitest に落とした。

BS / PL の delta popup について Next クリックと Back クリックでクリアされる挙動を計7件追加。全 pass を確認して 91d23b1 test(financial-statement): delta popup の Next/Back クリア挙動を自動化 でコミットした。

no-dynamic-delete を機械的に 0件にしたあと、人間が触らないとリグレッションに気づけない箇所をテストで守る、という流れに着地した。

6. lint 全体(1637件)の整理計画 — Codex レビュー必須

no-dynamic-delete が 0件になっても、pnpm exec eslint . の総数は依然 1637 problems (584 errors, 1053 warnings)。これも整理対象に乗せる流れになった。

ルール別の集計を出してみたら、最初の集計はファイルパスを誤検出していた。集計し直して、整理計画書 memo/2026-05-11/lint-rest-triage.md を起こした。

ここで毎回守っているルールを発動した — 計画書を ExitPlanMode に出す前に Codex (gpt-5.5) のレビューを通す

codex exec -m gpt-5.5 "このプランをレビューして。瑣末な点へのクソリプはしないで。致命的な点だけ指摘して: memo/2026-05-11/lint-rest-triage.md"

Codex から 致命的指摘3点 + 補足1点 が返ってきた。

  1. .pytest_cache を eslint ignore に追加すべき(誤検出されてカウントが膨らんでいた)
  2. ルール別集計の精度(ファイルパス文字列を拾っていた)
  3. 対応コスト欄の見積もり粒度

eslint.config.mjs.pytest_cache/** を追加し、計画書を3点 + 補足を反映して修正。修正後にもう一度 Codex に投げて承認を取った。

致命的指摘だけ拾う運用にしておくと、Codex が瑣末な点で発散するのを抑えつつ、見落としたい致命傷だけ拾える。今回も「.pytest_cache の ignore 追加」は自分一人ではしばらく気づけなかった気がする。

7. 自動修正で 1637 → 717(-920件)

整理計画書に従って自動修正可能な部分を一気に流した。

  • 全体の lint エラー: 1637 → 717(-920件)
  • コミット: 93 ファイル
  • 全テスト pass(1782/1782)

untracked な cockpit-slides 系ファイルが glob に巻き込まれたので、リセットして tracked ファイルだけステージし直した。事前に未コミットだった4ファイル(.claude/settings.local.json, courses/[slug].vue, quiz/practice.vue, pnpm-lock.yaml)は別グループ用に残して除外。

差し引き93ファイルだけがこのコミットに乗った。「git status を見て差分の境界を引く」を1回で正しくやれた。

8. 翌日タスクをコピペで再開できる形に

「明日やるからメモディレクトリに置いといて」とユーザーから指示が来た。memo/2026-05-12/lint-rest-tasks.md を作って、計画書末尾の「次セッション再開用プロンプト集」セクションをコピペで貼って、コミット de63f6d で保存した。

新セッションを開いてこのファイルを読み込めば、グループ A〜D の続きをそのまま再開できる。スターター文をマークダウンに書いておくと、翌日の自分(≒ Claude Code の新セッション)がコンテキストロードに迷わない。

学びと次にやること

今日の収穫を3つに圧縮する。

  1. delete の用途を A/B/C に分類してから lint 修正に入ると、popup と watch とリアクティブで使い分けが綺麗に決まる
  2. 整理計画書は必ず Codex レビューを通してから走らせる。今日も .pytest_cache の ignore 漏れを Codex が拾った
  3. 翌日タスクは「コピペで再開できるスターター文」までセットで残す。新セッションのコンテキストロードコストが消える

明日は memo/2026-05-12/lint-rest-tasks.md を起点に、残った 717 件のうちグループ A から順に手で削っていく。