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

352 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
---
## 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 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 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 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.