Add Bootstrappers section, essays index, specs prose, layout fixes

- New Bootstrappers top-level section with overview index and JS bootstrapper
  page that runs bootstrap_js.py and displays both source and generated output
  with live script injection (full page load, not SX navigation)
- Essays section: index page with linked cards and summaries, sx-sucks moved
  to end of nav, removed "grand tradition" line
- Specs: English prose descriptions alongside all canonical .sx specs, added
  Boot/CSSX/Browser spec files to architecture page
- Layout: menu bar nav items wrap instead of overflow, baseline alignment
  between label and nav options
- Homepage: added copyright line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 16:12:38 +00:00
parent 436848060d
commit 1797bd4b16
8 changed files with 315 additions and 38 deletions

View File

@@ -71,7 +71,7 @@
(h1 (or site-title ""))
(when app-label
(span :class "text-lg text-white/80 font-normal" app-label))))
(nav :class "hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0"
(nav :class "hidden md:flex flex-wrap gap-4 text-sm ml-2 justify-end items-center flex-0"
(when nav-tree nav-tree)
(when auth-menu auth-menu)
(when nav-panel nav-panel)
@@ -92,7 +92,7 @@
(<>
(div :id id
:sx-swap-oob (if oob "outerHTML" nil)
:class (str "flex flex-col items-center md:flex-row justify-center md:justify-between w-full p-1 bg-" c "-" shade)
:class (str "flex flex-col items-center md:flex-row md:items-baseline justify-center md:justify-between w-full p-1 bg-" c "-" shade)
(div :class "relative nav-group"
(a :href link-href
:sx-get (if external nil link-href)
@@ -108,7 +108,7 @@
(when selected
(span :class "text-lg text-white/80 font-normal" selected))))))
(when nav
(nav :class "hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0"
(nav :class "hidden md:flex flex-wrap gap-4 text-sm ml-2 justify-end items-baseline flex-0"
nav)))
(when (and child-id (not oob))
(div :id child-id :class "flex flex-col w-full items-center"

View File

@@ -1,7 +1,23 @@
;; Essay content — static content extracted from essays.py
(defcomp ~essays-index-content ()
(~doc-page :title "Essays"
(div :class "space-y-4"
(p :class "text-lg text-stone-600 mb-4"
"Opinions, rationales, and explorations around SX and the ideas behind it.")
(div :class "space-y-3"
(map (fn (item)
(a :href (get item "href")
:sx-get (get item "href") :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(div :class "font-semibold text-stone-800" (get item "label"))
(when (get item "summary")
(p :class "text-sm text-stone-500 mt-1" (get item "summary")))))
essays-nav-items)))))
(defcomp ~essay-sx-sucks ()
(~doc-page :title "sx sucks" (p :class "text-stone-500 text-sm italic mb-8" "In the grand tradition of " (a :href "https://htmx.org/essays/htmx-sucks/" :class "text-violet-600 hover:underline" "htmx sucks")) (~doc-section :title "The parentheses" :id "parens" (p :class "text-stone-600" "S-expressions are parentheses. Lots of parentheses. You thought LISP was dead? No, someone just decided to use it for HTML templates. Your IDE will need a parenthesis counter. Your code reviews will be 40% closing parens. Every merge conflict will be about whether a paren belongs on this line or the next.")) (~doc-section :title "Nobody asked for this" :id "nobody-asked" (p :class "text-stone-600" "The JavaScript ecosystem has React, Vue, Svelte, Solid, Qwik, and approximately 47,000 other frameworks. htmx proved you can skip them all. sx looked at this landscape and said: you know what this needs? A Lisp dialect. For HTML. Over HTTP.") (p :class "text-stone-600" "Nobody was asking for this. The zero GitHub stars confirm it. It is not even on GitHub.")) (~doc-section :title "The author has never written a line of LISP" :id "no-lisp" (p :class "text-stone-600" "The author of sx has never written a single line of actual LISP. Not Common Lisp. Not Scheme. Not Clojure. Not even Emacs Lisp. The entire s-expression evaluator was written by someone whose mental model of LISP comes from reading the first three chapters of SICP and then closing the tab.") (p :class "text-stone-600" "This is like building a sushi restaurant when your only experience with Japanese cuisine is eating supermarket California rolls.")) (~doc-section :title "AI wrote most of it" :id "ai" (p :class "text-stone-600" "A significant portion of sx — the evaluator, the parser, the primitives, the CSS scanner, this very documentation site — was written with AI assistance. The author typed prompts. Claude typed code. This is not artisanal hand-crafted software. This is the software equivalent of a microwave dinner presented on a nice plate.") (p :class "text-stone-600" "He adds features by typing stuff like \"is there rom for macros within sx.js? what benefits m,ight that bring?\", skim-reading the response, and then entering \"crack on then!\" This is not software engineering. This is improv comedy with a compiler.") (p :class "text-stone-600" "Is that bad? Maybe. Is it honest? Yes. Is this paragraph also AI-generated? You will never know.")) (~doc-section :title "No ecosystem" :id "ecosystem" (p :class "text-stone-600" "npm has 2 million packages. PyPI has 500,000. sx has zero packages, zero plugins, zero middleware, zero community, zero Stack Overflow answers, and zero conference talks. If you get stuck, your options are: read the source, or ask the one person who wrote it.") (p :class "text-stone-600" "That person is busy. Good luck.")) (~doc-section :title "Zero jobs" :id "jobs" (p :class "text-stone-600" "Adding sx to your CV will not get you hired. It will get you questioned.") (p :class "text-stone-600" "The interview will end shortly after.")) (~doc-section :title "The creator thinks s-expressions are a personality trait" :id "personality" (p :class "text-stone-600" "Look at this documentation site. It has a violet colour scheme. It has credits to htmx. It has a future possibilities page about hypothetical sx:// protocol schemes. The creator built an entire microservice — with Docker, Redis, and a custom entrypoint script — just to serve documentation about a rendering engine that runs one website.") (p :class "text-stone-600" "This is not engineering. This is a personality disorder expressed in YAML."))))
(~doc-page :title "sx sucks" (~doc-section :title "The parentheses" :id "parens" (p :class "text-stone-600" "S-expressions are parentheses. Lots of parentheses. You thought LISP was dead? No, someone just decided to use it for HTML templates. Your IDE will need a parenthesis counter. Your code reviews will be 40% closing parens. Every merge conflict will be about whether a paren belongs on this line or the next.")) (~doc-section :title "Nobody asked for this" :id "nobody-asked" (p :class "text-stone-600" "The JavaScript ecosystem has React, Vue, Svelte, Solid, Qwik, and approximately 47,000 other frameworks. htmx proved you can skip them all. sx looked at this landscape and said: you know what this needs? A Lisp dialect. For HTML. Over HTTP.") (p :class "text-stone-600" "Nobody was asking for this. The zero GitHub stars confirm it. It is not even on GitHub.")) (~doc-section :title "The author has never written a line of LISP" :id "no-lisp" (p :class "text-stone-600" "The author of sx has never written a single line of actual LISP. Not Common Lisp. Not Scheme. Not Clojure. Not even Emacs Lisp. The entire s-expression evaluator was written by someone whose mental model of LISP comes from reading the first three chapters of SICP and then closing the tab.") (p :class "text-stone-600" "This is like building a sushi restaurant when your only experience with Japanese cuisine is eating supermarket California rolls.")) (~doc-section :title "AI wrote most of it" :id "ai" (p :class "text-stone-600" "A significant portion of sx — the evaluator, the parser, the primitives, the CSS scanner, this very documentation site — was written with AI assistance. The author typed prompts. Claude typed code. This is not artisanal hand-crafted software. This is the software equivalent of a microwave dinner presented on a nice plate.") (p :class "text-stone-600" "He adds features by typing stuff like \"is there rom for macros within sx.js? what benefits m,ight that bring?\", skim-reading the response, and then entering \"crack on then!\" This is not software engineering. This is improv comedy with a compiler.") (p :class "text-stone-600" "Is that bad? Maybe. Is it honest? Yes. Is this paragraph also AI-generated? You will never know.")) (~doc-section :title "No ecosystem" :id "ecosystem" (p :class "text-stone-600" "npm has 2 million packages. PyPI has 500,000. sx has zero packages, zero plugins, zero middleware, zero community, zero Stack Overflow answers, and zero conference talks. If you get stuck, your options are: read the source, or ask the one person who wrote it.") (p :class "text-stone-600" "That person is busy. Good luck.")) (~doc-section :title "Zero jobs" :id "jobs" (p :class "text-stone-600" "Adding sx to your CV will not get you hired. It will get you questioned.") (p :class "text-stone-600" "The interview will end shortly after.")) (~doc-section :title "The creator thinks s-expressions are a personality trait" :id "personality" (p :class "text-stone-600" "Look at this documentation site. It has a violet colour scheme. It has credits to htmx. It has a future possibilities page about hypothetical sx:// protocol schemes. The creator built an entire microservice — with Docker, Redis, and a custom entrypoint script — just to serve documentation about a rendering engine that runs one website.") (p :class "text-stone-600" "This is not engineering. This is a personality disorder expressed in YAML."))))
(defcomp ~essay-why-sexps ()
(~doc-page :title "Why S-Expressions Over HTML Attributes" (~doc-section :title "The problem with HTML attributes" :id "problem" (p :class "text-stone-600" "HTML attributes are strings. You can put anything in a string. htmx puts DSLs in strings — trigger modifiers, swap strategies, CSS selectors. This works but it means you're parsing a language within a language within a language.") (p :class "text-stone-600" "S-expressions are already structured. Keywords are keywords. Lists are lists. Nested expressions nest naturally. There's no need to invent a trigger modifier syntax because the expression language already handles composition.")) (~doc-section :title "Components without a build step" :id "components" (p :class "text-stone-600" "React showed that components are the right abstraction for UI. The price: a build step, a bundler, JSX transpilation. With s-expressions, defcomp is just another form in the language. No transpiler needed. The same source runs on server and client.")) (~doc-section :title "When attributes are better" :id "better" (p :class "text-stone-600" "HTML attributes work in any HTML document. S-expressions need a runtime. If you want progressive enhancement that works with JS disabled, htmx is better. If you want to write HTML by hand in static files, htmx is better. sx only makes sense when you're already rendering server-side and want components."))))

View File

@@ -11,8 +11,9 @@
(dict :label "Reference" :href "/reference/")
(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/"))))
(dict :label "Essays" :href "/essays/")
(dict :label "Specs" :href "/specs/")
(dict :label "Bootstrappers" :href "/bootstrappers/"))))
(<> (map (lambda (item)
(~nav-link
:href (get item "href")

View File

@@ -55,17 +55,28 @@
(dict :label "Retry" :href "/examples/retry")))
(define essays-nav-items (list
(dict :label "sx sucks" :href "/essays/sx-sucks")
(dict :label "Why S-Expressions" :href "/essays/why-sexps")
(dict :label "The htmx/React Hybrid" :href "/essays/htmx-react-hybrid")
(dict :label "On-Demand CSS" :href "/essays/on-demand-css")
(dict :label "Client Reactivity" :href "/essays/client-reactivity")
(dict :label "SX Native" :href "/essays/sx-native")
(dict :label "The SX Manifesto" :href "/essays/sx-manifesto")
(dict :label "Tail-Call Optimization" :href "/essays/tail-call-optimization")
(dict :label "Continuations" :href "/essays/continuations")
(dict :label "Godel, Escher, Bach" :href "/essays/godel-escher-bach")
(dict :label "The Reflexive Web" :href "/essays/reflexive-web")))
(dict :label "Why S-Expressions" :href "/essays/why-sexps"
:summary "Why SX uses s-expressions instead of HTML templates, JSX, or any other syntax.")
(dict :label "The htmx/React Hybrid" :href "/essays/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 "/essays/on-demand-css"
:summary "The CSSX system: keyword atoms resolved to class names, CSS rules injected on first use.")
(dict :label "Client Reactivity" :href "/essays/client-reactivity"
:summary "Reactive UI updates without a virtual DOM, diffing library, or build step.")
(dict :label "SX Native" :href "/essays/sx-native"
:summary "Extending SX beyond the browser — native desktop and mobile rendering from the same source.")
(dict :label "The SX Manifesto" :href "/essays/sx-manifesto"
:summary "The design principles behind SX: simplicity, self-hosting, and s-expressions all the way down.")
(dict :label "Tail-Call Optimization" :href "/essays/tail-call-optimization"
:summary "How SX implements proper tail calls via trampolining in a language that doesn't have them.")
(dict :label "Continuations" :href "/essays/continuations"
:summary "First-class continuations in a tree-walking evaluator — theory and implementation.")
(dict :label "Godel, Escher, Bach" :href "/essays/godel-escher-bach"
:summary "Self-reference, strange loops, and what a self-hosting language has to do with GEB.")
(dict :label "The Reflexive Web" :href "/essays/reflexive-web"
:summary "A web where pages can inspect, modify, and extend their own rendering pipeline.")
(dict :label "sx sucks" :href "/essays/sx-sucks"
:summary "An honest accounting of everything wrong with SX and why you probably shouldn't use it.")))
(define specs-nav-items (list
(dict :label "Architecture" :href "/specs/")
@@ -78,26 +89,61 @@
(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 "Browser" :href "/specs/browser")
(dict :label "SxEngine" :href "/specs/engine")
(dict :label "Orchestration" :href "/specs/orchestration")))
(dict :label "Orchestration" :href "/specs/orchestration")
(dict :label "Boot" :href "/specs/boot")
(dict :label "CSSX" :href "/specs/cssx")))
(define bootstrappers-nav-items (list
(dict :label "Overview" :href "/bootstrappers/")
(dict :label "JavaScript" :href "/bootstrappers/javascript")))
;; Spec file registry — canonical metadata for spec viewer pages.
;; Python only handles file I/O (read-spec-file); all metadata lives here.
;; The :prose field is an English-language description shown alongside the
;; canonical s-expression source.
(define core-spec-items (list
(dict :slug "parser" :filename "parser.sx" :title "Parser" :desc "Tokenization and parsing of SX source text into AST.")
(dict :slug "evaluator" :filename "eval.sx" :title "Evaluator" :desc "Tree-walking evaluation of SX expressions.")
(dict :slug "primitives" :filename "primitives.sx" :title "Primitives" :desc "All built-in pure functions and their signatures.")
(dict :slug "renderer" :filename "render.sx" :title "Renderer" :desc "Shared rendering registries and utilities used by all adapters.")))
(dict :slug "parser" :filename "parser.sx" :title "Parser"
:desc "Tokenization and parsing of SX source text into AST."
:prose "The parser converts SX source text into an abstract syntax tree. It tokenizes the input into atoms, strings, numbers, keywords, and delimiters, then assembles them into nested list structures. The parser is intentionally minimal — s-expressions need very little syntax to parse. Special reader macros handle quasiquote (\\`), unquote (~), splice (~@), and the quote (') shorthand. The output is a tree of plain lists, symbols, keywords, strings, and numbers that the evaluator can walk directly.")
(dict :slug "evaluator" :filename "eval.sx" :title "Evaluator"
:desc "Tree-walking evaluation of SX expressions."
:prose "The evaluator walks the AST produced by the parser and reduces it to values. It implements lexical scoping with closures, special forms (define, let, if, cond, fn, defcomp, defmacro, quasiquote, set!, do), and function application. Macros are expanded at eval time. Component definitions (defcomp) create callable component objects that participate in the rendering pipeline. The evaluator delegates rendering expressions — HTML tags, components, fragments — to whichever adapter is active, making the same source renderable to DOM nodes, HTML strings, or SX wire format.")
(dict :slug "primitives" :filename "primitives.sx" :title "Primitives"
:desc "All built-in pure functions and their signatures."
:prose "Primitives are the built-in functions available in every SX environment. Each entry declares a name, parameter signature, and semantics. Bootstrap compilers implement these natively per target (JavaScript, Python, etc.). The registry covers arithmetic, comparison, string manipulation, list operations, dict operations, type predicates, and control flow helpers. All primitives are pure — they take values and return values with no side effects. Platform-specific operations (DOM access, HTTP, file I/O) are provided separately via platform bridge functions, not primitives.")
(dict :slug "renderer" :filename "render.sx" :title "Renderer"
:desc "Shared rendering registries and utilities used by all adapters."
:prose "The renderer defines what is renderable and how arguments are parsed, but not the output format. It maintains registries of known HTML tags, SVG tags, void elements, and boolean attributes. It specifies how keyword arguments on elements become HTML attributes, how children are collected, and how special attributes (class, style, data-*) are handled. All three adapters (DOM, HTML, SX wire) share these definitions so they agree on what constitutes valid markup. The renderer also defines the StyleValue type used by the CSSX on-demand CSS system.")))
(define adapter-spec-items (list
(dict :slug "adapter-dom" :filename "adapter-dom.sx" :title "DOM Adapter" :desc "Renders SX expressions to live DOM nodes. Browser-only.")
(dict :slug "adapter-html" :filename "adapter-html.sx" :title "HTML Adapter" :desc "Renders SX expressions to HTML strings. Server-side.")
(dict :slug "adapter-sx" :filename "adapter-sx.sx" :title "SX Wire Adapter" :desc "Serializes SX for client-side rendering. Component calls stay unexpanded.")
(dict :slug "engine" :filename "engine.sx" :title "SxEngine" :desc "Pure logic for fetch, swap, history, SSE, triggers, morph, and indicators.")
(dict :slug "orchestration" :filename "orchestration.sx" :title "Orchestration" :desc "Browser wiring that binds engine logic to DOM events, fetch, and lifecycle.")))
(dict :slug "adapter-dom" :filename "adapter-dom.sx" :title "DOM Adapter"
:desc "Renders SX expressions to live DOM nodes. Browser-only."
:prose "The DOM adapter renders evaluated SX expressions into live browser DOM nodes — Elements, Text nodes, and DocumentFragments. It mirrors the HTML adapter's logic but produces DOM objects instead of strings. This is the adapter used by the browser-side SX runtime for initial mount, hydration, and dynamic updates. It handles element creation, attribute setting (including event handlers and style objects), SVG namespace handling, and fragment composition.")
(dict :slug "adapter-html" :filename "adapter-html.sx" :title "HTML Adapter"
:desc "Renders SX expressions to HTML strings. Server-side."
:prose "The HTML adapter renders evaluated SX expressions to HTML strings. It is used server-side to produce complete HTML pages and fragments. It handles void elements (self-closing tags like <br>, <img>), boolean attributes, style serialization, class merging, and proper escaping. The output is standard HTML5 that any browser can parse.")
(dict :slug "adapter-sx" :filename "adapter-sx.sx" :title "SX Wire Adapter"
:desc "Serializes SX for client-side rendering. Component calls stay unexpanded."
:prose "The SX wire adapter serializes expressions as SX source text for transmission to the browser, where sx.js renders them client-side. Unlike the HTML adapter, component calls (~name ...) are NOT expanded — they are sent to the client as-is, allowing the browser to render them with its local component registry. HTML tags ARE serialized as s-expression source. This is the format used for SX-over-HTTP responses and the page boot payload.")
(dict :slug "engine" :filename "engine.sx" :title "SxEngine"
:desc "Pure logic for fetch, swap, history, SSE, triggers, morph, and indicators."
:prose "The engine specifies the pure logic of the browser-side fetch/swap/history system. Like HTMX but native to SX. It defines trigger parsing (click, submit, intersect, poll, load, revealed), swap algorithms (innerHTML, outerHTML, morph, beforebegin, etc.), the morph/diff algorithm for patching existing DOM, history management (push-url, replace-url, popstate), out-of-band swap identification, Server-Sent Events parsing, retry logic with exponential backoff, request header building, response header processing, and optimistic UI updates. This file contains no browser API calls — all platform interaction is in orchestration.sx.")
(dict :slug "orchestration" :filename "orchestration.sx" :title "Orchestration"
:desc "Browser wiring that binds engine logic to DOM events, fetch, and lifecycle."
:prose "Orchestration is the browser wiring layer. It binds the pure engine logic to actual browser APIs: DOM event listeners, fetch(), AbortController, setTimeout/setInterval, IntersectionObserver, history.pushState, and EventSource (SSE). It implements the full request lifecycle — from trigger through fetch through swap — including CSS tracking, response type detection (SX vs HTML), OOB swap processing, script activation, element boosting, and preload. Dependency is strictly one-way: orchestration depends on engine, never the reverse.")))
(define all-spec-items (concat core-spec-items adapter-spec-items))
(define browser-spec-items (list
(dict :slug "boot" :filename "boot.sx" :title "Boot"
:desc "Browser startup lifecycle: mount, hydrate, script processing."
:prose "Boot handles the browser startup sequence and provides the public API for mounting SX content. On page load it: (1) initializes CSS tracking, (2) loads the style dictionary from inline JSON, (3) processes <script type=\"text/sx\"> tags (component definitions and mount directives), (4) hydrates [data-sx] elements, and (5) activates the engine on all elements. It also provides the public mount/hydrate/update/render-component API, and the head element hoisting logic that moves <meta>, <title>, and <link> tags from rendered content into <head>.")
(dict :slug "cssx" :filename "cssx.sx" :title "CSSX"
:desc "On-demand CSS: style dictionary, keyword resolution, rule injection."
:prose "CSSX is the on-demand CSS system. It resolves keyword atoms (:flex, :gap-4, :hover:bg-sky-200) into StyleValue objects with content-addressed class names, injecting CSS rules into the document on first use. The style dictionary is a JSON blob containing: atoms (keyword to CSS declarations), pseudo-variants (hover:, focus:, etc.), responsive breakpoints (md:, lg:, etc.), keyframe animations, arbitrary value patterns, and child selector prefixes (space-x-, space-y-). Classes are only emitted when used, keeping the CSS payload minimal. The dictionary is typically served inline in a <script type=\"text/sx-styles\"> tag.")))
(define all-spec-items (concat core-spec-items (concat adapter-spec-items browser-spec-items)))
(define find-spec
(fn (slug)

View File

@@ -122,6 +122,35 @@
"orchestration.sx"))
(td :class "px-3 py-2 text-stone-700" "Browser wiring — binds engine to DOM events, fetch, request lifecycle"))))))
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Browser")
(p :class "text-stone-600"
"Browser-level support: startup lifecycle and on-demand CSS. "
(code :class "text-violet-700 text-sm" "boot.sx")
" handles page load — processing scripts, mounting content, and hydrating elements. "
(code :class "text-violet-700 text-sm" "cssx.sx")
" provides the on-demand CSS system that resolves keyword atoms into class names and injects rules as needed.")
(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/boot" :class "hover:underline"
:sx-get "/specs/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 lifecycle — 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 "/specs/cssx" :class "hover:underline"
:sx-get "/specs/cssx" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
"cssx.sx"))
(td :class "px-3 py-2 text-stone-700" "On-demand CSS — style dictionary, keyword resolution, rule injection"))))))
(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"
@@ -136,7 +165,9 @@ adapter-html.sx depends on: render, eval
adapter-sx.sx depends on: render, eval
engine.sx depends on: eval, adapter-dom
orchestration.sx depends on: engine, adapter-dom")))
orchestration.sx depends on: engine, adapter-dom
cssx.sx depends on: render
boot.sx depends on: cssx, orchestration, adapter-dom, render")))
(div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Self-hosting")
@@ -163,6 +194,8 @@ orchestration.sx depends on: engine, adapter-dom")))
"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, split into pure logic and browser orchestration."
"Browser"
"Browser-level support: the startup lifecycle that boots SX in the browser, and the on-demand CSS system that resolves keyword atoms into Tailwind-compatible class names."
:else ""))
(div :class "space-y-8"
(map (fn (spec)
@@ -176,6 +209,8 @@ orchestration.sx depends on: engine, adapter-dom")))
(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 "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"))))))
@@ -185,15 +220,107 @@ orchestration.sx depends on: engine, adapter-dom")))
;; Detail page — full source of a single spec file
;; ---------------------------------------------------------------------------
(defcomp ~spec-detail-content (&key spec-title spec-desc spec-filename spec-source)
(defcomp ~spec-detail-content (&key spec-title spec-desc spec-filename spec-source spec-prose)
(~doc-page :title spec-title
(div :class "flex items-baseline gap-3 mb-4"
(span :class "text-sm text-stone-400 font-mono" spec-filename)
(span :class "text-sm text-stone-500" spec-desc))
(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 "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"))))))
;; ---------------------------------------------------------------------------
;; Bootstrappers — summary index
;; ---------------------------------------------------------------------------
(defcomp ~bootstrappers-index-content ()
(~doc-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 "/bootstrappers/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-stone-400" "bootstrap_py.py")
(td :class "px-3 py-2 font-mono text-sm text-stone-400" "shared/sx/")
(td :class "px-3 py-2 text-stone-400" "Planned"))
(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")))))
)))
;; ---------------------------------------------------------------------------
;; Bootstrapper detail — shows bootstrapper source + generated output
;; ---------------------------------------------------------------------------
;; @css border-violet-300 animate-pulse
(defcomp ~bootstrapper-js-content (&key bootstrapper-source bootstrapped-output)
(~doc-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 bootstrapped JavaScript is also injected as a script tag on this page. "
"Open the browser console to verify it loaded."))
(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 "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 "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"))))
(script :type "text/javascript" bootstrapped-output)))))
;; ---------------------------------------------------------------------------
;; Not found
;; ---------------------------------------------------------------------------

