自作会計システムに消費税を通した日。composable の await 連続で Nuxt コンテキストが消えた

開発accounting-system-lab

朝いちで投げた質問が「これってなんか積み残しありましたっけ。これ何のプロジェクトでしたっけ」だった。

自分で作ったものを、自分で忘れている。

返ってきた棚卸しはこうだった。自作の会計システムの MVP。Nuxt 4 と Nuxt UI 4、Drizzle、libSQL/SQLite で組んであって、pnpm dev を叩くと 3300 番で立ち上がる。Phase1 から Phase5 までは実装もコミットも終わっている。ユニットテストをその場で走らせたら67件が全通過した。

積み残しは4つ挙がった。Phase6 の消費税がまるごと未着手。トップページが「セットアップ中」の文言で止まっている。反対仕訳の自動起票が無い。Phase3 から Phase5 の E2E が足りていない。

「じゃあこれ順次続けてください」とだけ返した。

スキーマから始めた消費税対応

タスク管理ツールに積み直させてから、コード全体を読み込ませた。着手は Phase6 の消費税からにした。

順番は下から上に固定した。スキーマ、ドメイン関数、API と seed、最後に UI。

マイグレーションは ALTER TABLE の ADD COLUMN だけで適用し、既存のトリガーはそのまま残した。ドメイン関数まで通ったところで最初のタスクが閉じた。

次は API と seed だった。既存の仕訳フローを確認させてから、税区分マスタと消費税集計の API を足した。

税額の計算をどこに置くかだけは自分で決めた。仕訳の検証ロジックに、税区分の解決と消費税明細の生成をまとめて組み込ませた。検証を通った仕訳には必ず税額がついている状態になる。入力画面が増えても、税の出し方は一箇所しか無い。seed も合わせて更新させた。

UI では、仕訳入力画面に税区分の選択と税額プレビューを載せた。仕訳を入力する画面は2つあるので、共通のロジックは composable に切り出して両方から呼ばせた。

ここまでは詰まらずに進んだ。

Chrome DevTools MCP が繋がらない

描画を確認するため dev サーバーを起動させた。Chrome DevTools MCP がブラウザに接続できない。

繋げる工夫で粘るのはやめて、Playwright での確認に切り替えさせた。やりたいのは接続の復旧ではなく、画面が正しく描けているかの確認だ。

切り替えた先で不具合が出た。

composable の await と Nuxt コンテキスト

原因は composable の中にあった。await を連続させていて、その先で Nuxt のコンテキストが失われていた。形としてはこういう並びになる。

const useTaxPreview = () => {
  // 1つめの await までは通る
  // その先の await のあとでは Nuxt のインスタンスを拾えない
  // composable が内部で当てにしている機能が、そこから効かなくなる
}

仕訳画面から共通ロジックを引き剥がしたときに await が増えて、そのまま踏み抜いたのだと思う。

修正させたら、画面はすべて正常に出た。

既存の E2E も税の追加で壊れたので、設定を見直させた。Phase6 の E2E 6件が全部通ったところで消費税対応が閉じた。

トップページ、反対仕訳、管理画面

「セットアップ中」のままだったトップページをダッシュボードに変えさせた。続けて反対仕訳の自動起票を実装させた。借方と貸方を入れ替えた仕訳を、手で打ち直さずに起こせるようにする。

型チェックを回しながら、E2E 用に既存ページの testid を確認させた。テストを書き足す前に共通ヘルパーを切り出させて、そのあとで消費税、帳票、反対仕訳の3系統を追加した。13件が全通過。

最後がユーザー管理と会社設定の画面だった。ここまで足して E2E は18件になった。

エラーメッセージの中身と二重表示

新しい画面を動かしていて気づいた。エラーメッセージにサーバー側の理由が出ていない。共通の取り出し処理を入れて全画面に適用させた。

今度は同じエラーがモーダルの中とページ上部の両方に出た。モーダルを開いている間はページ側を隠すよう直させた。

もう一度描画を確認させたら、会社設定のヒント文が入力欄から離れて浮いていた。ここも詰めさせた。

コミットと学習ゲート

コミットしようとしたら、pre-commit の学習ゲートに止められた。/learn を起動したところで、こちらから「学習ゲートはパスでいい」と伝えた。理由を残してスキップコミットにした。ローカル設定の .claude/ は扱いの判断が要るので、コミットからは外してある。

積み残し7件をすべて実装して c81e87c でコミットした。ユニット100件、E2E18件、型チェックがすべて通った。画面は Playwright で実際に描かせて確認してある。

朝に走らせたときは67件だった。

そこでセッションの制限が近づいた。ドキュメントに詳細を残すよう頼んで、作業ログを memo 配下に1本書かせて 0bec8b9 でコミットした。作業ツリーはクリーンな状態で終わった。

「何のプロジェクトでしたっけ」と聞くところから始めて、積み残し7件が消えた。また同じことを聞く日は来る。そのときのために残したログでもある。

学び

  • ブラウザに繋がらないときは、繋げる工夫で粘らない。描画を確認するだけなら Playwright で足りる
  • composable への切り出しでは、中の await を数える。Nuxt のコンテキストは await をまたぐと失われる
  • テストを足す前に共通ヘルパーを切り出す。この順番は今回も効いた
  • エラー表示は「出るか」ではなく「理由が読めるか」で点検する。二重表示も同じ見方で拾える
  • 学習ゲートは黙って回避せず、理由を残したスキップで通す。判断そのものがログに残る