SX docs: configurable shell, SX-native event handlers, nav fixes

- Configurable page shell (~sx-page-shell kwargs + SX_SHELL app config)
  so each app controls its own assets — sx docs loads only sx-browser.js
- SX-evaluated sx-on:* handlers (eval-expr instead of new Function)
  with DOM primitives registered in PRIMITIVES table
- data-init boot mode for pure SX initialization scripts
- Jiggle animation on links while fetching
- Nav: 3-column grid for centered alignment, is-leaf sizing,
  fix map-indexed param order (index, item), guard mod-by-zero
- Async route eval failure now falls back to server fetch
  instead of silently rendering nothing
- Remove duplicate h1 title from ~doc-page
- Re-bootstrap sx-ref.js + sx-browser.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 11:00:59 +00:00
parent 31a6e708fc
commit 8a5c115557
20 changed files with 5763 additions and 3046 deletions

View File

@@ -132,7 +132,11 @@ def watch_sx_dir(directory: str) -> None:
def reload_if_changed() -> None:
"""Re-read sx files if any have changed on disk. Called per-request in dev."""
changed = False
import logging
import time
_logger = logging.getLogger("sx.reload")
changed_files = []
for directory in _watched_dirs:
for fp in sorted(
glob.glob(os.path.join(directory, "**", "*.sx"), recursive=True)
@@ -140,14 +144,27 @@ def reload_if_changed() -> None:
mtime = os.path.getmtime(fp)
if fp not in _file_mtimes or _file_mtimes[fp] != mtime:
_file_mtimes[fp] = mtime
changed = True
if changed:
changed_files.append(fp)
if changed_files:
for fp in changed_files:
_logger.info("Changed: %s", fp)
t0 = time.monotonic()
_COMPONENT_ENV.clear()
# Reload SX libraries first (e.g. z3.sx) so reader macros resolve
for cb in _reload_callbacks:
cb()
for directory in _watched_dirs:
load_sx_dir(directory)
t1 = time.monotonic()
_logger.info("Reloaded %d file(s), components in %.1fms",
len(changed_files), (t1 - t0) * 1000)
# Recompute render plans for all services that have pages
from .pages import _PAGE_REGISTRY, compute_page_render_plans
for svc in _PAGE_REGISTRY:
t2 = time.monotonic()
compute_page_render_plans(svc)
_logger.info("Render plans for %s in %.1fms", svc, (time.monotonic() - t2) * 1000)
def load_service_components(service_dir: str, service_name: str | None = None) -> None: