Restructure Specs section into Architecture, Core, and Adapters pages

- Add Architecture intro page explaining the spec's two-layer design
  (core language + selectable adapters) with dependency graph
- Split specs into Core (parser, eval, primitives, render) and
  Adapters (DOM, HTML, SX wire, SxEngine) overview pages
- Add individual detail pages for all adapter and engine specs
- Update nav with Architecture landing, Core, Adapters, and all
  individual spec file links

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 11:55:59 +00:00
parent 7c99002345
commit 3197022299
5 changed files with 203 additions and 25 deletions

View File

@@ -12,7 +12,7 @@
(dict :label "Protocols" :href "/protocols/wire-format")
(dict :label "Examples" :href "/examples/click-to-load")
(dict :label "Essays" :href "/essays/sx-sucks")
(dict :label "Specs" :href "/specs/core"))))
(dict :label "Specs" :href "/specs/"))))
(<> (map (lambda (item)
(~nav-link
:href (get item "href")

View File

@@ -68,11 +68,17 @@
(dict :label "The Reflexive Web" :href "/essays/reflexive-web")))
(define specs-nav-items (list
(dict :label "Architecture" :href "/specs/")
(dict :label "Core" :href "/specs/core")
(dict :label "Parser" :href "/specs/parser")
(dict :label "Evaluator" :href "/specs/evaluator")
(dict :label "Primitives" :href "/specs/primitives")
(dict :label "Renderer" :href "/specs/renderer")))
(dict :label "Renderer" :href "/specs/renderer")
(dict :label "Adapters" :href "/specs/adapters")
(dict :label "DOM Adapter" :href "/specs/adapter-dom")
(dict :label "HTML Adapter" :href "/specs/adapter-html")
(dict :label "SX Wire Adapter" :href "/specs/adapter-sx")
(dict :label "SxEngine" :href "/specs/engine")))
;; Find the current nav label for a slug by matching href suffix.
;; Returns the label string or nil if no match.

View File

