Phase 3: Client-side routing with SX page registry + routing analyzer demo
Add client-side route matching so pure pages (no IO deps) can render instantly without a server roundtrip. Page metadata serialized as SX dict literals (not JSON) in <script type="text/sx-pages"> blocks. - New router.sx spec: route pattern parsing and matching (6 pure functions) - boot.sx: process page registry using SX parser at startup - orchestration.sx: intercept boost links for client routing with try-first/fallback — client attempts local eval, falls back to server - helpers.py: _build_pages_sx() serializes defpage metadata as SX - Routing analyzer demo page showing per-page client/server classification - 32 tests for Phase 2 IO detection (scan_io_refs, transitive_io_refs, compute_all_io_refs, component_pure?) + fallback/ref parity - 37 tests for Phase 3 router functions + page registry serialization - Fix bootstrap_py.py _emit_let cell variable initialization bug - Fix missing primitive aliases (split, length, merge) in bootstrap_py.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -631,6 +631,7 @@ details.group{{overflow:hidden}}details.group>summary{{list-style:none}}details.
|
||||
<body class="bg-stone-50 text-stone-900">
|
||||
<script type="text/sx-styles" data-hash="{styles_hash}">{styles_json}</script>
|
||||
<script type="text/sx" data-components data-hash="{component_hash}">{component_defs}</script>
|
||||
<script type="text/sx-pages">{pages_sx}</script>
|
||||
<script type="text/sx" data-mount="body">{page_sx}</script>
|
||||
<script src="{asset_url}/scripts/sx-browser.js?v={sx_js_hash}"></script>
|
||||
<script src="{asset_url}/scripts/body.js?v={body_js_hash}"></script>
|
||||
@@ -638,6 +639,66 @@ details.group{{overflow:hidden}}details.group>summary{{list-style:none}}details.
|
||||
</html>"""
|
||||
|
||||
|
||||
def _build_pages_sx(service: str) -> str:
|
||||
"""Build SX page registry for client-side routing.
|
||||
|
||||
Returns SX dict literals (one per page) parseable by the client's
|
||||
``parse`` function. Each dict has keys: name, path, auth, has-data,
|
||||
content, closure.
|
||||
"""
|
||||
from .pages import get_all_pages
|
||||
from .parser import serialize as sx_serialize
|
||||
|
||||
pages = get_all_pages(service)
|
||||
if not pages:
|
||||
return ""
|
||||
|
||||
entries = []
|
||||
for page_def in pages.values():
|
||||
content_src = ""
|
||||
if page_def.content_expr is not None:
|
||||
try:
|
||||
content_src = sx_serialize(page_def.content_expr)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
auth = page_def.auth if isinstance(page_def.auth, str) else "custom"
|
||||
has_data = "true" if page_def.data_expr is not None else "false"
|
||||
|
||||
# Build closure as SX dict
|
||||
closure_parts: list[str] = []
|
||||
for k, v in page_def.closure.items():
|
||||
if isinstance(v, (str, int, float, bool)):
|
||||
closure_parts.append(f":{k} {_sx_literal(v)}")
|
||||
closure_sx = "{" + " ".join(closure_parts) + "}"
|
||||
|
||||
entry = (
|
||||
"{:name " + _sx_literal(page_def.name)
|
||||
+ " :path " + _sx_literal(page_def.path)
|
||||
+ " :auth " + _sx_literal(auth)
|
||||
+ " :has-data " + has_data
|
||||
+ " :content " + _sx_literal(content_src)
|
||||
+ " :closure " + closure_sx + "}"
|
||||
)
|
||||
entries.append(entry)
|
||||
|
||||
return "\n".join(entries)
|
||||
|
||||
|
||||
def _sx_literal(v: object) -> str:
|
||||
"""Serialize a Python value as an SX literal."""
|
||||
if v is None:
|
||||
return "nil"
|
||||
if isinstance(v, bool):
|
||||
return "true" if v else "false"
|
||||
if isinstance(v, (int, float)):
|
||||
return str(v)
|
||||
if isinstance(v, str):
|
||||
escaped = v.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
|
||||
return f'"{escaped}"'
|
||||
return "nil"
|
||||
|
||||
|
||||
def sx_page(ctx: dict, page_sx: str, *,
|
||||
meta_html: str = "") -> str:
|
||||
"""Return a minimal HTML shell that boots the page from sx source.
|
||||
@@ -692,6 +753,14 @@ def sx_page(ctx: dict, page_sx: str, *,
|
||||
else:
|
||||
styles_json = _build_style_dict_json()
|
||||
|
||||
# Page registry for client-side routing
|
||||
pages_sx = ""
|
||||
try:
|
||||
from quart import current_app
|
||||
pages_sx = _build_pages_sx(current_app.name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return _SX_PAGE_TEMPLATE.format(
|
||||
title=_html_escape(title),
|
||||
asset_url=asset_url,
|
||||
@@ -701,6 +770,7 @@ def sx_page(ctx: dict, page_sx: str, *,
|
||||
component_defs=component_defs,
|
||||
styles_hash=styles_hash,
|
||||
styles_json=styles_json,
|
||||
pages_sx=pages_sx,
|
||||
page_sx=page_sx,
|
||||
sx_css=sx_css,
|
||||
sx_css_classes=sx_css_classes,
|
||||
|
||||
Reference in New Issue
Block a user