Commit Graph

117 Commits

Author SHA1 Message Date
a05d642461 Phase 6: Streaming & Suspense — chunked HTML with suspense resolution
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>
2026-03-07 17:34:10 +00:00
3119b8e310 Add Testing as top-level docs section with per-module specs
New /testing/ section with 6 pages: overview (all specs), evaluator,
parser, router, renderer, and runners. Each page runs tests server-side
(Python) and offers a browser "Run tests" button (JS). Modular browser
runner (sxRunModularTests) loads framework + per-spec sources from DOM.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:37:30 +00:00
7eb158c79f Add live browser test runner to /specs/testing page
sx-browser.js evaluates test.sx directly in the browser — click
"Run 81 tests" to see SX test itself. Uses the same Sx global that
rendered the page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:00:37 +00:00
84ea5d4c16 IO proxy: client-side cache with 5min TTL, server Cache-Control
Client caches IO results by (name + args) in memory. In-flight
promises are cached too (dedup concurrent calls for same args).
Server adds Cache-Control: public, max-age=300 for HTTP caching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 09:51:51 +00:00
ff2ef29d8a Fix async map: use Lambda.params/body/closure (not _params/_body/_closure)
The Lambda constructor stores properties without underscore prefix,
but asyncRenderMap/asyncRenderMapIndexed accessed them with underscores.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 09:40:23 +00:00
aa67b036c7 IO proxy: POST for long payloads, network error resilience
- Switch to POST with JSON body when query string exceeds 1500 chars
  (highlight calls with large component sources hit URL length limits)
- Include CSRF token header on POST requests
- Add .catch() on fetch to gracefully handle network errors (return NIL)
- Upgrade async eval miss logs from logInfo to logWarn for visibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 09:23:20 +00:00
cb0990feb3 Dynamic IO proxy: derive proxied primitives from component io_refs
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>
2026-03-07 09:13:53 +00:00
79fa1411dc Phase 5: async IO rendering — components call IO primitives client-side
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>
2026-03-07 08:12:42 +00:00
04ff03f5d4 Live-read all DOM attributes: forms and preloads too
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m49s
bindBoostForm re-reads method/action at submit time.
bind-preload-for re-reads verb-info and headers at preload time.
No closed-over stale values anywhere in the event binding system.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:11:09 +00:00
b85a46bb62 Re-read element attributes at click time, not from closed-over bind values
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m39s
All click handlers (bind-event, bindBoostLink, bindClientRouteClick)
now re-read href/verb-info from the DOM element when the click fires,
instead of using values captured at bind time. This ensures correct
behavior when DOM is replaced or attributes are morphed after binding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:06:21 +00:00
6655f638b9 Optimize evaluator hot path: prototype-chain envs, imperative kwarg parsing
Three key optimizations to the JS evaluator platform layer:

1. envMerge uses Object.create() instead of copying all keys — O(own) vs O(all)
2. renderDomComponent/renderDomElement override: imperative kwarg/attr
   parsing replaces reduce+assoc pattern (no per-arg dict allocation)
3. callComponent/parseKeywordArgs override: same imperative pattern
   for the eval path (not just DOM rendering)

Wire format and spec semantics unchanged — these are host-level
performance overrides in the platform JS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:42:09 +00:00
fa295acfe3 Remove debug logs from client routing, Phase 4 confirmed working
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:23:32 +00:00
28ee441d9a Debug: log fallback path when client route fails
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:20:58 +00:00
1387d97c82 Clean up debug logs from try-client-route, keep deps check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:16:24 +00:00
b90cc59029 Check component deps before attempting client-side route render
Pages whose components aren't loaded client-side now fall through
to server fetch instead of silently failing in the async callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:13:09 +00:00
c15dbc3242 Debug: log has-data type and cache status in try-client-route
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:03:48 +00:00
ece2aa225d Fix popstate and client routing when no [sx-boost] container exists
handle-popstate falls back to #main-panel when no [sx-boost] element
is found, fixing back button for apps using explicit sx-target attrs.
bindClientRouteClick also checks sx-target on the link itself.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:53:08 +00:00
ac1dc34dad Fix: pass target selector to tryClientRoute from link clicks
bindClientRouteClick was calling tryClientRoute(pathname) without the
target-sel argument. This caused resolve-route-target to return nil,
so client routing ALWAYS fell back to server fetch on link clicks.
Now finds the sx-boost ancestor and passes its target selector.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:46:25 +00:00
f36583b620 Fix: register append!/dict-set! in PRIMITIVES after it is defined
The registrations were in the platform eval block which emits before
var PRIMITIVES = {}. Moved to core.list and core.dict primitive sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:33:49 +00:00
6772f1141f Register append! and dict-set! as proper primitives
Previously these mutating operations were internal helpers in the JS
bootstrapper but not declared in primitives.sx or registered in the
Python evaluator. Now properly specced and available in both hosts.

