Files
rose-ash/spec/tests/hyperscript-feature-audit.md
giles 7492ceac4e Restore hyperscript work on stable site base (908f4f80)
Reset to last known-good state (908f4f80) where links, stepper, and
islands all work, then recovered all hyperscript implementation,
conformance tests, behavioral tests, Playwright specs, site sandbox,
IO-aware server loading, and upstream test suite from f271c88a.

Excludes runtime changes (VM resolve hook, VmSuspended browser handler,
sx_ref.ml guard recovery) that need careful re-integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 19:29:56 +00:00

25 KiB

_hyperscript Feature Audit

Comprehensive audit of our SX _hyperscript implementation vs the upstream reference.

Implementation files:

  • lib/hyperscript/tokenizer.sx — lexer (129 keywords, 17 token types)
  • lib/hyperscript/parser.sx — parser (67 internal functions, ~3450 lines)
  • lib/hyperscript/compiler.sx — compiler (AST to SX, ~100 dispatch cases)
  • lib/hyperscript/runtime.sx — runtime shims (49 functions)

Test files:

  • spec/tests/test-hyperscript-behavioral.sx — 381 conformance tests (from upstream)
  • spec/tests/test-hyperscript-conformance.sx — 184 additional conformance tests
  • spec/tests/test-hyperscript-tokenizer.sx — 43 tokenizer tests
  • spec/tests/test-hyperscript-parser.sx — 93 parser tests
  • spec/tests/test-hyperscript-compiler.sx — 44 compiler tests
  • spec/tests/test-hyperscript-runtime.sx — 26 runtime tests
  • spec/tests/test-hyperscript-integration.sx — 12 integration tests

Upstream reference: spec/tests/hyperscript-upstream-tests.json — 831 tests from upstream master+dev


Upstream Test Breakdown

Complexity Count Description
simple 469 DOM-based tests, simplest to translate
run-eval 83 Eval-only tests (no DOM setup)
evaluate 125 Full browser eval with DOM interaction
promise 57 Async/promise-based tests
eval-only 39 Pure expression evaluation
script-tag 36 Tests using <script type="text/hyperscript">
sinon 17 Tests requiring sinon mock (fetch)
dialog 5 Dialog-specific tests

