From df8913e9a1d9ae027783217db42d328c5b5fb062 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 24 Apr 2026 06:52:47 +0000 Subject: [PATCH] HS-design: E39 WebWorker plugin Co-Authored-By: Claude Opus 4.7 (1M context) --- plans/designs/e39-webworker.md | 150 +++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 plans/designs/e39-webworker.md diff --git a/plans/designs/e39-webworker.md b/plans/designs/e39-webworker.md new file mode 100644 index 00000000..3add7aae --- /dev/null +++ b/plans/designs/e39-webworker.md @@ -0,0 +1,150 @@ +# E39 — WebWorker plugin + +Cluster 39 of `plans/hs-conformance-to-100.md`. Goal: +1 test. + +## 1. The 1 failing test + +Suite `hs-upstream-worker`, test: + +> `raises a helpful error when the worker plugin is not installed` + +Translated upstream (`test/features/worker.js`) — currently emitted as +`SKIP (untranslated)` in `spec/tests/test-hyperscript-behavioral.sx` path +`(117 2 2)`: + +``` +(deftest "raises a helpful error when the worker plugin is not installed" + (error "SKIP (untranslated): raises a helpful ...")) +``` + +Upstream assertion: + +```js +const msg = await error("worker MyWorker def noop() end end") +expect(msg).toContain('worker plugin') +expect(msg).toContain('hyperscript.org/features/worker') +``` + +Two substring checks on the error produced by compiling the source. + +## 2. Upstream `worker` syntax + +From `https://hyperscript.org/features/worker/`: + +``` +worker [(*)] + ( | )+ +end +``` + +- Declared at top level alongside `on`, `init`, `def`, `behavior`. +- Body restricted to `def` functions and `js ... end` blocks; no DOM / `window` access. +- Callers invoke as `WorkerName.fn(args)` from the main thread; calls + return promises but are async-transparent. +- Stock `_hyperscript` ships only a **stub** in the core bundle — the + real implementation is in the `worker` ext. Without the ext loaded, + parsing must fail with a message mentioning both `worker plugin` and + `hyperscript.org/features/worker`. + +## 3. Proposed SX shape + +We ship the stub only. Full runtime is out of scope for a 1-test cluster. + +### Parser addition (`lib/hyperscript/parser.sx`, `parse-feat`) + +Extend the feature-dispatch `cond` (around line 2620) with one branch +**before** the fallthrough to `parse-cmd-list`: + +``` +((= val "worker") (parse-worker-stub)) +``` + +`parse-worker-stub` raises immediately — it does not consume tokens, +does not need to recognise the body grammar. The error string contains +both required substrings: + +``` +"worker plugin is not installed — see https://hyperscript.org/features/worker" +``` + +`(error ...)` at parser scope is the existing failure channel (used by +`Expected 'of' or 'from' at position …` etc.), so this slots in without +changes to the error plumbing. + +### Compile output + +None. The stub never returns an AST; the error propagates out of +`hs-compile` and through `hs-to-sx-from-source`, surfacing to the test +runner as a caught exception. + +## 4. Runtime architecture + +For the 1 test: **no runtime.** Parsing fails, so `runtime.sx` never sees +a worker form. No `Worker` class needed in the `hs-run-filtered.js` +mock. Nothing touches the DOM shim. + +For a hypothetical full implementation (explicitly out of scope): + +- Server would bind `WorkerName` in the HS top-level env to a record + `{:worker-handle H :exports (list ...)}`. +- `WorkerName.fn(args)` would compile via the existing property-access + path to `(hs-method-call WorkerName "fn" args)`, which would detect + the worker handle and dispatch over a `postMessage` channel. +- Mock env would need a `Worker` class constructor + a serialisable + message loop driving the worker script's own tiny SX interpreter. + Deferred until the 7 skipped upstream tests become a target cluster. + +## 5. Test delta estimate + ++1 test. Feasibility: **high**. Two substring checks. Implementation +is a single `cond` branch plus a 2-line error string. Generator patch +to un-skip the test is mechanical. + +## 6. Risks + +- **Drift from future real plugin** — if we later implement the plugin, + the stub must be replaced, not shadowed. Mitigation: the stub lives + inline in `parse-feat`; adding the real plugin means deleting the + stub branch and replacing it with `(parse-worker-feat)`. Single site. +- **In-browser vs mock divergence** — none in this cluster. The stub + errors identically in both hosts because it's pure parser logic. +- **Message serialisation** — N/A until full plugin. +- **Error-message phrasing drift** — the upstream test only checks for + two substrings. We must keep `worker plugin` and + `hyperscript.org/features/worker` verbatim; change-detector tests + should reference this design if the wording is ever touched. +- **Generator mis-translation** — the upstream JS uses `await error(src)` + which the SX generator currently bails on (`return None`), producing + the `SKIP (untranslated)`. We can either (a) teach the generator to + translate `await error(src)` + two `toContain` checks, or (b) hand- + write the test. Recommend (b) for one test: less generator churn. + +## 7. Implementation checklist (single commit) + +1. `sx_replace_by_pattern` in `lib/hyperscript/parser.sx` — insert the + `((= val "worker") ...)` branch inside `parse-feat`'s `cond`. Body: + `(error "worker plugin is not installed — see https://hyperscript.org/features/worker")`. +2. `sx_replace_node` in `spec/tests/test-hyperscript-behavioral.sx` + path `(117 2)` — replace the `SKIP (untranslated)` body with: + ``` + (deftest "raises a helpful error when the worker plugin is not installed" + (let ((result (guard (e (true (if (string? e) e (str e)))) + (hs-compile "worker MyWorker def noop() end end") + ""))) + (assert (contains? result "worker plugin")) + (assert (contains? result "hyperscript.org/features/worker")))) + ``` +3. `sx_validate` on both files. +4. Run `node tests/hs-run-filtered.js` with `HS_SUITE=hs-upstream-worker`. + Expect 1/1. +5. Run full suite smoke (0–195) — expect no regressions (pure additive + branch in `parse-feat`; `"worker"` was previously caught by the + fallthrough `parse-cmd-list`, which would have errored anyway on + an unknown identifier — confirm by checking baseline). +6. Commit: `HS: E39 WebWorker plugin stub (+1 test)`. + +## Non-goals + +- Any worker runtime. Any real `Worker` object. Any message passing. +- The 7 sibling `test.skip(...)` cases upstream — they remain skipped. +- Generator patches — the test is hand-written per §6 above.