Prefix all SX URLs with /sx/ for WhatsApp-safe sharing

All routes moved under /sx/ prefix:
- / redirects to /sx/
- /sx/ serves home page
- /sx/<path:expr> is the catch-all for SX expression URLs
- Bare /(...) and /~... redirect to /sx/(...) and /sx/~...
- All ~600 hrefs, sx-get attrs, defhandler paths, redirect
  targets, and blueprint routes updated across 44 files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 19:07:09 +00:00
parent acd2fa6541
commit de80d921e9
44 changed files with 701 additions and 687 deletions

View File

@@ -119,15 +119,23 @@ def create_app() -> "Quart":
@app.before_request
async def sx_url_redirect():
"""Redirect old-style paths to SX expression URLs (301)."""
"""Redirect old-style paths and bare root to /sx/ URLs (301)."""
from quart import request, redirect as q_redirect
path = request.path
# Root → /sx/
if path == "/":
return q_redirect("/sx/", 301)
# Skip non-page paths
if path.startswith(("/static/", "/internal/", "/auth/", "/sx/")):
if path.startswith(("/static/", "/internal/", "/auth/")):
return None
# Skip SX expression URLs (already in new format)
# Skip SX expression URLs (already in new format under /sx/)
if path.startswith("/sx/"):
return None
# Redirect bare /(...) to /sx/(...)
if path.startswith("/(") or path.startswith("/~"):
return None
qs = request.query_string.decode()
target = f"/sx{path}" + ("?" + qs if qs else "")
return q_redirect(target, 301)
new_url = redirect_old_url(path)
if new_url:
qs = request.query_string.decode()
@@ -140,20 +148,26 @@ def create_app() -> "Quart":
from quart import request, redirect
path = request.path
# Skip SX expression URLs — they don't use trailing slashes
if "(" in path or path.startswith("/~"):
if "(" in path or "/~" in path:
return None
if (path != "/"
and path != "/sx/"
and not path.endswith("/")
and request.method == "GET"
and not path.startswith(("/static/", "/internal/", "/auth/"))
and not path.startswith(("/static/", "/internal/", "/auth/", "/sx/"))
and "." not in path.rsplit("/", 1)[-1]):
qs = request.query_string.decode()
target = path + "/" + ("?" + qs if qs else "")
return redirect(target, 301)
@app.get("/<path:expr>")
@app.get("/sx/")
async def sx_home():
"""SX docs home page."""
return await eval_sx_url("/")
@app.get("/sx/<path:expr>")
async def sx_eval_route(expr):
"""Catch-all: evaluate SX expression URLs."""
"""Catch-all: evaluate SX expression URLs under /sx/ prefix."""
result = await eval_sx_url(f"/{expr}")
if result is None:
from quart import abort

View File

@@ -23,7 +23,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# SSE stays in Python — fundamentally different paradigm (async generator).
# ------------------------------------------------------------------
@bp.get("/(geography.(hypermedia.(reference.(api.sse-time))))")
@bp.get("/sx/(geography.(hypermedia.(reference.(api.sse-time))))")
async def ref_sse_time():
async def generate():
for _ in range(30): # stream for 60 seconds max
@@ -38,7 +38,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_marsh_sale_idx = {"n": 0}
@bp.get("/(geography.(reactive.(api.flash-sale)))")
@bp.get("/sx/(geography.(reactive.(api.flash-sale)))")
async def api_marsh_flash_sale():
from shared.sx.helpers import sx_response
prices = [14.99, 9.99, 24.99, 12.49, 7.99, 29.99, 4.99, 16.50]
@@ -60,7 +60,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_settle_counter = {"n": 0}
@bp.get("/(geography.(reactive.(api.settle-data)))")
@bp.get("/sx/(geography.(reactive.(api.settle-data)))")
async def api_settle_data():
from shared.sx.helpers import sx_response
_settle_counter["n"] += 1
@@ -76,7 +76,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Demo 4: signal-bound URL endpoints ---
@bp.get("/(geography.(reactive.(api.search-products)))")
@bp.get("/sx/(geography.(reactive.(api.search-products)))")
async def api_search_products():
from shared.sx.helpers import sx_response
q = request.args.get("q", "")
@@ -95,7 +95,7 @@ def register(url_prefix: str = "/") -> Blueprint:
)
return sx_response(sx_src)
@bp.get("/(geography.(reactive.(api.search-events)))")
@bp.get("/sx/(geography.(reactive.(api.search-events)))")
async def api_search_events():
from shared.sx.helpers import sx_response
q = request.args.get("q", "")
@@ -114,7 +114,7 @@ def register(url_prefix: str = "/") -> Blueprint:
)
return sx_response(sx_src)
@bp.get("/(geography.(reactive.(api.search-posts)))")
@bp.get("/sx/(geography.(reactive.(api.search-posts)))")
async def api_search_posts():
from shared.sx.helpers import sx_response
q = request.args.get("q", "")
@@ -135,7 +135,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Demo 5: marsh transform endpoint ---
@bp.get("/(geography.(reactive.(api.catalog)))")
@bp.get("/sx/(geography.(reactive.(api.catalog)))")
async def api_catalog():
from shared.sx.helpers import sx_response
items = [

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.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
(a :href "/sx/(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 "/sx/(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.(essay.tail-call-optimization))" :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 "/sx/(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.(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."))
"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 "/sx/(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.(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."))
"An AI that understands SX understands the " (a :href "/sx/(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 "/sx/(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.(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.")
"This is why the " (a :href "/sx/(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

@@ -37,11 +37,11 @@
(p :class "text-stone-600"
"In SX, this is literal:")
(ul :class "space-y-2 text-stone-600 mt-2"
(li (strong "A URL is an s-expression.") " " (code "/(language.(doc.introduction))") " is not an opaque string — it is a parseable, composable expression. It can be decomposed into parts, transformed by functions, and reasoned about by the same evaluator that renders pages.")
(li (strong "A URL is an s-expression.") " " (code "/sx/(language.(doc.introduction))") " is not an opaque string — it is a parseable, composable expression. It can be decomposed into parts, transformed by functions, and reasoned about by the same evaluator that renders pages.")
(li (strong "A response is an s-expression.") " What the server sends is " (code "(div :class \"p-4\" (h2 \"Hello\"))") " — the same notation as the component that produced it. The wire format is the language.")
(li (strong "A component is an s-expression.") " " (code "(defcomp ~card (&key title) (div (h2 title)))") " is simultaneously a definition, a value, and data that can be inspected and transformed.")
(li (strong "A query is an s-expression.") " The URL " (code "/(language.(spec.core))") " is a function call. The response is the return value. Routing is evaluation.")
(li (strong "The specification is s-expressions.") " The " (a :href "/(language.(spec.core))" :class "text-violet-600 hover:underline" "SX spec") " is written in SX. The evaluator is defined in the language it evaluates. The parser is defined in the language it parses."))
(li (strong "A query is an s-expression.") " The URL " (code "/sx/(language.(spec.core))") " is a function call. The response is the return value. Routing is evaluation.")
(li (strong "The specification is s-expressions.") " The " (a :href "/sx/(language.(spec.core))" :class "text-violet-600 hover:underline" "SX spec") " is written in SX. The evaluator is defined in the language it evaluates. The parser is defined in the language it parses."))
(p :class "text-stone-600"
"There is " (em "one") " kind of stuff. Everything is made of it. The address of a thing and the thing itself are the same kind of thing."))
@@ -50,19 +50,19 @@
"When the medium is uniform, operations that were impossible become trivial:")
(ul :class "space-y-3 text-stone-600 mt-2"
(li (strong "URLs compose.") " If " (code "(language (doc introduction))") " and " (code "(language (spec core))") " are both expressions, then " (code "(diff (language (spec signals)) (language (spec eval)))") " is a natural composition. Two queries, side by side. The URL algebra falls out of the expression algebra. You do not need to design it separately — it was always there.")
(li (strong "The site can show its own source.") " " (code "/(source.(~essay-self-defining-medium))") " returns the component definition of this essay. Not a screenshot. Not a prettified view. The actual s-expression that, when evaluated, produces what you are reading now. The page and its source code are the same kind of thing, so displaying one as the other is just evaluation.")
(li (strong "The spec is executable documentation.") " The " (a :href "/(language.(bootstrapper.self-hosting))" :class "text-violet-600 hover:underline" "self-hosting bootstrapper") " reads the SX spec (written in SX) and produces a working evaluator. The documentation is the implementation. The implementation is the documentation. There is no drift because there is no gap.")
(li (strong "Inspection is free.") " " (code "/(inspect.(language.(doc.primitives)))") " can show the dependency graph, CSS footprint, and render plan of any page — because the page is data, and data can be walked, analysed, and reported on by the same system that renders it.")
(li (strong "The site can show its own source.") " " (code "/sx/(source.(~essay-self-defining-medium))") " returns the component definition of this essay. Not a screenshot. Not a prettified view. The actual s-expression that, when evaluated, produces what you are reading now. The page and its source code are the same kind of thing, so displaying one as the other is just evaluation.")
(li (strong "The spec is executable documentation.") " The " (a :href "/sx/(language.(bootstrapper.self-hosting))" :class "text-violet-600 hover:underline" "self-hosting bootstrapper") " reads the SX spec (written in SX) and produces a working evaluator. The documentation is the implementation. The implementation is the documentation. There is no drift because there is no gap.")
(li (strong "Inspection is free.") " " (code "/sx/(inspect.(language.(doc.primitives)))") " can show the dependency graph, CSS footprint, and render plan of any page — because the page is data, and data can be walked, analysed, and reported on by the same system that renders it.")
(li (strong "AI is a native speaker.") " An AI reading SX reads the same notation as the server, the client, the wire, and the spec. There is no translation layer. The AI does not generate code that must be compiled and deployed — it generates expressions that are evaluated. The medium is shared between human, machine, and network.")))
(~doc-section :title "The metacircular web" :id "metacircular-web"
(p :class "text-stone-600"
"McCarthy's " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#Connection_to_artificial_intelligence" :class "text-violet-600 hover:underline" "metacircular evaluator") " proved that a computing language can define itself. SX extends this proof to a networked hypermedium:")
(ul :class "space-y-2 text-stone-600 mt-2"
(li "The " (strong "evaluator") " is defined in SX: " (a :href "/(language.(spec.eval))" :class "text-violet-600 hover:underline" "eval.sx") " specifies the evaluation rules for all expression types, and this specification is itself an SX document.")
(li "The " (strong "parser") " is defined in SX: " (a :href "/(language.(spec.parser))" :class "text-violet-600 hover:underline" "parser.sx") " specifies tokenization, grammar, and serialization — written in the language it parses.")
(li "The " (strong "renderer") " is defined in SX: " (a :href "/(language.(spec.render))" :class "text-violet-600 hover:underline" "render.sx") " specifies how s-expressions become HTML, SX wire format, or DOM nodes.")
(li "The " (strong "router") " is an expression: " (code "/(language.(doc.introduction))") " is a function call that resolves to content. " (a :href "/(language.(spec.router))" :class "text-violet-600 hover:underline" "router.sx") " specifies the matching — in SX.")
(li "The " (strong "evaluator") " is defined in SX: " (a :href "/sx/(language.(spec.eval))" :class "text-violet-600 hover:underline" "eval.sx") " specifies the evaluation rules for all expression types, and this specification is itself an SX document.")
(li "The " (strong "parser") " is defined in SX: " (a :href "/sx/(language.(spec.parser))" :class "text-violet-600 hover:underline" "parser.sx") " specifies tokenization, grammar, and serialization — written in the language it parses.")
(li "The " (strong "renderer") " is defined in SX: " (a :href "/sx/(language.(spec.render))" :class "text-violet-600 hover:underline" "render.sx") " specifies how s-expressions become HTML, SX wire format, or DOM nodes.")
(li "The " (strong "router") " is an expression: " (code "/sx/(language.(doc.introduction))") " is a function call that resolves to content. " (a :href "/sx/(language.(spec.router))" :class "text-violet-600 hover:underline" "router.sx") " specifies the matching — in SX.")
(li "The " (strong "site itself") " is s-expressions: this essay, the navigation tree, the component registry, the wire protocol — all the way down."))
(p :class "text-stone-600"
"At every level, the description and the described are the same kind of thing. The specification is not " (em "about") " the system — it " (em "is") " the system. This is not metaphor. It is the literal architecture."))

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.(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."))
"Beyond parsing, the SX " (a :href "/sx/(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.(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."))
"The " (a :href "/sx/(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.(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.")
"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 "/sx/(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

@@ -8,7 +8,7 @@
:description "The simplest sx interaction: click a button, fetch content from the server, swap it in."
:demo-description "Click the button to load server-rendered content."
:demo (~click-to-load-demo)
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.click))))\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")"
:sx-code "(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.click))))\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")"
:handler-code (handler-source "ex-click")
:comp-placeholder-id "click-comp"
:wire-placeholder-id "click-wire"
@@ -20,7 +20,7 @@
:description "Forms with sx-post submit via AJAX and swap the response into a target."
:demo-description "Enter a name and submit."
:demo (~form-demo)
:sx-code "(form\n :sx-post \"/(geography.(hypermedia.(example.(api.form))))\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))"
:sx-code "(form\n :sx-post \"/sx/(geography.(hypermedia.(example.(api.form))))\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))"
:handler-code (handler-source "ex-form")
:comp-placeholder-id "form-comp"
:wire-placeholder-id "form-wire"))
@@ -31,7 +31,7 @@
:description "Use sx-trigger with \"every\" to poll the server at regular intervals."
:demo-description "This div polls the server every 2 seconds."
:demo (~polling-demo)
:sx-code "(div\n :sx-get \"/(geography.(hypermedia.(example.(api.poll))))\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")"
:sx-code "(div\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.poll))))\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")"
:handler-code (handler-source "ex-poll")
:comp-placeholder-id "poll-comp"
:wire-placeholder-id "poll-wire"
@@ -48,7 +48,7 @@
(list "3" "Write documentation")
(list "4" "Deploy to production")
(list "5" "Add unit tests")))
:sx-code "(button\n :sx-delete \"/(geography.(hypermedia.(example.(api.(delete.1)))))\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")"
:sx-code "(button\n :sx-delete \"/sx/(geography.(hypermedia.(example.(api.(delete.1)))))\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")"
:handler-code (handler-source "ex-delete")
:comp-placeholder-id "delete-comp"
:wire-placeholder-id "delete-wire"
@@ -73,7 +73,7 @@
:description "sx-swap-oob lets a single response update multiple elements anywhere in the DOM."
:demo-description "One request updates both Box A (via sx-target) and Box B (via sx-swap-oob)."
:demo (~oob-demo)
:sx-code ";; Button targets Box A\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.oob))))\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")"
:sx-code ";; Button targets Box A\n(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.oob))))\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")"
:handler-code (handler-source "ex-oob")
:wire-placeholder-id "oob-wire"
:wire-note "The fragment contains both the main content and an OOB element. sx.js splits them: main content goes to sx-target, OOB elements find their targets by ID."))
@@ -84,7 +84,7 @@
:description "Use sx-trigger=\"load\" to fetch content as soon as the element enters the DOM. Great for deferring expensive content below the fold."
:demo-description "Content loads automatically when the page renders."
:demo (~lazy-loading-demo)
:sx-code "(div\n :sx-get \"/(geography.(hypermedia.(example.(api.lazy))))\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))"
:sx-code "(div\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.lazy))))\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))"
:handler-code (handler-source "ex-lazy")
:comp-placeholder-id "lazy-comp"
:wire-placeholder-id "lazy-wire"))
@@ -95,7 +95,7 @@
:description "A sentinel element at the bottom uses sx-trigger=\"intersect once\" to load the next page when scrolled into view. Each response appends items and a new sentinel."
:demo-description "Scroll down in the container to load more items (5 pages total)."
:demo (~infinite-scroll-demo)
:sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/(geography.(hypermedia.(example.(api.scroll))))?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")"
:sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.scroll))))?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")"
:handler-code (handler-source "ex-scroll")
:comp-placeholder-id "scroll-comp"
:wire-placeholder-id "scroll-wire"))
@@ -106,7 +106,7 @@
:description "Start a server-side job, then poll for progress using sx-trigger=\"load delay:500ms\" on each response. The bar fills up and stops when complete."
:demo-description "Click start to begin a simulated job."
:demo (~progress-bar-demo)
:sx-code ";; Start the job\n(button\n :sx-post \"/(geography.(hypermedia.(example.(api.progress-start))))\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")\n\n;; Each response re-polls via sx-trigger=\"load\"\n(div :sx-get \"/(geography.(hypermedia.(example.(api.progress-status))))?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")"
:sx-code ";; Start the job\n(button\n :sx-post \"/sx/(geography.(hypermedia.(example.(api.progress-start))))\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")\n\n;; Each response re-polls via sx-trigger=\"load\"\n(div :sx-get \"/sx/(geography.(hypermedia.(example.(api.progress-status))))?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")"
:handler-code (str (handler-source "ex-progress-start") "\n\n" (handler-source "ex-progress-status"))
:comp-placeholder-id "progress-comp"
:wire-placeholder-id "progress-wire"))
@@ -117,7 +117,7 @@
:description "An input with sx-trigger=\"keyup delay:300ms changed\" debounces keystrokes and only fires when the value changes. The server filters a list of programming languages."
:demo-description "Type to search through 20 programming languages."
:demo (~active-search-demo)
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/(geography.(hypermedia.(example.(api.search))))\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")"
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.search))))\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")"
:handler-code (handler-source "ex-search")
:comp-placeholder-id "search-comp"
:wire-placeholder-id "search-wire"))
@@ -128,7 +128,7 @@
:description "Validate an email field on blur. The server checks format and whether it is taken, returning green or red feedback inline."
:demo-description "Enter an email and click away (blur) to validate."
:demo (~inline-validation-demo)
:sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/(geography.(hypermedia.(example.(api.validate))))\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")"
:sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.validate))))\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")"
:handler-code (handler-source "ex-validate")
:comp-placeholder-id "validate-comp"
:wire-placeholder-id "validate-wire"))
@@ -139,7 +139,7 @@
:description "Two linked selects: pick a category and the second select updates with matching items via sx-get."
:demo-description "Select a category to populate the item dropdown."
:demo (~value-select-demo)
:sx-code "(select :name \"category\"\n :sx-get \"/(geography.(hypermedia.(example.(api.values))))\"\n :sx-trigger \"change\"\n :sx-target \"#value-items\"\n :sx-swap \"innerHTML\"\n (option \"Languages\")\n (option \"Frameworks\")\n (option \"Databases\"))"
:sx-code "(select :name \"category\"\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.values))))\"\n :sx-trigger \"change\"\n :sx-target \"#value-items\"\n :sx-swap \"innerHTML\"\n (option \"Languages\")\n (option \"Frameworks\")\n (option \"Databases\"))"
:handler-code (handler-source "ex-values")
:comp-placeholder-id "values-comp"
:wire-placeholder-id "values-wire"))
@@ -150,7 +150,7 @@
:description "Use sx-on:afterSwap=\"this.reset()\" to clear form inputs after a successful submission."
:demo-description "Submit a message — the input resets after each send."
:demo (~reset-on-submit-demo)
:sx-code "(form :id \"reset-form\"\n :sx-post \"/(geography.(hypermedia.(example.(api.reset-submit))))\"\n :sx-target \"#reset-result\"\n :sx-swap \"innerHTML\"\n :sx-on:afterSwap \"this.reset()\"\n (input :type \"text\" :name \"message\")\n (button :type \"submit\" \"Send\"))"
:sx-code "(form :id \"reset-form\"\n :sx-post \"/sx/(geography.(hypermedia.(example.(api.reset-submit))))\"\n :sx-target \"#reset-result\"\n :sx-swap \"innerHTML\"\n :sx-on:afterSwap \"this.reset()\"\n (input :type \"text\" :name \"message\")\n (button :type \"submit\" \"Send\"))"
:handler-code (handler-source "ex-reset-submit")
:comp-placeholder-id "reset-comp"
:wire-placeholder-id "reset-wire"))
@@ -165,7 +165,7 @@
(list "2" "Widget B" "24.50" "89")
(list "3" "Widget C" "12.00" "305")
(list "4" "Widget D" "45.00" "67")))
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n \"edit\")\n\n;; Save sends form data via POST\n(button\n :sx-post \"/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")"
:sx-code "(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n \"edit\")\n\n;; Save sends form data via POST\n(button\n :sx-post \"/sx/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")"
:handler-code (str (handler-source "ex-editrow-form") "\n\n" (handler-source "ex-editrow-save"))
:comp-placeholder-id "editrow-comp"
:wire-placeholder-id "editrow-wire"))
@@ -181,7 +181,7 @@
(list "3" "Carol Zhang" "carol@example.com" "active")
(list "4" "Dan Okafor" "dan@example.com" "inactive")
(list "5" "Eve Larsson" "eve@example.com" "active")))
:sx-code "(button\n :sx-post \"/(geography.(hypermedia.(example.(api.bulk))))?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")"
:sx-code "(button\n :sx-post \"/sx/(geography.(hypermedia.(example.(api.bulk))))?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")"
:handler-code (handler-source "ex-bulk")
:comp-placeholder-id "bulk-comp"
:wire-placeholder-id "bulk-wire"))
@@ -192,7 +192,7 @@
:description "Demonstrates different swap modes: beforeend appends, afterbegin prepends, and none skips the main swap while still processing OOB updates."
:demo-description "Try each button to see different swap behaviours."
:demo (~swap-positions-demo)
:sx-code ";; Append to end\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend\"\n :sx-target \"#swap-log\" :sx-swap \"beforeend\"\n \"Add to End\")\n\n;; Prepend to start\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin\"\n :sx-target \"#swap-log\" :sx-swap \"afterbegin\"\n \"Add to Start\")\n\n;; No swap — OOB counter update only\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")"
:sx-code ";; Append to end\n(button :sx-post \"/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend\"\n :sx-target \"#swap-log\" :sx-swap \"beforeend\"\n \"Add to End\")\n\n;; Prepend to start\n(button :sx-post \"/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin\"\n :sx-target \"#swap-log\" :sx-swap \"afterbegin\"\n \"Add to Start\")\n\n;; No swap — OOB counter update only\n(button :sx-post \"/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")"
:handler-code (handler-source "ex-swap-log")
:wire-placeholder-id "swap-wire"))
@@ -202,7 +202,7 @@
:description "sx-select lets the client pick a specific section from the server response by CSS selector. The server always returns the full dashboard — the client filters."
:demo-description "Different buttons select different parts of the same server response."
:demo (~select-filter-demo)
:sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n :sx-select \"#dash-stats\"\n \"Stats Only\")\n\n;; No sx-select — get the full response\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")"
:sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n :sx-select \"#dash-stats\"\n \"Stats Only\")\n\n;; No sx-select — get the full response\n(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")"
:handler-code (handler-source "ex-dashboard")
:wire-placeholder-id "filter-wire"))
@@ -212,7 +212,7 @@
:description "Tab navigation using sx-push-url to update the browser URL. Back/forward buttons navigate between previously visited tabs."
:demo-description "Click tabs to switch content. Watch the browser URL change."
:demo (~tabs-demo)
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.(tabs.tab1)))))\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/(geography.(hypermedia.(example.tabs)))?tab=tab1\"\n \"Overview\")"
:sx-code "(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.(tabs.tab1)))))\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/sx/(geography.(hypermedia.(example.tabs)))?tab=tab1\"\n \"Overview\")"
:handler-code (handler-source "ex-tabs")
:wire-placeholder-id "tabs-wire"))
@@ -222,7 +222,7 @@
:description "CSS animations play on swap. The component injects a style tag with a keyframe animation and applies the class. Each click picks a random background colour."
:demo-description "Click to swap in content with a fade-in animation."
:demo (~animations-demo)
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.animate))))\"\n :sx-target \"#anim-target\"\n :sx-swap \"innerHTML\"\n \"Load with animation\")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class \"sx-fade-in ...\"\n (style \".sx-fade-in { animation: sxFadeIn 0.5s }\")\n (p \"Faded in!\")))"
:sx-code "(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.animate))))\"\n :sx-target \"#anim-target\"\n :sx-swap \"innerHTML\"\n \"Load with animation\")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class \"sx-fade-in ...\"\n (style \".sx-fade-in { animation: sxFadeIn 0.5s }\")\n (p \"Faded in!\")))"
:handler-code (handler-source "ex-animate")
:comp-placeholder-id "anim-comp"
:wire-placeholder-id "anim-wire"))
@@ -233,7 +233,7 @@
:description "Open a modal dialog by swapping in the dialog component. Close by swapping in empty content. Pure sx — no JavaScript library needed."
:demo-description "Click to open a modal dialog."
:demo (~dialogs-demo)
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dialog))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Open Dialog\")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dialog-close))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")"
:sx-code "(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.dialog))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Open Dialog\")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.dialog-close))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")"
:handler-code (str (handler-source "ex-dialog") "\n\n" (handler-source "ex-dialog-close"))
:comp-placeholder-id "dialog-comp"
:wire-placeholder-id "dialog-wire"))
@@ -244,7 +244,7 @@
:description "Use sx-trigger with keyup event filters and from:body to listen for global keyboard shortcuts. The filter prevents firing when typing in inputs."
:demo-description "Press s, n, or h on your keyboard."
:demo (~keyboard-shortcuts-demo)
:sx-code "(div :id \"kbd-target\"\n :sx-get \"/(geography.(hypermedia.(example.(api.keyboard))))?key=s\"\n :sx-trigger \"keyup[key=='s'&&!event.target.matches('input,textarea')] from:body\"\n :sx-swap \"innerHTML\"\n \"Press a shortcut key...\")"
:sx-code "(div :id \"kbd-target\"\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.keyboard))))?key=s\"\n :sx-trigger \"keyup[key=='s'&&!event.target.matches('input,textarea')] from:body\"\n :sx-swap \"innerHTML\"\n \"Press a shortcut key...\")"
:handler-code (handler-source "ex-keyboard")
:comp-placeholder-id "kbd-comp"
:wire-placeholder-id "kbd-wire"))
@@ -255,7 +255,7 @@
:description "sx-put replaces the entire resource. This example shows a profile card with an Edit All button that sends a PUT with all fields."
:demo-description "Click Edit All to replace the full profile via PUT."
:demo (~put-patch-demo :name "Ada Lovelace" :email "ada@example.com" :role "Engineer")
:sx-code ";; Replace entire resource\n(form :sx-put \"/(geography.(hypermedia.(example.(api.putpatch))))\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))"
:sx-code ";; Replace entire resource\n(form :sx-put \"/sx/(geography.(hypermedia.(example.(api.putpatch))))\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))"
:handler-code (str (handler-source "ex-pp-edit-all") "\n\n" (handler-source "ex-pp-put"))
:comp-placeholder-id "pp-comp"
:wire-placeholder-id "pp-wire"))
@@ -266,7 +266,7 @@
:description "Use sx-encoding=\"json\" to send form data as a JSON body instead of URL-encoded form data. The server echoes back what it received."
:demo-description "Submit the form and see the JSON body the server received."
:demo (~json-encoding-demo)
:sx-code "(form\n :sx-post \"/(geography.(hypermedia.(example.(api.json-echo))))\"\n :sx-target \"#json-result\"\n :sx-swap \"innerHTML\"\n :sx-encoding \"json\"\n (input :name \"name\" :value \"Ada\")\n (input :type \"number\" :name \"age\" :value \"36\")\n (button \"Submit as JSON\"))"
:sx-code "(form\n :sx-post \"/sx/(geography.(hypermedia.(example.(api.json-echo))))\"\n :sx-target \"#json-result\"\n :sx-swap \"innerHTML\"\n :sx-encoding \"json\"\n (input :name \"name\" :value \"Ada\")\n (input :type \"number\" :name \"age\" :value \"36\")\n (button \"Submit as JSON\"))"
:handler-code (handler-source "ex-json-echo")
:comp-placeholder-id "json-comp"
:wire-placeholder-id "json-wire"))
@@ -277,7 +277,7 @@
:description "sx-vals adds extra key/value pairs to the request parameters. sx-headers adds custom HTTP headers. The server echoes back what it received."
:demo-description "Click each button to see what the server receives."
:demo (~vals-headers-demo)
:sx-code ";; Send extra values with the request\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.echo-vals))))\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.echo-headers))))\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")"
:sx-code ";; Send extra values with the request\n(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.echo-vals))))\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.echo-headers))))\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")"
:handler-code (str (handler-source "ex-echo-vals") "\n\n" (handler-source "ex-echo-headers"))
:comp-placeholder-id "vals-comp"
:wire-placeholder-id "vals-wire"))
@@ -288,7 +288,7 @@
:description "sx.js adds the .sx-request CSS class to any element that has an active request. Use pure CSS to show spinners, disable buttons, or change opacity during loading."
:demo-description "Click the button — it shows a spinner during the 2-second request."
:demo (~loading-states-demo)
:sx-code ";; .sx-request class added during request\n(style \".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }\")\n\n(button :class \"sx-loading-btn\"\n :sx-get \"/(geography.(hypermedia.(example.(api.slow))))\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")"
:sx-code ";; .sx-request class added during request\n(style \".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }\")\n\n(button :class \"sx-loading-btn\"\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.slow))))\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")"
:handler-code (handler-source "ex-slow")
:comp-placeholder-id "loading-comp"
:wire-placeholder-id "loading-wire"))
@@ -299,7 +299,7 @@
:description "sx-sync=\"replace\" aborts any in-flight request before sending a new one. This prevents stale responses from overwriting newer ones, even with random server delays."
:demo-description "Type quickly — only the latest result appears despite random 0.5-2s server delays."
:demo (~sync-replace-demo)
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/(geography.(hypermedia.(example.(api.slow-search))))\"\n :sx-trigger \"keyup delay:200ms changed\"\n :sx-target \"#sync-result\"\n :sx-swap \"innerHTML\"\n :sx-sync \"replace\"\n \"Type to search...\")"
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.slow-search))))\"\n :sx-trigger \"keyup delay:200ms changed\"\n :sx-target \"#sync-result\"\n :sx-swap \"innerHTML\"\n :sx-sync \"replace\"\n \"Type to search...\")"
:handler-code (handler-source "ex-slow-search")
:comp-placeholder-id "sync-comp"
:wire-placeholder-id "sync-wire"))
@@ -310,7 +310,7 @@
:description "sx-retry=\"exponential:1000:8000\" retries failed requests with exponential backoff starting at 1s up to 8s. The endpoint fails the first 2 attempts and succeeds on the 3rd."
:demo-description "Click the button — watch it retry automatically after failures."
:demo (~retry-demo)
:sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.flaky))))\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")"
:sx-code "(button\n :sx-get \"/sx/(geography.(hypermedia.(example.(api.flaky))))\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")"
:handler-code (handler-source "ex-flaky")
:comp-placeholder-id "retry-comp"
:wire-placeholder-id "retry-wire"))

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 "/sx/(geography.(hypermedia.(reference.attributes)))"
:sx-get "/sx/(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 "/sx/(geography.(hypermedia.(reference.headers)))"
:sx-get "/sx/(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 "/sx/(geography.(hypermedia.(reference.events)))"
:sx-get "/sx/(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 "/sx/(geography.(hypermedia.(reference.js-api)))"
:sx-get "/sx/(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

