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>
This commit is contained in:
2026-03-07 08:12:42 +00:00
parent 04ff03f5d4
commit 79fa1411dc
10 changed files with 1502 additions and 264 deletions

View File

@@ -648,40 +648,71 @@
(do (log-warn (str "sx:route target not found: " target-sel)) false)
(if (not (deps-satisfied? match))
(do (log-info (str "sx:route deps miss for " page-name)) false)
(if (get match "has-data")
;; Data page: check cache, else resolve asynchronously
(let ((cache-key (page-data-cache-key page-name params))
(cached (page-data-cache-get cache-key)))
(if cached
;; Cache hit: render immediately
(let ((env (merge closure params cached))
(let ((has-io (get match "has-io")))
(if (get match "has-data")
;; Data page: check cache, else resolve asynchronously
(let ((cache-key (page-data-cache-key page-name params))
(cached (page-data-cache-get cache-key)))
(if cached
;; Cache hit
(let ((env (merge closure params cached)))
(if has-io
;; Async render (data+IO)
(do
(log-info (str "sx:route client+cache+async " pathname))
(try-async-eval-content content-src env
(fn (rendered)
(if (nil? rendered)
(log-warn (str "sx:route async eval failed for " pathname))
(swap-rendered-content target rendered pathname))))
true)
;; Sync render (data only)
(let ((rendered (try-eval-content content-src env)))
(if (nil? rendered)
(do (log-warn (str "sx:route cached eval failed for " pathname)) false)
(do
(log-info (str "sx:route client+cache " pathname))
(swap-rendered-content target rendered pathname)
true)))))
;; Cache miss: fetch, cache, render
(do
(log-info (str "sx:route client+data " pathname))
(resolve-page-data page-name params
(fn (data)
(page-data-cache-set cache-key data)
(let ((env (merge closure params data)))
(if has-io
;; Async render (data+IO)
(try-async-eval-content content-src env
(fn (rendered)
(if (nil? rendered)
(log-warn (str "sx:route data+async eval failed for " pathname))
(swap-rendered-content target rendered pathname))))
;; Sync render (data only)
(let ((rendered (try-eval-content content-src env)))
(if (nil? rendered)
(log-warn (str "sx:route data eval failed for " pathname))
(swap-rendered-content target rendered pathname)))))))
true)))
;; Non-data page
(if has-io
;; Async render (IO only, no data)
(do
(log-info (str "sx:route client+async " pathname))
(try-async-eval-content content-src (merge closure params)
(fn (rendered)
(if (nil? rendered)
(log-warn (str "sx:route async eval failed for " pathname))
(swap-rendered-content target rendered pathname))))
true)
;; Pure page: render immediately
(let ((env (merge closure params))
(rendered (try-eval-content content-src env)))
(if (nil? rendered)
(do (log-warn (str "sx:route cached eval failed for " pathname)) false)
(do (log-info (str "sx:route server (eval failed) " pathname)) false)
(do
(log-info (str "sx:route client+cache " pathname))
(swap-rendered-content target rendered pathname)
true)))
;; Cache miss: fetch, cache, render
(do
(log-info (str "sx:route client+data " pathname))
(resolve-page-data page-name params
(fn (data)
(page-data-cache-set cache-key data)
(let ((env (merge closure params data))
(rendered (try-eval-content content-src env)))
(if (nil? rendered)
(log-warn (str "sx:route data eval failed for " pathname))
(swap-rendered-content target rendered pathname)))))
true)))
;; Pure page: render immediately
(let ((env (merge closure params))
(rendered (try-eval-content content-src env)))
(if (nil? rendered)
(do (log-info (str "sx:route server (eval failed) " pathname)) false)
(do
(swap-rendered-content target rendered pathname)
true)))))))))))))
true)))))))))))))))
(define bind-client-route-link
@@ -991,6 +1022,8 @@
;;
;; === Client-side routing ===
;; (try-eval-content source env) → DOM node or nil (catches eval errors)
;; (try-async-eval-content source env callback) → void; async render,
;; calls (callback rendered-or-nil). Used for pages with IO deps.
;; (url-pathname href) → extract pathname from URL string
;; (resolve-page-data name params cb) → void; resolves data for a named page.
;; Platform decides transport (HTTP, cache, IPC, etc). Calls (cb data-dict)