# 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.