Merge branch 'worktree-iso-phase-4' into macros

This commit is contained in:
2026-03-07 18:34:24 +00:00
2 changed files with 37 additions and 27 deletions

View File

@@ -864,22 +864,31 @@ if(window.Sx&&Sx.resolveSuspense){Sx.resolveSuspense(i,s)}\
else{window.__sxPending.push({id:i,sx:s})}}</script>"""
def sx_page_streaming_parts(ctx: dict, page_sx: str, *,
def sx_page_streaming_parts(ctx: dict, page_html: str, *,
page_sx: str = "",
meta_html: str = "") -> tuple[str, str]:
"""Split the page into shell (before scripts) and tail (scripts).
Returns (shell, tail) where:
shell = everything up to and including the page SX mount script
tail = the suspense bootstrap + sx-browser.js + body.js scripts
For streaming, the initial page is rendered to **HTML** server-side so
``[data-suspense]`` elements are in the DOM immediately — no client-side
SX rendering needed for the shell. Resolution scripts can find and
replace suspense placeholders without waiting for sx-browser.js to boot.
For streaming, the caller yields shell first, then resolution chunks,
then tail to close the document.
Args:
page_html: Server-rendered HTML for the page body (with suspense
placeholders already as real HTML elements).
page_sx: SX source scanned for component deps (may differ from
page_html when components were expanded server-side).
"""
from .jinja_bridge import components_for_page, css_classes_for_page
from .css_registry import lookup_rules, get_preamble, registry_loaded, store_css_hash
from quart import current_app as _ca
component_defs, component_hash = components_for_page(page_sx, service=_ca.name)
# Scan the SX source for component deps (needed for resolution scripts
# that may contain component calls the client must render)
scan_source = page_sx or page_html
component_defs, component_hash = components_for_page(scan_source, service=_ca.name)
client_hash = _get_sx_comp_cookie()
if not _is_dev_mode() and client_hash and client_hash == component_hash:
@@ -888,7 +897,7 @@ def sx_page_streaming_parts(ctx: dict, page_sx: str, *,
sx_css = ""
sx_css_classes = ""
if registry_loaded():
classes = css_classes_for_page(page_sx, service=_ca.name)
classes = css_classes_for_page(scan_source, service=_ca.name)
classes.update(["bg-stone-50", "text-stone-900"])
rules = lookup_rules(classes)
sx_css = get_preamble() + rules
@@ -898,13 +907,6 @@ def sx_page_streaming_parts(ctx: dict, page_sx: str, *,
title = ctx.get("base_title", "Rose Ash")
csrf = _get_csrf_token()
if _is_dev_mode() and page_sx and page_sx.startswith("("):
from .parser import parse as _parse, serialize as _serialize
try:
page_sx = _serialize(_parse(page_sx), pretty=True)
except Exception:
pass
styles_hash = _get_style_dict_hash()
client_styles_hash = _get_sx_styles_cookie()
styles_json = "" if (not _is_dev_mode() and client_styles_hash == styles_hash) else _build_style_dict_json()
@@ -916,7 +918,7 @@ def sx_page_streaming_parts(ctx: dict, page_sx: str, *,
sx_js_hash = _script_hash("sx-browser.js")
body_js_hash = _script_hash("body.js")
# Shell: everything up to and including the page SX
# Shell: head + body with server-rendered HTML (not SX mount script)
shell = (
'<!doctype html>\n<html lang="en">\n<head>\n'
'<meta charset="utf-8">\n'
@@ -954,7 +956,8 @@ def sx_page_streaming_parts(ctx: dict, page_sx: str, *,
f'<script type="text/sx-styles" data-hash="{styles_hash}">{styles_json}</script>\n'
f'<script type="text/sx" data-components data-hash="{component_hash}">{component_defs}</script>\n'
f'<script type="text/sx-pages">{pages_sx}</script>\n'
f'<script type="text/sx" data-mount="body">{page_sx}</script>\n'
# Server-rendered HTML — suspense placeholders are real DOM elements
f'{page_html}\n'
)
# Tail: bootstrap suspense resolver + scripts + close