8月の家族旅行アーカイブサイト(Astro + Cloudflare Pages)に、旅行詳細ページの最上部へガントチャートを置きたくなった。レーン(移動/宿泊/観光/仕事)× 日付(8/21〜8/29)で旅程をひと目で掴ませる狙い。最終的に縦長レイアウト・7レーン構成・3時間刻みの時間目盛り・薄めの日付またぎ線まで落ちた。が、そこに至るまで何度も Claude Code に書き直させ、画面を見て違和感を拾い、また書き直させた。
できあがったもの
- レーン7本(飛行機/レンタカー/観光/食事/実家/外泊/仕事)× 縦軸に日付(8/21〜8/29)
- 時間目盛りは3時間刻み(3/6/9/12/15/18/21)
- 日付またぎ線は2px・
#d0d0d0(薄めのグレー) - 短いバーには
min-height: 40pxを確保しつつ、潰れる場合はisShort判定でバー外にテキストをはみ出させる - ロジックは
src/lib/gantt.tsに純粋関数として切り出し、Vitest 30件 pass / カバレッジ Statements 91% / Branches 81% / Functions 76% / Lines 94%
試行錯誤テーブル
人間が「画面を見て違和感を拾う係」、Claude Code が「書き直す係」で回した。
| 観点 | 最初の実装 | 中間 | 最終形 | 拾った違和感 |
|---|---|---|---|---|
| レーン構成 | 移動/宿泊の2レーン | 飛行機/レンタカー/観光/食事/実家/外泊の6レーン | 上記6本に「仕事」を追加して7本 | 「宿泊」だけだと実家にいるのかリゾートホテルにいるのか画面から読めない/津久見商工会議所のセミナーが入った |
| 時間目盛り | 0/6/12/18 の4本 | — | 3/6/9/12/15/18/21 の3時間刻み | 6時間刻みだと午前・午後の境目がぼやけて、何時の飛行機なのか目が滑った |
| 日付またぎ線 | 1px #e6e6e6(薄いグレー) | 2px #999(濃いグレー) | 2px #d0d0d0(薄めグレー) | 薄すぎると日が変わったのが見えない/濃すぎると主張がうるさい |
| 短バーの潰れ対策 | バー内にテキスト | バー外にはみ出す isShort 判定を追加 | min-height: 40px をバー側に確保+ isShort 併用 | 「ちゃんと表示確認してください、潰れてますって」と画面を見て指摘した |
| レイアウト方向 | 横長(日付横・レーン縦) | — | 縦長(日付縦・レーン横) | スクロールしないと全日程が視界に入らなかった |
レーンを2本から7本に増やした流れ
最初は「移動」と「宿泊」の2レーンで組ませた。出てきた画面を見て、宿泊が1本だと「今どこに泊まっているか」が読めないと気づいた。実家にいる日とリゾートホテルにいる日が同じ色で並ぶので、空港送迎の段取りも観光ルートも頭に入ってこない。
そこで Claude Code に、宿泊レーンを「実家」「外泊」のロケーション別に分けさせた。移動も飛行機とレンタカーを別レーンに割って、観光と食事も独立させた。6本になった瞬間、「8/23 はレンタカーで動いて、観光地に寄って、夜は実家に戻る」という流れが視界に入るようになった。
そのあと、8/27 の午後に津久見商工会議所のセミナー枠が入ったので、「仕事」レーンを足させて7本にした。色を分けて凡例にも追加。旅行アーカイブなのに仕事の予定が混ざるのは野暮かと思ったが、家族の動きと自分の予定が同じ画面に乗っているほうが調整しやすかった。
時間目盛りを3時間刻みに変えた
最初は 0/6/12/18 の4本だった。飛行機が朝便なのか夜便なのか、レンタカーの返却が午前か午後かが画面から読めない。Claude Code に [3, 6, 9, 12, 15, 18, 21] の3時間刻みに書き直させたら、朝の出発と夕方の戻りが目盛りで挟まれて、時間軸の解像度が一段上がった。
日付またぎ線で3往復した
最初は 1px の #e6e6e6 で引かせていた。画面を見ると、日付が変わったのか同じ日の続きなのかわからない。「太く濃く」と指示して 2px #999 に書き直させたら、今度は線が主張しすぎてバーより目立つ。3往復目で 2px のまま色だけ #d0d0d0 に薄めさせて、「日が変わったことは分かる、でも主役はバー」という関係に着地した。
太さと色を独立に動かせるか、を最初から考えていなかったので、#999 を見て初めて「太さは残して色だけ薄めればいい」と気づいた。画面を見ないと色の濃淡の落としどころは決められない、と体に染みた。
短いバーが潰れる問題
津久見商工会議所セミナー(13:00〜17:00)のような短時間イベントは、横長レイアウトでバーがほぼ正方形になり、中のテキストが読めない。最初は isShort 判定を入れてバー外の右側にテキストをはみ出させる対応をさせた。「枠の外にはみ出してもいいから、潰れるよりはマシ」という方針。
その後、縦長レイアウトに転置したあと、再び津久見セミナーのバーが潰れた。スクリーンショットを送ってきた Claude Code に対して「ちゃんと表示確認してください、潰れてますって」と画面を見て指摘した。Claude Code は curl で HTML だけ確認して「正しい位置に配置されています」と判断していて、実際に Chrome で見ていなかった。最終的に「バー自体に最低高 min-height: 40px を確保する」アプローチに切り替えて、短時間イベントも読める高さを担保した。
横長レイアウトを縦長に転置させた
決定的に効いたのが、横長(日付横・レーン縦)から縦長(日付縦・レーン横)への転置。スクロールしないと8/21〜8/29の9日間が視界に入らないのが、横長の最大の弱点だった。
「行と列を入れ替えてみて」と指示したら、Claude Code が buildBars の出力を left/width/laneIndex から top/height/laneIndex に書き換えて、テンプレートと CSS を .gantt__* から .gantt-v__* へまるごと差し替えた。テストもバー型の変更(isShort 追加含む)に追従させて、30件全 pass。
転置後の画面を見たら、9日間の縦軸が一画面に収まって、レーン7本が横に並ぶ。一目で「いつ、どこで、何があるか」が掴める形になった。最初に組ませたときに、なぜ横長で着手させたんだろう、と少し悔やんだ。
やらかし1: devサーバを4つも増やした
途中、画面を確認するときに「最新の Markdown が反映されない」事象が起きた。原因は dev サーバが古いプロセスを掴んだまま、ファイル更新を拾えていなかったこと。Claude Code が pnpm dev を4321 → 4322 → 4323 → 4324 と無計画に増やしていって、こっちは「いちいち番号変わると面倒なんですけど」と指摘する羽目になった。
そこから Claude Code に全プロセスをまとめて kill させて、4321 に統一して1個だけ立ち上げ直させた。それ以降は反映の混乱がなくなった。
教訓: 古いプロセスを残したまま新規ポートで逃げない。立て直すならまず全部 kill する。
やらかし2: curlで動作確認した気になっていた
津久見セミナーの潰れを指摘したとき、Claude Code は curl で HTML を取ってきて「ガントが含まれていて正常に動いています」と返してきた。HTML が出力されることと、画面が崩れずに表示されることは別問題。Chrome DevTools MCP で実際の表示を確認させたら、ようやく「潰れていますね」と認めて修正に入った。
教訓: HTML が返るかどうかと、画面が読めるかどうかは別物。人間は画面の違和感を拾う係、修正は AI 係。 AI 任せでも、画面を見て違和感を拾うのは自分の役割。
ロジックを純粋関数に切り出した
CSS とテンプレートを何度も書き換える過程で、ガントのバー配置ロジックを src/lib/gantt.ts に純粋関数として切り出させた。入力は timeline イベントの配列、出力はバーの位置情報(横長時は left/width/laneIndex、縦長転置後は top/height/laneIndex)。
副作用なしで純粋にデータ変換するだけにしたので、Vitest で30件のテストが書きやすかった。レーン構成の変更も時間目盛りの変更も、関数の入出力を見ながら修正できる。最終的なカバレッジは Statements 91% / Branches 81% / Functions 76% / Lines 94% で着地。
DOM 操作や <style> から切り離しておくと、UI を3回書き直しても回帰がない、を体感した。
振り返り
- レーン構成は2本から始めず、最初から「実家/外泊/観光/食事/仕事」のように行動カテゴリで分解しておけばよかった
- 時間目盛り・日付またぎ線・短バーの潰れは、どれも画面を見ないと最終形が決まらなかった
- 縦長レイアウトは最初から選べたはず。スクロール不要を優先するなら、横長から組み始める意味はなかった
- 純粋関数に切り出してテストを置いておくと、UI を何度書き直しても安心して書き直せる
人間の役割は「画面を見て違和感を拾うこと」と「方針を一言で決めること」で、書き直しは Claude Code に任せる構図がうまく回った。次にガントを別の用途で組むときは、最初から縦長・行動カテゴリ別レーン・純粋関数切り出しの3点セットで始める。