HS-design: E39 WebWorker plugin

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 06:52:47 +00:00
parent 4ee748bf42
commit df8913e9a1

View File

@@ -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 <name>[(<external-script-url>*)]
(<def-feature> | <js-block>)+
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 (0195) — 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.