@@ -1,9 +1,157 @@
;; Spec viewer components — display canonical SX specification source
(defcomp ~spec-core-content (&key spec-files)
(~doc-page :title "SX Core Specification"
;; ---------------------------------------------------------------------------
;; Architecture intro page
;; ---------------------------------------------------------------------------
(defcomp ~spec-architecture-content ()
(~doc-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 split into two layers: a "
(strong "core") " that defines the language itself, and "
(strong "adapters") " that connect it to specific environments."))
(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 "/specs/parser" :class "hover:underline"
:sx-get "/specs/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 "/specs/evaluator" :class "hover:underline"
:sx-get "/specs/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 "/specs/primitives" :class "hover:underline"
:sx-get "/specs/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 "/specs/renderer" :class "hover:underline"
:sx-get "/specs/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"))))))
(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 "/specs/adapter-dom" :class "hover:underline"
:sx-get "/specs/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 "/specs/adapter-html" :class "hover:underline"
:sx-get "/specs/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 "/specs/adapter-sx" :class "hover:underline"
:sx-get "/specs/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"))))))
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Engine")
(p :class "text-stone-600"
"The engine is the browser-side fetch/swap/history system. It processes "
(code :class "text-violet-700 text-sm" "sx-*")
" attributes on elements to make HTTP requests, swap content, manage browser history, and handle events. It depends on the core evaluator and the DOM adapter.")
(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 "/specs/engine" :class "hover:underline"
:sx-get "/specs/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" "SxEngine — fetch, swap, history, SSE, triggers, indicators"))))))
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Dependency graph")
(div :class "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"
"parser.sx (standalone — no dependencies)
primitives.sx (standalone — declarative registry)
eval.sx depends on: parser, primitives
render.sx (standalone — shared registries)
adapter-dom.sx depends on: render, eval
adapter-html.sx depends on: render, eval
adapter-sx.sx depends on: render, eval
engine.sx depends on: eval, adapter-dom")))
(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.")))))
;; ---------------------------------------------------------------------------
;; Overview pages (Core / Adapters) — show truncated previews of each file
;; ---------------------------------------------------------------------------
(defcomp ~spec-overview-content (&key spec-files)
(~doc-page :title (or spec-title "Specs")
(p :class "text-stone-600 mb-6"
"SX is defined in SX. These four files constitute the canonical, self-hosting specification of the language. Each file is both documentation and executable definition — bootstrap compilers read them to generate native implementations.")
(case spec-title
"Core Language"
"The core specification defines the language itself — parsing, evaluation, primitives, and shared rendering definitions. These four files are platform-independent and sufficient to implement SX on any target."
"Adapters & Engine"
"Adapters connect the core language to specific environments. Each adapter takes evaluated expression trees and produces output for its target. The engine adds browser-side fetch/swap behaviour."
:else ""))
(div :class "space-y-8"
(map (fn (spec)
(div :class "space-y-3"
@@ -21,6 +169,10 @@
(code (highlight (get spec "source") "sx"))))))
spec-files))))
;; ---------------------------------------------------------------------------
;; Detail page — full source of a single spec file
;; ---------------------------------------------------------------------------
(defcomp ~spec-detail-content (&key spec-title spec-desc spec-filename spec-source)
(~doc-page :title spec-title
(div :class "flex items-baseline gap-3 mb-4"
@@ -30,6 +182,10 @@
(pre :class "text-sm leading-relaxed whitespace-pre-wrap break-words"
(code (highlight spec-source "sx"))))))
;; ---------------------------------------------------------------------------
;; Not found
;; ---------------------------------------------------------------------------
(defcomp ~spec-not-found (&key slug)
(~doc-page :title "Spec Not Found"
(p :class "text-stone-600"

View File

@@ -253,11 +253,10 @@
:layout (:sx-section
:section "Specs"
:sub-label "Specs"
:sub-href "/specs/core"
:sub-nav (~section-nav :items specs-nav-items :current "Core")
:selected "Core")
:data (spec-data "core")
:content (~spec-core-content :spec-files spec-files))
:sub-href "/specs/"
:sub-nav (~section-nav :items specs-nav-items :current "Architecture")
:selected "Architecture")
:content (~spec-architecture-content))
(defpage specs-page
:path "/specs/<slug>"
@@ -265,7 +264,7 @@
:layout (:sx-section
:section "Specs"
:sub-label "Specs"
:sub-href "/specs/core"
:sub-href "/specs/"
:sub-nav (~section-nav :items specs-nav-items
:current (find-current specs-nav-items slug))
:selected (or (find-current specs-nav-items slug) ""))
@@ -273,7 +272,8 @@
:content (if spec-not-found
(~spec-not-found :slug slug)
(case slug
"core" (~spec-core-content :spec-files spec-files)
"core" (~spec-overview-content :spec-files spec-files)
"adapters" (~spec-overview-content :spec-files spec-files)
:else (~spec-detail-content
:spec-title spec-title
:spec-desc spec-desc

View File

@@ -104,21 +104,28 @@ def _reference_data(slug: str) -> dict:
}
_SPEC_FILES = {
_CORE_SPECS = {
"parser": ("parser.sx", "Parser", "Tokenization and parsing of SX source text into AST."),
"evaluator": ("eval.sx", "Evaluator", "Tree-walking evaluation of SX expressions."),
"primitives": ("primitives.sx", "Primitives", "All built-in pure functions and their signatures."),
"renderer": ("render.sx", "Renderer", "Rendering evaluated expressions to DOM, HTML, or SX wire format."),
"renderer": ("render.sx", "Renderer", "Shared rendering registries and utilities used by all adapters."),
}
_ADAPTER_SPECS = {
"adapter-dom": ("adapter-dom.sx", "DOM Adapter", "Renders SX expressions to live DOM nodes. Browser-only."),
"adapter-html": ("adapter-html.sx", "HTML Adapter", "Renders SX expressions to HTML strings. Server-side."),
"adapter-sx": ("adapter-sx.sx", "SX Wire Adapter", "Serializes SX for client-side rendering. Component calls stay unexpanded."),
"engine": ("engine.sx", "SxEngine", "Fetch/swap/history engine for browser-side SX. Like HTMX but native to SX."),
}
_ALL_SPECS = {**_CORE_SPECS, **_ADAPTER_SPECS}
def _spec_data(slug: str) -> dict:
"""Return spec file source and highlighted version for display."""
"""Return spec file source and metadata for display."""
import os
from content.highlight import highlight as _highlight
ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref")
# Normalise — inside container shared is at /app/shared
if not os.path.isdir(ref_dir):
ref_dir = "/app/shared/sx/ref"
@@ -128,19 +135,28 @@ def _spec_data(slug: str) -> dict:
if slug == "core":
specs = []
for key in ("parser", "evaluator", "primitives", "renderer"):
filename, title, desc = _SPEC_FILES[key]
filename, title, desc = _CORE_SPECS[key]
filepath = os.path.join(ref_dir, filename)
source = _read_spec(filepath)
specs.append({
"title": title,
"desc": desc,
"filename": filename,
"source": source,
"href": f"/specs/{key}",
"title": title, "desc": desc, "filename": filename,
"source": source, "href": f"/specs/{key}",
})
return {**base, "spec-title": "SX Core Specification", "spec-files": specs}
return {**base, "spec-title": "Core Language", "spec-files": specs}
info = _SPEC_FILES.get(slug)
if slug == "adapters":
specs = []
for key in ("adapter-dom", "adapter-html", "adapter-sx", "engine"):
filename, title, desc = _ADAPTER_SPECS[key]
filepath = os.path.join(ref_dir, filename)
source = _read_spec(filepath)
specs.append({
"title": title, "desc": desc, "filename": filename,
"source": source, "href": f"/specs/{key}",
})
return {**base, "spec-title": "Adapters & Engine", "spec-files": specs}
info = _ALL_SPECS.get(slug)
if not info:
return {**base, "spec-not-found": True}