Files
rose-ash/plans/hs-bucket-f.md
giles 985671cd76 hs: query targets, prolog hook, loop scripts, new plans, WASM regen
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>
2026-05-06 09:19:56 +00:00

16 KiB
Raw Blame History

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 197200 (hypertrace, slow), 615 (slow), 11971198 (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 mutation dispatch (+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 — Expected bar, got ``
  • your symbol represents the thing being told — Expected foo, 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 count
  • can filter events based on count range
  • can filter events based on unbounded count range
  • can mix ranges
  • on first click fires only once
  • multiple 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 work
  • async basic finally blocks work
  • exceptions in finally block don't kill the event queue
  • async exceptions in finally block don't kill the event queue
  • finally blocks work when exception thrown in catch
  • async 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" modifier
  • supports "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' event
  • uncaught exceptions trigger 'exception' event
  • can catch exceptions thrown in hyperscript functions
  • can 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 removed
  • listeners 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-initialisation
  • can ignore when target doesn't exist — target null guard
  • can handle an or after a from clause — parser edge case
  • each 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 mutations
  • can listen for attribute mutations on other elements
  • can listen for childList mutations
  • can listen for general mutations
  • can listen for multiple mutations
  • can listen for multiple mutations 2
  • can listen for specific attribute mutations
  • can pick event properties out by name
  • can pick detail fields out by name
  • attribute 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.


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 work
  • basic identity works
  • basic two arg identity works
  • can 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 false
  • or short-circuits when lhs promise resolves to true
  • or evaluates rhs when lhs promise resolves to false
  • should short circuit with and expression
  • should 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 expressions
  • throws on symbol references
  • throws 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 work
  • fires hyperscript:parse-error event with all errors
  • parse error at EOF on trailing newline does not crash
  • _hyperscript() evaluate API still throws on first error
  • fires hyperscript:before:init and hyperscript:after:init (bootstrap)
  • hyperscript:before:init can cancel initialization (bootstrap)

What's needed:

  • Parser must emit a hyperscript:parse-error CustomEvent on document when compilation fails, with the error list as detail.
  • hyperscript:before:init / hyperscript:after:init lifecycle events dispatched around element initialization.
  • before:init can 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 join
  • converts strings into fragments — string → DocumentFragment
  • converts elements into fragments — element → DocumentFragment
  • converts arrays into fragments — array of elements → DocumentFragment
  • converts array as Set — array → Set (dedup)
  • converts object as Map — object → Map
  • can accept custom conversionsas MyType via registered converter
  • can use the a modifier if you likeas a Number synonym

Two already-broken non-skip failures:

  • converts a complete form into Values — Expected dog, got ``
  • converts multiple selects with programmatically changed selections — Expected cat, got dog

Estimate: +8 for the tractable subset. Custom converters and Map/Set require runtime additions.


Group 11 — Miscellaneous runtime bugs (+12)

Small scattered failures, each 13 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 Datenew 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 error1 + 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 111 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 LowMedium (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 LowMedium (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

  1. Groups 1, 2, 5, 8 + breakpoint — quick wins, design docs ready, ~20 tests
  2. Groups 11 misc bugs — isolate and fix one suite at a time
  3. Group 9 parse error API — hooks into bootstrap, needs care
  4. Groups 3a, 3b (on-count + finally) — medium, self-contained
  5. Groups 4 (MutationObserver) + 3c/3d/3e (elsewhere, exceptions, cleanup)
  6. Groups 6, 7 (block literals, async logical ops) — new syntax
  7. Group 10 (as conversions) — additive, low regression risk
  8. 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.