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

@@ -114,10 +114,37 @@ def create_app() -> "Quart":
from shared.sx.pages import auto_mount_pages
auto_mount_pages(app, "sx")
# --- GraphSX catch-all route: SX expression URLs ---
from sxc.pages.sx_router import eval_sx_url, redirect_old_url
@app.before_request
async def sx_url_redirect():
"""Redirect old-style paths to SX expression URLs (301)."""
from quart import request, redirect as q_redirect
path = request.path
# Skip non-page paths
if path.startswith(("/static/", "/internal/", "/auth/", "/sx/")):
return None
# Skip SX expression URLs (already in new format)
if path.startswith("/(") or path.startswith("/~"):
return None
# Skip API/handler paths
if "/api/" in path:
return None
new_url = redirect_old_url(path)
if new_url:
qs = request.query_string.decode()
if qs:
new_url += "?" + qs
return q_redirect(new_url, 301)
@app.before_request
async def trailing_slash_redirect():
from quart import request, redirect
path = request.path
# Skip SX expression URLs — they don't use trailing slashes
if "(" in path or path.startswith("/~"):
return None
if (path != "/"
and not path.endswith("/")
and request.method == "GET"
@@ -128,6 +155,15 @@ def create_app() -> "Quart":
target = path + "/" + ("?" + qs if qs else "")
return redirect(target, 301)
@app.get("/<path:expr>")
async def sx_eval_route(expr):
"""Catch-all: evaluate SX expression URLs."""
result = await eval_sx_url(f"/{expr}")
if result is None:
from quart import abort
abort(404)
return result
@app.errorhandler(404)
async def sx_not_found(e):
from quart import request, make_response

View File

@@ -12,7 +12,7 @@
"Each bar shows how many of the "
(strong (str total-components))
" total components a page actually needs, computed by the "
(a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
(a :href "/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
" transitive closure algorithm. "
"Click a page to see its component tree; expand a component to see its SX source.")

View File

@@ -373,7 +373,7 @@
"directly. They arrive as part of the rendered HTML — keyframes, custom properties, "
"scoped rules, anything.")
(li (strong "External stylesheets: ") "Components can reference pre-loaded CSS files "
"or lazy-load them via " (code "~suspense") " (see " (a :href "/applications/cssx/async" "Async CSS") ").")
"or lazy-load them via " (code "~suspense") " (see " (a :href "/(applications.(cssx.async))" "Async CSS") ").")
(li (strong "Custom properties: ") "A " (code "~theme") " component sets "
(code "--color-primary") " etc. via a " (code "<style>") " block. Everything "
"using " (code "var()") " repaints automatically."))

View File

@@ -79,7 +79,7 @@
(p :class "text-stone-600"
"Special forms are syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface.")
(p :class "text-stone-600"
"Forms marked with a tail position enable " (a :href "/etc/essays/tco" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.")
"Forms marked with a tail position enable " (a :href "/(etc.(essay.tail-call-optimization))" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.")
(div :class "space-y-10" forms))))
(defcomp ~docs-server-rendering-content ()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@
(p :class "text-stone-600"
"The property these systems lack has a name: " (a :href "https://en.wikipedia.org/wiki/Reflection_(computer_programming)" :class "text-violet-600 hover:underline" "reflexivity") ". A reflexive system can represent itself, reason about its own structure, and modify itself based on that reasoning. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " has had this property " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" :class "text-violet-600 hover:underline" "since 1958") ". The web has never had it.")
(p :class "text-stone-600"
"SX is a complete Lisp. It has " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " — code is data, data is code. It has a " (a :href "/language/specs/core" :class "text-violet-600 hover:underline" "self-hosting specification") " — SX defined in SX. It has " (code "eval") " and " (code "quote") " and " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" :class "text-violet-600 hover:underline" "macros") ". And it runs on the wire — the format that travels between server and client IS the language. This combination has consequences."))
"SX is a complete Lisp. It has " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " — code is data, data is code. It has a " (a :href "/(language.(spec.core))" :class "text-violet-600 hover:underline" "self-hosting specification") " — SX defined in SX. It has " (code "eval") " and " (code "quote") " and " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" :class "text-violet-600 hover:underline" "macros") ". And it runs on the wire — the format that travels between server and client IS the language. This combination has consequences."))
(~doc-section :title "What homoiconicity changes" :id "homoiconicity"
(p :class "text-stone-600"
@@ -27,7 +27,7 @@
(p :class "text-stone-600"
"In an SX web, the AI reads the same s-expressions the browser reads. The component definitions " (em "are") " the documentation — a " (code "defcomp") " declares its parameters, its structure, and its semantics in one expression. There is no " (a :href "https://en.wikipedia.org/wiki/OpenAPI_Specification" :class "text-violet-600 hover:underline" "Swagger spec") " describing an API. The API " (em "is") " the language, and the language is self-describing.")
(p :class "text-stone-600"
"An AI that understands SX understands the " (a :href "/language/specs/core" :class "text-violet-600 hover:underline" "spec") ". And the spec is written in SX. So the AI understands the definition of the language it is using, in the language it is using. This " (a :href "https://en.wikipedia.org/wiki/Reflexivity_(social_theory)" :class "text-violet-600 hover:underline" "reflexive") " property means the AI does not need a separate mental model for \"the web\" and \"the language\" — they are the same thing."))
"An AI that understands SX understands the " (a :href "/(language.(spec.core))" :class "text-violet-600 hover:underline" "spec") ". And the spec is written in SX. So the AI understands the definition of the language it is using, in the language it is using. This " (a :href "https://en.wikipedia.org/wiki/Reflexivity_(social_theory)" :class "text-violet-600 hover:underline" "reflexive") " property means the AI does not need a separate mental model for \"the web\" and \"the language\" — they are the same thing."))
(~doc-section :title "Live system modification" :id "live-modification"
(p :class "text-stone-600"
@@ -51,7 +51,7 @@
(p :class "text-stone-600"
"A macro is a function that takes code and returns code. An AI generating macros is writing programs that write programs. With " (code "eval") ", those generated programs can generate more programs at runtime. This is not a metaphor — it is the literal mechanism.")
(p :class "text-stone-600"
"The " (a :href "/etc/philosophy/godel-escher-bach" :class "text-violet-600 hover:underline" "Gödel numbering") " parallel is not incidental. " (a :href "https://en.wikipedia.org/wiki/Kurt_G%C3%B6del" :class "text-violet-600 hover:underline" "Gödel") " showed that any sufficiently powerful formal system can encode statements about itself. A complete Lisp on the wire is a sufficiently powerful formal system. The web can make statements about itself — components that inspect other components, macros that rewrite the page structure, expressions that generate new expressions based on the current state of the system.")
"The " (a :href "/(etc.(philosophy.godel-escher-bach))" :class "text-violet-600 hover:underline" "Gödel numbering") " parallel is not incidental. " (a :href "https://en.wikipedia.org/wiki/Kurt_G%C3%B6del" :class "text-violet-600 hover:underline" "Gödel") " showed that any sufficiently powerful formal system can encode statements about itself. A complete Lisp on the wire is a sufficiently powerful formal system. The web can make statements about itself — components that inspect other components, macros that rewrite the page structure, expressions that generate new expressions based on the current state of the system.")
(p :class "text-stone-600"
"Consider what this enables for AI:")
(ul :class "space-y-2 text-stone-600 mt-2"
@@ -63,7 +63,7 @@
(p :class "text-stone-600"
"The same " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " that makes this powerful makes it dangerous. Code-as-data means an AI can inject " (em "behaviour") ", not just content. A malicious expression evaluated in the wrong context could exfiltrate data, modify other components, or disrupt the system.")
(p :class "text-stone-600"
"This is why the " (a :href "/language/specs/primitives" :class "text-violet-600 hover:underline" "primitive set") " is the critical security boundary. The spec defines exactly which operations are available. A sandboxed evaluator that only exposes pure primitives (arithmetic, string operations, list manipulation) cannot perform I/O. Cannot access the network. Cannot modify the DOM outside its designated target. The language is " (a :href "https://en.wikipedia.org/wiki/Turing_completeness" :class "text-violet-600 hover:underline" "Turing-complete") " within the sandbox and powerless outside it.")
"This is why the " (a :href "/(language.(spec.primitives))" :class "text-violet-600 hover:underline" "primitive set") " is the critical security boundary. The spec defines exactly which operations are available. A sandboxed evaluator that only exposes pure primitives (arithmetic, string operations, list manipulation) cannot perform I/O. Cannot access the network. Cannot modify the DOM outside its designated target. The language is " (a :href "https://en.wikipedia.org/wiki/Turing_completeness" :class "text-violet-600 hover:underline" "Turing-complete") " within the sandbox and powerless outside it.")
(p :class "text-stone-600"
"Different contexts grant different primitive sets. A component evaluated in a page slot gets rendering primitives. A macro gets code-transformation primitives. A federated expression from an untrusted node gets the minimal safe set. The sandbox is not bolted on — it is inherent in the language's architecture. What you can do depends on which primitives are in scope.")
(p :class "text-stone-600"

View File

@@ -36,7 +36,7 @@
(p :class "text-stone-600"
"This makes it trivial to build AI pipelines that generate SX. Parse the output. If it parses, evaluate it in a sandbox. If it does not parse, the error is always the same kind — unmatched parentheses — and the fix is always mechanical. There is no \"your JSX is invalid because you used " (code "class") " instead of " (code "className") "\" or \"you forgot the semicolon after the type annotation but before the generic constraint.\"")
(p :class "text-stone-600"
"Beyond parsing, the SX " (a :href "/language/specs/primitives" :class "text-violet-600 hover:underline" "boundary system") " provides semantic validation. A pure component cannot call IO primitives — not by convention, but by the evaluator refusing to resolve them. An AI generating a component can produce whatever expressions it wants; the sandbox ensures only permitted operations execute. Validation is not a separate step bolted onto the pipeline. It is the language."))
"Beyond parsing, the SX " (a :href "/(language.(spec.primitives))" :class "text-violet-600 hover:underline" "boundary system") " provides semantic validation. A pure component cannot call IO primitives — not by convention, but by the evaluator refusing to resolve them. An AI generating a component can produce whatever expressions it wants; the sandbox ensures only permitted operations execute. Validation is not a separate step bolted onto the pipeline. It is the language."))
(~doc-section :title "Components are self-documenting" :id "self-documenting"
(p :class "text-stone-600"
@@ -98,7 +98,7 @@
(p :class "text-stone-600"
"This is only possible because the representation is uniform. The AI does not need to switch between \"writing HTML mode\" and \"writing CSS mode\" and \"writing JavaScript mode\" and \"writing deployment config mode.\" It is always writing s-expressions. The cognitive load is constant. The error rate is constant. The speed is constant — regardless of whether it is generating a page layout, a macro expander, or a Docker healthcheck.")
(p :class "text-stone-600"
"The " (a :href "/etc/essays/sx-sucks" :class "text-violet-600 hover:underline" "sx sucks") " essay copped to the AI authorship and framed it as a weakness — microwave dinner on a nice plate. But the framing was wrong. If a language is so well-suited to machine generation that one person with no Lisp experience can build a self-hosting language, a multi-target bootstrapper, a reactive component framework, and a full documentation site through pure agentic AI — that is not a weakness of the language. That is the language working exactly as it should."))
"The " (a :href "/(etc.(essay.sx-sucks))" :class "text-violet-600 hover:underline" "sx sucks") " essay copped to the AI authorship and framed it as a weakness — microwave dinner on a nice plate. But the framing was wrong. If a language is so well-suited to machine generation that one person with no Lisp experience can build a self-hosting language, a multi-target bootstrapper, a reactive component framework, and a full documentation site through pure agentic AI — that is not a weakness of the language. That is the language working exactly as it should."))
(~doc-section :title "What this changes" :id "what-changes"
(p :class "text-stone-600"

File diff suppressed because one or more lines are too long

View File

@@ -127,7 +127,7 @@
(p :class "text-stone-600"
"Every page you are reading was produced through conversation with an agentic AI. The SX evaluator — a self-hosting interpreter with tail-call optimization, delimited continuations, macro expansion, and three rendering backends — was developed without opening a code editor. The specification files that define the language were written without an IDE. The bootstrappers that compile the spec to JavaScript and Python were produced without syntax highlighting or autocomplete. The test suite — hundreds of tests across evaluator, parser, renderer, router, dependency analyzer, and engine — was written without a test runner GUI. This documentation site — with its navigation, its code examples, its live demos — was built without a web development framework's CLI.")
(p :class "text-stone-600"
"The developer sat in a terminal. They described what they wanted. The AI produced the code. When something was wrong, they described what was wrong. The AI fixed it. When something needed to change, they described the change. The AI made the change. Across thousands of files, tens of thousands of lines of code, and months of development. Even the jokes — the " (a :href "/etc/essays/sx-sucks" :class "text-violet-600 hover:underline" "self-deprecating essay") " about everything wrong with SX, the deadpan tone of the documentation, the essay you are reading right now — all produced through conversation, not through typing.")
"The developer sat in a terminal. They described what they wanted. The AI produced the code. When something was wrong, they described what was wrong. The AI fixed it. When something needed to change, they described the change. The AI made the change. Across thousands of files, tens of thousands of lines of code, and months of development. Even the jokes — the " (a :href "/(etc.(essay.sx-sucks))" :class "text-violet-600 hover:underline" "self-deprecating essay") " about everything wrong with SX, the deadpan tone of the documentation, the essay you are reading right now — all produced through conversation, not through typing.")
(p :class "text-stone-600"
"No build step. No bundler. No transpiler. No package manager. No CSS preprocessor. No dev server. No linter. No formatter. No type checker. No framework CLI. No code editor.")
(p :class "text-stone-600"

View File

@@ -32,26 +32,26 @@
(p :class "text-stone-600 mb-6"
"Complete reference for the sx client library.")
(div :class "grid gap-4 sm:grid-cols-2"
(a :href "/geography/hypermedia/reference/attributes"
:sx-get "/geography/hypermedia/reference/attributes" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(geography.(hypermedia.(reference.attributes)))"
:sx-get "/(geography.(hypermedia.(reference.attributes)))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Attributes")
(p :class "text-stone-600 text-sm" "All sx attributes — request verbs, behavior modifiers, and sx-unique features."))
(a :href "/geography/hypermedia/reference/headers"
:sx-get "/geography/hypermedia/reference/headers" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(geography.(hypermedia.(reference.headers)))"
:sx-get "/(geography.(hypermedia.(reference.headers)))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Headers")
(p :class "text-stone-600 text-sm" "Custom HTTP headers used to coordinate between the sx client and server."))
(a :href "/geography/hypermedia/reference/events"
:sx-get "/geography/hypermedia/reference/events" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(geography.(hypermedia.(reference.events)))"
:sx-get "/(geography.(hypermedia.(reference.events)))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Events")
(p :class "text-stone-600 text-sm" "DOM events fired during the sx request lifecycle."))
(a :href "/geography/hypermedia/reference/js-api"
:sx-get "/geography/hypermedia/reference/js-api" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/(geography.(hypermedia.(reference.js-api)))"
:sx-get "/(geography.(hypermedia.(reference.js-api)))" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "JS API")

View File