Of the 469 simple tests, 454 are "clean" (no [@, ${, {css}, or <sel/> patterns that our tokenizer doesn't handle).

Our 381 behavioral tests were generated from the simple upstream tests and represent features that parse + compile + execute correctly in our sandbox environment.


Feature-by-Feature Audit

Legend

  • IMPL+TEST = Implemented in all four layers (tokenizer/parser/compiler/runtime) AND tested
  • PARTIAL = Compiles but not all sub-features work, or only basic forms tested
  • NOT IMPL = Parser/compiler doesn't handle it at all
  • IMPL-UNTESTED = Code exists in implementation but no test coverage

COMMANDS

Core assignment/mutation

Feature Status Tests Notes
set ... to ... IMPL+TEST 24+ Properties, locals, globals, attrs, styles, indirect. parse-set-cmd + emit-set
put ... into/before/after ... IMPL+TEST 37+ Full positional insertion. parse-put-cmd + emit-set/hs-put!
get IMPL+TEST 5 Parsed as expression (property access); call-cmd dispatch also handles get
increment IMPL+TEST 20 Variables, attributes, properties, arrays, possessives, style refs. parse-inc-cmd + emit-inc
decrement IMPL+TEST 20 Mirror of increment. parse-dec-cmd + emit-dec
append ... to ... IMPL+TEST 13 parse-append-cmd -> dom-append
default IMPL+TEST 9 Array elements, style refs, preserves zero/false. (Tested in behavioral)
empty/clear IMPL+TEST 12 Elements, inputs, textareas, checkboxes, forms. (Tested in behavioral)
swap IMPL+TEST 4 Variable/property/array swaps. (Tested in behavioral)

Class manipulation

Feature Status Tests Notes
add .class IMPL+TEST 14+ Single, multiple, double-dash, colons. parse-add-cmd -> dom-add-class
add .class to <target> IMPL+TEST 14+ Target resolution for classes
add [@attr="val"] NOT IMPL 0 Tokenizer doesn't emit [@ as attribute-set token. 4 upstream tests skipped
add {css-props} NOT IMPL 0 CSS property block syntax not tokenized. 2 upstream tests skipped
remove .class IMPL+TEST 10+ parse-remove-cmd -> dom-remove-class
remove (elements) IMPL+TEST 5 Remove self, other, parent elements
remove [@attr] NOT IMPL 0 Same tokenizer limitation as add [@]
remove {css} NOT IMPL 0 CSS block removal not implemented
toggle .class IMPL+TEST 28+ Single, multiple, timed, between two classes. parse-toggle-cmd -> hs-toggle-class!/hs-toggle-between!
toggle .class for <duration> IMPL+TEST 1 Timed toggle
toggle .class until <event> IMPL+TEST 1 Event-gated toggle
toggle between .a and .b IMPL+TEST 1 hs-toggle-between! runtime function
toggle [@attr] NOT IMPL 0 Attribute toggle not implemented
toggle {css} NOT IMPL 0 CSS block toggle not implemented
take .class IMPL+TEST 12 From siblings, for others, multiple classes. parse-take-cmd -> hs-take!
take [@attr] IMPL+TEST 10 Attribute take from siblings. (Tested in behavioral)

Control flow

Feature Status Tests Notes
if ... then ... end IMPL+TEST 18+ With else, else if, otherwise, triple nesting. parse-if-cmd
if ... else ... IMPL+TEST 18+ Naked else, else end, multiple commands
repeat ... times ... end IMPL+TEST 23+ Fixed count, expression count, forever, while, for-in. parse-repeat-cmd + hs-repeat-times/hs-repeat-forever
repeat forever IMPL+TEST 1+ hs-repeat-forever
repeat while IMPL+TEST 1+ While condition in repeat mode
repeat for x in collection IMPL+TEST 5+ For-in loop mode
for x in collection ... end IMPL+TEST 5+ parse-for-cmd + emit-for -> for-each
for x in ... index i IMPL+TEST 2 Index variable support
return IMPL+TEST varies parse-return-cmd, bare and with expression
throw IMPL+TEST varies parse-throw-cmd -> raise
catch IMPL+TEST 14 Exception handling in on blocks. (Tested in behavioral)
finally IMPL+TEST 6 Finally blocks. (Tested in behavioral)
break PARTIAL 0 Keyword recognized by tokenizer, but no dedicated parser/compiler path
continue PARTIAL 0 Keyword recognized by tokenizer, but no dedicated parser/compiler path
unless PARTIAL 0 Keyword recognized but no dedicated parser path (falls through)

Async/timing

Feature Status Tests Notes
wait <duration> IMPL+TEST 7 Duration parsing (ms, s). parse-wait-cmd -> hs-wait using perform
wait for <event> IMPL+TEST 7 Wait for DOM event. hs-wait-for using perform
wait for <event> from <source> IMPL+TEST 1 Source-specific event wait
wait for <event> or <timeout> IMPL+TEST 2 Timeout variant
settle IMPL+TEST 1 hs-settle using perform. Compiler emits (hs-settle me)
Async transparency IMPL+TEST varies perform/IO suspension provides true pause semantics

Events/messaging

Feature Status Tests Notes
send <event> IMPL+TEST 8 parse-send-cmd -> dom-dispatch. Dots, colons, args
send <event> to <target> IMPL+TEST 8 With detail dict, target expression
trigger <event> IMPL+TEST varies parse-trigger-cmd -> dom-dispatch

Navigation/display

Feature Status Tests Notes
go to <url> IMPL+TEST 3 parse-go-cmd -> hs-navigate!
hide IMPL+TEST 14 Multiple strategies (display:none, opacity:0, visibility:hidden). Custom strategies. (Tested in behavioral)
show IMPL+TEST 2 parse-show-cmd. (Tested in behavioral)
transition ... to ... over ... IMPL+TEST 22 Properties, custom duration, other elements, style refs. parse-transition-cmd + hs-transition
log IMPL+TEST 4 parse-log-cmd -> console-log
halt IMPL+TEST 6 Event propagation/default prevention. (Tested in behavioral)
halt the event IMPL+TEST 2 Stops propagation, continues execution
halt bubbling IMPL+TEST 1 Only stops propagation
halt default IMPL+TEST 1 Only prevents default

Function/behavior

Feature Status Tests Notes
call fn(args) IMPL+TEST 5 parse-call-cmd, global and instance functions
call obj.method(args) IMPL+TEST varies Method call dispatch via hs-method-call
def fn(params) ... end IMPL+TEST 3 parse-def-feat -> define
behavior Name(params) ... end IMPL+TEST varies parse-behavior-feat + emit-behavior
install BehaviorName IMPL+TEST 2 parse-install-cmd -> hs-install
make a <Type> IMPL+TEST varies parse-make-cmd + emit-make -> hs-make. Called keyword support
render <component> IMPL+TEST varies parse-render-cmd with kwargs, position, target. Bridges to SX component system

DOM/IO

Feature Status Tests Notes
fetch <url> IMPL+TEST 6 parse-fetch-cmd -> hs-fetch. JSON/text/HTML formats. URL keyword deprecated but parsed
fetch ... as json/text/html IMPL+TEST 6 Format dispatch in runtime
measure IMPL+TEST varies parse-measure-cmd -> hs-measure using perform
focus NOT IMPL 0 No parser, 3 upstream tests (all evaluate complexity)
scroll NOT IMPL 0 No parser, 8 upstream tests (all evaluate complexity)
select NOT IMPL 0 No parser, 4 upstream tests (all evaluate complexity)
reset IMPL+TEST 8 Forms, inputs, checkboxes, textareas, selects. (Tested in behavioral)
morph IMPL+TEST 4 (Tested in behavioral, simple complexity)
dialog (show/open/close) IMPL+TEST 5 Modal dialogs, details elements. (Tested in behavioral)

Other commands

Feature Status Tests Notes
tell <target> ... end IMPL+TEST 10 parse-tell-cmd. Scoping (you/your/yourself), attribute access, me restoration. (Tested in behavioral)
js ... end PARTIAL 1 Keyword recognized, parse-atom handles eval keyword -> sx-eval, but inline JS blocks not fully supported
pick NOT IMPL 0 7 upstream tests (all eval-only complexity). No parser path
beep! IMPL+TEST 1 Debug passthrough. parse-atom recognizes beep!, runtime hs-beep is identity

FEATURES (Event handlers / lifecycle)

Feature Status Tests Notes
on <event> ... end IMPL+TEST 59+ parse-on-feat + emit-on -> hs-on. Dots, colons, dashes in names
on <event> from <source> IMPL+TEST 5+ Source-specific listeners
every <event> IMPL+TEST 1+ hs-on-every — no queuing
on ... [<filter>] IMPL+TEST 3+ Event filtering in on blocks
Event destructuring IMPL+TEST 1+ can pick detail/event properties
on <event> count N / range IMPL+TEST 4 Count filter, range filter, unbounded range
on mutation IMPL+TEST 10 Attribute, childList, characterData mutations. Cross-element. (Tested in behavioral)
on first <event> IMPL+TEST 1 One-shot handler
on load IMPL+TEST 1 Load pseudo-event
Queue modes (queue, first, last, all, none) IMPL+TEST 5 Event queuing strategies
init ... end IMPL+TEST 1+ parse-init-feat -> hs-init
def name(params) ... end IMPL+TEST 3+ Feature-level function definitions
behavior Name(params) ... end IMPL+TEST varies Feature-level behavior definition
on <event> debounce <dur> NOT IMPL 0 Debounce modifier not parsed
on <event> throttle <dur> NOT IMPL 0 Throttle modifier not parsed
connect NOT IMPL 0 No parser path
disconnect NOT IMPL 0 No parser path
worker NOT IMPL 0 No parser path
socket NOT IMPL 0 4 upstream tests (all eval-only). No parser path
bind PARTIAL 1 Keyword in tokenizer. 44 upstream tests (mostly promise/evaluate). 1 simple test in behavioral. Parser doesn't have dedicated bind command
when (reactive) IMPL+TEST 5 Reactive when handler. (Tested in behavioral)
live NOT IMPL 0 23 upstream tests (evaluate/promise). No parser path
resize NOT IMPL 0 3 upstream tests (evaluate). No parser path
intersect NOT IMPL 0 No upstream tests. No parser path
every N seconds (polling) NOT IMPL 0 Time-based polling pseudo-feature not parsed

EXPRESSIONS

Literals & references

Feature Status Tests Notes
Number literals IMPL+TEST Yes Integer and float
String literals IMPL+TEST Yes Single and double quoted
Boolean literals (true/false) IMPL+TEST Yes
null/undefined IMPL+TEST Yes Both produce (null-literal)
me/I/my IMPL+TEST Yes Self-reference. my triggers possessive tail
it/its IMPL+TEST Yes Result reference. its triggers possessive tail
event IMPL+TEST Yes Event object reference
target IMPL+TEST Yes event.target
detail IMPL+TEST Yes event.detail
sender IMPL+TEST Yes Event sender reference
result IMPL+TEST Yes Implicit result
the IMPL+TEST Yes Article prefix, triggers parse-the-expr
you/your/yourself IMPL+TEST Yes Tell-scoping references
Local variables (:name) IMPL+TEST Yes Tokenizer emits local type
Template literals IMPL+TEST Yes ${expr} and $ident interpolation. Compiler handles nested parsing
Array literals [a, b, c] IMPL+TEST Yes parse-array-lit
Object literals {key: val} IMPL+TEST Yes parse-atom -> object-literal
Block literals \ param -> expr IMPL+TEST Yes Lambda syntax in parse-atom

Property access

Feature Status Tests Notes
Dot notation (obj.prop) IMPL+TEST Yes parse-prop-chain. Chained access
Method calls (obj.method(args)) IMPL+TEST Yes parse-prop-chain + method-call AST node
Bracket access (arr[i]) IMPL+TEST Yes parse-poss handles bracket-open -> array-index
Array slicing (arr[i..j]) IMPL+TEST Yes array-slice AST node -> hs-slice
Possessive (obj's prop) IMPL+TEST Yes parse-poss + parse-poss-tail
of syntax (prop of obj) IMPL+TEST Yes In parse-cmp -> (of ...) AST
Attribute ref (@attr) IMPL+TEST Yes Tokenizer emits attr type. Compiler -> dom-get-attr/dom-set-attr
Style ref (*prop) IMPL+TEST Yes Tokenizer emits style type. Compiler -> dom-get-style/dom-set-style
Class ref (.class) IMPL+TEST Yes Tokenizer emits class type
ID ref (#id) IMPL+TEST Yes Tokenizer emits id type -> (query "#id")
Selector ref (<sel/>) IMPL+TEST Yes Tokenizer emits selector type -> (query sel). 8 upstream simple tests use this
[@attr="val"] set syntax NOT IMPL 0 Tokenizer doesn't handle [@ — attribute SET inside add/remove/toggle

Comparison operators

Feature Status Tests Notes
is / is not IMPL+TEST Yes parse-cmp. Equality and negation
is equal to / is not equal to IMPL+TEST Yes Strict equality
is really / is not really IMPL+TEST Yes type-check-strict / strict type check
is a/an <Type> IMPL+TEST Yes Type checking with a/an article
is not a/an <Type> IMPL+TEST Yes Negated type check
is empty / is not empty IMPL+TEST Yes hs-empty? runtime
exists / does not exist IMPL+TEST Yes exists? AST node
matches / does not match IMPL+TEST Yes hs-matches? runtime
contains / does not contain IMPL+TEST Yes hs-contains? runtime
includes / does not include IMPL+TEST Yes Aliases for contains
<, >, <=, >= IMPL+TEST Yes Standard operators in parse-cmp and parse-arith
less than / greater than IMPL+TEST Yes English word forms
less than or equal to IMPL+TEST Yes Full English form
greater than or equal to IMPL+TEST Yes Full English form
==, != IMPL+TEST Yes Op tokens in parse-cmp
===, !== IMPL+TEST Yes Strict equality ops -> strict-eq
between PARTIAL 0 Keyword recognized in tokenizer but no dedicated parser path in parse-cmp
starts with / ends with NOT IMPL 0 No parser path
precedes / follows NOT IMPL 0 No parser path
is <prop> (property truthiness) IMPL+TEST Yes prop-is AST -> hs-prop-is

Logical operators

Feature Status Tests Notes
and IMPL+TEST Yes parse-logical
or IMPL+TEST Yes parse-logical
not IMPL+TEST Yes parse-atom prefix
no IMPL+TEST Yes parse-atom prefix -> (no expr) -> hs-falsy?

Math operators

Feature Status Tests Notes
+, -, *, / IMPL+TEST Yes parse-arith
% (modulo) IMPL+TEST Yes parse-arith handles % and mod keyword -> modulo
mod IMPL+TEST Yes Keyword alias for %
Unary - IMPL+TEST Yes In parse-atom
CSS unit postfix (10px, 50%) IMPL+TEST Yes string-postfix AST node

String operations

Feature Status Tests Notes
split by IMPL+TEST Yes parse-collection -> coll-split -> hs-split-by
joined by IMPL+TEST Yes parse-collection -> coll-joined -> hs-joined-by

Type coercion

Feature Status Tests Notes
as String IMPL+TEST Yes parse-cmp handles as keyword -> hs-coerce
as Int / as Float IMPL+TEST Yes Numeric coercion
as Array IMPL+TEST Yes Collection coercion
as Object IMPL+TEST Yes Object coercion
as JSON IMPL+TEST Yes JSON serialization/parse
as HTML / as Fragment IMPL+TEST Yes HTML/DOM coercion
as Date IMPL+TEST Yes Date coercion
as Number IMPL+TEST Yes Number coercion
as Values IMPL+TEST Yes Form values coercion
Custom type coercion (:param) IMPL+TEST Yes as Type:param syntax parsed
as response IMPL+TEST 1 (Tested in behavioral fetch tests)

Positional / traversal expressions

Feature Status Tests Notes
first IMPL+TEST Yes parse-pos-kw -> hs-query-first / hs-first
last IMPL+TEST Yes parse-pos-kw -> hs-query-last / hs-last
first ... in ... IMPL+TEST Yes Scoped first
last ... in ... IMPL+TEST Yes Scoped last
next IMPL+TEST Yes parse-trav -> hs-next. Class, ID, wildcard selectors
previous IMPL+TEST Yes parse-trav -> hs-previous
closest IMPL+TEST Yes parse-trav -> dom-closest. (Tested in behavioral)
random PARTIAL 0 Keyword recognized but no dedicated parser/compiler path

Collection expressions

Feature Status Tests Notes
where <condition> IMPL+TEST Yes parse-collection -> coll-where -> filter with it binding
sorted by <key> IMPL+TEST Yes parse-collection -> coll-sorted -> hs-sorted-by
sorted by <key> descending IMPL+TEST Yes coll-sorted-desc -> hs-sorted-by-desc
mapped to <expr> IMPL+TEST Yes parse-collection -> coll-mapped -> map
split by <sep> IMPL+TEST Yes In parse-collection
joined by <sep> IMPL+TEST Yes In parse-collection
some x in coll with <pred> IMPL+TEST Yes Quantifier in parse-atom -> (some ...)
every x in coll with <pred> IMPL+TEST Yes Quantifier in parse-atom -> (every ...)
filter (standalone) NOT IMPL 0 No standalone filter command
reduce NOT IMPL 0 No reduce in collection expressions
in (membership) IMPL+TEST Yes is in / is not in in parse-cmp -> in? / not-in?

Special forms

Feature Status Tests Notes
eval / SX interop IMPL+TEST Yes parse-atom handles eval -> (sx-eval ...). Inline SX from parens or expression
Component refs (~name) IMPL+TEST Yes Tokenizer emits component type. Compiler resolves to SX component call
new keyword PARTIAL 0 Keyword recognized but no dedicated constructor path

FEATURES NOT IMPLEMENTED (by upstream category)

These upstream test categories have zero coverage in our implementation:

Category Upstream Tests Complexity Why Missing
askAnswer 5 dialog ask/answer dialog commands not parsed
asExpression 17 eval-only/run-eval as expression standalone evaluation — partially covered by as in comparisons
asyncError 2 evaluate/promise Async error propagation edge cases
attributeRef 1 evaluate @attr as standalone assignable
cookies 1 eval-only Cookie access not implemented
evalStatically 8 eval-only Static evaluation optimization
focus 3 evaluate focus command not implemented
in 1 run-eval Standalone in expression
live 23 evaluate/promise live event sources not implemented
logicalOperator 3 eval-only Standalone logical operator eval (covered by inline use)
mathOperator 5 run-eval Standalone math eval (covered by inline use)
measure 2 evaluate measure runtime needs real DOM
objectLiteral 1 run-eval Standalone object literal eval (implemented, just no dedicated run-eval test)
pick 7 eval-only pick command not parsed
queryRef 1 evaluate <sel/> standalone (implemented but test requires DOM)
reactive-properties 4 evaluate/promise/run-eval Reactive property observation
relativePositionalExpression 4 eval-only/evaluate Relative position expressions (next/previous as standalone)
resize 3 evaluate resize pseudo-event not implemented
scroll 8 evaluate scroll command not implemented
select 4 evaluate select command not implemented
settle 1 simple settle command exists but upstream test is DOM-dependent
socket 4 eval-only WebSocket feature not implemented
splitJoin 7 run-eval Split/join standalone eval (implemented, tests are run-eval complexity)

DOM-SCOPE (^var) — Extended feature

Our implementation includes the full dom-scope (^var) feature:

Feature Status Tests
^var read from ancestor IMPL+TEST 23
^var write propagates to ancestor IMPL+TEST 23
isolated stops resolution IMPL+TEST 1
closest ancestor wins (shadowing) IMPL+TEST 1
when reacts to ^var changes IMPL+TEST 2
bind with ^var IMPL+TEST 1

COMPONENT feature (web components)

Feature Status Tests
<template> component registration IMPL+TEST 14
#if conditionals in templates IMPL+TEST 2
Named and default slots IMPL+TEST 2
Component ^var isolation IMPL+TEST 1
Multiple independent instances IMPL+TEST 1

Summary Statistics

Category Count
Features fully implemented + tested ~55
Features partially implemented ~8
Features not implemented ~18
Total upstream tests 831
Tests translated to SX (behavioral) 381 (46%)
Additional SX conformance tests 184
Tests skippable (non-simple complexity) 362 (44%)
Simple tests blocked by HTML patterns 15 (2%)
Clean simple tests available 454
Gap: clean simple not yet translated ~73

What blocks the remaining 73 clean simple tests

These tests exist in clean simple upstream but are not in our 381 behavioral tests. They likely involve features that:

  1. Require real DOM interaction (hide with strategies, fetch with network)
  2. Were added to upstream after our test generation
  3. Involve categories we partially support (halt, dialog, reset, morph, liveTemplate)

Top priorities for implementation

  1. [@attr="val"] syntax (tokenizer) — 4 simple upstream tests blocked
  2. {css-props} block syntax (tokenizer) — 2 simple upstream tests blocked
  3. debounce/throttle event modifiers — Common real-world usage
  4. scroll command — 8 upstream tests
  5. focus command — 3 upstream tests
  6. select command — 4 upstream tests
  7. pick command — 7 upstream tests
  8. live feature — 23 upstream tests, key for reactive data
  9. between comparison — Keyword exists, needs parser/compiler path
  10. starts with/ends with — Common string comparisons