Three-part fix for hs-upstream-core/asyncError test 2/2:
1. runtime.sx hs-win-call: when an async call returns a rejected promise,
store the error value in window.__hs_async_error (side-channel) and
raise the sentinel "__hs_async_error__" so the value survives the
raise boundary intact.
2. compiler.sx catch clause: inject `(let ((var (host-hs-normalize-exc var))) ...)`
around the catch body so the sentinel gets swapped for the real error
object before user code runs. Uses let (not set!) so shadowing works
correctly for guard catch variables.
3. tests/hs-run-filtered.js:
- host-promise-state wraps JS Error objects as plain {message:...} dicts
before they cross the WASM boundary (Error.toString() was producing
"Error: boom" strings instead of accessible objects)
- host-hs-normalize-exc native retrieves the side-channel value when
the sentinel arrives in a catch variable
- host-get coercion restricted to El instances — plain JS objects with
a "value" key were being stringified to "[object Object]"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add (when (not (nil? target)) ...) guards after every hs-null-raise!
call in both the compiler and runtime so execution stops cleanly when
a DOM element is not found, instead of continuing into a JS operation
on null that takes ~34 seconds to propagate.
Compiler: emit-set dot/poss, emit-inc/dec poss case, remove-element,
remove-attr, add-styles all now wrap the action after hs-null-raise!
in a nil guard.
Runtime: hs-toggle-class!, hs-toggle-between!, hs-dispatch!,
hs-set-attr!, hs-toggle-attr!, hs-set-inner-html!, hs-put!,
hs-transition all guarded — hs-settle and hs-measure already were.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parser.sx: detect bare ident "initial" after "to" in parse-one-transition,
emit string sentinel instead of (ref "initial") which evaluated to nil.
runtime.sx: hs-transition stores pre-first-transition style as
data-hs-init-{prop}; restores it when value=="initial".
Also commits E37 tokenizer and E40 fetch test implementations that
accumulated in the working tree but weren't staged in prior commits.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Mark cluster 22 done (+1): can refer to function in init blocks
- Scoreboard: merged 1280→1302 (+22 from stale rows 22/29/32/33/34/35)
- Fix stale rows: clusters 29 partial, 32 done, 33 partial+4, 34 partial+7, 35 done
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tracks the path from 1277/1496 (85.4%) to 100%. Records each blocker's
fix sketch, files in scope, and order of attack. Cluster #31 spec'd in
detail for the next focused sit-down.
Three-part change: (a) tests/hs-run-filtered.js gets a per-test
__hsCookieStore Map, a globalThis.cookies Proxy, and a
document.cookie getter/setter that reads/writes the store. Per-test
reset clears the store. (b) generate-sx-tests.py declares cookies in
the test header and emits hand-rolled deftests for basic set / update
/ length-when-empty (the three tractable tests). (c) regenerated
spec/tests/test-hyperscript-behavioral.sx via mcp_hs_test.regen.
No .sx edits — `set cookies.foo to 'bar'` already compiles to
(dom-set-prop cookies "foo" "bar") which routes through host-set!.
Suite hs-upstream-expressions/cookies: 0/5 → 3/5.
Smoke 0-195 unchanged at 170/195.
Remaining `basic clear` (needs hs-method-call host-call dispatch) and
`iterate` (needs hs-for-each host-array recognition) need runtime.sx
edits — deferred to a future sx-tree worktree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
loops/hs worktree ships without the sx-tree MCP binary built; even
after running `dune build bin/mcp_tree.exe` this iteration, tools
don't surface mid-session and the block-sx-edit hook prevents raw
`.sx` edits. The cluster scope itself spans parser/compiler/runtime
plus JS mock plus generator skip-list, so even with sx-tree loaded
it's a multi-commit job for a dedicated worktree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 18 tests are SKIP (untranslated). Implementing the upstream
`error("HS")` helper requires coordinated work across the generator,
compiler (~17 emit paths), runtime (named-target helpers), and
function-call/possessive-base null guards. Doesn't fit a single
loop iteration — needs a dedicated design doc + worktree like the
Bucket E subsystems.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sx-tree MCP file ops broken this session (Yojson 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, even
sx_load_check on existing files works but summarise fails). Can't edit
integration.sx to add before:init/after:init dispatch. Additionally 4
of the 6 tests fundamentally require stricter parser error-rejection
(add - to currently parses to (set! nil ...); on click blargh end
accepts blargh as symbol expression) — out of single-cluster budget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cluster 19 was implemented in 4be90bf2 but the plan/scoreboard rows
still marked it pending. Sync the plan state: mark done, add log entry,
bump merged total 1264 → 1277.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five new guest-language plans mirroring the js-on-sx / hs-loop pattern, each
with a phased roadmap (Progress log + Blockers), a self-contained agent
briefing for respawning a long-lived loop, and a shared restore-all.sh that
snapshots state across all seven language loops.
Briefings bake in the lessons from today's stall debugging: never call
sx_build (600s watchdog), only touch lib/<lang>/** + own plan file, commit
every feature, update Progress log on each commit, route shared-file
issues to Blockers rather than fixing them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire up the `ask` and `answer` commands end-to-end:
- tokenizer.sx: register `ask` and `answer` as hs-keywords.
- parser.sx: cmd-kw? gains both; parse-cmd dispatches to new
parse-ask-cmd (emits `(ask MSG)`) and parse-answer-cmd, which
reads `answer MSG [with YES or NO]`. The with/or pair reads
yes/no via parse-atom — parse-expr would collapse
`"Yes" or "No"` into `(or "Yes" "No")` before match-kw "or"
could fire. The no-`with` form emits `(answer-alert MSG)`.
- compiler.sx: three new cond branches (ask, answer, answer-alert)
compile to a let that binds __hs-a, sets `the-result` and `it`,
and returns the value — so `then put it into ...` works.
- runtime.sx: hs-ask / hs-answer / hs-answer-alert call
window.prompt / confirm / alert via host-call + host-global.
- tests/hs-run-filtered.js: test-name-keyed globalThis.{alert,
confirm,prompt}; __currentHsTestName is updated before each
test. Host-set! for innerHTML/textContent now coerces JS
null → "null" (browser behaviour) so `prompt → null` →
`put it into #out` renders literal text "null", which the
fourth test depends on.
Suite hs-upstream-askAnswer: 1/5 -> 5/5.
Smoke 0-195: 166/195 -> 170/195.
Three parser additions so scripts like `(on click (log me) (trigger foo))`
parse into a single feature with both commands in its body:
1. parse-feat: new cond branch for `paren-open` — advance, recurse
parse-feat, consume `paren-close`. Allows a feature like `(on click
...)` to be grouped in parens.
2. parse-cmd: two new cond branches — on `paren-close` return nil (so
cl-collect terminates at an outer group close), and on `paren-open`
advance / recurse / close. Allows single parenthesized commands like
`(log me)`.
3. cl-collect: previously only recursed when the next token was a
recognised command keyword (`cmd-kw?`), so after `(log me)` the
sibling `(trigger foo)` would end the feature body and re-surface as
a top-level feature. Extended the recursion predicate to also fire
when the next token is `paren-open`.
Suite hs-upstream-core/parser: 9/14 -> 10/14.
Smoke 0-195: 165/195 -> 166/195.
Two shared-file entries based on scoreboard patterns:
- Math trig/transcendental primitives missing. 34× "TypeError: not a
function" across Math category — sin/cos/tan/asin/acos/atan/atan2,
sinh/cosh/tanh/asinh/acosh/atanh, log/log2/log10/log1p/expm1,
clz32/imul/fround, variadic hypot/max/min. All need OCaml/JS platform
primitives; can't polyfill from pure SX and keep precision. Once
present in the runtime, `js-global.Math` gets one extension and all 34
failures flip together.
- Evaluator CPU bound at ~1 test/s on 2-core box. Runner already
auto-disables parallel workers on ≤2 cores. Optimization surface for
the shared evaluator: lexical addresses (vs name walk), inline caches
on js-get-prop (vs __proto__ walk), force-JIT transpiled JS bodies
(vs lazy), OCaml 5 domains (vs separate processes).
Progress-log entry for P0 harness cache added alongside.