@@ -55,7 +55,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-click
:path "/(geography.(hypermedia.(example.(api.click))))"
:path "/sx/(geography.(hypermedia.(example.(api.click))))"
:method :get
:returns "element"
(&key)
@@ -73,7 +73,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-form
:path "/(geography.(hypermedia.(example.(api.form))))"
:path "/sx/(geography.(hypermedia.(example.(api.form))))"
:method :post
:csrf false
:returns "element"
@@ -92,7 +92,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-poll
:path "/(geography.(hypermedia.(example.(api.poll))))"
:path "/sx/(geography.(hypermedia.(example.(api.poll))))"
:method :get
:returns "element"
(&key)
@@ -113,7 +113,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-delete
:path "/(geography.(hypermedia.(example.(api.(delete.<sx:item_id>)))))"
:path "/sx/(geography.(hypermedia.(example.(api.(delete.<sx:item_id>)))))"
:method :delete
:csrf false
:returns "element"
@@ -130,7 +130,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-edit-form
:path "/(geography.(hypermedia.(example.(api.edit))))"
:path "/sx/(geography.(hypermedia.(example.(api.edit))))"
:method :get
:returns "element"
(&key)
@@ -143,7 +143,7 @@
:text (str "(~inline-edit-form :value \"" value "\")")))))
(defhandler ex-edit-save
:path "/(geography.(hypermedia.(example.(api.edit))))"
:path "/sx/(geography.(hypermedia.(example.(api.edit))))"
:method :post
:csrf false
:returns "element"
@@ -157,7 +157,7 @@
:text (str "(~inline-view :value \"" value "\")")))))
(defhandler ex-edit-cancel
:path "/(geography.(hypermedia.(example.(api.edit-cancel))))"
:path "/sx/(geography.(hypermedia.(example.(api.edit-cancel))))"
:method :get
:returns "element"
(&key)
@@ -175,7 +175,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-oob
:path "/(geography.(hypermedia.(example.(api.oob))))"
:path "/sx/(geography.(hypermedia.(example.(api.oob))))"
:method :get
:returns "element"
(&key)
@@ -195,7 +195,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-lazy
:path "/(geography.(hypermedia.(example.(api.lazy))))"
:path "/sx/(geography.(hypermedia.(example.(api.lazy))))"
:method :get
:returns "element"
(&key)
@@ -213,7 +213,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-scroll
:path "/(geography.(hypermedia.(example.(api.scroll))))"
:path "/sx/(geography.(hypermedia.(example.(api.scroll))))"
:method :get
:returns "element"
(&key)
@@ -227,7 +227,7 @@
(range start (+ start 5)))
(if (<= (+ pg 1) 6)
(div :id "scroll-sentinel"
:sx-get (str "/(geography.(hypermedia.(example.(api.scroll))))?page=" (+ pg 1))
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.scroll))))?page=" (+ pg 1))
:sx-trigger "intersect once"
:sx-target "#scroll-items"
:sx-swap "beforeend"
@@ -244,7 +244,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-progress-start
:path "/(geography.(hypermedia.(example.(api.progress-start))))"
:path "/sx/(geography.(hypermedia.(example.(api.progress-start))))"
:method :post
:csrf false
:returns "element"
@@ -261,7 +261,7 @@
:text (str "(~progress-status :percent 0 :job-id \"" job-id "\")"))))))
(defhandler ex-progress-status
:path "/(geography.(hypermedia.(example.(api.progress-status))))"
:path "/sx/(geography.(hypermedia.(example.(api.progress-status))))"
:method :get
:returns "element"
(&key)
@@ -282,7 +282,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-search
:path "/(geography.(hypermedia.(example.(api.search))))"
:path "/sx/(geography.(hypermedia.(example.(api.search))))"
:method :get
:returns "element"
(&key)
@@ -304,7 +304,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-validate
:path "/(geography.(hypermedia.(example.(api.validate))))"
:path "/sx/(geography.(hypermedia.(example.(api.validate))))"
:method :get
:returns "element"
(&key)
@@ -331,7 +331,7 @@
:text (nth result 1))))))
(defhandler ex-validate-submit
:path "/(geography.(hypermedia.(example.(api.validate-submit))))"
:path "/sx/(geography.(hypermedia.(example.(api.validate-submit))))"
:method :post
:csrf false
:returns "element"
@@ -347,7 +347,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-values
:path "/(geography.(hypermedia.(example.(api.values))))"
:path "/sx/(geography.(hypermedia.(example.(api.values))))"
:method :get
:returns "element"
(&key)
@@ -367,7 +367,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-reset-submit
:path "/(geography.(hypermedia.(example.(api.reset-submit))))"
:path "/sx/(geography.(hypermedia.(example.(api.reset-submit))))"
:method :post
:csrf false
:returns "element"
@@ -387,7 +387,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-editrow-form
:path "/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
:path "/sx/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
:method :get
:returns "element"
(&key row-id)
@@ -402,7 +402,7 @@
:text (str "(~edit-row-form :id \"" (get row "id") "\" ...)"))))))
(defhandler ex-editrow-save
:path "/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
:path "/sx/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
:method :post
:csrf false
:returns "element"
@@ -420,7 +420,7 @@
:text (str "(~edit-row-view :id \"" row-id "\" ...)")))))
(defhandler ex-editrow-cancel
:path "/(geography.(hypermedia.(example.(api.(editrow-cancel.<sx:row_id>)))))"
:path "/sx/(geography.(hypermedia.(example.(api.(editrow-cancel.<sx:row_id>)))))"
:method :get
:returns "element"
(&key row-id)
@@ -439,7 +439,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-bulk
:path "/(geography.(hypermedia.(example.(api.bulk))))"
:path "/sx/(geography.(hypermedia.(example.(api.bulk))))"
:method :post
:csrf false
:returns "element"
@@ -476,7 +476,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-swap-log
:path "/(geography.(hypermedia.(example.(api.swap-log))))"
:path "/sx/(geography.(hypermedia.(example.(api.swap-log))))"
:method :post
:csrf false
:returns "element"
@@ -500,7 +500,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-dashboard
:path "/(geography.(hypermedia.(example.(api.dashboard))))"
:path "/sx/(geography.(hypermedia.(example.(api.dashboard))))"
:method :get
:returns "element"
(&key)
@@ -530,7 +530,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-tabs
:path "/(geography.(hypermedia.(example.(api.(tabs.<sx:tab>)))))"
:path "/sx/(geography.(hypermedia.(example.(api.(tabs.<sx:tab>)))))"
:method :get
:returns "element"
(&key tab)
@@ -551,7 +551,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-animate
:path "/(geography.(hypermedia.(example.(api.animate))))"
:path "/sx/(geography.(hypermedia.(example.(api.animate))))"
:method :get
:returns "element"
(&key)
@@ -571,7 +571,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-dialog
:path "/(geography.(hypermedia.(example.(api.dialog))))"
:path "/sx/(geography.(hypermedia.(example.(api.dialog))))"
:method :get
:returns "element"
(&key)
@@ -584,7 +584,7 @@
:text "(~dialog-modal :title \"Confirm Action\" :message \"...\")")))
(defhandler ex-dialog-close
:path "/(geography.(hypermedia.(example.(api.dialog-close))))"
:path "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:method :get
:returns "element"
(&key)
@@ -598,7 +598,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-keyboard
:path "/(geography.(hypermedia.(example.(api.keyboard))))"
:path "/sx/(geography.(hypermedia.(example.(api.keyboard))))"
:method :get
:returns "element"
(&key)
@@ -617,7 +617,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-pp-edit-all
:path "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
:path "/sx/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
:method :get
:returns "element"
(&key)
@@ -631,7 +631,7 @@
:text (str "(~pp-form-full :name \"" (get p "name") "\" ...)")))))
(defhandler ex-pp-put
:path "/(geography.(hypermedia.(example.(api.putpatch))))"
:path "/sx/(geography.(hypermedia.(example.(api.putpatch))))"
:method :put
:csrf false
:returns "element"
@@ -648,7 +648,7 @@
:text (str "(~pp-view :name \"" name "\" ...)")))))
(defhandler ex-pp-cancel
:path "/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
:path "/sx/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
:method :get
:returns "element"
(&key)
@@ -667,7 +667,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-json-echo
:path "/(geography.(hypermedia.(example.(api.json-echo))))"
:path "/sx/(geography.(hypermedia.(example.(api.json-echo))))"
:method :post
:csrf false
:returns "element"
@@ -688,7 +688,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-echo-vals
:path "/(geography.(hypermedia.(example.(api.echo-vals))))"
:path "/sx/(geography.(hypermedia.(example.(api.echo-vals))))"
:method :get
:returns "element"
(&key)
@@ -705,7 +705,7 @@
:text (str "(~echo-result :label \"values\" :items (list ...))")))))))
(defhandler ex-echo-headers
:path "/(geography.(hypermedia.(example.(api.echo-headers))))"
:path "/sx/(geography.(hypermedia.(example.(api.echo-headers))))"
:method :get
:returns "element"
(&key)
@@ -725,7 +725,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-slow
:path "/(geography.(hypermedia.(example.(api.slow))))"
:path "/sx/(geography.(hypermedia.(example.(api.slow))))"
:method :get
:returns "element"
(&key)
@@ -744,7 +744,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-slow-search
:path "/(geography.(hypermedia.(example.(api.slow-search))))"
:path "/sx/(geography.(hypermedia.(example.(api.slow-search))))"
:method :get
:returns "element"
(&key)
@@ -764,7 +764,7 @@
;; --------------------------------------------------------------------------
(defhandler ex-flaky
:path "/(geography.(hypermedia.(example.(api.flaky))))"
:path "/sx/(geography.(hypermedia.(example.(api.flaky))))"
:method :get
:returns "element"
(&key)

View File

@@ -6,7 +6,7 @@
;; --- sx-get demo: server time ---
(defhandler ref-time
:path "/(geography.(hypermedia.(reference.(api.time))))"
:path "/sx/(geography.(hypermedia.(reference.(api.time))))"
:method :get
:returns "element"
(&key)
@@ -19,7 +19,7 @@
;; --- sx-post demo: greet ---
(defhandler ref-greet
:path "/(geography.(hypermedia.(reference.(api.greet))))"
:path "/sx/(geography.(hypermedia.(reference.(api.greet))))"
:method :post
:csrf false
:returns "element"
@@ -33,7 +33,7 @@
;; --- sx-put demo: status update ---
(defhandler ref-status
:path "/(geography.(hypermedia.(reference.(api.status))))"
:path "/sx/(geography.(hypermedia.(reference.(api.status))))"
:method :put
:csrf false
:returns "element"
@@ -47,7 +47,7 @@
;; --- sx-patch demo: theme ---
(defhandler ref-theme
:path "/(geography.(hypermedia.(reference.(api.theme))))"
:path "/sx/(geography.(hypermedia.(reference.(api.theme))))"
:method :patch
:csrf false
:returns "element"
@@ -61,7 +61,7 @@
;; --- sx-delete demo ---
(defhandler ref-delete-item
:path "/(geography.(hypermedia.(reference.(api.(item.<sx:item_id>)))))"
:path "/sx/(geography.(hypermedia.(reference.(api.(item.<sx:item_id>)))))"
:method :delete
:csrf false
:returns "element"
@@ -72,7 +72,7 @@
;; --- sx-trigger demo: search ---
(defhandler ref-trigger-search
:path "/(geography.(hypermedia.(reference.(api.trigger-search))))"
:path "/sx/(geography.(hypermedia.(reference.(api.trigger-search))))"
:method :get
:returns "element"
(&key)
@@ -89,7 +89,7 @@
;; --- sx-swap demo ---
(defhandler ref-swap-item
:path "/(geography.(hypermedia.(reference.(api.swap-item))))"
:path "/sx/(geography.(hypermedia.(reference.(api.swap-item))))"
:method :get
:returns "element"
(&key)
@@ -102,7 +102,7 @@
;; --- sx-swap-oob demo ---
(defhandler ref-oob
:path "/(geography.(hypermedia.(reference.(api.oob))))"
:path "/sx/(geography.(hypermedia.(reference.(api.oob))))"
:method :get
:returns "element"
(&key)
@@ -117,7 +117,7 @@
;; --- sx-select demo ---
(defhandler ref-select-page
:path "/(geography.(hypermedia.(reference.(api.select-page))))"
:path "/sx/(geography.(hypermedia.(reference.(api.select-page))))"
:method :get
:returns "element"
(&key)
@@ -134,7 +134,7 @@
;; --- sx-sync demo: slow echo ---
(defhandler ref-slow-echo
:path "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:path "/sx/(geography.(hypermedia.(reference.(api.slow-echo))))"
:method :get
:returns "element"
(&key)
@@ -148,7 +148,7 @@
;; --- sx-prompt demo ---
(defhandler ref-prompt-echo
:path "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:path "/sx/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:method :get
:returns "element"
(&key)
@@ -161,7 +161,7 @@
;; --- Error demo ---
(defhandler ref-error-500
:path "/(geography.(hypermedia.(reference.(api.error-500))))"
:path "/sx/(geography.(hypermedia.(reference.(api.error-500))))"
:method :get
:returns "nil"
(&key)
@@ -175,7 +175,7 @@
;; --- sx-encoding demo: file upload name ---
(defhandler ref-upload-name
:path "/(geography.(hypermedia.(reference.(api.upload-name))))"
:path "/sx/(geography.(hypermedia.(reference.(api.upload-name))))"
:method :post
:csrf false
:returns "element"
@@ -190,7 +190,7 @@
;; --- sx-headers demo: echo custom headers ---
(defhandler ref-echo-headers
:path "/(geography.(hypermedia.(reference.(api.echo-headers))))"
:path "/sx/(geography.(hypermedia.(reference.(api.echo-headers))))"
:method :get
:returns "element"
(&key)
@@ -214,7 +214,7 @@
;; --- sx-include demo: echo GET query params ---
(defhandler ref-echo-vals-get
:path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:path "/sx/(geography.(hypermedia.(reference.(api.echo-vals))))"
:method :get
:returns "element"
(&key)
@@ -235,7 +235,7 @@
;; --- sx-vals demo: echo POST form values ---
(defhandler ref-echo-vals-post
:path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:path "/sx/(geography.(hypermedia.(reference.(api.echo-vals))))"
:method :post
:csrf false
:returns "element"
@@ -257,7 +257,7 @@
;; --- sx-retry demo: flaky endpoint (fails 2/3 times) ---
(defhandler ref-flaky
:path "/(geography.(hypermedia.(reference.(api.flaky))))"
:path "/sx/(geography.(hypermedia.(reference.(api.flaky))))"
:method :get
:returns "element"
(&key)
@@ -275,7 +275,7 @@
;; --- sx-trigger-event demo: response header triggers ---
(defhandler ref-trigger-event
:path "/(geography.(hypermedia.(reference.(api.trigger-event))))"
:path "/sx/(geography.(hypermedia.(reference.(api.trigger-event))))"
:method :get
:returns "element"
(&key)
@@ -287,7 +287,7 @@
;; --- sx-retarget demo: response header retargets ---
(defhandler ref-retarget
:path "/(geography.(hypermedia.(reference.(api.retarget))))"
:path "/sx/(geography.(hypermedia.(reference.(api.retarget))))"
:method :get
:returns "element"
(&key)

View File

@@ -24,8 +24,8 @@
(div :style (str (display "block") (max-w (get cssx-max-widths "3xl"))
(mx-auto) (px 4) (pt 8) (pb 4) (align "center"))
;; Logo — only this navigates home
(a :href "/"
:sx-get "/" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/"
:sx-get "/sx/" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:style (str (display "block") (decoration "none"))
(lake :id "logo"

View File

@@ -3,246 +3,246 @@
;; @css aria-selected:bg-violet-200 aria-selected:text-violet-900
(define docs-nav-items (list
(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))")))
(dict :label "Introduction" :href "/sx/(language.(doc.introduction))")
(dict :label "Getting Started" :href "/sx/(language.(doc.getting-started))")
(dict :label "Components" :href "/sx/(language.(doc.components))")
(dict :label "Evaluator" :href "/sx/(language.(doc.evaluator))")
(dict :label "Primitives" :href "/sx/(language.(doc.primitives))")
(dict :label "Special Forms" :href "/sx/(language.(doc.special-forms))")
(dict :label "Server Rendering" :href "/sx/(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 "/sx/(geography.(hypermedia.(reference.attributes)))")
(dict :label "Headers" :href "/sx/(geography.(hypermedia.(reference.headers)))")
(dict :label "Events" :href "/sx/(geography.(hypermedia.(reference.events)))")
(dict :label "JS API" :href "/sx/(geography.(hypermedia.(reference.js-api)))")))
(define protocols-nav-items (list
(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))")))
(dict :label "Wire Format" :href "/sx/(applications.(protocol.wire-format))")
(dict :label "Fragments" :href "/sx/(applications.(protocol.fragments))")
(dict :label "Resolver I/O" :href "/sx/(applications.(protocol.resolver-io))")
(dict :label "Internal Services" :href "/sx/(applications.(protocol.internal-services))")
(dict :label "ActivityPub" :href "/sx/(applications.(protocol.activitypub))")
(dict :label "Future" :href "/sx/(applications.(protocol.future))")))
(define examples-nav-items (list
(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)))")))
(dict :label "Click to Load" :href "/sx/(geography.(hypermedia.(example.click-to-load)))")
(dict :label "Form Submission" :href "/sx/(geography.(hypermedia.(example.form-submission)))")
(dict :label "Polling" :href "/sx/(geography.(hypermedia.(example.polling)))")
(dict :label "Delete Row" :href "/sx/(geography.(hypermedia.(example.delete-row)))")
(dict :label "Inline Edit" :href "/sx/(geography.(hypermedia.(example.inline-edit)))")
(dict :label "OOB Swaps" :href "/sx/(geography.(hypermedia.(example.oob-swaps)))")
(dict :label "Lazy Loading" :href "/sx/(geography.(hypermedia.(example.lazy-loading)))")
(dict :label "Infinite Scroll" :href "/sx/(geography.(hypermedia.(example.infinite-scroll)))")
(dict :label "Progress Bar" :href "/sx/(geography.(hypermedia.(example.progress-bar)))")
(dict :label "Active Search" :href "/sx/(geography.(hypermedia.(example.active-search)))")
(dict :label "Inline Validation" :href "/sx/(geography.(hypermedia.(example.inline-validation)))")
(dict :label "Value Select" :href "/sx/(geography.(hypermedia.(example.value-select)))")
(dict :label "Reset on Submit" :href "/sx/(geography.(hypermedia.(example.reset-on-submit)))")
(dict :label "Edit Row" :href "/sx/(geography.(hypermedia.(example.edit-row)))")
(dict :label "Bulk Update" :href "/sx/(geography.(hypermedia.(example.bulk-update)))")
(dict :label "Swap Positions" :href "/sx/(geography.(hypermedia.(example.swap-positions)))")
(dict :label "Select Filter" :href "/sx/(geography.(hypermedia.(example.select-filter)))")
(dict :label "Tabs" :href "/sx/(geography.(hypermedia.(example.tabs)))")
(dict :label "Animations" :href "/sx/(geography.(hypermedia.(example.animations)))")
(dict :label "Dialogs" :href "/sx/(geography.(hypermedia.(example.dialogs)))")
(dict :label "Keyboard Shortcuts" :href "/sx/(geography.(hypermedia.(example.keyboard-shortcuts)))")
(dict :label "PUT / PATCH" :href "/sx/(geography.(hypermedia.(example.put-patch)))")
(dict :label "JSON Encoding" :href "/sx/(geography.(hypermedia.(example.json-encoding)))")
(dict :label "Vals & Headers" :href "/sx/(geography.(hypermedia.(example.vals-and-headers)))")
(dict :label "Loading States" :href "/sx/(geography.(hypermedia.(example.loading-states)))")
(dict :label "Request Abort" :href "/sx/(geography.(hypermedia.(example.sync-replace)))")
(dict :label "Retry" :href "/sx/(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 "/sx/(applications.(cssx))")
(dict :label "Patterns" :href "/sx/(applications.(cssx.patterns))")
(dict :label "Delivery" :href "/sx/(applications.(cssx.delivery))")
(dict :label "Async CSS" :href "/sx/(applications.(cssx.async))")
(dict :label "Live Styles" :href "/sx/(applications.(cssx.live))")
(dict :label "Comparisons" :href "/sx/(applications.(cssx.comparisons))")
(dict :label "Philosophy" :href "/sx/(applications.(cssx.philosophy))")))
(define essays-nav-items (list
(dict :label "Why S-Expressions" :href "/(etc.(essay.why-sexps))"
(dict :label "Why S-Expressions" :href "/sx/(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.(essay.htmx-react-hybrid))"
(dict :label "The htmx/React Hybrid" :href "/sx/(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.(essay.on-demand-css))"
(dict :label "On-Demand CSS" :href "/sx/(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.(essay.client-reactivity))"
(dict :label "Client Reactivity" :href "/sx/(etc.(essay.client-reactivity))"
:summary "Reactive UI updates without a virtual DOM, diffing library, or build step.")
(dict :label "SX Native" :href "/(etc.(essay.sx-native))"
(dict :label "SX Native" :href "/sx/(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.(essay.tail-call-optimization))"
(dict :label "Tail-Call Optimization" :href "/sx/(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.(essay.continuations))"
(dict :label "Continuations" :href "/sx/(etc.(essay.continuations))"
:summary "First-class continuations in a tree-walking evaluator — theory and implementation.")
(dict :label "The Reflexive Web" :href "/(etc.(essay.reflexive-web))"
(dict :label "The Reflexive Web" :href "/sx/(etc.(essay.reflexive-web))"
:summary "A web where pages can inspect, modify, and extend their own rendering pipeline.")
(dict :label "Server Architecture" :href "/(etc.(essay.server-architecture))"
(dict :label "Server Architecture" :href "/sx/(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.(essay.separation-of-concerns))"
(dict :label "Separate your Own Concerns" :href "/sx/(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.(essay.sx-and-ai))"
(dict :label "SX and AI" :href "/sx/(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.(essay.no-alternative))"
(dict :label "There Is No Alternative" :href "/sx/(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.(essay.sx-sucks))"
(dict :label "sx sucks" :href "/sx/(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.(essay.zero-tooling))"
(dict :label "Tools for Fools" :href "/sx/(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.(essay.react-is-hypermedia))"
(dict :label "React is Hypermedia" :href "/sx/(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.(essay.hegelian-synthesis))"
(dict :label "The Hegelian Synthesis" :href "/sx/(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.(essay.the-art-chain))"
(dict :label "The Art Chain" :href "/sx/(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.")
(dict :label "The True Hypermedium" :href "/(etc.(essay.self-defining-medium))"
(dict :label "The True Hypermedium" :href "/sx/(etc.(essay.self-defining-medium))"
:summary "The true hypermedium must define itself with itself. On ontological uniformity, the metacircular web, and why address and content should be the same stuff.")))
(define philosophy-nav-items (list
(dict :label "The SX Manifesto" :href "/(etc.(philosophy.sx-manifesto))"
(dict :label "The SX Manifesto" :href "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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.(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))"})}))
{:label "Core" :href "/sx/(language.(spec.core))" :children (list
{:label "Parser" :href "/sx/(language.(spec.parser))"}
{:label "Evaluator" :href "/sx/(language.(spec.evaluator))"}
{:label "Primitives" :href "/sx/(language.(spec.primitives))"}
{:label "Special Forms" :href "/sx/(language.(spec.special-forms))"}
{:label "Renderer" :href "/sx/(language.(spec.renderer))"})}
{:label "Adapters" :href "/sx/(language.(spec.adapters))" :children (list
{:label "DOM Adapter" :href "/sx/(language.(spec.adapter-dom))"}
{:label "HTML Adapter" :href "/sx/(language.(spec.adapter-html))"}
{:label "SX Wire Adapter" :href "/sx/(language.(spec.adapter-sx))"}
{:label "Async Adapter" :href "/sx/(language.(spec.adapter-async))"})}
{:label "Browser" :href "/sx/(language.(spec.browser))" :children (list
{:label "SxEngine" :href "/sx/(language.(spec.engine))"}
{:label "Orchestration" :href "/sx/(language.(spec.orchestration))"}
{:label "Boot" :href "/sx/(language.(spec.boot))"}
{:label "Router" :href "/sx/(language.(spec.router))"})}
{:label "Reactive" :href "/sx/(language.(spec.reactive))" :children (list
{:label "Signals" :href "/sx/(language.(spec.signals))"})}
{:label "Host Interface" :href "/sx/(language.(spec.host))" :children (list
{:label "Boundary" :href "/sx/(language.(spec.boundary))"}
{:label "Forms" :href "/sx/(language.(spec.forms))"}
{:label "Page Helpers" :href "/sx/(language.(spec.page-helpers))"})}
{:label "Extensions" :href "/sx/(language.(spec.extensions))" :children (list
{:label "Continuations" :href "/sx/(language.(spec.continuations))"}
{:label "call/cc" :href "/sx/(language.(spec.callcc))"}
{:label "Types" :href "/sx/(language.(spec.types))"}
{:label "Deps" :href "/sx/(language.(spec.deps))"})}))
(define testing-nav-items (list
(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))")))
(dict :label "Overview" :href "/sx/(language.(test))")
(dict :label "Evaluator" :href "/sx/(language.(test.eval))")
(dict :label "Parser" :href "/sx/(language.(test.parser))")
(dict :label "Router" :href "/sx/(language.(test.router))")
(dict :label "Renderer" :href "/sx/(language.(test.render))")
(dict :label "Dependencies" :href "/sx/(language.(test.deps))")
(dict :label "Engine" :href "/sx/(language.(test.engine))")
(dict :label "Orchestration" :href "/sx/(language.(test.orchestration))")
(dict :label "Runners" :href "/sx/(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 "/sx/(geography.(isomorphism))")
(dict :label "Bundle Analyzer" :href "/sx/(geography.(isomorphism.bundle-analyzer))")
(dict :label "Routing Analyzer" :href "/sx/(geography.(isomorphism.routing-analyzer))")
(dict :label "Data Test" :href "/sx/(geography.(isomorphism.data-test))")
(dict :label "Async IO" :href "/sx/(geography.(isomorphism.async-io))")
(dict :label "Streaming" :href "/sx/(geography.(isomorphism.streaming))")
(dict :label "Affinity" :href "/sx/(geography.(isomorphism.affinity))")
(dict :label "Optimistic" :href "/sx/(geography.(isomorphism.optimistic))")
(dict :label "Offline" :href "/sx/(geography.(isomorphism.offline))")))
(define plans-nav-items (list
(dict :label "Status" :href "/(etc.(plan.status))"
(dict :label "Status" :href "/sx/(etc.(plan.status))"
:summary "Audit of all plans — what's done, what's in progress, and what remains.")
(dict :label "Reader Macros" :href "/(etc.(plan.reader-macros))"
(dict :label "Reader Macros" :href "/sx/(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.(plan.reader-macro-demo))"
(dict :label "Reader Macro Demo" :href "/sx/(etc.(plan.reader-macro-demo))"
:summary "Live demo: #z3 translates SX spec declarations to SMT-LIB verification conditions.")
(dict :label "Theorem Prover" :href "/(etc.(plan.theorem-prover))"
(dict :label "Theorem Prover" :href "/sx/(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.(plan.self-hosting-bootstrapper))"
(dict :label "Self-Hosting Bootstrapper" :href "/sx/(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.(plan.js-bootstrapper))"
(dict :label "JS Bootstrapper" :href "/sx/(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.(plan.sx-activity))"
(dict :label "SX-Activity" :href "/sx/(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.(plan.predictive-prefetch))"
(dict :label "Predictive Prefetching" :href "/sx/(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.(plan.content-addressed-components))"
(dict :label "Content-Addressed Components" :href "/sx/(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.(plan.environment-images))"
(dict :label "Environment Images" :href "/sx/(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.(plan.runtime-slicing))"
(dict :label "Runtime Slicing" :href "/sx/(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.(plan.typed-sx))"
(dict :label "Typed SX" :href "/sx/(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.(plan.nav-redesign))"
(dict :label "Nav Redesign" :href "/sx/(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.(plan.fragment-protocol))"
(dict :label "Fragment Protocol" :href "/sx/(etc.(plan.fragment-protocol))"
:summary "Structured sexp request/response for cross-service component transfer.")
(dict :label "Glue Decoupling" :href "/(etc.(plan.glue-decoupling))"
(dict :label "Glue Decoupling" :href "/sx/(etc.(plan.glue-decoupling))"
:summary "Eliminate all cross-app model imports via glue service layer.")
(dict :label "Social Sharing" :href "/(etc.(plan.social-sharing))"
(dict :label "Social Sharing" :href "/sx/(etc.(plan.social-sharing))"
:summary "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon.")
(dict :label "SX CI Pipeline" :href "/(etc.(plan.sx-ci))"
(dict :label "SX CI Pipeline" :href "/sx/(etc.(plan.sx-ci))"
:summary "Build, test, and deploy in s-expressions — CI pipelines as SX components.")
(dict :label "Live Streaming" :href "/(etc.(plan.live-streaming))"
(dict :label "Live Streaming" :href "/sx/(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.(plan.sx-web-platform))"
(dict :label "sx-web Platform" :href "/sx/(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.(plan.sx-forge))"
(dict :label "sx-forge" :href "/sx/(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.(plan.sx-swarm))"
(dict :label "sx-swarm" :href "/sx/(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.(plan.sx-proxy))"
(dict :label "sx-proxy" :href "/sx/(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.(plan.async-eval-convergence))"
(dict :label "Async Eval Convergence" :href "/sx/(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.(plan.wasm-bytecode-vm))"
(dict :label "WASM Bytecode VM" :href "/sx/(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.(plan.generative-sx))"
(dict :label "Generative SX" :href "/sx/(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.(plan.art-dag-sx))"
(dict :label "Art DAG on SX" :href "/sx/(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.(plan.spec-explorer))"
(dict :label "Spec Explorer" :href "/sx/(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))"
(dict :label "SX Protocol" :href "/sx/(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 "/sx/(geography.(reactive))"
:summary "Architecture, four levels (L0-L3), and current implementation status.")
(dict :label "Demo" :href "/(geography.(reactive.demo))"
(dict :label "Demo" :href "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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.(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))")))
(dict :label "Overview" :href "/sx/(language.(bootstrapper))")
(dict :label "JavaScript" :href "/sx/(language.(bootstrapper.javascript))")
(dict :label "Python" :href "/sx/(language.(bootstrapper.python))")
(dict :label "Self-Hosting (py.sx)" :href "/sx/(language.(bootstrapper.self-hosting))")
(dict :label "Self-Hosting JS (js.sx)" :href "/sx/(language.(bootstrapper.self-hosting-js))")
(dict :label "Page Helpers" :href "/sx/(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.
@@ -359,34 +359,34 @@
;; ---------------------------------------------------------------------------
(define sx-nav-tree
{:label "sx" :href "/"
{:label "sx" :href "/sx/"
:children (list
{:label "Geography" :href "/(geography)"
{:label "Geography" :href "/sx/(geography)"
:children (list
{:label "Reactive Islands" :href "/(geography.(reactive))" :children reactive-islands-nav-items}
{:label "Hypermedia Lakes" :href "/(geography.(hypermedia))"
{:label "Reactive Islands" :href "/sx/(geography.(reactive))" :children reactive-islands-nav-items}
{:label "Hypermedia Lakes" :href "/sx/(geography.(hypermedia))"
:children (list
{: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))"
{:label "Reference" :href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items}
{:label "Examples" :href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items})}
{:label "Marshes" :href "/sx/(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 "/sx/(geography.(isomorphism))" :children isomorphism-nav-items})}
{:label "Language" :href "/sx/(language)"
:children (list
{: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)"
{:label "Docs" :href "/sx/(language.(doc))" :children docs-nav-items}
{:label "Specs" :href "/sx/(language.(spec))" :children specs-nav-items}
{:label "Bootstrappers" :href "/sx/(language.(bootstrapper))" :children bootstrappers-nav-items}
{:label "Testing" :href "/sx/(language.(test))" :children testing-nav-items})}
{:label "Applications" :href "/sx/(applications)"
:children (list
{:label "SX URLs" :href "/(applications.(sx-urls))"}
{:label "CSSX" :href "/(applications.(cssx))" :children cssx-nav-items}
{:label "Protocols" :href "/(applications.(protocol))" :children protocols-nav-items})}
{:label "Etc" :href "/(etc)"
{:label "SX URLs" :href "/sx/(applications.(sx-urls))"}
{:label "CSSX" :href "/sx/(applications.(cssx))" :children cssx-nav-items}
{:label "Protocols" :href "/sx/(applications.(protocol))" :children protocols-nav-items})}
{:label "Etc" :href "/sx/(etc)"
:children (list
{: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})})})
{:label "Essays" :href "/sx/(etc.(essay))" :children essays-nav-items}
{:label "Philosophy" :href "/sx/(etc.(philosophy))" :children philosophy-nav-items}
{:label "Plans" :href "/sx/(etc.(plan))" :children plans-nav-items})})})
;; ---------------------------------------------------------------------------
;; Nav resolution — tree-descent matching (no prefix heuristics)

View File

@@ -11,8 +11,8 @@
(p :class "mt-2"
:style (cssx (:text (colour "stone" 400) (size "sm") (family "mono")))
path))
(a :href "/"
:sx-get "/" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/"
:sx-get "/sx/" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "inline-block mt-6 px-4 py-2 rounded border transition-colors"
:style (cssx (:text (colour "violet" 700) (size "sm"))

View File

@@ -193,7 +193,7 @@
(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") "))")
:filename (get item "filename") :href (str "/sx/(language.(spec." (get item "slug") "))")
:source (read-spec-file (get item "filename"))))
items)))

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.(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."))
(p "The " (a :href "/sx/(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.(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 (a :href "/sx/(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 "/sx/(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 "/sx/(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.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 1"))
(td :class "px-3 py-2" (a :href "/sx/(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.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 2"))
(td :class "px-3 py-2" (a :href "/sx/(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.(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).")))))
(p :class "text-amber-800 text-sm" (strong "Builds on: ") (a :href "/sx/(etc.(plan.content-addressed-components))" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/sx/(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.(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."))
(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 "/sx/(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.(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"))
(a :href "/sx/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/sx/(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.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
(a :href "/sx/(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 "/sx/(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.(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"))
(a :href "/sx/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/sx/(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.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
(a :href "/sx/(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 "/sx/(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.(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"))
(a :href "/sx/(language.(spec.router))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
(a :href "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(geography.(isomorphism.streaming))" :class "text-violet-700 underline" "/sx/(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 "/sx/(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 "/sx/(geography.(isomorphism.optimistic))" :class "text-violet-600 hover:underline" "/sx/(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 "/sx/(geography.(isomorphism.offline))" :class "text-violet-600 hover:underline" "/sx/(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.(plan.content-addressed-components))" "content-addressed components")
(p "Combined with the " (a :href "/sx/(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.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "isomorphic architecture roadmap")
(a :href "/sx/(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.(plan.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 "/sx/(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 "/sx/(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.(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 "Per the " (a :href "/sx/(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.(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.")))
(p "Combined with " (a :href "/sx/(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.(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."))
(li (a :href "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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.(bootstrapper.self-hosting))" :class "underline text-green-600 font-medium"
(a :href "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(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.(test))" :class "font-semibold text-green-800 underline" "Modular Test Architecture"))
(a :href "/sx/(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.(plan.fragment-protocol))" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
(a :href "/sx/(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.(plan.reader-macros))" :class "font-semibold text-stone-800 underline" "Reader Macros"))
(a :href "/sx/(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.(plan.sx-activity))" :class "font-semibold text-stone-800 underline" "SX-Activity"))
(a :href "/sx/(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.(plan.glue-decoupling))" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue"))
(a :href "/sx/(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.(plan.social-sharing))" :class "font-semibold text-stone-800 underline" "Social Network Sharing"))
(a :href "/sx/(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 "/sx/(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 "/sx/(geography.(isomorphism.streaming))" "/sx/(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 "/sx/(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.(plan.spec-explorer))" :class "font-semibold text-stone-800 underline" "Spec Explorer — The Fifth Ring"))
(a :href "/sx/(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."))))))

View File

@@ -10,7 +10,7 @@
"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 "
(code "/sx/(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
@@ -61,7 +61,7 @@
(~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."
"/sx/(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. "
@@ -144,7 +144,7 @@
(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"))
(td :class "py-2 px-3" "GET " (code "/sx/(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)")
@@ -185,31 +185,31 @@
(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 font-mono text-sm" "/sx/(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 font-mono text-sm" "/sx/(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 font-mono text-sm" "/sx/(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 font-mono text-sm" "/sx/(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 font-mono text-sm" "/sx/(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 font-mono text-sm" "/sx/(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 font-mono text-sm" "/sx/(json.(language.(doc.primitives)))")
(td :class "py-2 px-3" "Return data as JSON — pure query mode"))))))
(~doc-section :title "Evaluation Model" :id "eval"
@@ -218,7 +218,7 @@
"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"
"/sx/(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"

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.(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."))
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/sx/(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.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") "'s domain.")
(li (strong "Termination.") " That's " (a :href "/sx/(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.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:")
(p (a :href "/sx/(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.(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."))
(li (a :href "/sx/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
(li (a :href "/sx/(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 "/sx/(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 "/sx/(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 "/sx/(geography.(reactive.named-stores))" :sx-get "/sx/(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 "/sx/(geography.(reactive.phase2))" :sx-get "/sx/(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

@@ -291,7 +291,7 @@
(~demo-marsh-product)
(~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/(geography.(reactive.(api.flash-sale)))\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp"))
(~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/sx/(geography.(reactive.(api.flash-sale)))\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp"))
(~doc-code :code (highlight ";; Server returns SX content + a data-init script:\n;;\n;; (<>\n;; (p \"Flash sale! Price: $14.99\")\n;; (script :type \"text/sx\" :data-init\n;; \"(reset! (use-store \\\"demo-price\\\") 14.99)\"))\n;;\n;; The <p> is swapped in as normal hypermedia content.\n;; The script writes to the store signal.\n;; The island's (deref price), total, and savings\n;; all update reactively — no re-render, no diffing." "lisp"))
@@ -315,7 +315,7 @@
(div :id "marsh-flash-target"
:class "min-h-[2rem]")
(button :class "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/(geography.(reactive.(api.flash-sale)))"
:sx-get "/sx/(geography.(reactive.(api.flash-sale)))"
:sx-target "#marsh-flash-target"
:sx-swap "innerHTML"
"Fetch from server"))
@@ -334,7 +334,7 @@
(~demo-marsh-settle)
(~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/(geography.(reactive.(api.settle-data)))\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp"))
(~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/sx/(geography.(reactive.(api.settle-data)))\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp"))
(p "The server knows nothing about signals or counters. It returns plain content. The " (code "sx-on-settle") " hook is a client-side concern — it runs in the global SX environment with access to all primitives."))
@@ -348,7 +348,7 @@
(~demo-marsh-signal-url)
(~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/(geography.(reactive.(api.search-\"\n (deref mode) \")))\" \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp"))
(~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/sx/(geography.(reactive.(api.search-\"\n (deref mode) \")))\" \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp"))
(p "No custom plumbing. The same " (code "reactive-attr") " mechanism that makes " (code ":class") " reactive also makes " (code ":sx-get") " reactive. " (code "get-verb-info") " reads " (code "dom-get-attr") " at trigger time — it sees the current URL because the effect already updated the DOM attribute."))
@@ -361,7 +361,7 @@
(~demo-marsh-view-transform)
(~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/(geography.(reactive.(api.catalog)))\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp"))
(~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/sx/(geography.(reactive.(api.catalog)))\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp"))
(p "The view signal doesn't just toggle CSS classes — it fundamentally reshapes the DOM. List view shows description. Grid view arranges in columns. Compact view shows names only. All from the same server data, transformed by client state."))
@@ -408,7 +408,7 @@
(div :class "border-t border-stone-200 pt-3"
(div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/(geography.(reactive.(api.flash-sale)))"
:sx-get "/sx/(geography.(reactive.(api.flash-sale)))"
:sx-target "#marsh-server-msg"
:sx-swap "innerHTML"
"Fetch Price from Server")
@@ -471,7 +471,7 @@
(span :class "text-sm text-stone-600" " times")))
(div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/(geography.(reactive.(api.settle-data)))"
:sx-get "/sx/(geography.(reactive.(api.settle-data)))"
:sx-target "#settle-result"
:sx-swap "innerHTML"
:sx-on-settle "(swap! (use-store \"settle-count\") inc)"
@@ -516,7 +516,7 @@
:class "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400")
(button :class "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get (computed (fn ()
(str "/(geography.(reactive.(api.search-" (deref mode)
(str "/sx/(geography.(reactive.(api.search-" (deref mode)
")))""?q=" (deref query))))
:sx-target "#signal-results"
:sx-swap "innerHTML"
@@ -524,7 +524,7 @@
;; Current URL display
(p :class "text-xs text-stone-400 font-mono"
"URL: " (computed (fn ()
(str "/(geography.(reactive.(api.search-" (deref mode) ")))" "?q=" (deref query)))))
(str "/sx/(geography.(reactive.(api.search-" (deref mode) ")))" "?q=" (deref query)))))
;; Results
(div :id "signal-results" :class "min-h-[3rem] rounded bg-stone-50 p-2"
(p :class "text-sm text-stone-400 italic" "Select a category and search.")))))
@@ -561,7 +561,7 @@
;; Fetch button — response writes structured data to store via data-init
(div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700"
:sx-get "/(geography.(reactive.(api.catalog)))"
:sx-get "/sx/(geography.(reactive.(api.catalog)))"
:sx-target "#catalog-msg"
:sx-swap "innerHTML"
"Fetch Catalog")

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 "/sx/(geography.(reactive.event-bridge))" :sx-get "/sx/(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.(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."))))
(p :class "mt-4" "The recommendation from the " (a :href "/sx/(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.(spec.router))" :class "text-violet-700 underline" "router.sx")
(a :href "/sx/(language.(spec.router))" :class "text-violet-700 underline" "router.sx")
" route matching and "
(a :href "/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
(a :href "/sx/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
" IO detection.")
(div :class "mb-8 grid grid-cols-4 gap-4"

View File

@@ -35,8 +35,8 @@
(div
(h1 :class "text-2xl font-bold text-stone-800" title)
(p :class "text-sm text-stone-500 mt-1" desc))
(a :href (str "/(language.(spec." slug "))")
:sx-get (str "/(language.(spec." slug "))")
(a :href (str "/sx/(language.(spec." slug "))")
:sx-get (str "/sx/(language.(spec." slug "))")
: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"

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.(spec.parser))" :class "hover:underline"
:sx-get "/(language.(spec.parser))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.parser))" :class "hover:underline"
:sx-get "/sx/(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.(spec.evaluator))" :class "hover:underline"
:sx-get "/(language.(spec.evaluator))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.evaluator))" :class "hover:underline"
:sx-get "/sx/(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.(spec.primitives))" :class "hover:underline"
:sx-get "/(language.(spec.primitives))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.primitives))" :class "hover:underline"
:sx-get "/sx/(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.(spec.special-forms))" :class "hover:underline"
:sx-get "/(language.(spec.special-forms))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.special-forms))" :class "hover:underline"
:sx-get "/sx/(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.(spec.renderer))" :class "hover:underline"
:sx-get "/(language.(spec.renderer))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.renderer))" :class "hover:underline"
:sx-get "/sx/(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.(spec.adapter-dom))" :class "hover:underline"
:sx-get "/(language.(spec.adapter-dom))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.adapter-dom))" :class "hover:underline"
:sx-get "/sx/(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.(spec.adapter-html))" :class "hover:underline"
:sx-get "/(language.(spec.adapter-html))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.adapter-html))" :class "hover:underline"
:sx-get "/sx/(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.(spec.adapter-sx))" :class "hover:underline"
:sx-get "/(language.(spec.adapter-sx))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.adapter-sx))" :class "hover:underline"
:sx-get "/sx/(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.(spec.adapter-async))" :class "hover:underline"
:sx-get "/(language.(spec.adapter-async))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.adapter-async))" :class "hover:underline"
:sx-get "/sx/(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.(spec.engine))" :class "hover:underline"
:sx-get "/(language.(spec.engine))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.engine))" :class "hover:underline"
:sx-get "/sx/(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.(spec.orchestration))" :class "hover:underline"
:sx-get "/(language.(spec.orchestration))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.orchestration))" :class "hover:underline"
:sx-get "/sx/(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.(spec.boot))" :class "hover:underline"
:sx-get "/(language.(spec.boot))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.boot))" :class "hover:underline"
:sx-get "/sx/(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.(spec.router))" :class "hover:underline"
:sx-get "/(language.(spec.router))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.router))" :class "hover:underline"
:sx-get "/sx/(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.(spec.signals))" :class "hover:underline"
:sx-get "/(language.(spec.signals))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.signals))" :class "hover:underline"
:sx-get "/sx/(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.(spec.boundary))" :class "hover:underline"
:sx-get "/(language.(spec.boundary))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.boundary))" :class "hover:underline"
:sx-get "/sx/(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.(spec.forms))" :class "hover:underline"
:sx-get "/(language.(spec.forms))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.forms))" :class "hover:underline"
:sx-get "/sx/(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.(spec.page-helpers))" :class "hover:underline"
:sx-get "/(language.(spec.page-helpers))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.page-helpers))" :class "hover:underline"
:sx-get "/sx/(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.(spec.continuations))" :class "hover:underline"
:sx-get "/(language.(spec.continuations))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.continuations))" :class "hover:underline"
:sx-get "/sx/(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.(spec.callcc))" :class "hover:underline"
:sx-get "/(language.(spec.callcc))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.callcc))" :class "hover:underline"
:sx-get "/sx/(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.(spec.types))" :class "hover:underline"
:sx-get "/(language.(spec.types))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.types))" :class "hover:underline"
:sx-get "/sx/(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.(spec.deps))" :class "hover:underline"
:sx-get "/(language.(spec.deps))" :sx-target "#main-panel" :sx-select "#main-panel"
(a :href "/sx/(language.(spec.deps))" :class "hover:underline"
:sx-get "/sx/(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.(spec.(explore." (replace spec-filename ".sx" "") ")))")
:sx-get (str "/(language.(spec.(explore." (replace spec-filename ".sx" "") ")))")
(a :href (str "/sx/(language.(spec.(explore." (replace spec-filename ".sx" "") ")))")
:sx-get (str "/sx/(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.(bootstrapper.javascript))" :class "hover:underline"
(a :href "/sx/(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.(bootstrapper.python))" :class "hover:underline"
(a :href "/sx/(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.(bootstrapper.self-hosting))" :class "hover:underline"
(a :href "/sx/(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

@@ -17,7 +17,7 @@
"a nested function call that the server evaluates to produce a page.")
(p "Every page on this site is addressed by an SX URL. You are currently reading:")
(~doc-code :code (highlight
"/(applications.(sx-urls))"
"/sx/(applications.(sx-urls))"
"lisp"))
(p "This is a function call: " (code "applications") " is called with the result of "
(code "(sx-urls)") ". The server evaluates it, wraps the content in a layout, "
@@ -73,13 +73,13 @@
"Called with no arguments, they return their index page. "
"Called with content, they pass it through:")
(~doc-code :code (highlight
"/(geography) ;; geography section index\n/(language) ;; language section index\n/(applications) ;; applications section index\n/(etc) ;; etc section index"
"/sx/(geography) ;; geography section index\n/(language) ;; language section index\n/(applications) ;; applications section index\n/(etc) ;; etc section index"
"lisp")))
(~doc-subsection :title "Sub-sections"
(p "Sub-sections nest inside sections:")
(~doc-code :code (highlight
"/(geography.(hypermedia)) ;; hypermedia sub-section index\n/(geography.(reactive)) ;; reactive islands sub-section\n/(geography.(isomorphism)) ;; isomorphism sub-section\n/(language.(doc)) ;; documentation sub-section\n/(language.(spec)) ;; specification sub-section\n/(language.(bootstrapper)) ;; bootstrappers sub-section\n/(applications.(cssx)) ;; CSSX sub-section\n/(applications.(protocol)) ;; protocols sub-section"
"/sx/(geography.(hypermedia)) ;; hypermedia sub-section index\n/(geography.(reactive)) ;; reactive islands sub-section\n/(geography.(isomorphism)) ;; isomorphism sub-section\n/(language.(doc)) ;; documentation sub-section\n/(language.(spec)) ;; specification sub-section\n/(language.(bootstrapper)) ;; bootstrappers sub-section\n/(applications.(cssx)) ;; CSSX sub-section\n/(applications.(protocol)) ;; protocols sub-section"
"lisp")))
(~doc-subsection :title "Leaf pages"
@@ -104,7 +104,7 @@
"lisp"))
(p "New components are URL-accessible the moment they are defined. "
"No registration, no routing table update, no deploy. "
"Define a " (code "defcomp") ", refresh the page, visit " (code "/(~your-component)") "."))
"Define a " (code "defcomp") ", refresh the page, visit " (code "/sx/(~your-component)") "."))
;; -----------------------------------------------------------------
(~doc-section :title "Relative URLs" :id "relative"
@@ -186,27 +186,27 @@
(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 font-mono text-sm" "/sx/(!source.(~essay-sx-sucks))")
(td :class "py-2 px-3" "Show the defcomp source code"))
(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 font-mono text-sm" "/sx/(!inspect.(language.(doc.primitives)))")
(td :class "py-2 px-3" "Dependencies, CSS, render plan, I/O"))
(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 font-mono text-sm" "/sx/(!diff.(spec.signals).(spec.eval))")
(td :class "py-2 px-3" "Side-by-side comparison"))
(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 font-mono text-sm" "/sx/(!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 font-mono text-sm" "/sx/(!raw.(~some-component))")
(td :class "py-2 px-3" "Skip layout wrapping — raw content"))
(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 font-mono text-sm" "/sx/(!json.(language.(doc.primitives)))")
(td :class "py-2 px-3" "Return data as JSON")))))
(p "SX has four sigils, each marking a different kind of name:")
@@ -224,21 +224,21 @@
(~doc-subsection :title "Links"
(p "Standard anchor tags with SX URL hrefs:")
(~doc-code :code (highlight
";; Absolute links (the site navigation uses these):\n(a :href \"/(language.(doc.introduction))\" \"Introduction\")\n(a :href \"/(etc.(essay.sx-sucks))\" \"SX Sucks\")\n\n;; Relative links (navigate from current page):\n(a :href \"..inline-edit\" \"Inline Edit\") ;; up and over\n(a :href \".getting-started\" \"Getting Started\") ;; same level\n(a :href \"..\" \"Back\") ;; up one level\n\n;; Direct component links:\n(a :href \"/(~essay-self-defining-medium)\" \"The True Hypermedium\")"
";; Absolute links (the site navigation uses these):\n(a :href \"/sx/(language.(doc.introduction))\" \"Introduction\")\n(a :href \"/sx/(etc.(essay.sx-sucks))\" \"SX Sucks\")\n\n;; Relative links (navigate from current page):\n(a :href \"..inline-edit\" \"Inline Edit\") ;; up and over\n(a :href \".getting-started\" \"Getting Started\") ;; same level\n(a :href \"..\" \"Back\") ;; up one level\n\n;; Direct component links:\n(a :href \"/sx/(~essay-self-defining-medium)\" \"The True Hypermedium\")"
"lisp")))
(~doc-subsection :title "sx-get — HTMX-style fetching"
(p (code "sx-get") " fetches content from an SX URL and swaps it into the DOM. "
"SX URLs work exactly like path URLs:")
(~doc-code :code (highlight
";; Fetch and swap a page section:\n(div :sx-get \"/(geography.(hypermedia.(example.progress-bar)))\"\n :sx-trigger \"click\"\n :sx-target \"#content\"\n :sx-swap \"innerHTML\"\n \"Load Progress Bar Example\")\n\n;; Relative sx-get — resolve relative to the current page:\n(div :sx-get \"..inline-edit\"\n :sx-trigger \"click\"\n :sx-target \"#content\"\n :sx-swap \"innerHTML\"\n \"Load Inline Edit\")\n\n;; Paginated content with keyword deltas:\n(button :sx-get \".:page.+1\"\n :sx-trigger \"click\"\n :sx-target \"#results\"\n :sx-swap \"innerHTML\"\n \"Next Page\")"
";; Fetch and swap a page section:\n(div :sx-get \"/sx/(geography.(hypermedia.(example.progress-bar)))\"\n :sx-trigger \"click\"\n :sx-target \"#content\"\n :sx-swap \"innerHTML\"\n \"Load Progress Bar Example\")\n\n;; Relative sx-get — resolve relative to the current page:\n(div :sx-get \"..inline-edit\"\n :sx-trigger \"click\"\n :sx-target \"#content\"\n :sx-swap \"innerHTML\"\n \"Load Inline Edit\")\n\n;; Paginated content with keyword deltas:\n(button :sx-get \".:page.+1\"\n :sx-trigger \"click\"\n :sx-target \"#results\"\n :sx-swap \"innerHTML\"\n \"Next Page\")"
"lisp")))
(~doc-subsection :title "sx-boost — progressive enhancement"
(p (code "sx-boost") " upgrades regular links to use HTMX-style fetch+swap "
"instead of full-page navigation:")
(~doc-code :code (highlight
";; A navigation list with boosted links:\n(nav :sx-boost \"true\"\n (ul\n (li (a :href \"/(language.(doc.introduction))\" \"Introduction\"))\n (li (a :href \"/(language.(doc.components))\" \"Components\"))\n (li (a :href \"/(language.(doc.evaluator))\" \"Evaluator\"))))\n\n;; Clicking any link fetches content via SX URL and swaps,\n;; rather than triggering a full page load.\n;; URL bar updates. Back button works. No JavaScript needed."
";; A navigation list with boosted links:\n(nav :sx-boost \"true\"\n (ul\n (li (a :href \"/sx/(language.(doc.introduction))\" \"Introduction\"))\n (li (a :href \"/sx/(language.(doc.components))\" \"Components\"))\n (li (a :href \"/sx/(language.(doc.evaluator))\" \"Evaluator\"))))\n\n;; Clicking any link fetches content via SX URL and swaps,\n;; rather than triggering a full page load.\n;; URL bar updates. Back button works. No JavaScript needed."
"lisp")))
(~doc-subsection :title "Pagination pattern"
@@ -265,7 +265,7 @@
(~doc-subsection :title "Step 1: Parse"
(p "Strip the leading " (code "/") ", replace dots with spaces, parse as SX:")
(~doc-code :code (highlight
"/(language.(doc.introduction))\n→ strip / → (language.(doc.introduction))\n→ dots to spaces → (language (doc introduction))\n→ parse → [Symbol(\"language\"), [Symbol(\"doc\"), Symbol(\"introduction\")]]"
"/sx/(language.(doc.introduction))\n→ strip / → (language.(doc.introduction))\n→ dots to spaces → (language (doc introduction))\n→ parse → [Symbol(\"language\"), [Symbol(\"doc\"), Symbol(\"introduction\")]]"
"lisp")))
(~doc-subsection :title "Step 2: Auto-quote"
@@ -386,7 +386,7 @@
"This means:")
(ul :class "space-y-1 text-stone-600 list-disc pl-5"
(li (strong "Cacheable") " — CDNs cache by URL, and these are URLs")
(li (strong "Bookmarkable") " — save " (code "/(language.(spec.signals))") " in your browser")
(li (strong "Bookmarkable") " — save " (code "/sx/(language.(spec.signals))") " in your browser")
(li (strong "Shareable") " — paste it in chat, it works")
(li (strong "Indexable") " — crawlers follow " (code "<a href>") " links")
(li (strong "No client library") " — " (code "curl '/(language.(doc.intro))'") " returns content"))
@@ -398,17 +398,17 @@
;; -----------------------------------------------------------------
(~doc-section :title "The Router Spec" :id "spec"
(p "SX URLs are not a Python or JavaScript feature — they are specified in SX itself. "
"The " (a :href "/(language.(spec.router))" :class "text-violet-600 hover:underline" "router spec")
"The " (a :href "/sx/(language.(spec.router))" :class "text-violet-600 hover:underline" "router spec")
" (" (code "router.sx") ") defines all URL parsing, matching, relative resolution, "
"and special form detection as pure functions.")
(p "Key functions from the spec:")
(~doc-code :code (highlight
";; Parse a URL into a typed descriptor:\n(parse-sx-url \"/(language.(doc.intro))\")\n→ {\"type\" \"absolute\" \"raw\" \"/(language.(doc.intro))\"}\n\n(parse-sx-url \"/(!source.(~essay))\")\n→ {\"type\" \"special-form\" \"form\" \"!source\"\n \"inner\" \"(~essay)\" \"raw\" \"/(!source.(~essay))\"}\n\n(parse-sx-url \"/(~essay-sx-sucks)\")\n→ {\"type\" \"direct-component\" \"name\" \"~essay-sx-sucks\"\n \"raw\" \"/(~essay-sx-sucks)\"}\n\n(parse-sx-url \"..eval\")\n→ {\"type\" \"relative\" \"raw\" \"..eval\"}\n\n;; Resolve relative URLs:\n(resolve-relative-url\n \"/(geography.(hypermedia.(example.progress-bar)))\"\n \"..inline-edit\")\n→ \"/(geography.(hypermedia.(example.inline-edit)))\"\n\n;; Keyword delta:\n(resolve-relative-url\n \"/(language.(spec.(explore.signals.:page.3)))\"\n \".:page.+1\")\n→ \"/(language.(spec.(explore.signals.:page.4)))\"\n\n;; Check URL type:\n(relative-sx-url? \"..eval\") → true\n(relative-sx-url? \"(.slug)\") → true\n(relative-sx-url? \"/(foo)\") → false\n\n;; Special form inspection:\n(url-special-form? \"!source\") → true\n(url-special-form? \"inspect\") → false\n(url-special-form-name \"/(!source.(~essay))\") → \"!source\"\n(url-special-form-inner \"/(!source.(~essay))\") → \"(~essay)\""
";; Parse a URL into a typed descriptor:\n(parse-sx-url \"/sx/(language.(doc.intro))\")\n→ {\"type\" \"absolute\" \"raw\" \"/sx/(language.(doc.intro))\"}\n\n(parse-sx-url \"/sx/(!source.(~essay))\")\n→ {\"type\" \"special-form\" \"form\" \"!source\"\n \"inner\" \"(~essay)\" \"raw\" \"/sx/(!source.(~essay))\"}\n\n(parse-sx-url \"/sx/(~essay-sx-sucks)\")\n→ {\"type\" \"direct-component\" \"name\" \"~essay-sx-sucks\"\n \"raw\" \"/sx/(~essay-sx-sucks)\"}\n\n(parse-sx-url \"..eval\")\n→ {\"type\" \"relative\" \"raw\" \"..eval\"}\n\n;; Resolve relative URLs:\n(resolve-relative-url\n \"/sx/(geography.(hypermedia.(example.progress-bar)))\"\n \"..inline-edit\")\n→ \"/sx/(geography.(hypermedia.(example.inline-edit)))\"\n\n;; Keyword delta:\n(resolve-relative-url\n \"/sx/(language.(spec.(explore.signals.:page.3)))\"\n \".:page.+1\")\n→ \"/sx/(language.(spec.(explore.signals.:page.4)))\"\n\n;; Check URL type:\n(relative-sx-url? \"..eval\") → true\n(relative-sx-url? \"(.slug)\") → true\n(relative-sx-url? \"/sx/(foo)\") → false\n\n;; Special form inspection:\n(url-special-form? \"!source\") → true\n(url-special-form? \"inspect\") → false\n(url-special-form-name \"/sx/(!source.(~essay))\") → \"!source\"\n(url-special-form-inner \"/sx/(!source.(~essay))\") → \"(~essay)\""
"lisp"))
(p "These functions are "
(a :href "/(language.(test.router))" :class "text-violet-600 hover:underline" "tested with 115 SX tests")
(a :href "/sx/(language.(test.router))" :class "text-violet-600 hover:underline" "tested with 115 SX tests")
" covering every edge case — structural navigation, keyword operations, "
"delta values, special form parsing, and bare-dot shorthand. "
"The spec is bootstrapped to both JavaScript (" (code "sx-browser.js") ") and "
@@ -434,7 +434,7 @@
(~doc-section :title "The Site Is a REPL" :id "repl"
(p "The address bar is the input line. The page is the output.")
(~doc-code :code (highlight
"/(about) ;; renders the about page\n/(!source.(about)) ;; returns its source code\n\n/(language.(spec.signals)) ;; the signals spec\n/(!inspect.(language.(spec.signals))) ;; its dependency graph\n\n/(~essay-sx-sucks) ;; the essay\n/(!source.(~essay-sx-sucks)) ;; its s-expression source\n\n;; The website IS a REPL. Every page is a function call.\n;; Every function call is a URL. Evaluation is navigation."
"/sx/(about) ;; renders the about page\n/(!source.(about)) ;; returns its source code\n\n/(language.(spec.signals)) ;; the signals spec\n/(!inspect.(language.(spec.signals))) ;; its dependency graph\n\n/(~essay-sx-sucks) ;; the essay\n/(!source.(~essay-sx-sucks)) ;; its s-expression source\n\n;; The website IS a REPL. Every page is a function call.\n;; Every function call is a URL. Evaluation is navigation."
"lisp"))
(p "You do not need to explain what SX is. "
"Show someone a URL, and they immediately understand the philosophy. "

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.(test.eval))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/sx/(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.(test.parser))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/sx/(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.(test.router))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/sx/(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.(test.render))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/sx/(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.(test.deps))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/sx/(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.(test.engine))" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(a :href "/sx/(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"))))

View File

@@ -22,7 +22,7 @@
(div :id "click-result" :class "p-4 rounded bg-stone-100 text-stone-500 text-center"
"Click the button to load content.")
(button
:sx-get "/(geography.(hypermedia.(example.(api.click))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.click))))"
:sx-target "#click-result"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors"
@@ -39,7 +39,7 @@
(defcomp ~form-demo ()
(div :class "space-y-4"
(form
:sx-post "/(geography.(hypermedia.(example.(api.form))))"
:sx-post "/sx/(geography.(hypermedia.(example.(api.form))))"
:sx-target "#form-result"
:sx-swap "innerHTML"
:class "space-y-3"
@@ -63,7 +63,7 @@
(defcomp ~polling-demo ()
(div :class "space-y-4"
(div :id "poll-target"
:sx-get "/(geography.(hypermedia.(example.(api.poll))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.poll))))"
:sx-trigger "load, every 2s"
:sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono"
@@ -99,7 +99,7 @@
(td :class "px-3 py-2 text-stone-700" name)
(td :class "px-3 py-2"
(button
:sx-delete (str "/(geography.(hypermedia.(example.(api.(delete." id ")))))")
:sx-delete (str "/sx/(geography.(hypermedia.(example.(api.(delete." id ")))))")
:sx-target (str "#row-" id)
:sx-swap "outerHTML"
:sx-confirm "Delete this item?"
@@ -116,7 +116,7 @@
(div :class "flex items-center justify-between p-3 rounded border border-stone-200"
(span :class "text-stone-800" value)
(button
:sx-get (str "/(geography.(hypermedia.(example.(api.edit))))?value=" value)
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.edit))))?value=" value)
:sx-target "#edit-target"
:sx-swap "innerHTML"
:class "text-sm text-violet-600 hover:text-violet-800"
@@ -124,7 +124,7 @@
(defcomp ~inline-edit-form (&key value)
(form
:sx-post "/(geography.(hypermedia.(example.(api.edit))))"
:sx-post "/sx/(geography.(hypermedia.(example.(api.edit))))"
:sx-target "#edit-target"
:sx-swap "innerHTML"
:class "flex items-center gap-2 p-3 rounded border border-violet-300 bg-violet-50"
@@ -134,7 +134,7 @@
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"save")
(button :type "button"
:sx-get (str "/(geography.(hypermedia.(example.(api.edit-cancel))))?value=" value)
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.edit-cancel))))?value=" value)
:sx-target "#edit-target"
:sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
@@ -152,7 +152,7 @@
(p :class "text-stone-500" "Box B")
(p :class "text-sm text-stone-400" "Waiting...")))
(button
:sx-get "/(geography.(hypermedia.(example.(api.oob))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.oob))))"
:sx-target "#oob-box-a"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -164,7 +164,7 @@
(div :class "space-y-4"
(p :class "text-sm text-stone-500" "The content below loads automatically when the page renders.")
(div :id "lazy-target"
:sx-get "/(geography.(hypermedia.(example.(api.lazy))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.lazy))))"
:sx-trigger "load"
:sx-swap "innerHTML"
:class "p-6 rounded border border-stone-200 bg-stone-100 text-center"
@@ -187,7 +187,7 @@
(str "Item " (+ i 1) " — loaded with the page")))
(list 1 2 3 4 5))
(div :id "scroll-sentinel"
:sx-get "/(geography.(hypermedia.(example.(api.scroll))))?page=2"
:sx-get "/sx/(geography.(hypermedia.(example.(api.scroll))))?page=2"
:sx-trigger "intersect once"
:sx-target "#scroll-items"
:sx-swap "beforeend"
@@ -201,7 +201,7 @@
items)
(when (<= page 5)
(div :id "scroll-sentinel"
:sx-get (str "/(geography.(hypermedia.(example.(api.scroll))))?page=" page)
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.scroll))))?page=" page)
:sx-trigger "intersect once"
:sx-target "#scroll-items"
:sx-swap "beforeend"
@@ -217,7 +217,7 @@
(div :class "bg-violet-600 h-4 rounded-full transition-all" :style "width: 0%"))
(p :class "text-sm text-stone-500 text-center" "Click start to begin."))
(button
:sx-post "/(geography.(hypermedia.(example.(api.progress-start))))"
:sx-post "/sx/(geography.(hypermedia.(example.(api.progress-start))))"
:sx-target "#progress-target"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -230,7 +230,7 @@
:style (str "width: " percent "%")))
(p :class "text-sm text-stone-500 text-center" (str percent "% complete"))
(when (< percent 100)
(div :sx-get (str "/(geography.(hypermedia.(example.(api.progress-status))))?job=" job-id)
(div :sx-get (str "/sx/(geography.(hypermedia.(example.(api.progress-status))))?job=" job-id)
:sx-trigger "load delay:500ms"
:sx-target "#progress-target"
:sx-swap "innerHTML"))
@@ -242,7 +242,7 @@
(defcomp ~active-search-demo ()
(div :class "space-y-3"
(input :type "text" :name "q"
:sx-get "/(geography.(hypermedia.(example.(api.search))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.search))))"
:sx-trigger "keyup delay:300ms changed"
:sx-target "#search-results"
:sx-swap "innerHTML"
@@ -262,11 +262,11 @@
;; --- Inline validation demo ---
(defcomp ~inline-validation-demo ()
(form :class "space-y-4" :sx-post "/(geography.(hypermedia.(example.(api.validate-submit))))" :sx-target "#validation-result" :sx-swap "innerHTML"
(form :class "space-y-4" :sx-post "/sx/(geography.(hypermedia.(example.(api.validate-submit))))" :sx-target "#validation-result" :sx-swap "innerHTML"
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Email")
(input :type "text" :name "email" :placeholder "user@example.com"
:sx-get "/(geography.(hypermedia.(example.(api.validate))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.validate))))"
:sx-trigger "blur"
:sx-target "#email-feedback"
:sx-swap "innerHTML"
@@ -290,7 +290,7 @@
(div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Category")
(select :name "category"
:sx-get "/(geography.(hypermedia.(example.(api.values))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.values))))"
:sx-trigger "change"
:sx-target "#value-items"
:sx-swap "innerHTML"
@@ -314,7 +314,7 @@
(defcomp ~reset-on-submit-demo ()
(div :class "space-y-3"
(form :id "reset-form"
:sx-post "/(geography.(hypermedia.(example.(api.reset-submit))))"
:sx-post "/sx/(geography.(hypermedia.(example.(api.reset-submit))))"
:sx-target "#reset-result"
:sx-swap "innerHTML"
:sx-on:afterSwap "this.reset()"
@@ -354,7 +354,7 @@
(td :class "px-3 py-2 text-stone-700" stock)
(td :class "px-3 py-2"
(button
:sx-get (str "/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-target (str "#erow-" id)
:sx-swap "outerHTML"
:class "text-sm text-violet-600 hover:text-violet-800"
@@ -373,14 +373,14 @@
:class "w-20 px-2 py-1 border border-stone-300 rounded text-sm"))
(td :class "px-3 py-2 space-x-1"
(button
:sx-post (str "/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-post (str "/sx/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-target (str "#erow-" id)
:sx-swap "outerHTML"
:sx-include (str "#erow-" id)
:class "text-sm text-emerald-600 hover:text-emerald-800"
"save")
(button
:sx-get (str "/(geography.(hypermedia.(example.(api.(editrow-cancel." id ")))))")
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.(editrow-cancel." id ")))))")
:sx-target (str "#erow-" id)
:sx-swap "outerHTML"
:class "text-sm text-stone-500 hover:text-stone-700"
@@ -393,14 +393,14 @@
(form :id "bulk-form"
(div :class "flex gap-2 mb-3"
(button :type "button"
:sx-post "/(geography.(hypermedia.(example.(api.bulk))))?action=activate"
:sx-post "/sx/(geography.(hypermedia.(example.(api.bulk))))?action=activate"
:sx-target "#bulk-table"
:sx-swap "innerHTML"
:sx-include "#bulk-form"
:class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
"Activate")
(button :type "button"
:sx-post "/(geography.(hypermedia.(example.(api.bulk))))?action=deactivate"
:sx-post "/sx/(geography.(hypermedia.(example.(api.bulk))))?action=deactivate"
:sx-target "#bulk-table"
:sx-swap "innerHTML"
:sx-include "#bulk-form"
@@ -437,19 +437,19 @@
(div :class "space-y-3"
(div :class "flex gap-2"
(button
:sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend"
:sx-post "/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend"
:sx-target "#swap-log"
:sx-swap "beforeend"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Add to End")
(button
:sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin"
:sx-post "/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin"
:sx-target "#swap-log"
:sx-swap "afterbegin"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Add to Start")
(button
:sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=none"
:sx-post "/sx/(geography.(hypermedia.(example.(api.swap-log))))?mode=none"
:sx-target "#swap-log"
:sx-swap "none"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
@@ -469,21 +469,21 @@
(div :class "space-y-3"
(div :class "flex gap-2"
(button
:sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target"
:sx-swap "innerHTML"
:sx-select "#dash-stats"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Stats Only")
(button
:sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target"
:sx-swap "innerHTML"
:sx-select "#dash-header"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Header Only")
(button
:sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target"
:sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
@@ -505,10 +505,10 @@
(defcomp ~tab-btn (&key tab label active)
(button
:sx-get (str "/(geography.(hypermedia.(example.(api.(tabs." tab ")))))")
:sx-get (str "/sx/(geography.(hypermedia.(example.(api.(tabs." tab ")))))")
:sx-target "#tab-content"
:sx-swap "innerHTML"
:sx-push-url (str "/(geography.(hypermedia.(example.tabs)))?tab=" tab)
:sx-push-url (str "/sx/(geography.(hypermedia.(example.tabs)))?tab=" tab)
:class (str "px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors "
(if (= active "true")
"border-violet-600 text-violet-600"
@@ -520,7 +520,7 @@
(defcomp ~animations-demo ()
(div :class "space-y-4"
(button
:sx-get "/(geography.(hypermedia.(example.(api.animate))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.animate))))"
:sx-target "#anim-target"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -539,7 +539,7 @@
(defcomp ~dialogs-demo ()
(div
(button
:sx-get "/(geography.(hypermedia.(example.(api.dialog))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -549,7 +549,7 @@
(defcomp ~dialog-modal (&key title message)
(div :class "fixed inset-0 z-50 flex items-center justify-center"
(div :class "absolute inset-0 bg-black/50"
:sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML")
(div :class "relative bg-stone-100 rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4"
@@ -557,13 +557,13 @@
(p :class "text-stone-600" message)
(div :class "flex justify-end gap-2"
(button
:sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
"Cancel")
(button
:sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
@@ -586,16 +586,16 @@
(kbd :class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono" "h")
(span :class "text-sm text-stone-500" "Help"))))
(div :id "kbd-target"
:sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=s"
:sx-get "/sx/(geography.(hypermedia.(example.(api.keyboard))))?key=s"
:sx-trigger "keyup[key=='s'&&!event.target.matches('input,textarea')] from:body"
:sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-400 text-sm" "Press a shortcut key..."))
(div :sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=n"
(div :sx-get "/sx/(geography.(hypermedia.(example.(api.keyboard))))?key=n"
:sx-trigger "keyup[key=='n'&&!event.target.matches('input,textarea')] from:body"
:sx-target "#kbd-target"
:sx-swap "innerHTML")
(div :sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=h"
(div :sx-get "/sx/(geography.(hypermedia.(example.(api.keyboard))))?key=h"
:sx-trigger "keyup[key=='h'&&!event.target.matches('input,textarea')] from:body"
:sx-target "#kbd-target"
:sx-swap "innerHTML")))
@@ -619,7 +619,7 @@
(p :class "text-sm text-stone-500" email)
(p :class "text-sm text-stone-500" role))
(button
:sx-get "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
:sx-target "#pp-target"
:sx-swap "innerHTML"
:class "text-sm text-violet-600 hover:text-violet-800"
@@ -627,7 +627,7 @@
(defcomp ~pp-form-full (&key name email role)
(form
:sx-put "/(geography.(hypermedia.(example.(api.putpatch))))"
:sx-put "/sx/(geography.(hypermedia.(example.(api.putpatch))))"
:sx-target "#pp-target"
:sx-swap "innerHTML"
:class "space-y-3"
@@ -648,7 +648,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Save All (PUT)")
(button :type "button"
:sx-get "/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
:sx-target "#pp-target"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
@@ -659,7 +659,7 @@
(defcomp ~json-encoding-demo ()
(div :class "space-y-4"
(form
:sx-post "/(geography.(hypermedia.(example.(api.json-echo))))"
:sx-post "/sx/(geography.(hypermedia.(example.(api.json-echo))))"
:sx-target "#json-result"
:sx-swap "innerHTML"
:sx-encoding "json"
@@ -691,7 +691,7 @@
(div :class "space-y-2"
(h4 :class "text-sm font-semibold text-stone-700" "sx-vals — send extra values")
(button
:sx-get "/(geography.(hypermedia.(example.(api.echo-vals))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.echo-vals))))"
:sx-target "#vals-result"
:sx-swap "innerHTML"
:sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}"
@@ -702,7 +702,7 @@
(div :class "space-y-2"
(h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers")
(button
:sx-get "/(geography.(hypermedia.(example.(api.echo-headers))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.echo-headers))))"
:sx-target "#headers-result"
:sx-swap "innerHTML"
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
@@ -723,7 +723,7 @@
(defcomp ~loading-states-demo ()
(div :class "space-y-4"
(button
:sx-get "/(geography.(hypermedia.(example.(api.slow))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.slow))))"
:sx-target "#loading-result"
:sx-swap "innerHTML"
:class "sx-loading-btn px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm flex items-center gap-2"
@@ -742,7 +742,7 @@
(defcomp ~sync-replace-demo ()
(div :class "space-y-3"
(input :type "text" :name "q"
:sx-get "/(geography.(hypermedia.(example.(api.slow-search))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.slow-search))))"
:sx-trigger "keyup delay:200ms changed"
:sx-target "#sync-result"
:sx-swap "innerHTML"
@@ -762,7 +762,7 @@
(defcomp ~retry-demo ()
(div :class "space-y-4"
(button
:sx-get "/(geography.(hypermedia.(example.(api.flaky))))"
:sx-get "/sx/(geography.(hypermedia.(example.(api.flaky))))"
:sx-target "#retry-result"
:sx-swap "innerHTML"
:sx-retry "exponential:1000:8000"

View File

@@ -21,7 +21,7 @@
:path "/language/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(language)"))
:content (~sx-doc :path "/sx/(language)"))
;; ---------------------------------------------------------------------------
;; Docs section (under Language)
@@ -31,13 +31,13 @@
:path "/language/docs/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(language.(doc))" (~docs-introduction-content)))
:content (~sx-doc :path "/sx/(language.(doc))" (~docs-introduction-content)))
(defpage docs-page
:path "/language/docs/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(language.(doc." slug "))")
:content (~sx-doc :path (str "/sx/(language.(doc." slug "))")
(case slug
"introduction" (~docs-introduction-content)
"getting-started" (~docs-getting-started-content)
@@ -58,20 +58,20 @@
:path "/geography/hypermedia/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(geography.(hypermedia))"))
:content (~sx-doc :path "/sx/(geography.(hypermedia))"))
(defpage reference-index
:path "/geography/hypermedia/reference/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(geography.(hypermedia.(reference)))" (~reference-index-content)))
:content (~sx-doc :path "/sx/(geography.(hypermedia.(reference)))" (~reference-index-content)))
(defpage reference-page
:path "/geography/hypermedia/reference/<slug>"
:auth :public
:layout :sx-docs
:data (reference-data slug)
:content (~sx-doc :path (str "/(geography.(hypermedia.(reference." slug ")))")
:content (~sx-doc :path (str "/sx/(geography.(hypermedia.(reference." slug ")))")
(case slug
"attributes" (~reference-attrs-content
:req-table (~doc-attr-table-from-data :title "Request Attributes" :attrs req-attrs)
@@ -98,7 +98,7 @@
:auth :public
:layout :sx-docs
:data (attr-detail-data slug)
:content (~sx-doc :path (str "/(geography.(hypermedia.(reference-detail.attributes." slug ")))")
:content (~sx-doc :path (str "/sx/(geography.(hypermedia.(reference-detail.attributes." slug ")))")
(if attr-not-found
(~reference-attr-not-found :slug slug)
(~reference-attr-detail-content
@@ -114,7 +114,7 @@
:auth :public
:layout :sx-docs
:data (header-detail-data slug)
:content (~sx-doc :path (str "/(geography.(hypermedia.(reference-detail.headers." slug ")))")
:content (~sx-doc :path (str "/sx/(geography.(hypermedia.(reference-detail.headers." slug ")))")
(if header-not-found
(~reference-attr-not-found :slug slug)
(~reference-header-detail-content
@@ -129,7 +129,7 @@
:auth :public
:layout :sx-docs
:data (event-detail-data slug)
:content (~sx-doc :path (str "/(geography.(hypermedia.(reference-detail.events." slug ")))")
:content (~sx-doc :path (str "/sx/(geography.(hypermedia.(reference-detail.events." slug ")))")
(if event-not-found
(~reference-attr-not-found :slug slug)
(~reference-event-detail-content
@@ -146,7 +146,7 @@
:path "/applications/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(applications)"))
:content (~sx-doc :path "/sx/(applications)"))
;; ---------------------------------------------------------------------------
;; Protocols section (under Applications)
@@ -156,13 +156,13 @@
:path "/applications/protocols/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(applications.(protocol))" (~protocol-wire-format-content)))
:content (~sx-doc :path "/sx/(applications.(protocol))" (~protocol-wire-format-content)))
(defpage protocol-page
:path "/applications/protocols/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(applications.(protocol." slug "))")
:content (~sx-doc :path (str "/sx/(applications.(protocol." slug "))")
(case slug
"wire-format" (~protocol-wire-format-content)
"fragments" (~protocol-fragments-content)
@@ -180,13 +180,13 @@
:path "/geography/hypermedia/examples/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(geography.(hypermedia.(example)))"))
:content (~sx-doc :path "/sx/(geography.(hypermedia.(example)))"))
(defpage examples-page
:path "/geography/hypermedia/examples/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(geography.(hypermedia.(example." slug ")))")
:content (~sx-doc :path (str "/sx/(geography.(hypermedia.(example." slug ")))")
(case slug
"click-to-load" (~example-click-to-load)
"form-submission" (~example-form-submission)
@@ -225,7 +225,7 @@
:path "/etc/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(etc)"))
:content (~sx-doc :path "/sx/(etc)"))
;; ---------------------------------------------------------------------------
;; Essays section (under Etc)
@@ -235,13 +235,13 @@
:path "/etc/essays/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(etc.(essay))" (~essays-index-content)))
:content (~sx-doc :path "/sx/(etc.(essay))" (~essays-index-content)))
(defpage essay-page
:path "/etc/essays/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(etc.(essay." slug "))")
:content (~sx-doc :path (str "/sx/(etc.(essay." slug "))")
(case slug
"sx-sucks" (~essay-sx-sucks)
"why-sexps" (~essay-why-sexps)
@@ -271,13 +271,13 @@
:path "/etc/philosophy/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(etc.(philosophy))" (~philosophy-index-content)))
:content (~sx-doc :path "/sx/(etc.(philosophy))" (~philosophy-index-content)))
(defpage philosophy-page
:path "/etc/philosophy/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(etc.(philosophy." slug "))")
:content (~sx-doc :path (str "/sx/(etc.(philosophy." slug "))")
(case slug
"sx-manifesto" (~essay-sx-manifesto)
"godel-escher-bach" (~essay-godel-escher-bach)
@@ -294,13 +294,13 @@
:path "/applications/cssx/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(applications.(cssx))" (~cssx-overview-content)))
:content (~sx-doc :path "/sx/(applications.(cssx))" (~cssx-overview-content)))
(defpage cssx-page
:path "/applications/cssx/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(applications.(cssx." slug "))")
:content (~sx-doc :path (str "/sx/(applications.(cssx." slug "))")
(case slug
"patterns" (~cssx-patterns-content)
"delivery" (~cssx-delivery-content)
@@ -318,18 +318,18 @@
:path "/language/specs/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(language.(spec))" (~spec-architecture-content)))
:content (~sx-doc :path "/sx/(language.(spec))" (~spec-architecture-content)))
(defpage specs-page
:path "/language/specs/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(language.(spec." slug "))")
:content (~sx-doc :path (str "/sx/(language.(spec." slug "))")
(let ((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") "))")
:filename (get item "filename") :href (str "/sx/(language.(spec." (get item "slug") "))")
:source (read-spec-file (get item "filename"))))
items))))
(case slug
@@ -369,7 +369,7 @@
:path "/language/specs/explore/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(language.(spec.(explore." slug ")))")
:content (~sx-doc :path (str "/sx/(language.(spec.(explore." slug ")))")
(let ((spec (find-spec slug)))
(if spec
(let ((data (spec-explorer-data
@@ -389,14 +389,14 @@
:path "/language/bootstrappers/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(language.(bootstrapper))" (~bootstrappers-index-content)))
:content (~sx-doc :path "/sx/(language.(bootstrapper))" (~bootstrappers-index-content)))
(defpage bootstrapper-page
:path "/language/bootstrappers/<slug>"
:auth :public
:layout :sx-docs
:data (bootstrapper-data slug)
:content (~sx-doc :path (str "/(language.(bootstrapper." slug "))")
:content (~sx-doc :path (str "/sx/(language.(bootstrapper." slug "))")
(if bootstrapper-not-found
(~spec-not-found :slug slug)
(case slug
@@ -434,14 +434,14 @@
:path "/geography/isomorphism/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(geography.(isomorphism))" (~plan-isomorphic-content)))
:content (~sx-doc :path "/sx/(geography.(isomorphism))" (~plan-isomorphic-content)))
(defpage bundle-analyzer
:path "/geography/isomorphism/bundle-analyzer"
:auth :public
:layout :sx-docs
:data (bundle-analyzer-data)
:content (~sx-doc :path "/(geography.(isomorphism.bundle-analyzer))"
:content (~sx-doc :path "/sx/(geography.(isomorphism.bundle-analyzer))"
(~bundle-analyzer-content
:pages pages :total-components total-components :total-macros total-macros
:pure-count pure-count :io-count io-count)))
@@ -451,7 +451,7 @@
:auth :public
:layout :sx-docs
:data (routing-analyzer-data)
:content (~sx-doc :path "/(geography.(isomorphism.routing-analyzer))"
:content (~sx-doc :path "/sx/(geography.(isomorphism.routing-analyzer))"
(~routing-analyzer-content
:pages pages :total-pages total-pages :client-count client-count
:server-count server-count :registry-sample registry-sample)))
@@ -461,7 +461,7 @@
:auth :public
:layout :sx-docs
:data (data-test-data)
:content (~sx-doc :path "/(geography.(isomorphism.data-test))"
:content (~sx-doc :path "/sx/(geography.(isomorphism.data-test))"
(~data-test-content
:server-time server-time :items items
:phase phase :transport transport)))
@@ -470,14 +470,14 @@
:path "/geography/isomorphism/async-io"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(geography.(isomorphism.async-io))" (~async-io-demo-content)))
:content (~sx-doc :path "/sx/(geography.(isomorphism.async-io))" (~async-io-demo-content)))
(defpage streaming-demo
:path "/geography/isomorphism/streaming"
:auth :public
:stream true
:layout :sx-docs
:shell (~sx-doc :path "/(geography.(isomorphism.streaming))"
:shell (~sx-doc :path "/sx/(geography.(isomorphism.streaming))"
(~streaming-demo-layout
(~suspense :id "stream-fast" :fallback (~stream-skeleton))
(~suspense :id "stream-medium" :fallback (~stream-skeleton))
@@ -494,7 +494,7 @@
:auth :public
:layout :sx-docs
:data (affinity-demo-data)
:content (~sx-doc :path "/(geography.(isomorphism.affinity))"
:content (~sx-doc :path "/sx/(geography.(isomorphism.affinity))"
(~affinity-demo-content :components components :page-plans page-plans)))
(defpage optimistic-demo
@@ -502,7 +502,7 @@
:auth :public
:layout :sx-docs
:data (optimistic-demo-data)
:content (~sx-doc :path "/(geography.(isomorphism.optimistic))"
:content (~sx-doc :path "/sx/(geography.(isomorphism.optimistic))"
(~optimistic-demo-content :items items :server-time server-time)))
(defpage offline-demo
@@ -510,7 +510,7 @@
:auth :public
:layout :sx-docs
:data (offline-demo-data)
:content (~sx-doc :path "/(geography.(isomorphism.offline))"
:content (~sx-doc :path "/sx/(geography.(isomorphism.offline))"
(~offline-demo-content :notes notes :server-time server-time)))
;; Wildcard must come AFTER specific routes (first-match routing)
@@ -518,7 +518,7 @@
:path "/geography/isomorphism/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(geography.(isomorphism." slug "))")
:content (~sx-doc :path (str "/sx/(geography.(isomorphism." slug "))")
(case slug
"bundle-analyzer" (~bundle-analyzer-content
:pages pages :total-components total-components :total-macros total-macros
@@ -536,7 +536,7 @@
:path "/etc/plans/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(etc.(plan))" (~plans-index-content)))
:content (~sx-doc :path "/sx/(etc.(plan))" (~plans-index-content)))
(defpage plan-page
:path "/etc/plans/<slug>"
@@ -545,7 +545,7 @@
:data (case slug
"theorem-prover" (prove-data)
:else nil)
:content (~sx-doc :path (str "/(etc.(plan." slug "))")
:content (~sx-doc :path (str "/sx/(etc.(plan." slug "))")
(case slug
"status" (~plan-status-content)
"reader-macros" (~plan-reader-macros-content)
@@ -586,7 +586,7 @@
:path "/geography/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(geography)"))
:content (~sx-doc :path "/sx/(geography)"))
;; ---------------------------------------------------------------------------
;; Reactive Islands section (under Geography)
@@ -596,13 +596,13 @@
:path "/geography/reactive/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(geography.(reactive))" (~reactive-islands-index-content)))
:content (~sx-doc :path "/sx/(geography.(reactive))" (~reactive-islands-index-content)))
(defpage reactive-islands-page
:path "/geography/reactive/<slug>"
:auth :public
:layout :sx-docs
:content (~sx-doc :path (str "/(geography.(reactive." slug "))")
:content (~sx-doc :path (str "/sx/(geography.(reactive." slug "))")
(case slug
"demo" (~reactive-islands-demo-content)
"event-bridge" (~reactive-islands-event-bridge-content)
@@ -619,7 +619,7 @@
:path "/geography/marshes/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/(geography.(marshes))" (~reactive-islands-marshes-content)))
:content (~sx-doc :path "/sx/(geography.(marshes))" (~reactive-islands-marshes-content)))
;; ---------------------------------------------------------------------------
;; Bootstrapped page helpers demo
@@ -630,7 +630,7 @@
:auth :public
:layout :sx-docs
:data (page-helpers-demo-data)
:content (~sx-doc :path "/(language.(bootstrapper.page-helpers))"
:content (~sx-doc :path "/sx/(language.(bootstrapper.page-helpers))"
(~page-helpers-demo-content
:sf-categories sf-categories :sf-total sf-total :sf-ms sf-ms
:ref-sample ref-sample :ref-ms ref-ms
@@ -652,7 +652,7 @@
:auth :public
:layout :sx-docs
:data (run-modular-tests "all")
:content (~sx-doc :path "/(language.(test))"
:content (~sx-doc :path "/sx/(language.(test))"
(~testing-overview-content
:server-results server-results
:framework-source framework-source
@@ -676,7 +676,7 @@
"engine" (run-modular-tests "engine")
"orchestration" (run-modular-tests "orchestration")
:else (dict))
:content (~sx-doc :path (str "/(language.(test." slug "))")
:content (~sx-doc :path (str "/sx/(language.(test." slug "))")
(case slug
"eval" (~testing-spec-content
:spec-name "eval"

View File

@@ -214,8 +214,8 @@ async def eval_sx_url(raw_path: str) -> Any:
ctx = _get_request_context()
# Use raw URL path for nav matching — dots in hrefs match dots in URLs
path_str = raw_path
# Nav hrefs use /sx/ prefix — reconstruct the full path for nav matching
path_str = f"/sx{raw_path}" if raw_path != "/" else "/sx/"
# Check if expression head is a component (~name) — if so, skip
# async_eval and pass directly to _eval_slot. Components contain HTML
@@ -285,109 +285,109 @@ _REDIRECT_PATTERNS = [
# Reference API: /geography/hypermedia/reference/api/item/<id>
(re.compile(r"^/geography/hypermedia/reference/api/item/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference.(api.(item.{m.group(1)})))))"),
lambda m: f"/sx/(geography.(hypermedia.(reference.(api.(item.{m.group(1)})))))"),
# Reference API: /geography/hypermedia/reference/api/<name>
(re.compile(r"^/geography/hypermedia/reference/api/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference.(api.{m.group(1)}))))"),
lambda m: f"/sx/(geography.(hypermedia.(reference.(api.{m.group(1)}))))"),
# Example API: sub-paths with params
(re.compile(r"^/geography/hypermedia/examples/api/editrow/(.+?)/cancel/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.(editrow-cancel.{m.group(1)})))))"),
lambda m: f"/sx/(geography.(hypermedia.(example.(api.(editrow-cancel.{m.group(1)})))))"),
(re.compile(r"^/geography/hypermedia/examples/api/editrow/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.(editrow.{m.group(1)})))))"),
lambda m: f"/sx/(geography.(hypermedia.(example.(api.(editrow.{m.group(1)})))))"),
(re.compile(r"^/geography/hypermedia/examples/api/delete/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.(delete.{m.group(1)})))))"),
lambda m: f"/sx/(geography.(hypermedia.(example.(api.(delete.{m.group(1)})))))"),
(re.compile(r"^/geography/hypermedia/examples/api/tabs/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.(tabs.{m.group(1)})))))"),
lambda m: f"/sx/(geography.(hypermedia.(example.(api.(tabs.{m.group(1)})))))"),
# Example API: sub-paths collapsed to hyphens
(re.compile(r"^/geography/hypermedia/examples/api/edit/cancel/?$"),
"/(geography.(hypermedia.(example.(api.edit-cancel))))"),
"/sx/(geography.(hypermedia.(example.(api.edit-cancel))))"),
(re.compile(r"^/geography/hypermedia/examples/api/progress/start/?$"),
"/(geography.(hypermedia.(example.(api.progress-start))))"),
"/sx/(geography.(hypermedia.(example.(api.progress-start))))"),
(re.compile(r"^/geography/hypermedia/examples/api/progress/status/?$"),
"/(geography.(hypermedia.(example.(api.progress-status))))"),
"/sx/(geography.(hypermedia.(example.(api.progress-status))))"),
(re.compile(r"^/geography/hypermedia/examples/api/validate/submit/?$"),
"/(geography.(hypermedia.(example.(api.validate-submit))))"),
"/sx/(geography.(hypermedia.(example.(api.validate-submit))))"),
(re.compile(r"^/geography/hypermedia/examples/api/dialog/close/?$"),
"/(geography.(hypermedia.(example.(api.dialog-close))))"),
"/sx/(geography.(hypermedia.(example.(api.dialog-close))))"),
(re.compile(r"^/geography/hypermedia/examples/api/putpatch/edit-all/?$"),
"/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"),
"/sx/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"),
(re.compile(r"^/geography/hypermedia/examples/api/putpatch/cancel/?$"),
"/(geography.(hypermedia.(example.(api.putpatch-cancel))))"),
"/sx/(geography.(hypermedia.(example.(api.putpatch-cancel))))"),
# Example API: simple names
(re.compile(r"^/geography/hypermedia/examples/api/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.{m.group(1)}))))"),
lambda m: f"/sx/(geography.(hypermedia.(example.(api.{m.group(1)}))))"),
# Reactive API: sub-paths collapsed
(re.compile(r"^/geography/reactive/api/search/(.+?)/?$"),
lambda m: f"/(geography.(reactive.(api.search-{m.group(1)})))"),
lambda m: f"/sx/(geography.(reactive.(api.search-{m.group(1)})))"),
(re.compile(r"^/geography/reactive/api/(.+?)/?$"),
lambda m: f"/(geography.(reactive.(api.{m.group(1)})))"),
lambda m: f"/sx/(geography.(reactive.(api.{m.group(1)})))"),
# --- Page redirects ---
# More specific first
(re.compile(r"^/language/specs/explore/(.+?)/?$"),
lambda m: f"/(language.(spec.(explore.{m.group(1)})))"),
lambda m: f"/sx/(language.(spec.(explore.{m.group(1)})))"),
(re.compile(r"^/language/docs/(.+?)/?$"),
lambda m: f"/(language.(doc.{m.group(1)}))"),
lambda m: f"/sx/(language.(doc.{m.group(1)}))"),
(re.compile(r"^/language/docs/?$"),
"/(language.(doc))"),
"/sx/(language.(doc))"),
(re.compile(r"^/language/specs/(.+?)/?$"),
lambda m: f"/(language.(spec.{m.group(1)}))"),
lambda m: f"/sx/(language.(spec.{m.group(1)}))"),
(re.compile(r"^/language/bootstrappers/page-helpers/?$"),
"/(language.(bootstrapper.page-helpers))"),
"/sx/(language.(bootstrapper.page-helpers))"),
(re.compile(r"^/language/bootstrappers/(.+?)/?$"),
lambda m: f"/(language.(bootstrapper.{m.group(1)}))"),
lambda m: f"/sx/(language.(bootstrapper.{m.group(1)}))"),
(re.compile(r"^/language/testing/(.+?)/?$"),
lambda m: f"/(language.(test.{m.group(1)}))"),
lambda m: f"/sx/(language.(test.{m.group(1)}))"),
(re.compile(r"^/geography/hypermedia/reference/attributes/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference-detail.attributes.{m.group(1)})))"),
lambda m: f"/sx/(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)})))"),
lambda m: f"/sx/(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)})))"),
lambda m: f"/sx/(geography.(hypermedia.(reference-detail.events.{m.group(1)})))"),
(re.compile(r"^/geography/hypermedia/reference/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference.{m.group(1)})))"),
lambda m: f"/sx/(geography.(hypermedia.(reference.{m.group(1)})))"),
(re.compile(r"^/geography/hypermedia/examples/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.{m.group(1)})))"),
lambda m: f"/sx/(geography.(hypermedia.(example.{m.group(1)})))"),
(re.compile(r"^/geography/hypermedia/reference/?$"),
"/(geography.(hypermedia.(reference)))"),
"/sx/(geography.(hypermedia.(reference)))"),
(re.compile(r"^/geography/hypermedia/examples/?$"),
"/(geography.(hypermedia.(example)))"),
"/sx/(geography.(hypermedia.(example)))"),
(re.compile(r"^/geography/reactive/(.+?)/?$"),
lambda m: f"/(geography.(reactive.{m.group(1)}))"),
lambda m: f"/sx/(geography.(reactive.{m.group(1)}))"),
(re.compile(r"^/geography/isomorphism/(.+?)/?$"),
lambda m: f"/(geography.(isomorphism.{m.group(1)}))"),
lambda m: f"/sx/(geography.(isomorphism.{m.group(1)}))"),
(re.compile(r"^/geography/marshes/?$"),
"/(geography.(marshes))"),
"/sx/(geography.(marshes))"),
(re.compile(r"^/applications/cssx/(.+?)/?$"),
lambda m: f"/(applications.(cssx.{m.group(1)}))"),
lambda m: f"/sx/(applications.(cssx.{m.group(1)}))"),
(re.compile(r"^/applications/protocols/(.+?)/?$"),
lambda m: f"/(applications.(protocol.{m.group(1)}))"),
lambda m: f"/sx/(applications.(protocol.{m.group(1)}))"),
(re.compile(r"^/etc/essays/(.+?)/?$"),
lambda m: f"/(etc.(essay.{m.group(1)}))"),
lambda m: f"/sx/(etc.(essay.{m.group(1)}))"),
(re.compile(r"^/etc/philosophy/(.+?)/?$"),
lambda m: f"/(etc.(philosophy.{m.group(1)}))"),
lambda m: f"/sx/(etc.(philosophy.{m.group(1)}))"),
(re.compile(r"^/etc/plans/(.+?)/?$"),
lambda m: f"/(etc.(plan.{m.group(1)}))"),
lambda m: f"/sx/(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)"),
(re.compile(r"^/language/docs/?$"), "/sx/(language.(doc))"),
(re.compile(r"^/language/specs/?$"), "/sx/(language.(spec))"),
(re.compile(r"^/language/bootstrappers/?$"), "/sx/(language.(bootstrapper))"),
(re.compile(r"^/language/testing/?$"), "/sx/(language.(test))"),
(re.compile(r"^/language/?$"), "/sx/(language)"),
(re.compile(r"^/geography/hypermedia/?$"), "/sx/(geography.(hypermedia))"),
(re.compile(r"^/geography/reactive/?$"), "/sx/(geography.(reactive))"),
(re.compile(r"^/geography/isomorphism/?$"), "/sx/(geography.(isomorphism))"),
(re.compile(r"^/geography/?$"), "/sx/(geography)"),
(re.compile(r"^/applications/cssx/?$"), "/sx/(applications.(cssx))"),
(re.compile(r"^/applications/protocols/?$"), "/sx/(applications.(protocol))"),
(re.compile(r"^/applications/?$"), "/sx/(applications)"),
(re.compile(r"^/etc/essays/?$"), "/sx/(etc.(essay))"),
(re.compile(r"^/etc/philosophy/?$"), "/sx/(etc.(philosophy))"),
(re.compile(r"^/etc/plans/?$"), "/sx/(etc.(plan))"),
(re.compile(r"^/etc/?$"), "/sx/(etc)"),
]

View File

@@ -10,7 +10,7 @@
(defcomp ~ref-get-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-get-result"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -26,7 +26,7 @@
(defcomp ~ref-post-demo ()
(div :class "space-y-3"
(form
:sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
:sx-post "/sx/(geography.(hypermedia.(reference.(api.greet))))"
:sx-target "#ref-post-result"
:sx-swap "innerHTML"
:class "flex gap-2"
@@ -48,7 +48,7 @@
(div :class "flex items-center justify-between p-3 bg-stone-100 rounded"
(span :class "text-stone-700 text-sm" "Status: " (strong "draft"))
(button
:sx-put "/(geography.(hypermedia.(reference.(api.status))))"
:sx-put "/sx/(geography.(hypermedia.(reference.(api.status))))"
:sx-target "#ref-put-view"
:sx-swap "innerHTML"
:sx-vals "{\"status\": \"published\"}"
@@ -63,17 +63,17 @@
(div :class "space-y-2"
(div :id "ref-del-1" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item A")
(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.1)))))"
(button :sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.1)))))"
:sx-target "#ref-del-1" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-del-2" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item B")
(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.2)))))"
(button :sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.2)))))"
:sx-target "#ref-del-2" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-del-3" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item C")
(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.3)))))"
(button :sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.3)))))"
:sx-target "#ref-del-3" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove"))))
@@ -86,11 +86,11 @@
(div :class "p-3 bg-stone-100 rounded"
(span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light")))
(div :class "flex gap-2"
(button :sx-patch "/(geography.(hypermedia.(reference.(api.theme))))"
(button :sx-patch "/sx/(geography.(hypermedia.(reference.(api.theme))))"
:sx-vals "{\"theme\": \"dark\"}"
:sx-target "#ref-patch-val" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-800 text-white rounded text-sm" "Dark")
(button :sx-patch "/(geography.(hypermedia.(reference.(api.theme))))"
(button :sx-patch "/sx/(geography.(hypermedia.(reference.(api.theme))))"
:sx-vals "{\"theme\": \"light\"}"
:sx-target "#ref-patch-val" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-100 border border-stone-300 text-stone-700 rounded text-sm" "Light"))))
@@ -102,7 +102,7 @@
(defcomp ~ref-trigger-demo ()
(div :class "space-y-3"
(input :type "text" :name "q" :placeholder "Type to search..."
:sx-get "/(geography.(hypermedia.(reference.(api.trigger-search))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.trigger-search))))"
:sx-trigger "input changed delay:300ms"
:sx-target "#ref-trigger-result"
:sx-swap "innerHTML"
@@ -118,12 +118,12 @@
(defcomp ~ref-target-demo ()
(div :class "space-y-3"
(div :class "flex gap-2"
(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-target-a"
:sx-swap "innerHTML"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Update Box A")
(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-target-b"
:sx-swap "innerHTML"
:class "px-3 py-1 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
@@ -141,13 +141,13 @@
(defcomp ~ref-swap-demo ()
(div :class "space-y-3"
(div :class "flex gap-2 flex-wrap"
(button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-swap-list" :sx-swap "beforeend"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "beforeend")
(button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-swap-list" :sx-swap "afterbegin"
:class "px-3 py-1 bg-emerald-600 text-white rounded text-sm" "afterbegin")
(button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-swap-list" :sx-swap "innerHTML"
:class "px-3 py-1 bg-blue-600 text-white rounded text-sm" "innerHTML"))
(div :id "ref-swap-list"
@@ -160,7 +160,7 @@
(defcomp ~ref-oob-demo ()
(div :class "space-y-3"
(button :sx-get "/(geography.(hypermedia.(reference.(api.oob))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.oob))))"
:sx-target "#ref-oob-main"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
@@ -179,7 +179,7 @@
(defcomp ~ref-select-demo ()
(div :class "space-y-3"
(button :sx-get "/(geography.(hypermedia.(reference.(api.select-page))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.select-page))))"
:sx-target "#ref-select-result"
:sx-select "#the-content"
:sx-swap "innerHTML"
@@ -198,7 +198,7 @@
(div :id "ref-confirm-item"
:class "flex items-center justify-between p-3 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Important file.txt")
(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.confirm)))))"
(button :sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.confirm)))))"
:sx-target "#ref-confirm-item" :sx-swap "delete"
:sx-confirm "Are you sure you want to delete this file?"
:class "px-3 py-1 text-red-500 text-sm border border-red-200 rounded hover:bg-red-50"
@@ -211,14 +211,14 @@
(defcomp ~ref-pushurl-demo ()
(div :class "space-y-3"
(div :class "flex gap-2"
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
(a :href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-get "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"sx-get page")
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
:sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
(a :href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
:sx-get "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
:sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
@@ -233,7 +233,7 @@
(defcomp ~ref-sync-demo ()
(div :class "space-y-3"
(input :type "text" :name "q" :placeholder "Type quickly..."
:sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-trigger "input changed delay:100ms"
:sx-sync "replace"
:sx-target "#ref-sync-result"
@@ -251,7 +251,7 @@
(defcomp ~ref-encoding-demo ()
(div :class "space-y-3"
(form :sx-post "/(geography.(hypermedia.(reference.(api.upload-name))))"
(form :sx-post "/sx/(geography.(hypermedia.(reference.(api.upload-name))))"
:sx-encoding "multipart/form-data"
:sx-target "#ref-encoding-result"
:sx-swap "innerHTML"
@@ -271,7 +271,7 @@
(defcomp ~ref-headers-demo ()
(div :class "space-y-3"
(button :sx-get "/(geography.(hypermedia.(reference.(api.echo-headers))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.echo-headers))))"
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
:sx-target "#ref-headers-result"
:sx-swap "innerHTML"
@@ -295,7 +295,7 @@
(option :value "all" "All")
(option :value "books" "Books")
(option :value "tools" "Tools")))
(button :sx-get "/(geography.(hypermedia.(reference.(api.echo-vals))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.echo-vals))))"
:sx-include "#ref-inc-cat"
:sx-target "#ref-include-result"
:sx-swap "innerHTML"
@@ -311,7 +311,7 @@
(defcomp ~ref-vals-demo ()
(div :class "space-y-3"
(button :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"
(button :sx-post "/sx/(geography.(hypermedia.(reference.(api.echo-vals))))"
:sx-vals "{\"source\": \"demo\", \"page\": \"3\"}"
:sx-target "#ref-vals-result"
:sx-swap "innerHTML"
@@ -327,8 +327,8 @@
(defcomp ~ref-media-demo ()
(div :class "space-y-3"
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
(a :href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-get "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:sx-media "(min-width: 768px)"
@@ -346,13 +346,13 @@
(div :class "grid grid-cols-2 gap-3"
(div :class "p-3 border border-stone-200 rounded"
(p :class "text-xs text-stone-400 mb-2" "sx enabled")
(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-dis-a" :sx-swap "innerHTML"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "Load")
(div :id "ref-dis-a" :class "mt-2 text-sm text-stone-500" "—"))
(div :sx-disable "true" :class "p-3 border border-stone-200 rounded"
(p :class "text-xs text-stone-400 mb-2" "sx disabled")
(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-dis-b" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-400 text-white rounded text-sm" "Load")
(div :id "ref-dis-b" :class "mt-2 text-sm text-stone-500"
@@ -378,7 +378,7 @@
(defcomp ~ref-retry-demo ()
(div :class "space-y-3"
(button :sx-get "/(geography.(hypermedia.(reference.(api.flaky))))"
(button :sx-get "/sx/(geography.(hypermedia.(reference.(api.flaky))))"
:sx-target "#ref-retry-result"
:sx-swap "innerHTML"
:sx-retry "true"
@@ -414,13 +414,13 @@
(defcomp ~ref-boost-demo ()
(div :class "space-y-3"
(nav :sx-boost "true" :class "flex gap-3"
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
(a :href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-get")
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
(a :href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
:class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-post")
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-target)))"
(a :href "/sx/(geography.(hypermedia.(reference-detail.attributes.sx-target)))"
:class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-target"))
(p :class "text-xs text-stone-400"
@@ -434,7 +434,7 @@
(defcomp ~ref-preload-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-preload-result"
:sx-swap "innerHTML"
:sx-preload "mouseover"
@@ -452,7 +452,7 @@
(div :class "space-y-3"
(div :class "flex gap-2 items-center"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-preserve-container"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -473,7 +473,7 @@
(div :class "space-y-3"
(div :class "flex gap-3 items-center"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-target "#ref-indicator-result"
:sx-swap "innerHTML"
:sx-indicator "#ref-spinner"
@@ -495,7 +495,7 @@
(defcomp ~ref-validate-demo ()
(div :class "space-y-3"
(form
:sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
:sx-post "/sx/(geography.(hypermedia.(reference.(api.greet))))"
:sx-target "#ref-validate-result"
:sx-swap "innerHTML"
:sx-validate "true"
@@ -517,7 +517,7 @@
(defcomp ~ref-ignore-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-ignore-container"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -539,14 +539,14 @@
(div :id "ref-opt-item-1"
:class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Optimistic item A")
(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.opt1)))))"
(button :sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.opt1)))))"
:sx-target "#ref-opt-item-1" :sx-swap "delete"
:sx-optimistic "remove"
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-opt-item-2"
:class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Optimistic item B")
(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.opt2)))))"
(button :sx-delete "/sx/(geography.(hypermedia.(reference.(api.(item.opt2)))))"
:sx-target "#ref-opt-item-2" :sx-swap "delete"
:sx-optimistic "remove"
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
@@ -560,7 +560,7 @@
(defcomp ~ref-replace-url-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-replurl-result"
:sx-swap "innerHTML"
:sx-replace-url "true"
@@ -578,7 +578,7 @@
(div :class "space-y-3"
(div :class "flex gap-3 items-center"
(button :id "ref-diselt-btn"
:sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-target "#ref-diselt-result"
:sx-swap "innerHTML"
:sx-disabled-elt "#ref-diselt-btn"
@@ -597,7 +597,7 @@
(defcomp ~ref-prompt-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:sx-target "#ref-prompt-result"
:sx-swap "innerHTML"
:sx-prompt "Enter your name:"
@@ -614,7 +614,7 @@
(defcomp ~ref-params-demo ()
(div :class "space-y-3"
(form
:sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:sx-post "/sx/(geography.(hypermedia.(reference.(api.echo-vals))))"
:sx-target "#ref-params-result"
:sx-swap "innerHTML"
:sx-params "name"
@@ -636,7 +636,7 @@
(defcomp ~ref-sse-demo ()
(div :class "space-y-3"
(div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"
(div :sx-sse "/sx/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time"
:sx-swap "innerHTML"
(div :id "ref-sse-result"
@@ -656,7 +656,7 @@
(defcomp ~ref-header-prompt-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:sx-target "#ref-hdr-prompt-result"
:sx-swap "innerHTML"
:sx-prompt "Enter your name:"
@@ -673,7 +673,7 @@
(defcomp ~ref-header-trigger-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.trigger-event))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.trigger-event))))"
:sx-target "#ref-hdr-trigger-result"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -690,7 +690,7 @@
(defcomp ~ref-header-retarget-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.retarget))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.retarget))))"
:sx-target "#ref-hdr-retarget-main"
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -717,7 +717,7 @@
(input :id "ref-evt-br-input" :type "text" :placeholder "Type something first..."
:class "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(button
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-evt-br-result"
:sx-swap "innerHTML"
:sx-on:sx:beforeRequest "if (!document.getElementById('ref-evt-br-input').value) { event.preventDefault(); document.getElementById('ref-evt-br-result').textContent = 'Cancelled — input is empty!'; }"
@@ -734,7 +734,7 @@
(defcomp ~ref-event-after-request-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-evt-ar-result"
:sx-swap "innerHTML"
:sx-on:sx:afterRequest "document.getElementById('ref-evt-ar-log').textContent = 'Response status: ' + (event.detail ? event.detail.status : '?')"
@@ -754,7 +754,7 @@
(defcomp ~ref-event-after-swap-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-evt-as-list"
:sx-swap "beforeend"
:sx-on:sx:afterSwap "var items = document.querySelectorAll('#ref-evt-as-list > div'); if (items.length) items[items.length-1].scrollIntoView({behavior:'smooth'}); document.getElementById('ref-evt-as-count').textContent = items.length + ' items'"
@@ -774,7 +774,7 @@
(defcomp ~ref-event-response-error-demo ()
(div :class "space-y-3"
(button
:sx-get "/(geography.(hypermedia.(reference.(api.error-500))))"
:sx-get "/sx/(geography.(hypermedia.(reference.(api.error-500))))"
:sx-target "#ref-evt-err-result"
:sx-swap "innerHTML"
:sx-on:sx:responseError "var s=document.getElementById('ref-evt-err-status'); s.style.display='block'; s.textContent='Error ' + (event.detail ? event.detail.status || '?' : '?') + ' received'"
@@ -796,7 +796,7 @@
(defcomp ~ref-event-validation-failed-demo ()
(div :class "space-y-3"
(form
:sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
:sx-post "/sx/(geography.(hypermedia.(reference.(api.greet))))"
:sx-target "#ref-evt-vf-result"
:sx-swap "innerHTML"
:sx-validate "true"
@@ -847,13 +847,13 @@
"Open DevTools console, then navigate to a pure page (no :data expression). "
"You'll see \"sx:route client /path\" in the console — no network request is made.")
(div :class "flex gap-2 flex-wrap"
(a :href "/(etc.(essay))"
(a :href "/sx/(etc.(essay))"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Essays")
(a :href "/(etc.(plan))"
(a :href "/sx/(etc.(plan))"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Plans")
(a :href "/(applications.(protocol))"
(a :href "/sx/(applications.(protocol))"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Protocols"))
(p :class "text-xs text-stone-400"
@@ -866,7 +866,7 @@
(defcomp ~ref-event-sse-open-demo ()
(div :class "space-y-3"
(div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"
(div :sx-sse "/sx/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time"
:sx-swap "innerHTML"
:sx-on:sx:sseOpen "document.getElementById('ref-evt-sseopen-status').textContent = 'Connected'; document.getElementById('ref-evt-sseopen-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-700'"
@@ -884,7 +884,7 @@
(defcomp ~ref-event-sse-message-demo ()
(div :class "space-y-3"
(div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"
(div :sx-sse "/sx/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time"
:sx-swap "innerHTML"
:sx-on:sx:sseMessage "var c = parseInt(document.getElementById('ref-evt-ssemsg-count').dataset.count || '0') + 1; document.getElementById('ref-evt-ssemsg-count').dataset.count = c; document.getElementById('ref-evt-ssemsg-count').textContent = c + ' messages received'"
@@ -902,7 +902,7 @@
(defcomp ~ref-event-sse-error-demo ()
(div :class "space-y-3"
(div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"
(div :sx-sse "/sx/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time"
:sx-swap "innerHTML"
:sx-on:sx:sseError "document.getElementById('ref-evt-sseerr-status').textContent = 'Disconnected'; document.getElementById('ref-evt-sseerr-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-red-100 text-red-700'"