Hyperscript compiler/runtime:
- query target support in set/fire/put commands
- hs-set-prolog-hook! / hs-prolog-hook / hs-prolog in runtime
- runtime log-capture cleanup
Scripts: sx-loops-up/down, sx-hs-e-up/down, sx-primitives-down
Plans: datalog, elixir, elm, go, koka, minikanren, ocaml, hs-bucket-f,
designs (breakpoint, null-safety, step-limit, tell, cookies, eval,
plugin-system)
lib/prolog/hs-bridge.sx: initial hook-based bridge draft
lib/common-lisp/tests/runtime.sx: CL runtime tests
WASM: regenerate sx_browser.bc.js from updated hs sources
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
16 KiB
HS Conformance — Bucket F Plan
Based on a full suite run on 2026-04-26. Current score: ~1297/1489 covered (~87%). Skipped from runs: tests 197–200 (hypertrace, slow), 615 (slow), 1197–1198 (repeat-forever timeouts).
⚠ Updated 2026-04-26: The hs-loop completed significant Bucket D work before being stopped.
hs-f branches from loops/hs HEAD which already includes:
- MutationObserver mock +
on mutationdispatch (+7) → Group 4 likely done - Cookie API partial (+3/5) → Group 5 partially done
elsewhere/from elsewhere+ count filters (+7) → Group 3a/3c partially done- Namespaced
def(+3) → already done - SourceInfo E38 (+4) + WebWorker E39 (+1) → already merged
The Bucket F agent must run hs_test_run on each group's suite before implementing,
to verify what's actually still failing. Skip any group that already passes.
Total remaining failures: ~193. Broken into groups below.
Group 0 — Bucket E payoff (~47 tests, will land automatically)
These are already implemented or in-flight on Bucket E branches. Once merged they close ~47 tests.
| Suite | Tests | Status |
|---|---|---|
hs-upstream-core/tokenizer |
17 | E37 in progress |
hs-upstream-socket |
16 | E36 in progress |
hs-upstream-fetch |
8 | E40 in progress |
hs-upstream-core/sourceInfo |
4 | E38 done, not yet merged |
hs-upstream-worker |
1 | E39 done, not yet merged |
| E37 string interpolation bug | 1 | E37 |
Do not plan these — they resolve when Bucket E merges.
Group 1 — Null safety reporting (+7)
Suite: hs-upstream-core/runtimeErrors
Failures: 7 tests, all "Expected '#doesntExist' is null, got ``"
What's needed: When a command like put, increment, decrement, default, remove, settle, transition receives a null element (e.g. #doesntExist), HS must throw a structured null-safety error with the element reference in the message. The null check + error format is already designed in Bucket D #31 (cluster 31 of hs-conformance-to-100.md).
Estimate: +7. Straightforward — null guard at command dispatch entry.
Group 2 — tell semantics (+3)
Suite: hs-upstream-tell
Failures:
attributes refer to the thing being told— Expectedbar, got ``your symbol represents the thing being told— Expectedfoo, got ``does not overwrite the me symbol— assertion fail
What's needed: Inside a tell X block, you/your must resolve to X, attribute refs must resolve against X, and me must retain its original value (not be rebound to X). Currently tell rebinds me instead of introducing a separate you binding.
Estimate: +3. Scoping fix in the tell command handler.
Group 3 — on event handler features (+19, skip-list)
Suite: hs-upstream-on
34 tests on skip-list. Prioritise tractable subsets:
3a — Event filtering by count (+6)
can filter events based on countcan filter events based on count rangecan filter events based on unbounded count rangecan mix rangeson first click fires only oncemultiple event handlers at a time are allowed to execute with the every keyword
The on (N), on (N to M), on first, every modifiers. Parser + runtime counter state per handler.
3b — finally blocks (+6)
basic finally blocks workasync basic finally blocks workexceptions in finally block don't kill the event queueasync exceptions in finally block don't kill the event queuefinally blocks work when exception thrown in catchasync finally blocks work when exception thrown in catch
on … catch … finally analogous to JS try/catch/finally. Needs a finally-frame in the CEK machine (similar to dynamic-wind).
3c — elsewhere modifier (+2)
supports "elsewhere" modifiersupports "from elsewhere" modifier
on click elsewhere = click outside the element. Needs a global listener + target exclusion check.
3d — Exception events (+3)
rethrown exceptions trigger 'exception' eventuncaught exceptions trigger 'exception' eventcan catch exceptions thrown in hyperscript functionscan catch exceptions thrown in js functions
When an unhandled exception escapes an on handler, HS must dispatch an exception CustomEvent on the element.
3e — Element removal cleanup (+2)
listeners on other elements are removed when the registering element is removedlisteners on self are not removed when the element is removed
Cleanup hook via MutationObserver watching for element removal.
Deferred (skip-list, complex):
can be in a top level script tag— requires script tag re-initialisationcan ignore when target doesn't exist— target null guardcan handle an or after a from clause— parser edge caseeach behavior installation has its own event queue— behavior isolation
Group 4 — MutationObserver / on mutation (+10)
Suite: hs-upstream-on (mutation subset, skip-list)
Tests:
can listen for attribute mutationscan listen for attribute mutations on other elementscan listen for childList mutationscan listen for general mutationscan listen for multiple mutationscan listen for multiple mutations 2can listen for specific attribute mutationscan pick event properties out by namecan pick detail fields out by nameattribute observers are persistent (not recreated on re-run)(hs-upstream-when)
What's needed: MutationObserver mock in the test runner (hs-run-filtered.js) + on mutation command in the parser/runtime. Already prototyped in Bucket D #32.
Estimate: +10.
Group 5 — Cookie API (+5)
Suite: hs-upstream-expressions/cookies
All 5 tests untranslated. Cookie read/write as an expression: cookies.name, set cookies.name to val, cookies.name is undefined. Needs document.cookie mock in runner + cookie-expression parse path.
Estimate: +5. Self-contained.
Group 6 — Block literals (+4)
Suite: hs-upstream-expressions/blockLiteral
All 4 untranslated. Syntax: [x | x + 1] — an inline lambda. Used as a first-class value passable to map, filter etc.
basic block literals workbasic identity worksbasic two arg identity workscan map an array
Estimate: +4. Parser addition + runtime callable wrapping.
Group 7 — Async logical operators (+5)
Suite: hs-upstream-expressions/logicalOperator
Promise-aware and/or:
and short-circuits when lhs promise resolves to falseor short-circuits when lhs promise resolves to trueor evaluates rhs when lhs promise resolves to falseshould short circuit with and expressionshould short circuit with or expression
What's needed: and/or must await promise operands before short-circuiting. Currently they evaluate eagerly without awaiting.
Estimate: +5. Async await integration in logical operator eval.
Group 8 — evalStatically (+3)
Suite: hs-upstream-core/evalStatically
throws on math expressionsthrows on symbol referencesthrows on template strings
_hyperscript.evaluate(src, {}, { throwErrors: true }) must throw synchronously for expressions with side-effects or unresolved symbols. Currently the static evaluator doesn't gate on throwErrors.
Estimate: +3. Flag-gated error throw path.
Group 9 — Parse error API (+6)
Suite: hs-upstream-core/parser + hs-upstream-core/bootstrap
basic parse error messages workfires hyperscript:parse-error event with all errorsparse error at EOF on trailing newline does not crash_hyperscript() evaluate API still throws on first errorfires hyperscript:before:init and hyperscript:after:init(bootstrap)hyperscript:before:init can cancel initialization(bootstrap)
What's needed:
- Parser must emit a
hyperscript:parse-errorCustomEvent ondocumentwhen compilation fails, with the error list as detail. hyperscript:before:init/hyperscript:after:initlifecycle events dispatched around element initialization.before:initcan cancel (return false /event.preventDefault()).
Estimate: +6. Event dispatch hooks in the bootstrap/init path.
Group 10 — as expression conversions (+8)
Suite: hs-upstream-expressions/asExpression
Currently 30/42 = 12 failures. Tractable subset:
converts a NodeList into HTML— NodeList → outerHTML joinconverts strings into fragments— string → DocumentFragmentconverts elements into fragments— element → DocumentFragmentconverts arrays into fragments— array of elements → DocumentFragmentconverts array as Set— array → Set (dedup)converts object as Map— object → Mapcan accept custom conversions—as MyTypevia registered convertercan use the a modifier if you like—as a Numbersynonym
Two already-broken non-skip failures:
converts a complete form into Values— Expecteddog, got ``converts multiple selects with programmatically changed selections— Expectedcat, gotdog
Estimate: +8 for the tractable subset. Custom converters and Map/Set require runtime additions.
Group 11 — Miscellaneous runtime bugs (+12)
Small scattered failures, each 1–3 tests:
| Suite | Failure | Likely cause |
|---|---|---|
hs-upstream-put |
properly processes hyperscript ×3 (got 40, expected 42) |
Off-by-one in put ... before/after reprocessing |
hs-upstream-put |
waits on promises |
Promise await missing from put target eval |
hs-upstream-js |
can return values to _hyperscript |
JS block return value not threaded back |
hs-upstream-js |
can do both of the above |
Same |
hs-upstream-js |
handles rejected promises without hanging |
Rejected promise in js block uncaught |
hs-upstream-set |
set waits on promises |
Same as put |
hs-upstream-set |
can set into indirect style ref 3 |
Indirect style ref path bug |
hs-upstream-hide |
retain original display |
none vs block display tracking |
hs-upstream-toggle |
toggle for fixed time |
Timed toggle assertion timing |
hs-upstream-transition |
initial value |
initial keyword not restoring computed value |
hs-upstream-expressions/arrayLiteral |
objects with _order |
_order internal key leaking into equality check |
hs-upstream-core/bootstrap |
4 bugs | Event handler bugs in reinit, cleanup, respond |
hs-upstream-expressions/closest |
where clause |
where consumed by closest instead of outer |
hs-upstream-core/scoping |
2 bugs | Pseudo-possessive, built-in variable clash |
Estimate: +12 once individually triaged.
Group 12 — Formerly "hard floor" — now in scope
Initial assessment was wrong — these are medium difficulty, not genuinely hard. All 16 are worth attempting.
| Suite | Tests | Actual difficulty | What's needed |
|---|---|---|---|
hs-upstream-breakpoint |
2 | Trivial | No-op parser command + generator translation. Design: plans/designs/f-breakpoint.md |
hs-upstream-expressions/logicalOperator (unparenthesized error) |
2 | Low | Parser strictness: 1 + 2 + 3 should throw "ambiguous operator precedence" |
hs-upstream-core/security |
1 | Medium | _hyperscript.config.disableScripting = true guard at hs-activate! time |
hs-upstream-expressions/asExpression (Date, custom dynamic) |
3 | Medium | as a Date → new Date(val); custom converters via _hyperscript.addType registry |
hs-upstream-on (remaining skip-list) |
~8 | Medium | Script tag reinit (MutationObserver on <script> changes); behavior isolation queue |
Breakpoint — both tests just check that breakpoint parses without throwing. No devtools. See design doc.
Security — test creates a div with _="on click add .foo", activates it, clicks, asserts .foo is NOT added. This is a disableScripting config flag: when set, hs-activate! skips initialisation. One guard at activation entry.
Unparenthesized operator error — 1 + 2 + 3 in HS is ambiguous (no defined associativity for chained operators). Parser should throw a parse error rather than silently picking left-associativity. Needs a "multiple operators at same precedence level" check after parsing a binary expression.
Sequence these last — after groups 1–11 are done. Breakpoint is a 30-min job and should be pulled into the quick-wins batch.
Summary
| Group | Tests | Difficulty | Design doc |
|---|---|---|---|
| 0 — Bucket E payoff | ~47 | Free | (E branches) |
| 1 — Null safety | +7 | Low | f1-null-safety.md |
2 — tell semantics |
+3 | Low | f2-tell.md |
3 — on event features |
+19 | Medium | (TBD) |
| 4 — MutationObserver | +10 | Medium | (TBD) |
| 5 — Cookie API | +5 | Low | f5-cookies.md |
| 6 — Block literals | +4 | Medium | (TBD) |
| 7 — Async logical ops | +5 | Medium | (TBD) |
| 8 — evalStatically | +3 | Low | f8-eval-statically.md |
| 9 — Parse error API | +6 | Medium | (TBD) |
10 — as conversions |
+8 | Medium | (TBD) |
| 11 — Misc bugs | +12 | Low–Medium | (TBD) |
| 12 — Breakpoint | +2 | Trivial | f-breakpoint.md |
| 12 — Security config | +1 | Medium | (TBD) |
| 12 — Unparenthesized op error | +2 | Low | (TBD) |
12 — as Date + custom |
+3 | Medium | (TBD) |
12 — on remaining |
+8 | Medium | (TBD) |
| Total recoverable | ~145 |
Group 13 — Step limit + meta.caller (+5 → 100%)
Design doc: plans/designs/f13-step-limit-and-meta.md
| Test | Failure | Fix |
|---|---|---|
repeat forever works (×2) |
Step limit — loop terminates in 5 iterations but two compilation warm-up guards eat the budget first | Raise HS_STEP_LIMIT to 2,000,000 in hs-run-filtered.js |
hypertrace is reasonable |
Step limit — trace recorder may be on globally inflating step count | Raise step limit; disable global trace if on |
query template returns values |
Step limit (37s) — CSS template query <${"p"}/> may rebuild on every call |
Raise step limit; cache compiled template query if still slow |
has proper stack from event handler |
Wrong value — meta.caller.meta.feature.type returns "" instead of "onFeature" |
Implement meta dict in def function call scope; wire {:feature {:type "onFeature"}} into event handler contexts |
Summary
| Group | Tests | Difficulty | Design doc |
|---|---|---|---|
| 0 — Bucket E payoff | ~47 | Free | (E branches) |
| 1 — Null safety | +7 | Low | f1-null-safety.md |
2 — tell semantics |
+3 | Low | f2-tell.md |
3 — on event features |
+19 | Medium | (TBD) |
| 4 — MutationObserver | +10 | Medium | (TBD) |
| 5 — Cookie API | +5 | Low | f5-cookies.md |
| 6 — Block literals | +4 | Medium | (TBD) |
| 7 — Async logical ops | +5 | Medium | (TBD) |
| 8 — evalStatically | +3 | Low | f8-eval-statically.md |
| 9 — Parse error API | +6 | Medium | (TBD) |
10 — as conversions |
+8 | Medium | (TBD) |
| 11 — Misc bugs | +12 | Low–Medium | (TBD) |
| 12 — Breakpoint | +2 | Trivial | f-breakpoint.md |
| 12 — Security config | +1 | Medium | (TBD) |
| 12 — Unparenthesized op error | +2 | Low | (TBD) |
12 — as Date + custom |
+3 | Medium | (TBD) |
12 — on remaining |
+8 | Medium | (TBD) |
| 13 — Step limit + meta.caller | +5 | Low | f13-step-limit-and-meta.md |
| Total recoverable | ~150 |
Projected ceiling: ~1299 + 47 + 150 = 1496/1496 = 100%
Suggested sequencing for Bucket F loop
- Groups 1, 2, 5, 8 + breakpoint — quick wins, design docs ready, ~20 tests
- Groups 11 misc bugs — isolate and fix one suite at a time
- Group 9 parse error API — hooks into bootstrap, needs care
- Groups 3a, 3b (on-count + finally) — medium, self-contained
- Groups 4 (MutationObserver) + 3c/3d/3e (elsewhere, exceptions, cleanup)
- Groups 6, 7 (block literals, async logical ops) — new syntax
- Group 10 (as conversions) — additive, low regression risk
- Group 12 remainder — security config, unparenthesized op error, as-Date, on remaining
Each group should get a design doc in plans/designs/f<N>-<name>.md before implementation starts.