Commit Graph

192 Commits

Author SHA1 Message Date
dda3becbab HS: throw respond via exception event (+2 tests)
`hs-on` now wraps each event handler in a `guard` that catches thrown
exceptions and re-dispatches them as an `exception` DOM event on the
same target with `{error: e}` as detail. The `on exception(error)`
handler, registered the same way, receives the event and destructures
`error` from the detail. Wrapping skips `exception`/`error` event
handlers to avoid infinite loops — those bubble out as before.

Suite hs-upstream-throw: 5/7 → 7/7. Smoke 0-195: 162/195 unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:54:21 +00:00
baa5cd9341 js-on-sx: .toString()/.toFixed()/.valueOf() on numbers and booleans
js-invoke-method now branches on (number? recv) and (boolean? recv) before
falling through to the generic dict/fn path. js-invoke-number-method handles
toString (incl. radix 2-36), toFixed, valueOf, toLocaleString, toPrecision,
toExponential. js-invoke-boolean-method handles toString and valueOf.

Numbers had no .toString() on bare values before — (5).toString() crashed
with 'TypeError: toString is not a function'. This is one of the bigger
scoreboard misses on built-ins/Number category.

10 new unit tests, 469/471 total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:51:58 +00:00
3d35205533 HS: transition query-ref + multi-prop (+2 tests)
Three parts: (a) parser `collect-transitions` recognises `style`
tokens (`*prop`) as a continuation, so
`transition *width from A to B *height from A to B` chains both
transitions instead of dropping the second. (b) Mock `El` class gets
`nextSibling`/`previousSibling` (plus `*ElementSibling` aliases) so
`transition *W of the next <span/>` can resolve the next-sibling
target via host-get. (c) Generator pattern for
`const X = await evaluate(() => { const el = document.querySelector(SEL);
el.dispatchEvent(new Event(NAME, ...)); return ... })`; optionally
prefixed by a destructuring assignment and allowing trailing
`expect(...).toBe(...)` junk because `_body_statements` only splits on
`;` at depth 0.

Remaining `can use initial to transition to original value` needs
`on click N` count-filtered events (same mock-sync block as cluster 13).

Suite hs-upstream-transition: 13/17 → 15/17. Smoke 0-195: 162/195
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:44:46 +00:00
e5346d5ea3 js-on-sx: runner classifier maps parser errors to SyntaxError
classify_error now catches 'unexpected token', 'unexpected char',
'expected ident/punct/keyword' as SyntaxError variants.

classify_negative_result maps parser errors to SyntaxError for negative:parse
tests that expect a SyntaxError. Also maps 'undefined symbol' to
ReferenceError for negative:runtime tests. This reclassifies ~39+36 tests
per wide run from 'fail' to 'pass (negative)'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:44:05 +00:00
5f3a8e43c0 js-on-sx: array-like receivers for Array.prototype.* methods
Array.prototype.slice.call({length:3, 0:41, 1:42, 2:43}) used to crash with
'Not callable: {dict}' because js-array-proto-fn passed the dict straight
into js-invoke-method, which then tried (append! dict x) etc.

Now js-array-proto-fn converts dict-with-length receivers to a list via
js-arraylike-to-list before dispatch. Mutation methods (push/pop/shift/
reverse/sort/fill) still require a real list — array-likes only work for
read-only methods.

Targets the 455x 'Not callable: {:length N :0 v1 :1 v2 ...}' scoreboard item.

6 new unit tests, 459/461 total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:40:46 +00:00
bd821c0445 HS: toggle multi-class + until event (+2 tests)
Parser `parse-toggle-cmd`: after the leading class ref, collect any
additional class refs and treat `toggle .foo .bar` as `toggle-between`
(pair-only). Recognise a `until EVENT [from SOURCE]` modifier and emit
a new `toggle-class-until` AST node. Compiler handles the new node by
emitting `(begin (hs-toggle-class! tgt cls) (hs-wait-for src ev)
(hs-toggle-class! tgt cls))` which uses the existing event-waiter
machinery to flip the class back when the specified event fires.

Remaining toggle test (`can toggle for a fixed amount of time`)
depends on the mock's sync io-sleep resuming immediately — the click
handler toggles on/off synchronously, so the pre-timeout assertion
can never see the `.foo` class present. Needs an async scheduler in
the mock to handle.

