Load sx/sx/geography/cek/ recursively so content/demo/freeze index.sx
pages bind as ~geography/cek/{content,demo,freeze}. Update docs.sx
cek-page dispatch + test-examples cek:content-pages suite to reference
those real names (were stale ~geography/cek/cek-content etc.).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the top-level gallery/index.sx that links into the one-per-file
gallery pages committed in the prior commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New sxc/ content tree with 120 page files across docs, examples, home,
and reference demos. sx/sx/testing/ adds page-runner.sx (317L) and
index-runner.sx (394L) — SX-native test runner pages for
browser-based evaluation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrates hyperscript demo/reference pages from grouped index files into
one-per-page directory layout. Each gallery-<topic>/index.sx is a single
defpage with its own demo, matching the one-per-file convention used
elsewhere in sx/sx/applications/.
Covers: control (call/go/if/log/repeat/settle), dom (add/append/empty/
focus/hide/measure/morph/put/remove/reset/scroll/set/show/swap/take/
toggle), events (asyncError/bootstrap/dialog/fetch/halt/init/on/pick/
send/socket/tell/wait/when), expressions (asExpression/attributeRef/
closest/collectionExpressions/comparisonOperator/default/in/increment/
logicalOperator/mathOperator/no/objectLiteral/queryRef/select/splitJoin),
language (askAnswer/assignableElements/component/cookies/def/dom-scope/
evalStatically/js/parser/relativePositionalExpression/scoping), and
reactivity (bind/live/liveTemplate/reactive-properties/resize/transition).
Adds _islands/hs-test-card.sx — a shared island for hyperscript demos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- call: use make-symbol for fn name, rest-rest for args (was string + nth)
- on: extract (ref ...) nodes from body as event.detail let-bindings
- host-set!: add ListRef+Number case for array index mutation
- append!: support index 0 for prepend
- hs-put!: branch on list? for array start/end operations
- hs-reset!: form reset restoring defaultValue/checked/textContent
- 522/793 pass (was 493/754)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- inject_path_name: strip _islands/ convention dirs from path-derived names
- page-functions.sx: fix geography (→ ~geography) and isomorphism (→ ~etc/plan/isomorphic)
- request-handler.sx: rewrite sx-eval-page to call page functions explicitly
via env-get+apply, avoiding provide special form intercepting (provide) calls
- sx_server.ml: set expand-components? on AJAX aser paths so server-side
components expand for the browser (islands stay unexpanded for hydration)
- Rename 19 component references in geography/spreads, geography/provide,
geography/scopes to use path-qualified names matching inject_path_name output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
guard and cek-try both create CEK frames that don't survive async
perform/resume. Instead, run-action returns nil on success and an
error string on failure. The for-each loop checks the return value
and sets fail-msg. No exceptions cross async boundaries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an error occurs during resumed VM execution (after perform/hs-wait),
resume_vm now checks the VM's handler_stack. If a handler exists (from a
compiled guard form's OP_PUSH_HANDLER), it unwinds frames and jumps to
the catch block — exactly like OP_RAISE. This enables try/catch across
async perform/resume boundaries.
The guard form compiles to OP_PUSH_HANDLER which lives on the vm struct
and survives across setTimeout-based async resume. Previously, errors
during resume escaped to the JS console as unhandled exceptions.
Also restored guard in the test runner (was cek-try which doesn't survive
async) and restored error-throwing assertions in run-action.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs at every step: run-all start, test name, reload-frame, wait-for-el,
actions done, PASS/FAIL, run-all complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous insert wrapped reload-frame and wait-for-el in a begin
block instead of making them separate letrec bindings. This made
reload-frame invisible to later bindings. Fixed by inserting
wait-for-el at index 3 in the letrec bindings list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wait-for-el polls the iframe doc for a CSS selector up to max-tries
times with 200ms intervals. Used before running test actions to ensure
the target elements exist in the iframe after page load.
Also restores reload-frame timing and keeps cek-try error handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The guard form (call/cc + handler-bind expansion) doesn't survive async
IO suspension — the CEK continuation from guard's call/cc captures frames
that become invalid after the VM resumes from hs-wait. Replacing guard
with cek-try (which compiles to VM-native OP_PUSH_HANDLER/OP_POP_HANDLER)
avoids the CEK boundary crossing.
The test runner now executes: suspends on hs-wait, resumes, runs test
actions, and test assertions fire correctly. The "Not callable: nil"
error is eliminated. Remaining: test assertion errors from iframe content
not loading fast enough (timing issue, not a framework bug).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
runner.sx: Converted define forms inside island body to letrec. Multiple
define forms in a let body cause render-to-dom to fall back to eval-expr
for the whole body, which evaluates (div ...) as a list instead of
rendering it to DOM. letrec keeps the last body expression (div) as the
render target.
sx_browser.ml: js_to_value now stores plain JS functions as host objects
(Dict with __host_handle) instead of wrapping as NativeFn. This preserves
the original JS function identity through the SX→JS round-trip, keeping
_driveAsync wrappers from host-callback intact when passed to
addEventListener via host-call.
Remaining: IO suspension in click handler is caught as "IO suspension in
non-IO context" instead of being driven by _driveAsync. The host-callback
wrapper creates the right JS function, but the event dispatch path doesn't
go through K.callFn.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Test runner island (~test-runner) with 8 test definitions as SX data
- SSR renders test list with expandable deftest source
- Island body has run-all/run-action/reload-frame/wait-boot helpers
- Header: "test" link on every page, derives test URL from current path
- _test added to skip_dirs in sx_server.ml (both load_dir locations)
- Handler names: ex-{slug} convention for dispatch compatibility
- JS fallback runner updated with data-role selectors
Next: wire island hydration so browser re-evaluates the island body
(component bundler needs to include ~test-runner in page scripts)
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>
The effect form returns a VM closure (disposer) which the island DOM
renderer displayed as text. Moving it to a let binding (_eff) captures
the return value without rendering it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause identified: :ref attribute on DOM elements inside defisland
triggers dict-set!/reduce error in WASM kernel hydration system.
Minimal repro:
(defisland ~test ()
(let ((el-ref (signal nil)))
(div (div :ref (fn (el) (reset! el-ref el)) ""))))
→ "dict-set!: dict key val (in reduce → reduce → for-each)"
Without :ref: works perfectly (signals, effects, canvas FFI,
break-lines, pretext-layout-lines all functional).
Working version: full Pretext with 3 controls + effect + layout
computation, outputs text via (deref result). 34 disposers, no error.
Just needs :ref fix to add DOM rendering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Isolated the dict-set!/reduce error to complex island body parsing,
not the reactive system or library functions. Proven working:
- break-lines inside effect ✓
- canvas.measureText inside effect ✓
- pretext-layout-lines inside effect ✓
- signal + slider + reactive update ✓
The error triggers only with large island bodies (many ~tw spreads,
nested controls). This is a component definition parser bug in the
WASM kernel, not a Pretext or reactive system issue.
Current island: minimal working version with effect-based layout,
slider control, and innerHTML rendering. Ready for incremental
expansion once the parser size limit is identified.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 7 new tests in computed-ho-forms suite: computed with map, reduce,
for-each, nested map, dict creation, signal updates. All pass on
OCaml and WASM sandbox.
- Removed standalone pretext-position-line and pretext-layout-lines
from pretext-demo.sx — now in text-layout library only
- Root cause of island error: pretext-demo.sx had old define with
(reduce + 0 lwid) that the server serialized into component defs,
overriding the library's sum-loop version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added sublist helper (portable list extraction, avoids 3-arg slice
which fails in browser WASM kernel)
- Replaced reduce + 0 lwid with manual sum loop (reduce has browser
compat issues with dict-set! error in call stack)
- Imperative DOM update via effect for clean paragraph re-rendering
on signal changes (clear container, create new spans)
- String slice in hyphenate-word kept (works on strings)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The (let ((lines (deref layout))) ...) pattern captured the layout value
once at island initialization. Replacing with (deref layout) inline in the
DOM expressions makes the reactive system track the dependency and
re-render when signals change.
Sliders and algorithm toggle now trigger layout recomputation and DOM
update. Remaining: reactive DOM patching for absolutely-positioned spans
creates visual artifacts (old spans persist). Needs keyed list or full
container replacement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: sx_insert_near placed break-lines-greedy, pretext-position-line,
pretext-layout-lines OUTSIDE the define-library begin block. The bytecode
compiler only compiles forms inside begin as STORE_GLOBAL — forms outside
are invisible to the browser VM.
Fix: moved all function definitions inside (begin ...) of (define-library).
Bytecode now includes all 17 functions (11K compiled, was 9K).
Browser load-sxbc: simplified VmSuspended handling — just catch and
continue, since STORE_GLOBAL ops already ran before the import OP_PERFORM.
sync_vm_to_env copies them to global_env.
Island now calls break-lines and pretext-layout-lines from bytecode-compiled
library — runs on VM, not CEK interpreter.
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>
- Move all layout functions inside defisland body (browser can't access
top-level defines from component defs bundle)
- Use div placeholder with data-sx-island attr (matches island root tag)
- Rename pretext-island.sx → pretext-client.sx for alphabetical load order
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
defisland ~pretext-demo/live — same Knuth-Plass algorithm running in
the browser with canvas.measureText for pixel-perfect font metrics.
- Width slider (200-700px), font size slider (10-24px)
- Greedy vs Knuth-Plass toggle button
- Reactive re-layout on every control change
- All layout functions inlined in the island (no library deps)
- Perfectly straight right edges — browser measures AND renders
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>
stream-colors dict had green/blue keys but data used emerald/violet — all three
slots now render with correct Tailwind color classes. Platform: resolveSuspense
must not exist on Sx until boot completes, otherwise bootstrap __sxResolve calls
it before web stack loads and resolves silently fail. Moved to post-boot setup
so all pre-boot resolves queue in __sxPending and drain correctly.
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>
Server (sx_server.ml):
- eval_with_io: CEK evaluator with IO suspension handling (io-sleep, import)
- io-sleep platform primitive: raises CekPerformRequest, resolved by eval_with_io
- Streaming render uses eval_with_io for data + content evaluation
- Data items with "delay" field sleep before resolving (async streaming)
- Removed hardcoded streaming-demo-data — application logic belongs in .sx
Application (streaming-demo.sx):
- streaming-demo-data defined in SX: 3 items with 1s/3s/5s delays
- Each item has delay, stream-id, and display data fields
- Shell renders instantly, slots fill progressively as IO completes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The lake preview ("the joy of sx") was flashing because:
1. SSR renders preview in lake (server-only guard)
2. replaceChildren swaps island DOM (lake now empty)
3. rebuild-preview effect was either skipped or deferred (rAF/setTimeout)
4. Browser paints empty lake → visible flash
Fix: first-run effect uses queueMicrotask instead of schedule-idle.
Microtasks fire after the current synchronous code (including
replaceChildren) but BEFORE the browser paints. The lake is filled
before any frame renders with empty content.
Also restored the (when (not (client?))) lake guard — the client
can't render steps-to-preview (returns raw SX expressions that
render-to-dom shows as source text, not rendered HTML).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: the lake had (when (not (client?)) ...) guard — SSR rendered
"the joy of sx" preview but client skipped it. replaceChildren swapped
in an empty lake. The rebuild-preview effect was skipped (first-run
optimization), so the preview stayed blank for ~500ms.
Fix: remove the client? guard so the lake renders on both server and
client. The template's steps-to-preview produces the initial preview.
The effect only fires on subsequent step changes (not first run).
Test: replaced MutationObserver approach with screenshot comparison.
Loads page with JS blocked (pure SSR), takes screenshot. Loads with JS
(hydration), takes screenshot. Compares pixels. Any visual difference
fails the test.
Result: "No visual flash: screenshots identical" — passes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: the stepper's rebuild effect (update-code-highlight,
rebuild-preview) fired immediately on hydration via schedule-idle,
modifying the DOM after replaceChildren swapped in identical content.
This caused a visible text change after the initial frame.
Fix: track initial step-idx value and first-run flag. Skip the
effect on first run if the current step matches the SSR state
(from cookie). The effect only fires on actual user interaction.
Result: SSR and hydrated text content are identical. replaceChildren
swaps DOM nodes but the visual content doesn't change. Zero flash.
Test: "No clobber: clean" — 0 text changes during hydration.
All 8 home features pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three changes to eliminate the stepper flash:
1. home-stepper.sx: server path reads cookie via (get-cookie) for
step-idx initial value. Client path reads document.cookie via
def-store. Both default to 0 when no cookie exists.
2. sx_server.ml: bypass response cache when sx-home-stepper cookie
is present. Render on main thread (not worker) so get-cookie
sees the parsed request cookies.
3. site-full.spec.js: flash detection test sets cookie=7 via
Playwright context, checks SSR HTML matches hydrated state.
Test: "No flash: SSR=7 hydrated=7 (cookie=7)" — passes.
Tested on fresh stack=site server subprocess.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously SSR rendered at step 16 (hardcoded) but client initialized
from cookie, causing a flash from 16 to the cookie value on return visits.
Fix: Both SSR and client default to step 0. The def-store initializer
reads the cookie for the client's initial value. Return visits show
a progressive fill (0 → cookie value) instead of a jarring state jump.
- step-idx default: (signal 0) in both SSR and client paths
- def-store: reads sx-home-stepper cookie for initial value, defaults to 0
- Removed redundant post-hydration cookie reset block
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>
- ~hyperscript/example component: shows "Try it" button with _= attr
for all on-click examples, source pre wraps long lines
- Added CSS for .active/.light/.dark demo classes with !important
to override Tailwind hover states
- Added #target div for the "put into" example
- Replaced broken examples (items, ~card, js-date-now) with
self-contained ones that use available primitives
- Repeat example left in with note: continuation after loop pending
- New test suite io-suspension-continuation documenting the stub VM
bug: outer do continuation lost after suspension/resume completes
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/sx/hyperscript.sx — _hyperscript playground page at
/sx/(applications.(hyperscript))
- compile-result defcomp (SSR compilation display)
- pipeline documentation (tokenize → parse → compile)
- example showcases with pre-compiled output
- sx-post form → handler for interactive compilation
sx/sx/handlers/hyperscript-api.sx — POST handler:
/sx/(applications.(hyperscript.(api.compile)))
Accepts source param, returns compiled SX + parse tree HTML
NOTE: hs-parse returns (do) in server context — JIT/CEK runtime
issue where parser closures don't evaluate correctly. Works in
test runner (3127/3127). Investigating separately.
sx_server.ml — url_decode fix: decode + as space in form data
Standard application/x-www-form-urlencoded uses + for spaces.
Nav: _hyperscript added to Applications section.
Config: handler:hs- prefix added for handler dispatch.
3127/3127 tests, zero regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the VM or CEK hits an undefined symbol, it checks a symbol→library
index (built from manifest exports at boot), loads the library that
exports it, and returns the value. Execution continues as if the module
was always loaded. No import statements, no load-library! calls, no
Suspense boundaries — just call the function.
This is the same mechanism as IO suspension for data fetching. The
programmer doesn't distinguish between calling a local function and
calling one that needs its module fetched first. The runtime treats
code as just another resource.
Implementation:
- _symbol_resolve_hook in sx_types.ml — called by env_get_id (CEK path)
and vm_global_get (VM path) when a symbol isn't found
- Symbol→library index built from manifest exports in sx-platform.js
- __resolve-symbol native calls __sxLoadLibrary, module loads, symbol
appears in globals, execution resumes
- compile-modules.js extracts export lists into module-manifest.json
- Playground page demonstrates: (freeze-scope) triggers freeze.sxbc
download transparently on first use
2650/2650 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>