• #Vue
  • #リファクタリング
  • #Codexレビュー
  • #eurekapu
  • #レイアウト
  • #UX
eurekapu-nuxt4

朝、Excel講座の contents.vue に手を入れたところから一日が転がった。冒頭フックに「30分」「3つの習慣」を埋め、第2章で答え合わせをする伏線回収の流れを組み込んだ。short-video-strategy スキルに入っているド素人ホテル再建計画氏の方法論——最初に謎を提示して、本編で正体を一気に明かす——を講座のスクロール体験に移植した形になる。

書き出してすぐ、伏線と回収が噛み合っていない箇所が浮き上がった。「3つのポイント、その正体」のセクションで章番号を伏字(→第2章で開示)にしておいたのに、本編では別の角度から答えていた。「2つのキー」「データの持ち方」「外側」の3点で受け止め直すよう書き換え、冒頭の問いと末尾の答えがつながるようにした。

画像モーダルとフォントの足回りを整える

image-modal.client.ts プラグインに .scroll-img.exercise-img のセレクタを追加した。SVGをモーダルで開くと背景が透過して文字が見えなくなっていたので、背景色を白に固定する1行も足した。

ChatBox と VideoSubtitlePlayer のフォントも、イントロセクションの基準(font-size: 1.125rem / line-height: 2)に揃えた。3つのコンポーネントで微妙に値が違っていて、スクロールするたびに文字の重みが変わっていたのを撤去した形になる。

hardware章をスライド型から統合記事型へ

ここから本題。hardware章は ThinkPad / Monitor / PC の3パートに分かれていて、それぞれが独立したスライドとして表示されていた。1ページに集約する方が読み手の負荷が低いと判断し、article モード(統合記事型)に置き換えた。

TheaterViewer.vue の article モードには目次がなかったので、h3 を抽出して左サイドに並べる実装を足した。デプロイしてブラウザで開いた瞬間、ユーザーから指摘が飛んできた。

contents.vue と hardware側で目次レイアウトが違う

並べて見ると、たしかに目次の位置・幅・スクロール挙動がそれぞれ別物だった。原因を辿ると、両者で scroll-layout / scroll-content / scroll-inner の DOM 構造が分岐していた。contents.vue は独自に書いていて、TheaterViewer は別系統の CSS で組まれていた。

計画書を書いて Codex に3回レビューさせた

その場で直すと別の差異を生むと踏んで、まず memo/2026-04-29/scroll-layout-unification-plan.md に計画書を起こした。

codex exec -m gpt-5.4 "このプランをレビューして。瑣末な点へのクソリプはしないで。致命的な点だけ指摘して: ..."

3回回した。1回目は「URL hash を更新するか否か」を詰めろと言われた。スクロール連動で hash を書き換えると、戻るボタンが履歴で埋まって機能しなくなる懸念がある。案 X(hash を更新しない方針)を採用した。

2回目は h2/h3 の id 属性が DOM に正しく付与されるかを確認しろという指摘。MDC レンダラ経由だと id が落ちることがあるので、ScrollArticle 側で明示的に slug を生成して付ける実装に倒した。3回目は実装方針の最終確認だけで、致命的な指摘はなくなった。

ScrollArticle.vue を切り出す

共通コンポーネントとして ScrollArticle.vue を新規作成した。インターフェースはこの形に落ち着いた。

<ScrollArticle
  :sections="sections"
  :show-toc="true"
  @section-enter="onSectionEnter"
/>

sections には h2 単位のブロックを配列で渡し、内部で h3 を抽出して目次を作る。contents.vue と TheaterViewer の article モードがこの1コンポーネントを呼ぶ形に置き換わり、scroll-layout / scroll-content / scroll-inner の3層構造が両者で揃った。

deep link をブラウザで踏んで、#section-2-1 のような h3 アンカーまで正しくスクロールすることを確認した。差し替え前は片方の経路でだけ id が消えていた。

モバイル幅で出た重複バグ

PC では問題なかったが、Chrome DevTools のモバイルビューに切り替えた瞬間、画面に ScrollArticle と theater-mobile が縦に2つ並んで描画された。CSS のメディアクエリで display: none を切り替える側が、共通化の過程で両方残ってしまっていた。条件分岐を v-if に寄せて DOM レベルで片方を消すよう直した。

stream-deck も統合記事型に寄せる

勢いで、stream-deck の chapter(si=0〜4)も「1 chapter = 1 統合記事」のフォーマットに集約した。si ごとに画像とテキストが小刻みに分かれていたのを、章単位の長尺ページにまとめた。スクロールが減って、読み手が自分のペースで進めるようになった。

/simplify で掃除

仕上げに /simplify を回して、staging に紛れ込んでいたデバッグPNG10枚を git から外した。ScrollArticle.vue に書き散らした使われていない class も削除した。

# 不要なclassを掃除
- .scroll-debug-overlay
- .toc-temp-fallback
- .legacy-section-wrapper

用語を固定した

説明するたびに「スクロール型」「統合型」「長尺型」と揺れていたので、コードベースとドキュメントの用語を「統合記事パターン」と「スライドパターン」の2つに固定した。明日以降、全ページをこの2分類でレビューしていく。

明日やること

  • ゲシュタルトの章で使っている静止画を、GIF動画に置き換える(読み手が原理を1サイクル見られるようにする)
  • 全ページを統合記事パターン / スライドパターンのどちらに寄せるか棚卸しする
  • ScrollArticle のセクション境界アニメーションを 200ms 以内に収めて、スクロール時のカクつきを取る
  • stream-deck の chapter ごとに目次の h3 数を5個以下に絞る