Removes mock injections from cache tests — they use real primitives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:21:17 +00:00
d3617ab7f3 Phase 4 complete: client data cache + plan update
- Add page data cache in orchestration.sx (30s TTL, keyed by page-name+params)
- Cache hit path: sx:route client+cache (instant render, no fetch)
- Cache miss path: sx:route client+data (fetch, cache, render)
- Fix HTMX response dep computation to include :data pages
- Update isomorphic-sx-plan.md: Phases 1-4 marked done with details,
  reorder remaining phases (continuations→Phase 5, suspense→Phase 6,
  optimistic updates→Phase 7)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:06:22 +00:00
732923a7ef Fix: auto-include router spec module when boot adapter is present
boot.sx uses parse-route-pattern from router.sx, but router was only
included as an opt-in spec module. Now auto-included when boot is in
the adapter set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:53:55 +00:00
a657d0831c Phase 4: Client-side rendering of :data pages via abstract resolve-page-data
Spec layer (orchestration.sx):
- try-client-route now handles :data pages instead of falling back to server
- New abstract primitive resolve-page-data(name, params, callback) — platform
  decides transport (HTTP, IPC, cache, etc)
- Extracted swap-rendered-content and resolve-route-target helpers

Platform layer (bootstrap_js.py):
- resolvePageData() browser implementation: fetches /sx/data/<name>, parses
  SX response, calls callback. Other hosts provide their own transport.

Server layer (pages.py):
- evaluate_page_data() evaluates :data expr, serializes result as SX
- auto_mount_page_data() mounts /sx/data/ endpoint with per-page auth
- _build_pages_sx now computes component deps for all pages (not just pure)

Test page at /isomorphism/data-test exercises the full pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:46:30 +00:00
eee2954559 Update reference docs: fix event names, add demos, document sx-boost target
- Remove sx:afterSettle (not dispatched), rename sx:sendError → sx:requestError
- Add sx:clientRoute event (Phase 3 client-side routing)
- Add working demos for all 10 events (afterRequest, afterSwap, requestError,
  clientRoute, sseOpen, sseMessage, sseError were missing demos)
- Update sx-boost docs: configurable target selector, client routing behavior
- Remove app-specific nav logic from orchestration.sx, use sx:clientRoute event
- Pass page content deps to sx_response for component loading after server fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:38 +00:00
093050059d Remove debug env logging from tryClientRoute
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:18:59 +00:00
6a5cb31123 Debug: log env keys and params in tryClientRoute
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:15:20 +00:00
bcb58d340f Unknown components throw instead of rendering error box
render-dom-unknown-component now calls (error ...) instead of
creating a styled div. This lets tryEvalContent catch the error
and fall back to server fetch, instead of rendering "Unknown
component: ~name" into the page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:12:59 +00:00
b98a8f8c41 Try-first routing: attempt eval, fall back on failure
Remove strict deps check — for case expressions like essay pages,
deps includes ALL branches but only one is taken. Instead, just
try to eval the content. If a component is missing, tryEvalContent
catches the error and we transparently fall back to server fetch.
deps field remains in registry for future prefetching use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:10:35 +00:00
14c5316d17 Add component deps to page registry, check before client routing
Each page entry now includes a deps list of component names needed.
Client checks all deps are loaded before attempting eval — if any
are missing, falls through to server fetch with a clear log message.
No bundle bloat: server sends components for the current page only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:06:28 +00:00
3b00a7095a Fix (not) compilation: use isSxTruthy for NIL-safe negation
NIL is a frozen sentinel object ({_nil:true}) which is truthy in JS.
(not expr) compiled to !expr, so (not nil) returned false instead of
true. Fixed to compile as !isSxTruthy(expr) which correctly handles
NIL. This was preventing client-side routing from activating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:01:39 +00:00
719dfbf732 Debug: log each isGetLink condition individually
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:59:58 +00:00
5ea0f5c546 Debug: log mods.delay and isGetLink result
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:59:17 +00:00
74428cc433 Debug: log verbInfo method and url in click handler
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:58:43 +00:00
d1a47e1e52 Restore click handler logging + use logInfo for errors (SES-safe)
SES lockdown may suppress console.error. Use logInfo for error
reporting since we know it works ([sx-ref] prefix visible).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:57:44 +00:00
3d191099e0 Surface all errors: wrap event handlers in try/catch with console.error
- domAddListener wraps callbacks so exceptions always log to console
- Add "sx:route trying <url>" log before tryClientRoute call
- Remove redundant bind-event fired log (replaced with route-specific logs)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:53:54 +00:00
70cf501c49 Debug: log every bind-event click handler firing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:51:04 +00:00
2a978e6e9f Add explicit logging for route decisions in bind-event
- Log "sx:route server fetch <url>" when falling back to network
- Use console.error for eval errors (not console.warn)
- Restructure bind-event to separate client route check from &&-chain

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:44:55 +00:00
3a8ee0dbd6 Fix router.sx: use len not length (correct SX primitive name)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:38:23 +00:00
c346f525d2 Include router spec module in sx-browser.js bootstrap
parseRoutePattern was undefined because the router module
wasn't included in the build. Now passing --spec-modules router.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:37:41 +00:00
79ee3bc46e Fix page registry: process page scripts before mount scripts
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>
2026-03-06 21:35:59 +00:00
c80b5d674f Add debug logging to page registry pipeline
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>
2026-03-06 21:31:53 +00:00
eb70e7237e Log route count on boot and no-match on route attempts
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>
2026-03-06 21:10:24 +00:00
a7d09291b8 Add version logging and route decision logging to sx-browser
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>
2026-03-06 21:05:39 +00:00
2d5096be6c Add console logging for client-side routing decisions
tryClientRoute now logs why it falls through: has-data, no content,
eval failed, or #main-panel not found. tryEvalContent logs the actual
error on catch. Added logWarn platform function (console.warn).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:43:27 +00:00
f70861c175 Try client-side routing for all sx-get link clicks, not just boost links
bind-event now checks tryClientRoute before executeRequest for GET
clicks on links. Previously only boost links (inside [sx-boost]
containers) attempted client routing — explicit sx-get links like
~nav-link always hit the network. Now essay/doc nav links render
client-side when possible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:31:23 +00:00
20ac0fe948 Spec eval-cond and process-bindings in render.sx (remove platform implementations)
eval-cond and process-bindings were hand-written platform JS in
bootstrap_js.py rather than specced in .sx files. This violated the
SX host architecture principle. Now specced in render.sx as shared
render adapter helpers, bootstrapped to both JS and Python.

