[{"data":1,"prerenderedAt":368},["ShallowReactive",2],{"content-/interactive-journal-engine":3,"all-pages-for-dir":366,"og-image-/interactive-journal-engine":367},{"id":4,"title":5,"body":6,"category":348,"description":349,"extension":350,"meta":351,"navigation":307,"ogImage":352,"path":353,"project_name":354,"published":355,"publishedAt":356,"seo":357,"stem":358,"tags":359,"todo":352,"unpublished":355,"updatedAt":352,"__hash__":365},"pages/2026-05/2026-05-01/interactive-journal-engine.md","簿記学習用インタラクティブ仕訳エンジンを純HTMLで実装した話",{"type":7,"value":8,"toc":336},"minimark",[9,13,17,20,36,39,42,45,154,157,160,163,166,169,172,175,178,181,184,188,191,223,229,232,236,239,242,253,256,259,262,265,269,280,284,287,290,293,296,332],[10,11,12],"p",{},"簿記学習用の章ページに「ボタンを押すと仕訳帳に転記され、残高試算表でアニメーションが走る」インタラクティブブロックを純HTML+CSS+JS（IIFE）で組み込んだ。設計は10分で固まったが、実装中に3回つまずき、最後はChrome DevToolsでスクショを撮るまでバグに気づかなかった。",[14,15,16],"h2",{"id":16},"やったこと",[10,18,19],{},"cash-3-topics.html というキャッシュ章のページに、仕訳エンジンを埋め込む。要件はこうだ。",[21,22,23,27,30,33],"ul",{},[24,25,26],"li",{},"ユーザーが「仕訳をプッシュ」ボタンを押すと、仕訳帳テーブルに行が追加される",[24,28,29],{},"同時に残高試算表の該当勘定の数字が増減し、差分がアニメーションで光る",[24,31,32],{},"期間切替（前期/当期）はExcelシート風のタブで切り替える",[24,34,35],{},"前期は記帳済み・閲覧専用、当期だけ操作可能",[10,37,38],{},"Vue化はまだなので、まずは1ファイルで完結する純HTMLで動かす方針にした。",[14,40,41],{"id":41},"レイアウトの分割",[10,43,44],{},"借方・貸方を縦に並べる愚直な見せ方だと、BS/PLの構造が伝わらない。CSS Gridで4象限に切った。",[46,47,52],"pre",{"className":48,"code":49,"language":50,"meta":51,"style":51},"language-css shiki shiki-themes vitesse-light vitesse-light",".tb-grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-template-rows: auto auto;\n  gap: 8px;\n}\n/* 借方=資産・費用 / 貸方=負債・純資産・収益 */\n","css","",[53,54,55,71,88,110,125,141,147],"code",{"__ignoreMap":51},[56,57,60,64,68],"span",{"class":58,"line":59},"line",1,[56,61,63],{"class":62},"shFtX",".",[56,65,67],{"class":66},"s4oTP","tb-grid",[56,69,70],{"class":62}," {\n",[56,72,74,78,81,85],{"class":58,"line":73},2,[56,75,77],{"class":76},"sz8Xr","  display",[56,79,80],{"class":62},":",[56,82,84],{"class":83},"snbK4"," grid",[56,86,87],{"class":62},";\n",[56,89,91,94,96,100,104,106,108],{"class":58,"line":90},3,[56,92,93],{"class":76},"  grid-template-columns",[56,95,80],{"class":62},[56,97,99],{"class":98},"sM54T"," 1",[56,101,103],{"class":102},"stQ0i","fr",[56,105,99],{"class":98},[56,107,103],{"class":102},[56,109,87],{"class":62},[56,111,113,116,118,121,123],{"class":58,"line":112},4,[56,114,115],{"class":76},"  grid-template-rows",[56,117,80],{"class":62},[56,119,120],{"class":83}," auto",[56,122,120],{"class":83},[56,124,87],{"class":62},[56,126,128,131,133,136,139],{"class":58,"line":127},5,[56,129,130],{"class":76},"  gap",[56,132,80],{"class":62},[56,134,135],{"class":98}," 8",[56,137,138],{"class":102},"px",[56,140,87],{"class":62},[56,142,144],{"class":58,"line":143},6,[56,145,146],{"class":62},"}\n",[56,148,150],{"class":58,"line":149},7,[56,151,153],{"class":152},"sxvE3","/* 借方=資産・費用 / 貸方=負債・純資産・収益 */\n",[10,155,156],{},"純資産には繰越利益剰余金（期首）と当期純利益を別行で出し、子要素として2文字インデント。当期純利益は費用区分の末尾にも添えてPLをバランスさせた。これだけで「PLの利益が純資産に積み上がる」感覚が視覚的に伝わるようになった。",[10,158,159],{},"帳簿側は黒の角張ったカードで囲み、ヘッダーを黒背景＋白文字にした。入力側カードと帳簿側カードの間に「帳簿に反映 ▼」というフロー矢印を1本入れたら、操作の因果がひと目で読めるようになった。",[14,161,162],{"id":162},"マトリックス形式で出して怒られた",[10,164,165],{},"最初、受取時と支払時を縦横の2x2マトリックスで並べて見せたら、ユーザーから「完全に独立した仕訳を縦に並べる形式に直して」と一発で却下された。",[10,167,168],{},"確かに、マトリックスは「同じ取引の表裏」を示すには良いが、独立した2つの取引には不向きだった。受取時と支払時で勘定が変わる構造を縮約しすぎていた。例題タブで「① 不一致発覚 / ② 原因判明」をタブ切替にして縦長を圧縮し、各タブの中では仕訳をシンプルに縦並びにした。",[10,170,171],{},"リセットボタンも「リセット」のままだと押しづらいという指摘があり、「↺ 一括取消」と動詞ラベルに変えて右上に逃がした。",[14,173,174],{"id":174},"漫画風の差分吹き出し",[10,176,177],{},"仕訳プッシュ後、何がいくら動いたかを可視化したかった。試算表右上のFYタブ行に「現金が +2,000 増加 / 売掛金が −2,000 減少」をマゼンタ薄背景（10% alpha）の吹き出しで出すようにした。",[10,179,180],{},"最初は仕訳行の真上に出していたが、「大事な部分が見えなくなる」と指摘されて即座に右上固定に変えた。これも独りよがりな配置だった。",[10,182,183],{},"取消後の挙動も悩んだ。新しい吹き出しを作ると「取り消したのに何か出る」状態で混乱を招く。残ったままにして、次のプッシュで上書きされる方針に落ち着けた。",[14,185,187],{"id":186},"simplifyスキルで踏んだtdz","simplifyスキルで踏んだTDZ",[10,189,190],{},"ロジックがふくらんできたので simplify スキルで畳み込んだ。",[21,192,193,200,213],{},[24,194,195,196,199],{},"preposted展開を ",[53,197,198],{},"applyEntry"," に統合",[24,201,202,205,206,209,210,199],{},[53,203,204],{},"switchFYJournal"," と ",[53,207,208],{},"switchFYTb"," を ",[53,211,212],{},"switchFY",[24,214,215,216,205,219,222],{},"アニメーション重複防止に ",[53,217,218],{},"activeRafs",[53,220,221],{},"pendingTimers"," を導入",[10,224,225,226,228],{},"ここで ",[53,227,198],{}," の宣言順序が崩れて、初期化時にTDZ（Temporal Dead Zone）エラーが出るバグを仕込んだ。エディタで開いてもエラーは出ず、ビルドも通る。「直したつもり」で次のタスクに進みかけた。",[10,230,231],{},"ユーザーから「Chrome DevToolsで撮ってみて」と言われ、初めてコンソールに赤いエラーが出ているのを見た。スクショなしで「ファイル更新済み＝正常」と判断したのが完全にミスだった。",[14,233,235],{"id":234},"教訓-ファイル更新だけで動いたと言わない","教訓: ファイル更新だけで「動いた」と言わない",[10,237,238],{},"シンプル化のリファクタは、見た目のロジックは正しくても実行順序を壊す。今回もコードを読み返した時点では「変数を上に集めて宣言した」つもりが、関数式の代入が後ろに残っていて、初回呼び出しで参照不能になっていた。",[10,240,241],{},"教訓は3つに集約された。",[21,243,244,247,250],{},[24,245,246],{},"ファイルを保存しただけで「動いた」と言わない",[24,248,249],{},"インタラクティブ要素を触ったら必ずブラウザでスクショを撮る",[24,251,252],{},"simplify後は特に、初期化パスを通す動作確認が必須",[14,254,255],{"id":255},"ナレーション化と縦切り検証",[10,257,258],{},"cash-3-topics-narration.vue を別途作り、3名話者（ずんだもん他）でVOICEVOX音声を58行生成した。NarrationViewer.vue に line-change emit を追加し、Cash3TopicsExample.vue / Cash3TopicsNarrationViewer.vue を新規作成して、ナレーションの行送りに合わせてインタラクティブ要素のステートが切り替わる作りにした。",[10,260,261],{},"縦切り検証で「あるナレーション行に来たら、対応する仕訳が自動でプッシュされる」というイベント同期がきれいに動いた瞬間は、思わずもう一度再生ボタンを押した。",[10,263,264],{},"計画書は Codex GPT-5.5 で3ラウンドレビューしてもらい、致命的な指摘13個を全部潰してからOKをもらった。指摘の質が高くて、自分で見直すより速い。",[14,266,268],{"id":267},"共通化-svg拡大とモーダル","共通化: SVG拡大とモーダル",[10,270,271,272,275,276,279],{},"マウスオーバーでSVGが拡大、クリックでモーダル拡大、ESCで閉じる。この3つは他の章でも使うので ",[53,273,274],{},"_layout.css/_layout.js"," に共通化した。各章ページは ",[53,277,278],{},"\u003Csvg>"," を置くだけで自動的に拡大UIが効く。",[14,281,283],{"id":282},"nuxtvue化への移行計画","Nuxt/Vue化への移行計画",[10,285,286],{},"HTMLだけで全章を展開するのは破綻する。1ファイルが2000行を超え、章をまたいだステート共有もできない。Nuxt/Vue化と並行で進める方針を立てた。",[10,288,289],{},"移行計画はM0でTypeScript章メタデータと分類を入れ、仕訳の借方/貸方/金額はTSにせず、章ごとの例題タイトル一覧をマークダウンに残すハイブリッドにした。仕訳データを完全TS化すると編集コストが跳ね上がるためだ。",[10,291,292],{},"この計画書も Codex で複数回レビューに回し、5つの致命指摘（ルーティング方針統一、完了条件明確化、マイルストーンの縦切り順）を反映してから本実装に入る。",[14,294,295],{"id":295},"明日やること",[21,297,300,310,316,322],{"className":298},[299],"contains-task-list",[24,301,304,309],{"className":302},[303],"task-list-item",[305,306],"input",{"disabled":307,"type":308},true,"checkbox"," M0着手: 章メタデータのTypeScript型を切る",[24,311,313,315],{"className":312},[303],[305,314],{"disabled":307,"type":308}," 例題タイトル一覧のマークダウン雛形を1章ぶん作る",[24,317,319,321],{"className":318},[303],[305,320],{"disabled":307,"type":308}," cash-3-topics.html のTDZ修正をユニットテスト相当の手動チェックリスト化",[24,323,325,327,328,331],{"className":324},[303],[305,326],{"disabled":307,"type":308}," simplify後の動作確認手順を ",[53,329,330],{},".claude/rules/"," にメモする",[333,334,335],"style",{},"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 .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}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":51,"searchDepth":73,"depth":73,"links":337},[338,339,340,341,342,343,344,345,346,347],{"id":16,"depth":73,"text":16},{"id":41,"depth":73,"text":41},{"id":162,"depth":73,"text":162},{"id":174,"depth":73,"text":174},{"id":186,"depth":73,"text":187},{"id":234,"depth":73,"text":235},{"id":255,"depth":73,"text":255},{"id":267,"depth":73,"text":268},{"id":282,"depth":73,"text":283},{"id":295,"depth":73,"text":295},"dev","仕訳プッシュで残高試算表が動く学習UIを実装。シンプル化バグ、UI指示の取り違え、スクショなしで「正常」判定したミスまで含めた試行錯誤の記録","md",{},null,"/interactive-journal-engine","eurekapu-nuxt4",false,"2026-05-01T00:00:00.000Z",{"title":5,"description":349},"2026-05/2026-05-01/interactive-journal-engine",[360,361,362,363,364],"インタラクティブUI","Vue","VOICEVOX","ナレーション","リファクタリング","0RQLP_-9eNzgU6-pL53oqXZsG9wYo0PAcnz9M0b7rtU",[],"https://log.eurekapu.com/og/blog/interactive-journal-engine.png?v=2026-05-01T00%3A00%3A00.000Z&title=%E7%B0%BF%E8%A8%98%E5%AD%A6%E7%BF%92%E7%94%A8%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96%E4%BB%95%E8%A8%B3%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3%E3%82%92%E7%B4%94HTML%E3%81%A7%E5%AE%9F%E8%A3%85%E3%81%97%E3%81%9F%E8%A9%B1&author=Kei%20Komatsu&sig=ee918592397c167f",1782528831576]