Commit Graph

8 Commits

Author SHA1 Message Date
ebbdec8f4c Fix orchestration.sx parse error, add parser line/col diagnostics
The parser was reporting "Unexpected char: )" with no position info.
Added line number, column, and byte position to all parse errors.

Root cause: bind-sse-swap had one extra close paren that naive paren
counting missed because a "(" exists inside a string literal on L1074
(starts-with? trimmed "("). Parse-aware counting (skipping strings
and comments) correctly identified the imbalance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:29:28 +00:00
f0f16d24bc Fix bridge newline escaping, stepper optimization, dom-listen dedup
- ocaml_sync.py: escape newlines in eval/load_source to prevent
  protocol desync (bridge crashed on any multi-line SX)
- Stepper: do-back uses rebuild-preview (O(1) render) instead of
  replaying all steps. Hydration effect same. Cookie save on button
  click only.
- dom.sx: remove duplicate dom-listen (was shadowing the one at
  line 351 that adapter-dom.sx's dom-on wraps)
- orchestration.sx: fix bind-sse-swap close paren count
- safe_eq: Dict equality via __host_handle for DOM node identity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:14:10 +00:00
fb8dbeba9f Fix dom-listen: rename dom-on to dom-listen in dom.sx
adapter-dom.sx defines dom-on as a wrapper around dom-listen (adds
post-render hooks). But dom-listen was never defined — my earlier
dom-on in dom.sx was overwritten by the adapter's version. Rename
to dom-listen so the adapter's dom-on can call it.

This fixes click handlers not firing on island buttons (stepper,
stopwatch, counter, etc.).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 14:56:34 +00:00
1a3ee40e0d Event buffering during hydration gap
Clicks on island elements before hydration completes are captured
and replayed after boot finishes:

- shell.sx: inline script (capture phase) buffers clicks on
  [data-sx-island] elements that aren't hydrated yet into window._sxQ
- boot.sx: after hydration + process-elements, replays buffered clicks
  by calling target.click() on elements still connected to the DOM

This makes SSR islands feel interactive immediately — the user can
click a button while the SX kernel is still loading/hydrating, and
the action fires as soon as the handler is wired up.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 14:45:04 +00:00
998536f52d Cache-bust wasm scripts, fix orchestration.sx paren balance
- Add wasm_hash (MD5 of sx_browser.bc.js) to shell template
- Script tags: /wasm/sx_browser.bc.js?v={hash}, /wasm/sx-platform.js?v={hash}
- Pass wasm_hash through helpers.py and ocaml_bridge.py
- Fix missing close paren in bind-sse-swap (broke SX parsing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 14:21:51 +00:00
1e42eb62a2 Fix create-text-node in DOM adapter: host handle Dicts are DOM nodes
The DOM adapter treated all Dict values as empty (create-fragment).
But DOM nodes (text nodes, elements) from create-text-node/host-call
are wrapped as Dict { __host_handle: N } by js_of_ocaml. Now checks
for __host_handle and passes them through as DOM nodes.

Fixes stopwatch button text ("Start") and timer display ("0.0s")
which were missing because create-text-node results were discarded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 14:10:28 +00:00
eacde62806 Fix navigation: outerHTML swap, island markers, host handle equality, dispose
Navigation pipeline now works end-to-end:
- outerHTML swap uses dom-replace-child instead of morph-node (morph has
  a CEK continuation issue with nested for-each that needs separate fix)
- swap-dom-nodes returns the new element for outerHTML so post-swap
  hydrates the correct (new) DOM, not the detached old element
- sx-render uses marker mode: islands rendered as empty span[data-sx-island]
  markers, hydrated by post-swap. Prevents duplicate content from island
  body expansion + SX response nav rows.
- dispose-island (singular) called on old island before morph, not just
  dispose-islands-in (which only disposes sub-islands)

OCaml runtime:
- safe_eq: Dict equality checks __host_handle for DOM node identity
  (js_to_value creates new Dict wrappers per call, breaking physical ==)
- contains?: same host handle check
- to_string: trampoline thunks (fixes <thunk> display)
- as_number: trampoline thunks (fixes arithmetic on leaked thunks)

DOM platform:
- dom-remove, dom-attr-list (name/value pairs), dom-child-list (SX list),
  dom-is-active-element?, dom-is-input-element?, dom-is-child-of?, dom-on

All 5 reactive-nav Playwright tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:19:29 +00:00
07bbcaf1bb OCaml VM browser: safe equality, thunk trampolining, platform functions, nav pipeline
Core runtime fixes:
- Safe equality (=, !=): physical equality for dicts/lambdas/signals,
  structural only for acyclic types. Prevents infinite loops on circular
  signal subscriber chains.
- contains?: same safe comparison (physical first, structural for simple types)
- Thunk trampolining in as_number and to_string: leaked thunks auto-resolve
  instead of showing <thunk> or erroring "Expected number, got thunk"
- Diagnostic first error: shows actual type received

Island hydration fixes:
- adapter-dom.sx: skip scope-emit for spreads inside islands (was tripling classes)
- schedule-idle: wrap callback to absorb requestIdleCallback deadline arg
- home-stepper: remove spread-specific highlighting (all tokens same style per step)

Platform functions (boot-helpers.sx):
- fetch-request: 3-arg interface (config, success-fn, error-fn) with promise chain
- build-request-body: form serialization for GET/POST
- strip-component-scripts / extract-response-css: SX text processing
- Navigation: bind-boost-link, bind-client-route-click via execute-request
- Loading state: show-indicator, disable-elements, clear-loading-state
- DOM extras: dom-remove, dom-attr-list (name/value pairs), dom-child-list (SX list),
  dom-is-active-element?, dom-is-input-element?, dom-is-child-of?, dom-on,
  dom-parse-html-document, dom-body-inner-html, create-script-clone
- All remaining stubs: csrf-token, loaded-component-names, observe-intersection,
  event-source-connect/listen, with-transition, cross-origin?, etc.

Navigation pipeline:
- browser-push-state/replace-state: accept 1-arg (URL only) or 3-arg
- boot.sx: wire popstate listener to handle-popstate
- URL updates working via handle-history + pushState fix

Morph debugging (WIP):
- dom-child-list returns proper SX list (was JS Array)
- dom-query accepts optional root element for scoped queries
- Navigation fetches and renders SX responses, URL updates, but morph
  doesn't replace content div (investigating dom-child-list on new elements)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:57:24 +00:00