Files
rose-ash/plans/designs/e39-webworker.md
giles df8913e9a1 HS-design: E39 WebWorker plugin
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 07:08:02 +00:00

5.8 KiB
Raw Blame History

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:

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.