[{"data":1,"prerenderedAt":542},["ShallowReactive",2],{"content-/oita-trip-itinerary-gantt":3,"all-pages-for-dir":540,"og-image-/oita-trip-itinerary-gantt":541},{"id":4,"title":5,"body":6,"category":522,"description":523,"extension":524,"meta":525,"navigation":491,"ogImage":526,"path":527,"project_name":528,"published":529,"publishedAt":530,"seo":531,"stem":532,"tags":533,"todo":526,"unpublished":529,"updatedAt":526,"__hash__":539},"pages/2026-05/2026-05-29/oita-trip-itinerary-gantt.md","家族旅行アーカイブの行程ガントを実日程に合わせて大幅更新した話",{"type":7,"value":8,"toc":510},"minimark",[9,12,21,26,43,57,60,64,83,136,142,146,153,161,164,167,183,194,197,201,208,227,230,234,241,248,252,263,274,277,439,442,477,480,506],[10,11,5],"h1",{"id":5},[13,14,15,16,20],"p",{},"夏の家族旅行（九州のある県、妻と子ども3人の5名）の行程を、自前のAstro製アーカイブ（",[17,18,19],"code",{},"localhost:4321","）にひたすら流し込んだ一日。最初は2泊ぶんを足すだけのつもりが、便の変更や宿の制約が次々に出てきて、開始日を前倒し・終了日を延長・宿泊ブロックの統一・CSSの拡張・宿候補の探索まで芋づる式に広がった。「ガントの数値とスクリーンショットを見て自分が違和感を拾う係、編集を回すのはClaude Code係」という分担で進めた記録を残しておく。",[22,23,25],"h2",{"id":24},"ガントタイムラインに宿泊と移動を反映していく","ガント（タイムライン）に宿泊と移動を反映していく",[13,27,28,29,32,33,32,36,39,40,42],{},"このアーカイブは旅行ごとにMarkdownのfrontmatterで ",[17,30,31],{},"startDate"," / ",[17,34,35],{},"endDate",[17,37,38],{},"timeline"," を持ち、ページ側でガント（縦に日付、横にレーン）を描画する作りになっている。まずは高原エリアのキャンプ場2泊と、そこからテーマパークへの移動ルート（約1.5時間、休憩込み2時間）を ",[17,41,38],{}," に追加させた。",[13,44,45,46,48,49,52,53,56],{},"ここで最初の気づき。",[17,47,38],{}," のスキーマに ",[17,50,51],{},"link"," フィールドが無く、Googleマップのルートリンクをそのまま持たせられない。表示側で ",[17,54,55],{},"note"," がどう描画されるかを先に確認させてから、ルートはメモ欄にリンクを埋め込む形に落とした。",[13,58,59],{},"既存データとの衝突もこのとき拾った。同じ夜に「実家泊」とキャンプ場泊が二重に入っていたので、キャンプ場を入れるぶん実家泊を置き換える、と判断。地名・施設名の表記ゆれ（自分が口頭で言った施設名と、地図URL上の正式名が違った）もここで正した。",[22,61,63],{"id":62},"開始日を前倒し終了日を延長してカレンダーを縦に伸ばす","開始日を前倒し、終了日を延長してカレンダーを縦に伸ばす",[13,65,66,67,69,70,73,74,82],{},"「終了日をもう少し後ろに伸ばしたい」という要望から、",[17,68,35],{}," を後ろにずらした。ここでガント描画ロジック（",[17,71,72],{},"buildDays"," と日数計算）を先に確認させたのが効いた。日数が増えると ",[75,76,77,78,81],"strong",{},"背景行のCSS（",[17,79,80],{},"nth-child","）が10日ぶんまでしか定義されておらず、それを超えた日の背景が崩れる"," と事前に分かったからだ。",[84,85,90],"pre",{"className":86,"code":87,"language":88,"meta":89,"style":89},"language-css shiki shiki-themes vitesse-light vitesse-light","/* 10日目までしか背景ストライプが無く、11日目以降が抜ける */\n.gantt-bg-row:nth-child(n) { /* ...10まで... */ }\n","css","",[17,91,92,101],{"__ignoreMap":89},[93,94,97],"span",{"class":95,"line":96},"line",1,[93,98,100],{"class":99},"sxvE3","/* 10日目までしか背景ストライプが無く、11日目以降が抜ける */\n",[93,102,104,108,112,115,117,120,124,127,130,133],{"class":95,"line":103},2,[93,105,107],{"class":106},"shFtX",".",[93,109,111],{"class":110},"s4oTP","gantt-bg-row",[93,113,114],{"class":106},":",[93,116,80],{"class":110},[93,118,119],{"class":106},"(",[93,121,123],{"class":122},"sM54T","n",[93,125,126],{"class":106},")",[93,128,129],{"class":106}," {",[93,131,132],{"class":99}," /* ...10まで... */",[93,134,135],{"class":106}," }\n",[13,137,138,139,141],{},"必要日数ぶんに ",[17,140,80],{}," を拡張して崩れを潰した。さらに後段で往路を前倒しして開始日も延ばしたため、最終的に表示は12日ぶんになり、背景行の高さ（1056px）まで数値で確認させて整合を取った。日数が変わるたびに「何日表示になるはず」を先に宣言してからリロードで答え合わせする、という進め方が安全だった。",[22,143,145],{"id":144},"宿泊ブロックを1800翌0900の均一幅に統一する","宿泊ブロックを「18:00〜翌09:00」の均一幅に統一する",[13,147,148,149,152],{},"当初、実家泊が複数日にまたがる1本の長いバーになっていて、ガントが間延びして見えた。これを ",[75,150,151],{},"毎晩「18:00〜翌09:00」の均一ブロック"," に分割するよう揃えた。実家泊だけでなく、キャンプ場2泊・高原のホテル・大型温泉ホテルも、すべて同じ幅のブロックに統一。夜のエリアだけを示し、横に広げない、というルールに寄せたら一気に見やすくなった。",[84,154,159],{"className":155,"code":157,"language":158},[156],"language-text","# Before: 実家泊が5日ぶん1本の帯（間延び）\n実家泊  ████████████████████  8/26〜8/30\n\n# After: 1泊=1ブロックに分割（18:00〜翌09:00）で揃える\n実家泊  ▓ ▓ ▓ ▓ ▓   各夜ごとに独立\n","text",[17,160,157],{"__ignoreMap":89},[22,162,163],{"id":163},"便変更とレンタカー再見積を予算と予約テーブルに反映する",[13,165,166],{},"航空券（地方系LCC）とレンタカーが、行程の前後延長で再予約・再見積になった。ここは便名が1つ変わると連動する箇所が多く、何度もリロードで答え合わせした。",[168,169,170,174,177,180],"ul",{},[171,172,173],"li",{},"往路を前倒し、復路は同時刻のまま日付を後ろへ。便名が変わるたびに、空港→キャンプ場の移動時刻、レンタカーの受取・返却、最終日の空港戻りルートまで連動させた",[171,175,176],{},"レンタカーは当初7日 90,750円 → 約10日 119,445円（+28,695円、1日あたり約9,500円増）。確定値なので料金内訳テーブルとfrontmatterの予算（移動費・合計）を更新",[171,178,179],{},"航空券が確定したタイミングで、総額・座席数（5席）まで反映。予算は移動費 232,662円（航空券113,217＋レンタカー119,445）、合計 462,662円で整合を取った",[171,181,182],{},"便が確定する前は「変更予定（点線バー）」、確定後は「確定（実線バー）」とステータスを切り替え。frontmatterの予算と本文の予約テーブルの両方を、毎回そろえて直した",[13,184,185,186,189,190,193],{},"各編集のたびにdev serverのリロードでスキーマ検証（",[17,187,188],{},"/trips/..."," が200を返すか＝frontmatter検証パス）を確認し、ガント配置を数値とスクリーンショットの両方で検証する、というループを徹底した。",[17,191,192],{},"@astrojs/check"," の追加インストールを促されたが、それは入れず、dev serverのログとリロード結果で代替した。型チェックのためだけに依存を増やすより、すでに動いているdev serverの200応答とログを信じるほうが軽い。",[13,195,196],{},"ついでに「保有マイルがこの航空会社で使えるのか」という素朴な疑問も湧いたので調べさせた。記憶で答えると間違えやすい領域なので、公式情報で裏取りさせたうえで「独自マイルはあるが、今回の運賃区分では貯まり方が控えめで、この旅行ぶんだけで特典航空券にするのは現実的でない」と結論まで出してもらった。旅程の数字を詰めるついでに、こういう周辺の疑問もその場で潰せるのがアーカイブをコードで持っている強み。",[22,198,200],{"id":199},"ついでにssot化のリファクタ参加者を家族マスタから導出","ついでにSSOT化のリファクタ：参加者を家族マスタから導出",[13,202,203,204,207],{},"旅行一覧ページの「参加者」列が古い手入力データ（仮の名前）のままで、詳細ページの家族メンバー欄（",[17,205,206],{},"src/lib/family"," 由来の正しい5名）と食い違っていた。二重管理になっていたので、Single Source of Truthに寄せるリファクタをかけた。",[168,209,210,217,224],{},[171,211,212,213,216],{},"frontmatterの手入力 ",[17,214,215],{},"members","（自由記述の名前）を廃止",[171,218,219,220,223],{},"代わりに任意の ",[17,221,222],{},"participants","（家族マスタのrole参照、省略時は全員）に変更",[171,225,226],{},"一覧ページ・詳細ページの両方を、家族マスタから参加者を導出するよう書き換え",[13,228,229],{},"両ページを開いて、一覧の参加者列と詳細の家族欄が一致し、エラーが出ないことを確認した。画面の数字の食い違いに気づいたら、源泉を1本にまとめるところまで持っていく、という小さな掃除。",[22,231,233],{"id":232},"_1泊目の宿候補探しチェックイン締切に間に合わない問題","1泊目の宿候補探し：チェックイン締切に間に合わない問題",[13,235,236,237,240],{},"ここが一番考えさせられた制約。初日は空港到着が午後、レンタカー手続き＋約2時間の運転で、キャンプ場の ",[75,238,239],{},"チェックイン締切（18:30）に間に合わない"," ことが判明した。そこで初日の宿だけ「未定」に切り替え（ガント上は点線の未定スタイルで独立表示）、エリア内の代替候補を探すことにした。",[13,242,243,244,247],{},"条件は「5名対応・素泊まり・遅着OK」。複数クエリで並行検索させて、3系統（温泉旅館・民泊/一棟貸し・コテージ村）の候補を拾った。一番効いた条件が ",[75,245,246],{},"「チェックインが遅くてもOK」"," で、深夜0時までチェックイン可の温泉旅館が条件にかなり合致。あとから地図リンクで追加候補（露天風呂付きで5名1室OK、門限なしのコテージ村）も拾い、候補表と地図ピンに足していった。",[22,249,251],{"id":250},"宿の口コミはwebfetchで取り切れずagent-browserで全文取得","宿の口コミはWebFetchで取り切れず、agent-browserで全文取得",[13,253,254,255,262],{},"候補の宿の口コミを網羅したくて、まずWebFetchで口コミサイトを叩いた。件数・平均点・項目別評価は取れた（553件・平均4.13）が、",[75,256,257,258,261],{},"ページネーション（",[17,259,260],{},"f_page","）がWebFetchでは効かず、上位の口コミ中心"," にしか取れない。属性の異なる実レビューを15件超は拾えたものの、網羅性に欠けた。",[13,264,265,266,269,270,273],{},"そこで自分のChromeにつながっている ",[75,267,268],{},"agent-browser経由"," に切り替えたら、ページ1の全文（約20件、本文＋項目別評価＋宿側の返信つき）が一発で取れた。テーマが飽和したところでブラウザを閉じ、これをもとに網羅的な「読み物」メモを ",[17,271,272],{},"memo/2026-05-29/"," 配下に作成し、旅行ドキュメントからリンクした。WebFetchがボット対策やページネーションで詰まったら即agent-browserに切り替える、というのは今回も鉄板だった。",[22,275,276],{"id":276},"今日の試行錯誤",[278,279,280,302],"table",{},[281,282,283],"thead",{},[284,285,286,290,293,296,299],"tr",{},[287,288,289],"th",{},"#",[287,291,292],{},"テーマ",[287,294,295],{},"試したこと",[287,297,298],{},"結果",[287,300,301],{},"気づき",[303,304,305,330,349,368,384,400,419],"tbody",{},[284,306,307,311,314,322,325],{},[308,309,310],"td",{},"1",[308,312,313],{},"ルートリンク",[308,315,316,318,319,321],{},[17,317,38],{}," に ",[17,320,51],{}," を持たせようとした",[308,323,324],{},"失敗（スキーマに無い）",[308,326,327,329],{},[17,328,55],{}," 欄にリンクを埋める形へ。表示側を先に読んでおくと回避できた",[284,331,332,335,338,343,346],{},[308,333,334],{},"2",[308,336,337],{},"カレンダー延長",[308,339,340,342],{},[17,341,35],{}," を後ろにずらす",[308,344,345],{},"部分的に崩れる懸念",[308,347,348],{},"背景行CSSが10日ぶんしか無いと先に判明。日数を変える前に描画ロジックを読む",[284,350,351,354,357,362,365],{},[308,352,353],{},"3",[308,355,356],{},"背景行CSS",[308,358,359,361],{},[17,360,80],{}," を必要日数ぶんに拡張",[308,363,364],{},"成功",[308,366,367],{},"12日表示でも背景の高さ1056pxまで数値確認して整合",[284,369,370,373,376,379,381],{},[308,371,372],{},"4",[308,374,375],{},"宿泊バー",[308,377,378],{},"長い1本帯を「18:00〜翌09:00」の均一ブロックに分割",[308,380,364],{},[308,382,383],{},"幅を揃えるだけで間延びが消えた",[284,385,386,389,392,395,397],{},[308,387,388],{},"5",[308,390,391],{},"便・予算連動",[308,393,394],{},"便変更→移動時刻・レンタカー・予算を芋づるで更新",[308,396,364],{},[308,398,399],{},"1箇所直すと3〜4箇所に波及。毎回リロードで答え合わせ",[284,401,402,405,408,414,416],{},[308,403,404],{},"6",[308,406,407],{},"参加者の二重管理",[308,409,410,411,413],{},"手入力 ",[17,412,215],{}," を廃し家族マスタから導出",[308,415,364],{},[308,417,418],{},"一覧と詳細の食い違いで気づき、SSOTに寄せた",[284,420,421,424,427,430,436],{},[308,422,423],{},"7",[308,425,426],{},"宿の口コミ取得",[308,428,429],{},"WebFetchで口コミサイトのページネーション取得",[308,431,432,433,435],{},"失敗（",[17,434,260],{}," が効かず上位中心）",[308,437,438],{},"agent-browser（ログイン済みChrome）で全文取得に切り替え",[22,440,441],{"id":441},"今日の学び",[168,443,444,453,459,465,471],{},[171,445,446,449,450,452],{},[75,447,448],{},"日数が変わるUIは「背景の繰り返し定義」が落とし穴","。ガントの ",[17,451,80],{}," が固定数までしか無いと、日数を伸ばした瞬間に静かに崩れる。日数を変える前に描画ロジックを読んで上限を確認しておくと事故らない",[171,454,455,458],{},[75,456,457],{},"時間ブロックは幅を揃えるだけで見やすさが激変する","。宿泊を均一の「18:00〜翌09:00」に揃えただけで、間延びしていたガントが一気に締まった",[171,460,461,464],{},[75,462,463],{},"frontmatterの数値とリロード結果を毎回突き合わせる","のが安全。便名・時刻・予算は連動する箇所が多く、1つ直すと3〜4箇所に波及する。リロードで200（スキーマ検証パス）＋数値＋スクショの3点確認をループにした",[171,466,467,470],{},[75,468,469],{},"二重管理は画面の食い違いで露見する","。一覧と詳細で参加者が違って初めて気づいた。気づいたらSSOTに寄せるところまでやる",[171,472,473,476],{},[75,474,475],{},"WebFetchが詰まったらagent-browserに即フォールバック","。ページネーションが効かない口コミサイトでも、ログイン済みChrome経由なら全文が取れた",[22,478,479],{"id":479},"明日やること",[168,481,484,494,500],{"className":482},[483],"contains-task-list",[171,485,488,493],{"className":486},[487],"task-list-item",[489,490],"input",{"disabled":491,"type":492},true,"checkbox"," 初日（未定）の宿を、遅着OKの候補から1つに絞って予約する",[171,495,497,499],{"className":496},[487],[489,498],{"disabled":491,"type":492}," 確定した宿が決まったら、ガントの点線（未定）を実線（確定）に切り替える",[171,501,503,505],{"className":502},[487],[489,504],{"disabled":491,"type":492}," 残りの宿候補の口コミメモも、同じ要領でagent-browser経由で網羅しておく",[507,508,509],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":89,"searchDepth":103,"depth":103,"links":511},[512,513,514,515,516,517,518,519,520,521],{"id":24,"depth":103,"text":25},{"id":62,"depth":103,"text":63},{"id":144,"depth":103,"text":145},{"id":163,"depth":103,"text":163},{"id":199,"depth":103,"text":200},{"id":232,"depth":103,"text":233},{"id":250,"depth":103,"text":251},{"id":276,"depth":103,"text":276},{"id":441,"depth":103,"text":441},{"id":479,"depth":103,"text":479},"dev","Astro製の家族旅行アーカイブで、宿泊・移動・観光をガント（タイムライン）へ反映。開始日と終了日を延ばし、宿泊を均一ブロックに揃え、便変更・宿候補探しまで一気に詰めた作業ログ。","md",{},null,"/oita-trip-itinerary-gantt","family-trips",false,"2026-05-29T00:00:00.000Z",{"title":5,"description":523},"2026-05/2026-05-29/oita-trip-itinerary-gantt",[534,535,536,537,538],"家族旅行","Astro","ガントチャート","タイムライン","旅程管理","k9FLEq1NJEnJEAh6dxCgfeIFlVAA3QrQajYBfd9jDFk",[],"https://log.eurekapu.com/og/blog/oita-trip-itinerary-gantt.png?v=2026-05-29T00%3A00%3A00.000Z&title=%E5%AE%B6%E6%97%8F%E6%97%85%E8%A1%8C%E3%82%A2%E3%83%BC%E3%82%AB%E3%82%A4%E3%83%96%E3%81%AE%E8%A1%8C%E7%A8%8B%E3%82%AC%E3%83%B3%E3%83%88%E3%82%92%E5%AE%9F%E6%97%A5%E7%A8%8B%E3%81%AB%E5%90%88%E3%82%8F%E3%81%9B%E3%81%A6%E5%A4%A7%E5%B9%85%E6%9B%B4%E6%96%B0%E3%81%97%E3%81%9F%E8%A9%B1&author=Kei%20Komatsu&sig=ee7bfd61311a18b9",1782528845085]