- scrape-hs-upstream.py: new scraper walks /tmp/hs-upstream/test/**/*.js
and emits body-style records for all 1,496 v0.9.90 tests (up from 831).
Widens coverage into 66 previously-missing categories — templates,
reactivity, behavior, worker, classRef, make, throw, htmx, tailwind,
viewTransition, and more.
- build-hs-manifest.py + hyperscript-upstream-manifest.{json,md}:
coverage manifest tagging each upstream test with a status
(runnable / skip-listed / untranslated / missing) and block reason.
- generate-sx-tests.py: emit (error "SKIP (...)") instead of silent
(hs-cleanup!) no-op for both skip-listed tests and generator-
untranslatable bodies. Stub counter now reports both buckets.
- hyperscript-feature-audit-0.9.90.md: gap audit against the 0.9.90
spec; pre-0.9.90.json backs up prior 831-test snapshot.
New honest baseline (ocaml runner, test-hyperscript-behavioral):
831 -> 1,496 tests; 645 -> 1,013 passing (67.7% conformance).
483 failures split: 45 skip-list, 151 untranslated, 287 real.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
29 KiB
_hyperscript 0.9.90 Gap Audit
Date: 2026-04-22
Target release: upstream 0.9.90 (2026-04-13), with awareness of 0.9.91 bugfixes (2026-04-14)
Supersedes: hyperscript-feature-audit.md (2026-04-09)
Our implementation lives in /root/rose-ash/lib/hyperscript/:
tokenizer.sx(618 lines),parser.sx(2414 lines),compiler.sx(1739 lines),runtime.sx(1609 lines),integration.sx(109 lines).
Tests in spec/tests/test-hyperscript-*.sx + extracted fixture hyperscript-upstream-tests.json (831 entries).
1. Version alignment
What release is our JSON snapshot from?
The fixture spec/tests/hyperscript-upstream-tests.json and the prior audit were both written on 2026-04-09 (four days before 0.9.90 tagged on 2026-04-13). File timestamps confirm this; there is no earlier git history for the JSON (it first appears in commit 7492ceac).
The contents are clearly 0.9.90 (pre-tag) or equivalent:
- 63
ontests, 44bind, 41when, 23live, 10liveTemplate, 19component, 4reactive-properties— all new-in-0.9.90 reactivity features - 10
morph, 8reset, 13empty, 10dialog, 4swap, 7halt, 5askAnswer, 3focus, 8scroll, 23fetchincludingfetch:beforeRequest/fetch:errorlifecycle — all 0.9.90 features or overhauls - Keywords
between,ignoring(case),precedes/follows,where,sorted/mapped/split/joined by— 0.9.90 collection expressions - Total 831 tests vs upstream's "~1332 tests" claim for 0.9.90 — we have the subset that was non-sinon, non-script-tag, non-dialog-API when extracted
Conclusion: the JSON is a valid 0.9.90-era snapshot. No re-extraction is strictly required, but upstream added tests post-tag (changelog mentions 1332 tests at 0.9.90 release) — we may be missing late additions.
What 0.9.90 added over 0.9.8 / 0.9.14
Full changelog consulted at github.com/bigskysoftware/_hyperscript/blob/master/CHANGELOG.md.
Breaking changes in 0.9.90:
processNode()→process()as JSONnow parses a string; old stringify behavior →as JSONString;as Values | JSONString/as Values | FormEncodedreplaceValues:JSON/Values:Formdefaultuses nullish check (no longer overwrites0/false)go to url ...deprecated →go to /pathorgo to "url"go to the top of ...deprecated →scroll to the top ofasynckeyword removed (was confusing)/* */block comments removedtransition width→transition *width(explicit*style ref)fetchthrows on non-2xx by default;do not throworas Responserestores old behavior[@attr]bracket-style attribute access deprecated (still parsed, use@attr)
New features in 0.9.90:
- Reactivity (
live,when ... changes,bind) — dependency tracking + UI updates - Templates in core —
rendercommand +<template>elements +${}interpolation +#for/#if/#else/#end morphcommand (idiomorph-based DOM morph)- Components system —
<template component="name">custom elements with slots - DOM-scoped variables (
^name) open/close,focus/blur,empty/clear,reset,swap,select,ask/answer,speak,breakpointtoggle betweenattributes:toggle between [@data-state='active'] and [@data-state='inactive']toggle x between a, b and cN-way cycle- Collection expressions:
where,sorted by,mapped to,split by,joined by - Magic symbols:
clipboard,selection on resize(ResizeObserver),on first click(one-shot)view transitioncommand (View Transitions API)interceptfeature (service-worker DSL)- Pipe operator
|for chained conversions (as Values | JSONString) repeat ... until x end/repeat ... while x end(bottom-tested)ignoring casemodifier on comparisonsbetween,starts with,ends withcomparisonsput null into @attrremoves attribute- DOM expressions (
#id,.class,<sel/>) are writable via.replaceWith() set arr to arr + [1, 2]array concatenationcleanup()API +hyperscript:before/after:init/:cleanuplifecycle events +data-hyperscript-powered
0.9.91 bugfixes (worth tracking):
on resize from window/documentfalls through to native resize (not ResizeObserver)toggle ... for <duration>no longer consumes a followingfor-in${}template hang fix (debug log leftover)
2. Category coverage gap
Our behavioral test file test-hyperscript-behavioral.sx contains 67 defsuite blocks covering 52 unique JSON categories. The file has 831 eval-hs calls matching the 831 JSON entries — every upstream test has a behavioral entry, but 89 of them are replaced with safe no-op / NOT-IMPLEMENTED stubs (commit 71cf5b84).
Per-category upstream size vs. stub count:
| Category | Upstream tests | Stubs in behavioral | Simple-clean upstream (estimate) | Status |
|---|---|---|---|---|
| on | 63 | 32 | ~30 | many non-simple (event queue / debounced / throttled / intersection / mutation) |
| bind | 44 | ~44 | 1 | almost entirely new-0.9.90, not implemented |
| when | 41 | ~36 | 5 | when ... changes reactivity mostly missing |
| comparisonOperator | 40 | ~36 | 4 | starts with / ends with / between / ignoring case mostly missing |
| put | 38 | 8 | 37 | implemented; stubs are complex positional forms |
| toggle | 30 | 2 | 28 | strong coverage |
| repeat | 30 | 4 | 23 | break / continue / until now work (recent commits) |
| def | 27 | 7 | 3 | basic only; async/catch variants partial |
| set | 25 | 7 | 24 | strong; stubs are array-concat and rare forms |
| dom-scope | 25 | — | 23 | 23/25 upstream simple — ^var works |
| transition | 23 | 1 | 22 | strong |
| live | 23 | — | 0 | 0/23 clean-simple — reactivity runtime missing |
| fetch | 23 | 3 | 6 | 11/23 passing per recent commit 5c66095b; sinon-required tests stubbed |
| collectionExpressions | 22 | — | 5 | where / sorted by / mapped to / split by / joined by — new 0.9.90, mostly missing |
| increment | 20 | — | 20 | full |
| if | 19 | — | 18 | full |
| component | 19 | — | 14 | 14/19 simple — <template component> custom-element bridge missing on JS host |
| add | 19 | 4 | 18 | full minus [@attr] / {css} forms |
| asExpression | 17 | — | 0 | 0/17 simple — as JSON/JSONString/FormEncoded + pipe operator missing |
| remove | 14 | 4 | 12 | strong |
| hide | 14 | — | 14 | full |
| bootstrap | 14 | — | 2 | 2/14 simple — lifecycle events / cleanup() / data-hyperscript-powered not covered |
| empty | 13 | — | 12 | full (recent commit 802ccd23) |
| append | 13 | 5 | 13 | strong |
| take | 12 | — | 12 | full |
| tell | 10 | 6 | 10 | parses; stubs are the multi-statement tell variants |
| morph | 10 | — | 4 | 4/10 simple — string target variant works, element-target variants stubbed |
| liveTemplate | 10 | — | 7 | 7/10 simple — no implementation of <template> + #for/#if |
| dialog | 10 | 1 | 5 | 5/10 simple — dialog open/close partial |
| default | 9 | — | 9 | full |
| send | 8 | — | 8 | full |
| scroll | 8 | — | 0 | 0/8 simple — scroll to command not implemented |
| reset | 8 | — | 8 | full (recent commit 802ccd23) |
| evalStatically | 8 | — | 0 | 0/8 simple — static eval API not exposed |
| assignableElements | 8 | — | 3 | partial |
| wait | 7 | — | 7 | full |
| splitJoin | 7 | — | 0 | 0/7 simple — split by / joined by absent |
| pick | 7 | — | 0 | 0/7 simple — regex/char/item pick missing |
| parser | 7 | — | 3 | partial |
| halt | 7 | — | 6 | full (recent commit 802ccd23) |
| call | 6 | 7 | 5 | partial — call functions that return promises stubbed |
| no | 5 | — | 1 | no <expr> predicate mostly missing |
| mathOperator | 5 | — | 0 | 0/5 simple — mod/abs + async math promise-aware ops missing |
| go | 5 | — | 3 | basic go to works; new scroll to form missing |
| askAnswer | 5 | — | 0 | 0/5 simple — ask / answer not parsed |
| swap | 4 | — | 4 | full |
| socket | 4 | — | 0 | extension, out of scope |
| select | 4 | — | 0 | 0/4 — select command not parsed (only compiler stub) |
| relativePositionalExpression | 4 | — | 0 | 0/4 — first in, last in, random in collection-relative expressions missing |
| reactive-properties | 4 | — | 0 | 0/4 — property-reactivity runtime not present |
| log | 4 | — | 4 | full |
| resize | 3 | — | 0 | 0/3 — on resize synthetic event missing |
| logicalOperator | 3 | — | 0 | 0/3 — async-safe and/or promise propagation absent |
| init | 3 | — | 1 | partial |
| focus | 3 | — | 0 | 0/3 — focus / blur commands not wired |
| closest | 3 | — | 1 | partial |
| show | 2 | — | 2 | full |
| measure | 2 | — | 0 | 0/2 — measure command stubbed |
| asyncError | 2 | — | 0 | 0/2 — async error propagation |
| settle | 1 | — | 1 | full |
| scoping | 1 | — | 1 | — |
| queryRef | 1 | — | 0 | <sel/> selector refs — tokenizer skips |
| objectLiteral | 1 | — | 0 | JS-object literal in hyperscript expression missing |
| js | 1 | — | 1 | js ... end inline JS — we parse but don't emit real JS (no JS host) |
| in | 1 | — | 0 | in collection op missing |
| cookies | 1 | — | 0 | cookies.name magic symbol missing |
| attributeRef | 1 | — | 0 | @attr attribute-ref expressions |
Categories with 0 simple-clean coverage (highest priority): asExpression (17), splitJoin (7), pick (7), mathOperator (5), askAnswer (5), scroll (8), evalStatically (8), socket (4), select (4), relativePositionalExpression (4), reactive-properties (4), resize (3), logicalOperator (3), focus (3), measure (2), asyncError (2), queryRef (1), objectLiteral (1), in (1), cookies (1), attributeRef (1), live (23), bind (43), collectionExpressions (17).
3. Feature gaps (parser / compiler / runtime)
Commands
| Command | tokenizer | parser | compiler | runtime | Notes |
|---|---|---|---|---|---|
| add | yes | yes | yes | yes | complete minus [@attr] / {css-block} forms (new 0.9.90 block-CSS added in b23da319 ✓) |
| remove | yes | yes | yes | yes | as above |
| toggle | yes | yes | yes | yes | inc. between and N-way cycle (commit 00bf13a2) |
| set | yes | yes | yes | yes | array concat form (set a to a + [1]) untested |
| put | yes | yes | yes | yes | most positional forms |
| append | yes | yes | yes | yes | landed c8aab54d |
| prepend | no | no | no | no | not a 0.9.90 core command — only insert before via put, ignore |
| transition | yes | yes | yes | yes | inc. possessives |
| show / hide | yes | yes | yes | yes | when clause on hide (new 0.9.90) untested |
| wait / settle | yes | yes | yes | yes | via perform IO suspension |
| send / trigger | yes | yes | — | partial | send lands in compiler dispatch; trigger alias |
| call / get | yes | yes | yes | partial | call functions that return promises stubbed |
| log | yes | yes | — | partial | behavior OK |
| fetch | yes | yes | yes | yes | 11/23 passing; non-2xx throw + as Response + do not throw missing |
| throw | yes | yes | yes | yes | raise-based |
| continue / break | yes | yes | yes | yes | landed f200418d |
| return / exit | yes | yes | yes | yes | guard-based (commit 97818c6d) |
| halt | yes | yes | yes | yes | modes landed 922e7a78 |
| repeat | yes | yes | yes | yes | forever, while, until, times, for-in, index |
| for | yes | yes | yes | yes | alias of repeat for |
| if / unless / else / end / then / with | yes | yes | yes | yes | unless recognized but not parsed as separate — unchanged from prior audit |
| js | yes | (no) | (no) | (no) | embedded JS — we can't execute JS without a JS host |
| go | yes | yes | yes | yes | basic go to <path>; go to the top/bottom of deprecated in 0.9.90 — replaced by scroll to |
| scroll | yes | yes | yes | partial | scroll to the top/bottom of — parser has 2 hits, but 0/8 simple tests pass — runtime likely does not dispatch all forms |
| push/pull state | no | no | no | no | history API — not in 0.9.90 core docs either |
| pick | yes | yes | yes | yes | landed 41cfa562; 0/7 simple — pick command needs item/char/regex runtime |
| render | yes | yes | — | — | parser has 8 hits but NO compiler emit — render + <template> interpolation not implemented |
| make | yes | yes | yes | yes | hs-make + hs-make-object |
| increment / decrement | yes | yes | yes | yes | full |
| measure | yes | yes | yes | partial | 0/2 — runtime stub incomplete |
| tell | yes | yes | — | partial | parses, compiler handles single-cmd tell |
| take | yes | yes | yes | yes | full |
| focus | yes | yes | — | — | parser has 2 hits; no compiler emit, 0/3 simple pass |
| blur | no | no | no | no | 0.9.90 addition, absent |
| hide the / show the | — | — | — | — | alt grammar variant; not tracked separately |
| beep! | yes | yes | yes | yes | (runtime no-op) |
| trim / cut / copy / paste | no | no | no | no | clipboard magic symbols + commands absent |
| media | no | no | no | no | not in 0.9.90 core |
| dialog | no keyword | yes | yes | partial | parser parses dialog-open/close forms, 5/10 simple pass; commit 802ccd23 recent |
| morph | yes | yes | yes | yes | landed 5b0c8569; 4/10 simple |
| empty / clear | yes | yes | yes | yes | landed 802ccd23 |
| reset | yes | yes | yes | yes | landed (form reset 9d246f5c) |
| swap | yes | yes | — | partial | tokenizer has it; compiler dispatch missing — 4/4 simple currently pass? (verify) |
| open / close | yes | yes | yes | partial | dialog/details/popover/fullscreen variants |
| select | yes | yes | partial | partial | parser + compiler dispatch; select command 0/4 simple — runtime not complete |
| ask / answer | no | no | no | no | 0/5 simple — new 0.9.90, absent |
| speak | no | no | no | no | 0.9.90 addition, absent (Web Speech API) |
| breakpoint | no | no | no | no | 0.9.90 addition, absent |
| view transition | no | no | no | no | 0.9.90 addition, absent |
| intercept | no | no | no | no | 0.9.90 service-worker DSL, out of scope |
Expressions / operators
| Expression | Status | Notes |
|---|---|---|
me, my, it, its, result, event, detail, target |
yes | tokenizer & parser |
that |
no | not in tokenizer — uncommon, low priority |
element |
partial | parser has 1 hit |
body, document, window |
no | not tokenized — high-value gap for e.g. on resize from window |
you, yourself, sender |
partial | you/sender tokenized, not parsed |
response |
no | fetch response magic — used in error handlers |
clipboard, selection |
no | 0.9.90 additions |
first/last/random in |
partial | tokens; random has 1 parser hit — 0/4 relativePositionalExpression tests pass |
closest |
yes | 1/3 simple |
next, previous |
yes | tokenized |
parent of, children of |
no | not tokenized — parent/children expressions missing |
class ref (.foo), id ref (#foo) |
yes | tokenizer handles |
selector ref (<sel/>) |
partial | tokenizer has < but not /> closer — 88 JSON tests use this |
attribute ref (@attr) |
yes | but [@attr] bracket form deprecated — we likely have mixed support |
style ref (*prop) |
yes | parser has 7 hits |
CSS block ({color: red}) |
no | — 19 JSON tests use this |
string interpolation ${...} |
no | — 21 JSON tests use this |
time refs (1s, 1ms) |
yes | parse-duration |
math + - * / |
yes | via evaluator |
mod |
yes tok | 0/5 simple — runtime doesn't propagate promises |
abs |
no tok | absent |
| floor/ceil/round | no | absent as hyperscript keywords (only via JS interop) |
is / is not / == |
yes | |
matches / contains |
yes | inc. ignore-case (ef5faa6b) |
starts with / ends with |
yes | parser has both with ignore-case variants |
between ... and ... |
yes | 9 parser hits; may not cover comparison form |
precedes / follows |
yes | 3 parser hits each |
ignoring case |
yes | 1 tokenizer hit; needs coverage per-primitive |
in / not in |
yes-partial | 3 tests |
empty (predicate) |
yes | |
exists |
partial | dropped short-circuit (ef5faa6b); 1 parser hit |
no <expr> |
partial | 1/5 simple |
not, and, or |
yes | short-circuit async missing → 0/3 logicalOperator simple |
async and/or |
no | 0/3 logicalOperator simple; needs promise propagation |
some |
partial | tokenized |
collection: where |
yes tok | 1 parser hit — 0 working simple tests |
collection: sorted by / sorted by desc |
yes tok | runtime hs-sorted-by / -desc present, not wired from parser for all forms |
collection: mapped to |
yes tok | — |
collection: split by / joined by |
yes tok | runtime hs-split-by / hs-joined-by present — 0/7 splitJoin simple |
type conversion as Foo |
partial | 4/17 asExpression simple — JSONString / FormEncoded / **pipe ` |
| pipe ` | ` for conversions | no |
ternary / if ... else ... expression |
partial | command-form only |
object literal {key: val} |
partial | 0/1 — special parser |
Event syntax / feature forms
| Feature | Status | Notes |
|---|---|---|
on <event> |
yes | core |
on every <event> |
yes | hs-on-every |
on <event> from <target> |
yes | inc. window / document textual — but tokenizer lacks window/document |
event queueing (queue all/none/first/last) |
no | 0 parser hits for queue — new in 0.9.90-era polish |
debounced at <duration> |
no | 0 parser hits |
throttled at <duration> |
no | 0 parser hits |
halt the event / halt bubbling / halt default |
yes | mode form in parser line 1953 |
on <event> when <cond> |
yes | when-cond parser |
on intersection / on mutation observers |
no | needs IntersectionObserver/MutationObserver wiring |
on resize (synthetic) |
no | 0/3 resize tests; ResizeObserver adapter missing |
on first <event> |
no | one-shot modifier |
on load / init |
yes | in feat parser |
on <event> in <selector> |
partial | delegation |
behavior Name ... install <Name> |
yes (parsed) | behavior parsing at line 2390+ |
def name(args) ... end |
yes | (commit 9d246f5c) |
worker |
no | extension, out of scope |
live feature |
partial | tokenizer has live, parser 3 hits; no reactivity runtime → 0/23 live simple |
bind feature |
no | tokenizer + parser both 0 hits — biggest single feature gap (44 upstream tests) |
when <expr> changes |
partial | changes is tokenized; reactive wiring missing |
Scope
| Feature | Status | Notes |
|---|---|---|
locals (set x ...) |
yes | full |
element-scope (me.x, my x) |
yes | my/your alias |
globals ($x) |
yes | |
| dotted names | yes | |
local x |
partial | tokenized |
DOM-scope ^var |
yes | 23/25 simple — strong coverage |
4. Test complexity buckets we're NOT running
| Bucket | Upstream count | Our coverage | What's missing |
|---|---|---|---|
simple |
469 | ~454 via behavioral (real) + ~89 stubs | 15 skipped for pattern reasons |
evaluate |
125 | 0 as evaluate-typed tests; some merged into behavioral | DOM-mutation-after-eval tests — need real browser, not sandbox |
run-eval |
83 | 0 distinct | Mixed run+eval — possible via sandbox with state reads |
promise |
57 | partial via perform |
Async promise chains — we have IO suspension but no sinon stub |
eval-only |
39 | partial | Pure expression tests — easy to add (just run eval-hs and assert result) |
script-tag |
36 | 0 | <script type="text/hyperscript"> bootstrap path — we don't simulate the script-tag loader |
sinon |
17 | 0 | Fetch stubs — our fetch tests mock via host-* server stubs, not sinon. 11/23 fetch pass, but the 17 sinon-specific tests aren't run |
dialog |
5 | partial | <dialog> element's showModal() / close() — needs dialog API mock |
What each bucket would need:
eval-onlyandrun-eval— cheapest wins. These just needeval-hswith result assertion. Missing infrastructure: none (we already have this intest-framework.sx). Gap is that the extractor tagged themeval-onlyso they weren't included in the "simple" migration.evaluate— needs DOM mutation → DOM query. Our sandbox has mock DOM so most should be portable.script-tag— needs script-tag loader: scan HTML for<script type="text/hyperscript">, compile and run. Separate from_=attribute bootstrap. Not hard — just a hook in integration.sx.sinon— needs fetch mocking per-test. We havehost-*server stubs globally. Fix: extendhs-fetchmock to support per-test route registration (some already done in673be857).dialog— needsHTMLDialogElement.showModal()/.close()+returnValueattribute. Minimal mock.promise— most already work viaperform. The ones that don't involvePromise.resolve(x)wrapping in JS (call functions that return promises). Needs awaiter mock.
5. Pattern classes the tokenizer skips
Global JSON counts (from html + check + action concatenation):
| Pattern | Upstream tests using it | Upstream feature | Our parser |
|---|---|---|---|
[@attr="val"] bracket attribute |
6 | Deprecated in 0.9.90 — attribute-access/mutation syntax | tokenizer doesn't emit [@ pair — 4 add, 4 toggle, 1 remove tests skipped |
${...} template interpolation |
21 | String interpolation in expressions and templates | Not tokenized — blocks all live/liveTemplate/render tests (~35 tests) |
<sel/> selector ref |
88 | Inline CSS-selector reference as a value | Tokenizer has < but no closing-slash detection |
{prop:val;...} CSS block |
19 | CSS-property block in add/remove/toggle |
Tokenizer has {/} but no CSS-block parsing (partially landed b23da319 for add; not for remove/toggle) |
Parser surface:
- Attribute ref
@foo— yes (as expression), 1/1 simple attributeRef test still 0 pass → compiler/runtime gap - Selector ref
<sel/>— tokenizer returns"op"for/, parser has no rule to assemble as a single value - String interp
${x}— tokenizer returns"str"but doesn't split on${...}; no parser for expression embedding - CSS block — tokenizer consumes
{/}as punctuation; no recognition of CSS-property list inside
Recommendation:
[@attr]— since 0.9.90 deprecated it, don't invest${...}— required forlive,liveTemplate,render,bind. High priority<sel/>— 88 tests blocked; large ROI. Tokenizer change + parser rule{css}— already partially done; extend toremove/toggle
6. Summary table
| Category | Upstream | Impl (parse+compile+run) | Simple tests passing | Gap class |
|---|---|---|---|---|
| add | 19 | yes (minus [@/{css} forms) |
18 | skip patterns |
| remove | 14 | yes | 12 | skip patterns |
| toggle | 30 | yes (inc. between/cycle) | 28 | skip patterns |
| set | 25 | yes | 24 | array-concat |
| put | 38 | yes | 37 | complex positionals |
| append | 13 | yes | 13 | — |
| transition | 23 | yes | 22 | — |
| show | 2 | yes | 2 | — |
| hide | 14 | yes | 14 | — |
| wait | 7 | yes | 7 | — |
| send/trigger | 8 | yes | 8 | — |
| call | 6 | partial | 5 | promise await |
| fetch | 23 | partial | 11 | non-2xx throw, do not throw, pipe ` |
| throw/catch/finally | incl. def | yes | — | — |
| if/unless/else | 19 | yes | 18 | — |
| repeat | 30 | yes | 23 | index, by step |
| for | — | yes | — | — |
| def | 27 | partial | 3 | async/catch variants |
| behavior/install | — | parses | — | — |
| init | 3 | partial | 1 | immediate init variants |
| increment/decrement | 20 | yes | 20 | — |
| take | 12 | yes | 12 | — |
| tell | 10 | partial | 10 | multi-stmt tell |
| halt | 7 | yes | 6 | — |
| empty/clear | 13 | yes | 12 | — |
| reset | 8 | yes | 8 | — |
| swap | 4 | partial-runtime | 4 | — |
| open/close | — | partial | — | dialog/popover |
| dialog | 10 | partial | 5 | dialog API mock |
| morph | 10 | yes | 4 | element-target forms |
| focus | 3 | no | 0 | not wired through compile |
| blur | — | no | — | absent |
| scroll | 8 | partial | 0 | runtime dispatch incomplete |
| measure | 2 | partial | 0 | runtime stub |
| pick | 7 | partial | 0 | runtime item/regex modes |
| select | 4 | partial | 0 | select-command runtime |
| bind | 44 | no | 1 | reactivity runtime |
| live | 23 | no | 0 | reactivity runtime |
| when-changes | 41 | partial | 5 | reactivity deps |
| reactive-properties | 4 | no | 0 | property reactivity |
| liveTemplate | 10 | no | 7 | ${} + <template> |
| component | 19 | partial | 14 | custom-element register |
dom-scope (^var) |
25 | yes | 23 | — |
| collectionExpressions | 22 | partial | 5 | where/sorted/mapped/split/joined |
| comparisonOperator | 40 | partial | 4 | starts/ends/between/ic async |
| logicalOperator | 3 | partial | 0 | async and/or |
| mathOperator | 5 | partial | 0 | mod/abs + async |
| asExpression | 17 | partial | 0 | JSONString/FormEncoded + pipe |
| askAnswer | 5 | no | 0 | ask/answer keywords |
| speak | — | no | — | Web Speech API |
| breakpoint | — | no | — | devtools breakpoint |
| intercept | — | no | — | service-worker (out of scope) |
| view transition | — | no | — | View Transitions API |
| resize | 3 | no | 0 | ResizeObserver synthetic event |
| bootstrap | 14 | partial | 2 | lifecycle events + cleanup() |
| evalStatically | 8 | no | 0 | _hyperscript.evaluate(...) static API |
| relativePositionalExpression | 4 | partial | 0 | first in xs/last in xs |
| assignableElements | 8 | partial | 3 | — |
| go | 5 | partial | 3 | go to the top of deprecated → scroll to |
| in (operator) | 1 | partial | 0 | — |
| no (predicate) | 5 | partial | 1 | — |
| closest | 3 | partial | 1 | — |
| cookies | 1 | no | 0 | cookies.name magic |
| queryRef | 1 | no | 0 | <sel/> as value |
| objectLiteral | 1 | partial | 0 | JS-object literal expr |
| attributeRef | 1 | partial | 0 | @attr as expression |
| js (inline) | 1 | parsed | 1 | — |
| splitJoin | 7 | runtime-only | 0 | parser wiring |
| asyncError | 2 | partial | 0 | async catch paths |
| parser | 7 | partial | 3 | parse-error events |
| scoping | 1 | yes | 1 | — |
| settle | 1 | yes | 1 | — |
Top 5 priority gaps (by upstream test count × foundational weight)
- Reactivity runtime (
bind+live+when...changes+reactive-properties) — blocks 112 upstream tests (44 + 23 + 41 + 4). Requires: dependency-tracked evaluation, signal graph per-element, change propagation, two-way binding with dedup. Foundational: unlocks templates and components. Needs${}string interp in parser. ${...}interpolation +<template>render +#for/#if/#elsecontrol flow (render+liveTemplate) — blocks ~35 tests but is foundational for templating. Tokenizer change required (${...}inside strings); newrender-commandruntime; template AST with#tags.- Comparison + collection + conversion operator completion (
comparisonOperator+collectionExpressions+asExpression+splitJoin) — 86 tests. Already have primitives (hs-split-by,hs-joined-by,hs-sorted-by,hs-starts-with-ic, etc.) but parser doesn't wire them. Lower-risk plumbing work. - Missing simple commands:
focus/blur,scroll to,ask/answer,speak,breakpoint,select(command),measure— ~25 tests, and small per-command scope.focus/blur+scroll toalone unblocks 11 tests.ask/answerneedhost-prompt/host-alert/host-confirmIO effects. - Event syntax modifiers:
queue,debounced at,throttled at,on first,on resize,on intersection,on mutation— 0 parser hits for any of these. Each is small in scope but together they affect test realism.on resizewith ResizeObserver synth is the most valuable (0.9.91 bug in this area).
Secondary gaps (foundational but lower-count)
body/document/windowmagic symbols — blockson resize from windowand manyscrolltests. Add to tokenizer.parent of/children ofexpressions — not tokenized. Easy win.- Pipe operator
|— required for 0.9.90 chained conversions. fetchnon-2xx throws +do not throw+as Response— 0.9.90 breaking change; 12 fetch tests blocked.cleanup()API + lifecycle events — needed forbootstrapsuite (14 tests) and htmx 4 interop.- Script-tag loader — 36 tests in
script-tagbucket blocked for want of a<script type="text/hyperscript">parser entry.
Non-priorities
worker,socket,eventsource— extensions, out of scopeintercept— service-worker DSL, out of scopeview transition— browser API, nice-to-have[@attr]bracket form — deprecated upstream, don't investjs ... end— can't execute real JS without a JS host; we parse but can't semantically emit