- 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>
- orchestration.sx: add hs-boot-subtree! call to process-elements
- integration.sx: remove load-library! calls (browser loads via manifest)
- sx_vm.ml: add __resolve-symbol hook to OP_GLOBAL_GET for lazy loading
- compile-modules.js: add HS modules as lazy_deps in manifest
HS compilation works in browser (tokenize→parse→compile verified).
Activation pipeline partially working — hs-activate! needs debugging
(dom-get-data/dom-set-data interaction with WASM host-get on functions).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reset to last known-good state (908f4f80) where links, stepper, and
islands all work, then recovered all hyperscript implementation,
conformance tests, behavioral tests, Playwright specs, site sandbox,
IO-aware server loading, and upstream test suite from f271c88a.
Excludes runtime changes (VM resolve hook, VmSuspended browser handler,
sx_ref.ml guard recovery) that need careful re-integration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Compiler match for type-check-strict was still using old name type-check!
- Deploy updated HS source files to shared/static/wasm/sx/
- Sandbox runner validates 16/16 hard cases pass with cek-eval
(no runtime let-binding hacks needed in WASM context)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parser:
- null-literal: null/undefined produce (null-literal) AST, not bare nil
- is a/an String!: check ! as next token, not suffix in string
- type-check! renamed to type-check-strict (! in symbol names)
Compiler:
- the first/last of: emit hs-first/hs-last instead of (get x "first")
- empty? dispatch: match parser-emitted empty?, emit hs-empty?
- modulo: emit modulo instead of % symbol
Runtime:
- hs-contains?: recursive implementation (avoids some primitive)
- hs-empty?: len-based checks (avoids empty? primitive in tree-walker)
- hs-falsy?: handles empty lists and zero
- hs-first/hs-last: wrappers for tree-walker context
- hs-type-check-strict: renamed from hs-type-check!
Test infrastructure:
- eval-hs: try-call wraps both compile AND eval steps
- Mutable _hs-result captures value through try-call boundary
- Removed DOM-dependent fixtures that cause uncatchable OCaml crashes
(selectors <body/>, .class refs in exists/empty tests)
Scorecard: 62/109 tests passing (55%), up from 57/112.
3 fixtures removed (DOM-only crashers), net +5 passing tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The when form's continuation for the second body expression was lost
across perform/cek_resume cycles. Wrapping (thunk) and (do-repeat)
in an explicit (do ...) gives when a single body, and do's own
continuation handles the sequencing correctly.
Sandbox confirms: 6/6 io-sleep suspensions now chain through
host-callback → _driveAsync → resume_vm (was 1/6 before fix).
Also fix sandbox async timing: _asyncPending counter tracks in-flight
IO chains so page.evaluate waits for all resumes to complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New sx_playwright mode="sandbox" — injects the WASM kernel into about:blank
with full FFI, IO suspension tracing, and real DOM. No server needed.
Predefined stacks: core (kernel only), web (full web stack), hs (+ hyperscript),
test (+ test framework). Custom files and setup expressions supported.
Reproduces the host-callback IO suspension bug: direct callFn chains 6/6
suspensions correctly, but host-callback → addEventListener → _driveAsync
only completes 1/6. Bug is in the _driveAsync resume chain context.
Also: debug.sx mock DOM harness, test_hs_repeat.js Node.js reproduction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sx_eval now accepts files (smart-loaded by mtime — unchanged files skip),
trace_io (harness-wrapped IO capture), mock (evaluated platform overrides),
and setup params. Definitions survive between calls. sx_harness_eval also
uses smart loading. sx_write_file can create new files.
New lib/hyperscript/debug.sx: mock DOM platform for instant hyperscript
testing — compile and execute HS expressions against simulated elements,
see every DOM mutation and wait in the IO trace.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Runtime visibility fix:
- eval-hs now injects runtime helpers (hs-add, hs-falsy?, hs-strict-eq,
hs-type-check, hs-matches?, hs-contains?, hs-coerce) via outer let
binding so the tree-walker evaluator can resolve them
Parser fixes:
- null/undefined: return (null-literal) AST node instead of bare nil
(nil was indistinguishable from "no parse result" sentinel)
- === / !== tokenized as single 3-char operators
- mod operator: emit (modulo) instead of (%) — modulo is a real primitive
Compiler fixes:
- null-literal → nil
- % → modulo
- contains? → hs-contains? (avoids tree-walker primitive arity conflict)
Runtime additions:
- hs-contains?: wraps list membership + string containment
Tokenizer:
- Added keywords: a, an (removed — broke all tokenization), exist
- Triple operators: === and !== now tokenized correctly
Scorecard: 54/112 test groups passing, +23 from baseline.
Unlocked: really-equals, english comparisons, is-in, null is empty,
null exists, type checks, strict equality, mod.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract pure expression tests from the official _hyperscript test suite
and implement parser/compiler/runtime extensions to pass them.
Test infrastructure:
- 222 fixtures extracted from evalHyperScript calls (no DOM dependency)
- SX data format with eval-hs bridge and run-hs-fixture runner
- 24 suites covering expressions, comparisons, coercion, logic, etc.
Parser extensions (parser.sx):
- mod as infix arithmetic operator
- English comparison phrases (is less than, is greater than or equal to)
- is a/an Type typecheck syntax
- === / !== strict equality operators
- I as me synonym, am as is for comparisons
- does not exist/match/contain postfix
- some/every ... with quantifier expressions
- undefined keyword → nil
Compiler updates (compiler.sx):
- + emits hs-add (type-dispatching: string concat or numeric add)
- no emits hs-falsy? (HS truthiness: empty string is falsy)
- matches? emits hs-matches? (string regex in non-DOM context)
- New cases: not-in?, in?, type-check, strict-eq, some, every
Runtime additions (runtime.sx):
- hs-coerce: Int/Integer truncation via floor
- hs-add: string concat when either operand is string
- hs-falsy?: HS-compatible truthiness (nil, false, "" are falsy)
- hs-matches?: string pattern matching
- hs-type-check/hs-type-check!: lenient/strict type checking
- hs-strict-eq: type + value equality
Tokenizer (tokenizer.sx):
- Added keywords: I, am, does, some, mod, equal, equals, really,
include, includes, contain, undefined, exist
Scorecard: 47/112 test groups passing. 0 non-HS regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
compile-let called scope-define-local eagerly as part of the let
binding, adding the new local to the scope before compile-expr ran
for the init expression. When nested lets rebound the same variable
(e.g. the hyperscript parser's 4 chained `parts` bindings), the init
expression resolved the name to the new uninitialized slot instead of
the outer one — producing nil where it should have read the previous
value.
Move scope-define-local after compile-expr so init expressions see the
outer scope's binding. Fixes all 11 JIT hyperscript parser failures.
3127/3127 JIT + non-JIT, 25/25 standalone hyperscript tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Break up the 1735-line handle_tool match into 45 individual handler functions
with hashtable-based dispatch. Add mtime-based file parse caching (AST + CST),
consolidated run_command helper replacing 9 bare open_process_in patterns,
require_file/require_dir input validation, and pagination (limit/offset) for
sx_find_across, sx_comp_list, sx_comp_usage. Also includes pending VM changes:
rest-arity support, hyperscript parser, compiler/transpiler updates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sx_server.ml: url_decode now decodes + as space (RFC 1866)
- parser.sx: changed begin block to do (no behavioral difference)
- handler: clean compile handler with source param
NOTE: hs-parse still returns (do) in ASER mode. Mutable closures
work (counter test passes), tokenizer works (JIT OK), but
hs-parse's 50+ define chain inside a single let fails in ASER.
Investigating as a separate evaluator issue.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two hyperscript extensions beyond stock:
render ~component :key val [into|before|after target]
Tokenizer: ~ + ident → component token type
Parser: render command with kwargs and optional position
Compiler: emits (render-to-html ~comp :key val) or
(hs-put! (render-to-html ...) pos target)
Bridges hyperscript flow to SX component rendering
eval (sx-expression) — SX escape hatch
Inside eval (...), content is SX syntax (not hyperscript)
Parser: collect-sx-source extracts balanced parens from raw source
Compiler: sx-parse at compile time, inlines AST directly
Result: SX runs in handler scope — hyperscript variables visible!
Also supports string form: eval '(+ 1 2)' for backward compat
set name to "Giles"
set greeting to eval (str "Hello " name) -- name is visible!
16 new tests (parser + compiler + integration).
3127/3127 full build, zero regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lib/hyperscript/integration.sx — connects compiled hyperscript to DOM:
hs-handler(src) — compile source → callable (fn (me) ...) via eval-expr-cek
hs-activate!(el) — read _="...", compile, execute with me=element
hs-boot!() — scan document for [_] elements, activate all
hs-boot-subtree!(root) — activate within subtree (for HTMX swaps)
Handler wraps compiled SX in (fn (me) (let ((it nil) (event nil)) ...))
so each element gets its own me binding and clean it/event state.
Double-activation prevented via data-hs-active marker.
12 integration tests verify full pipeline: source → compile → eval.
Handlers correctly bind me, support arithmetic, conditionals, sequences,
for loops, and repeat. 3111/3111 full build, zero regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tokenizer:
* and % now emit as operators (were silently swallowed)
Added keywords: install, measure, behavior, called
5 new arithmetic operator tests
Parser — expression layer:
Arithmetic (+, -, *, /, %) via parse-arith
Unary not, no, unary minus
the X of Y possessive (parse-the-expr)
as Type conversion, X in Y membership, array literals [...]
fetch URL parsing fixed — no longer consumes "as" meant for fetch
Parser — 8 new commands:
return, throw, append...to, tell...end, for...in...end,
make a Type, install Behavior, measure
Parser — 2 new features:
def name(params)...end, behavior Name(params)...end
Parser — enhanced:
wait for event [from target], on every event modifier
33 new parser tests (16 suites), 5 tokenizer tests.
3043/3043 full build, zero regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lib/hyperscript/parser.sx — parses token stream from hs-tokenize into
SX AST forms. Covers:
Commands: add/remove/toggle class, set/put, log, hide/show, settle
Events: on with from/filter, command sequences
Sequencing: then, wait (with time units)
Conditionals: if/then/else/end
Expressions: property chains, it, comparisons, exists, refs
DOM traversal: closest, next, previous
Send/trigger events to targets
Repeat: forever, N times
Fetch/call with argument lists
55 tests across 12 suites. 3005/3005 full build, zero regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>