Parser:
- halt default/bubbling: match ident type (not just keyword)
- halt the event's: consume possessive marker
Runtime:
- hs-halt! dispatches: default→preventDefault, bubbling→stopPropagation,
event→both
Mock DOM:
- Add event method dispatch: preventDefault, stopPropagation,
stopImmediatePropagation set correct flags on event dict
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compiler:
- append to symbol → (set! target (hs-append target value))
- append to DOM → (hs-append! value target)
Runtime:
- hs-append: pure function for string concat and list append
- hs-append!: DOM insertAdjacentHTML for element targets
Mock DOM:
- dom_stringify handles List by joining elements with commas
(matching JS Array.toString() behavior)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hs-for-each now converts dicts to key lists and nil to empty list
before iterating, fixing regression where for-in loops over object
properties stopped working after the for-each → hs-for-each switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In hyperscript, 'your' refers to the element in a 'tell' scope,
functioning identically to 'my' for property access. Fixes
"Expected into/before/after/at" parse errors in tell commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integration:
- hs-collect-vars: scan compiled SX for set! targets, collect symbols
- hs-handler: pre-declare collected variables in closure let-bindings
so increment/decrement work on first use (variable persists across
event handler calls via closure scope)
Compiler:
- Fix emit-inc/emit-dec: use expr (variable) not tgt-override (element)
- Simplify to plain (set! x (+ x amount)) since vars are pre-declared
Mock DOM:
- Add mock console object to host-global
- Add console handler (no-op) to host-call dispatch
- Override console-log/debug/error as no-op primitives to avoid
str hitting circular refs in mock DOM elements
Fixes 4 log timeouts, 2+ increment/decrement failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser:
- Reorder toggle style parsing: target before between clause
- Handle "indexed" keyword, "indexed by" syntax
- Use parse-atom (not parse-expr) for between values to avoid
consuming "and" as boolean operator
- Support 3-4 value cycles via toggle-style-cycle
Compiler:
- Add toggle-style-cycle dispatch → hs-toggle-style-cycle!
Runtime:
- Add hs-toggle-style-between! (2-value toggle)
- Add hs-toggle-style-cycle! (N-value round-robin)
Mock DOM:
- Parse CSS strings from setAttribute "style" into style sub-dict
so dom-get-style/dom-set-style work correctly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser:
- Relax (number? v) to v in parse-one-transition so (expr)unit works
- Add (match-kw "then") before parse-cmd-list in parse-for-cmd
- Handle "indexed by" syntax alongside "index" in for loops
- Add "indexed" to hs-keywords to prevent unit-suffix consumption
Compiler:
- Use map-indexed instead of for-each for indexed for-loops
Test generator:
- Preserve \" escapes in process_hs_val via placeholder/restore
Mock DOM:
- Coerce insertAdjacentHTML values via dom_stringify (match browser)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser:
- `add VALUE to :var` → (add-value) for array append
- `remove VALUE from :var` → (remove-value) for array removal
- `toggle .foo for 10ms` → (toggle-class-for) with duration
- `append VALUE` without `to` → implicit target (it)
- `set {obj} on target` → (set-on) for object property spread
- `repeat in` body: remove spurious nil (body at index 3→2)
- Keywords followed by `(` parsed as function calls (fixes `increment()`)
Compiler:
- Handle add-value, remove-value, toggle-class-for, set-on AST nodes
- Local variables (`set :var`) use `define` instead of `set!`
Runtime:
- hs-add-to!: append value to list
- hs-remove-from!: filter value from list
- hs-set-on!: spread dict properties onto target
- `as String` for lists: comma-join (JS Array.toString compat)
Tests:
- eval-hs/eval-hs-with-me: guard for hs-return exceptions
(return compiles to raise, needs handler to extract value)
Parse errors: 20→12 (8 fixed). Remaining: 6 embedded HTML quotes
(tokenizer), 6 transition template values `(expr)px`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser: fetch command consumes {method:"POST"}, with {opts}, and
handles as-format both before and after options.
Mock: Number format case-insensitive, /test route has number field.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compiler: do-blocks containing IO commands (hs-fetch, hs-wait, perform)
are compiled as (let ((it cmd1)) (let ((it cmd2)) ...)) to chain the
it variable through IO suspensions. Non-IO do-blocks stay as plain
(do cmd1 cmd2). This enables fetch X then put it into me pattern.
Parser: then-separator handled via __then__ markers (stripped in output).
fetch URL /path parsing. Default format "text".
Runtime: hs-fetch simplified to single perform (io-fetch url format).
Test runner: mock fetch routes with format-specific responses.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The let/it wrapping changed semantics of ALL multi-command sequences,
breaking independent side-effect chains like (do (add-class) (add-class)).
Need a targeted approach — chain it only for then-separated commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compiler: do-blocks now compile to (let ((it cmd1)) (let ((it cmd2)) ...))
instead of (do cmd1 cmd2 ...). This chains the `it` variable through
command sequences, enabling `fetch X then put it into me` pattern.
Each command's result is bound to `it` for the next command.
Runtime: hs-fetch simplified to single perform (io-fetch url format)
instead of two-stage io-fetch + io-parse-text/json.
Parser: fetch URL /path handled by reading /+ident tokens.
Default fetch format changed to "text" (was "json").
Test runner: mock fetch routes with format-specific responses.
io-fetch handler returns content directly based on format param.
Fetch tests still need IO suspension to chain through let continuations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser: handle /path URLs in fetch command by reading /+ident tokens.
Test runner: mock fetch routes (/test→yay, /test-json→{"foo":1}),
io-parse-text, io-parse-json, io-parse-html handlers in _driveAsync.
Fetch tests still fail (0/23) because the do-block halts after
hs-fetch's perform suspension — the CEK machine doesn't continue
to the next command (put it into me) after IO resume. This needs
the IO suspension model to properly chain do-block continuations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- return compiles to (raise (list "hs-return" value)) instead of
silently discarding the return keyword
- def wraps function body in guard that catches hs-return exceptions,
enabling early exit from repeat-forever loops via return
- def params correctly extract name from (ref name) AST nodes
Note: IO suspension kernel changes reduced baseline from 519→487.
The HS parser/compiler/runtime fixes are all intact.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Compiler: return compiles to (raise (list "hs-return" value))
- Compiler: def wraps body in guard to catch hs-return exceptions
- Compiler: def params extract name from (ref name) nodes
- Test generator: extract <script type="text/hyperscript"> blocks
and compile def functions as setup before tests
- Test generator: add eval-hs-with-me for {me: N} opts
The return mechanism enables repeat-forever with early exit via return.
Direct SX guard/raise works (returns correct value), but the compiled
HS repeat-forever thunk body needs further debugging for full coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
parse-poss-tail now handles style token type after 's operator.
#div2's *color, #foo's *width etc. now correctly produce
(style prop owner) AST which compiles to dom-set/get-style.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Parser: take @attr=value with replacement restored (was reverted)
- Runtime: take @attr bare doesn't remove from scope (hyperscript keeps
source attr, only sets on target). Only take @attr=val with replacement
modifies scope elements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Parser: multi-property transition (width from 0px to 100px height from...)
with collect-transitions loop. CSS value parsing uses parse-atom + manual
number+unit concat to avoid greedy string-postfix chaining.
- Compiler: take! passes attr-val and with-val (restored from revert)
- Runtime: hs-empty-target! handles FORM by iterating child inputs,
hs-starts-with-ic/hs-ends-with-ic for case-insensitive comparison
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- htmx-boot-subtree! wired into process-elements for auto-activation
- Fixed cond compilation bug in hx-verb-info (Clojure-style flat cond)
- Platform io-fetch upgraded: method/body/headers support, full response dict
- Replaced perform IO ops with browser primitives (set-timeout, browser-confirm, etc)
- SX→HTML rendering in hx-do-swap with OOB section filtering
- hx-collect-params: collects input name/value for all methods
- Handler naming: ex-{slug} convention, removed perform IO dependencies
- Test runner page at (test.(applications.(htmx))) with iframe-based runner
- Header "test" link on every page linking to test URL
- Page file restructure: 285 files moved to URL-matching paths (a/b/c/index.sx)
- page-functions.sx: ~100 component name references updated
- _test added to skip_dirs, test- file prefix convention for test files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser: skip unit suffix when next ident is a comparison keyword
(starts, ends, contains, matches, is, does, in, precedes, follows).
Fixes "123 starts with '12'" returning "123starts" instead of true.
eval-hs: use hs-compile directly instead of hs-to-sx-from-source with
"return " prefix, which was causing the parser to consume the comparison
as a string suffix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser now handles 'does not start with' and 'does not end with'
comparison operators, compiling to (not (starts-with? ...)) and
(not (ends-with? ...)) respectively.
Test runner: host-set!/host-get stringify innerHTML/textContent.
437/831 (52.6%) — parser fix doesn't change count yet (comparison tests
use 'is a' type checks which need separate fix).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds attribute reference case to the 'of' branch in emit-set:
(set @bar of #div2 to "foo") now compiles to (dom-set-attr target "bar" "foo")
instead of falling through to the broken (set! (host-get ...)) catchall.
417/831 (50.2%), +2 from attr-of fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sx_vm.ml: VM timeout now compares vm_insn_count > step_limit instead of
unconditionally throwing after 65536 instructions when limit > 0
- sx_browser.ml: Expose setStepLimit/resetStepCount APIs on SxKernel;
callFn now returns {__sx_error, message} on Eval_error instead of null
- compiler.sx: emit-set handles array-index targets (host-set! instead of
nth) and 'of' property chains (dom-set-prop with chain navigation)
- hs-run-fast.js: New Node.js test runner with step-limit timeouts,
SX-level guard for error detection, insertAdjacentHTML mock,
range selection (HS_START/HS_END), wall-clock timeout in driveAsync
- hs-debug-test.js: Single-test debugger with DOM state inspection
- hs-verify.js: Assertion verification (proves pass/fail detection works)
Test results: 415/831 (50%), up from 408/831 (49%) baseline.
Fixes: set my style["color"], set X of Y, put at end of (insertAdjacentHTML).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
as Boolean now uses hs-falsy? for JS-compatible truthiness (0, "", nil, false → false)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Parse `is X ignoring case` → (eq-ignore-case left right)
- Parse `is not X ignoring case` → (not (eq-ignore-case left right))
- Compiler: eq-ignore-case → hs-eq-ignore-case
- Runtime: hs-eq-ignore-case using downcase/str
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- text-layout.sx added to WASM bytecode pipeline (9K compiled)
- Fix multi-list map calls (map-indexed + nth instead of map fn list1 list2)
- pretext-layout-lines and pretext-position-line moved to library exports
- Browser load-sxbc: handle VmSuspended for import, copy library exports
to global_env after module load (define-library export fix)
- compile-modules.js: text-layout in SOURCE_MAP, FILES, and entry deps
- Island uses library functions (break-lines, pretext-layout-lines)
instead of inlining — runs on bytecode VM when exports resolve
Known issue: define-library exports don't propagate to browser global env
yet. The load-sxbc import suspension handler resumes correctly but
bind_import_set doesn't fire. Needs deeper investigation into how the
WASM kernel's define-library registers exports vs how other libraries
(adapter-html, tw) make their exports available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser/compiler/runtime for focus command. Tokenizer: focus, blur,
precedes, follows, ignoring, case keywords. Test spec: per-test
failure output for diagnosis.
374/831 (45%)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Streaming chunked transfer with shell-first suspense and resolve scripts.
Hyperscript parser/compiler/runtime expanded for conformance. WASM static
assets added to OCaml host. Playwright streaming and page-level test suites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- is between X and Y / is not between X and Y: uses parse-atom for
bounds to avoid consuming 'and' as logical operator
- starts with / ends with: comparison operators mapping to
starts-with? / ends-with? primitives
- comparisonOperator: 12→17/40
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- parse-cmd: catch/finally/end/else/otherwise are now terminators that
stop parse-cmd-list (return nil from parse-cmd)
- parse-on-feat: optional catch var handler / finally handler clauses
after the command body, before 'end'
- emit-on: scan-on passes catch-info/finally-info through recursion,
wraps compiled body in (guard (var (true catch-body)) body) when
catch clause is present
- Runtime: hs-put! handles "start" (afterbegin) and "end" (beforeend)
- Removed duplicate conformance-dev.sx (all 110 tests already in behavioral)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- put parser: added 'at start of' and 'at end of' positional syntax
- take parser: added 'for' as alternative to 'from' for target clause
- runtime: hs-put! handles "start" (afterbegin) and "end" (beforeend)
- eval-hs: smart wrapping for commands vs expressions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sx_browser.ml: restore VmSuspended handler in api_call_fn with
make_js_callFn_suspension for IO suspension chains (wait, fetch)
- runtime.sx: delete host-get stub that shadowed platform native —
hs-toggle-class! now uses real FFI host-get for classList access
All three live demo examples work:
Toggle Color — classList.toggle on click
Bounce — add .animate-bounce, wait 1s suspend, remove
Count Clicks — increment @data-count, put into innerHTML
4/4 bytecode regression tests pass (was 0/4 without VmSuspended).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs fixed:
1. host-get in sx-platform.js: return true for function-valued properties
so dom-get-attr/dom-set-attr guards pass (functions can't cross WASM boundary)
2. hs-runtime.sx: renamed host-get→hs-host-get and dom-query→hs-dom-query to
stop shadowing platform natives when loaded as .sx source
3. compile-modules.js: HS dependency chain (integration→runtime→compiler→parser→tokenizer)
so lazy loading pulls in all deps. Non-library modules load as .sx source
for CEK env visibility.
Result: 8/8 elements activate, hs-on attaches listeners. Click handler needs
IO suspension support (VmSuspended in sx_browser.ml) to fire — next step.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>