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>
352 lines
16 KiB
Markdown
352 lines
16 KiB
Markdown
# 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 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.
|
||
|
||
---
|
||
|
||
## 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 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 conversions` — `as MyType` via registered converter
|
||
- `can use the a modifier if you like` — `as 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 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
|
||
|
||
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.
|