View File

@@ -4,8 +4,10 @@
(div :class "max-w-4xl mx-auto px-6 py-16 text-center"
(h1 :class "text-5xl font-bold text-stone-900 mb-4"
(span :class "text-violet-600 font-mono" "(<sx>)"))
(p :class "text-2xl text-stone-600 mb-8"
(p :class "text-2xl text-stone-600 mb-4"
"s-expressions for the web")
(p :class "text-sm text-stone-400"
"\u00a9 Giles Bradshaw 2026")
(p :class "text-lg text-stone-500 max-w-2xl mx-auto mb-12"
"A hypermedia-driven UI engine that combines htmx's server-first philosophy "
"with React's component model. S-expressions over the wire — no HTML, no JavaScript frameworks.")

View File

@@ -214,10 +214,10 @@
:layout (:sx-section
:section "Essays"
:sub-label "Essays"
:sub-href "/essays/sx-sucks"
:sub-nav (~section-nav :items essays-nav-items :current "sx sucks")
:selected "sx sucks")
:content (~essay-sx-sucks))
:sub-href "/essays/"
:sub-nav (~section-nav :items essays-nav-items :current "")
:selected "")
:content (~essays-index-content))
(defpage essay-page
:path "/essays/<slug>"
@@ -225,7 +225,7 @@
:layout (:sx-section
:section "Essays"
:sub-label "Essays"
:sub-href "/essays/sx-sucks"
:sub-href "/essays/"
:sub-nav (~section-nav :items essays-nav-items
:current (find-current essays-nav-items slug))
:selected (or (find-current essays-nav-items slug) ""))
@@ -241,7 +241,7 @@
"continuations" (~essay-continuations)
"godel-escher-bach" (~essay-godel-escher-bach)
"reflexive-web" (~essay-reflexive-web)
:else (~essay-sx-sucks)))
:else (~essays-index-content)))
;; ---------------------------------------------------------------------------
;; Specs section
@@ -273,6 +273,7 @@
:spec-title "Core Language"
:spec-files (map (fn (item)
(dict :title (get item "title") :desc (get item "desc")
:prose (get item "prose")
:filename (get item "filename") :href (str "/specs/" (get item "slug"))
:source (read-spec-file (get item "filename"))))
core-spec-items))
@@ -280,14 +281,56 @@
:spec-title "Adapters & Engine"
:spec-files (map (fn (item)
(dict :title (get item "title") :desc (get item "desc")
:prose (get item "prose")
:filename (get item "filename") :href (str "/specs/" (get item "slug"))
:source (read-spec-file (get item "filename"))))
adapter-spec-items))
"browser" (~spec-overview-content
:spec-title "Browser"
:spec-files (map (fn (item)
(dict :title (get item "title") :desc (get item "desc")
:prose (get item "prose")
:filename (get item "filename") :href (str "/specs/" (get item "slug"))
:source (read-spec-file (get item "filename"))))
browser-spec-items))
:else (let ((spec (find-spec slug)))
(if spec
(~spec-detail-content
:spec-title (get spec "title")
:spec-desc (get spec "desc")
:spec-filename (get spec "filename")
:spec-source (read-spec-file (get spec "filename")))
:spec-source (read-spec-file (get spec "filename"))
:spec-prose (get spec "prose"))
(~spec-not-found :slug slug)))))
;; ---------------------------------------------------------------------------
;; Bootstrappers section
;; ---------------------------------------------------------------------------
(defpage bootstrappers-index
:path "/bootstrappers/"
:auth :public
:layout (:sx-section
:section "Bootstrappers"
:sub-label "Bootstrappers"
:sub-href "/bootstrappers/"
:sub-nav (~section-nav :items bootstrappers-nav-items :current "Overview")
:selected "Overview")
:content (~bootstrappers-index-content))
(defpage bootstrapper-page
:path "/bootstrappers/<slug>"
:auth :public
:layout (:sx-section
:section "Bootstrappers"
:sub-label "Bootstrappers"
:sub-href "/bootstrappers/"
:sub-nav (~section-nav :items bootstrappers-nav-items
:current (find-current bootstrappers-nav-items slug))
:selected (or (find-current bootstrappers-nav-items slug) ""))
:data (bootstrapper-data slug)
:content (if bootstrapper-not-found
(~spec-not-found :slug slug)
(~bootstrapper-js-content
:bootstrapper-source bootstrapper-source
:bootstrapped-output bootstrapped-output)))