eval-cond handles both scheme-style ((test body) ...) and clojure-style
(test body test body ...) cond clauses. Returns unevaluated body
expression for the adapter to render in its own mode.

process-bindings evaluates let-binding pairs and returns extended env.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:58:53 +00:00
a2d0a8a0fa Fix evalCond in HTML/DOM renderers: handle scheme-style cond clauses
The platform evalCond helper (used by render-to-html and render-to-dom)
only handled clojure-style (test body test body ...) but components use
scheme-style ((test body) (test body) ...). This caused "Not callable:
true" errors when rendering cond with nested clause pairs, breaking the
test dashboard and any page using scheme-style cond.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:49:42 +00:00
3749fe9625 Fix bootstrapper dict literal transpilation: emit values through emit()
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m51s
The SX parser produces native Python dicts for {:key val} syntax, but
both JSEmitter and PyEmitter had no dict case in emit() — falling through
to str(expr) which output raw AST. This broke client-side routing because
process-page-scripts used {"parsed" (parse-route-pattern ...)} and the
function call was emitted as a JS array of Symbols instead of an actual
function call.

Add _emit_native_dict() to both bootstrappers + 8 unit tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:24:44 +00:00
cf5e767510 Phase 3: Client-side routing with SX page registry + routing analyzer demo
Add client-side route matching so pure pages (no IO deps) can render
instantly without a server roundtrip. Page metadata serialized as SX
dict literals (not JSON) in <script type="text/sx-pages"> blocks.

- New router.sx spec: route pattern parsing and matching (6 pure functions)
- boot.sx: process page registry using SX parser at startup
- orchestration.sx: intercept boost links for client routing with
  try-first/fallback — client attempts local eval, falls back to server
- helpers.py: _build_pages_sx() serializes defpage metadata as SX
- Routing analyzer demo page showing per-page client/server classification
- 32 tests for Phase 2 IO detection (scan_io_refs, transitive_io_refs,
  compute_all_io_refs, component_pure?) + fallback/ref parity
- 37 tests for Phase 3 router functions + page registry serialization
- Fix bootstrap_py.py _emit_let cell variable initialization bug
- Fix missing primitive aliases (split, length, merge) in bootstrap_py.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:47:56 +00:00
0ba7ebe349 Phase 2: IO detection & selective expansion in deps.sx
Extend the spec with IO scanning functions (scan-io-refs, transitive-io-refs,
compute-all-io-refs, component-pure?) that detect IO primitive references in
component ASTs. Components are classified as pure (no IO deps, safe for client
rendering) or IO-dependent (must expand server-side).

The partial evaluator (_aser) now uses per-component IO metadata instead of
the global _expand_components toggle: IO-dependent components expand server-
side, pure components serialize for client. Layout slot context still expands
all components for backwards compat.

Spec: 5 new functions + 2 platform interface additions in deps.sx
Host: io_refs field + is_pure property on Component, compute_all_io_refs()
Bootstrap: both sx_ref.py and sx-ref.js updated with IO functions
Bundle analyzer: shows pure/IO classification per page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:19:17 +00:00