diff --git a/lib/hyperscript/compiler.sx b/lib/hyperscript/compiler.sx index 211d1eeb..f2b7eca0 100644 --- a/lib/hyperscript/compiler.sx +++ b/lib/hyperscript/compiler.sx @@ -109,7 +109,7 @@ (hs-to-sx (nth target 1)) (hs-to-sx (nth target 2)) value)) - ((or (= th (quote next)) (= th (quote previous)) (= th (quote closest))) + ((or (= th (quote next)) (= th (quote previous)) (= th (quote closest)) (= th (quote closest-parent))) (list (quote hs-set-inner-html!) (hs-to-sx target) value)) ((= th (quote of)) (let @@ -966,7 +966,11 @@ ((= prop "first") (list (quote hs-first) target)) ((= prop "last") (list (quote hs-last) target)) (true (list (quote host-get) target prop))))) - ((= head (quote ref)) (if (= (nth ast 1) "selection") (list (quote hs-get-selection)) (make-symbol (nth ast 1)))) + ((= head (quote ref)) + (if + (= (nth ast 1) "selection") + (list (quote hs-get-selection)) + (make-symbol (nth ast 1)))) ((= head (quote query)) (list (quote hs-query-first) (nth ast 1))) ((= head (quote query-scoped)) @@ -1153,6 +1157,14 @@ (quote dom-closest) (hs-to-sx (nth ast 2)) (nth ast 1))) + ((= head (quote closest-parent)) + (list + (quote dom-closest) + (list + (quote host-get) + (hs-to-sx (nth ast 2)) + "parentElement") + (nth ast 1))) ((= head (quote next)) (list (quote hs-next) (hs-to-sx (nth ast 2)) (nth ast 1))) ((= head (quote previous)) diff --git a/lib/hyperscript/parser.sx b/lib/hyperscript/parser.sx index e67b947b..ce97ef38 100644 --- a/lib/hyperscript/parser.sx +++ b/lib/hyperscript/parser.sx @@ -89,6 +89,8 @@ (let ((typ (tp-type)) (val (tp-val))) (cond + ((and (= kind (quote closest)) (= typ "ident") (= val "parent")) + (do (adv!) (parse-trav (quote closest-parent)))) ((= typ "selector") (do (adv!) (list kind val (list (quote me))))) ((= typ "class") @@ -2507,8 +2509,13 @@ ((acc2 (append acc (list cmd)))) (cond ((match-kw "unless") - (let ((cnd (parse-expr))) - (cl-collect (append acc (list (list (quote if) (list (quote no) cnd) cmd)))))) + (let + ((cnd (parse-expr))) + (cl-collect + (append + acc + (list + (list (quote if) (list (quote no) cnd) cmd)))))) ((match-kw "then") (cl-collect (append acc2 (list (quote __then__))))) ((and (not (at-end?)) (= (tp-type) "keyword") (cmd-kw? (tp-val))) diff --git a/plans/hs-conformance-scoreboard.md b/plans/hs-conformance-scoreboard.md index 7d0d16d1..7315be7f 100644 --- a/plans/hs-conformance-scoreboard.md +++ b/plans/hs-conformance-scoreboard.md @@ -4,10 +4,10 @@ Live tally for `plans/hs-conformance-to-100.md`. Update after every cluster comm ``` Baseline: 1213/1496 (81.1%) -Merged: 1247/1496 (83.4%) delta +34 +Merged: 1248/1496 (83.4%) delta +35 Worktree: all merged Target: 1496/1496 (100.0%) -Remaining: ~249 tests +Remaining: ~248 tests ``` ## Cluster ledger @@ -43,7 +43,7 @@ Remaining: ~249 tests | 20 | `repeat` property for-loops + where | pending | (+3 est) | — | | 21 | `possessiveExpression` property access via its | done | +1 | f0c41278 | | 22 | window global fn fallback | blocked | — | — | -| 23 | `me symbol works in from expressions` | pending | (+1 est) | — | +| 23 | `me symbol works in from expressions` | done | +1 | (pending) | | 24 | `properly interpolates values 2` | pending | (+1 est) | — | | 25 | parenthesized commands and features | pending | (+1 est) | — | @@ -86,7 +86,7 @@ Defer until A–D drain. Estimated ~25 recoverable tests. | Bucket | Done | Partial | In-prog | Pending | Blocked | Design-done | Total | |--------|-----:|--------:|--------:|--------:|--------:|------------:|------:| | A | 12 | 4 | 0 | 0 | 1 | — | 17 | -| B | 1 | 0 | 0 | 5 | 1 | — | 7 | +| B | 2 | 0 | 0 | 4 | 1 | — | 7 | | C | 0 | 0 | 0 | 5 | 0 | — | 5 | | D | 0 | 0 | 0 | 5 | 0 | — | 5 | | E | 0 | 0 | 0 | 0 | 0 | 5 | 5 | diff --git a/plans/hs-conformance-to-100.md b/plans/hs-conformance-to-100.md index 5c726999..fb10ee2a 100644 --- a/plans/hs-conformance-to-100.md +++ b/plans/hs-conformance-to-100.md @@ -80,7 +80,7 @@ Each cluster below is one commit. Order is rough — a loop agent may skip ahead 22. **[blocked: tried three compile-time emits — (1) guard (can't catch Undefined symbol since it's a host-level error, not an SX raise), (2) env-has? (primitive not loaded in HS kernel — `Unhandled exception: "env-has?"`), and (3) hs-win-call runtime helper (works when reached but SX can't CALL a host-handle function directly — `Not callable: {:__host_handle N}` because NativeFn is not callable here). Needs either a host-call-fn primitive with arity-agnostic dispatch OR a symbol-bound? predicate in the HS kernel.] window global fn fallback** — `regressions / can invoke functions w/ numbers in name` + unlocks several others. When calling `foo()` where `foo` isn't SX-defined, fall back to `(host-global "foo")`. Design decision: either compile-time emit `(or foo (host-global "foo"))` via a helper, or add runtime lookup in the dispatch path. Expected: +2-4. -23. **[pending] `me symbol works in from expressions`** — `regressions` (1 test, Expected `Foo`). Check `from` expression compilation. Expected: +1. +23. **[done (+1)] `me symbol works in from expressions`** — `regressions` (1 test, Expected `Foo`). Check `from` expression compilation. Expected: +1. 24. **[pending] `properly interpolates values 2`** — URL interpolation regression (1 test). Likely template string + property access. Expected: +1. @@ -162,6 +162,9 @@ Many tests are `SKIP (untranslated)` because `tests/playwright/generate-sx-tests (Reverse chronological — newest at top.) +### 2026-04-24 — cluster 23 me symbol works in from expressions +- **COMMIT** — `HS: closest parent traversal (+1 test)`. `parse-trav` now recognises `parent` as an ident modifier after the `closest` keyword: consumes it and re-invokes itself with kind `closest-parent`, so `closest parent
` produces AST `(closest-parent "div" (me))` instead of `(string-postfix (closest "*" (me)) "parent")` — the latter was the generic trailing-ident-as-unit rule swallowing `parent`. Compiler translates `(closest-parent sel target)` to `(dom-closest (host-get target "parentElement") sel)` so `me` (the element with the `_` attribute) is skipped and only strict ancestors match. Also added `closest-parent` to the `put X into ` inner-html shortcut alongside `next`/`previous`/`closest`. Suite hs-upstream-core/regressions: 10/16 → 11/16. Smoke 0-195: 162/195 → 163/195. + ### 2026-04-24 — cluster 8 select returns selected text (cherry-picked from worktree) - **0b9bbc7b** — `HS: select returns selected text (+1 test)`. Runtime `hs-get-selection` prefers `window.__test_selection` stash and falls back to `getSelection().toString()`. Compiler rewrites `(ref "selection")` to `(hs-get-selection)`. Generator detects `document.createRange() + getElementById(ID).firstChild + setStart/setEnd` and emits a single `host-set!` on `window.__test_selection` with the resolved substring, sidestepping a propagating DOM range/text-node mock. Runner resets `__test_selection` between tests. Suite hs-upstream-select: 3/4 → 4/4. diff --git a/shared/static/wasm/sx/hs-compiler.sx b/shared/static/wasm/sx/hs-compiler.sx index 211d1eeb..f2b7eca0 100644 --- a/shared/static/wasm/sx/hs-compiler.sx +++ b/shared/static/wasm/sx/hs-compiler.sx @@ -109,7 +109,7 @@ (hs-to-sx (nth target 1)) (hs-to-sx (nth target 2)) value)) - ((or (= th (quote next)) (= th (quote previous)) (= th (quote closest))) + ((or (= th (quote next)) (= th (quote previous)) (= th (quote closest)) (= th (quote closest-parent))) (list (quote hs-set-inner-html!) (hs-to-sx target) value)) ((= th (quote of)) (let @@ -966,7 +966,11 @@ ((= prop "first") (list (quote hs-first) target)) ((= prop "last") (list (quote hs-last) target)) (true (list (quote host-get) target prop))))) - ((= head (quote ref)) (if (= (nth ast 1) "selection") (list (quote hs-get-selection)) (make-symbol (nth ast 1)))) + ((= head (quote ref)) + (if + (= (nth ast 1) "selection") + (list (quote hs-get-selection)) + (make-symbol (nth ast 1)))) ((= head (quote query)) (list (quote hs-query-first) (nth ast 1))) ((= head (quote query-scoped)) @@ -1153,6 +1157,14 @@ (quote dom-closest) (hs-to-sx (nth ast 2)) (nth ast 1))) + ((= head (quote closest-parent)) + (list + (quote dom-closest) + (list + (quote host-get) + (hs-to-sx (nth ast 2)) + "parentElement") + (nth ast 1))) ((= head (quote next)) (list (quote hs-next) (hs-to-sx (nth ast 2)) (nth ast 1))) ((= head (quote previous)) diff --git a/shared/static/wasm/sx/hs-parser.sx b/shared/static/wasm/sx/hs-parser.sx index e67b947b..ce97ef38 100644 --- a/shared/static/wasm/sx/hs-parser.sx +++ b/shared/static/wasm/sx/hs-parser.sx @@ -89,6 +89,8 @@ (let ((typ (tp-type)) (val (tp-val))) (cond + ((and (= kind (quote closest)) (= typ "ident") (= val "parent")) + (do (adv!) (parse-trav (quote closest-parent)))) ((= typ "selector") (do (adv!) (list kind val (list (quote me))))) ((= typ "class") @@ -2507,8 +2509,13 @@ ((acc2 (append acc (list cmd)))) (cond ((match-kw "unless") - (let ((cnd (parse-expr))) - (cl-collect (append acc (list (list (quote if) (list (quote no) cnd) cmd)))))) + (let + ((cnd (parse-expr))) + (cl-collect + (append + acc + (list + (list (quote if) (list (quote no) cnd) cmd)))))) ((match-kw "then") (cl-collect (append acc2 (list (quote __then__))))) ((and (not (at-end?)) (= (tp-type) "keyword") (cmd-kw? (tp-val)))