消費税のインタラクティブ教材をNuxtで構築 — 調査エージェント2本を並行で走らせてコースページを実装した

開発mdx-playground

消費税のインタラクティブ教材をNuxtで構築した

朝、AI活用レビューのダッシュボード(memo/2026-07-02/ai-utilization-review.html)を開いて、計画書だけ作って止まっていた項目を上から順に消していくことにした。その流れで着手したのが、消費税の経理処理を学ぶインタラクティブ教材(コースページ)の構築。計画書を作った日から放置していたやつだ。

調査エージェント2本を並行で走らせる

実装に入る前に、バックグラウンドの調査エージェントを2本並行で起動した。

  • 素材収集: 蔵書DBに取り込み済みの実務書から、教材に使える論点の対比メモを作らせる
  • 器の調査: 教材をどのUIに載せるか、既存の実装資産(コンポーネント・型定義)を洗い出させる

自分は待っている間、別の計画書の片付けを進めた。器の調査が先に上がってきて、結論は「縦スクロールのコース型(セクション内に演習埋め込み)」。仕訳表示は既存の JournalPatternCard.vue、税区分の型は quiz-types.tsTaxClassification / QuizJournalLine を流用できると分かった。

面白かったのは Miller Column を捨てる判断。サイト内の問題集ページで使っている3ペイン構成だが、あれは「目次から選んで解く問題集」向きの器で、順序立てて読み進めるコースには合わない、という理由まで添えて返してきた。この判断はそのまま採用した。

素材収集も完了し、対比メモ(memo/2026-07-02/shohizei-course-source-notes.md)と演習アイデア24件が揃った。両方の結果を統合した構成案(5セクション+演習24問の配置)を設計させ、合意ゲートで中身を確認してから実装に進んだ。

コースの構成 — 5セクション+演習24問

合意した構成はこの5本。各セクションの本文の途中に、読んだ直後の知識を試す演習カードを埋め込む形にした。

  1. 消費税のしくみと課税の4要件
  2. 非課税・免税・不課税の違い
  3. 税込経理と税抜経理の仕訳
  4. 仕入税額控除とインボイス
  5. 納税額の計算方式(原則・簡易・2割特例)

「読んでから章末にまとめて解く」ではなく「読んだ直後に1問挟む」配置にしたのは、素材収集エージェントが出してきた演習アイデアがどれも論点単位で細かかったから。セクション末にまとめると出題根拠の本文から距離が開いてしまう。

実装 — データ・採点ロジック・コンポーネント・ページ

実装は Claude Code に一気通貫でやらせた。できたものは4つ。

  • コースデータ app/data/consumptionTaxCourse.ts(523行): 5セクション+演習24問。数値例はすべて自作させた。実務書の設例をそのまま写すと著作権の問題になるので、対比メモは論点の骨格だけに使う
  • 採点ロジック app/utils/consumptionTaxScoring.ts: judgeAnswer / computeStats / isComplete / parseAnswerMap の純粋関数群
  • 演習カード app/components/consumption-tax/CourseExercise.vue: セクション本文の途中に埋め込む選択式カード
  • ページ本体 app/pages/consumption-tax-course/index.vue: Breadcrumb と TableOfContents の props 形式を先に確認させてから書かせた

採点まわりはグローバルルールどおり、計算を純粋関数に閉じ込めてコンポーネント側は表示とイベントだけにした。演習タイプは3種類。

export type CtcExerciseType = 'judgment' | 'journal' | 'calc'
// judgment: 課税/非課税等の判定、journal: 仕訳の選択、calc: 納税額の計算

回答状態は Record<string, number> の素朴なマップで持ち、computeStats が正答数・進捗をまとめて返す。localStorage 復元用の parseAnswerMap も、不正なJSONを食わされたら空マップを返すだけの防御に留めた。

検証で2回転んだ

すんなりは通らなかった。

1つ目は Vitest。テストを書かせたら auto-import 頼みになっていて、既存の慣例(明示import)と食い違った。慣例どおりに直させてユニット19件がパス。演習カード24枚がすべてSSRされることも確認した。

2つ目は E2E。ユーザー操作フロー(演習に答えて採点される流れ)の追加なのでE2Eは必須と判断して1本書かせたら、初回実行で落ちた。issue を切らせてエラーを追うと、ページ自体は描画されていてアサーションの書き方の問題だった。失敗したアサーションを特定して修正し、パス。app/utils を触ったのでカバレッジも計測させて、採点ロジックは100%になった。

フォールバック起床が空振りした

小ネタをひとつ。調査エージェントを起動したとき、「完了を取りこぼしたら困る」と思ってフォールバックの起床タスク(完了状況を確認して未完了なら待つ)を仕込んでおいた。ところが実際には調査→構成案→実装→検証まで全部終わったあとに起床タスクが起きてきて、「すでに完了済み、追加作業なし」と報告して終了した。保険が空振りするのは悪いことではないが、起床タイミングの設計はもう少し考える余地がある。

導線が存在しなかった

完成後、ふと「/consumption-tax-course にインデックスページからアクセスする方法はあるのか」と聞いてみたら、なかった

definePageMetaincludeInList: true と書いて満足していたが、調べさせるとこのフラグを読む一覧機能がコードベースのどこにも存在しない。つまり検証まで済ませた教材が、どこからもリンクされていない状態で浮いていた。トップページ(index.vue)の消費税経理処理パターンのカードの上に、コースへの公開カードを追加して dev で表示を確認した。

ついでにもう1件。コース末尾に置いた /tax-patterns/quiz へのリンクが、本番では404になる(開発環境限定のページ)と分かったので、リンク自体を開発環境限定に修正させた。

最後にこの一連の流れ(素材収集→構成合意→実装→検証)をスキル化して、計画書とレビューHTMLを実施済みの状態に更新して締めた。

学び

  • 調査エージェントの並行実行は「素材」と「器」のように独立した軸で切ると待ち時間が消える。片方の結果がもう片方の前提にならない切り方が肝
  • UIの器は「コンテンツの読み方」で選ぶ。目次から選ぶ問題集なら Miller Column、順序立てて学ぶコースなら縦スクロール。既存の器を惰性で流用しない
  • 教材データの数値例は自作させる。元ネタの実務書は論点の骨格の参照に留める
  • includeInList: true のような「書いただけで読む側がいないフラグ」は完成の錯覚を生む。ページを作ったら導線の実在まで確認する
  • フォールバック起床は保険として仕込んでよいが、本線が先に完走したら空振りで終わる前提で軽く作る

関連ファイル

  • apps/web/app/pages/consumption-tax-course/index.vue
  • apps/web/app/data/consumptionTaxCourse.ts
  • apps/web/app/utils/consumptionTaxScoring.ts
  • apps/web/app/components/consumption-tax/CourseExercise.vue
  • memo/2026-07-02/shohizei-course-source-notes.md(素材対比メモ)
  • memo/2026-07-02/ai-utilization-review.html(起点のダッシュボード)