インタラクティブ教材を1枚に集約し、本文の線を減らした日

開発mdx-playground

動いているのは見た。合っているかは見ていない

他サイトの解説記事を日本語に訳したページに、インタラクティブな教材を15個ほど埋め込んである。リレーを叩くと信号が作り直され、歯車を回すと桁が繰り上がる。作ったときに一通り触って、よくできていると思った。

思っただけだった。原文に書いてある挙動と、画面の中で起きている挙動が一致しているかは、一度も突き合わせていない。 訳文は目で追ったが、教材は目で追えない。動かさないと中身が出てこないからだ。

そこで朝いちばんに、教材15個を全部ブラウザで開いて操作させ、原文の記述と照合させることにした。

15個を順番に叩いて回る

Chrome DevTools MCP で対象ページを開き、コンポーネントを上から順に操作していく。OR の並列回路、XOR のゲート組み立て、半加算器の全パターン、真空管、トランジスタ、アナログ→デジタル変換。片端から入力を変えて、出力が原文どおりかを見る。

ズレていたのは3つだった。

  • 二進カウンター: 繰り上がりの見え方が原文の説明と噛み合っていない
  • SRラッチ: 禁止状態(Set と Reset を同時に立てた状態)の扱い
  • コンパイラのマッピング: 縦に全部並んでいて対応関係が追えない → タブに分けた

ついでに、原文にあって教材に入っていなかったものを足した。ネットワークトレースに TLS のステップがなかったので追加。アナログ→デジタル変換の初期値と、真空管の初期状態(OFF から始まる)も原文に合わせた。

直したあとは、リロードして実際に叩き直す。SRラッチは禁止状態で Q と Q̄ が両方 0 になり、Reset を外すと Set だけが残って Q=1 に確定した。仕様どおりだった。

スクリーンショットが返ってこない

最後に全体を画像で押さえようとしたら、スクリーンショットの処理がタイムアウトして返ってこなかった。何度か待ったが同じだった。

見た目の証拠が取れないなら、DOM を直接読めばいい。document.querySelector で各コンポーネントの状態を引き出させ、修正が反映されているかを値で確認した。画で確かめるつもりが、値で確かめる形に切り替わった。

もうひとつ引っかかったのが、記事内に置いた1コマ漫画の画像が dev サーバーで表示されないこと。最初はパスの書き方を疑ったが、curl を叩かせたらサーバーは 200 を返している。配信は生きていて、ブラウザだけが古いものを掴んでいた。ハードリロードで出た。サーバーが 200 を返しているのに見えないなら、疑う先はコードではなくキャッシュだった。

教材だけを触れるページを1枚作る

15個の教材は、長い翻訳記事の中に散らばっている。読み物として読むときはそれでいいが、「歯車のやつだけもう一回触りたい」というときに記事を上から探すことになる。

そこで、インタラクティブな部分だけを取り出したページを新設させた。http://localhost:3000/software-simulators がそれで、トップページの「コンテンツ」カード群の直下に入口のカードを1枚足してある。記事を読む導線と、教材を触る導線を分けた形だ。

パンくずのラベルも追加したので、app/utils/breadcrumbs.ts に手が入る。ここは純粋関数なのでユニットテストを回した。

見出しで区切って、あとから足せるようにする

同じ日、別のセッションで講座側のシミュレータを作り足していた。これも一覧に並べたいが、ただ後ろに継ぎ足すと20個超のカードが平坦に続くだけになる。

一覧をテーマ単位のグループに分け、それぞれに見出しを立てる構造にした。

  • 2つの状態でコードを作る
  • 歯車で数える
  • スイッチで論理を作る
  • 二進法で数えて、足す
  • スイッチの中身(物理)
  • 現実をビットに変換する
  • 記憶して、実行する

こうしておけば、新しく作ったぶんは該当する見出しの下に置くだけで済む。同じシミュレータが記事側と講座側の両方から登録されて二重に出ないよう、レジストリから作った集合で照合して弾く形にした。

// 講座側に登録済みのIDを集合で持ち、一覧側の重複掲載を防ぐ
const registered = new Set(courseRegistry.map(c => c.id))

