GraphSX URL routing: s-expression URLs for sx-docs
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m50s

Replace path-based URLs with nested s-expression URLs across the sx app.
URLs like /language/docs/introduction become /(language.(doc.introduction)),
making the URL simultaneously a query, render instruction, and address.

- Add sx_router.py: catch-all route evaluator with dot→space conversion,
  auto-quoting slugs, two-phase eval, streaming detection, 301 redirects
- Add page-functions.sx: section + page functions for URL dispatch
- Rewrite nav-data.sx: ~200 hrefs to SX expression format, tree-descent
  nav matching via has-descendant-href? (replaces prefix heuristics)
- Convert ~120 old-style hrefs across 26 .sx content files
- Add SX Protocol proposal (etc/plans/sx-protocol)
- Wire catch-all route in app.py with before_request redirect handler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 09:51:04 +00:00
parent 0cc2f178a9
commit da1ca6009a
32 changed files with 1763 additions and 348 deletions

View File

@@ -32,36 +32,36 @@
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/parser" :class "hover:underline"
:sx-get "/language/specs/parser" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.parser))" :class "hover:underline"
:sx-get "/(language.(spec.parser))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"parser.sx"))
(td :class "px-3 py-2 text-stone-700" "Tokenization and parsing of SX source text into AST"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/evaluator" :class "hover:underline"
:sx-get "/language/specs/evaluator" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.evaluator))" :class "hover:underline"
:sx-get "/(language.(spec.evaluator))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"eval.sx"))
(td :class "px-3 py-2 text-stone-700" "Tree-walking evaluation of SX expressions"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/primitives" :class "hover:underline"
:sx-get "/language/specs/primitives" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.primitives))" :class "hover:underline"
:sx-get "/(language.(spec.primitives))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"primitives.sx"))
(td :class "px-3 py-2 text-stone-700" "All built-in pure functions and their signatures"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/special-forms" :class "hover:underline"
:sx-get "/language/specs/special-forms" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.special-forms))" :class "hover:underline"
:sx-get "/(language.(spec.special-forms))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"special-forms.sx"))
(td :class "px-3 py-2 text-stone-700" "All special forms — syntactic constructs with custom evaluation rules"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/renderer" :class "hover:underline"
:sx-get "/language/specs/renderer" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.renderer))" :class "hover:underline"
:sx-get "/(language.(spec.renderer))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"render.sx"))
(td :class "px-3 py-2 text-stone-700" "Shared rendering registries and utilities used by all adapters"))))))
@@ -79,32 +79,32 @@
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/adapter-dom" :class "hover:underline"
:sx-get "/language/specs/adapter-dom" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.adapter-dom))" :class "hover:underline"
:sx-get "/(language.(spec.adapter-dom))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"adapter-dom.sx"))
(td :class "px-3 py-2 text-stone-700" "Live DOM nodes")
(td :class "px-3 py-2 text-stone-500" "Browser"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/adapter-html" :class "hover:underline"
:sx-get "/language/specs/adapter-html" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.adapter-html))" :class "hover:underline"
:sx-get "/(language.(spec.adapter-html))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"adapter-html.sx"))
(td :class "px-3 py-2 text-stone-700" "HTML strings")
(td :class "px-3 py-2 text-stone-500" "Server"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/adapter-sx" :class "hover:underline"
:sx-get "/language/specs/adapter-sx" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.adapter-sx))" :class "hover:underline"
:sx-get "/(language.(spec.adapter-sx))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"adapter-sx.sx"))
(td :class "px-3 py-2 text-stone-700" "SX wire format")
(td :class "px-3 py-2 text-stone-500" "Server to client"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/adapter-async" :class "hover:underline"
:sx-get "/language/specs/adapter-async" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.adapter-async))" :class "hover:underline"
:sx-get "/(language.(spec.adapter-async))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"adapter-async.sx"))
(td :class "px-3 py-2 text-stone-700" "HTML/SX with awaited I/O")
@@ -122,29 +122,29 @@
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/engine" :class "hover:underline"
:sx-get "/language/specs/engine" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.engine))" :class "hover:underline"
:sx-get "/(language.(spec.engine))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"engine.sx"))
(td :class "px-3 py-2 text-stone-700" "Pure logic — trigger parsing, swap algorithms, morph, history, SSE, indicators"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/orchestration" :class "hover:underline"
:sx-get "/language/specs/orchestration" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.orchestration))" :class "hover:underline"
:sx-get "/(language.(spec.orchestration))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"orchestration.sx"))
(td :class "px-3 py-2 text-stone-700" "Browser wiring — binds engine to DOM events, fetch, request lifecycle"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/boot" :class "hover:underline"
:sx-get "/language/specs/boot" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.boot))" :class "hover:underline"
:sx-get "/(language.(spec.boot))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"boot.sx"))
(td :class "px-3 py-2 text-stone-700" "Browser startup — mount, hydrate, script processing, head hoisting"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/router" :class "hover:underline"
:sx-get "/language/specs/router" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.router))" :class "hover:underline"
:sx-get "/(language.(spec.router))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"router.sx"))
(td :class "px-3 py-2 text-stone-700" "Client-side route matching — Flask-style patterns, param extraction"))))))
@@ -161,8 +161,8 @@
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/signals" :class "hover:underline"
:sx-get "/language/specs/signals" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.signals))" :class "hover:underline"
:sx-get "/(language.(spec.signals))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"signals.sx"))
(td :class "px-3 py-2 text-stone-700" "Signal runtime — signal, deref, reset!, swap!, computed, effect, batch, island scope"))))))
@@ -179,22 +179,22 @@
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/boundary" :class "hover:underline"
:sx-get "/language/specs/boundary" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.boundary))" :class "hover:underline"
:sx-get "/(language.(spec.boundary))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"boundary.sx"))
(td :class "px-3 py-2 text-stone-700" "Language boundary — I/O primitives, page helpers, tier declarations"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/forms" :class "hover:underline"
:sx-get "/language/specs/forms" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.forms))" :class "hover:underline"
:sx-get "/(language.(spec.forms))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"forms.sx"))
(td :class "px-3 py-2 text-stone-700" "Definition forms — defhandler, defquery, defaction, defpage"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/page-helpers" :class "hover:underline"
:sx-get "/language/specs/page-helpers" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.page-helpers))" :class "hover:underline"
:sx-get "/(language.(spec.page-helpers))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"page-helpers.sx"))
(td :class "px-3 py-2 text-stone-700" "Pure data-transformation helpers for page rendering"))))))
@@ -248,29 +248,29 @@ deps.sx depends on: eval")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/continuations" :class "hover:underline"
:sx-get "/language/specs/continuations" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.continuations))" :class "hover:underline"
:sx-get "/(language.(spec.continuations))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"continuations.sx"))
(td :class "px-3 py-2 text-stone-700" "Delimited continuations — shift/reset"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/callcc" :class "hover:underline"
:sx-get "/language/specs/callcc" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.callcc))" :class "hover:underline"
:sx-get "/(language.(spec.callcc))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"callcc.sx"))
(td :class "px-3 py-2 text-stone-700" "Full first-class continuations — call/cc"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/types" :class "hover:underline"
:sx-get "/language/specs/types" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.types))" :class "hover:underline"
:sx-get "/(language.(spec.types))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"types.sx"))
(td :class "px-3 py-2 text-stone-700" "Gradual type system — registration-time checking, zero runtime cost"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/specs/deps" :class "hover:underline"
:sx-get "/language/specs/deps" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(language.(spec.deps))" :class "hover:underline"
:sx-get "/(language.(spec.deps))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"deps.sx"))
(td :class "px-3 py-2 text-stone-700" "Component dependency analysis — bundling, IO detection, CSS scoping"))))))
@@ -337,8 +337,8 @@ deps.sx depends on: eval")))
(div :class "flex items-center gap-3 mb-4"
(span :class "text-sm text-stone-400 font-mono" spec-filename)
(span :class "text-sm text-stone-500 flex-1" spec-desc)
(a :href (str "/language/specs/explore/" (replace spec-filename ".sx" ""))
:sx-get (str "/language/specs/explore/" (replace spec-filename ".sx" ""))
(a :href (str "/(language.(spec.(explore." (replace spec-filename ".sx" "") ")))")
:sx-get (str "/(language.(spec.(explore." (replace spec-filename ".sx" "") ")))")
:sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "text-sm text-violet-600 hover:text-violet-800 font-medium whitespace-nowrap"
@@ -380,21 +380,21 @@ deps.sx depends on: eval")))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "JavaScript")
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/bootstrappers/javascript" :class "hover:underline"
(a :href "/(language.(bootstrapper.javascript))" :class "hover:underline"
"bootstrap_js.py"))
(td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx-browser.js")
(td :class "px-3 py-2 text-green-600" "Live"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Python")
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/bootstrappers/python" :class "hover:underline"
(a :href "/(language.(bootstrapper.python))" :class "hover:underline"
"bootstrap_py.py"))
(td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx_ref.py")
(td :class "px-3 py-2 text-green-600" "Live"))
(tr :class "border-b border-stone-100 bg-green-50"
(td :class "px-3 py-2 text-stone-700" "Python (self-hosting)")
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/language/bootstrappers/self-hosting" :class "hover:underline"
(a :href "/(language.(bootstrapper.self-hosting))" :class "hover:underline"
"py.sx"))
(td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx_ref.py")
(td :class "px-3 py-2 text-green-600" "G0 == G1"))