Files
improvise/web/worker.js
T
Edward Langley 3d3bec43e0 docs(web,harness): cap the JavaScript and the browser harness
web/app.js's transport/watchdog/frameCost arguments and web/worker.js's two
ordering rules — including the one nothing here can test, that an `async`
message listener does not serialize — are now prose in context/code/clients.md.
The harness keeps its own section: why a worker, why a report, the engine
coverage and what `wipe` deliberately cannot clear.

harness/tests/*.spec.js's comments were the record of what each assertion
proves; they are in context/test-evidence/clients.md under the exact test
titles, including the `test.fail` measurement that could NOT become a CI guard
because its trigger is the host's network stack, and the closed-invalid
improvise-xps8 report that a simulated fix appeared to confirm.

ws-server/src/main.rs's ```text fence was prose claiming the doctest
exemption, not a runnable doctest; the synopsis is now two lines and the full
form is in the prose file. No fences remain in this unit.

Verified: node --check on every JS file touched; the non-comment diff for both
'*.rs' and '*.js' is empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 10:02:03 -07:00

64 lines
2.1 KiB
JavaScript

// The worker half of the standalone deployment (improvise-9x6): strings only,
// every decision in Rust. The two rules it must not break: context/code/clients.md.
import init, { start } from "./worker-pkg/improvise_worker_server.js";
const params = new URLSearchParams(self.location.search);
const DOC = params.get("doc") || "demo.improv";
// Where the first copy of the document comes from on a fresh origin: fetched
// and WRITTEN through the VFS, never injected past storage.
const SEED = params.get("seed") || "demo.improv";
// Fetch the seed if there is one to fetch. A missing seed is not an error:
// with none, the worker creates an empty model under DOC instead.
async function seedBytes() {
try {
const res = await fetch(SEED);
if (!res.ok) return undefined;
return new Uint8Array(await res.arrayBuffer());
} catch (_) {
return undefined;
}
}
let server = null;
function post(replies) {
for (const reply of replies) self.postMessage(reply);
}
async function handle(data) {
const replies = server.on_message(data);
post(replies);
await server.flush();
}
// Startup, and the head of the chain: frames arrive before this settles, and
// chaining onto it holds them in arrival order with no queue to drain.
const started = (async () => {
await init();
server = await start(DOC, await seedBytes());
})();
started.catch((err) => {
console.error("improvise worker: failed to start", err);
});
// One frame at a time, in arrival order: the listener is deliberately NOT
// `async` — see context/code/clients.md.
let tail = started.catch(() => {});
self.onmessage = (event) => {
tail = tail
.then(async () => {
// `start()` failed. There is nothing to answer with, and the main
// thread's handshake watchdog is what tells the user.
if (!server) return;
await handle(event.data);
})
.catch((err) => {
// An unhandled rejection here reaches the devtools console and nothing
// else. Caught, so one bad frame does not poison every later one.
console.error("improvise worker: frame rejected", event.data, err);
});
};