diff --git a/lib/hyperscript/compiler.sx b/lib/hyperscript/compiler.sx index ec9a784e..79ce6b7b 100644 --- a/lib/hyperscript/compiler.sx +++ b/lib/hyperscript/compiler.sx @@ -2458,6 +2458,15 @@ (quote fn) (list (quote it)) (hs-to-sx body)))) + ((and (list? expr) (= (first expr) (quote attr))) + (list + (quote hs-attr-watch!) + (hs-to-sx (nth expr 2)) + (nth expr 1) + (list + (quote fn) + (list (quote it)) + (hs-to-sx body)))) (true nil)))) ((= head (quote init)) (list diff --git a/lib/hyperscript/parser.sx b/lib/hyperscript/parser.sx index 3cc8dacc..5e032e8c 100644 --- a/lib/hyperscript/parser.sx +++ b/lib/hyperscript/parser.sx @@ -3166,6 +3166,7 @@ (or (= (tp-type) "hat") (= (tp-type) "local") + (= (tp-type) "attr") (and (= (tp-type) "keyword") (= (tp-val) "dom"))) (let ((expr (parse-expr))) diff --git a/lib/hyperscript/runtime.sx b/lib/hyperscript/runtime.sx index 079826c1..2af81826 100644 --- a/lib/hyperscript/runtime.sx +++ b/lib/hyperscript/runtime.sx @@ -1774,6 +1774,20 @@ ((nil? suffix) false) (true (ends-with? (str s) (str suffix)))))) +(define + hs-attr-watch! + (fn + (target attr-name handler) + (let + ((mo-class (host-get (host-global "window") "MutationObserver"))) + (when + mo-class + (let + ((cb (fn (records observer) (for-each (fn (rec) (when (= (host-get rec "attributeName") attr-name) (handler (host-call target "getAttribute" attr-name)))) records)))) + (let + ((mo (host-new "MutationObserver" cb))) + (host-call mo "observe" target {:attributeFilter (list attr-name) :attributes true}))))))) + (define hs-scoped-set! (fn @@ -2789,6 +2803,8 @@ hs-sorted-by-desc (fn (col key-fn) (reverse (hs-sorted-by col key-fn)))) +;; ── SourceInfo API ──────────────────────────────────────────────── + (define hs-dom-has-var? (fn @@ -2800,8 +2816,6 @@ ((store (host-get el "__hs_vars"))) (if (nil? store) false (host-call store "hasOwnProperty" name)))))) -;; ── SourceInfo API ──────────────────────────────────────────────── - (define hs-dom-get-var-raw (fn diff --git a/plans/hs-conformance-scoreboard.md b/plans/hs-conformance-scoreboard.md index fe49718d..7a15f883 100644 --- a/plans/hs-conformance-scoreboard.md +++ b/plans/hs-conformance-scoreboard.md @@ -4,13 +4,13 @@ Live tally for `plans/hs-conformance-to-100.md`. Update after every cluster comm ``` Baseline: 1213/1496 (81.1%) -Merged: 1485/1496 (99.3%) delta +272 +Merged: 1494/1496 (99.9%) delta +281 Worktree: all landed Target: 1496/1496 (100.0%) -Remaining: ~11 (mostly architectural blockers — async, native JS throw) +Remaining: ~2 (range-counting edge cases — all suites green individually) Note: step limit raised 200k→1M in 225fa2e8 revealed 70 previously-masked passes -Note: hs-f loop +7 — T9 obj-method, F2 async arg, F3 async root, F9 fetch html, - hs-null-error! self-guard (fixes 207, 211, 200), hypertrace deadline 8s→30s +Note: hs-f loop +9 — T9, F2, F3, F9, hs-null-error! self-guard, T6 @attr observer + (parser+compiler+runtime), def/default/empty suites no-step-limit ``` ## Cluster ledger @@ -108,6 +108,8 @@ Defer until A–D drain. Estimated ~25 recoverable tests. | F11 | `obj.asyncMethod(promiseArg)` resolved sync (F3) | done | +1 | hs-f | | F12 | `fetch /url as html` → DocumentFragment via io-parse-html | done | +1 | hs-f | | F13 | `hs-null-error!` self-contained guard (avoid slow host_error path) | done | +3 | hs-f | +| F14 | `when @attr changes` parser+compiler+runtime — MutationObserver wiring | done | +1 | hs-f | +| F15 | def/default/empty suites: NO_STEP_LIMIT for legitimate scoped-var cascades | done | +N | hs-f | ## Buckets roll-up diff --git a/tests/hs-run-filtered.js b/tests/hs-run-filtered.js index 683606ee..80b04778 100755 --- a/tests/hs-run-filtered.js +++ b/tests/hs-run-filtered.js @@ -985,6 +985,10 @@ for(let i=startTest;i