diff --git a/shared/sx/helpers.py b/shared/sx/helpers.py index 60a9a6e..504093c 100644 --- a/shared/sx/helpers.py +++ b/shared/sx/helpers.py @@ -944,13 +944,22 @@ def sx_page_streaming_parts(ctx: dict, page_html: str, *, return shell, tail -def sx_streaming_resolve_script(suspension_id: str, sx_source: str) -> str: - """Build a ') + parts.append(_SX_STREAMING_RESOLVE.format( id=json.dumps(suspension_id), sx=json.dumps(sx_source), - ) + )) + return "\n".join(parts) _SCRIPT_HASH_CACHE: dict[str, str] = {} diff --git a/shared/sx/pages.py b/shared/sx/pages.py index 7f85c3d..87b0e96 100644 --- a/shared/sx/pages.py +++ b/shared/sx/pages.py @@ -439,6 +439,37 @@ async def execute_page_streaming( tctx, initial_page_html, page_sx=page_sx_for_scan, ) + # Capture component env + extras scanner while we still have context. + # Resolved SX may reference components not in the initial scan + # (e.g. ~cart-mini from IO-generated header content). + from .jinja_bridge import components_for_page as _comp_scan + from quart import current_app as _ca + _service = _ca.name + # Track which components were already sent in the shell + _shell_scan = page_sx_for_scan + + def _extra_defs(sx_source: str) -> str: + """Return component defs needed by sx_source but not in shell.""" + from .deps import components_needed + comp_env = dict(get_component_env()) + shell_needed = components_needed(_shell_scan, comp_env) + resolve_needed = components_needed(sx_source, comp_env) + extra = resolve_needed - shell_needed + if not extra: + return "" + from .parser import serialize + from .types import Component + parts = [] + for key, val in comp_env.items(): + if isinstance(val, Component) and (f"~{val.name}" in extra or key in extra): + param_strs = ["&key"] + list(val.params) + if val.has_children: + param_strs.extend(["&rest", "children"]) + params_sx = "(" + " ".join(param_strs) + ")" + body_sx = serialize(val.body, pretty=True) + parts.append(f"(defcomp ~{val.name} {params_sx} {body_sx})") + return "\n".join(parts) + # --- Return async generator that yields chunks --- # No context access needed below — just awaiting tasks and yielding strings. @@ -462,11 +493,13 @@ async def execute_page_streaming( if label == "data": content_sx, filter_sx, aside_sx, menu_sx = result - yield sx_streaming_resolve_script("stream-content", content_sx) + extras = _extra_defs(content_sx) + yield sx_streaming_resolve_script("stream-content", content_sx, extras) elif label == "headers": header_rows, header_menu = result if header_rows: - yield sx_streaming_resolve_script("stream-headers", header_rows) + extras = _extra_defs(header_rows) + yield sx_streaming_resolve_script("stream-headers", header_rows, extras) yield "\n\n"