View File

@@ -17,6 +17,7 @@ def _register_sx_helpers() -> None:
"reference-data": _reference_data,
"attr-detail-data": _attr_detail_data,
"read-spec-file": _read_spec_file,
"bootstrapper-data": _bootstrapper_data,
})
@@ -118,6 +119,47 @@ def _read_spec_file(filename: str) -> str:
return ";; spec file not found"
def _bootstrapper_data(target: str) -> dict:
"""Return bootstrapper source and generated output for a target.
Returns a dict whose keys become SX env bindings:
- bootstrapper-source: the Python bootstrapper source code
- bootstrapped-output: the generated JavaScript
- bootstrapper-not-found: truthy if target unknown
"""
import os
if target != "javascript":
return {"bootstrapper-not-found": True}
ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref")
if not os.path.isdir(ref_dir):
ref_dir = "/app/shared/sx/ref"
# Read bootstrapper source
bs_path = os.path.join(ref_dir, "bootstrap_js.py")
try:
with open(bs_path, encoding="utf-8") as f:
bootstrapper_source = f.read()
except FileNotFoundError:
bootstrapper_source = "# bootstrapper source not found"
# Run the bootstrap to generate JS
from shared.sx.ref.bootstrap_js import compile_ref_to_js
try:
bootstrapped_output = compile_ref_to_js(
adapters=["dom", "engine", "orchestration", "boot", "cssx"]
)
except Exception as e:
bootstrapped_output = f"// bootstrap error: {e}"
return {
"bootstrapper-not-found": None,
"bootstrapper-source": bootstrapper_source,
"bootstrapped-output": bootstrapped_output,
}
def _attr_detail_data(slug: str) -> dict:
"""Return attribute detail data for a specific attribute slug.