Fix aser server-affinity expansion: keyword values, OOB wrapper, page helpers

Three bugs in aser-expand-component (adapter-sx.sx):
- Keyword values were eval'd (eval-expr can't handle <>, HTML tags);
  now asered, matching the aser's rendering capabilities
- Missing default nil binding for unset &key params (caused
  "Undefined symbol" errors for optional params like header-rows)
- aserCall string-quoted keyword values that were already serialized
  SX — now inlines values starting with "(" directly

Server-affinity annotations for layout/nav shells:
- ~shared:layout/app-body, ~shared:layout/oob-sx — page structure
- ~layouts/nav-sibling-row, ~layouts/nav-children — server-side data
- ~layouts/doc already had :affinity :server
- ~cssx/flush marked :affinity :client (browser-only state)

Navigation fix: restore oob_page_sx wrapper for HTMX responses
so #main-panel section exists for sx-select/sx-swap targeting.

OCaml bridge: lazy page helper injection into kernel via IO proxy
(define name (fn (...) (helper "name" ...))) — enables aser_slot
to evaluate highlight/component-source etc. via coroutine bridge.

Playwright tests: added pageerror listener to test_no_console_errors,
new test_navigate_from_home_to_geography for HTMX nav regression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 12:06:24 +00:00
parent 171c18d3be
commit 109ca7c70b
10 changed files with 201 additions and 31 deletions

View File

@@ -31,7 +31,7 @@ def nav(page: Page, path: str):
"""Navigate to an SX URL and wait for rendered content."""
page.goto(f"{BASE}/sx/{path}", wait_until="networkidle")
# Wait for SX to render — look for any heading or paragraph in main panel
page.wait_for_selector("#main-panel h2, #main-panel p, #main-panel div", timeout=15000)
page.wait_for_selector("#main-panel h2, #main-panel p, #main-panel div", timeout=30000)
# ---------------------------------------------------------------------------
@@ -506,6 +506,37 @@ class TestSpecExplorer:
# Key doc pages (smoke tests)
# ---------------------------------------------------------------------------
class TestHomePage:
def test_home_loads(self, page: Page):
nav(page, "")
expect(page.locator("#main-panel")).to_contain_text("sx", timeout=10000)
def test_no_console_errors(self, page: Page):
"""Home page should have no JS errors (console or uncaught)."""
errors = []
page.on("console", lambda msg: errors.append(msg.text) if msg.type == "error" else None)
page.on("pageerror", lambda err: errors.append(f"UNCAUGHT: {err.message}"))
page.goto(f"{BASE}/sx/", wait_until="networkidle")
page.wait_for_timeout(3000)
fatal = [e for e in errors if "Not callable" in e or "Undefined symbol" in e or "SES_UNCAUGHT" in e or "UNCAUGHT" in e]
assert not fatal, f"JS errors on home page: {fatal}"
def test_navigate_from_home_to_geography(self, page: Page):
"""Click Geography nav link from home — content must render."""
errors = []
page.on("pageerror", lambda err: errors.append(f"UNCAUGHT: {err.message}"))
nav(page, "")
# Click the Geography link in the nav children
geo_link = page.locator("a[sx-push-url]:has-text('Geography')").first
expect(geo_link).to_be_visible(timeout=10000)
geo_link.click()
page.wait_for_timeout(5000)
# Content must still be visible after navigation
expect(page.locator("#main-panel")).to_contain_text("Geography", timeout=10000)
fatal = [e for e in errors if "Not callable" in e or "Undefined symbol" in e or "UNCAUGHT" in e]
assert not fatal, f"JS errors after navigation: {fatal}"
class TestDocPages:
@pytest.mark.parametrize("path,expected", [
("(geography.(reactive))", "Reactive Islands"),