Refactor page components (docs, examples, specs, reference, layouts) and adapters (adapter-sx, boot-helpers, orchestration) across sx/ and web/ directories. Add Playwright SPA navigation tests. Rebuild WASM kernel with updated bytecode. Add OCaml primitives for request handling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1108 lines
48 KiB
Plaintext
1108 lines
48 KiB
Plaintext
(defcomp
|
|
~specs/architecture-content
|
|
()
|
|
(~docs/page
|
|
:title "Spec Architecture"
|
|
(div
|
|
:class "space-y-8"
|
|
(div
|
|
:class "space-y-4"
|
|
(p
|
|
:class "text-lg text-stone-600"
|
|
"SX is defined in SX. The canonical specification is a set of s-expression files that are both documentation and executable definition. Bootstrap compilers read these files to generate native implementations in JavaScript, Python, Rust, or any other target.")
|
|
(p
|
|
:class "text-stone-600"
|
|
"The spec is organized into six sections: "
|
|
(strong "Core")
|
|
" (the language itself), "
|
|
(strong "Adapters")
|
|
" (rendering backends), "
|
|
(strong "Browser")
|
|
" (client-side runtime), "
|
|
(strong "Reactive")
|
|
" (signal system), "
|
|
(strong "Host Interface")
|
|
" (platform contract), and "
|
|
(strong "Extensions")
|
|
" (optional add-ons)."))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Core")
|
|
(p
|
|
:class "text-stone-600"
|
|
"The core is platform-independent. It defines how SX source is parsed, how expressions are evaluated, what primitives exist, and what shared rendering definitions all adapters use. These four files are the language.")
|
|
(div
|
|
:class "overflow-x-auto rounded border border-stone-200"
|
|
(table
|
|
:class "w-full text-left text-sm"
|
|
(thead
|
|
(tr
|
|
:class "border-b border-stone-200 bg-stone-100"
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Role")))
|
|
(tbody
|
|
(tr
|
|
:class "border-b border-stone-100"
|
|
(td
|
|
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
|
(a
|
|
:href "/sx/(language.(spec.parser))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.parser))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.evaluator))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.evaluator))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.primitives))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.primitives))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.special-forms))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.special-forms))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.renderer))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.renderer))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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"))))))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Adapters")
|
|
(p
|
|
:class "text-stone-600"
|
|
"Adapters are selectable rendering backends. Each one takes the same evaluated expression tree and produces output for a specific environment. You only need the adapters relevant to your target.")
|
|
(div
|
|
:class "overflow-x-auto rounded border border-stone-200"
|
|
(table
|
|
:class "w-full text-left text-sm"
|
|
(thead
|
|
(tr
|
|
:class "border-b border-stone-200 bg-stone-100"
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Output")
|
|
(th
|
|
:class "px-3 py-2 font-medium text-stone-600"
|
|
"Environment")))
|
|
(tbody
|
|
(tr
|
|
:class "border-b border-stone-100"
|
|
(td
|
|
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
|
(a
|
|
:href "/sx/(language.(spec.adapter-dom))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.adapter-dom))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.adapter-html))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.adapter-html))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.adapter-sx))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.adapter-sx))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.adapter-async))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.adapter-async))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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")
|
|
(td :class "px-3 py-2 text-stone-500" "Server (async)"))))))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Browser Runtime")
|
|
(p
|
|
:class "text-stone-600"
|
|
"The browser runtime handles the full client-side lifecycle: parsing triggers, making HTTP requests, swapping content, managing history, and booting the page. Split into pure logic (engine), browser wiring (orchestration), startup (boot), and URL matching (router).")
|
|
(div
|
|
:class "overflow-x-auto rounded border border-stone-200"
|
|
(table
|
|
:class "w-full text-left text-sm"
|
|
(thead
|
|
(tr
|
|
:class "border-b border-stone-200 bg-stone-100"
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Role")))
|
|
(tbody
|
|
(tr
|
|
:class "border-b border-stone-100"
|
|
(td
|
|
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
|
(a
|
|
:href "/sx/(language.(spec.engine))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.engine))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.orchestration))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.orchestration))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.boot))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.boot))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.router))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.router))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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"))))))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Reactive")
|
|
(p
|
|
:class "text-stone-600"
|
|
"Fine-grained reactive primitives for client-side islands. Signals notify subscribers on change, computed values derive lazily, effects run side-effects with cleanup, and batch coalesces updates.")
|
|
(div
|
|
:class "overflow-x-auto rounded border border-stone-200"
|
|
(table
|
|
:class "w-full text-left text-sm"
|
|
(thead
|
|
(tr
|
|
:class "border-b border-stone-200 bg-stone-100"
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Role")))
|
|
(tbody
|
|
(tr
|
|
:class "border-b border-stone-100"
|
|
(td
|
|
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
|
(a
|
|
:href "/sx/(language.(spec.signals))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.signals))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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"))))))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Host Interface")
|
|
(p
|
|
:class "text-stone-600"
|
|
"The contract between SX and its host environment. Boundary declares what the host must provide. Forms define server-side application constructs. Page helpers offer pure data transformations for rendering.")
|
|
(div
|
|
:class "overflow-x-auto rounded border border-stone-200"
|
|
(table
|
|
:class "w-full text-left text-sm"
|
|
(thead
|
|
(tr
|
|
:class "border-b border-stone-200 bg-stone-100"
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Role")))
|
|
(tbody
|
|
(tr
|
|
:class "border-b border-stone-100"
|
|
(td
|
|
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
|
(a
|
|
:href "/sx/(language.(spec.boundary))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.boundary))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.forms))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.forms))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.page-helpers))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.page-helpers))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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"))))))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Dependency graph")
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl"
|
|
(pre
|
|
:class "text-sm leading-relaxed whitespace-pre-wrap break-words font-mono text-stone-700"
|
|
";; Core\nparser.sx (standalone — no dependencies)\nprimitives.sx (standalone — declarative registry)\nspecial-forms.sx (standalone — declarative registry)\neval.sx depends on: parser, primitives, special-forms\nrender.sx (standalone — shared registries)\n\n;; Adapters\nadapter-dom.sx depends on: render, eval\nadapter-html.sx depends on: render, eval\nadapter-sx.sx depends on: render, eval\nadapter-async.sx depends on: adapter-html, adapter-sx, eval\n\n;; Browser Runtime\nengine.sx depends on: eval, adapter-dom\norchestration.sx depends on: engine, adapter-dom\nboot.sx depends on: orchestration, adapter-dom, render\nrouter.sx (standalone — pure string/list ops)\n\n;; Reactive\nsignals.sx depends on: eval\n\n;; Host Interface\nboundary.sx (standalone — declarative contract)\nforms.sx depends on: eval\npage-helpers.sx (standalone — declarative registry)\n\n;; Extensions (optional)\ncontinuations.sx depends on: eval\ncallcc.sx depends on: eval\ntypes.sx depends on: eval, primitives\ndeps.sx depends on: eval")))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Extensions")
|
|
(p
|
|
:class "text-stone-600"
|
|
"Optional bolt-on specifications that extend the core language. Bootstrappers include them only when the target requests them. Code that doesn't use extensions pays zero cost.")
|
|
(div
|
|
:class "overflow-x-auto rounded border border-stone-200"
|
|
(table
|
|
:class "w-full text-left text-sm"
|
|
(thead
|
|
(tr
|
|
:class "border-b border-stone-200 bg-stone-100"
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Role")))
|
|
(tbody
|
|
(tr
|
|
:class "border-b border-stone-100"
|
|
(td
|
|
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
|
(a
|
|
:href "/sx/(language.(spec.continuations))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.continuations))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.callcc))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.callcc))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.types))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.types))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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 "/sx/(language.(spec.deps))"
|
|
:class "hover:underline"
|
|
:sx-get "/sx/(language.(spec.deps))"
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
: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"))))))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Self-hosting")
|
|
(p
|
|
:class "text-stone-600"
|
|
"Every spec file is written in the same restricted subset of SX that the evaluator itself defines. A bootstrap compiler for a new target only needs to understand this subset — roughly 20 special forms and 80 primitives — to generate a fully native implementation. The spec files are the single source of truth; implementations are derived artifacts.")
|
|
(p
|
|
:class "text-stone-600"
|
|
"This is not a theoretical exercise. The JavaScript implementation ("
|
|
(code :class "text-violet-700 text-sm" "sx.js")
|
|
") and the Python implementation ("
|
|
(code :class "text-violet-700 text-sm" "shared/sx/")
|
|
") are both generated from these spec files via "
|
|
(code :class "text-violet-700 text-sm" "bootstrap_js.py")
|
|
" and its Python counterpart.")))))
|
|
|
|
(defcomp
|
|
~specs/overview-content
|
|
(&key (spec-title :as string) (spec-files :as list))
|
|
(~docs/page
|
|
:title (or spec-title "Specs")
|
|
(p
|
|
:class "text-stone-600 mb-6"
|
|
(case
|
|
spec-title
|
|
"Core Language"
|
|
"The core specification defines the language itself — parsing, evaluation, primitives, special forms, and shared rendering definitions. These five files are platform-independent and sufficient to implement SX on any target."
|
|
"Adapters"
|
|
"Adapters connect the core language to specific environments. Each adapter takes evaluated expression trees and produces output for its target — DOM nodes, HTML strings, SX wire format, or async-aware server rendering."
|
|
"Browser Runtime"
|
|
"The browser runtime handles the client-side lifecycle: parsing triggers, making requests, swapping content, managing history, and booting the page. Split into pure logic (engine), browser wiring (orchestration), startup (boot), and URL matching (router)."
|
|
"Reactive System"
|
|
"Fine-grained reactive primitives for client-side islands. Signals, computed values, effects, and batching — the reactive graph that powers L2-L3 interactivity without a virtual DOM."
|
|
"Host Interface"
|
|
"The contract between SX and its host environment. Boundary declarations specify what the host must provide, forms define server-side application constructs, and page helpers offer pure data transformations."
|
|
"Extensions"
|
|
"Optional bolt-on specifications that extend the core language. Bootstrappers include them only when the target requests them. Code that doesn't use extensions pays zero cost."
|
|
:else ""))
|
|
(div
|
|
:class "space-y-8"
|
|
(map
|
|
(fn
|
|
(spec)
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2
|
|
:class "text-2xl font-semibold text-stone-800"
|
|
(a
|
|
:href (get spec "href")
|
|
:sx-get (get spec "href")
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
:sx-swap "outerHTML"
|
|
:sx-push-url "true"
|
|
:class "text-violet-700 hover:text-violet-900 underline"
|
|
(get spec "title")))
|
|
(span
|
|
:class "text-sm text-stone-400 font-mono"
|
|
(get spec "filename")))
|
|
(p :class "text-stone-600" (get spec "desc"))
|
|
(when
|
|
(get spec "prose")
|
|
(p
|
|
:class "text-sm text-stone-500 leading-relaxed"
|
|
(get spec "prose")))
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-72 overflow-y-auto"
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight (get spec "source") "sx"))))))
|
|
spec-files))))
|
|
|
|
(defcomp
|
|
~specs/detail-content
|
|
(&key
|
|
(spec-title :as string)
|
|
(spec-desc :as string)
|
|
(spec-filename :as string)
|
|
(spec-source :as string)
|
|
(spec-prose :as string?))
|
|
(~docs/page
|
|
:title spec-title
|
|
(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
|
|
"/sx/(language.(spec.(explore."
|
|
(replace spec-filename ".sx" "")
|
|
")))")
|
|
:sx-get (str
|
|
"/sx/(language.(spec.(explore."
|
|
(replace spec-filename ".sx" "")
|
|
")))")
|
|
:sx-target "#sx-content"
|
|
:sx-select "#sx-content"
|
|
:sx-swap "outerHTML"
|
|
:sx-push-url "true"
|
|
:class "text-sm text-violet-600 hover:text-violet-800 font-medium whitespace-nowrap"
|
|
"Explore"))
|
|
(when
|
|
spec-prose
|
|
(div
|
|
:class "mb-6 space-y-3"
|
|
(p :class "text-stone-600 leading-relaxed" spec-prose)
|
|
(p
|
|
:class "text-xs text-stone-400 italic"
|
|
"The s-expression source below is the canonical specification. "
|
|
"The English description above is a summary.")))
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl"
|
|
(pre
|
|
:class "text-sm leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight spec-source "sx"))))))
|
|
|
|
(defcomp
|
|
~specs/bootstrappers-index-content
|
|
()
|
|
(~docs/page
|
|
:title "Bootstrappers"
|
|
(div
|
|
:class "space-y-6"
|
|
(p
|
|
:class "text-lg text-stone-600"
|
|
"A bootstrapper reads the canonical "
|
|
(code :class "text-violet-700 text-sm" ".sx")
|
|
" specification files and emits a native implementation for a specific target. "
|
|
"The spec files are the single source of truth — bootstrappers are the bridge from specification to runnable code.")
|
|
(p
|
|
:class "text-stone-600"
|
|
"Each bootstrapper is a compiler that understands the restricted SX subset used in the spec files "
|
|
"(roughly 20 special forms and 80 primitives) and translates it into idiomatic target code. "
|
|
"Platform-specific operations (DOM access, HTTP, timers) are emitted as native implementations "
|
|
"rather than translated from SX.")
|
|
(div
|
|
:class "overflow-x-auto rounded border border-stone-200"
|
|
(table
|
|
:class "w-full text-left text-sm"
|
|
(thead
|
|
(tr
|
|
:class "border-b border-stone-200 bg-stone-100"
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Target")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Bootstrapper")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Output")
|
|
(th :class "px-3 py-2 font-medium text-stone-600" "Status")))
|
|
(tbody
|
|
(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 "/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 "/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 "/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"))
|
|
(tr
|
|
:class "border-b border-stone-100"
|
|
(td :class "px-3 py-2 text-stone-700" "Rust")
|
|
(td
|
|
:class "px-3 py-2 font-mono text-sm text-stone-400"
|
|
"bootstrap_rs.py")
|
|
(td
|
|
:class "px-3 py-2 font-mono text-sm text-stone-400"
|
|
"sx-native")
|
|
(td :class "px-3 py-2 text-stone-400" "Planned"))))))))
|
|
|
|
(defcomp
|
|
~specs/bootstrapper-js-content
|
|
(&key bootstrapper-source bootstrapped-output)
|
|
(~docs/page
|
|
:title "JavaScript Bootstrapper"
|
|
(div
|
|
:class "space-y-8"
|
|
(div
|
|
:class "space-y-3"
|
|
(p
|
|
:class "text-stone-600"
|
|
"This page reads the canonical "
|
|
(code :class "text-violet-700 text-sm" ".sx")
|
|
" spec files, runs the Python bootstrapper, and displays both the compiler source and its generated JavaScript output. "
|
|
"The generated code below is live — it was produced by the bootstrapper at page load time, not served from a static file.")
|
|
(p
|
|
:class "text-xs text-stone-400 italic"
|
|
"The sx-browser.js powering this page IS the bootstrapped output. "
|
|
"This page re-runs the bootstrapper to display the source and result."))
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Bootstrapper")
|
|
(span :class "text-sm text-stone-400 font-mono" "bootstrap_js.py"))
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"The compiler reads "
|
|
(code :class "text-violet-700 text-sm" ".sx")
|
|
" spec files (parser, eval, primitives, render, adapters, engine, orchestration, boot, cssx) "
|
|
"and emits a standalone JavaScript file. Platform bridge functions (DOM operations, fetch, timers) "
|
|
"are emitted as native JS implementations.")
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200"
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight bootstrapper-source "python")))))
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2
|
|
:class "text-2xl font-semibold text-stone-800"
|
|
"Generated Output")
|
|
(span :class "text-sm text-stone-400 font-mono" "sx-browser.js"))
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"The JavaScript below was generated by running the bootstrapper against the current spec files. "
|
|
"It is a complete, self-contained SX runtime — parser, evaluator, DOM adapter, engine, and CSS system.")
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-violet-300"
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight bootstrapped-output "javascript"))))))))
|
|
|
|
(defcomp
|
|
~specs/bootstrapper-self-hosting-content
|
|
(&key
|
|
(py-sx-source :as string)
|
|
(g0-output :as string)
|
|
(g1-output :as string)
|
|
(defines-matched :as number)
|
|
(defines-total :as number)
|
|
(g0-lines :as number)
|
|
(g0-bytes :as number)
|
|
(verification-status :as string))
|
|
(~docs/page
|
|
:title "Self-Hosting Bootstrapper (py.sx)"
|
|
(div
|
|
:class "space-y-8"
|
|
(div
|
|
:class "space-y-3"
|
|
(p
|
|
:class "text-stone-600"
|
|
(code :class "text-violet-700 text-sm" "py.sx")
|
|
" is an SX-to-Python translator written in SX. "
|
|
"This page runs it live: loads py.sx into the evaluator, translates each spec file, "
|
|
"and diffs the result against "
|
|
(code :class "text-violet-700 text-sm" "bootstrap_py.py")
|
|
".")
|
|
(div
|
|
:class "rounded-lg p-4"
|
|
:class (if
|
|
(= verification-status "identical")
|
|
"bg-green-50 border border-green-200"
|
|
"bg-red-50 border border-red-200")
|
|
(div
|
|
:class "flex items-center gap-3"
|
|
(span
|
|
:class "inline-flex items-center rounded-full px-3 py-1 text-sm font-semibold"
|
|
:class (if
|
|
(= verification-status "identical")
|
|
"bg-green-100 text-green-800"
|
|
"bg-red-100 text-red-800")
|
|
(if (= verification-status "identical") "G0 == G1" "MISMATCH"))
|
|
(p
|
|
:class "text-sm"
|
|
:class (if
|
|
(= verification-status "identical")
|
|
"text-green-700"
|
|
"text-red-700")
|
|
defines-matched
|
|
"/"
|
|
defines-total
|
|
" defines match. "
|
|
g0-lines
|
|
" lines, "
|
|
g0-bytes
|
|
" bytes."))))
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "py.sx Source")
|
|
(span
|
|
:class "text-sm text-stone-400 font-mono"
|
|
"shared/sx/ref/py.sx"))
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"The SX-to-Python translator — 55 "
|
|
(code "define")
|
|
" forms. "
|
|
"Name mangling (200+ RENAMES), expression emission, statement emission, "
|
|
"cell variable detection for "
|
|
(code "set!")
|
|
" across lambda boundaries.")
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200"
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight py-sx-source "lisp")))))
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "G0 Output")
|
|
(span
|
|
:class "text-sm text-stone-400 font-mono"
|
|
"bootstrap_py.py → sx_ref.py"))
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"Generated by the hand-written Python bootstrapper.")
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200"
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight g0-output "python")))))
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "G1 Output")
|
|
(span
|
|
:class "text-sm text-stone-400 font-mono"
|
|
"py.sx → sx_ref.py"))
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"Generated by py.sx running on the Python evaluator. "
|
|
(if
|
|
(= verification-status "identical")
|
|
(strong "Byte-for-byte identical to G0.")
|
|
"Differs from G0 — see mismatch details."))
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border"
|
|
:class (if
|
|
(= verification-status "identical")
|
|
"border-green-200"
|
|
"border-red-200")
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight g1-output "python"))))))))
|
|
|
|
(defcomp
|
|
~specs/bootstrapper-self-hosting-js-content
|
|
(&key
|
|
js-sx-source
|
|
defines-matched
|
|
defines-total
|
|
js-sx-lines
|
|
verification-status)
|
|
(~docs/page
|
|
:title "Self-Hosting Bootstrapper (js.sx)"
|
|
(div
|
|
:class "space-y-8"
|
|
(div
|
|
:class "space-y-3"
|
|
(p
|
|
:class "text-stone-600"
|
|
(code :class "text-violet-700 text-sm" "js.sx")
|
|
" is an SX-to-JavaScript translator written in SX. "
|
|
"This page runs it live: loads js.sx into the evaluator, translates every spec file, "
|
|
"and verifies each define matches "
|
|
(code :class "text-violet-700 text-sm" "bootstrap_js.py")
|
|
"'s JSEmitter.")
|
|
(div
|
|
:class "rounded-lg p-4"
|
|
:class (if
|
|
(= verification-status "identical")
|
|
"bg-green-50 border border-green-200"
|
|
"bg-red-50 border border-red-200")
|
|
(div
|
|
:class "flex items-center gap-3"
|
|
(span
|
|
:class "inline-flex items-center rounded-full px-3 py-1 text-sm font-semibold"
|
|
:class (if
|
|
(= verification-status "identical")
|
|
"bg-green-100 text-green-800"
|
|
"bg-red-100 text-red-800")
|
|
(if (= verification-status "identical") "G0 == G1" "MISMATCH"))
|
|
(p
|
|
:class "text-sm"
|
|
:class (if
|
|
(= verification-status "identical")
|
|
"text-green-700"
|
|
"text-red-700")
|
|
defines-matched
|
|
"/"
|
|
defines-total
|
|
" defines match across all spec files. "
|
|
js-sx-lines
|
|
" lines of SX."))))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "G0 Bug Discovery")
|
|
(div
|
|
:class "rounded-lg bg-amber-50 border border-amber-200 p-4"
|
|
(div
|
|
:class "flex items-start gap-3"
|
|
(span
|
|
:class "inline-flex items-center rounded-full bg-amber-100 px-3 py-1 text-sm font-semibold text-amber-800"
|
|
"Fixed")
|
|
(div
|
|
:class "text-sm text-amber-700 space-y-2"
|
|
(p
|
|
"Building js.sx revealed a bug in "
|
|
(code "bootstrap_js.py")
|
|
"'s "
|
|
(code "_emit_define")
|
|
" method. The Python code:")
|
|
(pre
|
|
:class "bg-amber-100 rounded p-2 text-xs font-mono"
|
|
"val = self.emit(fn_expr) if fn_expr else \"NIL\"")
|
|
(p
|
|
"Python's "
|
|
(code "if fn_expr")
|
|
" treats "
|
|
(code "0")
|
|
", "
|
|
(code "False")
|
|
", and "
|
|
(code "\"\"")
|
|
" as falsy. So "
|
|
(code "(define *batch-depth* 0)")
|
|
" emitted "
|
|
(code "var _batchDepth = NIL")
|
|
" instead of "
|
|
(code "var _batchDepth = 0")
|
|
". Similarly, "
|
|
(code "(define _css-hash \"\")")
|
|
" emitted "
|
|
(code "var _cssHash = NIL")
|
|
" instead of "
|
|
(code "var _cssHash = \"\"")
|
|
".")
|
|
(p
|
|
"Fix: "
|
|
(code "if fn_expr is not None")
|
|
" — explicit None check. "
|
|
"js.sx never had this bug because SX's "
|
|
(code "nil?")
|
|
" only matches "
|
|
(code "nil")
|
|
", not "
|
|
(code "0")
|
|
" or "
|
|
(code "false")
|
|
". "
|
|
"The self-hosting bootstrapper caught a host language bug.")))))
|
|
(div
|
|
:class "space-y-3"
|
|
(h2
|
|
:class "text-2xl font-semibold text-stone-800"
|
|
"Translation Differences from py.sx")
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"Both py.sx and js.sx translate the same SX ASTs, but target languages differ:")
|
|
(div
|
|
:class "overflow-x-auto rounded border border-stone-200"
|
|
(table
|
|
:class "w-full text-sm"
|
|
(thead
|
|
:class "bg-stone-50"
|
|
(tr
|
|
(th
|
|
:class "px-4 py-2 text-left font-semibold text-stone-700"
|
|
"Feature")
|
|
(th
|
|
:class "px-4 py-2 text-left font-semibold text-stone-700"
|
|
"py.sx → Python")
|
|
(th
|
|
:class "px-4 py-2 text-left font-semibold text-stone-700"
|
|
"js.sx → JavaScript")))
|
|
(tbody
|
|
(tr
|
|
:class "border-t border-stone-100"
|
|
(td :class "px-4 py-2 text-stone-600" "Name mangling")
|
|
(td
|
|
:class "px-4 py-2 font-mono text-xs"
|
|
"eval-expr → eval_expr")
|
|
(td
|
|
:class "px-4 py-2 font-mono text-xs"
|
|
"eval-expr → evalExpr"))
|
|
(tr
|
|
:class "border-t border-stone-100"
|
|
(td :class "px-4 py-2 text-stone-600" "Declarations")
|
|
(td :class "px-4 py-2 font-mono text-xs" "name = value")
|
|
(td :class "px-4 py-2 font-mono text-xs" "var name = value;"))
|
|
(tr
|
|
:class "border-t border-stone-100"
|
|
(td :class "px-4 py-2 text-stone-600" "Functions")
|
|
(td :class "px-4 py-2 font-mono text-xs" "lambda x: body")
|
|
(td
|
|
:class "px-4 py-2 font-mono text-xs"
|
|
"function(x) { return body; }"))
|
|
(tr
|
|
:class "border-t border-stone-100"
|
|
(td :class "px-4 py-2 text-stone-600" "set! (mutation)")
|
|
(td
|
|
:class "px-4 py-2 font-mono text-xs"
|
|
"_cells dict (closure hack)")
|
|
(td
|
|
:class "px-4 py-2 font-mono text-xs"
|
|
"Direct assignment (JS captures by ref)"))
|
|
(tr
|
|
:class "border-t border-stone-100"
|
|
(td :class "px-4 py-2 text-stone-600" "Tail recursion")
|
|
(td :class "px-4 py-2 font-mono text-xs" "—")
|
|
(td
|
|
:class "px-4 py-2 font-mono text-xs"
|
|
"while(true) { continue; }"))
|
|
(tr
|
|
:class "border-t border-stone-100"
|
|
(td :class "px-4 py-2 text-stone-600" "let binding")
|
|
(td
|
|
:class "px-4 py-2 font-mono text-xs"
|
|
"(lambda x: body)(val)")
|
|
(td
|
|
:class "px-4 py-2 font-mono text-xs"
|
|
"(function() { var x = val; return body; })()"))
|
|
(tr
|
|
:class "border-t border-stone-100"
|
|
(td :class "px-4 py-2 text-stone-600" "and/or")
|
|
(td :class "px-4 py-2 font-mono text-xs" "ternary chains")
|
|
(td :class "px-4 py-2 font-mono text-xs" "&& / sxOr()"))))))
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "js.sx Source")
|
|
(span
|
|
:class "text-sm text-stone-400 font-mono"
|
|
"shared/sx/ref/js.sx"))
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"The SX-to-JavaScript translator — 61 "
|
|
(code "define")
|
|
" forms. "
|
|
"camelCase mangling (500+ RENAMES), expression/statement emission, "
|
|
"self-tail-recursive while loop optimization.")
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200"
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight js-sx-source "lisp"))))))))
|
|
|
|
(defcomp
|
|
~specs/bootstrapper-py-content
|
|
(&key bootstrapper-source bootstrapped-output)
|
|
(~docs/page
|
|
:title "Python Bootstrapper"
|
|
(div
|
|
:class "space-y-8"
|
|
(div
|
|
:class "space-y-3"
|
|
(p
|
|
:class "text-stone-600"
|
|
"This page reads the canonical "
|
|
(code :class "text-violet-700 text-sm" ".sx")
|
|
" spec files, runs the Python bootstrapper, and displays both the compiler source and its generated Python output. "
|
|
"The generated code below is live — it was produced by the bootstrapper at page load time.")
|
|
(p
|
|
:class "text-xs text-stone-400 italic"
|
|
"With SX_USE_REF=1, the server-side SX evaluator running this page IS the bootstrapped output. "
|
|
"This page re-runs the bootstrapper to display the source and result."))
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2 :class "text-2xl font-semibold text-stone-800" "Bootstrapper")
|
|
(span :class "text-sm text-stone-400 font-mono" "bootstrap_py.py"))
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"The compiler reads "
|
|
(code :class "text-violet-700 text-sm" ".sx")
|
|
" spec files (eval, primitives, render, adapter-html) "
|
|
"and emits a standalone Python module. Platform bridge functions (type constructors, environment ops) "
|
|
"are emitted as native Python implementations.")
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200"
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight bootstrapper-source "python")))))
|
|
(div
|
|
:class "space-y-3"
|
|
(div
|
|
:class "flex items-baseline gap-3"
|
|
(h2
|
|
:class "text-2xl font-semibold text-stone-800"
|
|
"Generated Output")
|
|
(span :class "text-sm text-stone-400 font-mono" "sx_ref.py"))
|
|
(p
|
|
:class "text-sm text-stone-500"
|
|
"The Python below was generated by running the bootstrapper against the current spec files. "
|
|
"It is a complete server-side SX evaluator — eval, primitives, and HTML renderer.")
|
|
(div
|
|
:class "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-violet-300"
|
|
(pre
|
|
:class "text-xs leading-relaxed whitespace-pre-wrap break-words"
|
|
(code (highlight bootstrapped-output "python"))))))))
|
|
|
|
(defcomp
|
|
~specs/not-found
|
|
(&key (slug :as string))
|
|
(~docs/page
|
|
:title "Spec Not Found"
|
|
(p
|
|
:class "text-stone-600"
|
|
"No specification found for \""
|
|
slug
|
|
"\". This spec may not exist yet.")))
|