From 2c56d3e14bdd58675a32c021166a66cf781f3e12 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 7 Mar 2026 01:26:39 +0000 Subject: [PATCH] Include all :data page component deps in every page's client bundle Per-page bundling now unions deps from all :data pages in the service, so navigating between data pages uses client-side rendering + cache instead of expensive server fetch + SX parse. Co-Authored-By: Claude Opus 4.6 --- shared/sx/helpers.py | 7 ++++--- shared/sx/jinja_bridge.py | 29 ++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/shared/sx/helpers.py b/shared/sx/helpers.py index 922dc7c..4d9d776 100644 --- a/shared/sx/helpers.py +++ b/shared/sx/helpers.py @@ -740,8 +740,9 @@ def sx_page(ctx: dict, page_sx: str, *, from .jinja_bridge import components_for_page, css_classes_for_page from .css_registry import lookup_rules, get_preamble, registry_loaded, store_css_hash - # Per-page component bundle: only definitions this page needs - component_defs, component_hash = components_for_page(page_sx) + # Per-page component bundle: this page's deps + all :data page deps + from quart import current_app as _ca + component_defs, component_hash = components_for_page(page_sx, service=_ca.name) # Check if client already has this version cached (via cookie) # In dev mode, always send full source so edits are visible immediately @@ -755,7 +756,7 @@ def sx_page(ctx: dict, page_sx: str, *, sx_css_classes = "" sx_css_hash = "" if registry_loaded(): - classes = css_classes_for_page(page_sx) + classes = css_classes_for_page(page_sx, service=_ca.name) # Always include body classes classes.update(["bg-stone-50", "text-stone-900"]) rules = lookup_rules(classes) diff --git a/shared/sx/jinja_bridge.py b/shared/sx/jinja_bridge.py index 9f44cd4..57860a8 100644 --- a/shared/sx/jinja_bridge.py +++ b/shared/sx/jinja_bridge.py @@ -332,17 +332,32 @@ def client_components_tag(*names: str) -> str: return f'' -def components_for_page(page_sx: str) -> tuple[str, str]: +def components_for_page(page_sx: str, service: str | None = None) -> tuple[str, str]: """Return (component_defs_source, page_hash) for a page. Scans *page_sx* for component references, computes the transitive closure, and returns only the definitions needed for this page. + + When *service* is given, also includes deps for all :data pages + in that service so the client can render them without a server + roundtrip on navigation. + The hash is computed from the page-specific bundle for caching. """ from .deps import components_needed from .parser import serialize needed = components_needed(page_sx, _COMPONENT_ENV) + + # Include deps for all :data pages so the client can render them + if service: + from .pages import get_all_pages + for page_def in get_all_pages(service).values(): + if page_def.data_expr is not None and page_def.content_expr is not None: + content_src = serialize(page_def.content_expr) + data_deps = components_needed(content_src, _COMPONENT_ENV) + needed |= data_deps + if not needed: return "", "" @@ -375,16 +390,24 @@ def components_for_page(page_sx: str) -> tuple[str, str]: return source, digest -def css_classes_for_page(page_sx: str) -> set[str]: +def css_classes_for_page(page_sx: str, service: str | None = None) -> set[str]: """Return CSS classes needed for a page's component bundle + page source. Instead of unioning ALL component CSS classes, only includes classes - from components the page actually uses. + from components the page actually uses (plus all :data page deps). """ from .deps import components_needed from .css_registry import scan_classes_from_sx + from .parser import serialize needed = components_needed(page_sx, _COMPONENT_ENV) + + if service: + from .pages import get_all_pages + for page_def in get_all_pages(service).values(): + if page_def.data_expr is not None and page_def.content_expr is not None: + content_src = serialize(page_def.content_expr) + needed |= components_needed(content_src, _COMPONENT_ENV) classes: set[str] = set() for key, val in _COMPONENT_ENV.items():