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>
This commit is contained in:
2026-03-07 17:34:10 +00:00
parent 85083a0fff
commit a05d642461
15 changed files with 586 additions and 38 deletions

View File

@@ -26,6 +26,7 @@ def _register_sx_helpers() -> None:
"data-test-data": _data_test_data,
"run-spec-tests": _run_spec_tests,
"run-modular-tests": _run_modular_tests,
"streaming-demo-data": _streaming_demo_data,
})
@@ -791,3 +792,21 @@ def _data_test_data() -> dict:
"phase": "Phase 4 — Client Async & IO Bridge",
"transport": "SX wire format (text/sx)",
}
async def _streaming_demo_data() -> dict:
"""Simulate slow IO for streaming demo — 1.5s delay."""
import asyncio
await asyncio.sleep(1.5)
from datetime import datetime, timezone
return {
"streamed-at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"message": "This content was streamed after a 1.5 second delay.",
"items": [
{"label": "Shell", "detail": "HTML shell with suspense placeholders sent immediately"},
{"label": "Bootstrap", "detail": "sx-browser.js loads, renders fallback skeletons"},
{"label": "IO Start", "detail": "Data fetch and header fetch run concurrently"},
{"label": "Resolve", "detail": "As each IO completes, <script> chunk replaces placeholder"},
{"label": "Done", "detail": "Page fully rendered — all suspense resolved"},
],
}