parse() returns a list of expressions. resolveSuspense was passing the
entire array to renderToDom, which interpreted [(<> ...)] as a call
expression ((<> ...)) — causing "Not callable: {}". Now iterates
each expression individually, matching the public render() API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Streaming resolve scripts arrive after boot, so any extra component
defs sent as <script type="text/sx"> tags weren't being loaded.
Fix in the spec (boot.sx): call (process-sx-scripts nil) at the
start of resolve-suspense so late-arriving component defs are
available in componentEnv before rendering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server streams HTML shell with ~suspense placeholders immediately,
then sends resolution <script> chunks as async IO completes. Browser
renders loading skeletons instantly, replacing them with real content
as data arrives via __sxResolve().
- defpage :stream true opts pages into streaming response
- ~suspense component renders fallback with data-suspense attr
- resolve-suspense in boot.sx (spec) + bootstrapped to sx-browser.js
- __sxPending queue handles resolution before sx-browser.js loads
- execute_page_streaming() async generator with concurrent IO tasks
- Streaming demo page at /isomorphism/streaming with 1.5s simulated delay
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded IO primitive lists on both client and server with
data-driven registration. Page registry entries carry :io-deps (list
of IO primitive names) instead of :has-io boolean. Client registers
proxied IO on demand per page via registerIoDeps(). Server builds
allowlist from component analysis.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire async rendering into client-side routing: pages whose component
trees reference IO primitives (highlight, current-user, etc.) now
render client-side via Promise-aware asyncRenderToDom. IO calls proxy
through /sx/io/<name> endpoint, which falls back to page helpers.
- Add has-io flag to page registry entries (helpers.py)
- Remove IO purity filter — include IO-dependent components in bundles
- Extend try-client-route with 4 paths: pure, data, IO, data+IO
- Convert tryAsyncEvalContent to callback style, add platform mapping
- IO proxy falls back to page helpers (highlight works via proxy)
- Demo page: /isomorphism/async-io with inline highlight calls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The data-mount="body" script replaces the entire body content,
destroying the <script type="text/sx-pages"> tag. Moving
processPageScripts before processSxScripts ensures the page
registry is read before the body is replaced.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server-side: log page count, output size, and first 200 chars in _build_pages_sx.
Client-side: log script tag count, text length, parsed entry count in processPageScripts.
Helps diagnose why pages: 0 routes loaded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows "pages: N routes loaded" at startup and
"sx:route no match (N routes) /path" when no route matches,
so we can see if routes loaded and why matching fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
boot-init prints SX_VERSION (build timestamp) to console on startup.
tryClientRoute logs why it falls through: has-data, no content, eval
failed, #main-panel not found. tryEvalContent logs the actual error.
Added logWarn platform function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>