@@ -3,242 +3,244 @@
;; @css aria-selected:bg-violet-200 aria-selected:text-violet-900
(define docs-nav-items (list
(dict :label "Introduction" :href "/language/docs/introduction")
(dict :label "Getting Started" :href "/language/docs/getting-started")
(dict :label "Components" :href "/language/docs/components")
(dict :label "Evaluator" :href "/language/docs/evaluator")
(dict :label "Primitives" :href "/language/docs/primitives")
(dict :label "Special Forms" :href "/language/docs/special-forms")
(dict :label "Server Rendering" :href "/language/docs/server-rendering")))
(dict :label "Introduction" :href "/(language.(doc.introduction))")
(dict :label "Getting Started" :href "/(language.(doc.getting-started))")
(dict :label "Components" :href "/(language.(doc.components))")
(dict :label "Evaluator" :href "/(language.(doc.evaluator))")
(dict :label "Primitives" :href "/(language.(doc.primitives))")
(dict :label "Special Forms" :href "/(language.(doc.special-forms))")
(dict :label "Server Rendering" :href "/(language.(doc.server-rendering))")))
(define reference-nav-items (list
(dict :label "Attributes" :href "/geography/hypermedia/reference/attributes")
(dict :label "Headers" :href "/geography/hypermedia/reference/headers")
(dict :label "Events" :href "/geography/hypermedia/reference/events")
(dict :label "JS API" :href "/geography/hypermedia/reference/js-api")))
(dict :label "Attributes" :href "/(geography.(hypermedia.(reference.attributes)))")
(dict :label "Headers" :href "/(geography.(hypermedia.(reference.headers)))")
(dict :label "Events" :href "/(geography.(hypermedia.(reference.events)))")
(dict :label "JS API" :href "/(geography.(hypermedia.(reference.js-api)))")))
(define protocols-nav-items (list
(dict :label "Wire Format" :href "/applications/protocols/wire-format")
(dict :label "Fragments" :href "/applications/protocols/fragments")
(dict :label "Resolver I/O" :href "/applications/protocols/resolver-io")
(dict :label "Internal Services" :href "/applications/protocols/internal-services")
(dict :label "ActivityPub" :href "/applications/protocols/activitypub")
(dict :label "Future" :href "/applications/protocols/future")))
(dict :label "Wire Format" :href "/(applications.(protocol.wire-format))")
(dict :label "Fragments" :href "/(applications.(protocol.fragments))")
(dict :label "Resolver I/O" :href "/(applications.(protocol.resolver-io))")
(dict :label "Internal Services" :href "/(applications.(protocol.internal-services))")
(dict :label "ActivityPub" :href "/(applications.(protocol.activitypub))")
(dict :label "Future" :href "/(applications.(protocol.future))")))
(define examples-nav-items (list
(dict :label "Click to Load" :href "/geography/hypermedia/examples/click-to-load")
(dict :label "Form Submission" :href "/geography/hypermedia/examples/form-submission")
(dict :label "Polling" :href "/geography/hypermedia/examples/polling")
(dict :label "Delete Row" :href "/geography/hypermedia/examples/delete-row")
(dict :label "Inline Edit" :href "/geography/hypermedia/examples/inline-edit")
(dict :label "OOB Swaps" :href "/geography/hypermedia/examples/oob-swaps")
(dict :label "Lazy Loading" :href "/geography/hypermedia/examples/lazy-loading")
(dict :label "Infinite Scroll" :href "/geography/hypermedia/examples/infinite-scroll")
(dict :label "Progress Bar" :href "/geography/hypermedia/examples/progress-bar")
(dict :label "Active Search" :href "/geography/hypermedia/examples/active-search")
(dict :label "Inline Validation" :href "/geography/hypermedia/examples/inline-validation")
(dict :label "Value Select" :href "/geography/hypermedia/examples/value-select")
(dict :label "Reset on Submit" :href "/geography/hypermedia/examples/reset-on-submit")
(dict :label "Edit Row" :href "/geography/hypermedia/examples/edit-row")
(dict :label "Bulk Update" :href "/geography/hypermedia/examples/bulk-update")
(dict :label "Swap Positions" :href "/geography/hypermedia/examples/swap-positions")
(dict :label "Select Filter" :href "/geography/hypermedia/examples/select-filter")
(dict :label "Tabs" :href "/geography/hypermedia/examples/tabs")
(dict :label "Animations" :href "/geography/hypermedia/examples/animations")
(dict :label "Dialogs" :href "/geography/hypermedia/examples/dialogs")
(dict :label "Keyboard Shortcuts" :href "/geography/hypermedia/examples/keyboard-shortcuts")
(dict :label "PUT / PATCH" :href "/geography/hypermedia/examples/put-patch")
(dict :label "JSON Encoding" :href "/geography/hypermedia/examples/json-encoding")
(dict :label "Vals & Headers" :href "/geography/hypermedia/examples/vals-and-headers")
(dict :label "Loading States" :href "/geography/hypermedia/examples/loading-states")
(dict :label "Request Abort" :href "/geography/hypermedia/examples/sync-replace")
(dict :label "Retry" :href "/geography/hypermedia/examples/retry")))
(dict :label "Click to Load" :href "/(geography.(hypermedia.(example.click-to-load)))")
(dict :label "Form Submission" :href "/(geography.(hypermedia.(example.form-submission)))")
(dict :label "Polling" :href "/(geography.(hypermedia.(example.polling)))")
(dict :label "Delete Row" :href "/(geography.(hypermedia.(example.delete-row)))")
(dict :label "Inline Edit" :href "/(geography.(hypermedia.(example.inline-edit)))")
(dict :label "OOB Swaps" :href "/(geography.(hypermedia.(example.oob-swaps)))")
(dict :label "Lazy Loading" :href "/(geography.(hypermedia.(example.lazy-loading)))")
(dict :label "Infinite Scroll" :href "/(geography.(hypermedia.(example.infinite-scroll)))")
(dict :label "Progress Bar" :href "/(geography.(hypermedia.(example.progress-bar)))")
(dict :label "Active Search" :href "/(geography.(hypermedia.(example.active-search)))")
(dict :label "Inline Validation" :href "/(geography.(hypermedia.(example.inline-validation)))")
(dict :label "Value Select" :href "/(geography.(hypermedia.(example.value-select)))")
(dict :label "Reset on Submit" :href "/(geography.(hypermedia.(example.reset-on-submit)))")
(dict :label "Edit Row" :href "/(geography.(hypermedia.(example.edit-row)))")
(dict :label "Bulk Update" :href "/(geography.(hypermedia.(example.bulk-update)))")
(dict :label "Swap Positions" :href "/(geography.(hypermedia.(example.swap-positions)))")
(dict :label "Select Filter" :href "/(geography.(hypermedia.(example.select-filter)))")
(dict :label "Tabs" :href "/(geography.(hypermedia.(example.tabs)))")
(dict :label "Animations" :href "/(geography.(hypermedia.(example.animations)))")
(dict :label "Dialogs" :href "/(geography.(hypermedia.(example.dialogs)))")
(dict :label "Keyboard Shortcuts" :href "/(geography.(hypermedia.(example.keyboard-shortcuts)))")
(dict :label "PUT / PATCH" :href "/(geography.(hypermedia.(example.put-patch)))")
(dict :label "JSON Encoding" :href "/(geography.(hypermedia.(example.json-encoding)))")
(dict :label "Vals & Headers" :href "/(geography.(hypermedia.(example.vals-and-headers)))")
(dict :label "Loading States" :href "/(geography.(hypermedia.(example.loading-states)))")
(dict :label "Request Abort" :href "/(geography.(hypermedia.(example.sync-replace)))")
(dict :label "Retry" :href "/(geography.(hypermedia.(example.retry)))")))
(define cssx-nav-items (list
(dict :label "Overview" :href "/applications/cssx/")
(dict :label "Patterns" :href "/applications/cssx/patterns")
(dict :label "Delivery" :href "/applications/cssx/delivery")
(dict :label "Async CSS" :href "/applications/cssx/async")
(dict :label "Live Styles" :href "/applications/cssx/live")
(dict :label "Comparisons" :href "/applications/cssx/comparisons")
(dict :label "Philosophy" :href "/applications/cssx/philosophy")))
(dict :label "Overview" :href "/(applications.(cssx))")
(dict :label "Patterns" :href "/(applications.(cssx.patterns))")
(dict :label "Delivery" :href "/(applications.(cssx.delivery))")
(dict :label "Async CSS" :href "/(applications.(cssx.async))")
(dict :label "Live Styles" :href "/(applications.(cssx.live))")
(dict :label "Comparisons" :href "/(applications.(cssx.comparisons))")
(dict :label "Philosophy" :href "/(applications.(cssx.philosophy))")))
(define essays-nav-items (list
(dict :label "Why S-Expressions" :href "/etc/essays/why-sexps"
(dict :label "Why S-Expressions" :href "/(etc.(essay.why-sexps))"
:summary "Why SX uses s-expressions instead of HTML templates, JSX, or any other syntax.")
(dict :label "The htmx/React Hybrid" :href "/etc/essays/htmx-react-hybrid"
(dict :label "The htmx/React Hybrid" :href "/(etc.(essay.htmx-react-hybrid))"
:summary "How SX combines the server-driven simplicity of htmx with the component model of React.")
(dict :label "On-Demand CSS" :href "/etc/essays/on-demand-css"
(dict :label "On-Demand CSS" :href "/(etc.(essay.on-demand-css))"
:summary "How SX delivers only the CSS each page needs — server scans rendered classes, sends the delta.")
(dict :label "Client Reactivity" :href "/etc/essays/client-reactivity"
(dict :label "Client Reactivity" :href "/(etc.(essay.client-reactivity))"
:summary "Reactive UI updates without a virtual DOM, diffing library, or build step.")
(dict :label "SX Native" :href "/etc/essays/sx-native"
(dict :label "SX Native" :href "/(etc.(essay.sx-native))"
:summary "Extending SX beyond the browser — native desktop and mobile rendering from the same source.")
(dict :label "Tail-Call Optimization" :href "/etc/essays/tail-call-optimization"
(dict :label "Tail-Call Optimization" :href "/(etc.(essay.tail-call-optimization))"
:summary "How SX implements proper tail calls via trampolining in a language that doesn't have them.")
(dict :label "Continuations" :href "/etc/essays/continuations"
(dict :label "Continuations" :href "/(etc.(essay.continuations))"
:summary "First-class continuations in a tree-walking evaluator — theory and implementation.")
(dict :label "The Reflexive Web" :href "/etc/essays/reflexive-web"
(dict :label "The Reflexive Web" :href "/(etc.(essay.reflexive-web))"
:summary "A web where pages can inspect, modify, and extend their own rendering pipeline.")
(dict :label "Server Architecture" :href "/etc/essays/server-architecture"
(dict :label "Server Architecture" :href "/(etc.(essay.server-architecture))"
:summary "How SX enforces the boundary between host and embedded language, and what it looks like across targets.")
(dict :label "Separate your Own Concerns" :href "/etc/essays/separation-of-concerns"
(dict :label "Separate your Own Concerns" :href "/(etc.(essay.separation-of-concerns))"
:summary "The web's HTML/CSS/JS split separates the framework's concerns, not your application's. Real separation is domain-specific.")
(dict :label "SX and AI" :href "/etc/essays/sx-and-ai"
(dict :label "SX and AI" :href "/(etc.(essay.sx-and-ai))"
:summary "Why s-expressions are the most AI-friendly representation for web interfaces.")
(dict :label "There Is No Alternative" :href "/etc/essays/no-alternative"
(dict :label "There Is No Alternative" :href "/(etc.(essay.no-alternative))"
:summary "Every attempt to escape s-expressions leads back to s-expressions. This is not an accident.")
(dict :label "sx sucks" :href "/etc/essays/sx-sucks"
(dict :label "sx sucks" :href "/(etc.(essay.sx-sucks))"
:summary "An honest accounting of everything wrong with SX and why you probably shouldn't use it.")
(dict :label "Tools for Fools" :href "/etc/essays/zero-tooling"
(dict :label "Tools for Fools" :href "/(etc.(essay.zero-tooling))"
:summary "SX was built without a code editor. No IDE, no build tools, no linters, no bundlers. What zero-tooling web development looks like.")
(dict :label "React is Hypermedia" :href "/etc/essays/react-is-hypermedia"
(dict :label "React is Hypermedia" :href "/(etc.(essay.react-is-hypermedia))"
:summary "A React Island is a hypermedia control. Its behavior is specified in SX.")
(dict :label "The Hegelian Synthesis" :href "/etc/essays/hegelian-synthesis"
(dict :label "The Hegelian Synthesis" :href "/(etc.(essay.hegelian-synthesis))"
:summary "On the dialectical resolution of the hypertext/reactive contradiction. Thesis: the server renders. Antithesis: the client reacts. Synthesis: the island in the lake.")
(dict :label "The Art Chain" :href "/etc/essays/the-art-chain"
(dict :label "The Art Chain" :href "/(etc.(essay.the-art-chain))"
:summary "On making, self-making, and the chain of artifacts that produces itself. Ars, techne, content addressing, and why the spec is the art.")))
(define philosophy-nav-items (list
(dict :label "The SX Manifesto" :href "/etc/philosophy/sx-manifesto"
(dict :label "The SX Manifesto" :href "/(etc.(philosophy.sx-manifesto))"
:summary "The design principles behind SX: simplicity, self-hosting, and s-expressions all the way down.")
(dict :label "Strange Loops" :href "/etc/philosophy/godel-escher-bach"
(dict :label "Strange Loops" :href "/(etc.(philosophy.godel-escher-bach))"
:summary "Self-reference, and the tangled hierarchy of a language that defines itself.")
(dict :label "SX and Wittgenstein" :href "/etc/philosophy/wittgenstein"
(dict :label "SX and Wittgenstein" :href "/(etc.(philosophy.wittgenstein))"
:summary "The limits of my language are the limits of my world — Wittgenstein's philosophy and what it means for SX.")
(dict :label "SX and Dennett" :href "/etc/philosophy/dennett"
(dict :label "SX and Dennett" :href "/(etc.(philosophy.dennett))"
:summary "Real patterns, intentional stance, and multiple drafts — Dennett's philosophy of mind as a framework for understanding SX.")
(dict :label "S-Existentialism" :href "/etc/philosophy/existentialism"
(dict :label "S-Existentialism" :href "/(etc.(philosophy.existentialism))"
:summary "Existence precedes essence — Sartre, Camus, and the absurd freedom of writing a Lisp for the web.")))
(define specs-nav-items (list
{:label "Core" :href "/language/specs/core" :children (list
{:label "Parser" :href "/language/specs/parser"}
{:label "Evaluator" :href "/language/specs/evaluator"}
{:label "Primitives" :href "/language/specs/primitives"}
{:label "Special Forms" :href "/language/specs/special-forms"}
{:label "Renderer" :href "/language/specs/renderer"})}
{:label "Adapters" :href "/language/specs/adapters" :children (list
{:label "DOM Adapter" :href "/language/specs/adapter-dom"}
{:label "HTML Adapter" :href "/language/specs/adapter-html"}
{:label "SX Wire Adapter" :href "/language/specs/adapter-sx"}
{:label "Async Adapter" :href "/language/specs/adapter-async"})}
{:label "Browser" :href "/language/specs/browser" :children (list
{:label "SxEngine" :href "/language/specs/engine"}
{:label "Orchestration" :href "/language/specs/orchestration"}
{:label "Boot" :href "/language/specs/boot"}
{:label "Router" :href "/language/specs/router"})}
{:label "Reactive" :href "/language/specs/reactive" :children (list
{:label "Signals" :href "/language/specs/signals"})}
{:label "Host Interface" :href "/language/specs/host" :children (list
{:label "Boundary" :href "/language/specs/boundary"}
{:label "Forms" :href "/language/specs/forms"}
{:label "Page Helpers" :href "/language/specs/page-helpers"})}
{:label "Extensions" :href "/language/specs/extensions" :children (list
{:label "Continuations" :href "/language/specs/continuations"}
{:label "call/cc" :href "/language/specs/callcc"}
{:label "Types" :href "/language/specs/types"}
{:label "Deps" :href "/language/specs/deps"})}))
{:label "Core" :href "/(language.(spec.core))" :children (list
{:label "Parser" :href "/(language.(spec.parser))"}
{:label "Evaluator" :href "/(language.(spec.evaluator))"}
{:label "Primitives" :href "/(language.(spec.primitives))"}
{:label "Special Forms" :href "/(language.(spec.special-forms))"}
{:label "Renderer" :href "/(language.(spec.renderer))"})}
{:label "Adapters" :href "/(language.(spec.adapters))" :children (list
{:label "DOM Adapter" :href "/(language.(spec.adapter-dom))"}
{:label "HTML Adapter" :href "/(language.(spec.adapter-html))"}
{:label "SX Wire Adapter" :href "/(language.(spec.adapter-sx))"}
{:label "Async Adapter" :href "/(language.(spec.adapter-async))"})}
{:label "Browser" :href "/(language.(spec.browser))" :children (list
{:label "SxEngine" :href "/(language.(spec.engine))"}
{:label "Orchestration" :href "/(language.(spec.orchestration))"}
{:label "Boot" :href "/(language.(spec.boot))"}
{:label "Router" :href "/(language.(spec.router))"})}
{:label "Reactive" :href "/(language.(spec.reactive))" :children (list
{:label "Signals" :href "/(language.(spec.signals))"})}
{:label "Host Interface" :href "/(language.(spec.host))" :children (list
{:label "Boundary" :href "/(language.(spec.boundary))"}
{:label "Forms" :href "/(language.(spec.forms))"}
{:label "Page Helpers" :href "/(language.(spec.page-helpers))"})}
{:label "Extensions" :href "/(language.(spec.extensions))" :children (list
{:label "Continuations" :href "/(language.(spec.continuations))"}
{:label "call/cc" :href "/(language.(spec.callcc))"}
{:label "Types" :href "/(language.(spec.types))"}
{:label "Deps" :href "/(language.(spec.deps))"})}))
(define testing-nav-items (list
(dict :label "Overview" :href "/language/testing/")
(dict :label "Evaluator" :href "/language/testing/eval")
(dict :label "Parser" :href "/language/testing/parser")
(dict :label "Router" :href "/language/testing/router")
(dict :label "Renderer" :href "/language/testing/render")
(dict :label "Dependencies" :href "/language/testing/deps")
(dict :label "Engine" :href "/language/testing/engine")
(dict :label "Orchestration" :href "/language/testing/orchestration")
(dict :label "Runners" :href "/language/testing/runners")))
(dict :label "Overview" :href "/(language.(test))")
(dict :label "Evaluator" :href "/(language.(test.eval))")
(dict :label "Parser" :href "/(language.(test.parser))")
(dict :label "Router" :href "/(language.(test.router))")
(dict :label "Renderer" :href "/(language.(test.render))")
(dict :label "Dependencies" :href "/(language.(test.deps))")
(dict :label "Engine" :href "/(language.(test.engine))")
(dict :label "Orchestration" :href "/(language.(test.orchestration))")
(dict :label "Runners" :href "/(language.(test.runners))")))
(define isomorphism-nav-items (list
(dict :label "Roadmap" :href "/geography/isomorphism/")
(dict :label "Bundle Analyzer" :href "/geography/isomorphism/bundle-analyzer")
(dict :label "Routing Analyzer" :href "/geography/isomorphism/routing-analyzer")
(dict :label "Data Test" :href "/geography/isomorphism/data-test")
(dict :label "Async IO" :href "/geography/isomorphism/async-io")
(dict :label "Streaming" :href "/geography/isomorphism/streaming")
(dict :label "Affinity" :href "/geography/isomorphism/affinity")
(dict :label "Optimistic" :href "/geography/isomorphism/optimistic")
(dict :label "Offline" :href "/geography/isomorphism/offline")))
(dict :label "Roadmap" :href "/(geography.(isomorphism))")
(dict :label "Bundle Analyzer" :href "/(geography.(isomorphism.bundle-analyzer))")
(dict :label "Routing Analyzer" :href "/(geography.(isomorphism.routing-analyzer))")
(dict :label "Data Test" :href "/(geography.(isomorphism.data-test))")
(dict :label "Async IO" :href "/(geography.(isomorphism.async-io))")
(dict :label "Streaming" :href "/(geography.(isomorphism.streaming))")
(dict :label "Affinity" :href "/(geography.(isomorphism.affinity))")
(dict :label "Optimistic" :href "/(geography.(isomorphism.optimistic))")
(dict :label "Offline" :href "/(geography.(isomorphism.offline))")))
(define plans-nav-items (list
(dict :label "Status" :href "/etc/plans/status"
(dict :label "Status" :href "/(etc.(plan.status))"
:summary "Audit of all plans — what's done, what's in progress, and what remains.")
(dict :label "Reader Macros" :href "/etc/plans/reader-macros"
(dict :label "Reader Macros" :href "/(etc.(plan.reader-macros))"
:summary "Extensible parse-time transformations via # dispatch — datum comments, raw strings, and quote shorthand.")
(dict :label "Reader Macro Demo" :href "/etc/plans/reader-macro-demo"
(dict :label "Reader Macro Demo" :href "/(etc.(plan.reader-macro-demo))"
:summary "Live demo: #z3 translates SX spec declarations to SMT-LIB verification conditions.")
(dict :label "Theorem Prover" :href "/etc/plans/theorem-prover"
(dict :label "Theorem Prover" :href "/(etc.(plan.theorem-prover))"
:summary "prove.sx — constraint solver and property prover for SX primitives, written in SX.")
(dict :label "Self-Hosting Bootstrapper" :href "/etc/plans/self-hosting-bootstrapper"
(dict :label "Self-Hosting Bootstrapper" :href "/(etc.(plan.self-hosting-bootstrapper))"
:summary "py.sx — an SX-to-Python translator written in SX. Complete: G0 == G1, 128/128 defines match.")
(dict :label "JS Bootstrapper" :href "/etc/plans/js-bootstrapper"
(dict :label "JS Bootstrapper" :href "/(etc.(plan.js-bootstrapper))"
:summary "js.sx — SX-to-JavaScript translator + ahead-of-time component compiler. Zero-runtime static sites.")
(dict :label "SX-Activity" :href "/etc/plans/sx-activity"
(dict :label "SX-Activity" :href "/(etc.(plan.sx-activity))"
:summary "A new web built on SX — executable content, shared components, parsers, and logic on IPFS, provenance on Bitcoin, all running within your own security context.")
(dict :label "Predictive Prefetching" :href "/etc/plans/predictive-prefetch"
(dict :label "Predictive Prefetching" :href "/(etc.(plan.predictive-prefetch))"
:summary "Prefetch missing component definitions before the user clicks — hover a link, fetch its deps, navigate client-side.")
(dict :label "Content-Addressed Components" :href "/etc/plans/content-addressed-components"
(dict :label "Content-Addressed Components" :href "/(etc.(plan.content-addressed-components))"
:summary "Components identified by CID, stored on IPFS, fetched from anywhere. Canonical serialization, content verification, federated sharing.")
(dict :label "Environment Images" :href "/etc/plans/environment-images"
(dict :label "Environment Images" :href "/(etc.(plan.environment-images))"
:summary "Serialize evaluated environments as content-addressed images. Spec CID → image CID → every endpoint is fully executable and verifiable.")
(dict :label "Runtime Slicing" :href "/etc/plans/runtime-slicing"
(dict :label "Runtime Slicing" :href "/(etc.(plan.runtime-slicing))"
:summary "Tier the client runtime by need: L0 hypermedia (~5KB), L1 DOM ops (~8KB), L2 islands (~15KB), L3 full eval (~44KB). Sliced by slice.sx, translated by js.sx.")
(dict :label "Typed SX" :href "/etc/plans/typed-sx"
(dict :label "Typed SX" :href "/(etc.(plan.typed-sx))"
:summary "Gradual type system with static effect checking. Optional type annotations, deftype (aliases, unions, records), and effect declarations — checked at registration time, zero runtime cost. types.sx — specced, bootstrapped, catches composition and boundary errors.")
(dict :label "Nav Redesign" :href "/etc/plans/nav-redesign"
(dict :label "Nav Redesign" :href "/(etc.(plan.nav-redesign))"
:summary "Replace menu bars with vertical breadcrumb navigation. Logo → section → page, arrows for siblings, children below. No dropdowns, no hamburger, infinite depth.")
(dict :label "Fragment Protocol" :href "/etc/plans/fragment-protocol"
(dict :label "Fragment Protocol" :href "/(etc.(plan.fragment-protocol))"
:summary "Structured sexp request/response for cross-service component transfer.")
(dict :label "Glue Decoupling" :href "/etc/plans/glue-decoupling"
(dict :label "Glue Decoupling" :href "/(etc.(plan.glue-decoupling))"
:summary "Eliminate all cross-app model imports via glue service layer.")
(dict :label "Social Sharing" :href "/etc/plans/social-sharing"
(dict :label "Social Sharing" :href "/(etc.(plan.social-sharing))"
:summary "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon.")
(dict :label "SX CI Pipeline" :href "/etc/plans/sx-ci"
(dict :label "SX CI Pipeline" :href "/(etc.(plan.sx-ci))"
:summary "Build, test, and deploy in s-expressions — CI pipelines as SX components.")
(dict :label "Live Streaming" :href "/etc/plans/live-streaming"
(dict :label "Live Streaming" :href "/(etc.(plan.live-streaming))"
:summary "SSE and WebSocket transports for re-resolving suspense slots after initial page load — live data, real-time collaboration.")
(dict :label "sx-web Platform" :href "/etc/plans/sx-web-platform"
(dict :label "sx-web Platform" :href "/(etc.(plan.sx-web-platform))"
:summary "sx-web.org as online development platform — embedded Claude Code, IPFS storage, sx-activity publishing, sx-ci testing. Author, stage, test, deploy from the browser.")
(dict :label "sx-forge" :href "/etc/plans/sx-forge"
(dict :label "sx-forge" :href "/(etc.(plan.sx-forge))"
:summary "Git forge in SX — repositories, issues, pull requests, CI, permissions, and federation. Configuration as macros, diffs as components.")
(dict :label "sx-swarm" :href "/etc/plans/sx-swarm"
(dict :label "sx-swarm" :href "/(etc.(plan.sx-swarm))"
:summary "Container orchestration in SX — service definitions, environment macros, deploy pipelines. Replace YAML with a real language.")
(dict :label "sx-proxy" :href "/etc/plans/sx-proxy"
(dict :label "sx-proxy" :href "/(etc.(plan.sx-proxy))"
:summary "Reverse proxy in SX — routes, TLS, middleware chains, load balancing. Macros generate config from the same service definitions as the orchestrator.")
(dict :label "Async Eval Convergence" :href "/etc/plans/async-eval-convergence"
(dict :label "Async Eval Convergence" :href "/(etc.(plan.async-eval-convergence))"
:summary "Eliminate hand-written evaluators — bootstrap async_eval.py from the spec via an async adapter layer. One spec, one truth, zero divergence.")
(dict :label "WASM Bytecode VM" :href "/etc/plans/wasm-bytecode-vm"
(dict :label "WASM Bytecode VM" :href "/(etc.(plan.wasm-bytecode-vm))"
:summary "Compile SX to bytecode, run in a Rust/WASM VM. Compact wire format, no parse overhead, near-native speed, DOM via JS bindings.")
(dict :label "Generative SX" :href "/etc/plans/generative-sx"
(dict :label "Generative SX" :href "/(etc.(plan.generative-sx))"
:summary "Programs that write themselves as they run — self-compiling specs, runtime self-extension, generative testing, seed networks.")
(dict :label "Art DAG on SX" :href "/etc/plans/art-dag-sx"
(dict :label "Art DAG on SX" :href "/(etc.(plan.art-dag-sx))"
:summary "SX endpoints as portals into media processing environments — recipes as programs, split execution across GPU/cache/live boundaries, streaming AV output.")
(dict :label "Spec Explorer" :href "/etc/plans/spec-explorer"
:summary "The fifth ring — SX exploring itself. Per-function cards showing source, Python/JS/Z3 translations, platform dependencies, tests, proofs, and usage examples.")))
(dict :label "Spec Explorer" :href "/(etc.(plan.spec-explorer))"
:summary "The fifth ring — SX exploring itself. Per-function cards showing source, Python/JS/Z3 translations, platform dependencies, tests, proofs, and usage examples.")
(dict :label "SX Protocol" :href "/(etc.(plan.sx-protocol))"
:summary "S-expressions as a universal protocol for networked hypermedia — replacing URLs, HTTP verbs, query languages, and rendering with one evaluable format.")))
(define reactive-islands-nav-items (list
(dict :label "Overview" :href "/geography/reactive/"
(dict :label "Overview" :href "/(geography.(reactive))"
:summary "Architecture, four levels (L0-L3), and current implementation status.")
(dict :label "Demo" :href "/geography/reactive/demo"
(dict :label "Demo" :href "/(geography.(reactive.demo))"
:summary "Live demonstration of signals, computed, effects, batch, and defisland — all transpiled from spec.")
(dict :label "Event Bridge" :href "/geography/reactive/event-bridge"
(dict :label "Event Bridge" :href "/(geography.(reactive.event-bridge))"
:summary "DOM events for htmx lake → island communication. Server-rendered buttons dispatch custom events that island effects listen for.")
(dict :label "Named Stores" :href "/geography/reactive/named-stores"
(dict :label "Named Stores" :href "/(geography.(reactive.named-stores))"
:summary "Page-level signal containers via def-store/use-store — persist across island destruction/recreation.")
(dict :label "Plan" :href "/geography/reactive/plan"
(dict :label "Plan" :href "/(geography.(reactive.plan))"
:summary "The full design document — rendering boundary, state flow, signal primitives, island lifecycle.")
(dict :label "Phase 2" :href "/geography/reactive/phase2"
(dict :label "Phase 2" :href "/(geography.(reactive.phase2))"
:summary "Input binding, keyed lists, reactive class/style, refs, portals, error boundaries, suspense, transitions.")))
(define bootstrappers-nav-items (list
(dict :label "Overview" :href "/language/bootstrappers/")
(dict :label "JavaScript" :href "/language/bootstrappers/javascript")
(dict :label "Python" :href "/language/bootstrappers/python")
(dict :label "Self-Hosting (py.sx)" :href "/language/bootstrappers/self-hosting")
(dict :label "Self-Hosting JS (js.sx)" :href "/language/bootstrappers/self-hosting-js")
(dict :label "Page Helpers" :href "/language/bootstrappers/page-helpers")))
(dict :label "Overview" :href "/(language.(bootstrapper))")
(dict :label "JavaScript" :href "/(language.(bootstrapper.javascript))")
(dict :label "Python" :href "/(language.(bootstrapper.python))")
(dict :label "Self-Hosting (py.sx)" :href "/(language.(bootstrapper.self-hosting))")
(dict :label "Self-Hosting JS (js.sx)" :href "/(language.(bootstrapper.self-hosting-js))")
(dict :label "Page Helpers" :href "/(language.(bootstrapper.page-helpers))")))
;; Spec file registry — canonical metadata for spec viewer pages.
;; Python only handles file I/O (read-spec-file); all metadata lives here.
@@ -334,7 +336,7 @@
(fn (items slug)
(when slug
(some (fn (item)
(when (ends-with? (get item "href") slug)
(when (ends-with? (get item "href") (str "." slug "))"))
(get item "label")))
items))))
@@ -357,31 +359,57 @@
(define sx-nav-tree
{:label "sx" :href "/"
:children (list
{:label "Geography" :href "/geography/"
{:label "Geography" :href "/(geography)"
:children (list
{:label "Reactive Islands" :href "/geography/reactive/" :children reactive-islands-nav-items}
{:label "Hypermedia Lakes" :href "/geography/hypermedia/"
{:label "Reactive Islands" :href "/(geography.(reactive))" :children reactive-islands-nav-items}
{:label "Hypermedia Lakes" :href "/(geography.(hypermedia))"
:children (list
{:label "Reference" :href "/geography/hypermedia/reference/" :children reference-nav-items}
{:label "Examples" :href "/geography/hypermedia/examples/" :children examples-nav-items})}
{:label "Marshes" :href "/geography/marshes/"
{:label "Reference" :href "/(geography.(hypermedia.(reference)))" :children reference-nav-items}
{:label "Examples" :href "/(geography.(hypermedia.(example)))" :children examples-nav-items})}
{:label "Marshes" :href "/(geography.(marshes))"
:summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted."}
{:label "Isomorphism" :href "/geography/isomorphism/" :children isomorphism-nav-items})}
{:label "Language" :href "/language/"
{:label "Isomorphism" :href "/(geography.(isomorphism))" :children isomorphism-nav-items})}
{:label "Language" :href "/(language)"
:children (list
{:label "Docs" :href "/language/docs/" :children docs-nav-items}
{:label "Specs" :href "/language/specs/" :children specs-nav-items}
{:label "Bootstrappers" :href "/language/bootstrappers/" :children bootstrappers-nav-items}
{:label "Testing" :href "/language/testing/" :children testing-nav-items})}
{:label "Applications" :href "/applications/"
{:label "Docs" :href "/(language.(doc))" :children docs-nav-items}
{:label "Specs" :href "/(language.(spec))" :children specs-nav-items}
{:label "Bootstrappers" :href "/(language.(bootstrapper))" :children bootstrappers-nav-items}
{:label "Testing" :href "/(language.(test))" :children testing-nav-items})}
{:label "Applications" :href "/(applications)"
:children (list
{:label "CSSX" :href "/applications/cssx/" :children cssx-nav-items}
{:label "Protocols" :href "/applications/protocols/" :children protocols-nav-items})}
{:label "Etc" :href "/etc/"
{:label "CSSX" :href "/(applications.(cssx))" :children cssx-nav-items}
{:label "Protocols" :href "/(applications.(protocol))" :children protocols-nav-items})}
{:label "Etc" :href "/(etc)"
:children (list
{:label "Essays" :href "/etc/essays/" :children essays-nav-items}
{:label "Philosophy" :href "/etc/philosophy/" :children philosophy-nav-items}
{:label "Plans" :href "/etc/plans/" :children plans-nav-items})})})
{:label "Essays" :href "/(etc.(essay))" :children essays-nav-items}
{:label "Philosophy" :href "/(etc.(philosophy))" :children philosophy-nav-items}
{:label "Plans" :href "/(etc.(plan))" :children plans-nav-items})})})
;; ---------------------------------------------------------------------------
;; Nav resolution — tree-descent matching (no prefix heuristics)
;; ---------------------------------------------------------------------------
;; Check if a node or any of its descendants has the given href.
(define has-descendant-href?
(fn (node path)
(let ((children (get node "children")))
(when children
(some (fn (child)
(or (= (get child "href") path)
(has-descendant-href? child path)))
children)))))
;; Find a nav item that matches the given path: exact match or descendant match.
(define find-nav-match
(fn (items path)
;; Exact match first
(or (some (fn (item)
(when (= (get item "href") path) item))
items)
;; Descendant match: some child/grandchild has this path
(some (fn (item)
(when (has-descendant-href? item path) item))
items))))
;; Resolve a URL path to a nav trail + children.
;; Returns {:trail [{:node N :siblings S} ...] :children [...] :depth N}
@@ -409,34 +437,6 @@
:children (get (get deepest "node") "children")
:depth depth}))))))
;; Find a nav item whose href matches the given path (or path prefix).
(define find-nav-match
(fn (items path)
;; Exact match first
(or (some (fn (item)
(when (= (get item "href") path) item))
items)
;; Prefix match: path starts with item href (for /plans/typed-sx matching /plans/)
(some (fn (item)
(let ((href (get item "href")))
(when (and (ends-with? href "/")
(starts-with? path href))
item)))
items)
;; Path contains section: /plans/typed-sx matches section with /plans/ children
;; Also handles prefix matching on children for nested groups (e.g. Geography → Reactive Islands)
(some (fn (item)
(let ((children (get item "children")))
(when children
(when (some (fn (child)
(let ((href (get child "href")))
(or (= href path)
(and (ends-with? href "/")
(starts-with? path href)))))
children)
item))))
items))))
;; Find the index of a nav item in a list by matching href.
(define find-nav-index
(fn (items node)

521
sx/sx/page-functions.sx Normal file
View File

@@ -0,0 +1,521 @@
;; SX docs page functions — section + page dispatch for GraphSX URL routing.
;;
;; IMPORTANT: Page functions return QUOTED expressions (unevaluated ASTs).
;; The Python router evaluates these functions with async_eval to get the AST,
;; then passes it through _eval_slot (aser) for component expansion and HTML
;; tag handling. This two-phase approach is necessary because eval_expr doesn't
;; handle HTML tags — only the aser/render paths do.
;;
;; Pattern:
;; Simple: '(~component-name)
;; Data: (let ((data (helper))) `(~component :key ,val))
;;
;; URL eval: /(language.(doc.introduction))
;; → (language (doc "introduction"))
;; → async_eval returns [Symbol("~docs-introduction-content")]
;; → _eval_slot wraps in (~sx-doc :path "..." <ast>) and renders via aser
;;
;; NOTE: Lambda &rest is not supported by call-lambda in the current spec.
;; All functions take explicit positional params; missing args default to nil.
;; ---------------------------------------------------------------------------
;; Section functions — structural, pass through content or return index
;; ---------------------------------------------------------------------------
(define home
(fn (content)
(if (nil? content) '(~sx-home-content) content)))
(define language
(fn (content)
(if (nil? content) nil content)))
(define geography
(fn (content)
(if (nil? content) nil content)))
(define applications
(fn (content)
(if (nil? content) nil content)))
(define etc
(fn (content)
(if (nil? content) nil content)))
;; Sub-section functions
(define hypermedia
(fn (content)
(if (nil? content) nil content)))
(define reactive
(fn (slug)
(if (nil? slug)
'(~reactive-islands-index-content)
(case slug
"demo" '(~reactive-islands-demo-content)
"event-bridge" '(~reactive-islands-event-bridge-content)
"named-stores" '(~reactive-islands-named-stores-content)
"plan" '(~reactive-islands-plan-content)
"phase2" '(~reactive-islands-phase2-content)
:else '(~reactive-islands-index-content)))))
(define marshes
(fn (content)
(if (nil? content) '(~reactive-islands-marshes-content) content)))
(define isomorphism
(fn (slug)
(if (nil? slug)
'(~plan-isomorphic-content)
(case slug
"bundle-analyzer"
(let ((data (bundle-analyzer-data)))
`(~bundle-analyzer-content
:pages ,(get data "pages")
:total-components ,(get data "total-components")
:total-macros ,(get data "total-macros")
:pure-count ,(get data "pure-count")
:io-count ,(get data "io-count")))
"routing-analyzer"
(let ((data (routing-analyzer-data)))
`(~routing-analyzer-content
:pages ,(get data "pages")
:total-pages ,(get data "total-pages")
:client-count ,(get data "client-count")
:server-count ,(get data "server-count")
:registry-sample ,(get data "registry-sample")))
"data-test"
(let ((data (data-test-data)))
`(~data-test-content
:server-time ,(get data "server-time")
:items ,(get data "items")
:phase ,(get data "phase")
:transport ,(get data "transport")))
"async-io" '(~async-io-demo-content)
"affinity"
(let ((data (affinity-demo-data)))
`(~affinity-demo-content
:components ,(get data "components")
:page-plans ,(get data "page-plans")))
"optimistic"
(let ((data (optimistic-demo-data)))
`(~optimistic-demo-content
:items ,(get data "items")
:server-time ,(get data "server-time")))
"offline"
(let ((data (offline-demo-data)))
`(~offline-demo-content
:notes ,(get data "notes")
:server-time ,(get data "server-time")))
;; "streaming" → handled as special case by Python router
:else '(~plan-isomorphic-content)))))
;; ---------------------------------------------------------------------------
;; Page functions — leaf dispatch to content components
;; ---------------------------------------------------------------------------
;; Docs (under language)
(define doc
(fn (slug)
(if (nil? slug)
'(~docs-introduction-content)
(case slug
"introduction" '(~docs-introduction-content)
"getting-started" '(~docs-getting-started-content)
"components" '(~docs-components-content)
"evaluator" '(~docs-evaluator-content)
"primitives"
(let ((data (primitives-data)))
`(~docs-primitives-content
:prims (~doc-primitives-tables :primitives ,data)))
"special-forms"
(let ((data (special-forms-data)))
`(~docs-special-forms-content
:forms (~doc-special-forms-tables :forms ,data)))
"server-rendering" '(~docs-server-rendering-content)
:else '(~docs-introduction-content)))))
;; Specs (under language)
(define spec
(fn (slug)
(if (nil? slug)
'(~spec-architecture-content)
(case slug
"core"
(let ((files (make-spec-files core-spec-items)))
`(~spec-overview-content :spec-title "Core Language" :spec-files ,files))
"adapters"
(let ((files (make-spec-files adapter-spec-items)))
`(~spec-overview-content :spec-title "Adapters" :spec-files ,files))
"browser"
(let ((files (make-spec-files browser-spec-items)))
`(~spec-overview-content :spec-title "Browser Runtime" :spec-files ,files))
"reactive"
(let ((files (make-spec-files reactive-spec-items)))
`(~spec-overview-content :spec-title "Reactive System" :spec-files ,files))
"host"
(let ((files (make-spec-files host-spec-items)))
`(~spec-overview-content :spec-title "Host Interface" :spec-files ,files))
"extensions"
(let ((files (make-spec-files extension-spec-items)))
`(~spec-overview-content :spec-title "Extensions" :spec-files ,files))
:else (let ((found-spec (find-spec slug)))
(if found-spec
(let ((src (read-spec-file (get found-spec "filename"))))
`(~spec-detail-content
:spec-title ,(get found-spec "title")
:spec-desc ,(get found-spec "desc")
:spec-filename ,(get found-spec "filename")
:spec-source ,src
:spec-prose ,(get found-spec "prose")))
`(~spec-not-found :slug ,slug)))))))
;; Spec explorer (under language → spec)
(define explore
(fn (slug)
(if (nil? slug)
'(~spec-architecture-content)
(let ((found-spec (find-spec slug)))
(if found-spec
(let ((data (spec-explorer-data
(get found-spec "filename")
(get found-spec "title")
(get found-spec "desc"))))
(if data
`(~spec-explorer-content :data ,data)
`(~spec-not-found :slug ,slug)))
`(~spec-not-found :slug ,slug))))))
;; Helper used by spec — make-spec-files
(define make-spec-files
(fn (items)
(map (fn (item)
(dict :title (get item "title") :desc (get item "desc")
:prose (get item "prose")
:filename (get item "filename") :href (str "/(language.(spec." (get item "slug") "))")
:source (read-spec-file (get item "filename"))))
items)))
;; Bootstrappers (under language)
(define bootstrapper
(fn (slug)
(if (nil? slug)
'(~bootstrappers-index-content)
(let ((data (bootstrapper-data slug)))
(if (get data "bootstrapper-not-found")
`(~spec-not-found :slug ,slug)
(case slug
"self-hosting"
`(~bootstrapper-self-hosting-content
:py-sx-source ,(get data "py-sx-source")
:g0-output ,(get data "g0-output")
:g1-output ,(get data "g1-output")
:defines-matched ,(get data "defines-matched")
:defines-total ,(get data "defines-total")
:g0-lines ,(get data "g0-lines")
:g0-bytes ,(get data "g0-bytes")
:verification-status ,(get data "verification-status"))
"self-hosting-js"
`(~bootstrapper-self-hosting-js-content
:js-sx-source ,(get data "js-sx-source")
:defines-matched ,(get data "defines-matched")
:defines-total ,(get data "defines-total")
:js-sx-lines ,(get data "js-sx-lines")
:verification-status ,(get data "verification-status"))
"python"
`(~bootstrapper-py-content
:bootstrapper-source ,(get data "bootstrapper-source")
:bootstrapped-output ,(get data "bootstrapped-output"))
"page-helpers"
(let ((ph-data (page-helpers-demo-data)))
`(~page-helpers-demo-content
:sf-categories ,(get ph-data "sf-categories")
:sf-total ,(get ph-data "sf-total")
:sf-ms ,(get ph-data "sf-ms")
:ref-sample ,(get ph-data "ref-sample")
:ref-ms ,(get ph-data "ref-ms")
:attr-result ,(get ph-data "attr-result")
:attr-ms ,(get ph-data "attr-ms")
:comp-source ,(get ph-data "comp-source")
:comp-ms ,(get ph-data "comp-ms")
:routing-result ,(get ph-data "routing-result")
:routing-ms ,(get ph-data "routing-ms")
:server-total-ms ,(get ph-data "server-total-ms")
:sf-source ,(get ph-data "sf-source")
:attr-detail ,(get ph-data "attr-detail")
:req-attrs ,(get ph-data "req-attrs")
:attr-keys ,(get ph-data "attr-keys")))
:else
`(~bootstrapper-js-content
:bootstrapper-source ,(get data "bootstrapper-source")
:bootstrapped-output ,(get data "bootstrapped-output"))))))))
;; Testing (under language)
(define test
(fn (slug)
(if (nil? slug)
(let ((data (run-modular-tests "all")))
`(~testing-overview-content
:server-results ,(get data "server-results")
:framework-source ,(get data "framework-source")
:eval-source ,(get data "eval-source")
:parser-source ,(get data "parser-source")
:router-source ,(get data "router-source")
:render-source ,(get data "render-source")
:deps-source ,(get data "deps-source")
:engine-source ,(get data "engine-source")))
(case slug
"runners" '(~testing-runners-content)
:else
(let ((data (run-modular-tests slug)))
(case slug
"eval" `(~testing-spec-content
:spec-name "eval" :spec-title "Evaluator Tests"
:spec-desc "81 tests covering the core evaluator and all primitives."
:spec-source ,(get data "spec-source")
:framework-source ,(get data "framework-source")
:server-results ,(get data "server-results"))
"parser" `(~testing-spec-content
:spec-name "parser" :spec-title "Parser Tests"
:spec-desc "39 tests covering tokenization and parsing."
:spec-source ,(get data "spec-source")
:framework-source ,(get data "framework-source")
:server-results ,(get data "server-results"))
"router" `(~testing-spec-content
:spec-name "router" :spec-title "Router Tests"
:spec-desc "18 tests covering client-side route matching."
:spec-source ,(get data "spec-source")
:framework-source ,(get data "framework-source")
:server-results ,(get data "server-results"))
"render" `(~testing-spec-content
:spec-name "render" :spec-title "Renderer Tests"
:spec-desc "23 tests covering HTML rendering."
:spec-source ,(get data "spec-source")
:framework-source ,(get data "framework-source")
:server-results ,(get data "server-results"))
"deps" `(~testing-spec-content
:spec-name "deps" :spec-title "Dependency Analysis Tests"
:spec-desc "33 tests covering component dependency analysis."
:spec-source ,(get data "spec-source")
:framework-source ,(get data "framework-source")
:server-results ,(get data "server-results"))
"engine" `(~testing-spec-content
:spec-name "engine" :spec-title "Engine Tests"
:spec-desc "37 tests covering engine pure functions."
:spec-source ,(get data "spec-source")
:framework-source ,(get data "framework-source")
:server-results ,(get data "server-results"))
"orchestration" `(~testing-spec-content
:spec-name "orchestration" :spec-title "Orchestration Tests"
:spec-desc "17 tests covering orchestration."
:spec-source ,(get data "spec-source")
:framework-source ,(get data "framework-source")
:server-results ,(get data "server-results"))
:else `(~testing-overview-content
:server-results ,(get data "server-results"))))))))
;; Reference (under geography → hypermedia)
(define reference
(fn (slug)
(if (nil? slug)
'(~reference-index-content)
(let ((data (reference-data slug)))
(case slug
"attributes" `(~reference-attrs-content
:req-table (~doc-attr-table-from-data :title "Request Attributes" :attrs ,(get data "req-attrs"))
:beh-table (~doc-attr-table-from-data :title "Behavior Attributes" :attrs ,(get data "beh-attrs"))
:uniq-table (~doc-attr-table-from-data :title "Unique to sx" :attrs ,(get data "uniq-attrs")))
"headers" `(~reference-headers-content
:req-table (~doc-headers-table-from-data :title "Request Headers" :headers ,(get data "req-headers"))
:resp-table (~doc-headers-table-from-data :title "Response Headers" :headers ,(get data "resp-headers")))
"events" `(~reference-events-content
:table (~doc-two-col-table-from-data
:intro "sx fires custom DOM events at various points in the request lifecycle."
:col1 "Event" :col2 "Description" :items ,(get data "events-list")))
"js-api" `(~reference-js-api-content
:table (~doc-two-col-table-from-data
:intro "The client-side sx.js library exposes a public API for programmatic use."
:col1 "Method" :col2 "Description" :items ,(get data "js-api-list")))
:else `(~reference-attrs-content
:req-table (~doc-attr-table-from-data :title "Request Attributes" :attrs ,(get data "req-attrs"))
:beh-table (~doc-attr-table-from-data :title "Behavior Attributes" :attrs ,(get data "beh-attrs"))
:uniq-table (~doc-attr-table-from-data :title "Unique to sx" :attrs ,(get data "uniq-attrs"))))))))
;; Reference detail pages (under geography → hypermedia → reference)
;; Takes two positional args: kind and slug
(define reference-detail
(fn (kind slug)
(if (nil? slug) nil
(case kind
"attributes"
(let ((data (attr-detail-data slug)))
(if (get data "attr-not-found")
`(~reference-attr-not-found :slug ,slug)
`(~reference-attr-detail-content
:title ,(get data "attr-title")
:description ,(get data "attr-description")
:demo ,(get data "attr-demo")
:example-code ,(get data "attr-example")
:handler-code ,(get data "attr-handler")
:wire-placeholder-id ,(get data "attr-wire-id"))))
"headers"
(let ((data (header-detail-data slug)))
(if (get data "header-not-found")
`(~reference-attr-not-found :slug ,slug)
`(~reference-header-detail-content
:title ,(get data "header-title")
:direction ,(get data "header-direction")
:description ,(get data "header-description")
:example-code ,(get data "header-example")
:demo ,(get data "header-demo"))))
"events"
(let ((data (event-detail-data slug)))
(if (get data "event-not-found")
`(~reference-attr-not-found :slug ,slug)
`(~reference-event-detail-content
:title ,(get data "event-title")
:description ,(get data "event-description")
:example-code ,(get data "event-example")
:demo ,(get data "event-demo"))))
:else nil))))
;; Examples (under geography → hypermedia)
(define example
(fn (slug)
(if (nil? slug)
nil
(case slug
"click-to-load" '(~example-click-to-load)
"form-submission" '(~example-form-submission)
"polling" '(~example-polling)
"delete-row" '(~example-delete-row)
"inline-edit" '(~example-inline-edit)
"oob-swaps" '(~example-oob-swaps)
"lazy-loading" '(~example-lazy-loading)
"infinite-scroll" '(~example-infinite-scroll)
"progress-bar" '(~example-progress-bar)
"active-search" '(~example-active-search)
"inline-validation" '(~example-inline-validation)
"value-select" '(~example-value-select)
"reset-on-submit" '(~example-reset-on-submit)
"edit-row" '(~example-edit-row)
"bulk-update" '(~example-bulk-update)
"swap-positions" '(~example-swap-positions)
"select-filter" '(~example-select-filter)
"tabs" '(~example-tabs)
"animations" '(~example-animations)
"dialogs" '(~example-dialogs)
"keyboard-shortcuts" '(~example-keyboard-shortcuts)
"put-patch" '(~example-put-patch)
"json-encoding" '(~example-json-encoding)
"vals-and-headers" '(~example-vals-and-headers)
"loading-states" '(~example-loading-states)
"sync-replace" '(~example-sync-replace)
"retry" '(~example-retry)
:else '(~example-click-to-load)))))
;; CSSX (under applications)
(define cssx
(fn (slug)
(if (nil? slug)
'(~cssx-overview-content)
(case slug
"patterns" '(~cssx-patterns-content)
"delivery" '(~cssx-delivery-content)
"async" '(~cssx-async-content)
"live" '(~cssx-live-content)
"comparisons" '(~cssx-comparison-content)
"philosophy" '(~cssx-philosophy-content)
:else '(~cssx-overview-content)))))
;; Protocols (under applications)
(define protocol
(fn (slug)
(if (nil? slug)
'(~protocol-wire-format-content)
(case slug
"wire-format" '(~protocol-wire-format-content)
"fragments" '(~protocol-fragments-content)
"resolver-io" '(~protocol-resolver-io-content)
"internal-services" '(~protocol-internal-services-content)
"activitypub" '(~protocol-activitypub-content)
"future" '(~protocol-future-content)
:else '(~protocol-wire-format-content)))))
;; Essays (under etc)
(define essay
(fn (slug)
(if (nil? slug)
'(~essays-index-content)
(case slug
"sx-sucks" '(~essay-sx-sucks)
"why-sexps" '(~essay-why-sexps)
"htmx-react-hybrid" '(~essay-htmx-react-hybrid)
"on-demand-css" '(~essay-on-demand-css)
"client-reactivity" '(~essay-client-reactivity)
"sx-native" '(~essay-sx-native)
"tail-call-optimization" '(~essay-tail-call-optimization)
"continuations" '(~essay-continuations)
"reflexive-web" '(~essay-reflexive-web)
"server-architecture" '(~essay-server-architecture)
"separation-of-concerns" '(~essay-separation-of-concerns)
"sx-and-ai" '(~essay-sx-and-ai)
"no-alternative" '(~essay-no-alternative)
"zero-tooling" '(~essay-zero-tooling)
"react-is-hypermedia" '(~essay-react-is-hypermedia)
"hegelian-synthesis" '(~essay-hegelian-synthesis)
"the-art-chain" '(~essay-the-art-chain)
:else '(~essays-index-content)))))
;; Philosophy (under etc)
(define philosophy
(fn (slug)
(if (nil? slug)
'(~philosophy-index-content)
(case slug
"sx-manifesto" '(~essay-sx-manifesto)
"godel-escher-bach" '(~essay-godel-escher-bach)
"wittgenstein" '(~essay-sx-and-wittgenstein)
"dennett" '(~essay-sx-and-dennett)
"existentialism" '(~essay-s-existentialism)
:else '(~philosophy-index-content)))))
;; Plans (under etc)
(define plan
(fn (slug)
(if (nil? slug)
'(~plans-index-content)
(case slug
"status" '(~plan-status-content)
"reader-macros" '(~plan-reader-macros-content)
"reader-macro-demo" '(~plan-reader-macro-demo-content)
"theorem-prover"
(let ((data (prove-data)))
'(~plan-theorem-prover-content))
"self-hosting-bootstrapper" '(~plan-self-hosting-bootstrapper-content)
"js-bootstrapper" '(~plan-js-bootstrapper-content)
"sx-activity" '(~plan-sx-activity-content)
"predictive-prefetch" '(~plan-predictive-prefetch-content)
"content-addressed-components" '(~plan-content-addressed-components-content)
"environment-images" '(~plan-environment-images-content)
"runtime-slicing" '(~plan-runtime-slicing-content)
"typed-sx" '(~plan-typed-sx-content)
"nav-redesign" '(~plan-nav-redesign-content)
"fragment-protocol" '(~plan-fragment-protocol-content)
"glue-decoupling" '(~plan-glue-decoupling-content)
"social-sharing" '(~plan-social-sharing-content)
"sx-ci" '(~plan-sx-ci-content)
"live-streaming" '(~plan-live-streaming-content)
"sx-web-platform" '(~plan-sx-web-platform-content)
"sx-forge" '(~plan-sx-forge-content)
"sx-swarm" '(~plan-sx-swarm-content)
"sx-proxy" '(~plan-sx-proxy-content)
"async-eval-convergence" '(~plan-async-eval-convergence-content)
"wasm-bytecode-vm" '(~plan-wasm-bytecode-vm-content)
"generative-sx" '(~plan-generative-sx-content)
"art-dag-sx" '(~plan-art-dag-sx-content)
"spec-explorer" '(~plan-spec-explorer-content)
"sx-urls" '(~plan-sx-urls-content)
"sx-protocol" '(~plan-sx-protocol-content)
:else '(~plans-index-content)))))

View File

@@ -280,7 +280,7 @@
(~doc-subsection :title "CID References in Page Registry"
(p "The page registry (shipped to the client as " (code "<script type=\"text/sx-pages\">") ") currently lists deps by name. Extend to include CIDs:")
(~doc-code :code (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :deps ({:name \"~essay-foo\" :cid \"bafy...essay\"}\n {:name \"~doc-code\" :cid \"bafy...doccode\"})\n :content \"(case slug ...)\" :closure {}}" "lisp"))
(p "The " (a :href "/etc/plans/predictive-prefetch" :class "text-violet-700 underline" "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint."))
(p "The " (a :href "/(etc.(plan.predictive-prefetch))" :class "text-violet-700 underline" "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint."))
(~doc-subsection :title "SX Response Component Headers"
(p "Currently, " (code "SX-Components") " header lists loaded component names. Extend to support CIDs:")
@@ -407,9 +407,9 @@
(~doc-section :title "Relationships" :id "relationships"
(p "This plan is the foundation for several other plans and roadmaps:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/etc/plans/sx-activity" :class "text-violet-700 underline" "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.")
(li (a :href "/etc/plans/predictive-prefetch" :class "text-violet-700 underline" "Predictive prefetching") " gains CID-based resolution — the " (code "/sx/components") " endpoint and IPFS gateway become alternative resolution paths in the prefetch cascade.")
(li (a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "Isomorphic architecture") " Phase 1 (component distribution) is enhanced — CIDs make per-page bundles verifiable and cross-server shareable.")
(li (a :href "/(etc.(plan.sx-activity))" :class "text-violet-700 underline" "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.")
(li (a :href "/(etc.(plan.predictive-prefetch))" :class "text-violet-700 underline" "Predictive prefetching") " gains CID-based resolution — the " (code "/sx/components") " endpoint and IPFS gateway become alternative resolution paths in the prefetch cascade.")
(li (a :href "/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "Isomorphic architecture") " Phase 1 (component distribution) is enhanced — CIDs make per-page bundles verifiable and cross-server shareable.")
(li "The SX-Activity vision of " (strong "serverless applications on IPFS") " depends entirely on this plan. Without content-addressed components, applications can't be pinned to IPFS as self-contained artifacts."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "deps.sx (complete), boundary enforcement (complete), IPFS infrastructure (exists in artdag, needs wiring to web platform)."))))))

View File

@@ -278,11 +278,11 @@
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Canonical serialization")
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started"))
(td :class "px-3 py-2" (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 1"))
(td :class "px-3 py-2" (a :href "/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 1"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Component CIDs")
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started"))
(td :class "px-3 py-2" (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 2"))
(td :class "px-3 py-2" (a :href "/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 2"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Purity verification")
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete"))
@@ -301,4 +301,4 @@
(td :class "px-3 py-2 text-stone-600" "artdag L1/L2, IPFSPin model")))))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Builds on: ") (a :href "/etc/plans/content-addressed-components" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/etc/plans/self-hosting-bootstrapper" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/etc/plans/sx-activity" :class "underline" "SX-Activity") " (serverless applications on IPFS).")))))
(p :class "text-amber-800 text-sm" (strong "Builds on: ") (a :href "/(etc.(plan.content-addressed-components))" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/(etc.(plan.self-hosting-bootstrapper))" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/(etc.(plan.sx-activity))" :class "underline" "SX-Activity") " (serverless applications on IPFS).")))))

View File

@@ -7,7 +7,7 @@
(~doc-section :title "Context" :id "context"
(p "SX has a working server-client pipeline: server evaluates pages with IO (DB, fragments), serializes as SX wire format, client parses and renders to DOM. The language and primitives are already isomorphic " (em "— same spec, same semantics, both sides.") " What's missing is the " (strong "plumbing") " that makes the boundary between server and client a sliding window rather than a fixed wall.")
(p "The key insight: " (strong "s-expressions can partially unfold on the server after IO, then finish unfolding on the client.") " The system knows which components have data fetches (via IO detection in " (a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx") "), resolves those server-side, and sends the rest as pure SX for client rendering. The boundary slides automatically based on what each component actually needs."))
(p "The key insight: " (strong "s-expressions can partially unfold on the server after IO, then finish unfolding on the client.") " The system knows which components have data fetches (via IO detection in " (a :href "/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx") "), resolves those server-side, and sends the rest as pure SX for client rendering. The boundary slides automatically based on what each component actually needs."))
(~doc-section :title "Current State" :id "current-state"
(ul :class "space-y-2 text-stone-700 list-disc pl-5"
@@ -32,8 +32,8 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/language/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/geography/isomorphism/bundle-analyzer" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer"))
(a :href "/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/(geography.(isomorphism.bundle-analyzer))" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Per-page component bundles instead of sending every definition to every page. Smaller payloads, faster boot, better cache hit rates."))
@@ -43,7 +43,7 @@
(~doc-subsection :title "Implementation"
(p "The dependency analysis algorithm is defined in "
(a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
(a :href "/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
" — a spec module bootstrapped to every host. Each host loads it via " (code "--spec-modules deps") " and provides 6 platform functions. The spec is the single source of truth; hosts are interchangeable.")
(div :class "space-y-4"
@@ -85,7 +85,7 @@
(li "15 dedicated tests: scan, transitive closure, circular deps, compute-all, components-needed")
(li "Bootstrapped output verified on both host targets")
(li "Full test suite passes with zero regressions")
(li (a :href "/geography/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app"))))
(li (a :href "/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app"))))
;; -----------------------------------------------------------------------
;; Phase 2
@@ -96,14 +96,14 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/language/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/geography/isomorphism/bundle-analyzer" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer with IO"))
(a :href "/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/(geography.(isomorphism.bundle-analyzer))" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer with IO"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Automatic IO detection and selective expansion. Server expands IO-dependent components, serializes pure ones for client. Per-component intelligence replaces global toggle."))
(~doc-subsection :title "IO Detection in the Spec"
(p "Five new functions in "
(a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
(a :href "/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
" extend the Phase 1 walker to detect IO primitive references:")
(div :class "space-y-4"
@@ -145,7 +145,7 @@
(li "Pure components (HTML-only) classified pure with empty io_refs")
(li "Transitive IO detection: component calling ~other where ~other calls (current-user) → IO-dependent")
(li "Bootstrapped to both hosts (sx_ref.py + sx-ref.js)")
(li (a :href "/geography/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification"))))
(li (a :href "/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification"))))
;; -----------------------------------------------------------------------
;; Phase 3
@@ -156,8 +156,8 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/language/specs/router" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
(a :href "/geography/isomorphism/routing-analyzer" :class "text-green-700 underline text-sm font-medium" "Live routing analyzer"))
(a :href "/(language.(spec.router))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
(a :href "/(geography.(isomorphism.routing-analyzer))" :class "text-green-700 underline text-sm font-medium" "Live routing analyzer"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "After initial page load, pure pages render instantly without server roundtrips. Client matches routes locally, evaluates content expressions with cached components, and only falls back to server for pages with :data dependencies."))
@@ -200,7 +200,7 @@
(li (code "/reference/<slug>") " (has " (code ":data (reference-data slug)") ")")
(li (code "/language/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")")
(li (code "/geography/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")")
(li (code "/geography/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "Phase 4 demo") ")")))
(li (code "/geography/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "Phase 4 demo") ")")))
(~doc-subsection :title "Try-first/fallback design"
(p "Client routing uses a try-first approach: attempt local evaluation in a try/catch, fall back to server fetch on any failure. This avoids needing perfect static analysis of content expressions — if a content expression calls a page helper the client doesn't have, the eval throws, and the server handles it transparently.")
@@ -232,7 +232,7 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/data-test" :class "text-green-700 underline text-sm font-medium" "Live data test page"))
(a :href "/(geography.(isomorphism.data-test))" :class "text-green-700 underline text-sm font-medium" "Live data test page"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Client fetches server-evaluated data and renders :data pages locally. Data cached with TTL to avoid redundant fetches on back/forward navigation. All IO stays server-side — no continuations needed."))
@@ -258,7 +258,7 @@
(li "Cache miss: " (code "sx:route client+data /path") " — fetches from server, caches, renders")
(li "Cache hit: " (code "sx:route client+cache /path") " — instant render from cached data")
(li "After TTL: stale entry evicted, fresh fetch on next visit"))
(p "Try it: navigate to the " (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch)."))))
(p "Try it: navigate to the " (a :href "/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch)."))))
(~doc-subsection :title "Files"
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
@@ -273,7 +273,7 @@
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "30 unit tests: serialize roundtrip, kebab-case, deps, full pipeline simulation, cache TTL")
(li "Console: " (code "sx:route client+data") " on first visit, " (code "sx:route client+cache") " on return within 30s")
(li (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "Live data test page") " exercises the full pipeline with server time + pipeline steps")
(li (a :href "/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "Live data test page") " exercises the full pipeline with server time + pipeline steps")
(li "append! and dict-set! registered as proper primitives in spec + both hosts"))))
;; -----------------------------------------------------------------------
@@ -331,7 +331,7 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/streaming" :class "text-green-700 underline text-sm font-medium" "Live streaming demo"))
(a :href "/(geography.(isomorphism.streaming))" :class "text-green-700 underline text-sm font-medium" "Live streaming demo"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately with loading skeletons, fills in suspended parts as data arrives."))
@@ -392,9 +392,9 @@
(li "sx/sxc/pages/helpers.py — streaming-demo-data page helper")))
(~doc-subsection :title "Demonstration"
(p "The " (a :href "/geography/isomorphism/streaming" :class "text-violet-700 underline" "streaming demo page") " exercises the full pipeline:")
(p "The " (a :href "/(geography.(isomorphism.streaming))" :class "text-violet-700 underline" "streaming demo page") " exercises the full pipeline:")
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
(li "Navigate to " (a :href "/geography/isomorphism/streaming" :class "text-violet-700 underline" "/geography/isomorphism/streaming"))
(li "Navigate to " (a :href "/(geography.(isomorphism.streaming))" :class "text-violet-700 underline" "/(geography.(isomorphism.streaming))"))
(li "The page skeleton appears " (strong "instantly") " — animated loading skeletons fill the content area")
(li "After ~1.5 seconds, the real content replaces the skeletons (streamed from server)")
(li "Open the Network tab — observe " (code "Transfer-Encoding: chunked") " on the document response")
@@ -481,7 +481,7 @@
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "5 new spec tests (page-render-plan suite)")
(li "Render plans visible on " (a :href "/geography/isomorphism/affinity" "affinity demo page"))
(li "Render plans visible on " (a :href "/(geography.(isomorphism.affinity))" "affinity demo page"))
(li "Client page registry includes :render-plan for each page"))))
(~doc-subsection :title "7c. Cache Invalidation & Optimistic Data Updates"
@@ -518,7 +518,7 @@
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Live demo at " (a :href "/geography/isomorphism/optimistic" :class "text-violet-600 hover:underline" "/geography/isomorphism/optimistic"))
(li "Live demo at " (a :href "/(geography.(isomorphism.optimistic))" :class "text-violet-600 hover:underline" "/(geography.(isomorphism.optimistic))"))
(li "Console log: " (code "sx:optimistic confirmed") " / " (code "sx:optimistic reverted")))))
(~doc-subsection :title "7d. Offline Data Layer"
@@ -553,7 +553,7 @@
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Live demo at " (a :href "/geography/isomorphism/offline" :class "text-violet-600 hover:underline" "/geography/isomorphism/offline"))
(li "Live demo at " (a :href "/(geography.(isomorphism.offline))" :class "text-violet-600 hover:underline" "/(geography.(isomorphism.offline))"))
(li "Test with DevTools Network → Offline mode")
(li "Console log: " (code "sx:offline queued") ", " (code "sx:offline syncing") ", " (code "sx:offline synced")))))

View File

@@ -479,7 +479,7 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
(code "createElement") ", " (code "appendChild") ", " (code "textContent")
". This gives SX a compilation target competitive with Svelte's "
"approach: components compile away, the framework disappears.")
(p "Combined with the " (a :href "/etc/plans/content-addressed-components" "content-addressed components")
(p "Combined with the " (a :href "/(etc.(plan.content-addressed-components))" "content-addressed components")
" plan, a page's compiled JS could be stored on IPFS by its content hash. "
"The server returns a CID. The browser fetches and executes pre-compiled JavaScript. "
"No parser, no evaluator, no network round-trip for component definitions."))

View File

@@ -248,7 +248,7 @@
(~doc-section :title "Relationship to Isomorphic Roadmap" :id "relationship"
(p "This plan sits between Phase 3 (client-side routing) and Phase 4 (client async & IO bridge) of the "
(a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "isomorphic architecture roadmap")
(a :href "/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "isomorphic architecture roadmap")
". It extends Phase 3 by making more navigations go client-side without needing any IO bridge — purely by ensuring component definitions are available before they're needed.")
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 3 (client-side routing with deps checking). No dependency on Phase 4.")))))

View File

@@ -28,7 +28,7 @@
(~doc-code :code (highlight "#'my-function → (quote my-function)" "lisp")))
(~doc-subsection :title "Extensible dispatch: #name"
(p "User-defined reader macros via " (code "#name expr") ". The parser reads an identifier after " (code "#") ", looks up a handler in the reader macro registry, and calls it with the next parsed expression. See the " (a :href "/etc/plans/reader-macro-demo" :class "text-violet-600 hover:underline" "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB.")))
(p "User-defined reader macros via " (code "#name expr") ". The parser reads an identifier after " (code "#") ", looks up a handler in the reader macro registry, and calls it with the next parsed expression. See the " (a :href "/(etc.(plan.reader-macro-demo))" :class "text-violet-600 hover:underline" "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB.")))
;; -----------------------------------------------------------------------

View File

@@ -40,7 +40,7 @@
;; -----------------------------------------------------------------------
(~doc-section :title "Tiers" :id "tiers"
(p "Four tiers, matching the " (a :href "/geography/reactive/plan" :class "text-violet-700 underline" "reactive islands") " levels:")
(p "Four tiers, matching the " (a :href "/(geography.(reactive.plan))" :class "text-violet-700 underline" "reactive islands") " levels:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
@@ -82,7 +82,7 @@
;; -----------------------------------------------------------------------
(~doc-section :title "The Slicer is SX" :id "slicer-is-sx"
(p "Per the " (a :href "/etc/plans/self-hosting-bootstrapper" :class "text-violet-700 underline" "self-hosting principle") ", the slicer is not a build tool — it's a spec module. " (code "slice.sx") " analyzes the spec's own dependency graph and determines which defines belong to which tier.")
(p "Per the " (a :href "/(etc.(plan.self-hosting-bootstrapper))" :class "text-violet-700 underline" "self-hosting principle") ", the slicer is not a build tool — it's a spec module. " (code "slice.sx") " analyzes the spec's own dependency graph and determines which defines belong to which tier.")
(p (code "js.sx") " (the self-hosting SX-to-JavaScript translator) already compiles the full spec. Slicing is a filter: " (code "js.sx") " translates only the defines that " (code "slice.sx") " selects for a given tier.")
(~doc-code :code (highlight ";; slice.sx — determine which defines each tier needs\n;;\n;; Input: the full list of defines from all spec files\n;; Output: a filtered list for the requested tier\n\n(define tier-deps\n ;; Which spec modules each tier requires\n {:L0 (list \"engine\" \"boot-partial\")\n :L1 (list \"engine\" \"boot-partial\" \"dom-partial\")\n :L2 (list \"engine\" \"boot-partial\" \"dom-partial\"\n \"signals\" \"dom-island\")\n :L3 (list \"eval\" \"render\" \"parser\"\n \"engine\" \"orchestration\" \"boot\"\n \"dom\" \"signals\" \"router\")})\n\n(define slice-defines\n (fn (tier all-defines)\n ;; 1. Get the module list for this tier\n ;; 2. Walk each define's dependency references\n ;; 3. Include a define only if ALL its deps are\n ;; satisfiable within the tier's module set\n ;; 4. Return the filtered define list\n (let ((modules (get tier-deps tier)))\n (filter\n (fn (d) (tier-satisfies? modules (define-deps d)))\n all-defines))))" "lisp"))
@@ -161,7 +161,7 @@
(~doc-subsection :title "Cache Behavior"
(p "Each tier file is content-hashed (like the current " (code "sx_js_hash") " mechanism). Cache-forever semantics. A user who visits any L0 page caches the L0 runtime permanently. If they later visit an L2 page, only the ~10KB delta downloads.")
(p "Combined with " (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "environment images") ": the image CID includes the tier. An L0 image is smaller than an L3 image — it contains fewer primitives, no parser state, no evaluator. The standalone HTML bundle for an L0 page is tiny.")))
(p "Combined with " (a :href "/(etc.(plan.environment-images))" :class "text-violet-700 underline" "environment images") ": the image CID includes the tier. An L0 image is smaller than an L3 image — it contains fewer primitives, no parser state, no evaluator. The standalone HTML bundle for an L0 page is tiny.")))
;; -----------------------------------------------------------------------
;; Automatic tier detection
@@ -273,10 +273,10 @@
(~doc-section :title "Relationships" :id "relationships"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.")
(li (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.")
(li (a :href "/geography/reactive/plan" :class "text-violet-700 underline" "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.")
(li (a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it."))
(li (a :href "/(etc.(plan.environment-images))" :class "text-violet-700 underline" "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.")
(li (a :href "/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.")
(li (a :href "/(geography.(reactive.plan))" :class "text-violet-700 underline" "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.")
(li (a :href "/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") (code "js.sx") " (complete), " (code "deps.sx") " (complete), " (code "bootstrap_js.py") " adapter selection (exists). " (strong "New: ") (code "slice.sx") " spec module.")))))

View File

@@ -17,7 +17,7 @@
(p :class "text-green-700 text-sm"
(code "py.sx") " is implemented and verified. G0 == G1: 128/128 defines match, "
"1490 lines, 88,955 bytes — byte-for-byte identical. "
(a :href "/language/bootstrappers/self-hosting" :class "underline text-green-600 font-medium"
(a :href "/(language.(bootstrapper.self-hosting))" :class "underline text-green-600 font-medium"
"See live verification."))))
;; -----------------------------------------------------------------------

View File

@@ -37,37 +37,37 @@
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 1: Dependency Analysis"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 1: Dependency Analysis"))
(p :class "text-sm text-stone-600" "Per-page component bundles via deps.sx. Transitive closure, scan-refs, components-needed, page-css-classes. 15 tests, bootstrapped to both hosts."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 2: IO Detection"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 2: IO Detection"))
(p :class "text-sm text-stone-600" "Automatic IO classification. scan-io-refs, transitive-io-refs, compute-all-io-refs. Server expands IO components, serializes pure ones for client."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing"))
(p :class "text-sm text-stone-600" "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
(p :class "text-sm text-stone-600" "Server evaluates :data expressions, serializes as SX wire format. Client fetches pre-evaluated data, caches with 30s TTL, renders :content locally. 30 unit tests."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 5: Client IO Proxy"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 5: Client IO Proxy"))
(p :class "text-sm text-stone-600" "IO primitives (highlight, asset-url, etc.) proxied to server via registerIoDeps(). Async DOM renderer handles promises through the render tree. Components with IO deps render client-side via server round-trips — no continuations needed."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/language/testing/" :class "font-semibold text-green-800 underline" "Modular Test Architecture"))
(a :href "/(language.(test))" :class "font-semibold text-green-800 underline" "Modular Test Architecture"))
(p :class "text-sm text-stone-600" "Per-module test specs (eval, parser, router, render) with 161 tests. Three runners: Python, Node.js, browser. 5 platform functions, everything else pure SX."))))
;; -----------------------------------------------------------------------
@@ -81,7 +81,7 @@
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial")
(a :href "/etc/plans/fragment-protocol" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
(a :href "/(etc.(plan.fragment-protocol))" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
(p :class "text-sm text-stone-600" "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers."))))
;; -----------------------------------------------------------------------
@@ -95,49 +95,49 @@
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-700 text-white uppercase" "Done")
(a :href "/etc/plans/reader-macros" :class "font-semibold text-stone-800 underline" "Reader Macros"))
(a :href "/(etc.(plan.reader-macros))" :class "font-semibold text-stone-800 underline" "Reader Macros"))
(p :class "text-sm text-stone-600" "# dispatch in parser.sx spec, Python parser.py, hand-written sx.js. Three built-ins (#;, #|...|, #') plus extensible #name dispatch. #z3 demo translates define-primitive to SMT-LIB.")
(p :class "text-sm text-stone-500 mt-1" "48 parser tests (SX + Python), all passing. Rebootstrapped to JS and Python."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/etc/plans/sx-activity" :class "font-semibold text-stone-800 underline" "SX-Activity"))
(a :href "/(etc.(plan.sx-activity))" :class "font-semibold text-stone-800 underline" "SX-Activity"))
(p :class "text-sm text-stone-600" "Federated SX over ActivityPub — 6 phases from SX wire format for activities to the evaluable web on IPFS. Existing AP infrastructure provides the foundation but no SX-specific federation code exists.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: shared/sx/activity.py (SX<->JSON-LD), shared/sx/ipfs.py, shared/sx/ref/ipfs-resolve.sx, shared/sx/registry.py, shared/sx/anchor.py."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/etc/plans/glue-decoupling" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue"))
(a :href "/(etc.(plan.glue-decoupling))" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue"))
(p :class "text-sm text-stone-600" "Eliminate all cross-app model imports by routing through a glue service layer. No glue/ directory exists. Apps are currently decoupled via HTTP interfaces and DTOs instead.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: glue/services/ for pages, page_config, calendars, marketplaces, cart_items, products, post_associations. 25+ cross-app imports to eliminate."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/etc/plans/social-sharing" :class "font-semibold text-stone-800 underline" "Social Network Sharing"))
(a :href "/(etc.(plan.social-sharing))" :class "font-semibold text-stone-800 underline" "Social Network Sharing"))
(p :class "text-sm text-stone-600" "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon via the account service. No models, blueprints, or platform clients created.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: SocialConnection model, social_crypto.py, platform OAuth clients (6), account/bp/social/ blueprint, share button fragment."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Streaming & Suspense"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Streaming & Suspense"))
(p :class "text-sm text-stone-600" "Server streams partially-evaluated SX as IO resolves. ~suspense component renders fallbacks, inline resolution scripts fill in content. Concurrent IO via asyncio, chunked transfer encoding.")
(p :class "text-sm text-stone-500 mt-1" "Demo: " (a :href "/geography/isomorphism/streaming" "/geography/isomorphism/streaming")))
(p :class "text-sm text-stone-500 mt-1" "Demo: " (a :href "/(geography.(isomorphism.streaming))" "/(geography.(isomorphism.streaming))")))
(div :class "rounded border border-green-300 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 7: Full Isomorphism"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 7: Full Isomorphism"))
(p :class "text-sm text-stone-600" "Affinity annotations, render plans, optimistic data updates, offline mutation queue, isomorphic testing harness, universal page descriptor.")
(p :class "text-sm text-stone-500 mt-1" "All 6 sub-phases (7a7f) complete."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/etc/plans/spec-explorer" :class "font-semibold text-stone-800 underline" "Spec Explorer — The Fifth Ring"))
(a :href "/(etc.(plan.spec-explorer))" :class "font-semibold text-stone-800 underline" "Spec Explorer — The Fifth Ring"))
(p :class "text-sm text-stone-600" "SX exploring itself. Per-function cards showing all five rings: SX source (nucleus), Python/JS/Z3 translations (bootstrapper), platform dependencies (bridge), tests and proofs (runtime), and usage examples (application). The documentation is the thing documenting itself.")
(p :class "text-sm text-stone-500 mt-1" "Prerequisite complete: 180+ functions annotated with :effects across all 14 spec files. Three increments: core + translations, bridge + runtime, examples + polish."))))))

197
sx/sx/plans/sx-protocol.sx Normal file
View File

@@ -0,0 +1,197 @@
;; SX Protocol — A Proposal
;; S-expressions as a universal protocol for networked hypermedia.
(defcomp ~plan-sx-protocol-content ()
(~doc-page :title "SX Protocol — A Proposal"
(~doc-section :title "Abstract" :id "abstract"
(p "SX is a Lisp dialect and a proposed universal protocol for networked hypermedia. "
"It replaces URLs, HTTP verbs, query strings, API query languages, and rendering layers "
"with a single unified concept: " (strong "the s-expression") ".")
(p "Everything is an expression. Everything is evaluable. Everything is composable."))
(~doc-section :title "The Problem With the Current Web" :id "problem"
(p "The modern web stack has accumulated layers of incompatible syntax to express "
"what are fundamentally the same things:")
(table :class "w-full text-sm border-collapse mb-4"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "Concern")
(th :class "text-left py-2 pr-4" "Current Syntax")
(th :class "text-left py-2" "Example")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Resource path")
(td :class "py-2 pr-4" "URL segments")
(td :class "py-2 font-mono text-xs" "/users/123/posts"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Query parameters")
(td :class "py-2 pr-4 font-mono text-xs" "?key=value")
(td :class "py-2 font-mono text-xs" "?filter=published&sort=date"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "API queries")
(td :class "py-2 pr-4" "GraphQL / REST")
(td :class "py-2 font-mono text-xs" "{ posts(filter: \"published\") { title } }"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Network verb")
(td :class "py-2 pr-4" "HTTP method")
(td :class "py-2 font-mono text-xs" "GET, POST, PUT"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Real-time")
(td :class "py-2 pr-4" "WebSocket URL")
(td :class "py-2 font-mono text-xs" "wss://site.com/live"))
(tr
(td :class "py-2 pr-4" "Rendering")
(td :class "py-2 pr-4" "HTML + CSS + JS")
(td :class "py-2" "Three separate languages"))))
(p "Each layer invented its own syntax. None of them compose. None of them are executable. "
"None of them are data."))
(~doc-section :title "The SX Approach" :id "approach"
(h3 :class "text-lg font-semibold mt-6 mb-2" "URLs as S-Expressions")
(p "A conventional URL:")
(~doc-code :code (highlight "https://site.com/blog/my-post?filter=published&sort=date" "text"))
(p "As an SX expression:")
(~doc-code :code (highlight "(get.site.com.(blog.(my-post.(filter.published.sort.date))))" "lisp"))
(ul
(li "The protocol/verb is the first atom: " (code "get"))
(li "The domain follows: " (code "site.com"))
(li "Path and parameters collapse into " (strong "one unified nested structure"))
(li "No " (code "?") ", no " (code "&") ", no " (code "/") " — just lists"))
(h3 :class "text-lg font-semibold mt-6 mb-2" "Dots, Not Spaces")
(p "Lisp conventionally uses spaces as separators. In URLs, spaces become " (code "%20")
". SX uses dots instead, which are URL-safe and semantically meaningful — a dot between "
"two atoms is a " (strong "cons pair") ", the fundamental unit of Lisp structure.")
(~doc-code :code (highlight ";; Clean, URL-safe, valid Lisp\n(blog.(filter.published).(sort.date.desc))" "lisp"))
(h3 :class "text-lg font-semibold mt-6 mb-2" "Verbs Are Just Atoms")
(p "HTTP methods are not special syntax — they are simply the first element of the expression:")
(~doc-code :code (highlight "(get.site.com.(post.my-first-post)) ; read\n(post.site.com.(submit-post.(title.hello))) ; write\n(ws.site.com.(live-feed)) ; websocket / subscribe" "lisp"))
(p "No special protocol prefixes. No " (code "https://") " vs " (code "wss://")
". The verb is data, like everything else."))
(~doc-section :title "Graph-SX: Hypermedia Queries" :id "graph-sx"
(p "GraphQL was a major advance over REST, but it made two compromises:")
(ol
(li "Queries are sent as POST bodies, sacrificing cacheability and shareability")
(li "Responses are dead data — JSON that must be separately rendered"))
(p (strong "Graph-SX") " addresses both.")
(h3 :class "text-lg font-semibold mt-6 mb-2" "Queries Are URLs")
(p "Because SX expressions are URLs, every query is a GET request:")
(~doc-code :code (highlight ";; This is a URL and a query simultaneously\n(get.site.com.(blog.(filter.(tag.lisp)).(limit.10)))" "lisp"))
(ul
(li "Fully cacheable by CDNs")
(li "Bookmarkable and shareable")
(li "No POST body required for reads")
(li "Browser back button works correctly"))
(h3 :class "text-lg font-semibold mt-6 mb-2" "Responses Include Rendering")
(p "GraphQL returns data. Graph-SX returns " (strong "hypermedia")
" — data and its presentation in the same expression:")
(~doc-code :code (highlight ";; GraphQL response (dead data)\n{\"title\": \"My Post\", \"body\": \"Hello world\"}\n\n;; Graph-SX response (live hypermedia)\n(article\n (h1 \"My Post\")\n (p \"Hello world\")\n (a (href (get.site.com.(post.next-post))) \"Next\"))" "lisp"))
(p "The server returns what the resource " (strong "is") " and how to "
(strong "present") " it in one unified structure. There is no separate rendering layer.")
(h3 :class "text-lg font-semibold mt-6 mb-2" "Queries Are Transformations")
(p "Because SX is a full programming language, the query and the transformation "
"are the same expression:")
(~doc-code :code (highlight ";; Fetch, filter, and transform in one expression\n(map (lambda (p) (title p))\n (filter published?\n (posts (after \"2025\"))))" "lisp"))
(p "No separate processing step. No client-side data manipulation layer."))
(~doc-section :title "Components" :id "components"
(p "SX supports server-side composable components via the " (code "~") " prefix convention:")
(~doc-code :code (highlight "(~get.everything-under-the-sun)" "lisp"))
(p "A " (code "~component") " is a named server-side function that:")
(ol
(li "Receives the expression as arguments")
(li "Makes onward queries as needed")
(li "Processes and composes results")
(li "Returns hypermedia"))
(p "Components compose naturally:")
(~doc-code :code (highlight "(~page.home\n (~hero.banner)\n (~get.latest-posts.(limit.5))\n (~get.featured.(filter.pinned)))" "lisp"))
(p "This is equivalent to React Server Components — but without a framework, "
"without a build step, and without leaving Lisp."))
(~doc-section :title "Cross-Domain Composition" :id "cross-domain"
(p "Because domain and verb are just atoms, cross-domain queries are structurally "
"identical to local ones:")
(~doc-code :code (highlight ";; Local\n(post.my-first-post)\n\n;; Remote — identical structure, qualified\n(get.site.com.(post.my-first-post))\n\n;; Composed across domains\n(~render\n (get.site.com.(post.my-first-post))\n (get.cdn.com.(image.hero)))" "lisp"))
(p "Network calls are function calls. Remote resources are just namespaced expressions."))
(~doc-section :title "Self-Describing and Introspectable" :id "introspectable"
(p "Because the site is implemented in SX and served as SX, every page is introspectable:")
(~doc-code :code (highlight "(get.sx.dev.(about)) ; the about page\n(get.sx.dev.(source.(about))) ; the SX source for the about page\n(get.sx.dev.(eval.(source.about))) ; re-evaluate it live" "lisp"))
(p "The site is its own documentation. The source is always one expression away."))
(~doc-section :title "Comparison" :id "comparison"
(table :class "w-full text-sm border-collapse mb-4"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "Feature")
(th :class "text-left py-2 pr-4" "REST")
(th :class "text-left py-2 pr-4" "GraphQL")
(th :class "text-left py-2" "Graph-SX")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Reads are GETs")
(td :class "py-2 pr-4 text-green-700" "Yes")
(td :class "py-2 pr-4 text-red-700" "No (POST)")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "CDN cacheable")
(td :class "py-2 pr-4 text-green-700" "Yes")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Nested queries")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-green-700" "Yes")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Response includes rendering")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Query is a transformation")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Composable across domains")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "One syntax for everything")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Bookmarkable deep links")
(td :class "py-2 pr-4 text-green-700" "Yes")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr
(td :class "py-2 pr-4" "Self-hosting / introspectable")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-stone-500" "Partial")
(td :class "py-2 text-green-700" "Yes")))))
(~doc-section :title "Future Direction" :id "future"
(p "The logical conclusion of SX is a " (strong "new internet protocol")
" in which the URL, the HTTP verb, the query language, the response format, "
"and the rendering layer are all unified under one evaluable expression format.")
(~doc-code :code (highlight ";; The entire network request — protocol, domain, verb, query, all one expression\n(get.sx.dev.(blog.(filter.(tag.lisp)).(limit.10)))" "lisp"))
(p "HTTP becomes one possible implementation of a more general principle:")
(blockquote :class "border-l-4 border-violet-300 pl-4 italic text-stone-600 my-4"
(p (strong "Evaluate this expression. Return an expression."))))
(~doc-section :title "Reference Implementation" :id "reference"
(p "SX is implemented in SX. The reference implementation is self-hosting and available at:")
(~doc-code :code (highlight "(get.sx.dev.(source.evaluator))" "lisp"))
(p :class "text-sm text-stone-500 mt-4 italic"
"This proposal was written in conversation with Claude (Anthropic). The ideas are the author's own."))))

302
sx/sx/plans/sx-urls.sx Normal file
View File

@@ -0,0 +1,302 @@
;; SX Expression URLs — GraphSX
;; Plan: replace path-based routing with s-expression URLs where the URL
;; IS the query, the render instruction, and the address — all at once.
(defcomp ~plan-sx-urls-content ()
(~doc-page :title "SX Expression URLs"
(~doc-section :title "Vision" :id "vision"
(p "URLs become s-expressions. The entire routing layer collapses into eval. "
"Every page is a function, every URL is a function call, and the nav tree hierarchy "
"is encoded directly in the nesting of the expression.")
(p "Current URLs like " (code "/language/docs/introduction") " become "
(code "/(language.(doc.introduction))") ". Dots replace spaces as the URL-friendly "
"separator — they are unreserved in RFC 3986, never percent-encoded, and visually clean. "
"The parser treats dot as whitespace: " (code "s/./ /") " before parsing as SX.")
(~doc-code :code (highlight
";; Current → SX URLs (dots = spaces)\n/language/specs/signals → /(language.(spec.signals))\n/language/specs/explore/signals → /(language.(spec.(explore.signals)))\n/language/docs/introduction → /(language.(doc.introduction))\n/etc/plans/spec-explorer → /(etc.(plan.spec-explorer))\n\n;; Direct component access — any defcomp is addressable\n/(~essay-sx-sucks)\n/(~plan-sx-urls-content)\n/(~bundle-analyzer-content)"
"lisp")))
(~doc-section :title "Scoping — The 30-Year Ambiguity, Fixed" :id "scoping"
(p "REST URLs have an inherent ambiguity: does a filter/parameter apply to "
"the last segment, or the whole path? Consider:")
(~doc-code :code (highlight
";; REST — ambiguous:\n/users/123/posts?filter=published\n;; Is the filter scoped to posts? Or to the user? Or the whole query?\n;; Nobody knows. Conventions vary. Documentation required.\n\n;; SX URLs — explicit scoping via nesting:\n/(hello.(sailor.(filter.hhh))) ;; filter scoped to sailor\n/(hello.sailor.(filter.hhh)) ;; filter scoped to hello\n\n;; These mean different things, both expressible.\n;; Parens make scope visible. No ambiguity. No documentation needed."
"lisp"))
(p "This is not a minor syntactic preference. REST has never been able to express "
"the difference between a parameter scoped to a sub-resource and one scoped to the "
"parent. Query strings are flat — " (code "?filter=x") " applies to... what, exactly? "
"The last path segment? The whole URL? It depends on the API.")
(p "S-expression nesting makes scope " (em "structural") ". "
"The paren boundaries are the scope boundaries. "
"What took REST 30 years of convention documents to approximate, "
"SX URLs express in the syntax itself."))
(~doc-section :title "Dots as URL-Safe Whitespace" :id "dots"
(p "Spaces in URLs are ugly — they become " (code "%20") " in copy-paste, curl, logs, and proxies. "
"Dots are unreserved in RFC 3986, never encoded, and read naturally as \"drill down.\"")
(p "The rule is simple: " (strong "dot = space, nothing more") ". "
"Parens carry all the structural meaning. Dots are syntactic sugar for URLs only:")
(~doc-code :code (highlight
";; These are identical after dot→space transform:\n/(language.(doc.introduction)) → (language (doc introduction))\n/(geography.(hypermedia.(reference.attributes)))\n → (geography (hypermedia (reference attributes)))\n\n;; Parens are still required for nesting:\n/(language.doc.introduction) → (language doc introduction)\n;; = language(\"doc\", \"introduction\") — WRONG\n\n;; Correct nesting:\n/(language.(doc.introduction)) → (language (doc introduction))\n;; = language(doc(\"introduction\")) — RIGHT"
"lisp"))
(p "The server's URL handler does one thing before parsing: "
(code "url_expr = raw_path[1:].replace('.', ' ')") ". Then standard SX parsing takes over."))
(~doc-section :title "The Lisp Tax" :id "parens"
(p "People will hate the parentheses. But consider what developers already accept:")
(~doc-code :code (highlight
";; Developers happily write this every day:\nhttps://api.site.com/v2/users/123/posts?filter=published&sort=date&order=desc&limit=10&offset=20\n\n;; And they would complain about this?\nhttps://site.com/(users.(posts.123.(filter.published.sort.date.limit.10)))\n\n;; The second is shorter, structured, unambiguous, and composable."
"lisp"))
(p "The real question: who is reading these URLs?")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "End users") " barely look at URLs anymore — they live in address bars people ignore")
(li (strong "Developers") " will love it once they get it — it is powerful and consistent")
(li (strong "Crawlers and bots") " do not care at all"))
(p "And this site is " (em "about SX, implemented in SX") ". "
"The audience is exactly the people who will love the parens. "
"Every URL on the site is a live example of SX in action. "
"Visiting a page is evaluating an expression."))
(~doc-section :title "The Site Is a REPL" :id "repl"
(p "The address bar becomes the input line of a REPL. The page is the output.")
(~doc-code :code (highlight
"/(about) ;; renders the about page\n/(source.(about)) ;; returns the SX source for the about page\n/(eval.(source.(about))) ;; re-evaluates it live\n\n;; The killer demo:\n/(eval.(map.double.(list.1.2.3))) ;; actually returns (2 4 6)\n\n;; The website IS a REPL. The address bar IS the input."
"lisp"))
(p "You do not need to explain what SX is. You show someone a URL and they "
"immediately understand the entire language and philosophy. "
"The whole site becomes a self-hosting proof of concept — "
"that is not just elegant, that is the pitch."))
(~doc-section :title "Components as Query Resolvers" :id "resolvers"
(p "The " (code "~") " sigil means \"find and execute this component.\" "
"Components can make onward queries, process results, and return composed content — "
"like server-side includes but Lispy and composable.")
(~doc-code :code (highlight
";; ~get is a component that fetches, processes, and returns\n/(~get.everything-under-the-sun)\n\n;; The flow:\n;; 1. Server finds ~get component in env\n;; 2. ~get makes onward queries\n;; 3. Processes and transforms results\n;; 4. Returns composed hypermedia\n\n;; Because it's all SX, you nest and compose:\n/(~page.home\n .(~hero.banner)\n .(~get.latest-posts.(limit.5))\n .(~get.featured.(filter.pinned)))\n\n;; Each ~component is independently:\n;; - cacheable (by its expression)\n;; - reusable (same component, different args)\n;; - testable (evaluate in isolation)"
"lisp"))
(p "This is what React Server Components are trying to do — server-side data resolution "
"composed with client-side rendering. Except React needs a framework, a bundler, "
"a serialization protocol, and \"use server\" pragmas. "
"SX gets it from a sigil and an evaluator."))
(~doc-section :title "HTTP Semantics — REST Re-Aligned" :id "http"
(p "GraphQL uses POST for queries even though they are pure reads — "
"because queries can be long and the body feels more natural for structured data. "
"But this violates HTTP semantics: POST implies side effects, "
"GET requests are cacheable, bookmarkable, shareable.")
(p "SX URLs naturally align with HTTP because the query " (em "is") " the URL:")
(div :class "overflow-x-auto mt-4"
(table :class "w-full text-sm text-left"
(thead
(tr :class "border-b border-stone-200"
(th :class "py-2 px-3 font-semibold text-stone-700" "Method")
(th :class "py-2 px-3 font-semibold text-stone-700" "Semantics")
(th :class "py-2 px-3 font-semibold text-stone-700" "Example")))
(tbody :class "text-stone-600"
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "GET")
(td :class "py-2 px-3" "Pure evaluation — cacheable, bookmarkable")
(td :class "py-2 px-3 font-mono text-sm" "GET /(language.(doc.intro))"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "POST")
(td :class "py-2 px-3" "Side effects — mutations, submissions")
(td :class "py-2 px-3 font-mono text-sm" "POST /(submit-post)")))))
(p "CDN caching of Lisp hypermedia just works. "
"This is what REST always wanted but GraphQL abandoned. "
"SX re-aligns with HTTP while being more powerful than both."))
(~doc-section :title "GraphSX — This Is a Query Language" :id "graphsx"
(p "The SX URL scheme is not just a routing convention — it is the emergence of "
(strong "GraphSX") ": GraphQL but Lisp. The structural parallel is exact:")
(div :class "overflow-x-auto mt-4"
(table :class "w-full text-sm text-left"
(thead
(tr :class "border-b border-stone-200"
(th :class "py-2 px-3 font-semibold text-stone-700" "Concept")
(th :class "py-2 px-3 font-semibold text-stone-700" "GraphQL")
(th :class "py-2 px-3 font-semibold text-stone-700" "GraphSX")))
(tbody :class "text-stone-600"
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Single endpoint")
(td :class "py-2 px-3" (code "/graphql"))
(td :class "py-2 px-3" "Catch-all " (code "/<path:expr>")))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Query structure")
(td :class "py-2 px-3" "Nested fields " (code "{ language { doc { ... } } }"))
(td :class "py-2 px-3" "Nested s-expressions " (code "(language.(doc....))")))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Resolvers")
(td :class "py-2 px-3" "Per-field functions")
(td :class "py-2 px-3" "Page/section functions + " (code "~components")))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Fragments")
(td :class "py-2 px-3" "Named reusable selections")
(td :class "py-2 px-3" "Components (" (code "defcomp") ")"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Schema")
(td :class "py-2 px-3" "Type definitions")
(td :class "py-2 px-3" "Page function registry + component env"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Scoping")
(td :class "py-2 px-3" "Flat — " (code "?filter=x") " applies to... what?")
(td :class "py-2 px-3" "Structural — parens ARE scope boundaries"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Transport")
(td :class "py-2 px-3" "POST JSON (violates HTTP GET semantics)")
(td :class "py-2 px-3" "GET " (code "/(expr)") " — cacheable, bookmarkable"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Response")
(td :class "py-2 px-3" "JSON data (needs separate rendering)")
(td :class "py-2 px-3" "Content " (em "and") " data — response is already meaningful")))))
(p "The killer difference: in GraphQL, query and rendering are separate concerns — "
"you fetch JSON, then a frontend renders it. In GraphSX, "
(strong "the query language and the rendering language are the same thing") ". "
(code "(language.(doc.introduction))") " is simultaneously:")
(ol :class "space-y-1 text-stone-600 list-decimal pl-5"
(li "A " (strong "query") " — resolve the docs/introduction content within the language section")
(li "A " (strong "render instruction") " — render it inside a language section wrapper with nav context")
(li "A " (strong "URL") " — addressable, bookmarkable, shareable, cacheable"))
(p "This is what Lisp does. The uniform syntax means there is no boundary between "
"routing DSL, query language, template language, and component system. "
"They are all s-expressions evaluated in the same environment. "
"GraphQL had to invent a special syntax for queries because JSON is data, not code. "
"S-expressions are both."))
(~doc-section :title "Direct Component URLs" :id "components"
(p "Any " (code "defcomp") " is directly addressable via its " (code "~name") ". "
"The URL evaluator sees " (code "~essay-sx-sucks") ", looks it up in the component env, "
"evaluates it, wraps in " (code "~sx-doc") ", and returns.")
(~doc-code :code (highlight
";; Page functions are convenience wrappers:\n/(etc.(essay.sx-sucks)) ;; dispatches via case statement\n\n;; But you can bypass them entirely:\n/(~essay-sx-sucks) ;; direct component — no routing needed\n\n;; Every defcomp is instantly URL-accessible:\n/(~plan-sx-urls-content) ;; this very page\n/(~bundle-analyzer-content) ;; tools\n/(~docs-evaluator-content) ;; docs"
"lisp"))
(p "New components are instantly URL-accessible without routing wiring. "
"Debugging is trivial — render any component in isolation."))
(~doc-section :title "URL Special Forms" :id "special-forms"
(p "URL-level functions that transform how content is resolved or displayed:")
(div :class "overflow-x-auto mt-4"
(table :class "w-full text-sm text-left"
(thead
(tr :class "border-b border-stone-200"
(th :class "py-2 px-3 font-semibold text-stone-700" "Form")
(th :class "py-2 px-3 font-semibold text-stone-700" "Example")
(th :class "py-2 px-3 font-semibold text-stone-700" "Effect")))
(tbody :class "text-stone-600"
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "source")
(td :class "py-2 px-3 font-mono text-sm" "/(source.(~essay-sx-sucks))")
(td :class "py-2 px-3" "Show defcomp source instead of rendering"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "inspect")
(td :class "py-2 px-3 font-mono text-sm" "/(inspect.(language.(doc.primitives)))")
(td :class "py-2 px-3" "Deps, CSS classes, render plan, IO requirements"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "diff")
(td :class "py-2 px-3 font-mono text-sm" "/(diff.(spec.signals).(spec.eval))")
(td :class "py-2 px-3" "Side-by-side two pages"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "search")
(td :class "py-2 px-3 font-mono text-sm" "/(search.\"define\".:in.(spec.signals))")
(td :class "py-2 px-3" "Grep within a page or spec"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "raw")
(td :class "py-2 px-3 font-mono text-sm" "/(raw.(~some-component))")
(td :class "py-2 px-3" "Skip ~sx-doc nav wrapping"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "eval")
(td :class "py-2 px-3 font-mono text-sm" "/(eval.(map.double.(list.1.2.3)))")
(td :class "py-2 px-3" "Arbitrary evaluation — the URL bar is a REPL"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "json")
(td :class "py-2 px-3 font-mono text-sm" "/(json.(language.(doc.primitives)))")
(td :class "py-2 px-3" "Return data as JSON — pure query mode"))))))
(~doc-section :title "Evaluation Model" :id "eval"
(p "The URL path (after stripping " (code "/") " and replacing dots with spaces) "
"is parsed as SX and evaluated with a " (strong "soft eval") ": "
"known function names are called; unknown symbols self-evaluate to their name as a string; "
"components (" (code "~name") ") are looked up in the component env.")
(~doc-code :code (highlight
"/(language.(doc.introduction))\n\n;; After dot→space: (language (doc introduction))\n;; 1. Eval `introduction` → not a known function → \"introduction\"\n;; 2. Eval (doc \"introduction\") → call doc(\"introduction\") → page content\n;; 3. Eval (language content) → call language(content) → passes through\n;; 4. Router wraps result in (~sx-doc :path \"(language (doc introduction))\" ...)\n\n/(~essay-sx-sucks)\n;; 1. Eval ~essay-sx-sucks → component lookup → evaluate → content\n;; 2. Router wraps in ~sx-doc"
"lisp"))
(~doc-subsection :title "Section Functions"
(p "Structural functions that encode hierarchy and pass through content:")
(~doc-code :code (highlight
"(define language\n (fn (&rest args)\n (if (empty? args) (language-index-content) (first args))))\n\n(define geography\n (fn (&rest args)\n (if (empty? args) (geography-index-content) (first args))))\n\n;; Sub-sections also pass through\n(define hypermedia\n (fn (&rest args)\n (if (empty? args) (hypermedia-index-content) (first args))))"
"lisp")))
(~doc-subsection :title "Page Functions"
(p "Leaf functions that dispatch to content components. "
"Data-dependent pages call helpers directly — the async evaluator handles IO:")
(~doc-code :code (highlight
"(define doc\n (fn (&rest args)\n (let ((slug (first-or-nil args)))\n (if (nil? slug)\n (~docs-introduction-content)\n (case slug\n \"introduction\" (~docs-introduction-content)\n \"getting-started\" (~docs-getting-started-content)\n ...)))))\n\n(define bootstrapper\n (fn (&rest args)\n (let ((slug (first-or-nil args))\n (data (when slug (bootstrapper-data slug))))\n (if (nil? slug)\n (~bootstrappers-index-content)\n (if (get data \"bootstrapper-not-found\")\n (~spec-not-found :slug slug)\n (case slug\n \"python\" (~bootstrapper-py-content ...)\n ...))))))"
"lisp"))))
(~doc-section :title "The Catch-All Route" :id "route"
(p "The entire routing layer becomes one handler:")
(~doc-code :code (highlight
"@app.get(\"/\")\nasync def sx_home():\n return await eval_sx_url(\"/\")\n\n@app.get(\"/<path:expr>\")\nasync def sx_eval_route(expr):\n return await eval_sx_url(f\"/{expr}\")"
"python"))
(p (code "eval_sx_url") " in seven steps:")
(ol :class "space-y-1 text-stone-600 list-decimal pl-5"
(li "URL-decode the path")
(li "Replace dots with spaces")
(li "Parse as SX expression")
(li "Auto-quote unknown symbols (slugs become strings)")
(li "Evaluate with components + helpers + page/section functions in env")
(li "Wrap result in " (code "~sx-doc") " with the URL expression as " (code ":path"))
(li "Return HTML or SX wire format depending on HTMX request"))
(p "Defhandler API endpoints and Python demo routes are registered " (em "before") " the catch-all, "
"so they match first."))
(~doc-section :title "Composability" :id "composability"
(~doc-code :code (highlight
";; Direct component access\n/(~essay-sx-sucks)\n/(~spec-explorer-content)\n\n;; URL special forms\n/(source.(~essay-sx-sucks)) ;; view defcomp source\n/(inspect.(language.(doc.primitives))) ;; deps, render plan\n/(diff.(language.(spec.signals)).(language.(spec.eval))) ;; side by side\n/(eval.(map.double.(list.1.2.3))) ;; REPL in the URL bar\n\n;; Components as query resolvers\n/(~page.home\n .(~hero.banner)\n .(~get.latest-posts.(limit.5))\n .(~get.featured.(filter.pinned)))\n\n;; Scoping is explicit\n/(users.(posts.123.(filter.published))) ;; filter scoped to posts\n/(users.posts.123.(filter.published)) ;; filter scoped to users\n\n;; Cross-service (future)\n/(market.(product.42.:fields.(name.price)))\n/(subscribe.(etc.(plan.status)))"
"lisp")))
(~doc-section :title "Implementation Phases" :id "phases"
(div :class "space-y-4"
(div :class "rounded border border-violet-200 bg-violet-50 p-4"
(p :class "font-semibold text-violet-800 mb-2" "Phase 1: Page Functions + Catch-All Route")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Create " (code "page-functions.sx") " — ~20 section + ~15 page functions")
(li "Create " (code "sx_router.py") " — URL parser (dot→space), soft eval, streaming detection")
(li "Replace " (code "auto_mount_pages") " with catch-all")
(li "Direct " (code "~component") " URL support")))
(div :class "rounded border border-blue-200 bg-blue-50 p-4"
(p :class "font-semibold text-blue-800 mb-2" "Phase 2: Navigation Data")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Rewrite ~200 " (code ":href") " values to dot-separated SX URLs")
(li "Rewrite " (code "resolve-nav-path") " for tree-descent matching")))
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4"
(p :class "font-semibold text-emerald-800 mb-2" "Phase 3: Backward Compatibility")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "301 redirects from all old paths to new SX URLs")
(li "Algorithmic pattern matching — ~25 regex rules")))
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
(p :class "font-semibold text-amber-800 mb-2" "Phase 4: URL Special Forms")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (code "source") ", " (code "inspect") ", " (code "diff") ", " (code "raw") ", " (code "eval") ", " (code "json"))
(li "Components as query resolvers (" (code "~get") ", " (code "~page") ")")))
(div :class "rounded border border-rose-200 bg-rose-50 p-4"
(p :class "font-semibold text-rose-800 mb-2" "Phase 5: Client-Side Routing")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Update " (code "_build_pages_sx()") " for function-based page registry")
(li "Client-side dot→space→parse→eval pipeline")
(li "Rebootstrap " (code "sx-ref.js") " and " (code "sx_ref.py"))))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(p :class "font-semibold text-stone-800 mb-2" "Phase 6: Cleanup")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Delete " (code "docs.sx") " (all 46 defpages)")
(li "Grep content files for stale old-style hrefs")))))
(~doc-section :title "What Stays the Same" :id "unchanged"
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "Defhandler API paths") " — registered before the catch-all, match first")
(li (strong "Python demo routes") " — registered via blueprint before the catch-all")
(li (strong "All other services") " — blog, market, etc. keep defpage routing")
(li (strong "Component definitions") " — all " (code ".sx") " files unchanged")
(li (strong "Page helpers") " — called from page functions instead of defpage " (code ":data"))
(li (strong "Layout, CSS, shell, rendering pipeline") " — no changes")))))

View File

@@ -8,7 +8,7 @@
(~doc-section :title "The Opportunity" :id "opportunity"
(p "SX already has types. Every primitive in " (code "primitives.sx") " declares " (code ":returns \"number\"") " or " (code ":returns \"boolean\"") ". Every IO primitive in " (code "boundary.sx") " declares " (code ":returns \"dict?\"") " or " (code ":returns \"any\"") ". Component params are named. The information exists — nobody checks it.")
(p "A gradual type system makes this information useful. Annotations are optional. Unannotated code works exactly as before. Annotated code gets checked at registration time — zero runtime cost, errors before any request is served. The checker is a spec module (" (code "types.sx") "), bootstrapped to every host.")
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") " already verifies primitive properties by exhaustive search. Types serve a different purpose: they catch " (strong "composition errors") " — wrong argument passed to a component, mismatched return type piped into another function, missing keyword arg. The kind of bug you find by reading the stack trace and slapping your forehead."))
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") " already verifies primitive properties by exhaustive search. Types serve a different purpose: they catch " (strong "composition errors") " — wrong argument passed to a component, mismatched return type piped into another function, missing keyword arg. The kind of bug you find by reading the stack trace and slapping your forehead."))
;; -----------------------------------------------------------------------
;; What already exists
@@ -116,7 +116,7 @@
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "Runtime values.") " " (code "(if condition 42 \"hello\")") " — the type is " (code "(or number string)") ". The checker doesn't know which branch executes.")
(li (strong "Dict key presence (yet).") " " (code "(get user \"name\")") " — the checker knows " (code "get") " returns " (code "any") " but doesn't track which keys a dict has. Phase 6 (" (code "deftype") " records) will enable this.")
(li (strong "Termination.") " That's " (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") "'s domain.")
(li (strong "Termination.") " That's " (a :href "/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") "'s domain.")
(li (strong "Full algebraic effects.") " The effect system (Phase 7) checks static effect annotations — it does not provide algebraic effect handlers, effect polymorphism, or continuation-based effect dispatch. That door remains open for the future.")))
@@ -231,7 +231,7 @@
;; -----------------------------------------------------------------------
(~doc-section :title "Types vs Proofs" :id "types-vs-proofs"
(p (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:")
(p (a :href "/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
@@ -483,10 +483,10 @@
(~doc-section :title "Relationships" :id "relationships"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
(li (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.")
(li (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "Environment Images") " — the type registry serializes into the image. Type checking happens once at image build time, not on every startup.")
(li (a :href "/etc/plans/runtime-slicing" :class "text-violet-700 underline" "Runtime Slicing") " — types.sx is a registration-time module, not a runtime module. It doesn't ship to the client. Zero impact on bundle size."))
(li (a :href "/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
(li (a :href "/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.")
(li (a :href "/(etc.(plan.environment-images))" :class "text-violet-700 underline" "Environment Images") " — the type registry serializes into the image. Type checking happens once at image build time, not on every startup.")
(li (a :href "/(etc.(plan.runtime-slicing))" :class "text-violet-700 underline" "Runtime Slicing") " — types.sx is a registration-time module, not a runtime module. It doesn't ship to the client. Zero impact on bundle size."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "primitives.sx (return + param types), boundary.sx (IO return types + IO classification), eval.sx (defcomp/deftype/defeffect parsing), deps.sx (IO detection — effects formalize this). " (strong "New: ") (code "types.sx") " spec module, type annotations in " (code "parse-comp-params") ", " (code "deftype") " declaration form, " (code "defeffect") " declaration form."))

View File

@@ -28,7 +28,7 @@
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "Swap inside island: ") "Signals survive. The lake content is replaced but the island's signal closures are untouched. Effects re-bind to new DOM nodes if needed.")
(li (strong "Swap outside island: ") "Signals survive. The island is not affected by swaps to other parts of the page.")
(li (strong "Swap replaces island: ") "Signals are " (em "lost") ". The island is disposed. This is where " (a :href "/geography/reactive/named-stores" :sx-get "/geography/reactive/named-stores" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "named stores") " come in — they persist at page level, surviving island destruction.")))
(li (strong "Swap replaces island: ") "Signals are " (em "lost") ". The island is disposed. This is where " (a :href "/(geography.(reactive.named-stores))" :sx-get "/(geography.(reactive.named-stores))" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "named stores") " come in — they persist at page level, surviving island destruction.")))
(~doc-section :title "Spec" :id "spec"
(p "The event bridge is spec'd in " (code "signals.sx") " (sections 12-13). Three functions:")

View File

@@ -145,7 +145,7 @@
(td :class "px-3 py-2 text-stone-700" "Phase 2 remaining")
(td :class "px-3 py-2 text-stone-500 font-medium" "P2")
(td :class "px-3 py-2 font-mono text-xs text-stone-500"
(a :href "/geography/reactive/phase2" :sx-get "/geography/reactive/phase2" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "Error boundaries + resource + patterns")))
(a :href "/(geography.(reactive.phase2))" :sx-get "/(geography.(reactive.phase2))" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "Error boundaries + resource + patterns")))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Error boundaries")
(td :class "px-3 py-2 text-green-700 font-medium" "Done")

View File

@@ -46,7 +46,7 @@
(~doc-section :title "htmx Lakes" :id "lakes"
(p "An htmx lake is server-driven content " (em "inside") " a reactive island. The island provides the reactive boundary; the lake content is swapped via " (code "sx-get") "/" (code "sx-post") " like normal hypermedia.")
(p "This works because signals live in JavaScript closures, not in the DOM. When a swap replaces lake content, the island's signals are unaffected. The lake can communicate back to the island via the " (a :href "/geography/reactive/event-bridge" :sx-get "/geography/reactive/event-bridge" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "event bridge") ".")
(p "This works because signals live in JavaScript closures, not in the DOM. When a swap replaces lake content, the island's signals are unaffected. The lake can communicate back to the island via the " (a :href "/(geography.(reactive.event-bridge))" :sx-get "/(geography.(reactive.event-bridge))" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "event bridge") ".")
(~doc-subsection :title "Navigation scenarios"
(div :class "space-y-3"
@@ -155,7 +155,7 @@
(li (strong "Spec-first.") " Signal semantics live in " (code "signals.sx") ". Bootstrapped to JS and Python. The same primitives will work in future hosts — Go, Rust, native.")
(li (strong "No build step.") " Reactive bindings are created at runtime during DOM rendering. No JSX compilation, no Babel transforms, no Vite plugins."))
(p :class "mt-4" "The recommendation from the " (a :href "/etc/essays/client-reactivity" :class "text-violet-700 underline" "Client Reactivity") " essay was: \"Tier 4 probably never.\" This plan is what happens when the answer changes. The design avoids every footgun that essay warns about — no useState cascading to useEffect cascading to Context cascading to a state management library. Signals are one primitive. Islands are one boundary. The rest is composition."))))
(p :class "mt-4" "The recommendation from the " (a :href "/(etc.(essay.client-reactivity))" :class "text-violet-700 underline" "Client Reactivity") " essay was: \"Tier 4 probably never.\" This plan is what happens when the answer changes. The design avoids every footgun that essay warns about — no useState cascading to useEffect cascading to Context cascading to a state management library. Signals are one primitive. Islands are one boundary. The rest is composition."))))
;; ---------------------------------------------------------------------------

View File

@@ -14,9 +14,9 @@
"Pages with data dependencies fall back to "
(span :class "text-amber-700 font-medium" "server fetch")
" transparently. Powered by "
(a :href "/language/specs/router" :class "text-violet-700 underline" "router.sx")
(a :href "/(language.(spec.router))" :class "text-violet-700 underline" "router.sx")
" route matching and "
(a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
(a :href "/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
" IO detection.")
(div :class "mb-8 grid grid-cols-4 gap-4"

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"))

View File

@@ -98,27 +98,27 @@ Per-spec platform functions:
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Test specs")
(div :class "grid grid-cols-1 md:grid-cols-2 gap-4"
(a :href "/language/testing/eval" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/(language.(test.eval))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Evaluator")
(p :class "text-sm text-stone-500" "81 tests — literals, arithmetic, strings, lists, dicts, special forms, lambdas, components, macros")
(p :class "text-xs text-violet-600 mt-1" "test-eval.sx"))
(a :href "/language/testing/parser" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/(language.(test.parser))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Parser")
(p :class "text-sm text-stone-500" "39 tests — tokenization, parsing, escape sequences, quote sugar, serialization, round-trips")
(p :class "text-xs text-violet-600 mt-1" "test-parser.sx"))
(a :href "/language/testing/router" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/(language.(test.router))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Router")
(p :class "text-sm text-stone-500" "18 tests — path splitting, pattern parsing, segment matching, parameter extraction")
(p :class "text-xs text-violet-600 mt-1" "test-router.sx"))
(a :href "/language/testing/render" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/(language.(test.render))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Renderer")
(p :class "text-sm text-stone-500" "23 tests — elements, attributes, void elements, fragments, escaping, control flow, components")
(p :class "text-xs text-violet-600 mt-1" "test-render.sx"))
(a :href "/language/testing/deps" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/(language.(test.deps))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Dependencies")
(p :class "text-sm text-stone-500" "33 tests — scan-refs, transitive-deps, components-needed, IO detection, purity classification")
(p :class "text-xs text-violet-600 mt-1" "test-deps.sx"))
(a :href "/language/testing/engine" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/(language.(test.engine))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Engine")
(p :class "text-sm text-stone-500" "37 tests — parse-time, trigger specs, swap specs, retry logic, param filtering")
(p :class "text-xs text-violet-600 mt-1" "test-engine.sx"))))

359
sx/sxc/pages/sx_router.py Normal file
View File

@@ -0,0 +1,359 @@
"""GraphSX URL router — evaluate s-expression URLs.
Handles URLs like /(language.(doc.introduction)) by:
1. Converting dots to spaces (dot = whitespace sugar)
2. Parsing the path as an SX expression
3. Auto-quoting unknown symbols to strings (slugs)
4. Evaluating the expression against page functions
5. Wrapping the result in (~sx-doc :path "..." content)
6. Returning full page or OOB response
Special cases:
- "/" → home page
- Streaming pages → delegate to existing PageDef infrastructure
- Direct component URLs: /(~component-name) → render component
"""
from __future__ import annotations
import logging
import re
from typing import Any
from urllib.parse import unquote
logger = logging.getLogger("sx.router")
# ---------------------------------------------------------------------------
# Page function names — known in the eval env, NOT auto-quoted
# ---------------------------------------------------------------------------
# Section functions (structural, pass through)
_SECTION_FNS = {
"home", "language", "geography", "applications", "etc",
"hypermedia", "reactive", "marshes", "isomorphism",
}
# Page functions (leaf dispatch)
_PAGE_FNS = {
"doc", "spec", "explore", "bootstrapper", "test",
"reference", "reference-detail", "example",
"cssx", "protocol", "essay", "philosophy", "plan",
}
# All known function names (don't auto-quote these)
_KNOWN_FNS = _SECTION_FNS | _PAGE_FNS | {
# Helpers defined in page-functions.sx
"make-spec-files", "page-helpers-demo-content-fn",
}
# ---------------------------------------------------------------------------
# Auto-quote slugs — convert unknown symbols to strings
# ---------------------------------------------------------------------------
def auto_quote_slugs(expr: Any, known_fns: set[str]) -> Any:
"""Walk AST and replace unknown symbols with their name as a string.
Known function names stay as symbols so they resolve to callables.
Everything else (slugs like 'introduction', 'getting-started') becomes
a string literal — no quoting needed in the URL.
"""
from shared.sx.types import Symbol, Keyword
if isinstance(expr, Symbol):
if expr.name in known_fns or expr.name.startswith("~"):
return expr
return expr.name # auto-quote to string
if isinstance(expr, list) and expr:
head = expr[0]
# Head stays as-is (it's the function position)
result = [head]
for item in expr[1:]:
result.append(auto_quote_slugs(item, known_fns))
return result
return expr
# ---------------------------------------------------------------------------
# Dot → space conversion
# ---------------------------------------------------------------------------
def _dots_to_spaces(s: str) -> str:
"""Convert dots to spaces in URL expressions.
Dots are unreserved in RFC 3986 and serve as URL-safe whitespace.
Applied before SX parsing: /(language.(doc.introduction))
becomes /(language (doc introduction)).
"""
return s.replace(".", " ")
# ---------------------------------------------------------------------------
# Build expression from URL path
# ---------------------------------------------------------------------------
def _parse_url_path(raw_path: str) -> Any:
"""Parse a URL path into an SX AST.
Returns the parsed expression, or None if the path isn't an SX URL.
"""
from shared.sx.parser import parse as sx_parse
from shared.sx.types import Symbol
path = unquote(raw_path).strip()
if path == "/":
return [Symbol("home")]
# SX URLs start with /( — e.g. /(language (doc intro))
if path.startswith("/(") and path.endswith(")"):
sx_source = _dots_to_spaces(path[1:]) # strip leading /
return sx_parse(sx_source)
# Direct component URLs: /~component-name
if path.startswith("/~"):
name = path[1:] # keep the ~ prefix
sx_source = _dots_to_spaces(name)
if " " in sx_source:
# /~comp.arg1.arg2 → (~comp arg1 arg2)
return sx_parse(f"({sx_source})")
return [Symbol(sx_source)]
return None # not an SX URL
# ---------------------------------------------------------------------------
# Streaming detection
# ---------------------------------------------------------------------------
_STREAMING_PAGES = {
# URL slug → defpage name for streaming pages
"streaming": "streaming-demo",
}
def _is_streaming_url(expr: Any) -> str | None:
"""Check if expression resolves to a streaming page.
Returns the defpage name if streaming, None otherwise.
Detects: (geography (isomorphism "streaming"))
"""
from shared.sx.types import Symbol
# Check for (geography (isomorphism "streaming"))
if not isinstance(expr, list) or len(expr) < 2:
return None
# Walk nested structure to find isomorphism call
def _find_slug(e: Any) -> str | None:
if not isinstance(e, list) or not e:
return None
head = e[0]
if isinstance(head, Symbol) and head.name == "isomorphism":
for arg in e[1:]:
if isinstance(arg, str) and arg in _STREAMING_PAGES:
return _STREAMING_PAGES[arg]
if isinstance(arg, Symbol) and arg.name in _STREAMING_PAGES:
return _STREAMING_PAGES[arg.name]
# Recurse into nested calls
for arg in e[1:]:
result = _find_slug(arg)
if result:
return result
return None
return _find_slug(expr)
# ---------------------------------------------------------------------------
# Main eval handler
# ---------------------------------------------------------------------------
async def eval_sx_url(raw_path: str) -> Any:
"""Evaluate an SX URL and return the response.
This is the main entry point for the catch-all route handler.
Returns a Quart Response object, or None if the path isn't an SX URL.
"""
from quart import make_response, Response
from shared.sx.jinja_bridge import get_component_env, _get_request_context
from shared.sx.pages import get_page, get_page_helpers, _eval_slot
from shared.sx.types import Symbol, Keyword
from shared.sx.parser import serialize
from shared.sx.helpers import full_page_sx, oob_page_sx, sx_response
from shared.sx.page import get_template_context
from shared.browser.app.utils.htmx import is_htmx_request
# Parse URL
expr = _parse_url_path(raw_path)
if expr is None:
return None # not an SX URL — let other handlers try
# Check for streaming page BEFORE auto-quoting
streaming_page = _is_streaming_url(expr)
if streaming_page:
# Delegate to existing streaming PageDef infrastructure
page_def = get_page("sx", streaming_page)
if page_def:
from shared.sx.pages import execute_page_streaming, execute_page_streaming_oob
if is_htmx_request():
gen = await execute_page_streaming_oob(page_def, "sx")
return Response(gen, content_type="text/sx; charset=utf-8")
gen = await execute_page_streaming(page_def, "sx")
return Response(gen, content_type="text/html; charset=utf-8")
# Build env: components + page helpers (includes page functions from define)
env = dict(get_component_env())
env.update(get_page_helpers("sx"))
# Auto-quote unknown symbols (slugs become strings)
known = _KNOWN_FNS | set(env.keys())
quoted_expr = auto_quote_slugs(expr, known)
ctx = _get_request_context()
# Use raw URL path for nav matching — dots in hrefs match dots in URLs
path_str = raw_path
# Check if expression head is a component (~name) — if so, skip
# async_eval and pass directly to _eval_slot. Components contain HTML
# tags that only the aser path can handle, not eval_expr.
head = quoted_expr[0] if isinstance(quoted_expr, list) and quoted_expr else None
is_component_call = (
isinstance(head, Symbol)
and head.name.startswith("~")
)
if is_component_call:
# Direct component URL: /(~essay-sx-sucks) or /(~comp :key val)
# Pass straight to _eval_slot — aser handles component expansion.
page_ast = quoted_expr
else:
# Two-phase evaluation for page function calls:
# Phase 1: Evaluate the page function expression with async_eval.
# Page functions return QUOTED expressions (unevaluated ASTs like
# [Symbol("~docs-intro-content")] or quasiquoted trees with data).
# This phase resolves routing + fetches data, but does NOT expand
# components or handle HTML tags (eval_expr can't do that).
# Phase 2: Wrap the returned AST in (~sx-doc :path "..." <ast>) and
# pass to _eval_slot (aser), which expands components and handles
# HTML tags correctly.
import os
if os.environ.get("SX_USE_REF") == "1":
from shared.sx.ref.async_eval_ref import async_eval
else:
from shared.sx.async_eval import async_eval
try:
page_ast = await async_eval(quoted_expr, env, ctx)
except Exception as e:
logger.error("SX URL page-fn eval failed for %s: %s", raw_path, e, exc_info=True)
return None
# page_ast is a quoted expression (list of Symbols/Keywords/data) or nil
if page_ast is None:
page_ast = [] # empty content for sections with no index
wrapped_ast = [
Symbol("~sx-doc"), Keyword("path"), path_str,
page_ast,
]
try:
content_sx = await _eval_slot(wrapped_ast, env, ctx)
except Exception as e:
logger.error("SX URL render failed for %s: %s", raw_path, e, exc_info=True)
return None
# Return response
if is_htmx_request():
return sx_response(await oob_page_sx(content=content_sx))
else:
tctx = await get_template_context()
html = await full_page_sx(tctx, header_rows="", content=content_sx)
return await make_response(html, 200)
# ---------------------------------------------------------------------------
# Old URL → SX URL redirect patterns
# ---------------------------------------------------------------------------
_REDIRECT_PATTERNS = [
# More specific first
(re.compile(r"^/language/specs/explore/(.+?)/?$"),
lambda m: f"/(language.(spec.(explore.{m.group(1)})))"),
(re.compile(r"^/language/docs/(.+?)/?$"),
lambda m: f"/(language.(doc.{m.group(1)}))"),
(re.compile(r"^/language/docs/?$"),
"/(language.(doc))"),
(re.compile(r"^/language/specs/(.+?)/?$"),
lambda m: f"/(language.(spec.{m.group(1)}))"),
(re.compile(r"^/language/bootstrappers/page-helpers/?$"),
"/(language.(bootstrapper.page-helpers))"),
(re.compile(r"^/language/bootstrappers/(.+?)/?$"),
lambda m: f"/(language.(bootstrapper.{m.group(1)}))"),
(re.compile(r"^/language/testing/(.+?)/?$"),
lambda m: f"/(language.(test.{m.group(1)}))"),
(re.compile(r"^/geography/hypermedia/reference/attributes/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference-detail.attributes.{m.group(1)})))"),
(re.compile(r"^/geography/hypermedia/reference/headers/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference-detail.headers.{m.group(1)})))"),
(re.compile(r"^/geography/hypermedia/reference/events/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference-detail.events.{m.group(1)})))"),
(re.compile(r"^/geography/hypermedia/reference/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference.{m.group(1)})))"),
(re.compile(r"^/geography/hypermedia/examples/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.{m.group(1)})))"),
(re.compile(r"^/geography/hypermedia/reference/?$"),
"/(geography.(hypermedia.(reference)))"),
(re.compile(r"^/geography/hypermedia/examples/?$"),
"/(geography.(hypermedia.(example)))"),
(re.compile(r"^/geography/reactive/(.+?)/?$"),
lambda m: f"/(geography.(reactive.{m.group(1)}))"),
(re.compile(r"^/geography/isomorphism/(.+?)/?$"),
lambda m: f"/(geography.(isomorphism.{m.group(1)}))"),
(re.compile(r"^/geography/marshes/?$"),
"/(geography.(marshes))"),
(re.compile(r"^/applications/cssx/(.+?)/?$"),
lambda m: f"/(applications.(cssx.{m.group(1)}))"),
(re.compile(r"^/applications/protocols/(.+?)/?$"),
lambda m: f"/(applications.(protocol.{m.group(1)}))"),
(re.compile(r"^/etc/essays/(.+?)/?$"),
lambda m: f"/(etc.(essay.{m.group(1)}))"),
(re.compile(r"^/etc/philosophy/(.+?)/?$"),
lambda m: f"/(etc.(philosophy.{m.group(1)}))"),
(re.compile(r"^/etc/plans/(.+?)/?$"),
lambda m: f"/(etc.(plan.{m.group(1)}))"),
# Section indices
(re.compile(r"^/language/docs/?$"), "/(language.(doc))"),
(re.compile(r"^/language/specs/?$"), "/(language.(spec))"),
(re.compile(r"^/language/bootstrappers/?$"), "/(language.(bootstrapper))"),
(re.compile(r"^/language/testing/?$"), "/(language.(test))"),
(re.compile(r"^/language/?$"), "/(language)"),
(re.compile(r"^/geography/hypermedia/?$"), "/(geography.(hypermedia))"),
(re.compile(r"^/geography/reactive/?$"), "/(geography.(reactive))"),
(re.compile(r"^/geography/isomorphism/?$"), "/(geography.(isomorphism))"),
(re.compile(r"^/geography/?$"), "/(geography)"),
(re.compile(r"^/applications/cssx/?$"), "/(applications.(cssx))"),
(re.compile(r"^/applications/protocols/?$"), "/(applications.(protocol))"),
(re.compile(r"^/applications/?$"), "/(applications)"),
(re.compile(r"^/etc/essays/?$"), "/(etc.(essay))"),
(re.compile(r"^/etc/philosophy/?$"), "/(etc.(philosophy))"),
(re.compile(r"^/etc/plans/?$"), "/(etc.(plan))"),
(re.compile(r"^/etc/?$"), "/(etc)"),
]
def redirect_old_url(path: str) -> str | None:
"""Check if an old-style path should redirect to an SX URL.
Returns the new SX URL if a redirect is needed, None otherwise.
"""
for pattern, replacement in _REDIRECT_PATTERNS:
m = pattern.match(path)
if m:
if callable(replacement):
return replacement(m)
return replacement
return None