Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84e7bc8a24 | |||
| 7735eb7512 | |||
| 4e2e2c781c |
@@ -4,10 +4,10 @@ Live tally for `plans/hs-conformance-to-100.md`. Update after every cluster comm
|
||||
|
||||
```
|
||||
Baseline: 1213/1496 (81.1%)
|
||||
Merged: 1277/1496 (85.4%) delta +64
|
||||
Merged: 1280/1496 (85.6%) delta +67
|
||||
Worktree: all landed
|
||||
Target: 1496/1496 (100.0%)
|
||||
Remaining: ~219 tests (cluster 29 blocked on sx-tree MCP outage + parser scope)
|
||||
Remaining: ~216 tests (clusters 17/22/29/31/32 blocked; 31/32 need dedicated sx-tree worktree; 33 partial)
|
||||
```
|
||||
|
||||
## Cluster ledger
|
||||
@@ -61,9 +61,9 @@ Remaining: ~219 tests (cluster 29 blocked on sx-tree MCP outage + parser scope)
|
||||
|
||||
| # | Cluster | Status | Δ |
|
||||
|---|---------|--------|---|
|
||||
| 31 | runtime null-safety error reporting | pending | (+15–18 est) |
|
||||
| 32 | MutationObserver mock + `on mutation` | pending | (+10–15 est) |
|
||||
| 33 | cookie API | pending | (+5 est) |
|
||||
| 31 | runtime null-safety error reporting | blocked | — |
|
||||
| 32 | MutationObserver mock + `on mutation` | blocked | — |
|
||||
| 33 | cookie API | partial | +3 |
|
||||
| 34 | event modifier DSL | pending | (+6–8 est) |
|
||||
| 35 | namespaced `def` | pending | (+3 est) |
|
||||
|
||||
@@ -88,7 +88,7 @@ Defer until A–D drain. Estimated ~25 recoverable tests.
|
||||
| A | 12 | 4 | 0 | 0 | 1 | — | 17 |
|
||||
| B | 6 | 0 | 0 | 0 | 1 | — | 7 |
|
||||
| C | 4 | 0 | 0 | 0 | 1 | — | 5 |
|
||||
| D | 0 | 0 | 0 | 5 | 0 | — | 5 |
|
||||
| D | 0 | 1 | 0 | 2 | 2 | — | 5 |
|
||||
| E | 0 | 0 | 0 | 0 | 0 | 5 | 5 |
|
||||
| F | — | — | — | ~10 | — | — | ~10 |
|
||||
|
||||
|
||||
@@ -115,11 +115,11 @@ Orchestrator cherry-picks worktree commits onto `architecture` one at a time; re
|
||||
|
||||
### Bucket D: medium features (bigger commits, plan-first)
|
||||
|
||||
31. **[pending] runtime null-safety error reporting** — 18 tests in `runtimeErrors`. When accessing `.foo` on nil, emit a structured error with position info. One coordinated fix in the compiler emit paths for property access, function calls, set/put. Expected: +15-18.
|
||||
31. **[blocked: Bucket-D plan-first scope, doesn't fit one cluster budget. All 18 tests are SKIP (untranslated) — generator has no `error("HS")` helper. Required pieces: (a) generator-side `eval-hs-error` helper + recognizer for `expect(await error("HS")).toBe("MSG")` blocks; (b) runtime helpers `hs-null-error!` / `hs-named-target` / `hs-named-target-list` raising `'<sel>' is null`; (c) compiler patches at every target-position `(query SEL)` emit to wrap in named-target carrying the original selector source — that's ~17 command emit paths (add, remove, hide, show, measure, settle, trigger, send, set, default, increment, decrement, put, toggle, transition, append, take); (d) function-call null-check at bare `(name)`, `hs-method-call`, and `host-get` chains, deriving the leftmost-uncalled-name `'x'` / `'x.y'` from the parse tree; (e) possessive-base null-check (`set x's y to true` → `'x' is null`). Each piece is straightforward in isolation but the cross-cutting compiler change touches every emit path and needs a coordinated design pass. Recommend a dedicated design doc + multi-commit worktree like buckets E36-E40.] runtime null-safety error reporting** — 18 tests in `runtimeErrors`. When accessing `.foo` on nil, emit a structured error with position info. One coordinated fix in the compiler emit paths for property access, function calls, set/put. Expected: +15-18.
|
||||
|
||||
32. **[pending] MutationObserver mock + `on mutation` dispatch** — 15 tests in `on`. Add MO mock to runner. Compile `on mutation [of attribute/childList/attribute-specific]`. Expected: +10-15.
|
||||
32. **[blocked: environment + scope. (env) The `loops/hs` worktree at `/root/rose-ash-loops/hs/` ships without a built sx-tree MCP binary; even after running `dune build bin/mcp_tree.exe` on this iteration, the tools don't surface to the current session — they'd need to load at session start, and rebuilding doesn't re-register them. CLAUDE.md mandates sx-tree for any `.sx` edit and a hook blocks Edit/Read/Write on `.sx`/`.sxc`. (scope) The cluster needs coordinated changes across `lib/hyperscript/parser.sx` (recognise `on mutation of <filter>` with attribute/childList/characterData/`@name [or @name]*`), `lib/hyperscript/compiler.sx` (analogue of intersection's `:having`-style attach call passing filter info), `lib/hyperscript/runtime.sx` (`hs-on-mutation-attach!` constructing real `MutationObserver` with config matched to filter, dispatching `mutation` event with detail), `tests/hs-run-filtered.js` (replace the no-op MutationObserver mock with a working version + hook `El.setAttribute`/`appendChild`/etc. to fire registered observers), `tests/playwright/generate-sx-tests.py` (drop 7 mutation entries from `SKIP_TEST_NAMES`). The current parser drops bodies after `of` because `parse-on-feat` only consumes `having` clauses — confirmed via compile snapshot (`on mutation of attributes put "Mutated" into me` → `(hs-on me "mutation" (fn (event) nil))`). Recommended path: dedicated worktree with sx-tree loaded at session start, multi-commit (parser, compiler+attach, mock+runner, generator skip-list pruning).] MutationObserver mock + `on mutation` dispatch** — 15 tests in `on`. Add MO mock to runner. Compile `on mutation [of attribute/childList/attribute-specific]`. Expected: +10-15.
|
||||
|
||||
33. **[pending] cookie API** — 5 tests in `expressions/cookies`. `document.cookie` mock in runner + `the cookies` + `set the xxx cookie` keywords. Expected: +5.
|
||||
33. **[done (+3) — partial, `basic clear cookie values work` needs `hs-method-call` runtime fallback to dispatch unknown methods through `host-call` (current `hs-method-call` returns nil for non-{map,push,filter,join,indexOf} methods, so `cookies.clear('foo')` is silently a no-op); `iterate cookies values work` needs `hs-for-each` to recognise host-array/proxy collections (currently `(list? collection)` returns false for the JS Proxy so the loop body never runs). Both need runtime.sx edits → next worktree.] cookie API** — 5 tests in `expressions/cookies`. `document.cookie` mock in runner + `the cookies` + `set the xxx cookie` keywords. Expected: +5.
|
||||
|
||||
34. **[pending] event modifier DSL** — 8 tests in `on`. `elsewhere`, `every`, `first click`, count filters (`once / twice / 3 times`, ranges), `from elsewhere`. Expected: +6-8.
|
||||
|
||||
@@ -177,6 +177,15 @@ Many tests are `SKIP (untranslated)` because `tests/playwright/generate-sx-tests
|
||||
|
||||
(Reverse chronological — newest at top.)
|
||||
|
||||
### 2026-04-25 — cluster 33 cookie API (partial +3)
|
||||
- No `.sx` edits needed — `set cookies.foo to 'bar'` already compiles to `(dom-set-prop cookies "foo" "bar")` which becomes `(host-set! cookies "foo" "bar")` once the `dom` module is loaded, and `cookies.foo` becomes `(host-get cookies "foo")`. So a JS-only Proxy + Python generator change does the trick. Two parts: (a) `tests/hs-run-filtered.js` adds a per-test `__hsCookieStore` Map, a `globalThis.cookies` Proxy with `length`/`clear`/named-key get traps and a set trap that writes the store, and a `Object.defineProperty(document, 'cookie', …)` getter/setter that reads and writes the same store (so the upstream `length is 0` test's pre-clear loop over `document.cookie` works). Per-test reset clears the store. (b) `tests/playwright/generate-sx-tests.py` declares `(define cookies (host-global "cookies"))` in the test header and emits hand-rolled deftests for the three tractable tests (`basic set`, `update`, `length is 0`). Suite hs-upstream-expressions/cookies: 0/5 → 3/5. Smoke 0-195 unchanged at 170/195. Remaining `basic clear` and `iterate` tests need runtime.sx edits (hs-method-call fallback + hs-for-each host-array recognition) — out of scope for a JS-only iteration.
|
||||
|
||||
### 2026-04-25 — cluster 32 MutationObserver mock + on mutation dispatch (blocked)
|
||||
- Two issues conspire: (1) `loops/hs` worktree has no pre-built sx-tree binary so MCP tools aren't loaded, and the block-sx-edit hook prevents raw `Edit`/`Read`/`Write` on `.sx` files. Built `hosts/ocaml/_build/default/bin/mcp_tree.exe` via `dune build` this iteration but tools don't surface mid-session. (2) Cluster scope is genuinely big: parser must learn `on mutation of <filter>` (currently drops body after `of` — verified via compile dump: `on mutation of attributes put "Mutated" into me` → `(hs-on me "mutation" (fn (event) nil))`), compiler needs `:of-filter` plumbing similar to intersection's `:having`, runtime needs `hs-on-mutation-attach!`, JS runner mock needs a real MutationObserver (currently no-op `class{observe(){}disconnect(){}}` at hs-run-filtered.js:348) plus `setAttribute`/`appendChild` instrumentation, and 7 entries removed from `SKIP_TEST_NAMES`. Recommended next step: dedicated worktree where sx-tree loads at session start, multi-commit shape (parser → compiler+attach → mock+runner → generator skip-list).
|
||||
|
||||
### 2026-04-25 — cluster 31 runtime null-safety error reporting (blocked)
|
||||
- All 18 tests are `SKIP (untranslated)` — generator has no `error("HS")` helper at all. Inspected representative compile outputs: `add .foo to #doesntExist` → `(for-each ... (hs-query-all "#doesntExist"))` (silently no-ops on empty list, no error); `hide #doesntExist` → `(hs-hide! (hs-query-all "#doesntExist") "display")` (likewise); `put 'foo' into #doesntExist` → `(hs-set-inner-html! (hs-query-first "#doesntExist") "foo")` (passes nil through); `x()` → `(x)` (raises `Undefined symbol: x`, wrong format); `x.y.z()` → `(hs-method-call (host-get x "y") "z")`. Implementing this requires generator helper + 17 compiler emit-path patches + function-call/method-call/possessive-base null guards + new `hs-named-target`/`hs-named-target-list` runtime — too many surfaces for a single-iteration commit. Bucket D explicitly says "plan-first" — recommended path is a dedicated design doc and multi-commit worktree like E36-E40, not a loop iteration.
|
||||
|
||||
### 2026-04-24 — cluster 29 hyperscript:before:init / :after:init / :parse-error (blocked)
|
||||
- **2b486976** — `HS-plan: mark cluster 29 blocked`. sx-tree MCP file ops returning `Yojson__Safe.Util.Type_error("Expected string, got null")` on every file-based call (sx_read_subtree, sx_find_all, sx_replace_by_pattern, sx_summarise, sx_pretty_print, sx_write_file). Only in-memory ops work (sx_eval, sx_build, sx_env). Without sx-tree I can't edit integration.sx to add before:init/after:init dispatch on hs-activate!. Investigated the 6 tests: 2 bootstrap (before/after init) need dispatchEvent wrapping activate; 4 parser tests require stricter parser error-rejection — `add - to` currently parses silently to `(set! nil (hs-add-to! (- 0 nil) nil))`, `on click blargh end on mouseenter also_bad` parses silently to `(do (hs-on me "click" (fn (event) blargh)) (hs-on me "mouseenter" (fn (event) also_bad)))`. Fundamental parser refactor is out of single-cluster budget regardless of sx-tree availability.
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
;; references them (e.g. `window.tmp`) can resolve through the host.
|
||||
(define window (host-global "window"))
|
||||
(define document (host-global "document"))
|
||||
(define cookies (host-global "cookies"))
|
||||
|
||||
(define hs-test-el
|
||||
(fn (tag hs-src)
|
||||
@@ -4885,13 +4886,21 @@
|
||||
(deftest "basic clear cookie values work"
|
||||
(error "SKIP (untranslated): basic clear cookie values work"))
|
||||
(deftest "basic set cookie values work"
|
||||
(error "SKIP (untranslated): basic set cookie values work"))
|
||||
(hs-cleanup!)
|
||||
(assert (nil? (eval-hs "cookies.foo")))
|
||||
(eval-hs "set cookies.foo to 'bar'")
|
||||
(assert= (eval-hs "cookies.foo") "bar"))
|
||||
(deftest "iterate cookies values work"
|
||||
(error "SKIP (untranslated): iterate cookies values work"))
|
||||
(deftest "length is 0 when no cookies are set"
|
||||
(error "SKIP (untranslated): length is 0 when no cookies are set"))
|
||||
(hs-cleanup!)
|
||||
(assert= (eval-hs "cookies.length") 0))
|
||||
(deftest "update cookie values work"
|
||||
(error "SKIP (untranslated): update cookie values work"))
|
||||
(hs-cleanup!)
|
||||
(eval-hs "set cookies.foo to 'bar'")
|
||||
(assert= (eval-hs "cookies.foo") "bar")
|
||||
(eval-hs "set cookies.foo to 'doh'")
|
||||
(assert= (eval-hs "cookies.foo") "doh"))
|
||||
)
|
||||
|
||||
;; ── expressions/dom-scope (20 tests) ──
|
||||
|
||||
@@ -327,6 +327,36 @@ const document = {
|
||||
createEvent(t){return new Ev(t);}, addEventListener(){}, removeEventListener(){},
|
||||
};
|
||||
globalThis.document=document; globalThis.window=globalThis; globalThis.HTMLElement=El; globalThis.Element=El;
|
||||
// cluster-33: cookie store + document.cookie + cookies Proxy.
|
||||
globalThis.__hsCookieStore = new Map();
|
||||
Object.defineProperty(document, 'cookie', {
|
||||
get(){ const out=[]; for(const[k,v] of globalThis.__hsCookieStore) out.push(k+'='+v); return out.join('; '); },
|
||||
set(s){
|
||||
const str=String(s||'');
|
||||
const m=str.match(/^\s*([^=]+?)\s*=\s*([^;]*)/);
|
||||
if(!m) return;
|
||||
const name=m[1].trim();
|
||||
const val=m[2];
|
||||
if(/expires=Thu,?\s*01\s*Jan\s*1970/i.test(str) || val==='') globalThis.__hsCookieStore.delete(name);
|
||||
else globalThis.__hsCookieStore.set(name, val);
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
globalThis.cookies = new Proxy({}, {
|
||||
get(_, k){
|
||||
if(k==='length') return globalThis.__hsCookieStore.size;
|
||||
if(k==='clear') return (name)=>globalThis.__hsCookieStore.delete(String(name));
|
||||
if(typeof k==='symbol' || k==='_type' || k==='_order') return undefined;
|
||||
return globalThis.__hsCookieStore.has(k) ? globalThis.__hsCookieStore.get(k) : null;
|
||||
},
|
||||
set(_, k, v){ globalThis.__hsCookieStore.set(String(k), String(v)); return true; },
|
||||
has(_, k){ return globalThis.__hsCookieStore.has(k); },
|
||||
ownKeys(){ return Array.from(globalThis.__hsCookieStore.keys()); },
|
||||
getOwnPropertyDescriptor(_, k){
|
||||
if(globalThis.__hsCookieStore.has(k)) return {value: globalThis.__hsCookieStore.get(k), enumerable: true, configurable: true};
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
// cluster-28: test-name-keyed confirm/prompt/alert mocks. The upstream
|
||||
// ask/answer tests each expect a deterministic return value. Keyed on
|
||||
// globalThis.__currentHsTestName which the test loop sets before each test.
|
||||
@@ -540,6 +570,7 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
||||
// Reset body
|
||||
_body.children=[];_body.childNodes=[];_body.innerHTML='';_body.textContent='';
|
||||
globalThis.__test_selection='';
|
||||
globalThis.__hsCookieStore.clear();
|
||||
globalThis.__currentHsTestName = name;
|
||||
|
||||
// Enable step limit for timeout protection
|
||||
|
||||
@@ -1832,6 +1832,36 @@ def generate_eval_only_test(test, idx):
|
||||
lines = []
|
||||
safe_name = sx_name(test['name'])
|
||||
|
||||
# Special case: cluster-33 cookie tests. Each test calls a sequence of
|
||||
# `_hyperscript("HS")` inside `page.evaluate(()=>{...})`. The runner backs
|
||||
# `cookies` with a Proxy over a per-test `__hsCookieStore` map (see
|
||||
# tests/hs-run-filtered.js). Tests handled: basic set, length-when-empty,
|
||||
# update. clear/iterate stay SKIP (need hs-method-call→host-call dispatch
|
||||
# and host-array iteration in hs-for-each — out of cluster-33 scope).
|
||||
if test['name'] == 'basic set cookie values work':
|
||||
return (
|
||||
f' (deftest "{safe_name}"\n'
|
||||
f' (hs-cleanup!)\n'
|
||||
f' (assert (nil? (eval-hs "cookies.foo")))\n'
|
||||
f' (eval-hs "set cookies.foo to \'bar\'")\n'
|
||||
f' (assert= (eval-hs "cookies.foo") "bar"))'
|
||||
)
|
||||
if test['name'] == 'update cookie values work':
|
||||
return (
|
||||
f' (deftest "{safe_name}"\n'
|
||||
f' (hs-cleanup!)\n'
|
||||
f' (eval-hs "set cookies.foo to \'bar\'")\n'
|
||||
f' (assert= (eval-hs "cookies.foo") "bar")\n'
|
||||
f' (eval-hs "set cookies.foo to \'doh\'")\n'
|
||||
f' (assert= (eval-hs "cookies.foo") "doh"))'
|
||||
)
|
||||
if test['name'] == 'length is 0 when no cookies are set':
|
||||
return (
|
||||
f' (deftest "{safe_name}"\n'
|
||||
f' (hs-cleanup!)\n'
|
||||
f' (assert= (eval-hs "cookies.length") 0))'
|
||||
)
|
||||
|
||||
# Special case: logAll config test. Body sets `_hyperscript.config.logAll = true`,
|
||||
# then mutates an element's innerHTML and calls `_hyperscript.processNode`.
|
||||
# Our runtime exposes this via hs-set-log-all! + hs-log-captured; we reuse
|
||||
@@ -2612,6 +2642,7 @@ output.append(';; Bind `window` and `document` as plain SX symbols so HS code th
|
||||
output.append(';; references them (e.g. `window.tmp`) can resolve through the host.')
|
||||
output.append('(define window (host-global "window"))')
|
||||
output.append('(define document (host-global "document"))')
|
||||
output.append('(define cookies (host-global "cookies"))')
|
||||
output.append('')
|
||||
output.append('(define hs-test-el')
|
||||
output.append(' (fn (tag hs-src)')
|
||||
|
||||
Reference in New Issue
Block a user