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:
@@ -258,6 +258,13 @@ class PyEmitter:
|
||||
"transitive-io-refs": "transitive_io_refs",
|
||||
"compute-all-io-refs": "compute_all_io_refs",
|
||||
"component-pure?": "component_pure_p",
|
||||
# router.sx
|
||||
"split-path-segments": "split_path_segments",
|
||||
"make-route-segment": "make_route_segment",
|
||||
"parse-route-pattern": "parse_route_pattern",
|
||||
"match-route-segments": "match_route_segments",
|
||||
"match-route": "match_route",
|
||||
"find-matching-route": "find_matching_route",
|
||||
}
|
||||
if name in RENAMES:
|
||||
return RENAMES[name]
|
||||
@@ -391,6 +398,9 @@ class PyEmitter:
|
||||
assignments.append((self._mangle(vname), self.emit(bindings[i + 1])))
|
||||
# Nested IIFE for sequential let (each binding can see previous ones):
|
||||
# (lambda a: (lambda b: body)(val_b))(val_a)
|
||||
# Cell variables (mutated by nested set!) are initialized in _cells dict
|
||||
# instead of lambda params, since the body reads _cells[name].
|
||||
cell_vars = getattr(self, '_current_cell_vars', set())
|
||||
body_parts = [self.emit(b) for b in body]
|
||||
if len(body) == 1:
|
||||
body_str = body_parts[0]
|
||||
@@ -399,7 +409,11 @@ class PyEmitter:
|
||||
# Build from inside out
|
||||
result = body_str
|
||||
for name, val in reversed(assignments):
|
||||
result = f"(lambda {name}: {result})({val})"
|
||||
if name in cell_vars:
|
||||
# Cell var: initialize in _cells dict, not as lambda param
|
||||
result = f"_sx_begin(_sx_cell_set(_cells, {self._py_string(name)}, {val}), {result})"
|
||||
else:
|
||||
result = f"(lambda {name}: {result})({val})"
|
||||
return result
|
||||
|
||||
def _emit_if(self, expr) -> str:
|
||||
@@ -828,6 +842,7 @@ ADAPTER_FILES = {
|
||||
|
||||
SPEC_MODULES = {
|
||||
"deps": ("deps.sx", "deps (component dependency analysis)"),
|
||||
"router": ("router.sx", "router (client-side route matching)"),
|
||||
}
|
||||
|
||||
|
||||
@@ -1947,6 +1962,9 @@ range = PRIMITIVES["range"]
|
||||
apply = lambda f, args: f(*args)
|
||||
assoc = PRIMITIVES["assoc"]
|
||||
concat = PRIMITIVES["concat"]
|
||||
split = PRIMITIVES["split"]
|
||||
length = PRIMITIVES["len"]
|
||||
merge = PRIMITIVES["merge"]
|
||||
'''
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user