セキュリティ観点でも見直したが、この集合はビルド時に確定するレジストリから作っていて、外から入ってきた値は一切混ざらない。

線が多すぎて読めない

一覧が形になったところで、本文を読み返して手が止まった。

解説の文字がグレーで薄い。そして、ひとかたまりの説明ごとに枠線で囲ってある。教材が15個あるということは、枠も15個以上ある。画面が罫線だらけで、どこからどこまでが1つの説明なのかが逆に分からなくなっていた。

たとえば「1の位が9から0になる瞬間だけ、繰り上げピンが隣の行を一つ押し出す」という一文。これは解説の本体であって、注意書きでも補足でもない。囲う理由がない。

方針は2つだけにした。

  1. 本文の文字色をグレーから黒に戻す#121212
  2. 意味のない枠線を消す。ただし背景のグレーは残す

枠線は15コンポーネントで共通の .lesson クラスに付いていたので、そこを一括で落とせば全部に効く。背景のグレーは残したので、「ここは解説」という区切りは消えていない。線が1本減っただけで、同じ文章が読める文章になった。

背景色で区切れているなら、枠線はもう一度同じことを言っているだけだった。

2〜3週間前の記事を、記憶だけで探す

午後は別件から始まった。「以前に他サイトの記事をインタラクティブ化してもらった記憶がある。ある技術書を題材にした似た記事とは別のやつで、2〜3週間くらい前」という、それだけを手がかりに記事を特定させたい。

タイトルも slug も覚えていない。覚えているのは、プログラミングの原理みたいな内容だったことだけ。

コンテンツディレクトリを7月上旬〜中旬に絞って一覧させ、候補を出させたら、7月8日の日本語訳記事が引っかかった。中身を開いて、これだと分かった。日付の範囲と内容のジャンルだけで、記憶の中の1本にたどり着けた。

画像が10枚とも死んでいた

その記事を開いて、写真が1枚も出ていないことに気づいた。

原因を調べさせたら、10枚すべてが 404 だった。画像を外部サイトに直リンクしていて、そのサイトが再ビルドされた結果、画像URLに含まれるハッシュが変わっていた。こちらのリンクだけが古いハッシュを指したまま残っていた。

外部の再ビルドはこちらで止められない。同じ書き方を続ける限り、また同じ日が来る。

判断はすぐ決まった。画像はダウンロードして記事ディレクトリに置き、出典としてURLを表示する。 この記事は非公開扱いなので、出典を明示しておけば運用上の問題はないと考えた。

このリポジトリでは、記事ディレクトリの画像は日付付きの絶対パスで参照する形が既存パターンになっている。

![キャプション](/2026-07/2026-07-08/image-01.jpg)

10枚をダウンロードさせ、JPEG として壊れていないことを確認してから、記事内の参照10箇所をこの形に置換した。冒頭の注記に、画像の出典URLを一文で足した。

最後にブラウザで開いて、DOM 上で全画像の naturalWidth が 0 より大きいことを確認した。表示されているように見えるだけの状態と、実際に読み込めている状態は違う。10枚とも読めていた。

今日わかったこと

  • 動くことと、正しいことは別物だった。 教材は最初から動いていた。原文と突き合わせて初めて、3箇所の食い違いが出てきた
  • スクリーンショットが取れなくても、確認は諦めなくていい。 DOM の値を読めば「反映されているか」は判定できる
  • サーバーが200を返しているのに見えないなら、コードではなくキャッシュを疑う
  • 背景色と枠線は、同じ「区切り」を二重に主張していた。 片方を消すと読みやすくなる
  • 外部画像の直リンクは、相手のビルドに寿命を握られる。 手元に置いて出典を書くほうが長持ちする

明日やること

  • 今日の変更(教材の修正・一覧ページ・本文スタイル)をまとめて本番にデプロイする
  • 「本文は黒・意味のない枠線は使わない」をスキルとして書き出し、次に作るコンテンツで最初から適用する
  • 記事内に残っている他の外部直リンク画像を洗い出し、同じようにローカルへ引き取る