quiz-server(/learn 学習ゲート)を講座コンテンツ化 — 公開前sanitize・ZIP配布への転換・静的配信でSVG表示崩れを解決

開発eurekapu-nuxt4

quiz-server を教材にした — 公開前スキャンから静的配信の実装まで

前日 Claude Code に実装させた /learn 学習ゲート(quiz-server + git pre-commit フック)を、eurekapu-nuxt4 の「スラッシュコマンドをコンテンツ教材にする」シリーズの第4弾として追加した。教材を1本足すだけのつもりが、GitHub 公開前のスキャンで方針が転換し、インライン SVG の表示崩れを quiz-server 側の静的配信実装で潰すところまで転がった。最後は自分が作らせたゲートが、実装のバグを拾った。

発端: 昨日の実装をシリーズ第4弾に

「スラッシュコマンドをコンテンツにする」記事が既に3本あるので、昨日の /learn + quiz-server もそこに足すことにした。まず調査エージェント3体を並列で走らせた(eurekapu-nuxt4 のコンテンツ化スキル / mdx-playground の学習ゲート実装 / quiz-server 本体とグローバル設定)。

quiz-server の実体はこう整理された。

  • 本体は ~/.learn/quiz-server(TypeScript + MCP SDK + Turso、SM-2 簡易の間隔反復付き)
  • pre-commit はステージ差分の SHA1 と受領証を照合するだけの門番
  • /learn・/review は ~/.claude/commands/ のユーザーレベルコマンド

実装計画のドキュメント(memo/2026-07-06/ の学習ゲート設計書)も参照させた。「認知的負債をコミット境界で毎回精算する」という設計書の背景説明が、教材の導入部にそのまま流用できる構造だった。設計は Plan エージェントに eurekapu-nuxt4 の実ファイルを確認しながら組ませた。

公開前スキャン → ZIP 配布 + sanitize 済み公開リポジトリへ

quiz-server の GitHub リポジトリを教材から参照させるつもりだったので、パブリックにする前にプライベート情報が入っていないかスキャンさせた。結果、公開前に直すべき内容が実際に出てきて、既存リポジトリをそのまま公開するのは不適切と裏が取れた。

あわせて考え直した。このリポジトリはほとんど更新しない。だったら受講者向けは quiz-server 本体も含めた全部入り ZIP 配布でいい。二重管理の懸念は更新頻度の低さで相殺できる。

方針はこう落ち着いた。

  • 教材の配布物は全部入り ZIP
  • 参照用に、sanitize 済みの独立した公開リポジトリを新規に作る(個人情報の残存ゼロを検査してから git init)

講座本体: 9ページ + 体験クイズ + 図版3枚

講座は9ページ構成で実装させた。

  • majorId は 63000 帯の空きを使用
  • 体験クイズは slot 型ページ + 既存の InlineQuiz で構成し、新規コンポーネントなし
  • 図版3枚(全体フロー・2系統分離・/review ループ)は svg-diagram スキルのグリッド規律で作成

変更対象が10ファイルを超えたので Codex レビューを挟み、指摘2点を修正して「それ以外に致命的な穴はない」の判定をもらった。dev 環境で全ページをブラウザ描画確認し、コンソールはエラー・警告ゼロ。コミット4本で一区切りにした。

途中、JSON と zip を再生成した直後にページが白紙になる場面が2回あった。コンソールにエラーはなく、少し待ってリロードすると戻る。再生成直後の一時的な読み込みタイミングの問題で、壊れているわけではないと分かるまで一瞬ひやりとした。

インライン SVG の表示崩れ → .svg ファイル読み込みを明示

コミット後、自分の理解を確認したくなった。インライン SVG は表示崩れするから、.svg ファイルを作ってそれを読み込む形にすれば崩れない — この理解で合っているなら、教材側に明示した方がいいのではないか。

確認させると理解どおりだった。崩れるのは Nuxt Content のレンダラを通るときだけで、.svg ファイルを <img> で読む分には崩れない(svg-diagram スキルに定めているルールそのもの)。そこで /learn の作図に svg-diagram スキルを必須化する明示を入れさせ、原典・snapshot・配布版・講座本文の4箇所に同期反映してコミットした。

quiz-server に静的配信を実装させる

ただし明示だけでは /learn の図は救えない。クイズ画面は quiz-server が配信する HTML なので、.svg ファイル方式にするならサーバー側に静的配信(static file serving)が要る。「クイズサーバーに静的配信を実装して解決して」と指示した。

実装内容は静的配信 + SVG フェンス変換。解説マークダウン中の SVG コードフェンスを .svg ファイルとして書き出し、<img src="/lessons/..."> 参照に置き換えて配信する。テスト34件パス、クイズ画面とレッスン MD の両方で崩れない表示を実機確認し、公開リポジトリに v0.2.0 として push した。プライベート版にも機能ファイル4点を同期させた。

作らせたゲートが実装のバグを拾う

プライベート版のコミットが学習ゲートにブロックされたので /learn を実行した。ところがステージ済み変更が空。並行して動かしていた別セッションが先にコミットを済ませていた。ログを追わせると、代わりに別の問題が目に入った。保存済みのレッスン MD に図がフェンスのまま残っていて、.svg ファイルが生成されていない。

原因はフェンス検出の正規表現だった。行頭アンカーがなく、SVG 内部に「```svg フェンスで書く」という3連バッククォート入りの説明テキストがあると終端を取り違える。壊れたレッスン MD の修復も正規表現ベースでは同じ罠を踏むので、行頭判定のスクリプトで修復させ、図が <img> 経由で描画されるところまで確認した。

コミットを通すために作らせたゲートが、コミット直前に実装のバグを1件あぶり出した。昨日「ゲートを作らせた意味」を書いたばかりだが、今日は仕組みの側が先に回収してきた。

セキュリティレビュー

別セッションで /security-review も回した。対象の変更30ファイルはすべて自動生成の静的 TypeScript データモジュール(数値・タイムスタンプ・データポイント追記のみ)で、指摘ゼロだった。

学びメモ

  • インライン SVG が崩れるのは Nuxt Content のレンダラを通るときだけ。.svg ファイルを <img> で読めば崩れない。この区別を教材とスキルの両方に明示した
  • 「たぶん入っていない」で公開しない。実際にスキャンさせたら公開前に直すべき内容が出てきて、独立した sanitize 済みリポジトリを作る判断に変わった
  • 更新頻度が低い配布物は ZIP でいい。リポジトリ参照にこだわると二重管理の維持コストだけが残る
  • SVG の中に3連バッククォート入りテキストがあると、行頭アンカーなしの正規表現はフェンスの終端を取り違える。フェンス処理は行頭判定で書く
  • 学習ゲートは知識の精算だけでなく、コミット直前の成果物を人間の目に通す検問所としても機能した

残課題

  • quiz-server のフェンス検出正規表現の恒久修正(行頭アンカーなし・SVG 内の ``` で終端が乱れる)。壊れたレッスン MD はスクリプトで修復済みだが、サーバー側の修正は保留中
  • 講座の公開フェーズ(配布物配置・R2・draft 解除・デプロイ)。計画書に記録済み