Suite hs-upstream-toggle: 22/25 → 24/25. Smoke 0-195: 162/195
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:35:30 +00:00
16df723e08 js-on-sx: numeric keys in object literals stringify on parse
{0: 41, 1: 42} was raising 'dict-set!: dict key val' because the parser
kept numeric keys as numbers in the entry dict, but SX dicts require string
keys. Now we str-coerce number-type tokens during jp-parse-object-entry.
Unblocks a huge chunk of test262 array-like-receiver tests that build
{length: N, 0: v, 1: v, ...} literals.

3 new tests, 453/455 total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:32:44 +00:00
92c1fc72a5 js-on-sx: Function.prototype.call/apply/bind
Adds js-invoke-function-method dispatched from js-invoke-method when the
receiver is a JS function (lambda/function/component/callable-dict) and the
method name is one of call/apply/bind/toString/name/length.

call and apply bind this around a single call; bind returns a closure with
prepended args. toString returns the native-code placeholder.

6 unit tests, 450/452 total (Array.prototype.push.call with arrayLike still
fails — tracked as the 455x 'Not callable array-like' scoreboard item which
needs array methods to treat dict-with-length as a list).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:27:18 +00:00
beb120baf7 HS: hide strategy config (+3 tests)
Three parts: (a) `runtime.sx` hs-hide-one!/hs-show-one! consult a new
`_hs-hide-strategies` dict (and `_hs-default-hide-strategy` override)
before falling through to the built-in display/opacity/etc. cases. The
strategy fn is called directly with (op, el, arg). New setters
`hs-set-hide-strategies!` and `hs-set-default-hide-strategy!`. (b)
`generate-sx-tests.py` `_hs_config_setup_ops` recognises
`_hyperscript.config.defaultHideShowStrategy = "X"`, `delete …default…`,
and `hideShowStrategies = { NAME: function (op, el, arg) { if …
classList.add/remove } }` with brace-matched function body extraction.
(c) Pre-setup emitter handles `__hs_config__` pseudo-name by emitting
the SX expression as-is (not a window.X = Y assignment).

Suite hs-upstream-hide: 12/16 → 15/16. Remaining test
(`hide element then show element retains original display`) needs
`on click 1 hide` / `on click 2 show` count-filtered events — separate
feature. Smoke 0-195: 162/195 unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:25:58 +00:00
65d4c70638 js-on-sx: parallel test262 runner with raw-fd line buffer
Rework test262-runner.py to support --workers N parallel shards, each running
a long-lived sx_server session. Replace thread-per-readline with a select-based
raw-fd line buffer.

On 2-core machines, 1 worker still beats 2 (OCaml eval is CPU-bound and starves
when shared). Auto-defaults n_workers=1 on <=2 CPU, nproc-1 (up to 8) otherwise.

Throughput baseline: ~1.1 Math tests/s serial on 2-core (unchanged; the
evaluator dominates). The runner framework is now ready to scale on bigger
machines without further code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:18:48 +00:00
f79f96c1c3 HS: wait on event basics (+4 tests)
Five parts: (a) tests/hs-run-filtered.js `io-wait-event` mock now
registers a one-shot listener on the target element and resumes with
the event, instead of immediately resuming with nil. (b) Added
hs-wait-for-or runtime form carrying a timeout-ms; mock resumes
immediately when a timeout is present (0ms tests). (c) parser
parse-wait-cmd recognises `wait for EV(v1, v2)` destructure syntax,
emits :destructure list on wait-for AST. (d) compiler emit-wait-for
updated for :from/:or combos; a new `__bind-from-detail__` form
compiles to `(define v (host-get (host-get it "detail") v))`, and the
`do`-sequence handler preprocesses wait-for to splice these synthetic
bindings after the wait. (e) generator extracts `detail: ...` from
`CustomEvent` options so dispatched events carry their payload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:05:53 +00:00
c94b340943 js-on-sx: updated Math scoreboard — 66/288 (22.9%)
Up from 56/288 (19.4%) baseline. Progress from:
- Math.sqrt/pow/trunc/sign/cbrt/hypot
- Array.prototype stubs for verifyProperty
- typeof callable-dict → "function"
- __callable__ dispatch in js-apply-fn
2026-04-23 23:41:38 +00:00
6293a0fe70 js-on-sx: delete operator
js-transpile-unop intercepts 'delete' before transpiling the
operand. Maps to (js-delete-prop obj key) for members and indexed
access. Runtime js-delete-prop sets the dict value to js-undefined
and returns true.

