eurekapu-nuxt4の積み残し解消:lint・依存脆弱性ゼロ化から語源単語ページ再構成まで
eurekapu-nuxt4の積み残し解消:lint・依存脆弱性ゼロ化から語源単語ページ再構成まで
積み残しは3つあった
朝、「昨日の積み残しって何でしたっけ」と聞くところから始めた。自分でも覚えていなかった。前日のメモを掘り起こしてもらうと、答えは3つ。ローカルに22コミット分溜まったままの未push、放置していたlintの警告とエラー、そして本番環境での表示未確認。全部「あとでやる」で止めていたやつだった。
まずpush。記録の追記だけでコード変更がない2件をコミットしてから流すと、実際には23コミットがまとめて本番リポジトリに反映された。直後にGitHubからDependabotの脆弱性警告が1件(High)届いた。今回のコミット自体は依存関係を一切いじっていないので、いったん保留にしてlintに取りかかることにした。
lintの警告16件・エラー12件を1つずつ潰す
残っていたのは警告16件とエラー12件。警告は--fixで流し込むだけで消えた。厄介なのはエラーの方で、1件ごとにコードの意図を確認してから直す必要があった。
- 使われていない
groupTopBarY変数を削除した。バッジ配置は個別のbarYで行う実装に切り替わっていて、完全に設計の名残だった financial-statements-quizの未使用ヘルパーchapterHrefを削除した。章番号はリンクではなくテキストバッジで表示する実装になっていて、リンク生成用の関数がそのまま浮いていた- スクリプト内で区切り文字に使っていた
\x00(NULL文字)を、本文に出現しない私用領域のUnicode文字に置き換えた。Bashは出力の途中でNULLバイトを落としてしまうので、直接置換するNode.jsスクリプトを書いて実行した - 未使用の
SITE_URL、未使用import、prefer-const違反の分割代入など、残りは機械的に潰していった
12件を直し終えて全体のlintを再実行すると、フルgreenになった。型チェックとテストも念のため回して、既存機能を壊していないことを確かめてからコミットした。ステージした瞬間に学習ゲートがブロックしてきたので、/learnのクイズを開いたが、今回はパスを選んでコミットを通した。
デプロイしたのに「本当に確認した?」と聞かれた
lint修正をpushすると、Dependabotの警告が1件から10件(Critical 1・High 7・Moderate 2)に膨れ上がった。今回の変更内容とは無関係で、スキャン結果が更新されただけらしいとは思ったが、詳しく見るのは後回しにして先に本番デプロイに進んだ。
デプロイ自体は数分で終わった。厄介だったのは表示確認の方で、Chrome DevTools MCPが繋がらず、拡張機能経由の接続も失敗した。curlでSSR出力だけは確認できたが、それは「データが埋め込まれている」ことの確認であって、画面が実際に見えるかどうかの確認ではない。最終的にagent-browserに切り替えて、コンソールエラーなし・画像表示も正常という結果を得て「完了」と報告した。
その後、Dependabotの10件をpnpm.overridesで潰す作業に入った。tar・svgo・shell-quote・js-yamlのバージョンを固定し、途中で一度overridesの指定が原因でminimatchの依存関係が壊れてlintがエラーを吐いたが、原因を切り分けて撤回した。パッチ版が自動で選ばれることが分かったので、overridesなしのままlint・型チェック・テスト・ビルドを確認してコミットした。反映までタイムラグはあったが、最終的に10件は0件まで落ちた。
ここでスクリーンショットが送られてきた。「本当に表示って確認してくれましたか?」。セクション列に「fac / fect / fic」という項目が1つだけ表示されている画面だった。自分が確認していたのはact-agとアトラスの標準ページだけで、詳説18章由来の語根ファミリーページはまるごと見ていなかった。指摘されて初めて気づいた抜け漏れだった。
セクション構成を「五感で貫く英単語4000」に合わせて作り直す
コードを確認すると、これはバグというより設計そのものの結果だった。詳説18章由来のファミリー(18件)は、実装時に「自分自身1件だけのtoc」を意図的に作る分岐になっていた。アトラス4000語をベースにした語根ファミリー(72件)とは別系統として扱われていて、fac-fect-ficのようなページだけ項目数が極端に少なく見えていた。
なぜ分かれているかは分かったが、直すかどうかは別の話だった。詳説18章のファミリーは「各章の主要語根+アトラス全体から同じ語根の語を追加収集したもの」、アトラスのファミリーは「アトラス4000語だけを語根でグループ化したもの」で、生成元が違う。ただ「語根でまとめた単語集合」という性質は同じなので統合していいはずだと伝えると、「五感で貫く英単語4000」と「五感ファミリーで見る英単語4000」という2つのセクションにまとめてほしいという指示が返ってきた。もともとバラバラだった18件のファミリーを、72件のアトラス側のセクションに合流させる形になる。
Codexにレビューさせたところ、今回の変更とは無関係な既存バグを1件見つけてきた。atlas/[module].vueで、目次は除外語を除いた配列を参照しているのに、本文側は除外前の配列をインデックスで参照していて、両者がズレる構造になっていた。前日実装したアトラス側の除外処理がきっかけで生じていたバグで、今回除外語をさらに増やす分だけズレの範囲が広がる。ついでに直すことにした。
実装後、gogen関連テスト215件と全体テストを流して全green、devサーバーで境界ケース(追加語0件のファミリー、22語のファミリー、47語で最大件数のファミリー)を目視で確認した。目次と本文の単語が一致することもピンポイントでチェックし、Codexが見つけたバグの修正が効いていることを確かめてから、2回目のデプロイに進んだ。本番でも「1/47・No.118」の統合表示が正しく出ていることを確認できた。
セクション列のスクロールバーが選択中の項目に追従しない
デプロイ直後、また画面のスクリーンショットが届いた。左側のセクション列で、スクロールバーが上に張り付いたまま、選択中の項目まで動いていない。ハイライトされた項目が画面中央に来るように自動で追従してほしいという要望だった。
この日はここで時間切れになった。構造だけ軽く確認して計画書にまとめ、次のセッションに引き継いだ。実装自体は別セッションで進めた。セクション列・チャプター列・トピック列・モバイル目次ドロワーの4か所それぞれに、Map管理のfunction refでDOM要素を紐づけ、選択状態が変わるたびにscrollIntoView({ block: 'center' })を呼ぶ方式にした。
すんなりとはいかなかった。まず型チェックで、ref関数の引数型がElementとComponentPublicInstanceのどちらになるか揺れていて、型ガードを追加する必要があった。次に、実装したはずのスクロールが実機では効いていないように見えた。手動で同じscrollIntoViewを実行して切り分けると、DOM側はちゃんと反応している。原因はフォント読み込みによるレイアウトシフトで、初回描画の時点ではまだ要素の位置が確定していなかった。デバッグ用のログを一時的に仕込んで動きを追い、想定通りの位置に収まることを確認してからログを消した。
残りのページの回帰確認はサブエージェントに任せた。20秒でツールを1回使っただけの完了報告が返ってきたので、中身を見ると検証していなかった。「別のサブエージェントが処理中と誤認識している」と判断して再指示を出し直した。やり直させた結果は正常に返ってきて、既存コースへの影響もないことを確認できた。
振り返り
積み残し3件を確認するところから始まった1日が、lintの完全解消、依存脆弱性の全件解消、既存バグの発見と修正、セクション構成の作り直し、スクロールバー自動追従の実装まで転がっていった。共通していたのは、どれも最初の「確認しました」だけでは終わらなかったことだ。スクリーンショット1枚の指摘が、見ていなかった画面の存在を教えてくれた。
学びメモ
- 積み残しの確認は前日メモを掘り起こすところから始まる。記録さえ残っていれば、翌朝すぐに机上へ並べ直せる
- lintのエラーは1件ずつ「なぜそのコードが要らなくなったか」を確認しながら消すと、デッドコードが生まれた経緯まで見えてくる
- 「表示確認しました」は、見ていないページが1つでもあると簡単に崩れる。確認済みの範囲を自分で言語化しないと抜け漏れに気づけない
- レビューを任せると、今回の変更と無関係な既存バグまで拾ってくることがある。ついでに直すかどうかの判断も含めて頼る価値がある
- スクロール系のUIバグは、実装が正しくても初回描画のレイアウトシフトで見え方がズレることがある。DOM側の反応を手動で切り分けると原因を絞り込みやすい