3d3bec43e0
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>
64 lines
2.1 KiB
JavaScript
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);
|
|
});
|
|
};
|