Files
rose-ash/sx/sx/streaming-demo.sx
giles 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

61 lines
3.5 KiB
Plaintext

;; Streaming & Suspense demo — Phase 6
;;
;; This page uses :stream true to enable chunked transfer encoding.
;; The browser receives the HTML shell immediately with loading skeletons,
;; then the content fills in when the (deliberately slow) data resolves.
;;
;; The :data expression simulates 1.5s IO delay. Without streaming, the
;; browser would wait the full 1.5s before seeing anything. With streaming,
;; the page skeleton appears instantly.
(defcomp ~streaming-demo-content (&key streamed-at message items)
(div :class "space-y-8"
(div :class "border-b border-stone-200 pb-6"
(h1 :class "text-2xl font-bold text-stone-900" "Streaming & Suspense Demo")
(p :class "mt-2 text-stone-600"
"This page uses " (code :class "bg-stone-100 px-1 rounded text-violet-700" ":stream true")
" in its defpage declaration. The browser receives the page skeleton instantly, "
"then content fills in as IO resolves."))
;; Timestamp proves this was streamed
(div :class "rounded-lg border border-green-200 bg-green-50 p-5 space-y-3"
(h2 :class "text-lg font-semibold text-green-900" "Streamed Content")
(p :class "text-green-800" message)
(p :class "text-green-700 text-sm"
"Data resolved at: " (code :class "bg-green-100 px-1 rounded" streamed-at))
(p :class "text-green-700 text-sm"
"This content arrived via a " (code :class "bg-green-100 px-1 rounded" "<script>__sxResolve(...)</script>")
" chunk streamed after the initial HTML shell."))
;; Flow diagram
(div :class "space-y-4"
(h2 :class "text-lg font-semibold text-stone-800" "Streaming Flow")
(div :class "grid gap-3"
(map (fn (item)
(div :class "flex items-start gap-3 rounded-lg border border-stone-200 bg-white p-4"
(div :class "flex-shrink-0 w-8 h-8 rounded-full bg-violet-100 flex items-center justify-center text-violet-700 font-bold text-sm"
(get item "label"))
(p :class "text-stone-700 text-sm pt-1" (get item "detail"))))
items)))
;; How it works
(div :class "rounded-lg border border-blue-200 bg-blue-50 p-5 space-y-3"
(h2 :class "text-lg font-semibold text-blue-900" "How Streaming Works")
(ol :class "list-decimal list-inside text-blue-800 space-y-2 text-sm"
(li "Server starts data fetch and header fetch " (em "concurrently"))
(li "HTML shell with " (code "~suspense") " placeholders is sent immediately")
(li "Browser loads sx-browser.js, renders the page with loading skeletons")
(li "Data IO completes — server sends " (code "<script>__sxResolve(\"stream-content\", ...)</script>"))
(li "sx.js calls " (code "Sx.resolveSuspense()") " — replaces skeleton with real content")
(li "Header IO completes — same process for header area")))
;; Technical details
(div :class "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2"
(p :class "font-semibold text-amber-800" "Implementation details")
(ul :class "list-disc list-inside text-amber-700 space-y-1"
(li (code "defpage :stream true") " — opts the page into streaming response")
(li (code "~suspense :id \"...\" :fallback (...)") " — renders loading skeleton until resolved")
(li "Quart async generator response — yields chunks as they become available")
(li "Resolution via " (code "__sxResolve(id, sx)") " inline scripts in the stream")
(li "Falls back to standard (non-streaming) response for SX/HTMX requests")))))