実行可能サンプル(RUN)を Web Worker 化して停止・タイムアウトに対応#240
Conversation
RUN ボタンは ruby.wasm をメインスレッドで同期実行しており、無限ループや 長い sleep を含むサンプルではタブの UI が完全に固まっていた(fix rurema#239)。 - 実行を新設の theme/default/js/run-worker.js (module Worker) に移動。 wasm のコンパイルはこれまでどおりメインスレッド側で一度だけ行いキャッシュ し、WebAssembly.Module を実行ごとに使い捨ての Worker へ postMessage で 渡す。VM は実行のたびに作り直すので、terminate() 後の再実行やコンパイル やり直しの問題は生じない - 実行中は RUN ボタンを STOP に切り替え、クリックで Worker.terminate() して中断できるようにした。30 秒(RUN_TIMEOUT_MS)経過でも自動的に terminate し、出力欄にその旨を追記する - $stdout/$stderr を StringIO に溜めて eval 完了後に一括取得していたのを やめ、Worker 内で書き込みのたびに JS.global.call(:postOutput, text) で メインスレッドへ逐次 postMessage するようにした。表示側の 64KB 打ち切り (旧 truncateOutput 相当)は accumulateOutput として維持しつつ、打ち切り 後の追加チャンクは即座に捨てるようにして無限出力ループでも O(1) で済む ようにした - Worker スクリプトの URL は new URL('run-worker.js', import.meta.url) で run.js 自身の URL から解決し、ページの階層やテンプレート側の custom_js_url() の実装に依存しないようにした - run-worker.js も run.js 同様に .mjs ではなく .js 命名にした(rurema#217 と同じ MIME 判定の理由)。statichtml のテーマファイルコピー処理 (copy_run_ruby_wasm_script)を RUN_RUBY_WASM_JS_FILES 経由で2ファイルとも コピーするよう拡張し、片方だけ欠けているテーマも許容する - test/js/test_run.mjs に純粋関数のテスト(accumulateOutput・ STOPPED_NOTE・timeoutNote・run-worker.js の PRELUDE/formatRunError)を 追加。Worker/DOM は QuickJS では扱えないため、テストはこれまでどおり 純粋関数部分のみを対象にしている Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- RUN/COPY を1つの float コンテナ(.highlight__button-group)にまとめ、 ボタン間・周囲の余白で pointer と text のカーソル形状がちらつかない ようにする(隙間のカーソルと選択不可はコンテナが引き受ける) - 編集可能にしたときに先頭へ空行が入るのを解消。コンパイラが <code> 直後に置く改行をプレーンテキスト化時に除去(editableText)し、編集時の code を inline-block + width:100%(float と同じ行に入れずボタンの下に 落ちる)から block に変更 - 貼り付けで改行が <br>/<div> になって前の行にくっつくのを解消。 contentEditable=plaintext-only を優先し、未対応ブラウザは paste で text/plain をキャレット位置に挿入するフォールバック
Claude Code へのレビュー結果入力:#240 の以下を確認しました。
以下の問題が気になりました。
RUN や COPY のボタンは調整が難しければ pre の外の方が良いかもしれません。 以下、Claude Code によるコメント:ブラウザでの確認と指摘ありがとうございます。指摘の3点に対応するコミットを追加しました。
「ボタンを pre の外に出す」案も検討しましたが、COPY は全ページの全 検証: qjs の2スイート(test_run.mjs / test_script.mjs、グループ構造と editableText のテストを追加)とフルスイート
|
pre 内への float 配置は、カーソル形状のちらつき・編集時のレイアウト 崩れ・pre の上 padding を 0 にするハックなど無理が多かったため、 pre の直前のツールバー行 <div class="highlight__toolbar"> に移す。 - script.js: 全 pre の直前にツールバー行を生成し、右端のボタン置き場に COPY を入れる。コンパイラが pre の直前に置く caption(タブ)があれば ツールバー左端に取り込み、pre への密着を保つ - run.js: RUN ボタンも同じボタン置き場の先頭に入れる。RUN の出力欄は highlight--with-output / highlight__toolbar--attached で入力欄の pre に 密着させて1ブロックに見せる - pre の上 padding を復活(1.1em)。出力・編集テキストが枠の上端に 張り付いていたのはこれが 0 だったため - 編集時に pre の padding 部分(1行サンプルの下半分など)をクリック しても入力欄に入れるよう、pre へのクリックで code にフォーカスして 末尾にキャレットを置く - RUN/COPY ボタンは角丸(4px)でボタンらしく、caption は上だけ角丸 (6px)でタブらしく
Claude Code へのレビュー結果入力:出力や編集可能なテキストが上にくっついているのは変化がありません。1行しかないサンプルコードで下半分でのクリックでは入力欄に入れなくなって不便になっています。 Claude Code によるコメント:再確認ありがとうございます。ご提案どおり、RUN/COPY ボタンを pre の外に移動するコミットを追加しました。
検証: qjs 2スイート(ツールバー構造・caption 取り込み・フックのテストを追加、計23+21件)とフルスイート ブラウザでの再確認をお願いしたい観点:
|
- ツールバー行が白い帯として浮き、コードの上の隙間が広く見えていた ため、pre と同じ背景色を与えてコードブロックと一体のヘッダー行に する。ヘッダーが上の余白を兼ねるので、直後の pre の上 padding は 0.25em に抑える - RUN の出力欄のヘッダーが入力欄の枠に貼り付いて見えていたため、 境に 2px の白を挟んで区切る - 出力欄だとわかるように、出力欄ヘッダーの左端へ caption と同じ タブ様式の「実行結果」ラベルを付ける
Claude Code へのレビュー結果入力:実行前のコードの上の隙間が広い気がします。 Claude Code によるコメント:確認ありがとうございます。指摘の3点に対応するコミットを追加しました。
検証: qjs 2スイート+フルスイート(17596 tests)全て pass。変更は CSS と run.js の出力欄生成部のみです。 ブラウザでの再確認をお願いしたい観点:
|
- ヘッダー行・caption・ボタンがどれも似た薄い灰色で埋没していたため、 ヘッダー行をコード部分よりひと段暗く(#e6e6e6)し、caption タブは コード部分と同じ色(#f2f2f2)で「下とつながったタブ」に、RUN/COPY は 白背景+枠線(opacity 廃止)でボタンとして浮き上がらせる - 出力が空のサンプルだと実行が終わったのかわからないため、正常終了時も 「(正常終了しました)」を出力欄の末尾に追記する(DONE_NOTE)
Claude Code へのレビュー結果入力:例や RUN, COPY ボタンも薄い灰色の中に入ってしまっています。 Claude Code によるコメント:確認ありがとうございます。指摘の2点に対応するコミットを追加しました。
検証: qjs 2スイート+フルスイート(17596 tests)全て pass。 ブラウザでの再確認をお願いしたい観点:
|
概要
RUN ボタンの実行を、ページのメインスレッドでの同期
vm.evalから Web Worker へ移します(fix #239)。無限ループや長いsleepを含むサンプルでタブの UI が完全に固まる問題を解消し、STOP ボタンでの中断・30 秒タイムアウト・出力の逐次表示を追加します。あわせて、レビューでのブラウザ確認を受けてサンプルブロックの UI を再設計しました。RUN/COPY ボタンは pre の中(float)から pre の外のヘッダー行に移り、「例」の caption や新設の「実行結果」タブと同じ行に並びます。
変更内容
実行の Web Worker 化
theme/default/js/run-worker.js(module Worker)を追加。1 回の実行につき 1 Worker を生成し、完了または中断で破棄します。VM は Worker 内で毎回作り直すため、実行同士の状態リークはありませんcreateOnceLoader)、WebAssembly.Moduleを構造化クローンで Worker へpostMessageします。STOP・タイムアウト後の再実行でもコンパイルのやり直しは不要ですWorker.terminate()して中断できます。30 秒(RUN_TIMEOUT_MS)で自動 terminate しますrun-worker.jsも.mjsではなく.js命名です(Rename js/run.mjs to js/run.js to survive strict MIME checking #217 と同じ MIME 判定の理由)出力の逐次表示と注記
$stdoutをStringIOに溜めて eval 後に一括読み出し」をやめ、Worker 内のPRELUDEが書き込みのたびにJS.global.call(:postOutput, text)でメインスレッドへ送ります。出力し続けるループで wasm メモリ内の文字列が膨らみ続ける問題も解消します$stdout/$stderrに入るJSStreamIOはwrite/flushだけでなくputs・print・printf・putc・<<・sync/sync=・tty?も実装しています。Kernel#puts/pは#writeフォールバックで動きますが、マニュアルのサンプルには$stderr.putsなどの直接呼び出しが多数あるためです(旧StringIOはこれらを備えていました)accumulateOutput)。打ち切り後のチャンクは即捨てるので暴走出力でも O(1) です(正常終了しました)・STOP(停止しました)・タイムアウト(30秒でタイムアウトしました)・例外はエラーメッセージ(赤字)。これらの注記は 1 回きりで長さが有界なので、64KB 打ち切り済みでも必ず末尾に表示されます(出力ゼロのサンプルでも完了がわかります)編集まわり
<code>直後に置く改行も除去します(editableText。編集開始時に先頭の空行として見えていたもの)<br>/<div>にならないようcontentEditable = 'plaintext-only'を優先し(Chrome/Safari/Firefox 136+)、未対応ブラウザは従来のtrue+pasteイベントでtext/plainをキャレット位置に挿入します。貼り付けた内容が前の行にくっつく問題の解消ですサンプルブロックの UI 再設計(レビュー指摘反映)
<div class="highlight__toolbar">を生成し、COPY(と run.js の RUN)をその右端のボタン置き場に入れます。pre 内への float 配置は、カーソル形状のちらつき・編集時のレイアウト崩れ・pre の上 padding を 0 にするハックなど無理が多いためやめました#e6e6e6でコードブロックと一体に見せます。caption(「例」)はコード部分と同じ#f2f2f2・上だけ角丸のタブとしてヘッダー左端に取り込み、RUN/COPY は白背景+枠線+角丸のボタンにして埋没を防いでいます1.1emに戻しました(上だけ 0 のハック廃止。中身が枠の上端に張り付く問題の解消)。ヘッダー行直後の pre は上 padding を0.25emに抑えますhighlight--with-output/highlight__toolbar--attached)ビルド・型
lib/bitclust/subcommands/statichtml_command.rbのcopy_run_ruby_wasm_scriptをRUN_RUBY_WASM_JS_FILES = %w[run.js run-worker.js]の一覧コピーに拡張しました。この一覧への追加漏れは RUN 機能が本番で無言で壊れる原因になるため要注意です。片方だけ欠けたテーマも警告してスキップしますsig/bitclust/subcommands/statichtml_command.rbsに型を追加実装メモ
new URL('run-worker.js', import.meta.url)でrun.js自身の URL から解決します。ページ階層やcustom_js_url()の実装に依存せず、テンプレートは無変更です(URLが無い QuickJS でのテスト実行を妨げないよう遅延評価){ module, code }、worker→main{ type: 'output', text }(0 回以上)→{ type: 'done' }または{ type: 'error', message }(必ず最後に 1 回)。STOP/タイムアウトは main 側がterminate()するだけですJSStreamIO#writeは worker 内の小さなヘルパーpostOutput()を呼び、{ type: ... }の封筒詰めは run-worker.js の 1 箇所に閉じていますcurrentで管理し、done/error/STOP/タイムアウトのどの経路も同じfinish()に収束させています検証
test/js/test_run.mjs(純粋関数:accumulateOutput・各注記・editableText・PRELUDEの形・formatRunError)、test/js/test_script.mjs(ヘッダー行の構造・caption の取り込み・ruremaAddCopyButtonフック・COPY の動作)全て passtest/test_run_worker_prelude.rbがPRELUDEを実 Ruby(JS ブリッジをスタブ)で eval し、Kernel#puts/pのフォールバック経路と$stderr.puts等の直接呼び出しの両方が旧StringIOと同じ出力になることを検証。test_statichtml_command.rbは 2 ファイルコピー+片方欠けテーマのテストを追加。フルスイートruby test/run_test.rb17596 tests 100% pass@ruby/wasm-wasiを module Worker 内で dynamic import する部分を含む)はレビューでのブラウザ確認に依っています。ローカルでの再現手順:fix #239
🤖 Generated with Claude Code