Fix streaming: render initial shell as HTML, not SX wire format
The streaming shell now uses render_to_html so [data-suspense] elements are real DOM elements immediately when the browser parses the HTML. Previously the shell used SX wire format in a <script data-mount> tag, requiring sx-browser.js to boot and render before suspense elements existed — creating a race condition where resolution scripts fired before the elements were in the DOM. Now: server renders HTML with suspense placeholders → browser has real DOM elements → resolution scripts find and replace them reliably. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -330,7 +330,8 @@ async def execute_page_streaming(
|
||||
from .async_eval import async_eval
|
||||
from .page import get_template_context
|
||||
from .helpers import (
|
||||
_render_to_sx, sx_page_streaming_parts,
|
||||
render_to_html as _helpers_render_to_html,
|
||||
sx_page_streaming_parts,
|
||||
sx_streaming_resolve_script,
|
||||
)
|
||||
from .parser import SxExpr, serialize as sx_serialize
|
||||
@@ -413,18 +414,24 @@ async def execute_page_streaming(
|
||||
data_task = asyncio.create_task(_eval_data_and_content())
|
||||
header_task = asyncio.create_task(_eval_headers())
|
||||
|
||||
# --- Build initial shell (still in request context) ---
|
||||
# --- Build initial shell as HTML (still in request context) ---
|
||||
# Render to HTML so [data-suspense] elements are real DOM immediately.
|
||||
# No dependency on sx-browser.js boot timing for the initial shell.
|
||||
|
||||
initial_page_sx = await _render_to_sx("app-body",
|
||||
header_rows=SxExpr(
|
||||
f'(~suspense :id "stream-headers" :fallback {header_fallback})'
|
||||
),
|
||||
content=SxExpr(
|
||||
f'(~suspense :id "stream-content" :fallback {fallback_sx})'
|
||||
),
|
||||
suspense_header_sx = f'(~suspense :id "stream-headers" :fallback {header_fallback})'
|
||||
suspense_content_sx = f'(~suspense :id "stream-content" :fallback {fallback_sx})'
|
||||
|
||||
initial_page_html = await _helpers_render_to_html("app-body",
|
||||
header_rows=SxExpr(suspense_header_sx),
|
||||
content=SxExpr(suspense_content_sx),
|
||||
)
|
||||
|
||||
shell, tail = sx_page_streaming_parts(tctx, initial_page_sx)
|
||||
# Pass the SX source for component scanning (resolution scripts may
|
||||
# contain component calls that the client needs to render)
|
||||
page_sx_for_scan = f'(~app-body :header-rows {suspense_header_sx} :content {suspense_content_sx})'
|
||||
shell, tail = sx_page_streaming_parts(
|
||||
tctx, initial_page_html, page_sx=page_sx_for_scan,
|
||||
)
|
||||
|
||||
# --- Return async generator that yields chunks ---
|
||||
# No context access needed below — just awaiting tasks and yielding strings.
|
||||
|
||||
Reference in New Issue
Block a user