444/446 unit (+2), 148/148 slice unchanged.
2026-04-23 23:34:05 +00:00
27bd25843e js-on-sx: tolerate destructuring params in fn decls/exprs (skipped as holes) 2026-04-23 23:27:29 +00:00
0a3425ba18 js-on-sx: Array.prototype.lastIndexOf 2026-04-23 23:24:02 +00:00
9f9e4e1e9d js-on-sx: obj destructure rename + rest + nested tolerance
Pattern {key: local-name} emits ("rename" key local). Transpile
emits (define local (js-get-prop tmp key)).

Rest in obj pattern stubs (no supported), nested {} and [] treated
as holes.

442/444 unit (+2), 148/148 slice unchanged.
2026-04-23 23:19:31 +00:00
c5e2bc2fe1 js-on-sx: Number.prototype stub with toString/valueOf/toFixed 2026-04-23 23:13:55 +00:00
835d42fd1a js-on-sx: Array.prototype and String.prototype stubs
Each prototype contains method-name → closure pairs. Each closure
reads this via js-this and dispatches through js-invoke-method.
Lets Array.prototype.push, String.prototype.slice etc. be accessed
and invoked as (expected) functions.

440/442 unit unchanged, 148/148 slice unchanged.
2026-04-23 23:11:06 +00:00
d7ad7172aa js-on-sx: js-apply-fn unwraps __callable__ before invoking 2026-04-23 23:06:24 +00:00
1079004981 js-on-sx: typeof returns 'function' for callable-dicts + 'object' for null 2026-04-23 23:02:15 +00:00
c257971bb1 js-on-sx: rest in array pattern + nested pattern tolerance
Array destructure now supports [a, ...rest]. Rest entry is
transpiled to (define name (js-list-slice tmp i (len tmp))).

Nested patterns like [[a,b], c] now parse (as holes) instead of
erroring. jp-skip-balanced skips nested groups.

440/442 unit (+2), 148/148 slice unchanged.
2026-04-23 22:58:49 +00:00
1459f7a637 js-on-sx: callable Number/String/Boolean/Array + Array.sort
Builtin constructors now have :__callable__ slot. js-call-plain
and js-function? detect dicts with __callable__ and dispatch
through it. Number('42')===42, String(true)==='true', Boolean(0)
===false, Array(3) builds length-3 list.

Array.prototype.sort(comparator?): bubble sort via js-list-sort-
outer!/-inner!. Default lex order, custom comparator supported.

Wide scoreboard committed: 259/5354 (4.8%) from earlier runtime.

438/440 unit (+11), 148/148 slice unchanged.
2026-04-23 22:53:13 +00:00
d6975d3c79 js-on-sx: logical assignment &&= ||= ??=
js-compound-update gains logical-assign operators:
- &&= → (if (js-to-boolean lhs) rhs lhs)
- ||= → (if (js-to-boolean lhs) lhs rhs)
- ??= → (if nullish? rhs lhs)

