# 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.
