きっかけ:縦に伸びた index ページ
決算書を読む教科書の index ページを開いて、スクロールバーが画面の右端で短く縮こまっているのを見つけた。Part 1 と Part 2 が縦に積まれていて、PC のワイドな横幅がまるごと余白になっていた。モバイルでは1カラムでいいが、PC で同じレイアウトを流すと「下にもっとある」というシグナルが弱い。
直す方針を決めた。PC幅では2カラム、モバイル ≤960px では1カラムに崩れるグリッドラッパー。Claude Code に「textbook の index を Part 1/2 で2カラムに、960px 以下で1カラムに畳んで」と渡して実装させた。
ここで一瞬迷ったのは、Part 2 の章番号を CH08〜のまま続けるか、Part 2 内で CH01 から振り直すかだった。リナンバーすれば「Part 2 単独」として綺麗に見えるが、URL・textbookChapters・quizRegistry・ファイル名を全部書き換える大手術になる。Part 2 は Part 1 の続編という積み上げ関係なので、通し番号のまま据え置くと決めた。意思決定としては5秒で済んだが、後から「やっぱり振り直そう」と戻ると半日溶ける類の選択だった。
積み残しを書き出させる
ここで「ついでに細かい改善を片付けたい」と思ったが、頭の中の TODO リストが既に曖昧だった。Claude Code に「今のリポジトリを舐めて、積み残しっぽい改善ポイントを memo/2026-05-18/00-backlog.md に書き出して」と振った。
返ってきたのは10件強の箇条書き。クイズ index も2カラムにすべき、教科書ページにパンくずが無い、view ページに目次が欲しい、トップから memo に飛ぶ動線が無い、など。優先度順に並んでいたので、上から潰してと指示して、5件をひと続きのコミットで処理させた。
ローカル専用のマークダウンビューア
backlog の中に「memo/ 配下を毎回 VSCode で開くのが面倒、ブラウザで読みたい」が混ざっていた。marked が既に依存として入っていたので、本番ビルドからは除外する dev 限定ページとして作ることにした。
実装は import.meta.dev ガードで本番ルートから完全に剥がす方針。Claude Code に「dev だけで /dev/memo 配下を歩いて、memo/ の中身を一覧表示できるツリー+ビューを作って」と頼んだ。ファイル一覧 API、view API、view ページの3点セットが20分で揃った。トップページにも v-if="isDev" でガードした「📓 memo ビューア」カードを追加して、localhost で開いた時だけ顔を出すようにした。
HMR 無限再ビルドを止める
書き終えて dev サーバーを再起動した。コンソールに「Nuxt Nitro server built」が連発し始めた。1秒に1回ペースで再ビルドが走り、ターミナルが緑色の文字で埋まっていく。
切り分けに入った。memo/ にファイルを追加したタイミングで vite/nitro の watch が反応していた。audio-assets/ も同じ症状を起こしていた。Nuxt の watch 対象から両ディレクトリを ignore する設定を nuxt.config.ts に追加して、ようやく再ビルドが止まった。緑文字が静かになった瞬間に、ファンの音が落ちて部屋が一段静かになった。
目次のハイライトが効かない
view ページに右サイドバー目次を生やした。H1〜H3 を抽出して、sticky で追従させて、日本語の見出しでも安全にアンカー化する。ここまでは10分で動いた。
問題は「スクロールに合わせて active のハイライトを淡いピンクで光らせる」処理だった。実装は入れたのに、スクロールしても色が動かない。クリックでジャンプは効く。IntersectionObserver も生きている、ように見える。
console.log を仕込んで原因を追った。setup が一度だけ走って、observer が登録される前に終わっていた。await useFetch(...) を setup の冒頭に書いていたせいで、setup が async になり、client side の継続部分が走らない構造になっていた。await を外して、useFetch の戻り値を .data 越しに watch する形に直したら、スクロールに合わせて目次の項目がピンクに光り始めた。
「async setup と client-only な副作用は混ぜるな」を体で覚え直した一件。SSR 文脈では当たり前の話なのに、書いている最中はうっかりやる。
クイズ index とトーンを揃える
教科書の index が綺麗になると、隣のクイズ index が急に古く見えた。Part 2 のアクセントカラー、フォント、part-header の組み方、secondary-link のホバー挙動、全部が微妙にズレている。同じ「Part 1 / Part 2 の2カラム」に揃えて、CSS の値もコピペで合わせた。
内部の topic 番号は据え置いた。リナンバーは教科書側で却下したのと同じ理由だ。代わりに、各クイズカードに「📖 対応 CH8 / CH9 / CH12」というバッジを足した。5-1〜5-7 の Part 1 分析セクションには CH06(マゼンタ)と CH10(紺)の両 Part バッジが並ぶ。8-1 営業 CF には CH08 + CH09 + CH12 の紺バッジが3つ刺さる。クイズと教科書の対応関係が、目で1秒で取れるようになった。
パンくず移植
仕上げに教科書ページのパンくずリストを追加した。クイズ側に同じ仕組みが既にあったので、Claude Code に「クイズのパンくずを教科書ページに流用して」と渡した。/textbook/ch08/01 のような階層を、トップ → 教科書 → Part 2 → CH08 → 01 と辿れるリンクとして頭に表示する。これも10分で終わった。
今日のコミット履歴
- textbook index を Part 1/2 の2カラムに変更(960px で1カラム)
/dev/memoローカルマークダウンビューアを新規追加- nuxt.config の watch から memo/ と audio-assets/ を ignore
- view ページに右サイドバー目次 + active ハイライトを実装
- quiz index を textbook と同じ2カラムに揃え、CH バッジを追加
- 教科書ページにパンくずリストを移植
- トップに dev 限定 memo ビューアカードを追加
学び
- 横並びにできる場所を縦に積んだままにすると、PC でユーザーが「下にもっとある」を見落とす。ワイヤレベルの2カラム化は ROI が高い
- 通し番号 vs リナンバーは、URL とデータ構造への波及で判断する。見た目だけで動かしてはいけない
- 本番に出したくない開発支援 UI は
import.meta.devで物理的に剥がす。CSS で隠すと bundle に残る - async setup と client-only な副作用を混ぜると、observer や watcher が登録される前に setup が終わる。
await useFetchは安易に書かない - HMR が暴走したら、watch 対象に余計なディレクトリが混ざっていないかを真っ先に疑う
明日以降の積み残し
- memo ビューアのツリーで、フォルダの開閉状態を localStorage に保存する
- 目次の active ハイライトを、見出し直下のセクション末尾まで延長する(今は次の見出しに乗った瞬間に切り替わる)
- クイズ index の CH バッジを、クリックで対応する教科書ページにジャンプできるリンクに昇格させる