427/429 unit (+4), 148/148 slice unchanged.
2026-04-23 22:43:38 +00:00
18ae63b0bd js-on-sx: optional chaining ?.
Parser: jp-parse-postfix handles op "?." followed by ident / [ / (
emitting (js-optchain-member obj name), (js-optchain-index obj k),
or (js-optchain-call callee args).

Transpile: each emits (js-optchain-get obj key) or (js-optchain-call
fn args).

Runtime: js-optchain-get and js-optchain-call short-circuit to
js-undefined when receiver is null/undefined.

423/425 unit (+5), 148/148 slice unchanged.
2026-04-23 22:38:45 +00:00
ed8d71c9b8 HS: send can reference sender (+1 test)
Three-part fix: (a) emit-send now builds detail=(dict "sender" me) on
(send NAME target) and bare (send NAME) instead of nil, so the receiving
handler has access to the sending element. (b) parser parse-atom now
recognises the `sender` keyword (previously swallowed as noise) and
emits it as (sender). (c) compiler translates bare `sender` symbol and
(sender) list-head to (hs-sender event) — a new runtime helper that
reads (get (host-get event "detail") "sender").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 22:37:18 +00:00
15c310cdc1 js-on-sx: object + array destructuring
Parser: jp-parse-vardecl handles {a, b} obj pattern and [a, , c]
arr pattern (with hole support) in addition to plain idents.
Emits (js-vardecl-obj names rhs) and (js-vardecl-arr names rhs).

Transpile: js-vardecl-forms dispatches on tag. Destructures emit
(define __destruct__ rhs) then (define name (js-get-prop __destruct__
key-or-index)) for each pattern element. Array holes (nil) are skipped.

418/420 unit (+4), 148/148 slice unchanged.
2026-04-23 22:32:24 +00:00
4800246b23 js-on-sx: spread ... in array literals and call args
Parser: jp-array-loop and jp-call-args-loop detect punct "..."
and emit (js-spread inner).

Transpile: when any element is spread, build array/args via
js-array-spread-build with (list "js-value" v) and (list
"js-spread" xs) tags.

Runtime: js-array-spread-build walks items, appending values or
splicing spreads via js-iterable-to-list (handles list/string/dict).

Works in arrays, call args, variadic fns (Math.max(...arr)),
and string spread ([...'abc']).

414/416 unit (+5), 148/148 slice unchanged.
2026-04-23 22:10:15 +00:00
b502b8f58e js-on-sx: js-num-to-int coerces strings via js-to-number 2026-04-23 21:59:08 +00:00
60bb7c4687 js-on-sx: String replace/search/match + Array.from
String: replace, search, match now work with either string or regex
arg. Regex path uses js-string-index-of on source (case-adjusted
when ignoreCase set).

Array.from(iter, mapFn?) normalizes via js-iterable-to-list and
optionally applies mapFn.

Fixed dict-set! on list bug in js-regex-stub-exec — just omit the
index/input metadata, spec-breaking but tests that just check [0]
work.

407/409 unit (+8), 148/148 slice unchanged.
2026-04-23 21:54:36 +00:00
108e25d418 HS: string template \${x} (+2 tests)
`\$window.foo` / `\${window.foo}` couldn't resolve. Two fixes:
(a) compiler.sx: in a dot-chain base position, known globals (window,
    document, navigator, location, history, screen, localStorage,
    sessionStorage, console) emit `(host-global "name")` instead of a
    bare unbound symbol.
(b) generator: `eval-hs-locals` now also sets each binding on
    `window.<name>` via `host-set!`, so tests that translated
    `window.X = Y` as a local pair still see `window.X` at eval time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 21:52:55 +00:00
f113b45d48 js-on-sx: for..of / for..in + more Array methods
Parser: jp-parse-for-stmt does 2-token lookahead for (var? ident
(of|in) expr), emits (js-for-of-in kind ident iter body) else
classic (js-for init cond step body).

Transpile: wraps body in (call/cc (__break__) (let items
(for-each (fn (ident) (call/cc (__continue__) body)) items))).

Runtime: js-iterable-to-list normalizes list/string/dict for of
iteration; js-string-to-list expands string to char list.

399/401 unit (+8), 148/148 slice unchanged.
2026-04-23 21:41:52 +00:00
4fe0b64965 HS: not precedence over or + truthy/falsy coercion (+3 tests)
parse-atom emitted (not (parse-expr)) which let or/and capture the whole
RHS before `not` could bind. Also emitted SX `not` which treats only nil/
false as falsy, so `not 0` returned false.

Fix: `not` now emits `(hs-falsy? (parse-atom))` — tight binding to the
following atom, and hyperscript-style truthy/falsy (0, "", nil, false, []).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 21:40:00 +00:00
e59c0b8e0a HS: Values dict insertion order (+2 tests)
Dict-set! keys iterate in scrambled order, so Values|FormEncoded and
Values|JSONString produced output in the wrong order. Fix: hs-values-absorb
now tracks insertion order in a hidden `_order` list on the dict itself.
hs-coerce FormEncoded/JSONString paths read `_order` when present and
iterate in that order (filtering the marker key out).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 21:35:47 +00:00
835025ec37 js-on-sx: Array.prototype flat + fill; fix indexOf start arg
flat: walk with depth, recursive when element is list and depth>0.
fill(value, start?, end?): in-place mutation, returns self.
indexOf: honor second arg as start position.

396/398 unit (+5), 148/148 slice unchanged.
2026-04-23 21:28:50 +00:00
39a597e9b6 HS: fetch JSON unwrap (+4 tests)
Adds hs-host-to-sx to convert raw host-handle JS objects/arrays returned by
json-parse or io-fetch into proper SX dicts/lists. hs-fetch now calls it on
the result when format is "json". Detects host handles via absence of the
internal `_type` marker, then walks Object.keys / Array items recursively.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 21:25:03 +00:00
ebaec1659e js-on-sx: JSON.stringify + JSON.parse
Recursive-descent parser/serializer in SX.

stringify: type-of dispatch for primitives, lists, dicts. Strings
escape \\ \" \n \r \t.

parse: {:s src :i idx} state dict threaded through helpers.
Handles primitives, strings (with escapes), numbers, arrays,
objects.

Wired into js-global.

391/393 unit (+10), 148/148 slice unchanged.
2026-04-23 21:22:32 +00:00
6f0b4fb476 js-on-sx: String.fromCharCode + parseInt + parseFloat
String global with fromCharCode (variadic). parseInt truncates via
js-math-trunc; parseFloat delegates to js-to-number. Wired into
js-global.

381/383 unit (+5), 148/148 slice unchanged.
2026-04-23 21:16:41 +00:00
2bd3a6b2ba js-on-sx: Array.prototype includes/find/some/every/reverse + Object fallbacks
Array: includes, find, findIndex, some, every, reverse via
tail-recursive helpers.

Object: hasOwnProperty, isPrototypeOf, propertyIsEnumerable,
toString, valueOf, toLocaleString fallback in js-invoke-method
when js-get-prop returns undefined. Lets o.hasOwnProperty('k')
work on plain dicts.

376/378 unit (+13), 148/148 slice unchanged.
2026-04-23 21:11:12 +00:00
9d3e54029a js-on-sx: switch/case/default/break
Parser: jp-parse-switch-stmt + jp-parse-switch-cases + jp-parse-switch-body.
AST: (js-switch discr (("case" val body) ("default" nil body) ...)).

Transpile: wraps body in (call/cc (fn (__break__) ...)). Each case
clause becomes (when (or __matched__ (js-strict-eq discr val))
(set! __matched__ true) body). Fall-through works naturally via
__matched__. Default appended as (when (not __matched__) body).

363/365 unit (+6), 148/148 slice unchanged.
2026-04-23 21:04:22 +00:00
275d2ecbae js-on-sx: String.prototype extensions + Object/Array builtins
Strings: includes, startsWith, endsWith, trim, trimStart, trimEnd,
repeat, padStart, padEnd, toString, valueOf.

Object: keys, values, entries, assign, freeze (no-op).
Array: isArray, of.

All wired into js-global. 17 new unit tests.

357/359 unit (+17), 148/148 slice unchanged.
2026-04-23 20:58:24 +00:00
6c4001a299 js-on-sx: postfix + prefix ++/--
Parser: jp-parse-postfix emits (js-postfix op target) on trailing
++/--; jp-parse-primary emits (js-prefix op target) before the
unary -/+/!/~ branch.

Transpile: js-transpile-prefix → (set! name (+ (js-to-number name)
±1)) for idents, (js-set-prop obj key ...) for members/indices.
js-transpile-postfix caches old value in a let binding, updates,
returns the saved value.

340/342 unit (+11), 148/148 slice unchanged.
2026-04-23 20:50:10 +00:00
e0531d730c js-on-sx: drop top-level (define NaN) / (define Infinity) — SX parses those as numbers 2026-04-23 20:45:12 +00:00
608a5088a4 js-on-sx: expanded Math + Number globals
Math gains sqrt/pow/trunc/sign/cbrt/hypot plus LN2/LN10/LOG2E/
LOG10E/SQRT2/SQRT1_2 constants and full-precision PI/E.

Number global: isFinite/isNaN/isInteger/isSafeInteger plus
MAX_VALUE/MIN_VALUE/MAX_SAFE_INTEGER/MIN_SAFE_INTEGER/EPSILON/
POSITIVE_INFINITY/NEGATIVE_INFINITY/NaN.

Global isFinite, isNaN, Infinity, NaN. Wired into js-global.

329/331 unit (+21), 148/148 slice unchanged.
2026-04-23 20:42:57 +00:00
ce46420c2e js-on-sx: regex literal lex+parse+transpile+runtime stub
Lexer: js-regex-context? disambiguates / based on prior token;
read-regex handles [...] classes and \ escapes. Emits
{:type "regex" :value {:pattern :flags}}.

Parser: new primary branch → (js-regex pat flags).

Transpile: (js-regex-new pat flags).

Runtime: js-regex? predicate, js-regex-new builds tagged dict with
source/flags/global/ignoreCase/multiline/sticky/unicode/dotAll/
hasIndices/lastIndex. js-regex-invoke-method dispatches .test/.exec/
.toString. js-invoke-method detects regex receivers. Stub engine
uses js-string-index-of; __js_regex_platform__ + override! let a
real engine plug in later.

Runner: repeatable --filter flags (OR'd).

308/310 unit (+30 regex tests), 148/148 slice unchanged.
2026-04-23 20:27:19 +00:00
6b0334affe HS: remove bare @attr, set X @attr, JSON clean, FormEncoded, HTML join
- parser remove/set: accept bare @attr (not just [@attr])
- parser set: wrap tgt as (attr name tgt) when @attr follows target
- runtime: hs-json-stringify walks sx-dict/list to emit plain JSON
  (strips _type key which leaked via JSON.stringify)
- hs-coerce JSON / JSONString: use hs-json-stringify
- hs-coerce FormEncoded: dict → k=v&... (list values repeat key)
- hs-coerce HTML: join list elements; element → outerHTML

+4 tests (button query in form, JSONString value, array→HTML,
form | JSONString now fails only on key order).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:14:03 +00:00
8984520f05 js-on-sx: runner fix, 8-test smoke 3/7 (was 0/8)
Agent committed before monitor-exit. Full tree still TODO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 20:08:46 +00:00
1613f551ef HS add/append: Set dedup, @attr support, when-clause result tracking
- runtime hs-add-to!/hs-append: dedupe on list targets (Set semantics)
- compiler emit-set: set result to X now syncs it too
- compiler append!: handle (local)/(ref) targets via emit-set so scoped
  vars get rebound to the returned list
- parser add/remove: accept bare @attr (not just [@attr])
- parser add-attr: support when-clause → emits add-attr-when
- compiler add-class-when/add-attr-when: collect matched items into
  the-result / it so subsequent "if the result is empty" works

+6 upstream tests in early range (add 13→17, append 10→12).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:55:27 +00:00
9e568ad886 js-on-sx: baseline commit (278/280 unit, 148/148 slice, runner stub)
Initial commit of the lib/js/ tree and plans/ directory. A previous
session left template-string work in progress — 278/280 unit tests pass
(2 failing: tpl part-count off-by-one, escaped-backtick ident lookup).
test262-runner.py and scoreboard are placeholders (0/8 with 7 timeouts);
fixing the runner is the next queue item.
2026-04-23 19:42:16 +00:00
14b6586e41 HS parser: atoms for you/yourself, distinct your-possessive starting at you
- parse-atom: 'you' and 'yourself' keywords resolve to (ref <name>) so
  they look up the let-binding the tell-command installs.
- 'your <prop>' no longer aliases 'my <prop>' — it's the possessive over
  the 'you' binding, mirroring 'its' over 'it'.

Unblocks 'you symbol represents the thing being told' and 'can take a class
and swap it with another via with' (via you/your in tell handlers). Net:
tell 6→7 (was 6/10).
2026-04-23 17:44:16 +00:00
1cd81e5369 HS activate: set data-hyperscript-powered attribute on activation
Upstream convention — elements wired up by hyperscript carry
data-hyperscript-powered='true' so callers can find them. Net: core/bootstrap
17→19.
2026-04-23 17:36:00 +00:00