[{"data":1,"prerenderedAt":426},["ShallowReactive",2],{"content-/chrome-extension-mf-term-carryover":3,"all-pages-for-dir":424,"og-image-/chrome-extension-mf-term-carryover":425},{"id":4,"title":5,"body":6,"category":405,"description":406,"extension":407,"meta":408,"navigation":211,"path":409,"project_name":410,"published":411,"publishedAt":412,"seo":413,"stem":414,"tags":415,"todo":422,"updatedAt":422,"__hash__":423},"pages/2026-04/2026-04-02/chrome-extension-mf-term-carryover.md","Chrome拡張で会計ソフトの次年度繰越を一括自動化した話 - 無限ループバグとUIデザイン3回改善の記録",{"type":7,"value":8,"toc":383},"minimark",[9,14,18,21,26,29,32,34,38,41,64,67,69,73,76,81,84,88,96,100,103,105,109,113,116,119,122,133,137,140,268,271,273,277,280,284,287,291,294,298,305,307,311,322,334,337,339,342,345,365,368,370,373,376,379],[10,11,13],"h1",{"id":12},"chrome拡張で会計ソフトの次年度繰越を一括自動化した話","Chrome拡張で会計ソフトの次年度繰越を一括自動化した話",[15,16,17],"p",{},"毎年3月決算後、会計ソフトAで「次年度繰り越し」ボタンを何度もクリックして年度を進める作業がある。顧問先が増えるほど、同じ画面を開いて同じボタンを押す回数が積み上がる。Chrome拡張にボタンを1つ追加して、複数年度分の繰越を一発で走らせる機能を実装した。完成までに無限ループバグを踏み、UIを3回作り直し、Codexレビューを3回受けた。",[19,20],"hr",{},[22,23,25],"h2",{"id":24},"chrome-devtools-mcpでフォーム構造を解析","Chrome DevTools MCPでフォーム構造を解析",[15,27,28],{},"まず会計ソフトAの繰越画面をChrome DevTools MCPで開き、フォームのDOM構造を調べた。実際にボタンをクリックしてNetworkタブを眺めると、内部APIのエンドポイントが見えてくる。フォームのhidden fieldにCTI（事業者ID）とTID（年度ID）が埋まっており、POSTリクエストで繰越処理が走る仕組みだった。",[15,30,31],{},"APIの構造が掴めたので、bridge.jsにAPI呼び出し関数を追加する方針を決めた。",[19,33],{},[22,35,37],{"id":36},"_3ファイル構成の実装","3ファイル構成の実装",[15,39,40],{},"実装は3つのファイルに分かれた。",[42,43,44,52,58],"ul",{},[45,46,47,51],"li",{},[48,49,50],"strong",{},"bridge.js",": 繰越API呼び出し関数を追加。CSRFトークン取得、POSTリクエスト送信、レスポンスのパースまでを1関数に閉じ込めた",[45,53,54,57],{},[48,55,56],{},"import.js",": オーケストレーション層。現在の年度を取得→繰越API呼び出し→次年度の確認→ループ、という流れを制御する",[45,59,60,63],{},[48,61,62],{},"content.js",": UIボタンの描画とクリックイベントのハンドリング",[15,65,66],{},"bridge.jsで薄いAPI関数を作り、import.jsがそれを呼んでループを回し、content.jsがUIを被せる。関心の分離がはっきりしている構成にした。",[19,68],{},[22,70,72],{"id":71},"codexレビュー3回の反映","Codexレビュー3回の反映",[15,74,75],{},"実装のたびにCodexにレビューを投げた。3回で指摘された内容はそれぞれ異なる。",[77,78,80],"h3",{"id":79},"第1回-ctitid整合性","第1回: CTI/TID整合性",[15,82,83],{},"繰越後に返ってくるレスポンスのCTIとTIDが、リクエスト時の値と一致しているか検証していなかった。別の事業者のデータを誤って繰り越すリスクがある。レスポンスのCTI/TIDをリクエスト時の値と突き合わせるバリデーションを追加。",[77,85,87],{"id":86},"第2回-成否判定の厳密化","第2回: 成否判定の厳密化",[15,89,90,91,95],{},"繰越APIのレスポンスがHTTP 200を返しても、body内のステータスフィールドが",[92,93,94],"code",{},"\"error\"","になっているケースがある。HTTPステータスだけでなく、bodyの中身まで見て成否を判定するよう修正した。",[77,97,99],{"id":98},"第3回-安全弁の上限到達時エラー表示","第3回: 安全弁の上限到達時エラー表示",[15,101,102],{},"無限ループ防止のために繰越回数に上限（10回）を設けていたが、上限に達したとき黙って止まるだけだった。ユーザーには「なぜ止まったのか」が伝わらない。上限到達時にエラーメッセージをUIに表示するよう修正した。",[19,104],{},[22,106,108],{"id":107},"致命的バグ-繰越フォームが常に表示される罠","致命的バグ: 繰越フォームが常に表示される罠",[77,110,112],{"id":111},"症状-2033年度まで繰越が走った","症状: 2033年度まで繰越が走った",[15,114,115],{},"テスト実行したら、繰越処理が止まらない。ログを眺めていると年度が2027、2028...と進んでいき、2033年度まで事業年度が作成されてしまった。",[77,117,118],{"id":118},"原因の特定",[15,120,121],{},"当初の終了条件は「繰越フォームが画面上に存在しなくなったら停止」だった。会計ソフトAの画面には「次年度へ繰り越す」ボタンが表示されており、最新年度まで進めばフォームが消えると想定していた。",[15,123,124,125,128,129,132],{},"しかし実際には、会計ソフトAは",[48,126,127],{},"最新年度であっても繰越フォームを常に表示する","。まだ存在しない未来の年度へも繰越できてしまう仕様だった。フォームの有無をチェックする",[92,130,131],{},"noForm","条件だけでは永遠に終わらない。",[77,134,136],{"id":135},"修正-実世界の年を基準にする","修正: 実世界の年を基準にする",[15,138,139],{},"終了条件を「現在年度の終了年 >= 実世界の年（2026）」に変更した。事業年度の終了日が2026年以降であれば、それ以上先に進める必要はない。",[141,142,147],"pre",{"className":143,"code":144,"language":145,"meta":146,"style":146},"language-javascript shiki shiki-themes vitesse-light vitesse-light","// Before: フォームの存在チェック（無限ループの原因）\nif (!document.querySelector('.carryover-form')) break;\n\n// After: 実世界の年で打ち止め\nconst currentYear = new Date().getFullYear();\nif (fiscalYearEnd >= currentYear) break;\n","javascript","",[92,148,149,158,206,213,219,246],{"__ignoreMap":146},[150,151,154],"span",{"class":152,"line":153},"line",1,[150,155,157],{"class":156},"sxvE3","// Before: フォームの存在チェック（無限ループの原因）\n",[150,159,161,165,169,173,177,180,184,187,191,195,197,200,203],{"class":152,"line":160},2,[150,162,164],{"class":163},"sHkkW","if",[150,166,168],{"class":167},"shFtX"," (",[150,170,172],{"class":171},"stQ0i","!",[150,174,176],{"class":175},"s4oTP","document",[150,178,179],{"class":167},".",[150,181,183],{"class":182},"senZ8","querySelector",[150,185,186],{"class":167},"(",[150,188,190],{"class":189},"sMJiu","'",[150,192,194],{"class":193},"sdGka",".carryover-form",[150,196,190],{"class":189},[150,198,199],{"class":167},"))",[150,201,202],{"class":163}," break",[150,204,205],{"class":167},";\n",[150,207,209],{"class":152,"line":208},3,[150,210,212],{"emptyLinePlaceholder":211},true,"\n",[150,214,216],{"class":152,"line":215},4,[150,217,218],{"class":156},"// After: 実世界の年で打ち止め\n",[150,220,222,225,228,231,234,237,240,243],{"class":152,"line":221},5,[150,223,224],{"class":171},"const",[150,226,227],{"class":175}," currentYear",[150,229,230],{"class":167}," =",[150,232,233],{"class":171}," new",[150,235,236],{"class":182}," Date",[150,238,239],{"class":167},"().",[150,241,242],{"class":182},"getFullYear",[150,244,245],{"class":167},"();\n",[150,247,249,251,253,256,259,261,264,266],{"class":152,"line":248},6,[150,250,164],{"class":163},[150,252,168],{"class":167},[150,254,255],{"class":175},"fiscalYearEnd",[150,257,258],{"class":167}," >=",[150,260,227],{"class":175},[150,262,263],{"class":167},")",[150,265,202],{"class":163},[150,267,205],{"class":167},[15,269,270],{},"2033年度まで作られてしまったテストデータは手動で削除した。この修正でCodexの第3回レビュー（安全弁）の指摘とも噛み合った。上限回数の安全弁と、年度ベースの終了条件の二重チェックで無限ループを防止する。",[19,272],{},[22,274,276],{"id":275},"uiデザインの変遷-v1-v2-v3","UIデザインの変遷: v1 → v2 → v3",[15,278,279],{},"繰越ボタンの配置とデザインを3回作り直した。",[77,281,283],{"id":282},"v1-設定タブ下部に独立セクション","v1: 設定タブ下部に独立セクション",[15,285,286],{},"最初は設定タブの下に「繰越処理」セクションを独立して配置した。動作はするが、事業者との関連が視覚的に切れている。どの事業者の繰越なのかが一目でわからない。",[77,288,290],{"id":289},"v2-事業者カード右側にボタン配置","v2: 事業者カード右側にボタン配置",[15,292,293],{},"「現在」バッジが付いている事業者カードの右側にボタンを置いた。事業者との紐付きが明確になったが、ボタンが「現在」バッジと近すぎて視線が散る。",[77,295,297],{"id":296},"v3-最終形","v3: 最終形",[15,299,300,301,304],{},"ボタン → 事業者番号 → 「現在」バッジの順に左から並べた。ボタンのテキストは「繰越処理を実行」、色はオレンジ（",[92,302,303],{},"#e65100","）。注意を引く色で、かつ「削除」の赤とは区別がつく。この配置でボタンの目的と対象が一目で伝わるようになった。",[19,306],{},[22,308,310],{"id":309},"chrome拡張リロード手順の学び","Chrome拡張リロード手順の学び",[15,312,313,314,317,318,321],{},"開発中、コードを修正してもブラウザ上の動作が変わらず、何度か首を傾げた。Chrome拡張は",[48,315,316],{},"拡張管理画面でのリロード","と",[48,319,320],{},"対象ページのリロード","の両方が必要になる。片方だけでは古いコードが残る。",[323,324,325,331],"ol",{},[45,326,327,330],{},[92,328,329],{},"chrome://extensions/"," で拡張の「更新」ボタンをクリック",[45,332,333],{},"対象の会計ソフトAのページをリロード",[15,335,336],{},"この2ステップを忘れると、修正したはずのコードが反映されず原因調査の時間を浪費する。",[19,338],{},[22,340,341],{"id":341},"テスト項目のマークダウン化と検証",[15,343,344],{},"実装完了後、テスト項目をマークダウンでリスト化した。",[42,346,347,350,353,356,359,362],{},[45,348,349],{},"繰越が1年度分だけ正しく実行されること",[45,351,352],{},"複数年度（例: 2023→2024→2025→2026）を一括で繰越できること",[45,354,355],{},"現在年度（2026）に到達したら自動停止すること",[45,357,358],{},"上限回数（10回）に達したらエラーメッセージが表示されること",[45,360,361],{},"CTI/TIDの不整合時にエラーで停止すること",[45,363,364],{},"HTTP 200だがbodyがエラーの場合に検知できること",[15,366,367],{},"各項目をChrome DevTools MCPで実際の画面を操作しながら検証し、すべてパスした。",[19,369],{},[22,371,372],{"id":372},"振り返り",[15,374,375],{},"一番手が止まったのは無限ループバグの原因特定だった。「フォームが消えたら終了」という前提が崩れた瞬間、ログに流れる2027、2028...の数字を見て背筋が凍った。外部サービスのUI仕様を前提条件にすると、仕様が想定と異なるだけで暴走する。終了条件は外部UIの状態ではなく、自分でコントロールできる値（実世界の年）に基づかせるべきだった。",[15,377,378],{},"UIデザインを3回やり直したのも収穫がある。v1で「動くけど使いにくい」を体感し、v2で「近すぎる」を体感し、v3でようやく視線の流れが揃った。画面に要素を並べてみないと気づけないことがある。",[380,381,382],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}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":146,"searchDepth":160,"depth":160,"links":384},[385,386,387,392,397,402,403,404],{"id":24,"depth":160,"text":25},{"id":36,"depth":160,"text":37},{"id":71,"depth":160,"text":72,"children":388},[389,390,391],{"id":79,"depth":208,"text":80},{"id":86,"depth":208,"text":87},{"id":98,"depth":208,"text":99},{"id":107,"depth":160,"text":108,"children":393},[394,395,396],{"id":111,"depth":208,"text":112},{"id":118,"depth":208,"text":118},{"id":135,"depth":208,"text":136},{"id":275,"depth":160,"text":276,"children":398},[399,400,401],{"id":282,"depth":208,"text":283},{"id":289,"depth":208,"text":290},{"id":296,"depth":208,"text":297},{"id":309,"depth":160,"text":310},{"id":341,"depth":160,"text":341},{"id":372,"depth":160,"text":372},"dev","会計ソフトAの次年度繰越をChrome拡張のボタン1つで複数年度分一括実行する機能を実装。Chrome DevTools MCPでAPI特定、Codexレビュー3回、致命的な無限ループバグの発見と修正、UIデザイン3回の変遷を記録","md",{},"/chrome-extension-mf-term-carryover","tax-assistant",false,"2026-04-02T00:00:00.000Z",{"title":5,"description":406},"2026-04/2026-04-02/chrome-extension-mf-term-carryover",[416,417,418,419,420,421],"Chrome拡張機能","クラウド会計","自動化","バグ修正","UIデザイン","Claude Code",null,"xpAvo3Sw5YQPU8BDAIj4kVTRcqdcjM9g09Yd_6urVQA",[],"https://log.eurekapu.com/favicon.svg",1775338203971]