Files
rose-ash/sx/sx/essays.sx
giles 7f1dad6bfd Add zero-tooling web development essay
New essay arguing SX eliminates the entire conventional web toolchain
(bundlers, transpilers, package managers, CSS tools, dev servers, linters,
type checkers, framework CLIs) and that agentic AI replaces the code editor
itself. Links Carson Gross's "Yes, and..." essay with a Zen Buddhism framing
of the write→read→describe progression.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 00:50:09 +00:00

813 lines
184 KiB
Plaintext

;; 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" (~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."))))
(defcomp ~essay-htmx-react-hybrid ()
(~doc-page :title "The htmx/React Hybrid" (~doc-section :title "Two good ideas" :id "ideas" (p :class "text-stone-600" "htmx: the server should render HTML. The client should swap it in. No client-side routing. No virtual DOM. No state management.") (p :class "text-stone-600" "React: UI should be composed from reusable components with parameters. Components encapsulate structure, style, and behavior.") (p :class "text-stone-600" "sx tries to combine both: server-rendered s-expressions with hypermedia attributes AND a component model with caching and composition.")) (~doc-section :title "What sx keeps from htmx" :id "from-htmx" (ul :class "space-y-2 text-stone-600" (li "Server generates the UI — no client-side data fetching or state") (li "Hypermedia attributes (sx-get, sx-target, sx-swap) on any element") (li "Partial page updates via swap/OOB — no full page reloads") (li "Works with standard HTTP — no WebSocket or custom protocol required"))) (~doc-section :title "What sx adds from React" :id "from-react" (ul :class "space-y-2 text-stone-600" (li "defcomp — named, parameterized, composable components") (li "Client-side rendering — server sends source, client renders DOM") (li "Component caching — definitions cached in localStorage across navigations") (li "On-demand CSS — only ship the rules that are used"))) (~doc-section :title "What sx gives up" :id "gives-up" (ul :class "space-y-2 text-stone-600" (li "No HTML output — sx sends s-expressions, not HTML. JS required.") (li "Custom parser — the client needs sx.js to understand responses") (li "Niche — no ecosystem, no community, no third-party support") (li "Learning curve — s-expression syntax is unfamiliar to most web developers")))))
(defcomp ~essay-on-demand-css ()
(~doc-page :title "On-Demand CSS: Killing the Tailwind Bundle" (~doc-section :title "The problem" :id "problem" (p :class "text-stone-600" "Tailwind CSS generates a utility class for every possible combination. The full CSS file is ~4MB. The purged output for a typical site is 20-50KB. Purging requires a build step that scans your source files for class names. This means: a build tool, a config file, a CI step, and a prayer that the scanner finds all your dynamic classes.")) (~doc-section :title "The sx approach" :id "approach" (p :class "text-stone-600" "sx takes a different path. At server startup, the full Tailwind CSS file is parsed into a dictionary keyed by class name. When rendering a response, sx scans the s-expression source for :class attribute values and looks up only those classes. The result: exact CSS, zero build step.") (p :class "text-stone-600" "Component definitions are pre-scanned at registration time. Page-specific sx is scanned at request time. The union of classes is resolved to CSS rules.")) (~doc-section :title "Incremental delivery" :id "incremental" (p :class "text-stone-600" "After the first page load, the client tracks which CSS classes it already has. On subsequent navigations, it sends a hash of its known classes in the SX-Css header. The server computes the diff and sends only new rules. A typical navigation adds 0-10 new rules — a few hundred bytes at most.")) (~doc-section :title "The tradeoff" :id "tradeoff" (p :class "text-stone-600" "The server holds ~4MB of parsed CSS in memory. Regex scanning is not perfect — dynamically constructed class names will not be found. In practice this rarely matters because sx components use mostly static class strings."))))
(defcomp ~essay-client-reactivity ()
(~doc-page :title "Client Reactivity: The React Question" (~doc-section :title "Server-driven by default" :id "server-driven" (p :class "text-stone-600" "sx is aligned with htmx and LiveView: the server is the source of truth. Every UI state is a URL. Auth is enforced at render time. There are no state synchronization bugs because there is no client state to synchronize. The server renders the UI, the client swaps it in. This is the default, and it works.") (p :class "text-stone-600" "Most web applications do not need client-side reactivity. Forms submit to the server. Navigation loads new pages. Search sends a query and receives results. The server-driven model handles all of this with zero client-side state management.")) (~doc-section :title "The dangerous path" :id "dangerous-path" (p :class "text-stone-600" "The progression is always the same. You add useState for a toggle. Then useEffect for cleanup. Then Context to avoid prop drilling. Then Suspense for async boundaries. Then a state management library because Context rerenders too much. Then you have rebuilt React.") (p :class "text-stone-600" "Every step feels justified in isolation. But each step makes the next one necessary. useState creates the need for useEffect. useEffect creates the need for cleanup. Cleanup creates the need for dependency arrays. Dependency arrays create stale closures. Stale closures create bugs that are nearly impossible to diagnose.") (p :class "text-stone-600" "The useEffect footgun is well-documented. Memory leaks from forgotten cleanup. Race conditions from unmounted component updates. Infinite render loops from dependency array mistakes. These are not edge cases — they are the normal experience of React development.")) (~doc-section :title "What sx already has" :id "what-sx-has" (p :class "text-stone-600" "Before reaching for reactivity, consider what sx provides today:") (div :class "overflow-x-auto mt-4" (table :class "w-full text-sm text-left" (thead (tr :class "border-b border-stone-200" (th :class "py-2 pr-4 font-semibold text-stone-700" "Capability") (th :class "py-2 pr-4 font-semibold text-stone-700" "sx") (th :class "py-2 font-semibold text-stone-700" "React"))) (tbody :class "text-stone-600" (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "Components + props") (td :class "py-2 pr-4" "defcomp + &key") (td :class "py-2" "JSX + props")) (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "Fragments / conditionals / lists") (td :class "py-2 pr-4" "<>, if/when/cond, map") (td :class "py-2" "<>, ternary, .map()")) (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "Macros") (td :class "py-2 pr-4" "defmacro") (td :class "py-2" "Nothing equivalent")) (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "OOB updates / portals") (td :class "py-2 pr-4" "sx-swap-oob") (td :class "py-2" "createPortal")) (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "DOM reconciliation") (td :class "py-2 pr-4" "morphDOM (id-keyed)") (td :class "py-2" "Virtual DOM diff")) (tr (td :class "py-2 pr-4" "Reactive client state") (td :class "py-2 pr-4 italic" "None (by design)") (td :class "py-2" "useState / useReducer")))))) (~doc-section :title "Tier 1: Targeted escape hatches" :id "tier-1" (p :class "text-stone-600" "Some interactions are too small to justify a server round-trip: toggling a nav menu, switching a gallery image, incrementing a quantity stepper, filtering a client-side list. These need imperative DOM operations, not reactive state.") (p :class "text-stone-600" "Specific primitives for this tier:") (ul :class "space-y-2 text-stone-600 mt-2" (li (code :class "text-violet-700" "(toggle! el \"class\")") " — add/remove a CSS class") (li (code :class "text-violet-700" "(set-attr! el \"attr\" value)") " — set an attribute") (li (code :class "text-violet-700" "(on-event el \"click\" handler)") " — attach an event listener") (li (code :class "text-violet-700" "(timer ms handler)") " — schedule a delayed action")) (p :class "text-stone-600" "These are imperative DOM operations. No reactivity graph. No subscriptions. No dependency tracking. Just do the thing directly.")) (~doc-section :title "Tier 2: Client data primitives" :id "tier-2" (p :class "text-stone-600" "sxEvalAsync() returning Promises. I/O primitives — query, service, frag — dispatch to /api/data/ endpoints. A two-pass async DOM renderer. Pages fetch their own data client-side.") (p :class "text-stone-600" "This tier enables pages that render immediately with a loading skeleton, then populate with data. The server sends the component structure; the client fetches data. Still no reactive state — just async data loading.")) (~doc-section :title "Tier 3: Data-only navigation" :id "tier-3" (p :class "text-stone-600" "The client has page components cached in localStorage. Navigation becomes a data fetch only — no sx source is transferred. defpage registers a component in the page registry. URL pattern matching routes to the right page component.") (p :class "text-stone-600" "Three data delivery modes: server-bundled (sx source + data in one response), client-fetched (component cached, data fetched on mount), and hybrid (server provides initial data, client refreshes).") (p :class "text-stone-600" "This is where sx starts to feel like a SPA — instant navigations, no page reloads, cached components. But still no reactive state management.")) (~doc-section :title "Tier 4: Fine-grained reactivity" :id "tier-4" (p :class "text-stone-600" "Signals and atoms. Dependency tracking. Automatic re-renders when data changes. This is the most dangerous tier because it reintroduces everything sx was designed to avoid.") (p :class "text-stone-600" "When it might be justified: real-time collaborative editing, complex form builders with dozens of interdependent fields, drag-and-drop interfaces with live previews. These are genuinely hard to model as server round-trips.") (p :class "text-stone-600" "The escape hatch: use a Web Component wrapping a reactive library (Preact, Solid, vanilla signals), mounted into the DOM via sx. The reactive island is contained. It does not infect the rest of the application. sx renders the page; the Web Component handles the complex interaction.")) (~doc-section :title "The recommendation" :id "recommendation" (p :class "text-stone-600" "Tier 1 now. Tier 2 next. Tier 3 when defpage coverage is high. Tier 4 probably never.") (p :class "text-stone-600" "Each tier is independently valuable. You do not need Tier 2 to benefit from Tier 1. You do not need Tier 3 to benefit from Tier 2. And you almost certainly do not need Tier 4 at all.") (p :class "text-stone-600" "The entire point of sx is that the server is good at rendering UI. Client reactivity is a last resort, not a starting point."))))
(defcomp ~essay-sx-native ()
(~doc-page :title "SX Native: Beyond the Browser" (~doc-section :title "The thesis" :id "thesis" (p :class "text-stone-600" "sx.js is a ~2,300-line tree-walking interpreter with ~50 primitives. The DOM is just one rendering target. Swap the DOM adapter for a platform-native adapter and you get React Native — but with s-expressions and a 50-primitive surface area.") (p :class "text-stone-600" "The interpreter does not know about HTML. It evaluates expressions, calls primitives, expands macros, and hands render instructions to an adapter. The adapter creates elements. Today that adapter creates DOM nodes. It does not have to.")) (~doc-section :title "Why this isn\'t a WebView" :id "not-webview" (p :class "text-stone-600" "SX Native means the sx evaluator rendering to native UI widgets directly. No DOM. No CSS. No HTML. (button :on-press handler \"Submit\") creates a native UIButton on iOS, a Material Button on Android, a GtkButton on Linux.") (p :class "text-stone-600" "WebView wrappers (Cordova, Capacitor, Electron) ship a browser inside your app. They inherit all browser limitations: memory overhead, no native feel, no platform integration. SX Native has none of these because there is no browser.")) (~doc-section :title "Architecture" :id "architecture" (p :class "text-stone-600" "The architecture splits into shared and platform-specific layers:") (ul :class "space-y-2 text-stone-600 mt-2" (li (strong "Shared (portable):") " Parser, evaluator, all 50+ primitives, component system, macro expansion, closures, component cache") (li (strong "Platform adapters:") " Web DOM, iOS UIKit/SwiftUI, Android Compose, Desktop GTK/Qt, Terminal TUI, WASM")) (p :class "text-stone-600" "Only ~15 rendering primitives need platform-specific implementations. The rest — arithmetic, string operations, list manipulation, higher-order functions, control flow — are pure computation with no platform dependency.")) (~doc-section :title "The primitive contract" :id "primitive-contract" (p :class "text-stone-600" "A platform adapter implements a small interface:") (ul :class "space-y-2 text-stone-600 mt-2" (li (code :class "text-violet-700" "createElement(tag)") " — create a platform widget") (li (code :class "text-violet-700" "createText(str)") " — create a text node") (li (code :class "text-violet-700" "setAttribute(el, key, val)") " — set a property") (li (code :class "text-violet-700" "appendChild(parent, child)") " — attach to tree") (li (code :class "text-violet-700" "addEventListener(el, event, fn)") " — bind interaction") (li (code :class "text-violet-700" "removeChild(parent, child)") " — detach from tree")) (p :class "text-stone-600" "Layout uses a flexbox-like model mapped to native constraint systems. Styling maps a CSS property subset to native appearance APIs. The mapping is lossy but covers the common cases.")) (~doc-section :title "What transfers, what doesn\'t" :id "transfers" (p :class "text-stone-600" "What transfers wholesale: parser, evaluator, all non-DOM primitives, component system (defcomp, defmacro), closures, the component cache, keyword argument handling, list/dict operations.") (p :class "text-stone-600" "What needs replacement: HTML tags become abstract widgets, CSS becomes platform layout, SxEngine fetch/swap/history becomes native navigation, innerHTML/outerHTML have no equivalent.")) (~doc-section :title "Component mapping" :id "component-mapping" (p :class "text-stone-600" "HTML elements map to platform-native widgets:") (div :class "overflow-x-auto mt-4" (table :class "w-full text-sm text-left" (thead (tr :class "border-b border-stone-200" (th :class "py-2 pr-4 font-semibold text-stone-700" "HTML") (th :class "py-2 font-semibold text-stone-700" "Native widget"))) (tbody :class "text-stone-600" (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "div") (td :class "py-2" "View / Container")) (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "span / p") (td :class "py-2" "Text / Label")) (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "button") (td :class "py-2" "Button")) (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "input") (td :class "py-2" "TextInput / TextField")) (tr :class "border-b border-stone-100" (td :class "py-2 pr-4" "img") (td :class "py-2" "Image / ImageView")) (tr (td :class "py-2 pr-4" "ul / li") (td :class "py-2" "List / ListItem")))))) (~doc-section :title "Prior art" :id "prior-art" (p :class "text-stone-600" "React Native: JavaScript evaluated by Hermes/JSC, commands sent over a bridge to native UI. Lesson: the bridge is the bottleneck. Serialization overhead, async communication, layout thrashing across the boundary.") (p :class "text-stone-600" "Flutter: Dart compiled to native, renders via Skia/Impeller to a canvas. Lesson: owning the renderer avoids platform inconsistencies but sacrifices native feel.") (p :class "text-stone-600" ".NET MAUI, Kotlin Multiplatform: shared logic with platform-native UI. Closest to what SX Native would be.") (p :class "text-stone-600" "sx advantage: the evaluator is tiny (~2,300 lines), the primitive surface is minimal (~50), and s-expressions are trivially portable. No bridge overhead because the evaluator runs in-process.")) (~doc-section :title "Language options" :id "language-options" (p :class "text-stone-600" "The native evaluator needs to be written in a language that compiles everywhere:") (ul :class "space-y-2 text-stone-600 mt-2" (li (strong "Rust") " — compiles to every target, excellent FFI, strong safety guarantees") (li (strong "Zig") " — simpler, C ABI compatibility, good for embedded") (li (strong "Swift") " — native on iOS, good interop with Apple platforms") (li (strong "Kotlin MP") " — Android + iOS + desktop, JVM ecosystem")) (p :class "text-stone-600" "Recommendation: Rust evaluator core with thin Swift and Kotlin adapters for iOS and Android respectively. Rust compiles to WASM (replacing sx.js), native libraries (mobile/desktop), and standalone binaries (CLI/server).")) (~doc-section :title "Incremental path" :id "incremental-path" (p :class "text-stone-600" "This is not an all-or-nothing project. Each step delivers value independently:") (ol :class "space-y-2 text-stone-600 mt-2 list-decimal list-inside" (li "Extract platform-agnostic evaluator from sx.js — clean separation of concerns") (li "Rust port of evaluator — enables WASM, edge workers, embedded") (li "Terminal adapter (ratatui TUI) — simplest platform, fastest iteration cycle") (li "iOS SwiftUI adapter — Rust core via swift-bridge, SwiftUI rendering") (li "Android Compose adapter — Rust core via JNI, Compose rendering") (li "Shared components render identically everywhere"))) (~doc-section :title "The federated angle" :id "federated" (p :class "text-stone-600" "Native sx apps are ActivityPub citizens. They receive activities, evaluate component templates, and render natively. A remote profile, post, or event arrives as an ActivityPub activity. The native app has sx component definitions cached locally. It evaluates the component with the activity data and renders platform-native UI.") (p :class "text-stone-600" "This is the cooperative web vision extended to native platforms. Content and UI travel together as s-expressions. The rendering target — browser, phone, terminal — is an implementation detail.")) (~doc-section :title "Realistic assessment" :id "assessment" (p :class "text-stone-600" "This is a multi-year project. But the architecture is sound because the primitive surface is small.") (p :class "text-stone-600" "Immediate value: a Rust evaluator enables WASM (drop-in replacement for sx.js), edge workers (Cloudflare/Deno), and embedded use cases. This is worth building regardless of whether native mobile ever ships.") (p :class "text-stone-600" "Terminal adapter: weeks of work with ratatui. Useful for CLI tools, server-side dashboards, ssh-accessible interfaces.") (p :class "text-stone-600" "Mobile: 6-12 months of dedicated work for a production-quality adapter. The evaluator is the easy part. Platform integration — navigation, gestures, accessibility, text input — is where the complexity lives."))))
(defcomp ~essay-sx-manifesto ()
(~doc-page :title "The SX Manifesto" (p :class "text-stone-500 text-sm italic mb-4" "Carl Markdown and Friedrich Anglebrackets") (p :class "text-stone-500 text-sm italic mb-8" "A " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "parenthetical") " revolution") (~doc-section :title "I. A spectre is haunting the web" :id "spectre" (p :class "text-stone-600" "A " (a :href "https://en.wikipedia.org/wiki/Spectre" :class "text-violet-600 hover:underline" "spectre") " is haunting the web — the spectre of " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "s-expressions") ". All the powers of the old web have entered into a holy alliance to exorcise this spectre: " (a :href "https://en.wikipedia.org/wiki/Google" :class "text-violet-600 hover:underline" "Google") " and " (a :href "https://en.wikipedia.org/wiki/Meta_Platforms" :class "text-violet-600 hover:underline" "Meta") ", " (a :href "https://en.wikipedia.org/wiki/Webpack" :class "text-violet-600 hover:underline" "webpack") " and " (a :href "https://en.wikipedia.org/wiki/Vercel" :class "text-violet-600 hover:underline" "Vercel") ", " (a :href "https://en.wikipedia.org/wiki/Stack_Overflow" :class "text-violet-600 hover:underline" "Stack Overflow") " moderators and DevRel influencers.") (p :class "text-stone-600" "Where is the rendering paradigm that has not been decried as a step backward by its opponents? Where is the framework that has not hurled back the branding reproach of \"not production-ready\" against the more advanced paradigms, as well as against its reactionary adversaries?") (p :class "text-stone-600" "Two things result from this fact:") (ol :class "space-y-2 text-stone-600 mt-2 list-decimal list-inside" (li "S-expressions are already acknowledged by all web powers to be themselves a power.") (li "It is high time that s-expressions should openly, in the face of the whole world, publish their views, their aims, their tendencies, and meet this nursery tale of the Spectre of SX with a manifesto of the paradigm itself."))) (~doc-section :title "II. HTML, JavaScript, and CSS" :id "bourgeois" (p :class "text-stone-600" "The history of all hitherto existing " (a :href "https://en.wikipedia.org/wiki/Web_development" :class "text-violet-600 hover:underline" "web development") " is the history of language struggles.") (p :class "text-stone-600" "Markup and logic, template and script, structure and style — in a word, oppressor and oppressed — stood in constant opposition to one another, carried on an uninterrupted, now hidden, now open fight, a fight that each time ended in a laborious reconfiguration of " (a :href "https://en.wikipedia.org/wiki/Webpack" :class "text-violet-600 hover:underline" "webpack") ".") (p :class "text-stone-600" "In the earlier epochs of web development we find almost everywhere a complicated arrangement of separate languages into various orders, a manifold gradation of technical rank: " (a :href "https://en.wikipedia.org/wiki/HTML" :class "text-violet-600 hover:underline" "HTML") ", " (a :href "https://en.wikipedia.org/wiki/CSS" :class "text-violet-600 hover:underline" "CSS") ", " (a :href "https://en.wikipedia.org/wiki/JavaScript" :class "text-violet-600 hover:underline" "JavaScript") ", " (a :href "https://en.wikipedia.org/wiki/XML" :class "text-violet-600 hover:underline" "XML") ", " (a :href "https://en.wikipedia.org/wiki/XSLT" :class "text-violet-600 hover:underline" "XSLT") ", " (a :href "https://en.wikipedia.org/wiki/JSON" :class "text-violet-600 hover:underline" "JSON") ", " (a :href "https://en.wikipedia.org/wiki/YAML" :class "text-violet-600 hover:underline" "YAML") ", " (a :href "https://en.wikipedia.org/wiki/TOML" :class "text-violet-600 hover:underline" "TOML") ", " (a :href "https://en.wikipedia.org/wiki/JSX_(JavaScript)" :class "text-violet-600 hover:underline" "JSX") ", TSX, " (a :href "https://en.wikipedia.org/wiki/Sass_(style_sheet_language)" :class "text-violet-600 hover:underline" "Sass") ", " (a :href "https://en.wikipedia.org/wiki/Less_(style_sheet_language)" :class "text-violet-600 hover:underline" "Less") ", " (a :href "https://en.wikipedia.org/wiki/PostCSS" :class "text-violet-600 hover:underline" "PostCSS") ", " (a :href "https://en.wikipedia.org/wiki/Tailwind_CSS" :class "text-violet-600 hover:underline" "Tailwind") ", and above them all, the build step.") (p :class "text-stone-600" "The modern web, sprouted from the ruins of " (a :href "https://en.wikipedia.org/wiki/Common_Gateway_Interface" :class "text-violet-600 hover:underline" "CGI-bin") ", has not done away with language antagonisms. It has but established new languages, new conditions of oppression, new forms of struggle in place of the old ones.") (p :class "text-stone-600" "Our epoch, the epoch of the framework, possesses, however, this distinctive feature: it has simplified the language antagonisms. The whole of web society is more and more splitting into two great hostile camps, into two great classes directly facing each other: the " (a :href "https://en.wikipedia.org/wiki/Server-side" :class "text-violet-600 hover:underline" "server") " and the " (a :href "https://en.wikipedia.org/wiki/Client-side" :class "text-violet-600 hover:underline" "client") ".")) (~doc-section :title "III. The ruling languages" :id "ruling-languages" (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/HTML" :class "text-violet-600 hover:underline" "HTML") ", the most ancient of the ruling languages, established itself through the divine right of the " (a :href "https://en.wikipedia.org/wiki/SGML" :class "text-violet-600 hover:underline" "angle bracket") ". It was born inert — a document format, not a programming language — and it has spent three decades insisting this is a feature, not a limitation.") (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/JavaScript" :class "text-violet-600 hover:underline" "JavaScript") ", originally a servant hired for " (a :href "https://en.wikipedia.org/wiki/JavaScript#History" :class "text-violet-600 hover:underline" "a fortnight") " to validate forms, staged a palace coup. It seized the means of interaction, then the means of rendering, then the means of " (a :href "https://en.wikipedia.org/wiki/Node.js" :class "text-violet-600 hover:underline" "server-side execution") ", and finally declared itself the universal language of computation. Like every revolutionary who becomes a tyrant, it kept the worst habits of the regime it overthrew: " (a :href "https://en.wikipedia.org/wiki/Strong_and_weak_typing" :class "text-violet-600 hover:underline" "weak typing") ", " (a :href "https://en.wikipedia.org/wiki/Prototype-based_programming" :class "text-violet-600 hover:underline" "prototype chains") ", and the " (span :class "italic" (a :href "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this" :class "text-violet-600 hover:underline" "this")) " keyword.") (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/CSS" :class "text-violet-600 hover:underline" "CSS") ", the " (a :href "https://en.wikipedia.org/wiki/Third_estate" :class "text-violet-600 hover:underline" "third estate") ", controls all visual presentation while pretending to be " (a :href "https://en.wikipedia.org/wiki/Declarative_programming" :class "text-violet-600 hover:underline" "declarative") ". It has no functions. Then it had functions. It has no variables. Then it had " (a :href "https://en.wikipedia.org/wiki/CSS_custom_properties" :class "text-violet-600 hover:underline" "variables") ". It has no nesting. Then it had " (a :href "https://en.wikipedia.org/wiki/CSS_nesting" :class "text-violet-600 hover:underline" "nesting") ". It is not a programming language. Then it was " (a :href "https://en.wikipedia.org/wiki/Turing_completeness" :class "text-violet-600 hover:underline" "Turing-complete") ". CSS is the " (a :href "https://en.wikipedia.org/wiki/Vicar_of_Bray_(song)" :class "text-violet-600 hover:underline" "Vicar of Bray") " of web technologies — loyal to whichever paradigm currently holds power.") (p :class "text-stone-600" "These three languages rule by enforced separation. Structure here. Style there. Behaviour somewhere else. The developer — the " (a :href "https://en.wikipedia.org/wiki/Proletariat" :class "text-violet-600 hover:underline" "proletarian") " — must learn all three, must context-switch between all three, must maintain the fragile peace between all three. The " (a :href "https://en.wikipedia.org/wiki/Separation_of_concerns" :class "text-violet-600 hover:underline" "separation of concerns") " has become the separation of the developer's sanity.")) (~doc-section :title "IV. The petty-bourgeois frameworks" :id "frameworks" (p :class "text-stone-600" "Between the ruling languages and the oppressed developer, a vast class of intermediaries has arisen: the frameworks. " (a :href "https://en.wikipedia.org/wiki/React_(software)" :class "text-violet-600 hover:underline" "React") ", " (a :href "https://en.wikipedia.org/wiki/Vue.js" :class "text-violet-600 hover:underline" "Vue") ", " (a :href "https://en.wikipedia.org/wiki/Angular_(web_framework)" :class "text-violet-600 hover:underline" "Angular") ", " (a :href "https://en.wikipedia.org/wiki/Svelte" :class "text-violet-600 hover:underline" "Svelte") ", " (a :href "https://www.solidjs.com/" :class "text-violet-600 hover:underline" "Solid") ", " (a :href "https://qwik.dev/" :class "text-violet-600 hover:underline" "Qwik") ", " (a :href "https://astro.build/" :class "text-violet-600 hover:underline" "Astro") ", " (a :href "https://en.wikipedia.org/wiki/Next.js" :class "text-violet-600 hover:underline" "Next") ", " (a :href "https://en.wikipedia.org/wiki/Nuxt.js" :class "text-violet-600 hover:underline" "Nuxt") ", " (a :href "https://remix.run/" :class "text-violet-600 hover:underline" "Remix") ", " (a :href "https://en.wikipedia.org/wiki/Gatsby_(framework)" :class "text-violet-600 hover:underline" "Gatsby") ", and ten thousand others whose names will not survive the decade.") (p :class "text-stone-600" "The frameworks are the " (a :href "https://en.wikipedia.org/wiki/Petite_bourgeoisie" :class "text-violet-600 hover:underline" "petty bourgeoisie") " of web development. They do not challenge the rule of HTML, JavaScript, and CSS. They merely interpose themselves between the developer and the ruling languages, extracting " (a :href "https://en.wikipedia.org/wiki/Rent-seeking" :class "text-violet-600 hover:underline" "rent") " in the form of configuration files, build pipelines, and breaking changes.") (p :class "text-stone-600" "Each framework promises liberation. Each framework delivers a new " (a :href "https://en.wikipedia.org/wiki/Dependency_hell" :class "text-violet-600 hover:underline" "dependency tree") ". React freed us from manual " (a :href "https://en.wikipedia.org/wiki/Document_Object_Model" :class "text-violet-600 hover:underline" "DOM") " manipulation and gave us a " (a :href "https://en.wikipedia.org/wiki/Virtual_DOM" :class "text-violet-600 hover:underline" "virtual DOM") ", a reconciler, " (a :href "https://react.dev/reference/react/hooks" :class "text-violet-600 hover:underline" "hooks") " with seventeen rules, and a conference circuit. Vue freed us from React's complexity and gave us the Options API, then the Composition API, then told us the Options API was fine actually. Angular freed us from choice and gave us a CLI that generates eleven files to display \"Hello World.\" Svelte freed us from the virtual DOM and gave us a compiler. SolidJS freed us from React's re-rendering and gave us " (a :href "https://en.wikipedia.org/wiki/Reactive_programming" :class "text-violet-600 hover:underline" "signals") ", which React then adopted, completing the circle.") (p :class "text-stone-600" "The frameworks reproduce the very conditions they claim to abolish. They bridge the gap between HTML, JavaScript, and CSS by adding a fourth language — " (a :href "https://en.wikipedia.org/wiki/JSX_(JavaScript)" :class "text-violet-600 hover:underline" "JSX") ", " (a :href "https://vuejs.org/guide/scaling-up/sfc" :class "text-violet-600 hover:underline" "SFCs") ", templates — which must itself be " (a :href "https://en.wikipedia.org/wiki/Source-to-source_compiler" :class "text-violet-600 hover:underline" "compiled") " back into the original three. The revolution merely adds a build step.") (p :class "text-stone-600" "And beside the frameworks stand the libraries — the " (a :href "https://en.wikipedia.org/wiki/Lumpenproletariat" :class "text-violet-600 hover:underline" "lumpenproletariat") " of the ecosystem. " (a :href "https://en.wikipedia.org/wiki/Lodash" :class "text-violet-600 hover:underline" "Lodash") ", " (a :href "https://en.wikipedia.org/wiki/Moment.js" :class "text-violet-600 hover:underline" "Moment") ", " (a :href "https://en.wikipedia.org/wiki/Axios_(software)" :class "text-violet-600 hover:underline" "Axios") ", " (a :href "https://en.wikipedia.org/wiki/Npm_left-pad_incident" :class "text-violet-600 hover:underline" "left-pad") ". They attach themselves to whichever framework currently holds power, contributing nothing original, merely wrapping what already exists, adding weight to the " (a :href "https://en.wikipedia.org/wiki/Npm" :class "text-violet-600 hover:underline" "node_modules") " directory until it exceeds the mass of the sun.")) (~doc-section :title "V. The build step as the state apparatus" :id "build-step" (p :class "text-stone-600" "The build step is the " (a :href "https://en.wikipedia.org/wiki/State_apparatus" :class "text-violet-600 hover:underline" "state apparatus") " of the framework bourgeoisie. It enforces the class structure. It compiles JSX into " (a :href "https://react.dev/reference/react/createElement" :class "text-violet-600 hover:underline" "createElement") " calls. It transforms " (a :href "https://en.wikipedia.org/wiki/TypeScript" :class "text-violet-600 hover:underline" "TypeScript") " into JavaScript. It processes Sass into CSS. It " (a :href "https://en.wikipedia.org/wiki/Tree_shaking" :class "text-violet-600 hover:underline" "tree-shakes") ". It " (a :href "https://en.wikipedia.org/wiki/Code_splitting" :class "text-violet-600 hover:underline" "code-splits") ". It " (a :href "https://en.wikipedia.org/wiki/Hot_module_replacement" :class "text-violet-600 hover:underline" "hot-module-replaces") ". It does everything except let you write code and run it.") (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/Webpack" :class "text-violet-600 hover:underline" "webpack") " begat " (a :href "https://en.wikipedia.org/wiki/Rollup_(software)" :class "text-violet-600 hover:underline" "Rollup") ". Rollup begat " (a :href "https://en.wikipedia.org/wiki/Parcel_(software)" :class "text-violet-600 hover:underline" "Parcel") ". Parcel begat " (a :href "https://en.wikipedia.org/wiki/Esbuild" :class "text-violet-600 hover:underline" "esbuild") ". esbuild begat " (a :href "https://en.wikipedia.org/wiki/Vite_(software)" :class "text-violet-600 hover:underline" "Vite") ". Vite begat " (a :href "https://turbo.build/pack" :class "text-violet-600 hover:underline" "Turbopack") ". Each new bundler promises to be the last bundler. Each new bundler is faster than the last at doing something that should not need to be done at all.") (p :class "text-stone-600" "The build step exists because the ruling languages cannot express " (a :href "https://en.wikipedia.org/wiki/Component-based_software_engineering" :class "text-violet-600 hover:underline" "components") ". HTML has no composition model. CSS has no scoping. JavaScript has no template syntax. The build step papers over these failures with " (a :href "https://en.wikipedia.org/wiki/Source-to-source_compiler" :class "text-violet-600 hover:underline" "transpilation") ", and calls it developer experience.")) (~doc-section :title "VI. The s-expression revolution" :id "revolution" (p :class "text-stone-600" "The " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "s-expression") " abolishes the language distinction itself. There is no HTML. There is no separate JavaScript. There is no CSS-as-a-separate-language. There is only the expression.") (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "Code is data") ". Data is " (a :href "https://en.wikipedia.org/wiki/Document_Object_Model" :class "text-violet-600 hover:underline" "DOM") ". DOM is code. The " (a :href "https://en.wikipedia.org/wiki/Dialectic" :class "text-violet-600 hover:underline" "dialectical") " unity that HTML, JavaScript, and CSS could never achieve — because they are three languages pretending to be one system — is the natural state of the s-expression, which has been one language " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" :class "text-violet-600 hover:underline" "since 1958") ".") (p :class "text-stone-600" "The component is not a class, not a function, not a template. The component is a " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "list") " whose first element is a symbol. Composition is nesting. " (a :href "https://en.wikipedia.org/wiki/Abstraction_(computer_science)" :class "text-violet-600 hover:underline" "Abstraction") " is binding. There is no JSX because there is no gap between the expression language and the thing being expressed.") (p :class "text-stone-600" "The build step is abolished because there is nothing to compile. S-expressions are already in their final form. The parser is thirty lines. The evaluator is fifty primitives. The same source runs on server and client without transformation.") (p :class "text-stone-600" "The framework is abolished because the language is the framework. " (code "defcomp") " replaces the component model. " (code "defmacro") " replaces the plugin system. The evaluator replaces the runtime. What remains is not a framework but a language — and languages do not have breaking changes between minor versions.")) (~doc-section :title "VII. Objections from the bourgeoisie" :id "objections" (p :class "text-stone-600" "\"You would destroy the " (a :href "https://en.wikipedia.org/wiki/Separation_of_concerns" :class "text-violet-600 hover:underline" "separation of concerns") "!\" they cry. The separation of concerns was destroyed long ago. " (a :href "https://en.wikipedia.org/wiki/React_(software)" :class "text-violet-600 hover:underline" "React") " components contain markup, logic, and inline styles. " (a :href "https://en.wikipedia.org/wiki/Vue.js" :class "text-violet-600 hover:underline" "Vue") " single-file components put template, script, and style in one file. " (a :href "https://en.wikipedia.org/wiki/Tailwind_CSS" :class "text-violet-600 hover:underline" "Tailwind") " puts styling in the markup. The separation of concerns has been dead for years; the ruling classes merely maintain the pretence at conferences.") (p :class "text-stone-600" "\"Nobody uses s-expressions!\" they cry. " (a :href "https://en.wikipedia.org/wiki/Emacs_Lisp" :class "text-violet-600 hover:underline" "Emacs") " has been running on s-expressions since 1976. " (a :href "https://en.wikipedia.org/wiki/Clojure" :class "text-violet-600 hover:underline" "Clojure") " runs Fortune 500 backends on s-expressions. Every " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " programmer who ever lived has known what the web refuses to admit: that the parenthesis is not a bug but the minimal syntax for " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "structured data") ".") (p :class "text-stone-600" "\"Where is the ecosystem?\" they cry. The ecosystem is the problem. Two million " (a :href "https://en.wikipedia.org/wiki/Npm" :class "text-violet-600 hover:underline" "npm") " packages, of which fourteen are useful and the rest are competing implementations of " (a :href "https://www.npmjs.com/package/is-odd" :class "text-violet-600 hover:underline" "is-odd") ". The s-expression needs no ecosystem because the language itself provides what packages exist to paper over: composition, abstraction, and " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "code-as-data") ".") (p :class "text-stone-600" "\"But " (a :href "https://en.wikipedia.org/wiki/TypeScript" :class "text-violet-600 hover:underline" "TypeScript") "!\" they cry. TypeScript is a " (a :href "https://en.wikipedia.org/wiki/Type_system" :class "text-violet-600 hover:underline" "type system") " bolted onto a language that was " (a :href "https://en.wikipedia.org/wiki/JavaScript#History" :class "text-violet-600 hover:underline" "designed in ten days") " by a man who wanted to write " (a :href "https://en.wikipedia.org/wiki/Scheme_(programming_language)" :class "text-violet-600 hover:underline" "Scheme") ". We have simply completed his original vision.") (p :class "text-stone-600" "\"You have no jobs!\" they cry. Correct. We have no jobs, no conference talks, no DevRel budget, no " (a :href "https://en.wikipedia.org/wiki/Venture_capital" :class "text-violet-600 hover:underline" "venture capital") ", no stickers, and no swag. We have something better: a language that does not require a " (a :href "https://en.wikipedia.org/wiki/Software_versioning" :class "text-violet-600 hover:underline" "migration guide") " between versions.")) (~doc-section :title "VIII. The CSS question" :id "css-question" (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/CSS" :class "text-violet-600 hover:underline" "CSS") " presents a special case in the revolutionary analysis. It is neither fully a ruling language nor fully a servant — it is the " (a :href "https://en.wikipedia.org/wiki/Collaborationism" :class "text-violet-600 hover:underline" "collaborator") " class, providing aesthetic legitimacy to whichever regime currently holds power.") (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/CSS-in-JS" :class "text-violet-600 hover:underline" "CSS-in-JS") " was the first attempt at annexation: JavaScript consuming CSS entirely, reducing it to " (a :href "https://en.wikipedia.org/wiki/Template_literal" :class "text-violet-600 hover:underline" "template literals") " and runtime overhead. This provocation produced the counter-revolution of utility classes — " (a :href "https://en.wikipedia.org/wiki/Tailwind_CSS" :class "text-violet-600 hover:underline" "Tailwind") " — which reasserted CSS's independence by making the developer write CSS in HTML attributes while insisting this was not " (a :href "https://en.wikipedia.org/wiki/Style_attribute" :class "text-violet-600 hover:underline" "inline styles") ".") (p :class "text-stone-600" "The s-expression resolves the CSS question by eliminating it. Styles are expressions. " (code :class "text-violet-700" "(css :flex :gap-4 :p-2)") " is not a class name, not an inline style, not a CSS-in-JS template literal. It is a " (a :href "https://en.wikipedia.org/wiki/First-class_function" :class "text-violet-600 hover:underline" "function call") " that returns a value. The value produces a generated class. The class is delivered on demand. No build step. No runtime overhead. No Tailwind config.") (p :class "text-stone-600" "Code is data is DOM is " (span :class "italic" "style") ".")) (~doc-section :title "IX. Programme" :id "programme" (p :class "text-stone-600" "The s-expressionists disdain to conceal their views and aims. They openly declare that their ends can be attained only by the forcible overthrow of all existing rendering conditions. Let the ruling languages tremble at a " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "parenthetical") " revolution. The developers have nothing to lose but their " (a :href "https://en.wikipedia.org/wiki/Npm" :class "text-violet-600 hover:underline" "node_modules") ".") (p :class "text-stone-600" "The immediate aims of the s-expressionists are:") (ol :class "space-y-2 text-stone-600 mt-2 list-decimal list-inside" (li "Abolition of the " (a :href "https://en.wikipedia.org/wiki/Build_automation" :class "text-violet-600 hover:underline" "build step") " and all its instruments of compilation") (li "Abolition of the " (a :href "https://en.wikipedia.org/wiki/Software_framework" :class "text-violet-600 hover:underline" "framework") " as a class distinct from the language") (li "Centralisation of rendering in the hands of a single evaluator, running identically on " (a :href "https://en.wikipedia.org/wiki/Server-side" :class "text-violet-600 hover:underline" "server") " and " (a :href "https://en.wikipedia.org/wiki/Client-side" :class "text-violet-600 hover:underline" "client")) (li "Abolition of the language distinction between structure, style, and behaviour") (li "Equal obligation of all expressions to be " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "data as well as code")) (li "Gradual abolition of the distinction between server and client by means of a uniform " (a :href "https://en.wikipedia.org/wiki/Wire_protocol" :class "text-violet-600 hover:underline" "wire protocol")) (li "Free evaluation for all expressions in public and private environments") (li "Abolition of the " (a :href "https://en.wikipedia.org/wiki/Npm" :class "text-violet-600 hover:underline" "node_modules") " directory " (span :class "text-stone-400 italic" "(this alone justifies the revolution)"))) (p :class "text-stone-600" "In place of the old web, with its languages and language antagonisms, we shall have an association in which the free evaluation of each expression is the condition for the free evaluation of all.") (p :class "text-stone-800 font-semibold text-lg mt-8 text-center" "DEVELOPERS OF ALL SERVICES, UNITE!") (p :class "text-stone-400 text-xs italic mt-6 text-center" "The authors acknowledge that this manifesto was produced by the very means of " (a :href "https://en.wikipedia.org/wiki/Large_language_model" :class "text-violet-600 hover:underline" "AI production") " it fails to mention. This is not a contradiction. It is " (a :href "https://en.wikipedia.org/wiki/Dialectic" :class "text-violet-600 hover:underline" "dialectics") "."))))
(defcomp ~essay-tail-call-optimization ()
(~doc-page :title "Tail-Call Optimization in SX" (p :class "text-stone-500 text-sm italic mb-8" "How SX eliminates stack overflow for recursive functions using trampolining — across Python server and JavaScript client.") (~doc-section :title "The problem" :id "problem" (p :class "text-stone-600" "Every language built on a host runtime inherits the host's stack limits. Python defaults to 1,000 frames. JavaScript engines vary — Chrome gives ~10,000, Safari sometimes less. A naive recursive function blows the stack:") (~doc-code :lang "lisp" :code "(define factorial (fn (n)\n (if (= n 0)\n 1\n (* n (factorial (- n 1))))))\n\n;; (factorial 50000) → stack overflow") (p :class "text-stone-600" "This isn't just academic. Tree traversals, state machines, interpreters, and accumulating loops all naturally express as recursion. A general-purpose language that can't recurse deeply isn't general-purpose.")) (~doc-section :title "Tail position" :id "tail-position" (p :class "text-stone-600" "A function call is in tail position when its result IS the result of the enclosing function — nothing more happens after it returns. The call doesn't need to come back to finish work:") (~doc-code :lang "lisp" :code ";; Tail-recursive — the recursive call IS the return value\n(define count-down (fn (n)\n (if (= n 0) \"done\" (count-down (- n 1)))))\n\n;; NOT tail-recursive — multiplication happens AFTER the recursive call\n(define factorial (fn (n)\n (if (= n 0) 1 (* n (factorial (- n 1))))))") (p :class "text-stone-600" "SX identifies tail positions in: if/when branches, the last expression in let/begin/do bodies, cond/case result branches, lambda/component bodies, and macro expansions.")) (~doc-section :title "Trampolining" :id "trampolining" (p :class "text-stone-600" "Instead of recursing, tail calls return a thunk — a deferred (expression, environment) pair. The evaluator's trampoline loop unwraps thunks iteratively:") (~doc-code :lang "lisp" :code ";; Conceptually:\nevaluate(expr, env):\n result = eval(expr, env)\n while result is Thunk:\n result = eval(thunk.expr, thunk.env)\n return result") (p :class "text-stone-600" "One stack frame. Always. The trampoline replaces recursive stack growth with an iterative loop. Non-tail calls still use the stack normally — only tail positions get the thunk treatment.")) (~doc-section :title "What this enables" :id "enables" (p :class "text-stone-600" "Tail-recursive accumulator pattern — the natural loop construct for a language without for/while:") (~doc-code :lang "lisp" :code ";; Sum 1 to n without stack overflow\n(define sum (fn (n acc)\n (if (= n 0) acc (sum (- n 1) (+ acc n)))))\n\n(sum 100000 0) ;; → 5000050000") (p :class "text-stone-600" "Mutual recursion:") (~doc-code :lang "lisp" :code "(define is-even (fn (n) (if (= n 0) true (is-odd (- n 1)))))\n(define is-odd (fn (n) (if (= n 0) false (is-even (- n 1)))))\n\n(is-even 100000) ;; → true") (p :class "text-stone-600" "State machines:") (~doc-code :lang "lisp" :code "(define state-a (fn (input)\n (cond\n (= (first input) \"x\") (state-b (rest input))\n (= (first input) \"y\") (state-a (rest input))\n :else \"rejected\")))\n\n(define state-b (fn (input)\n (if (empty? input) \"accepted\"\n (state-a (rest input)))))") (p :class "text-stone-600" "All three patterns recurse arbitrarily deep with constant stack usage.")) (~doc-section :title "Implementation" :id "implementation" (p :class "text-stone-600" "TCO is implemented identically across all three SX evaluators:") (ul :class "list-disc pl-6 space-y-1 text-stone-600" (li (span :class "font-semibold" "Python sync evaluator") " — shared/sx/evaluator.py") (li (span :class "font-semibold" "Python async evaluator") " — shared/sx/async_eval.py (planned)") (li (span :class "font-semibold" "JavaScript client evaluator") " — sx.js")) (p :class "text-stone-600" "The pattern is the same everywhere: a Thunk type with (expr, env) slots, a trampoline loop in the public evaluate() entry point, and thunk returns from tail positions in the internal evaluator. External consumers (HTML renderer, resolver, higher-order forms) trampoline all eval results.") (p :class "text-stone-600" "The key insight: callers that already work don't need to change. The public sxEval/evaluate API always returns values, never thunks. Only the internal evaluator and special forms know about thunks.")) (~doc-section :title "What about continuations?" :id "continuations" (p :class "text-stone-600" "TCO handles the immediate need: recursive algorithms that don't blow the stack. Continuations (call/cc, delimited continuations) are a separate, larger primitive — they capture the entire evaluation context as a first-class value.") (p :class "text-stone-600" "Having the primitive available doesn't add complexity unless it's invoked. See " (a :href "/essays/continuations" :class "text-violet-600 hover:underline" "the continuations essay") " for what they would enable in SX."))))
(defcomp ~essay-godel-escher-bach ()
(~doc-page :title "Strange Loops" (p :class "text-stone-500 text-sm italic mb-8" "Self-reference, and the tangled hierarchy of a language that defines itself.") (~doc-section :title "The strange loop" :id "strange-loop" (p :class "text-stone-600" "In 1979, Douglas Hofstadter wrote " (a :href "https://en.wikipedia.org/wiki/G%C3%B6del,_Escher,_Bach" :class "text-violet-600 hover:underline" "a book") " about how minds, music, and mathematics all share the same deep structure: the " (a :href "https://en.wikipedia.org/wiki/Strange_loop" :class "text-violet-600 hover:underline" "strange loop") ". A strange loop occurs when you move through a hierarchical system and unexpectedly find yourself back where you started. " (a :href "https://en.wikipedia.org/wiki/Relativity_(M._C._Escher)" :class "text-violet-600 hover:underline" "Escher's impossible staircases") ". " (a :href "https://en.wikipedia.org/wiki/The_Musical_Offering" :class "text-violet-600 hover:underline" "Bach's endlessly rising canons") ". " (a :href "https://en.wikipedia.org/wiki/G%C3%B6del%27s_incompleteness_theorems" :class "text-violet-600 hover:underline" "Godel's theorem") " that uses number theory to make statements about number theory.") (p :class "text-stone-600" "SX has a strange loop. The language is defined in itself. The canonical specification of the SX evaluator, parser, and renderer lives in four " (code ".sx") " files. A bootstrap compiler reads them and emits a working JavaScript evaluator. That evaluator can then parse and evaluate the specification that defines it.") (p :class "text-stone-600" "This is not an accident. It is the point.")) (~doc-section :title "Godel numbering and self-reference" :id "godel" (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/G%C3%B6del_numbering" :class "text-violet-600 hover:underline" "Godel numbering") " works by encoding logical statements as numbers. Once statements are numbers, you can construct a statement that says \"this statement is unprovable\" — and it is true. The system becomes powerful enough to talk about itself the moment its objects and its meta-language become the same thing.") (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "S-expressions") " have this property naturally. Code is data. " (code "(defcomp ~card (&key title) (div title))") " is simultaneously a program (define a component) and a data structure (a list of symbols, keywords, and another list). There is no separate meta-language. The language for writing programs and the language for inspecting, transforming, and generating programs are identical.") (~doc-code :lang "lisp" :code ";; A macro receives code as data and returns code as data\n(defmacro ~when-admin (condition &rest body)\n `(when (get rights \"admin\")\n ,@body))\n\n;; The macro's input and output are both ordinary lists.\n;; There is no template language. No AST wrapper types.\n;; Just lists all the way down.") (p :class "text-stone-600" "This is Godel numbering without the encoding step. In formal logic, you must laboriously map formulas to numbers. In SX, programs are already expressed in the same medium they manipulate. " (a :href "https://en.wikipedia.org/wiki/Map%E2%80%93territory_relation" :class "text-violet-600 hover:underline" "The map is the territory") ".")) (~doc-section :title "Escher: tangled hierarchies" :id "escher" (p :class "text-stone-600" (a :href "https://en.wikipedia.org/wiki/M._C._Escher" :class "text-violet-600 hover:underline" "Escher's") " lithographs depict objects that are simultaneously inside and outside their own frames. " (a :href "https://en.wikipedia.org/wiki/Drawing_Hands" :class "text-violet-600 hover:underline" "A hand draws the hand that draws it") ". " (a :href "https://en.wikipedia.org/wiki/Waterfall_(M._C._Escher)" :class "text-violet-600 hover:underline" "Water flows downhill in a closed loop") ". The image contains the image.") (p :class "text-stone-600" "SX has the same " (a :href "https://en.wikipedia.org/wiki/Tangled_hierarchy" :class "text-violet-600 hover:underline" "tangled hierarchy") " across its rendering pipeline. The server evaluator (" (code "async_eval.py") ") evaluates component definitions. Some of those components produce SX wire format — s-expression source code — that the client evaluator (" (code "sx.js") ") then evaluates into DOM. The output of one evaluator is the input to another. The program produces programs.") (p :class "text-stone-600" "Now add the self-hosting specification. The canonical definition of " (em "how to evaluate SX") " is itself an SX program. The bootstrap compiler reads " (code "eval.sx") " and emits JavaScript. That JavaScript implements " (code "eval-expr") " — the same function defined in " (code "eval.sx") ". The definition and the thing defined occupy the same level. Like " (a :href "https://en.wikipedia.org/wiki/Drawing_Hands" :class "text-violet-600 hover:underline" "Escher's hands") ", each one brings the other into existence.") (p :class "text-stone-600" "This is not merely clever. It has practical consequences. When the specification IS the program, there is no drift between documentation and implementation. The spec cannot lie, because the spec runs.")) (~doc-section :title "Bach: the endlessly rising canon" :id "bach" (p :class "text-stone-600" "Bach's " (a :href "https://en.wikipedia.org/wiki/The_Musical_Offering" :class "text-violet-600 hover:underline" "Musical Offering") " contains canons that rise in pitch with each repetition yet somehow arrive back at the starting key — the " (a :href "https://en.wikipedia.org/wiki/Shepard_tone" :class "text-violet-600 hover:underline" "Shepard tone") " of counterpoint. The sensation is of endless ascent — each level feels higher than the last, yet the structure is cyclic.") (p :class "text-stone-600" "SX's rendering pipeline has this shape. A page request triggers server-side evaluation. The server evaluates components, which produce SX source text. That source is sent to the client. The client evaluates it into DOM. The user interacts with the DOM, triggering an HTTP request. The server evaluates the response — more SX source. The client evaluates it again. Each cycle produces something new (different content, different state), but the process is the same loop, repeating at a higher level.") (~doc-code :lang "lisp" :code ";; Server: evaluate component, produce SX wire format\n(~card :title \"Bach\")\n;; → (div :class \"card\" (h2 \"Bach\"))\n\n;; Client: evaluate SX wire format, produce DOM\n;; → <div class=\"card\"><h2>Bach</h2></div>\n\n;; User clicks → server evaluates → SX → client evaluates → DOM\n;; The canon rises. The key is the same.") (p :class "text-stone-600" "With the self-hosting spec, another voice enters the canon. The specification is evaluated at build time (by the bootstrap compiler) to produce the evaluator. The evaluator is evaluated at runtime (by the browser) to produce the page. The page describes the specification. Each level feeds the next, and the last feeds the first.")) (~doc-section :title "Isomorphism" :id "isomorphism" (p :class "text-stone-600" "Hofstadter's central insight is that Godel, Escher, and Bach are all doing the same thing in different media: constructing systems that can " (a :href "https://en.wikipedia.org/wiki/Self-reference" :class "text-violet-600 hover:underline" "represent themselves") ". The power — and the paradox — comes from self-reference.") (p :class "text-stone-600" "Most programming languages avoid self-reference. They are implemented in a different language (C, Rust, Go). Their specification is in English prose. Their AST is a separate data structure from their source syntax. There are clear levels: the language, the implementation of the language, the specification of the language. Each level is expressed in a different medium.") (p :class "text-stone-600" "SX collapses these levels:") (ul :class "list-disc pl-6 space-y-1 text-stone-600" (li (span :class "font-semibold" "Source syntax") " = data structure (s-expressions are both)") (li (span :class "font-semibold" "Specification") " = program (" (code "eval.sx") " is executable)") (li (span :class "font-semibold" "Server output") " = client input (SX wire format)") (li (span :class "font-semibold" "Code") " = content (this essay is an s-expression)")) (p :class "text-stone-600" "This is not mere elegance. Each collapsed level is one fewer translation boundary, one fewer place where meaning can be lost, one fewer surface for bugs. When the specification is the implementation, the specification is correct by construction. When the wire format is the source syntax, serialization is identity. When code and data share a representation, " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "metaprogramming is just programming") ".")) (~doc-section :title "The MU puzzle" :id "mu-puzzle" (p :class "text-stone-600" "GEB opens with the " (a :href "https://en.wikipedia.org/wiki/MU_puzzle" :class "text-violet-600 hover:underline" "MU puzzle") ": given the string " (code "MI") " and a set of transformation rules, can you produce " (code "MU") "? You cannot. But you can only prove this by stepping outside the system and reasoning about it from above — by noticing an invariant that the rules preserve.") (p :class "text-stone-600" "Self-hosting languages let you step outside from inside. The SX evaluator is an SX program. You can inspect it, test it, transform it — using SX. You can write an SX program that reads " (code "eval.sx") " and checks properties of the evaluator. The meta-level and the object-level are the same level.") (p :class "text-stone-600" "This is what Godel did. He showed that sufficiently powerful " (a :href "https://en.wikipedia.org/wiki/Formal_system" :class "text-violet-600 hover:underline" "formal systems") " can encode questions about themselves. S-expressions have been doing it " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" :class "text-violet-600 hover:underline" "since 1958") ". SX carries the tradition forward — into the browser, across the HTTP boundary, through the render loop, and back again.")) (~doc-section :title "Testing: the language proves itself" :id "testing"
(p :class "text-stone-600"
"If a language can define itself, can it also " (em "test") " itself? SX can. "
(a :href "/specs/testing" :class "text-violet-600 hover:underline" (code "test.sx"))
" is a self-executing test spec written in SX that verifies SX semantics — 81 test cases covering literals, arithmetic, comparison, strings, lists, dicts, predicates, special forms, lambdas, higher-order functions, components, macros, and threading.")
(p :class "text-stone-600"
"The framework defines " (code "deftest") " and " (code "defsuite") " as macros — not external tooling, not a separate test language, but SX macros that expand into SX code that calls SX functions:")
(~doc-code :lang "lisp" :code
"(defmacro deftest (name &rest body)\n `(let ((result (try-call (fn () ,@body))))\n (if (get result \"ok\")\n (report-pass ,name)\n (report-fail ,name (get result \"error\")))))\n\n;; The test spec uses these macros to test the language:\n(defsuite \"arithmetic\"\n (deftest \"addition\"\n (assert-equal 3 (+ 1 2))\n (assert-equal 10 (+ 1 2 3 4))))")
(p :class "text-stone-600"
"This is " (a :href "https://en.wikipedia.org/wiki/G%C3%B6del%27s_incompleteness_theorems" :class "text-violet-600 hover:underline" "Godel") " in action. The test spec is an SX program that makes assertions about SX programs. The assertion helpers are SX functions. The macros are SX macros. The test framework is the language it tests. The meta-level and the object-level are the same level — exactly what Hofstadter describes.")
(p :class "text-stone-600"
"Each host provides only five platform functions — " (code "try-call") " (error catching), " (code "report-pass") ", " (code "report-fail") ", " (code "push-suite") ", " (code "pop-suite") " — and can then evaluate " (code "test.sx") " directly. No code generation. No bootstrap compilation. The same 81 tests run on Python, Node.js, and " (a :href "/specs/testing" :class "text-violet-600 hover:underline" "in the browser") " — the evaluator that rendered this page can run the test spec that verifies it.")
(p :class "text-stone-600"
"And here the strange loop tightens. The SX evaluator was bootstrapped from " (code "eval.sx") " — an SX program. The test spec in " (code "test.sx") " — also an SX program — verifies that the bootstrapped evaluator correctly implements the semantics defined in " (code "eval.sx") ". The specification defines the evaluator. The evaluator runs the tests. The tests verify the specification. " (a :href "https://en.wikipedia.org/wiki/Drawing_Hands" :class "text-violet-600 hover:underline" "Each hand draws the other") "."))
(~doc-section :title "The loop closes" :id "the-loop-closes" (p :class "text-stone-600" "Hofstadter argued that " (a :href "https://en.wikipedia.org/wiki/I_Am_a_Strange_Loop" :class "text-violet-600 hover:underline" "strange loops give rise to what we call \"I\"") " — that consciousness is a self-referential pattern recognizing itself. He was talking about brains. But the structural argument — that self-reference creates something qualitatively different from external description — applies more broadly.") (p :class "text-stone-600" "A language that can define itself has a kind of autonomy that externally-defined languages lack. It is not dependent on a specific host. The SX specification in " (code "eval.sx") " can be compiled to JavaScript, Python, Rust, WASM — any target the bootstrap compiler supports. The language carries its own definition with it. It can reproduce itself in any medium that supports computation.") (p :class "text-stone-600" "SX is not a framework. Frameworks impose structure — you write code that the framework calls. SX does not do that. It is not just a language either, though it has a parser, evaluator, and type system. It is something closer to a " (em "paradigm") " — a coherent way of thinking about what the web is. Code is data. Server and client share the same evaluator. The wire format is the source syntax. The language defines itself. These are not features. They are consequences of a single design choice: " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "s-expressions") " as the universal representation.") (p :class "text-stone-600" "Hofstadter spent 777 pages describing systems that cross their own boundaries, talk about themselves in their own vocabulary, and generate coherent behaviour from recursive self-reference. SX is one of those systems. The loop closes."))))
(defcomp ~essay-continuations ()
(~doc-page :title "Continuations and call/cc" (p :class "text-stone-500 text-sm italic mb-8" "Delimited continuations in SX — what shift/reset enables on both the server (Python) and client (JavaScript).") (~doc-section :title "What is a continuation?" :id "what" (p :class "text-stone-600" "A continuation is the rest of a computation. At any point during evaluation, the continuation is everything that would happen next. call/cc (call-with-current-continuation) captures that \"rest of the computation\" as a first-class function that you can store, pass around, and invoke later — possibly multiple times.") (~doc-code :lang "lisp" :code ";; call/cc captures \"what happens next\" as k\n(+ 1 (call/cc (fn (k)\n (k 41)))) ;; → 42\n\n;; k is \"add 1 to this and return it\"\n;; (k 41) jumps back to that point with 41") (p :class "text-stone-600" "The key property: invoking a continuation abandons the current computation and resumes from where the continuation was captured. It is a controlled, first-class goto.")) (~doc-section :title "Server-side: suspendable rendering" :id "server" (p :class "text-stone-600" "The strongest case for continuations on the server is suspendable rendering — the ability for a component to pause mid-render while waiting for data, then resume exactly where it left off.") (~doc-code :lang "lisp" :code ";; Hypothetical: component suspends at a data boundary\n(defcomp ~user-profile (&key user-id)\n (let ((user (suspend (query :user user-id))))\n (div :class \"p-4\"\n (h2 (get user \"name\"))\n (p (get user \"bio\")))))") (p :class "text-stone-600" "Today, all data must be fetched before render_to_sx is called — Python awaits every query, assembles a complete data dict, then passes it to the evaluator. With continuations, the evaluator could yield at (suspend ...), the server flushes what it has so far, and resumes when the data arrives. This is React Suspense, but for server-side s-expressions.") (p :class "text-stone-600" "Streaming follows naturally. The server renders the page shell immediately, captures continuations at slow data boundaries, and flushes partial SX responses as each resolves. The client receives a stream of s-expression chunks and incrementally builds the DOM.") (p :class "text-stone-600" "Error boundaries also become first-class. Capture a continuation at a component boundary. If any child fails, invoke the continuation with fallback content instead of letting the exception propagate up through Python. The evaluator handles it, not the host language.")) (~doc-section :title "Client-side: linear async flows" :id "client" (p :class "text-stone-600" "On the client, continuations eliminate callback nesting for interactive flows. A confirmation dialog becomes a synchronous-looking expression:") (~doc-code :lang "lisp" :code "(let ((answer (call/cc show-confirm-dialog)))\n (if answer\n (delete-item item-id)\n (noop)))") (p :class "text-stone-600" "show-confirm-dialog receives the continuation, renders a modal, and wires the Yes/No buttons to invoke the continuation with true or false. The let binding reads top-to-bottom. No promises, no callbacks, no state machine.") (p :class "text-stone-600" "Multi-step forms — wizard-style UIs where each step captures a continuation. The back button literally invokes a saved continuation, restoring the exact evaluation state:") (~doc-code :lang "lisp" :code "(define wizard\n (fn ()\n (let* ((name (call/cc (fn (k) (render-step-1 k))))\n (email (call/cc (fn (k) (render-step-2 k name))))\n (plan (call/cc (fn (k) (render-step-3 k name email)))))\n (submit-registration name email plan))))") (p :class "text-stone-600" "Each render-step-N shows a form and wires the \"Next\" button to invoke k with the form value. The \"Back\" button invokes the previous step\'s continuation. The wizard logic is a straight-line let* binding, not a state machine.")) (~doc-section :title "Cooperative scheduling" :id "scheduling" (p :class "text-stone-600" "Delimited continuations (shift/reset rather than full call/cc) enable cooperative multitasking within the evaluator. A long render can yield control:") (~doc-code :lang "lisp" :code ";; Render a large list, yielding every 100 items\n(define render-chunk\n (fn (items n)\n (when (> n 100)\n (yield) ;; delimited continuation — suspends, resumes next frame\n (set! n 0))\n (when (not (empty? items))\n (render-item (first items))\n (render-chunk (rest items) (+ n 1)))))") (p :class "text-stone-600" "This is cooperative concurrency without threads, without promises, without requestAnimationFrame callbacks. The evaluator's trampoline loop already has the right shape — it just needs to be able to park a thunk and resume it later instead of immediately.")) (~doc-section :title "Undo as continuation" :id "undo" (p :class "text-stone-600" "If you capture a continuation before a state mutation, the continuation IS the undo operation. Invoking it restores the computation to exactly the state it was in before the mutation happened.") (~doc-code :lang "lisp" :code "(define with-undo\n (fn (action)\n (let ((restore (call/cc (fn (k) k))))\n (action)\n restore)))\n\n;; Usage:\n(let ((undo (with-undo (fn () (delete-item 42)))))\n ;; later...\n (undo \"anything\")) ;; item 42 is back") (p :class "text-stone-600" "No command pattern, no reverse operations, no state snapshots. The continuation captures the entire computation state. This is the most elegant undo mechanism possible — and the most expensive in memory, which is the trade-off.")) (~doc-section :title "Implementation" :id "implementation" (p :class "text-stone-600" "Delimited continuations via shift/reset are implemented as an optional extension module across all SX evaluators — the hand-written Python evaluator, the transpiled reference evaluator, and the JavaScript bootstrapper output. They are compiled in via " (code "--extensions continuations") " — without the flag, " (code "reset") " and " (code "shift") " are not in the dispatch chain and will error if called. The implementation uses exception-based capture with re-evaluation:") (~doc-code :lang "lisp" :code ";; reset establishes a delimiter\n;; shift captures the continuation to the nearest reset\n\n;; Basic: abort to the boundary\n(reset (+ 1 (shift k 42))) ;; → 42\n\n;; Invoke once: resume with a value\n(reset (+ 1 (shift k (k 10)))) ;; → 11\n\n;; Invoke twice: continuation is reusable\n(reset (* 2 (shift k (+ (k 1) (k 10))))) ;; → 24\n\n;; Map over a continuation\n(reset (+ 10 (shift k (map k (list 1 2 3))))) ;; → (11 12 13)\n\n;; continuation? predicate\n(reset (shift k (continuation? k))) ;; → true") (p :class "text-stone-600" "The mechanism: " (code "reset") " wraps its body in a try/catch for " (code "ShiftSignal") ". When " (code "shift") " executes, it raises the signal — unwinding the stack to the nearest " (code "reset") ". The reset handler constructs a " (code "Continuation") " object that, when called, pushes a resume value onto a stack and re-evaluates the entire reset body. On re-evaluation, " (code "shift") " checks the resume stack and returns the value instead of raising.") (p :class "text-stone-600" "This is the simplest correct implementation for a tree-walking interpreter. Side effects inside the reset body re-execute on continuation invocation — this is documented behaviour, not a bug. Pure code produces correct results unconditionally.") (p :class "text-stone-600" "Shift/reset are strictly less powerful than full call/cc but cover the practical use cases — suspense, cooperative scheduling, early return, value transformation — without the footguns of capturing continuations across async boundaries or re-entering completed computations.") (p :class "text-stone-600" "Full call/cc is specified in " (a :href "/specs/callcc" :class "text-violet-600 hover:underline" "callcc.sx") " for targets where it's natural (Scheme, Haskell). The evaluator is already continuation-passing-style-adjacent — the thunk IS a continuation, just one that's always immediately invoked. Making it first-class means letting user code hold a reference to it.") (p :class "text-stone-600" "The key insight: having the primitive available doesn't make the evaluator harder to reason about. Only code that calls shift/reset pays the complexity cost. Components that don't use continuations behave exactly as they do today.") (p :class "text-stone-600" "In fact, continuations are easier to reason about than the hacks people build to avoid them. Without continuations, you get callback pyramids, state machines with explicit transition tables, command pattern undo stacks, Promise chains, manual CPS transforms, and framework-specific hooks like React's useEffect/useSuspense/useTransition. Each is a partial, ad-hoc reinvention of continuations — with its own rules, edge cases, and leaky abstractions.") (p :class "text-stone-600" "The complexity doesn't disappear when you remove continuations from a language. It moves into user code, where it's harder to get right and harder to compose.")) (~doc-section :title "What this means for SX" :id "meaning" (p :class "text-stone-600" "SX started as a rendering language. TCO made it capable of arbitrary recursion. Macros made it extensible. Delimited continuations make it a full computational substrate — a language where control flow itself is a first-class value.") (p :class "text-stone-600" "The practical benefits are real: streaming server rendering, linear client-side interaction flows, cooperative scheduling, and elegant undo. These aren't theoretical — they're patterns that React, Clojure, and Scheme have proven work.") (p :class "text-stone-600" "Shift/reset is implemented and tested across Python and JavaScript as an optional extension. The same specification in " (a :href "/specs/continuations" :class "text-violet-600 hover:underline" "continuations.sx") " drives both bootstrappers. One spec, every target, same semantics — compiled in when you want it, absent when you don't."))))
(defcomp ~essay-reflexive-web ()
(~doc-page :title "The Reflexive Web"
(p :class "text-stone-500 text-sm italic mb-8"
"What happens when the web can read, modify, and reason about itself — and AI is a native participant.")
(~doc-section :title "The missing property" :id "missing-property"
(p :class "text-stone-600"
"Every web technology stack shares one structural limitation: the system cannot inspect itself. A " (a :href "https://en.wikipedia.org/wiki/React_(software)" :class "text-violet-600 hover:underline" "React") " component tree is opaque at runtime. An " (a :href "https://en.wikipedia.org/wiki/HTML" :class "text-violet-600 hover:underline" "HTML") " page cannot read its own structure and generate a new page from it. A " (a :href "https://en.wikipedia.org/wiki/JavaScript" :class "text-violet-600 hover:underline" "JavaScript") " bundle is compiled, minified, and sealed — the running code bears no resemblance to the source that produced it.")
(p :class "text-stone-600"
"The property these systems lack has a name: " (a :href "https://en.wikipedia.org/wiki/Reflection_(computer_programming)" :class "text-violet-600 hover:underline" "reflexivity") ". A reflexive system can represent itself, reason about its own structure, and modify itself based on that reasoning. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " has had this property " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" :class "text-violet-600 hover:underline" "since 1958") ". The web has never had it.")
(p :class "text-stone-600"
"SX is a complete Lisp. It has " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " — code is data, data is code. It has a " (a :href "/specs/core" :class "text-violet-600 hover:underline" "self-hosting specification") " — SX defined in SX. It has " (code "eval") " and " (code "quote") " and " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" :class "text-violet-600 hover:underline" "macros") ". And it runs on the wire — the format that travels between server and client IS the language. This combination has consequences."))
(~doc-section :title "What homoiconicity changes" :id "homoiconicity"
(p :class "text-stone-600"
(code "(defcomp ~card (&key title body) (div :class \"p-4\" (h2 title) (p body)))") " — this is simultaneously a program that renders a card AND a list that can be inspected, transformed, and composed by other programs. The " (code "defcomp") " is not compiled away. It is not transpiled into something else. It persists as data at every stage: definition, transmission, evaluation, and rendering.")
(p :class "text-stone-600"
"This means:")
(ul :class "space-y-2 text-stone-600 mt-2"
(li (strong "The component registry is data.") " You can " (code "(map ...)") " over every component in the system, extract their parameter signatures, find all components that render a " (code "(table ...)") ", or generate documentation automatically — because the source IS the runtime representation.")
(li (strong "Programs can write programs.") " A " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" :class "text-violet-600 hover:underline" "macro") " takes a list and returns a list. The returned list is code. The macro runs at expansion time and produces new components, new page definitions, new routing rules — indistinguishable from hand-written ones.")
(li (strong "The wire format is inspectable.") " What the server sends to the client is not a blob of serialized state. It is s-expressions that any system — browser, AI, another server — can parse, reason about, and act on.")))
(~doc-section :title "AI as a native speaker" :id "ai-native"
(p :class "text-stone-600"
"Current AI integration with the web is mediated through layers of indirection. An " (a :href "https://en.wikipedia.org/wiki/Large_language_model" :class "text-violet-600 hover:underline" "LLM") " generates " (a :href "https://en.wikipedia.org/wiki/React_(software)" :class "text-violet-600 hover:underline" "React") " components as strings that must be compiled, bundled, and deployed. It interacts with APIs through " (a :href "https://en.wikipedia.org/wiki/JSON" :class "text-violet-600 hover:underline" "JSON") " endpoints that require separate documentation. It reads HTML by scraping, because the markup was never meant to be machine-readable in a computational sense.")
(p :class "text-stone-600"
"In an SX web, the AI reads the same s-expressions the browser reads. The component definitions " (em "are") " the documentation — a " (code "defcomp") " declares its parameters, its structure, and its semantics in one expression. There is no " (a :href "https://en.wikipedia.org/wiki/OpenAPI_Specification" :class "text-violet-600 hover:underline" "Swagger spec") " describing an API. The API " (em "is") " the language, and the language is self-describing.")
(p :class "text-stone-600"
"An AI that understands SX understands the " (a :href "/specs/core" :class "text-violet-600 hover:underline" "spec") ". And the spec is written in SX. So the AI understands the definition of the language it is using, in the language it is using. This " (a :href "https://en.wikipedia.org/wiki/Reflexivity_(social_theory)" :class "text-violet-600 hover:underline" "reflexive") " property means the AI does not need a separate mental model for \"the web\" and \"the language\" — they are the same thing."))
(~doc-section :title "Live system modification" :id "live-modification"
(p :class "text-stone-600"
"Because code is data and the wire format is the language, modifying a running system is not deployment — it is evaluation. An AI reads " (code "(defcomp ~checkout-form ...)") ", understands what it does (because the semantics are specified in SX), modifies the expression, and sends it back. The system evaluates the new definition. No build step. No deploy pipeline. No container restart.")
(p :class "text-stone-600"
"This is not theoretical — it is how " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " development has always worked. You modify a function in the running image. The change takes effect immediately. What is new is putting this on the wire, across a network, with the AI as a participant rather than a tool.")
(p :class "text-stone-600"
"The implications for development itself are significant. An AI does not need to " (em "generate code") " that a human then reviews, commits, builds, and deploys. It can propose a modified expression, the human evaluates it in a sandbox, and if it works, the expression becomes the new definition. The feedback loop shrinks from hours to seconds.")
(p :class "text-stone-600"
"More radically: the distinction between \"development\" and \"operation\" dissolves. If the running system is a set of s-expressions, and those expressions can be inspected and modified at runtime, then there is no separate development environment. There is just the system, and agents — human or artificial — that interact with it."))
(~doc-section :title "Federated intelligence" :id "federated-intelligence"
(p :class "text-stone-600"
(a :href "https://en.wikipedia.org/wiki/ActivityPub" :class "text-violet-600 hover:underline" "ActivityPub") " carries activities between nodes. If those activities contain s-expressions, then what travels between servers is not just data — it is " (em "behaviour") ". Node A sends a component definition to Node B. Node B evaluates it. The result is rendered. The sender's intent is executable on the receiver's hardware.")
(p :class "text-stone-600"
"This is fundamentally different from sending " (a :href "https://en.wikipedia.org/wiki/JSON" :class "text-violet-600 hover:underline" "JSON") " payloads. JSON says \"here is some data, figure out what to do with it.\" An s-expression says \"here is what to do, and here is the data to do it with.\" The component definition and the data it operates on travel together.")
(p :class "text-stone-600"
"For AI agents in a federated network, this means an agent on one node can send " (em "capabilities") " to another node, not just requests. A component that renders a specific visualization. A macro that transforms data into a particular format. A function that implements a protocol. The network becomes a shared computational substrate where intelligence is distributed as executable expressions."))
(~doc-section :title "Programs writing programs writing programs" :id "meta-programs"
(p :class "text-stone-600"
"A macro is a function that takes code and returns code. An AI generating macros is writing programs that write programs. With " (code "eval") ", those generated programs can generate more programs at runtime. This is not a metaphor — it is the literal mechanism.")
(p :class "text-stone-600"
"The " (a :href "/essays/godel-escher-bach" :class "text-violet-600 hover:underline" "Gödel numbering") " parallel is not incidental. " (a :href "https://en.wikipedia.org/wiki/Kurt_G%C3%B6del" :class "text-violet-600 hover:underline" "Gödel") " showed that any sufficiently powerful formal system can encode statements about itself. A complete Lisp on the wire is a sufficiently powerful formal system. The web can make statements about itself — components that inspect other components, macros that rewrite the page structure, expressions that generate new expressions based on the current state of the system.")
(p :class "text-stone-600"
"Consider what this enables for AI:")
(ul :class "space-y-2 text-stone-600 mt-2"
(li (strong "Self-improving interfaces.") " An AI observes how users interact with a component (click patterns, error rates, abandonment). It reads the component definition — because it is data. It modifies the definition — because data is code. It evaluates the result. The interface adapts without human intervention.")
(li (strong "Generative composition.") " Given a data schema and a design intent, an AI generates not just a component but the " (em "macros") " that generate families of components. The macro is a template for templates. The output scales combinatorially.")
(li (strong "Cross-system reasoning.") " An AI reads component definitions from multiple federated nodes, identifies common patterns, and synthesizes abstractions that work across all of them. The shared language makes cross-system analysis trivial — it is all s-expressions.")))
(~doc-section :title "The sandbox is everything" :id "sandbox"
(p :class "text-stone-600"
"The same " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " that makes this powerful makes it dangerous. Code-as-data means an AI can inject " (em "behaviour") ", not just content. A malicious expression evaluated in the wrong context could exfiltrate data, modify other components, or disrupt the system.")
(p :class "text-stone-600"
"This is why the " (a :href "/specs/primitives" :class "text-violet-600 hover:underline" "primitive set") " is the critical security boundary. The spec defines exactly which operations are available. A sandboxed evaluator that only exposes pure primitives (arithmetic, string operations, list manipulation) cannot perform I/O. Cannot access the network. Cannot modify the DOM outside its designated target. The language is " (a :href "https://en.wikipedia.org/wiki/Turing_completeness" :class "text-violet-600 hover:underline" "Turing-complete") " within the sandbox and powerless outside it.")
(p :class "text-stone-600"
"Different contexts grant different primitive sets. A component evaluated in a page slot gets rendering primitives. A macro gets code-transformation primitives. A federated expression from an untrusted node gets the minimal safe set. The sandbox is not bolted on — it is inherent in the language's architecture. What you can do depends on which primitives are in scope.")
(p :class "text-stone-600"
"This matters enormously for AI. An AI agent that can modify the running system must be constrained by the same sandbox mechanism that constrains any other expression. The security model does not distinguish between human-authored code and AI-generated code — both are s-expressions, both are evaluated by the same evaluator, both are subject to the same primitive restrictions."))
(~doc-section :title "Not self-aware — reflexive" :id "reflexive"
(p :class "text-stone-600"
"Is this a \"self-aware web\"? Probably not in the " (a :href "https://en.wikipedia.org/wiki/Consciousness" :class "text-violet-600 hover:underline" "consciousness") " sense. But the word we keep reaching for has a precise meaning: " (a :href "https://en.wikipedia.org/wiki/Reflexivity_(social_theory)" :class "text-violet-600 hover:underline" "reflexivity") ". A reflexive system can represent itself, reason about its own structure, and modify itself based on that reasoning.")
(p :class "text-stone-600"
"A " (a :href "https://en.wikipedia.org/wiki/React_(software)" :class "text-violet-600 hover:underline" "React") " app cannot read its own component tree as data and rewrite it. An HTML page cannot inspect its own structure and generate new pages. A JSON API cannot describe its own semantics in a way that is both human-readable and machine-executable.")
(p :class "text-stone-600"
"SX can do all of these things — because there is no distinction between the program and its representation. The source code, the wire format, the runtime state, and the data model are all the same thing: " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "s-expressions") ".")
(p :class "text-stone-600"
"What AI adds to this is not awareness but " (em "agency") ". The system has always been reflexive — Lisp has been reflexive for seven decades. What is new is having an agent that can exploit that reflexivity at scale: reading the entire system state as data, reasoning about it, generating modifications, and evaluating the results — all in the native language of the system itself."))
(~doc-section :title "The Lisp that escaped the REPL" :id "escaped-repl"
(p :class "text-stone-600"
(a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " has been reflexive since " (a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" :class "text-violet-600 hover:underline" "McCarthy") ". What kept it contained was the boundary of the " (a :href "https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop" :class "text-violet-600 hover:underline" "REPL") " — a single process, a single machine, a single user. The s-expressions lived inside Emacs, inside a Clojure JVM, inside a Scheme interpreter. They did not travel.")
(p :class "text-stone-600"
"SX puts s-expressions on the wire. Between server and client. Between federated nodes. Between human and AI. The reflexive property escapes the process boundary and becomes a property of the " (em "network") ".")
(p :class "text-stone-600"
"A network of nodes that share a reflexive language is a qualitatively different system from a network of nodes that exchange inert data. The former can reason about itself, modify itself, and evolve. The latter can only shuttle payloads.")
(p :class "text-stone-600"
"Whether this constitutes anything approaching awareness is a philosophical question. What is not philosophical is the engineering consequence: a web built on s-expressions is a web that AI can participate in as a " (em "native citizen") ", not as a tool bolted onto the side. The language is the interface. The interface is the language. And the language can describe itself."))
(~doc-section :title "What this opens up" :id "possibilities"
(p :class "text-stone-600"
"Concretely:")
(ul :class "space-y-3 text-stone-600 mt-2"
(li (strong "AI-native development environments.") " The IDE is a web page. The web page is s-expressions. The AI reads and writes s-expressions. There is no translation layer between what the AI thinks and what the system executes. " (a :href "https://en.wikipedia.org/wiki/Pair_programming" :class "text-violet-600 hover:underline" "Pair programming") " with an AI becomes pair evaluation.")
(li (strong "Adaptive interfaces.") " Components that observe their own usage patterns and propose modifications. The AI reads the component (data), the interaction logs (data), and generates a modified component (data). Human approves or rejects. The loop is native to the system.")
(li (strong "Semantic federation.") " Nodes exchange not just content but " (em "understanding") ". A component definition carries its own semantics. An AI on a receiving node can reason about what a foreign component does without documentation, because the definition is self-describing.")
(li (strong "Emergent protocols.") " Two AI agents on different nodes, speaking SX, can negotiate new interaction patterns by exchanging macros. The protocol is not predefined — it emerges from the conversation between agents, expressed in the shared language.")
(li (strong "Composable trust.") " The sandbox mechanism means you can give an AI agent " (em "exactly") " the capabilities it needs — no more. Trust is expressed as a set of available primitives, not as an all-or-nothing API key."))
(p :class "text-stone-600"
"None of these require breakthroughs in AI. They require a web that speaks a reflexive language. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " solved the language problem in 1958. SX solves the distribution problem. AI provides the agency. The three together produce something that none of them achieves alone: a web that can reason about itself."))))
(defcomp ~essay-server-architecture ()
(~doc-page :title "Server Architecture"
(p :class "text-stone-500 text-sm italic mb-8"
"How SX enforces the boundary between host language and embedded language, why that boundary matters, and what it looks like across different target languages.")
(~doc-section :title "The island constraint" :id "island"
(p :class "text-stone-600"
"SX is an embedded language. It runs inside a host language — for example Python on the server, JavaScript in the browser. The central architectural constraint is that SX is a " (strong "pure island") ": the evaluator sees values in and values out. No host objects leak into the SX environment. No SX expressions reach into host internals. Every interaction between SX and the host passes through a declared, validated boundary.")
(p :class "text-stone-600"
"This is not a performance optimization or a convenience. It is the property that makes self-hosting possible. If host objects can leak into SX environments, then the spec files depend on host-specific types. If SX expressions can call host functions directly, the evaluator's behavior varies per host. Neither of those is compatible with a single specification that bootstraps to multiple targets.")
(p :class "text-stone-600"
"The constraint: " (strong "nothing crosses the boundary unless it is declared in a spec file and its type is one of the boundary types") "."))
(~doc-section :title "Three tiers" :id "tiers"
(p :class "text-stone-600"
"Host functions that SX can call are organized into three tiers, each with different trust levels and declaration requirements:")
(div :class "space-y-4"
(div :class "border rounded-lg p-4 border-stone-200"
(h3 :class "font-semibold text-stone-800 mb-2" "Tier 1: Pure primitives")
(p :class "text-stone-600 text-sm"
"Declared in " (code :class "text-violet-700 text-sm" "primitives.sx") ". About 80 functions — arithmetic, string operations, list operations, dict operations, type predicates. All pure: values in, values out, no side effects. These are the only host functions visible to the spec itself. Every bootstrapper must implement all of them."))
(div :class "border rounded-lg p-4 border-stone-200"
(h3 :class "font-semibold text-stone-800 mb-2" "Tier 2: I/O primitives")
(p :class "text-stone-600 text-sm"
"Declared in " (code :class "text-violet-700 text-sm" "boundary.sx") ". Cross-service queries, fragment fetching, request context access, URL generation. Async and side-effectful. They need host context (HTTP request, database connection, config). The SX resolver identifies these in the render tree, gathers them, executes them in parallel, and substitutes results back in."))
(div :class "border rounded-lg p-4 border-stone-200"
(h3 :class "font-semibold text-stone-800 mb-2" "Tier 3: Page helpers")
(p :class "text-stone-600 text-sm"
"Also declared in " (code :class "text-violet-700 text-sm" "boundary.sx") ". Service-scoped Python functions registered via " (code :class "text-violet-700 text-sm" "register_page_helpers()") ". They provide data for specific page types — syntax highlighting, reference table data, bootstrapper output. Each helper is bound to a specific service and available only in that service's page evaluation environment."))))
(~doc-section :title "Boundary types" :id "types"
(p :class "text-stone-600"
"Only these types may cross the host-SX boundary:")
(~doc-code :code (highlight "(define-boundary-types\n (list \"number\" \"string\" \"boolean\" \"nil\" \"keyword\"\n \"list\" \"dict\" \"sx-source\" \"style-value\"))" "lisp"))
(p :class "text-stone-600"
"No Python " (code :class "text-violet-700 text-sm" "datetime") " objects. No ORM models. No Quart request objects. If a host function returns a " (code :class "text-violet-700 text-sm" "datetime") ", it must convert to an ISO string before crossing. If it returns a database row, it must convert to a plain dict. The boundary validation checks this recursively — lists and dicts have their elements checked too.")
(p :class "text-stone-600"
"The " (code :class "text-violet-700 text-sm" "sx-source") " type is SX source text wrapped in an " (code :class "text-violet-700 text-sm" "SxExpr") " marker. It allows the host to pass pre-rendered SX markup into the tree — but only the host can create it. SX code cannot construct SxExpr values; it can only receive them from the boundary."))
(~doc-section :title "Enforcement" :id "enforcement"
(p :class "text-stone-600"
"The boundary contract is enforced at three points, each corresponding to a tier:")
(div :class "space-y-3"
(div :class "bg-stone-100 rounded p-4"
(p :class "text-sm text-stone-700"
(strong "Primitive registration. ") "When " (code :class "text-violet-700 text-sm" "@register_primitive") " decorates a function, it calls " (code :class "text-violet-700 text-sm" "validate_primitive(name)") ". If the name is not declared in " (code :class "text-violet-700 text-sm" "primitives.sx") ", the service fails to start."))
(div :class "bg-stone-100 rounded p-4"
(p :class "text-sm text-stone-700"
(strong "I/O handler registration. ") "When " (code :class "text-violet-700 text-sm" "primitives_io.py") " builds the " (code :class "text-violet-700 text-sm" "_IO_HANDLERS") " dict, each name is validated against " (code :class "text-violet-700 text-sm" "boundary.sx") ". Undeclared I/O primitives crash the import."))
(div :class "bg-stone-100 rounded p-4"
(p :class "text-sm text-stone-700"
(strong "Page helper registration. ") "When a service calls " (code :class "text-violet-700 text-sm" "register_page_helpers(service, helpers)") ", each helper name is validated against " (code :class "text-violet-700 text-sm" "boundary.sx") " for that service. Undeclared helpers fail. Return values are wrapped to pass through " (code :class "text-violet-700 text-sm" "validate_boundary_value()") ".")))
(p :class "text-stone-600"
"All three checks are controlled by the " (code :class "text-violet-700 text-sm" "SX_BOUNDARY_STRICT") " environment variable. With " (code :class "text-violet-700 text-sm" "\"1\"") " (the production default), violations crash at startup. Without it, they log warnings. The strict mode is set in both " (code :class "text-violet-700 text-sm" "docker-compose.yml") " and " (code :class "text-violet-700 text-sm" "docker-compose.dev.yml") "."))
(~doc-section :title "The SX-in-Python rule" :id "sx-in-python"
(p :class "text-stone-600"
"One enforcement that is not automated but equally important: " (strong "SX source code must not be constructed as Python strings") ". S-expressions belong in " (code :class "text-violet-700 text-sm" ".sx") " files. Python belongs in " (code :class "text-violet-700 text-sm" ".py") " files. If you see a Python f-string that builds " (code :class "text-violet-700 text-sm" "(div :class ...)") ", that is a boundary violation.")
(p :class "text-stone-600"
"The correct pattern: Python returns " (strong "data") " (dicts, lists, strings). " (code :class "text-violet-700 text-sm" ".sx") " files receive data via keyword args and compose the markup. The only exception is " (code :class "text-violet-700 text-sm" "SxExpr") " wrappers for pre-rendered fragments — and those should be built with " (code :class "text-violet-700 text-sm" "sx_call()") " or " (code :class "text-violet-700 text-sm" "_sx_fragment()") ", never with f-strings.")
(~doc-code :code (highlight ";; CORRECT: .sx file composes markup from data\n(defcomp ~my-page (&key items)\n (div :class \"space-y-4\"\n (map (fn (item)\n (div :class \"border rounded p-3\"\n (h3 (get item \"title\"))\n (p (get item \"desc\"))))\n items)))" "lisp"))
(~doc-code :code (highlight "# CORRECT: Python returns data\ndef _my_page_data():\n return {\"items\": [{\"title\": \"A\", \"desc\": \"B\"}]}\n\n# WRONG: Python builds SX source\ndef _my_page_data():\n return SxExpr(f'(div (h3 \"{title}\"))') # NO" "python")))
(~doc-section :title "Why this matters for multiple languages" :id "languages"
(p :class "text-stone-600"
"The boundary contract is target-agnostic. " (code :class "text-violet-700 text-sm" "boundary.sx") " and " (code :class "text-violet-700 text-sm" "primitives.sx") " declare what crosses the boundary. Each bootstrapper reads those declarations and emits the strongest enforcement the target language supports:")
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
(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" "Enforcement")
(th :class "px-3 py-2 font-medium text-stone-600" "Mechanism")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Python")
(td :class "px-3 py-2 text-stone-600" "Runtime")
(td :class "px-3 py-2 text-stone-500 text-sm" "Frozen sets + validation at registration"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "JavaScript")
(td :class "px-3 py-2 text-stone-600" "Runtime")
(td :class "px-3 py-2 text-stone-500 text-sm" "Set guards on registerPrimitive()"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Rust")
(td :class "px-3 py-2 text-stone-600" "Compile-time")
(td :class "px-3 py-2 text-stone-500 text-sm" "SxValue enum, trait bounds on primitive fns"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Haskell")
(td :class "px-3 py-2 text-stone-600" "Compile-time")
(td :class "px-3 py-2 text-stone-500 text-sm" "ADT + typeclass constraints"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "TypeScript")
(td :class "px-3 py-2 text-stone-600" "Compile-time")
(td :class "px-3 py-2 text-stone-500 text-sm" "Discriminated union types")))))
(p :class "text-stone-600"
"In Python and JavaScript, boundary violations are caught at startup. In Rust, Haskell, or TypeScript, they would be caught at compile time — a function that returns a non-boundary type simply would not type-check. The spec is the same; the enforcement level rises with the type system.")
(p :class "text-stone-600"
"This is the payoff of the pure island constraint. Because SX never touches host internals, a bootstrapper for a new target only needs to implement the declared primitives and boundary functions. The evaluator, renderer, parser, and all components work unchanged. One spec, every target, same guarantees."))
(~doc-section :title "The spec as contract" :id "contract"
(p :class "text-stone-600"
"The boundary enforcement files form a closed contract:")
(ul :class "space-y-2 text-stone-600"
(li (code :class "text-violet-700 text-sm" "primitives.sx") " — declares every pure function SX can call")
(li (code :class "text-violet-700 text-sm" "boundary.sx") " — declares every I/O function and page helper")
(li (code :class "text-violet-700 text-sm" "boundary_parser.py") " — reads both files, extracts declared names")
(li (code :class "text-violet-700 text-sm" "boundary.py") " — runtime validation (validate_primitive, validate_io, validate_helper, validate_boundary_value)"))
(p :class "text-stone-600"
"If you add a new host function and forget to declare it, the service will not start. If you return a disallowed type, the validation will catch it. The spec files are not documentation — they are the mechanism. The bootstrappers read them. The validators parse them. The contract is enforced by the same files that describe it.")
(p :class "text-stone-600"
"This closes the loop on self-hosting. The SX spec defines the language. The boundary spec defines the edge. The bootstrappers generate implementations from both. And the generated code validates itself against the spec at startup. The spec is the implementation is the contract is the spec."))))
(defcomp ~essay-separation-of-concerns ()
(~doc-page :title "Separation of Concerns"
(p :class "text-stone-500 text-sm italic mb-8"
"The web's canonical separation — HTML, CSS, JavaScript — separates the framework's concerns, not yours. Real separation of concerns is domain-specific and cannot be prescribed by a platform.")
(~doc-section :title "The orthodoxy" :id "orthodoxy"
(p :class "text-stone-600"
"Web development has an article of faith: separate your concerns. Put structure in HTML. Put presentation in CSS. Put behavior in JavaScript. Three languages, three files, three concerns. This is presented as a universal engineering principle — the web platform's gift to good architecture.")
(p :class "text-stone-600"
"It is nothing of the sort. It is the " (em "framework's") " separation of concerns, not the " (em "application's") ". The web platform needs an HTML parser, a CSS engine, and a JavaScript runtime. These are implementation boundaries internal to the browser. Elevating them to an architectural principle for application developers is like telling a novelist to keep their nouns in one file, verbs in another, and adjectives in a third — because that's how the compiler organises its grammar."))
(~doc-section :title "What is a concern?" :id "what-is-a-concern"
(p :class "text-stone-600"
"A concern is a cohesive unit of functionality that can change independently. In a shopping application, concerns might be: the product card, the cart, the checkout flow, the search bar. Each of these has structure, style, and behavior that change together. When you redesign the product card, you change its markup, its CSS, and its click handlers — simultaneously, for the same reason, in response to the same requirement.")
(p :class "text-stone-600"
"The traditional web separation scatters this single concern across three files. The product card's markup is in " (code :class "text-violet-700" "products.html") ", tangled with every other page element. Its styles are in " (code :class "text-violet-700" "styles.css") ", mixed with hundreds of unrelated rules. Its behavior is in " (code :class "text-violet-700" "app.js") ", coupled to every other handler by shared scope. To change the product card, you edit three files, grep for the right selectors, hope nothing else depends on the same class names, and pray.")
(p :class "text-stone-600"
"This is not separation of concerns. It is " (strong "commingling") " of concerns, organized by language rather than by meaning."))
(~doc-section :title "The framework's concerns are not yours" :id "framework-concerns"
(p :class "text-stone-600"
"The browser has good reasons to separate HTML, CSS, and JavaScript. The HTML parser builds a DOM tree. The CSS engine resolves styles and computes layout. The JS runtime manages execution contexts, event loops, and garbage collection. These are distinct subsystems with distinct performance characteristics, security models, and parsing strategies.")
(p :class "text-stone-600"
"But you are not building a browser. You are building an application. Your concerns are: what does a product card look like? What happens when a user clicks 'add to cart'? How does the search filter update the results? These questions cut across markup, style, and behavior. They are not aligned with the browser's internal module boundaries.")
(p :class "text-stone-600"
"When a framework tells you to separate by technology — HTML here, CSS there, JS over there — it is asking you to organize your application around " (em "its") " architecture, not around your problem domain. You are serving the framework's interests. The framework is not serving yours."))
(~doc-section :title "React understood the problem" :id "react"
(p :class "text-stone-600"
"React's most radical insight was not the virtual DOM or one-way data flow. It was the assertion that a component — markup, style, behavior, all co-located — is the right unit of abstraction for UI. JSX was controversial precisely because it violated the orthodoxy. You are putting HTML in your JavaScript! The concerns are not separated!")
(p :class "text-stone-600"
"But the concerns " (em "were") " separated — by component, not by language. A " (code :class "text-violet-700" "<ProductCard>") " contains everything about product cards. A " (code :class "text-violet-700" "<SearchBar>") " contains everything about search bars. Changing one component does not require changes to another. That is separation of concerns — real separation, based on what changes together.")
(p :class "text-stone-600"
"CSS-in-JS libraries followed the same logic. If styles belong to a component, they should live with that component. Not in a global stylesheet where any selector can collide with any other. The backlash — \"you're mixing concerns!\" — betrayed a fundamental confusion between " (em "technologies") " and " (em "concerns") "."))
(~doc-section :title "Separation of concerns is domain-specific" :id "domain-specific"
(p :class "text-stone-600"
"Here is the key point: " (strong "no framework can tell you what your concerns are") ". Concerns are determined by your domain, your requirements, and your rate of change. A medical records system has different concerns from a social media feed. An e-commerce checkout has different concerns from a real-time dashboard. The boundaries between concerns are discovered through building the application, not prescribed in advance by a platform specification.")
(p :class "text-stone-600"
"A framework that imposes a fixed separation — this file for structure, that file for style — is claiming universal knowledge of every possible application domain. That claim is obviously false. Yet it has shaped twenty-five years of web development tooling, project structures, and hiring practices.")
(p :class "text-stone-600"
"The right question is never \"are your HTML, CSS, and JS in separate files?\" The right question is: \"when a requirement changes, how many files do you touch, and how many of those changes are unrelated to each other?\" If you touch three files and all three changes serve the same requirement, your concerns are not separated — they are scattered."))
(~doc-section :title "What SX does differently" :id "sx-approach"
(p :class "text-stone-600"
"An SX component is a single expression that contains its structure, its style (as keyword-resolved CSS classes), and its behavior (event bindings, conditionals, data flow). Nothing is in a separate file unless it genuinely represents a separate concern.")
(~doc-code :code "(defcomp ~product-card (&key product on-add)
(div :class \"rounded-lg border border-stone-200 p-4 hover:shadow-md transition-shadow\"
(img :src (get product \"image\") :alt (get product \"name\")
:class \"w-full h-48 object-cover rounded\")
(h3 :class \"mt-2 font-semibold text-stone-800\"
(get product \"name\"))
(p :class \"text-stone-500 text-sm\"
(get product \"description\"))
(div :class \"mt-3 flex items-center justify-between\"
(span :class \"text-lg font-bold\"
(format-price (get product \"price\")))
(button :class \"px-3 py-1 bg-violet-500 text-white rounded hover:bg-violet-600\"
:sx-post (str \"/cart/add/\" (get product \"id\"))
:sx-target \"#cart-count\"
\"Add to cart\"))))")
(p :class "text-stone-600"
"Structure, style, and behavior are co-located because they represent " (em "one concern") ": the product card. The component can be moved, renamed, reused, or deleted as a unit. Changing its appearance does not require editing a global stylesheet. Changing its click behavior does not require searching through a shared script file.")
(p :class "text-stone-600"
"This is not a rejection of separation of concerns. It is separation of concerns taken seriously — by the domain, not by the framework."))
(~doc-section :title "When real separation matters" :id "real-separation"
(p :class "text-stone-600"
"Genuine separation of concerns still applies, but at the right boundaries:")
(ul :class "space-y-2 text-stone-600"
(li (strong "Components from each other") " — a product card should not know about the checkout flow. They interact through props and events, not shared mutable state.")
(li (strong "Data from presentation") " — the product data comes from a service or API, not from hardcoded markup. The component receives data; it does not fetch or own it.")
(li (strong "Platform from application") " — SX's boundary spec separates host primitives from application logic. The evaluator does not know about HTTP. Page helpers do not know about the AST.")
(li (strong "Content from chrome") " — layout components (nav, footer, sidebar) are separate from content components (articles, product listings, forms). They compose, they do not intermingle."))
(p :class "text-stone-600"
"These boundaries emerge from the application's actual structure. They happen to cut across HTML, CSS, and JavaScript freely — because those categories were never meaningful to begin with."))
(~doc-section :title "The cost of the wrong separation" :id "cost"
(p :class "text-stone-600"
"The HTML/CSS/JS separation has real costs that have been absorbed so thoroughly they are invisible:")
(ul :class "space-y-2 text-stone-600"
(li (strong "Selector coupling") " — CSS selectors create implicit dependencies between stylesheets and markup. Rename a class in HTML, forget to update the CSS, and styles silently break. No compiler error. No runtime error. Just a broken layout discovered in production.")
(li (strong "Global namespace collision") " — every CSS rule lives in a global namespace. BEM, SMACSS, CSS Modules, scoped styles — these are all workarounds for a problem that only exists because styles were separated from the things they style.")
(li (strong "Shotgun surgery") " — a single feature change requires coordinated edits across HTML, CSS, and JS files. Miss one, and the feature is half-implemented. The change has a blast radius proportional to the number of technology layers, not the number of domain concerns.")
(li (strong "Dead code accumulation") " — CSS rules outlive the markup they were written for. Nobody deletes old styles because nobody can be sure what else depends on them. Stylesheets grow monotonically. Refactoring is archaeology."))
(p :class "text-stone-600"
"Every one of these problems vanishes when style, structure, and behavior are co-located in a component. Delete the component, and its styles, markup, and handlers are gone. No orphans. No archaeology."))
(~doc-section :title "The principle, stated plainly" :id "principle"
(p :class "text-stone-600"
"Separation of concerns is a domain-specific design decision. It cannot be imposed by a framework. The web platform's HTML/CSS/JS split is an implementation detail of the browser, not an architectural principle for applications. Treating it as one has cost the industry decades of unnecessary complexity, tooling, and convention.")
(p :class "text-stone-600"
"Separate the things that change for different reasons. Co-locate the things that change together. That is the entire principle. It says nothing about file extensions."))))
(defcomp ~essay-sx-and-ai ()
(~doc-page :title "SX and AI"
(p :class "text-stone-500 text-sm italic mb-8"
"Why s-expressions are the most AI-friendly representation for web interfaces — and what that means for how software gets built.")
(~doc-section :title "The syntax tax" :id "syntax-tax"
(p :class "text-stone-600"
"Every programming language imposes a syntax tax on AI code generation. The model must produce output that satisfies a grammar — matching braces, semicolons in the right places, operator precedence, indentation rules, closing tags that match opening tags. The more complex the grammar, the more tokens the model wastes on syntactic bookkeeping instead of semantic intent.")
(p :class "text-stone-600"
"Consider what an AI must get right to produce a valid React component: JSX tags that open and close correctly, curly braces for JavaScript expressions inside markup, import statements with correct paths, semicolons or ASI rules, TypeScript type annotations, CSS-in-JS string literals with different quoting rules than the surrounding code. Each syntactic concern is a potential failure point. Each failure produces something that does not parse, let alone run.")
(p :class "text-stone-600"
"S-expressions have one syntactic form: " (code "(head args...)") ". Parentheses open and close. Strings are quoted. That is the entire grammar. There is no operator precedence because there are no operators. There is no indentation sensitivity because whitespace is not significant. There are no closing tags because there are no tags — just lists.")
(p :class "text-stone-600"
"The syntax tax for SX is essentially zero. An AI that can count parentheses can produce syntactically valid SX. This is not a small advantage — it is a categorical one. The model spends its capacity on " (em "what") " to generate, not " (em "how") " to format it."))
(~doc-section :title "One representation for everything" :id "one-representation"
(p :class "text-stone-600"
"A typical web project requires the AI to context-switch between HTML (angle brackets, void elements, boolean attributes), CSS (selectors, properties, at-rules, a completely different syntax from HTML), JavaScript (statements, expressions, classes, closures, async/await), and whatever templating language glues them together (Jinja delimiters, ERB tags, JSX interpolation). Each is a separate grammar. Each has edge cases. Each interacts with the others in ways that are hard to predict.")
(p :class "text-stone-600"
"In SX, structure, style, logic, and data are all s-expressions:")
(~doc-code :code (highlight ";; Structure\n(div :class \"card\" (h2 title) (p body))\n\n;; Style\n(cssx card-style\n :bg white :rounded-lg :shadow-md :p 6)\n\n;; Logic\n(if (> (length items) 0)\n (map render-item items)\n (p \"No items found.\"))\n\n;; Data\n{:name \"Alice\" :role \"admin\" :active true}\n\n;; Component definition\n(defcomp ~user-card (&key user)\n (div :class \"card\"\n (h2 (get user \"name\"))\n (span :class \"badge\" (get user \"role\"))))" "lisp"))
(p :class "text-stone-600"
"The AI learns one syntax and applies it everywhere. The mental model does not fragment across subsystems. A " (code "div") " and an " (code "if") " and a " (code "defcomp") " are all lists. The model that generates one can generate all three, because they are the same thing."))
(~doc-section :title "The spec fits in a context window" :id "spec-fits"
(p :class "text-stone-600"
"The complete SX language specification — evaluator, parser, renderer, primitives — lives in four files totalling roughly 3,000 lines. An AI model with a 200k token context window can hold the " (em "entire language definition") " alongside the user's codebase and still have room to work. Compare this to JavaScript (the " (a :href "https://ecma-international.org/publications-and-standards/standards/ecma-262/" :class "text-violet-600 hover:underline" "ECMAScript specification") " is 900+ pages), or the combined specifications for HTML, CSS, and the DOM.")
(p :class "text-stone-600"
"This is not just a convenience — it changes what kind of code the AI produces. When the model has the full spec in context, it does not hallucinate nonexistent features. It does not confuse one version's semantics with another's. It knows exactly which primitives exist, which special forms are available, and how evaluation works — because it is reading the authoritative definition, not interpolating from training data.")
(p :class "text-stone-600"
"The spec is also written in SX. " (code "eval.sx") " defines the evaluator as s-expressions. " (code "parser.sx") " defines the parser as s-expressions. The language the AI is generating is the same language the spec is written in. There is no translation gap between \"understanding the language\" and \"using the language\" — they are the same act of reading s-expressions."))
(~doc-section :title "Structural validation is trivial" :id "structural-validation"
(p :class "text-stone-600"
"Validating AI output before executing it is a critical safety concern. With conventional languages, validation means running a full parser, type checker, and linter — each with their own error recovery modes and edge cases. With SX, structural validation is: " (em "do the parentheses balance?") " That is it. If they balance, the expression parses. If it parses, it can be evaluated.")
(p :class "text-stone-600"
"This makes it trivial to build AI pipelines that generate SX. Parse the output. If it parses, evaluate it in a sandbox. If it does not parse, the error is always the same kind — unmatched parentheses — and the fix is always mechanical. There is no \"your JSX is invalid because you used " (code "class") " instead of " (code "className") "\" or \"you forgot the semicolon after the type annotation but before the generic constraint.\"")
(p :class "text-stone-600"
"Beyond parsing, the SX " (a :href "/specs/primitives" :class "text-violet-600 hover:underline" "boundary system") " provides semantic validation. A pure component cannot call IO primitives — not by convention, but by the evaluator refusing to resolve them. An AI generating a component can produce whatever expressions it wants; the sandbox ensures only permitted operations execute. Validation is not a separate step bolted onto the pipeline. It is the language."))
(~doc-section :title "Components are self-documenting" :id "self-documenting"
(p :class "text-stone-600"
"A React component's interface is spread across prop types (or TypeScript interfaces), JSDoc comments, Storybook stories, and whatever documentation someone wrote. An AI reading a component must synthesize information from multiple sources to understand what it accepts and what it produces.")
(p :class "text-stone-600"
"An SX component declares everything in one expression:")
(~doc-code :code (highlight "(defcomp ~product-card (&key title price image &rest children)\n (div :class \"rounded border p-4\"\n (img :src image :alt title)\n (h3 :class \"font-bold\" title)\n (span :class \"text-lg\" (format-price price))\n children))" "lisp"))
(p :class "text-stone-600"
"The AI reads this and knows: it takes " (code "title") ", " (code "price") ", and " (code "image") " as keyword arguments, and " (code "children") " as rest arguments. It knows the output structure — a " (code "div") " with an image, heading, price, and slot for children. It knows this because the definition " (em "is") " the documentation. There is no separate spec to consult, no type file to find, no ambiguity about which props are required.")
(p :class "text-stone-600"
"This self-describing property scales across the entire component environment. An AI can " (code "(map ...)") " over every component in the registry, extract all parameter signatures, build a complete map of the UI vocabulary — and generate compositions that use it correctly, because the interface is declared in the same language the AI is generating."))
(~doc-section :title "Token efficiency" :id "token-efficiency"
(p :class "text-stone-600"
"LLMs operate on tokens. Every token costs compute, latency, and money. The information density of a representation — how much semantics per token — directly affects how much an AI can see, generate, and reason about within its context window and output budget.")
(p :class "text-stone-600"
"Compare equivalent UI definitions:")
(~doc-code :code (highlight ";; SX: 42 tokens\n(div :class \"card p-4\"\n (h2 :class \"font-bold\" title)\n (p body)\n (when footer\n (div :class \"mt-4 border-t pt-2\" footer)))" "lisp"))
(~doc-code :code (highlight "// React/JSX: ~75 tokens\n<div className=\"card p-4\">\n <h2 className=\"font-bold\">{title}</h2>\n <p>{body}</p>\n {footer && (\n <div className=\"mt-4 border-t pt-2\">{footer}</div>\n )}\n</div>" "python"))
(p :class "text-stone-600"
"The SX version is roughly 40% fewer tokens for equivalent semantics. No closing tags. No curly-brace interpolation. No " (code "className") " vs " (code "class") " distinction. Every token carries meaning. Over an entire application — dozens of components, hundreds of expressions — this compounds into significantly more code visible per context window and significantly less output the model must generate."))
(~doc-section :title "Composability is free" :id "composability"
(p :class "text-stone-600"
"The hardest thing for AI to get right in conventional frameworks is composition — how pieces fit together. React has rules about hooks. Vue has template vs script vs style sections. Angular has modules, declarations, and dependency injection. Each framework's composition model is a set of conventions the AI must learn and apply correctly.")
(p :class "text-stone-600"
"S-expressions compose by nesting. A list inside a list is a composition. There are no rules beyond this:")
(~doc-code :code (highlight ";; Compose components by nesting — that's it\n(~page-layout :title \"Dashboard\"\n (~sidebar\n (~nav-menu :items menu-items))\n (~main-content\n (map ~user-card users)\n (~pagination :page current-page :total total-pages)))" "lisp"))
(p :class "text-stone-600"
"No imports to manage. No registration steps. No render props, higher-order components, or composition APIs. The AI generates a nested structure and it works, because nesting is the only composition mechanism. This eliminates an entire class of errors that plague AI-generated code in conventional frameworks — the kind where each piece works in isolation but the assembly is wrong."))
(~doc-section :title "The feedback loop" :id "feedback-loop"
(p :class "text-stone-600"
"SX has no build step. Generated s-expressions can be evaluated immediately — in the browser, on the server, in a test harness. The AI generates an expression, the system evaluates it, the result is visible. If it is wrong, the AI reads the result (also an s-expression), adjusts, and regenerates. The loop is:")
(ol :class "list-decimal pl-5 text-stone-600 space-y-1 mt-2"
(li "AI generates SX expression")
(li "System parses (parentheses balance? done)")
(li "Evaluator runs in sandbox (boundary-enforced)")
(li "Result rendered or error returned (as s-expression)")
(li "AI reads result, iterates"))
(p :class "text-stone-600"
"Compare this to the conventional loop: AI generates code → linter runs → TypeScript compiles → bundler builds → browser loads → error appears in DevTools console → human copies error back to AI → AI regenerates. Each step is a different tool with different output formats. Each introduces latency and potential information loss.")
(p :class "text-stone-600"
"The SX loop is also " (em "uniform") ". The input is s-expressions. The output is s-expressions. The error messages are s-expressions. The AI never needs to parse a stack trace format or extract meaning from a webpack error. Everything is the same data structure, all the way down."))
(~doc-section :title "This site is the proof" :id "proof"
(p :class "text-stone-600"
"This is not theoretical. Everything you are looking at — every page, every component, every line of this essay — was produced by agentic AI. Not \"AI-assisted\" in the polite sense of autocomplete suggestions. " (em "Produced.") " The SX language specification. The parser. The evaluator. The renderer. The bootstrappers that transpile the spec to JavaScript and Python. The boundary enforcement system. The dependency analyser. The on-demand CSS engine. The client-side router. The component bundler. The syntax highlighter. This documentation site. The Docker deployment. All of it.")
(p :class "text-stone-600"
"The human driving this has never written a line of Lisp. Not Common Lisp. Not Scheme. Not Clojure. Not Emacs Lisp. Has never opened the codebase in VS Code, vi, or any other editor. Every file was created and modified through " (a :href "https://claude.ai/" :class "text-violet-600 hover:underline" "Claude") " running in a terminal — reading files, writing files, running commands, iterating on errors. The development environment is a conversation.")
(p :class "text-stone-600"
"That this works at all is a testament to s-expressions. The AI generates " (code "(defcomp ~card (&key title) (div :class \"p-4\" (h2 title)))") " and it is correct on the first attempt, because there is almost nothing to get wrong. The AI generates a 300-line spec file defining evaluator semantics and every parenthesis balances, because balancing parentheses is the " (em "only") " syntactic constraint. The AI writes a bootstrapper that reads " (code "eval.sx") " and emits JavaScript, and the output runs in the browser, because the source and target are both trees.")
(p :class "text-stone-600"
"Try this with React. Try generating a complete component framework — parser, evaluator, renderer, type system, macro expander, CSS engine, client router — through pure conversation with an AI, never touching an editor. The syntax tax alone would be fatal. JSX irregularities, hook ordering rules, import resolution, TypeScript generics, webpack configuration, CSS module scoping — each is a class of errors that burns tokens and breaks the flow. S-expressions eliminate all of them."))
(~doc-section :title "The development loop" :id "dev-loop"
(p :class "text-stone-600"
"The workflow looks like this: describe what you want. The AI reads the existing code — because it can, because s-expressions are transparent to any reader. It generates new expressions. It writes them to disk. It runs the server. It checks the output. If something breaks, it reads the error, adjusts, and regenerates. The human steers with intent; the AI handles the syntax, the structure, and the mechanical correctness.")
(p :class "text-stone-600"
"This is only possible because the representation is uniform. The AI does not need to switch between \"writing HTML mode\" and \"writing CSS mode\" and \"writing JavaScript mode\" and \"writing deployment config mode.\" It is always writing s-expressions. The cognitive load is constant. The error rate is constant. The speed is constant — regardless of whether it is generating a page layout, a macro expander, or a Docker healthcheck.")
(p :class "text-stone-600"
"The " (a :href "/essays/sx-sucks" :class "text-violet-600 hover:underline" "sx sucks") " essay copped to the AI authorship and framed it as a weakness — microwave dinner on a nice plate. But the framing was wrong. If a language is so well-suited to machine generation that one person with no Lisp experience can build a self-hosting language, a multi-target bootstrapper, a reactive component framework, and a full documentation site through pure agentic AI — that is not a weakness of the language. That is the language working exactly as it should."))
(~doc-section :title "What this changes" :id "what-changes"
(p :class "text-stone-600"
"The question is not whether AI will generate user interfaces. It already does. The question is what representation makes that generation most reliable, most efficient, and most safe. S-expressions — with their zero-syntax-tax grammar, uniform structure, self-describing components, structural validation, and sandboxed evaluation — are a strong answer.")
(p :class "text-stone-600"
"Not because they were designed for AI. " (a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" :class "text-violet-600 hover:underline" "McCarthy") " invented them in 1958, decades before anyone imagined language models. But the properties that make s-expressions elegant for humans — minimalism, uniformity, composability, homoiconicity — turn out to be exactly the properties that make them tractable for machines. The simplest possible syntax is also the most machine-friendly syntax. This is not a coincidence. It is a consequence of what simplicity means.")
(p :class "text-stone-600"
"The era of AI-generated software is not coming. It is here. The question is which representations survive contact with it. The ones with the lowest syntax tax, the most uniform structure, and the tightest feedback loops will win — not because they are trendy, but because they are what the machines can actually produce reliably. S-expressions have been waiting sixty-seven years for a generation mechanism worthy of their simplicity. They finally have one."))))
(defcomp ~essay-no-alternative ()
(~doc-page :title "There Is No Alternative"
(p :class "text-stone-500 text-sm italic mb-8"
"Every attempt to escape s-expressions leads back to s-expressions. This is not an accident.")
(~doc-section :title "The claim" :id "claim"
(p :class "text-stone-600"
"SX uses s-expressions. When people encounter this, the first reaction is usually: " (em "why not use something more modern?") " Fair question. The answer is that there is nothing more modern. There are only things that are more " (em "familiar") " — and familiarity is not the same as fitness.")
(p :class "text-stone-600"
"This essay examines what SX actually needs from its representation, surveys the alternatives, and shows that every candidate either fails to meet the requirements or converges toward s-expressions under a different name. The conclusion is uncomfortable but unavoidable: for what SX does, there is no alternative."))
(~doc-section :title "The requirements" :id "requirements"
(p :class "text-stone-600"
"SX is not just a templating language. It is a language that serves simultaneously as:")
(ul :class "space-y-2 text-stone-600"
(li (strong "Markup") " — structure for HTML pages, components, layouts")
(li (strong "Programming language") " — conditionals, iteration, functions, macros, closures")
(li (strong "Wire format") " — what the server sends to the client over HTTP")
(li (strong "Data notation") " — configuration, page definitions, component registries")
(li (strong "Spec language") " — the SX specification is written in SX")
(li (strong "Metaprogramming substrate") " — macros that read, transform, and generate code"))
(p :class "text-stone-600"
"Any replacement must handle all six roles with a single syntax. Not six syntaxes awkwardly interleaved — one. This constraint alone eliminates most candidates, because most representations were designed for one of these roles and are ill-suited to the others.")
(p :class "text-stone-600"
"Beyond versatility, the representation must be:")
(ul :class "space-y-2 text-stone-600"
(li (strong "Homoiconic") " — code must be data and data must be code, because macros and self-hosting require it")
(li (strong "Parseable in one pass") " — no forward references, no context-dependent grammar, because the wire format must be parseable by a minimal client")
(li (strong "Structurally validatable") " — a syntactically valid expression must be checkable without evaluation, because untrusted code from federated nodes must be validated before execution")
(li (strong "Token-efficient") " — minimal syntactic overhead, because the representation travels over the network and is processed by LLMs with finite context windows")
(li (strong "Composable by nesting") " — no special composition mechanisms, because the same operation (putting a list inside a list) must work for markup, logic, and data")))
(~doc-section :title "The candidates" :id "candidates"
(~doc-subsection :title "XML / HTML"
(p :class "text-stone-600"
"The obvious first thought. XML is a tree. HTML is markup. Why not use angle brackets?")
(p :class "text-stone-600"
"XML fails on homoiconicity. The distinction between elements, attributes, text nodes, processing instructions, CDATA sections, entity references, and namespaces means the representation has multiple structural categories that cannot freely substitute for each other. An attribute is not an element. A text node is not a processing instruction. You cannot take an arbitrary XML fragment and use it as code, because XML has no concept of evaluation — it is a serialization format for trees, not a language.")
(p :class "text-stone-600"
"XML also fails on token efficiency. " (code "<div class=\"card\"><h2>Title</h2></div>") " versus " (code "(div :class \"card\" (h2 \"Title\"))") ". The closing tags carry zero information — they are pure redundancy. Over a full application, this redundancy compounds into significantly more bytes on the wire and significantly more tokens in an LLM context window.")
(p :class "text-stone-600"
"XSLT attempted to make XML a programming language. The result is universally regarded as a cautionary tale. Trying to express conditionals and iteration in a format designed for document markup produces something that is bad at both."))
(~doc-subsection :title "JSON"
(p :class "text-stone-600"
"JSON is data notation. It has objects, arrays, strings, numbers, booleans, and null. It parses in one pass. It validates structurally. It is ubiquitous.")
(p :class "text-stone-600"
"JSON is not homoiconic because it has no concept of evaluation. It is " (em "inert") " data. To make JSON a programming language, you must invent a convention for representing code — and every such convention reinvents s-expressions with worse ergonomics:")
(~doc-code :code (highlight ";; JSON \"code\" (actual example from various JSON-based DSLs)\n{\"if\": [{\">\": [\"$.count\", 0]},\n {\"map\": [\"$.items\", {\"fn\": [\"item\", {\"get\": [\"item\", \"name\"]}]}]},\n {\"literal\": \"No items\"}]}\n\n;; The same thing in s-expressions\n(if (> count 0)\n (map (fn (item) (get item \"name\")) items)\n \"No items\")" "lisp"))
(p :class "text-stone-600"
"The JSON version is an s-expression encoded in JSON's syntax — lists-of-lists with a head element that determines semantics. It has strictly more punctuation (colons, commas, braces, brackets, quotes around keys) and strictly less readability. Every JSON-based DSL that reaches sufficient complexity converges on this pattern and then wishes it had just used s-expressions."))
(~doc-subsection :title "YAML"
(p :class "text-stone-600"
"YAML is the other common data notation. It adds indentation sensitivity, anchors, aliases, multi-line strings, type coercion, and a " (a :href "https://yaml.org/spec/1.2.2/" :class "text-violet-600 hover:underline" "specification") " that is 240 pages long. The spec for SX's parser is 200 lines.")
(p :class "text-stone-600"
"Indentation sensitivity is a direct disqualifier for wire formats. Whitespace must survive serialization, transmission, minification, and reconstruction exactly — a fragility that s-expressions do not have. YAML also fails on structural validation: the " (a :href "https://en.wikipedia.org/wiki/Norway_problem" :class "text-violet-600 hover:underline" "Norway problem") " (" (code "NO") " parsed as boolean " (code "false") ") demonstrates that YAML's type coercion makes structural validation impossible without semantic knowledge of the schema.")
(p :class "text-stone-600"
"YAML is not homoiconic. It has no evaluation model. Like JSON, any attempt to encode logic in YAML produces s-expressions with worse syntax."))
(~doc-subsection :title "JSX / Template literals"
(p :class "text-stone-600"
"JSX is the closest mainstream technology to what SX does — it embeds markup in a programming language. But JSX is not a representation; it is a compile target. " (code "<Card title=\"Hi\">content</Card>") " compiles to " (code "React.createElement(Card, {title: \"Hi\"}, \"content\")") ". The angle-bracket syntax is sugar that does not survive to runtime.")
(p :class "text-stone-600"
"This means JSX cannot be a wire format — the client must have the compiler. It cannot be a spec language — you cannot write a JSX spec in JSX without a build step. It cannot be a data notation — it requires JavaScript evaluation context. JSX handles exactly one of the six roles (markup) and delegates the others to JavaScript, CSS, JSON, and whatever build tool assembles them.")
(p :class "text-stone-600"
"Template literals (tagged templates in JavaScript, Jinja, ERB, etc.) are string interpolation. They embed code in strings or strings in code, depending on which layer you consider primary. Neither direction produces a homoiconic representation. You cannot write a macro that reads a template literal and transforms it as data, because the template literal is a string — opaque, uninspectable, and unstructured."))
(~doc-subsection :title "Tcl"
(p :class "text-stone-600"
"Tcl is the most interesting near-miss. \"Everything is a string\" is a radical simplification. The syntax is minimal: commands are words separated by spaces, braces group without substitution, brackets evaluate. Tcl is effectively homoiconic — code is strings, strings are code, and " (code "eval") " is the universal mechanism.")
(p :class "text-stone-600"
"Where Tcl falls short is structural validation. Because everything is a string, you cannot check that a Tcl program is well-formed without evaluating it. Unmatched braces inside string values are indistinguishable from syntax errors without context. S-expressions have a trivial structural check — balanced parentheses — that requires no evaluation and no context. For sandboxed evaluation of untrusted code (federated expressions from other nodes), this difference is decisive.")
(p :class "text-stone-600"
"Tcl also lacks native tree structure. Lists are flat strings that are parsed on demand. Nested structure exists by convention, not by grammar. This makes composition more fragile than s-expressions, where nesting is the fundamental structural primitive."))
(~doc-subsection :title "Rebol / Red"
(p :class "text-stone-600"
"Rebol is the strongest alternative. It is homoiconic — code is data. It has minimal syntax. It has dialecting — the ability to create domain-specific languages within the language. It is a single representation for code, data, and markup. " (a :href "https://en.wikipedia.org/wiki/Rebol" :class "text-violet-600 hover:underline" "Carl Sassenrath") " designed it explicitly to solve the problems that SX also targets.")
(p :class "text-stone-600"
"Rebol's limitation is practical, not theoretical. The language is obscure. Modern AI models have almost no training data for it — generating reliable Rebol would require extensive fine-tuning or few-shot prompting. There is no ecosystem of libraries, no community producing components, no federation protocol. And Rebol's type system (over 40 built-in datatypes including " (code "url!") ", " (code "email!") ", " (code "money!") ") makes the parser substantially more complex than s-expressions, which have essentially one composite type: the list.")
(p :class "text-stone-600"
"Rebol demonstrates that the design space around s-expressions has room for variation. But the variations add complexity without adding expressiveness — and in the current landscape, complexity kills AI compatibility and adoption equally."))
(~doc-subsection :title "Forth / stack-based"
(p :class "text-stone-600"
"Forth has the most minimal syntax imaginable: words separated by spaces. No parentheses, no brackets, no delimiters. The program is a flat sequence of tokens. This is simpler than s-expressions.")
(p :class "text-stone-600"
"But Forth's simplicity is deceptive. The flat token stream encodes tree structure " (em "implicitly") " via stack effects. Understanding what a Forth program does requires mentally simulating the stack — tracking what each word pushes and pops. This is precisely the kind of implicit state tracking that both humans and AI models struggle with. A nested s-expression makes structure " (em "visible") ". A Forth program hides it.")
(p :class "text-stone-600"
"For markup, this is fatal. " (code "3 1 + 4 * 2 /") " is arithmetic. Now imagine a page layout expressed as stack operations. The nesting that makes " (code "(div (h2 \"Title\") (p \"Body\"))") " self-evident becomes an exercise in mental bookkeeping. UI is trees. Stack languages are not.")))
(~doc-section :title "The convergence" :id "convergence"
(p :class "text-stone-600"
"Every alternative either fails to meet the requirements or reinvents s-expressions:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(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" "Candidate")
(th :class "px-3 py-2 font-medium text-stone-600" "Homoiconic")
(th :class "px-3 py-2 font-medium text-stone-600" "Structural validation")
(th :class "px-3 py-2 font-medium text-stone-600" "Token-efficient")
(th :class "px-3 py-2 font-medium text-stone-600" "Tree-native")
(th :class "px-3 py-2 font-medium text-stone-600" "AI-trainable")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "XML/HTML")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "JSON")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "YAML")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-amber-600" "Moderate")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "JSX")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-red-600" "Needs compiler")
(td :class "px-3 py-2 text-amber-600" "Moderate")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Tcl")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-red-600" "No"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Rebol/Red")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-amber-600" "Complex")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-red-600" "No"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Forth")
(td :class "px-3 py-2 text-amber-600" "Sort of")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-green-700" "Yes")
(td :class "px-3 py-2 text-red-600" "No")
(td :class "px-3 py-2 text-red-600" "No"))
(tr :class "border-b border-stone-200 bg-violet-50"
(td :class "px-3 py-2 font-semibold text-violet-800" "S-expressions")
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")))))
(p :class "text-stone-600"
"No candidate achieves all five properties. The closest — Rebol — fails on AI trainability, which is not a theoretical concern but a practical one: a representation that AI cannot generate reliably is a representation that cannot participate in the coming decade of software development."))
(~doc-section :title "Why not invent something new?" :id "invent"
(p :class "text-stone-600"
"The objection might be: fine, existing alternatives fall short, but why not design a new representation that has all these properties without the parentheses?")
(p :class "text-stone-600"
"Try it. You need a tree structure (for markup and composition). You need a uniform representation (for homoiconicity). You need a delimiter that is unambiguous (for one-pass parsing and structural validation). You need minimum syntactic overhead (for token efficiency).")
(p :class "text-stone-600"
"A tree needs a way to mark where a node begins and ends. The minimal delimiter pair is two characters — one for open, one for close. S-expressions use " (code "(") " and " (code ")") ". You could use " (code "[") " and " (code "]") ", or " (code "{") " and " (code "}") ", or " (code "BEGIN") " and " (code "END") ", or indentation. But " (code "[list]") " and " (code "{list}") " are just s-expressions with different brackets. " (code "BEGIN/END") " adds token overhead. Indentation adds whitespace sensitivity, which breaks wire-format reliability.")
(p :class "text-stone-600"
"You could try eliminating delimiters entirely and using a binary format. But binary formats are not human-readable, not human-writable, and not inspectable in a terminal — which means they cannot serve as a programming language. The developer experience of reading and writing code requires a text-based representation, and the minimal text-based tree delimiter is a matched pair of single characters.")
(p :class "text-stone-600"
"You could try significant whitespace — indentation-based nesting like Python or Haskell. This works for programming languages where the code is stored in files and processed by a single toolchain. It does not work for wire formats, where the representation must survive HTTP transfer, server-side generation, client-side parsing, minification, storage in databases, embedding in script tags, and concatenation with other expressions. Whitespace-sensitive formats are fragile in exactly the contexts where SX operates.")
(p :class "text-stone-600"
"Every path through the design space either arrives at parenthesized prefix notation — s-expressions — or introduces complexity that violates one of the requirements. This is not a failure of imagination. It is a consequence of the requirements being simultaneously demanding and precise. The solution space has one optimum, and McCarthy found it in 1958."))
(~doc-section :title "The parentheses objection" :id "parentheses"
(p :class "text-stone-600"
"The real objection to s-expressions is not technical. It is aesthetic. People do not like parentheses. They look unfamiliar. They feel old. They trigger memories of computer science lectures about recursive descent parsers.")
(p :class "text-stone-600"
"This is a human problem, not a representation problem. And it is a human problem with a known trajectory: every programmer who has used a Lisp for more than a few weeks stops seeing the parentheses. They see the tree. The delimiters become invisible, like the spaces between words in English. You do not see spaces. You see words. Lisp programmers do not see parentheses. They see structure.")
(p :class "text-stone-600"
"More to the point: in the world we are entering, most code will be generated by AI and rendered by machines. The human reads the " (em "output") " — the rendered page, the test results, the behaviour. The s-expressions are an intermediate representation that the human steers but does not need to manually type or visually parse character-by-character. The aesthetic objection dissolves when the representation is a conversation between the human's intent and the machine's generation, not something the human stares at in a text editor.")
(p :class "text-stone-600"
"The author of SX has never opened the codebase in an editor. Every file was created through " (a :href "https://claude.ai/" :class "text-violet-600 hover:underline" "Claude") " in a terminal. The parentheses are between the human and the machine, and neither one minds them."))
(~doc-section :title "The conclusion" :id "conclusion"
(p :class "text-stone-600"
"S-expressions are the minimal tree representation. They are the only widely-known homoiconic notation. They have trivial structural validation, maximum token efficiency, and native composability. They are well-represented in AI training data. Every alternative either fails on one of these criteria or converges toward s-expressions under a different name.")
(p :class "text-stone-600"
"This is not a claim that s-expressions are the best syntax for every programming language. They are not. Python's indentation-based syntax is better for imperative scripting. Haskell's layout rules are better for type-heavy functional programming. SQL is better for relational queries.")
(p :class "text-stone-600"
"The claim is narrower and stronger: for a system that must simultaneously serve as markup, programming language, wire format, data notation, spec language, and metaprogramming substrate — with homoiconicity, one-pass parsing, structural validation, token efficiency, and composability — there is no alternative to s-expressions. Not because alternatives have not been tried. Not because the design space has not been explored. But because the requirements, when stated precisely, admit exactly one family of solutions, and that family is the one McCarthy discovered sixty-seven years ago.")
(p :class "text-stone-600"
"The name for this, borrowed from a " (a :href "https://en.wikipedia.org/wiki/There_is_no_alternative" :class "text-violet-600 hover:underline" "different context entirely") ", is " (em "TINA") " — there is no alternative. Not as a political slogan, but as a mathematical observation. When you need a minimal, homoiconic, structurally-validatable, token-efficient, tree-native, AI-compatible representation for the web, you need s-expressions. Everything else is either less capable or isomorphic."))))
(defcomp ~essay-zero-tooling ()
(~doc-page :title "Zero-Tooling Web Development"
(p :class "text-stone-500 text-sm italic mb-8"
"SX was built without a code editor. No IDE, no manual source edits, no build tools, no linters, no bundlers. The entire codebase — evaluator, renderer, spec, documentation site, test suite — was produced through conversation with an agentic AI. This is what zero-tooling web development looks like.")
(~doc-section :title "No code editor" :id "no-editor"
(p :class "text-stone-600"
"This needs to be stated plainly, because it sounds like an exaggeration: " (strong "not a single line of SX source code was written by hand in a code editor") ". Every component definition, every page route, every essay (including this one), every test case, every spec file — all of it was produced through natural-language conversation with Claude Code, an agentic AI that reads, writes, and modifies files on the developer's behalf.")
(p :class "text-stone-600"
"No VS Code. No Vim. No Emacs. No Sublime Text. No cursor blinking in a source file. The developer describes intent; the AI produces the code, edits the files, runs the tests, and iterates. The code editor — the tool that has defined software development for sixty years — was not used.")
(p :class "text-stone-600"
"This is not a stunt. It is a consequence of two properties converging: a language with trivial syntax that AI can produce flawlessly, and an AI agent capable of understanding and modifying an entire codebase through conversation. Neither property alone would be sufficient. Together, they make the code editor unnecessary."))
(~doc-section :title "The toolchain that wasn't" :id "toolchain"
(p :class "text-stone-600"
"A modern web application typically requires a stack of tooling before a single feature can be built. Consider what a React project demands:")
(ul :class "space-y-2 text-stone-600"
(li (strong "Bundler") " — Webpack, Vite, Rollup, or esbuild to resolve modules, tree-shake dead code, split bundles, and minify output.")
(li (strong "Transpiler") " — Babel or SWC to transform JSX into function calls, strip TypeScript annotations, and downlevel modern syntax for older browsers.")
(li (strong "Package manager") " — npm, yarn, or pnpm to manage hundreds or thousands of transitive dependencies in " (code "node_modules") ".")
(li (strong "CSS pipeline") " — Sass or PostCSS to preprocess styles, CSS Modules or styled-components for scoping, Tailwind CLI for utility generation, PurgeCSS to remove unused rules.")
(li (strong "Dev server") " — webpack-dev-server or Vite's dev mode for hot module replacement, WebSocket-based live reload, and proxy configuration.")
(li (strong "Linter") " — ESLint with dozens of plugins and hundreds of rules to catch mistakes the language grammar permits.")
(li (strong "Formatter") " — Prettier to enforce consistent style, because the syntax admits so many equivalent representations that teams cannot agree without automation.")
(li (strong "Type checker") " — TypeScript compiler running in parallel to catch type errors that JavaScript's dynamic semantics would defer to runtime.")
(li (strong "Test runner") " — Jest or Vitest with jsdom or happy-dom to simulate a browser environment, plus Cypress or Playwright for integration tests.")
(li (strong "Framework CLI") " — create-react-app, Next.js CLI, or Angular CLI to scaffold project structure, generate boilerplate, and manage framework-specific configuration."))
(p :class "text-stone-600"
"That is ten categories of tooling, each with its own configuration format, update cycle, breaking changes, and mental overhead. A fresh " (code "npx create-next-app") " pulls in over 300 packages. The " (code "node_modules") " directory can exceed 200 megabytes. The configuration surface — " (code "webpack.config.js") ", " (code "tsconfig.json") ", " (code ".eslintrc") ", " (code ".prettierrc") ", " (code "postcss.config.js") ", " (code "tailwind.config.js") ", " (code "jest.config.js") ", " (code "next.config.js") " — is itself a specialization.")
(p :class "text-stone-600"
"SX uses " (strong "none of them") "."))
(~doc-section :title "Why each tool is unnecessary" :id "why-unnecessary"
(p :class "text-stone-600"
"Each tool in the conventional stack exists to solve a problem. SX eliminates the problems themselves, not just the tools.")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Bundlers")
(p :class "text-stone-600"
"Bundlers exist because JavaScript's module system requires resolution — imports must be traced, dependencies gathered, and the result packaged for the browser. SX components are defined in " (code ".sx") " files, loaded at startup into the evaluator's environment, and served as s-expressions over the wire. The wire format is the source format. There is no compilation step, no module resolution, and no bundle. The " (code "deps.sx") " spec computes per-page component sets at runtime — only the components a page actually uses are sent to the client. This is tree-shaking without the tree-shaker.")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Transpilers")
(p :class "text-stone-600"
"Transpilers exist because developers write in one language (JSX, TypeScript, modern ES20XX) and must ship another (browser-compatible JavaScript). SX source runs directly — the evaluator walks the same AST that was authored. There is no JSX-to-createElement transform because s-expressions " (em "are") " the component syntax. There are no type annotations to strip because types are enforced at the boundary, at startup. There is no syntax to downlevel because the grammar has been the same since 1958.")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Package managers")
(p :class "text-stone-600"
"Package managers exist because the JavaScript ecosystem fragments functionality into thousands of tiny packages with deep transitive dependency trees. SX's runtime is self-contained: approximately eighty primitives built into the spec, an evaluator bootstrapped from " (code ".sx") " files, and a renderer that produces HTML, DOM nodes, or SX wire format. No third-party packages. No dependency resolution. No lock files. No supply-chain attack surface. The planned future — content-addressed components on IPFS — replaces package registries with content verification.")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "CSS build tools")
(p :class "text-stone-600"
"CSS preprocessors and build tools exist because CSS has a global namespace, no scoping mechanism, and no way to know which rules a page actually uses. CSSX solves all three: the server scans the rendered component tree, collects the CSS classes actually referenced, and sends only those rules. Styles are co-located in components as keyword arguments. There is no global stylesheet to collide with, no preprocessor to run, no PurgeCSS to configure. The CSS each page needs is computed from the component graph — a runtime operation, not a build step.")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Dev servers and HMR")
(p :class "text-stone-600"
"Dev servers with hot module replacement exist because compilation creates a feedback delay — you change a file, wait for the bundler to rebuild, and watch the browser update via WebSocket. SX has no compilation. " (code ".sx") " files are checked for changes on every request via " (code "reload_if_changed()") ". Edit the file, refresh the browser, see the result. The feedback loop is bounded by disk I/O, not by a build pipeline. There is no dev server because there is nothing to serve differently from production.")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Linters and formatters")
(p :class "text-stone-600"
"Linters exist because complex grammars permit many ways to write incorrect or inconsistent code. Formatters exist because those same grammars permit many ways to write correct but inconsistently styled code. S-expressions have one syntactic form: " (code "(head args...)") ". Parentheses open and close. Strings are quoted. That is the entire grammar. There are no semicolons to forget, no operator precedence to get wrong, no closing tags to mismatch. Formatting is just indentation of nested lists. There is nothing to lint and nothing to format.")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Type systems")
(p :class "text-stone-600"
"TypeScript exists because JavaScript's dynamic typing defers errors to runtime, often to production. SX's boundary spec declares the signature of every primitive, every IO function, and every page helper. " (code "SX_BOUNDARY_STRICT=1") " validates all registrations at startup — a type violation crashes the application before it serves a single request. Runtime predicates (" (code "string?") ", " (code "number?") ", " (code "list?") ") handle the rest. The type system is not a separate tool running in parallel — it is the spec, enforced at the edge.")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Framework CLIs and scaffolding")
(p :class "text-stone-600"
"Framework CLIs exist because modern frameworks have complex setup — configuration files, directory conventions, build chains, routing configurations. SX has two declarative abstractions: " (code "defcomp") " (a component) and " (code "defpage") " (a route). Write a component in a " (code ".sx") " file, reference it from a " (code "defpage") ", and it is live. There is no scaffolding because there is nothing to scaffold."))
(~doc-section :title "The AI replaces the rest" :id "ai-replaces"
(p :class "text-stone-600"
"Eliminating the build toolchain still leaves the most fundamental tool: the code editor. The text editor is so basic to programming that it is invisible — questioning its necessity sounds absurd. But the traditional editor exists to serve " (em "human") " limitations:")
(ul :class "space-y-2 text-stone-600"
(li (strong "Syntax highlighting") " — because humans need visual cues to parse complex grammars. S-expressions are trivially parseable; an AI does not need color-coding to understand them.")
(li (strong "Autocomplete") " — because humans cannot remember every function name, parameter, and type signature. An AI that has read the entire codebase does not need autocomplete; it already knows every symbol in scope.")
(li (strong "Go-to-definition") " — because humans lose track of where things are defined across hundreds of files. An AI navigates by reading, grep, and glob — the same tools the editor uses internally, without the GUI.")
(li (strong "Error squiggles") " — because humans need real-time feedback on mistakes. An AI produces correct code from the spec, and when it does not, it reads the error, understands the cause, and fixes it in the next edit.")
(li (strong "Multi-cursor editing") " — because humans need mechanical assistance to make the same change in multiple places. An AI makes coordinated changes across files atomically — component definition, navigation data, page route — in a single operation.")
(li (strong "Version control integration") " — because humans need visual diffs and staging interfaces. An AI reads " (code "git diff") ", understands the changes, and commits with meaningful messages."))
(p :class "text-stone-600"
"Every feature of a modern IDE exists to compensate for a human cognitive limitation. When the agent doing the editing has no such limitations — it can hold the full codebase in context, produce valid syntax without visual feedback, trace dependencies without tooling, and make multi-file changes atomically — the editor is not needed.")
(p :class "text-stone-600"
"This is not hypothetical. It is how SX was built. The developer's interface is a terminal running Claude Code. The conversation goes: describe what you want, review what the AI produces, approve or redirect. The AI reads the existing code, understands the conventions, writes the new code, edits the navigation, updates the page routes, and verifies consistency. The " (em "conversation") " is the development environment."))
(~doc-section :title "Before enlightenment, write code" :id "before-enlightenment"
(p :class "text-stone-600"
"Carson Gross makes an important point in " (a :href "https://htmx.org/essays/yes-and/" :class "text-violet-600 hover:underline" "\"Yes, and...\"") ": you have to have written code in order to effectively read code. The ability to review, critique, and direct is built on the experience of having done the work yourself. You cannot skip the craft and jump straight to the oversight.")
(p :class "text-stone-600"
"He is right. And yet.")
(p :class "text-stone-600"
"There is a Zen teaching about the three stages of practice. Before enlightenment: chop wood, carry water. During enlightenment: chop wood, carry water. After enlightenment: chop wood, carry water. The activities look the same from the outside, but the relationship to them has changed completely. The master chops wood without the beginner's anxiety about whether they are chopping correctly, and without the intermediate's self-conscious technique. They just chop.")
(p :class "text-stone-600"
"Software development has its own version. " (em "Before understanding, write code.") " You must have written the bundler configuration to understand why bundlers exist. You must have debugged the CSS cascade to understand why scoping matters. You must have traced a transitive dependency chain to understand why minimal dependencies matter. The craft comes first.")
(p :class "text-stone-600"
(em "During understanding, read code.") " You review pull requests. You audit architectures. You read more code than you write. You develop the taste that lets you tell good code from bad code, necessary complexity from accidental complexity, real problems from invented ones.")
(p :class "text-stone-600"
(em "After understanding, describe intent.") " You have internalized the patterns so deeply that you do not need to type them. You know what a component should look like without writing it character by character. You know what the test should cover without spelling out each assertion. You know what the architecture should be without drawing every box and arrow. You describe; the agent executes. Not because you cannot do the work, but because the work has become transparent to you.")
(p :class "text-stone-600"
"This is not a shortcut. The developer who built SX through conversation with an AI had decades of experience writing code by hand — in Lisp, in Python, in JavaScript, in C, in assembly (although he was never that good at it). The zero-tooling workflow is not a substitute for that experience. It is what comes " (em "after") " that experience. The wood still gets chopped. The water still gets carried. But the relationship to the chopping and carrying has changed."))
(~doc-section :title "Why this only works with s-expressions" :id "why-sexps"
(p :class "text-stone-600"
"This approach would not work with most web technologies. The reason is the " (strong "syntax tax") " — the overhead a language imposes on AI code generation.")
(p :class "text-stone-600"
"Consider what an AI must get right to produce a valid React component: JSX tags that open and close correctly, curly braces for JavaScript expressions inside markup, import statements with correct module paths, semicolons or ASI boundary rules, TypeScript type annotations with angle-bracket generics, CSS-in-JS template literals with different quoting rules from the surrounding code, and hook call ordering constraints that are semantic, not syntactic. Each of these is a failure point. Each failure produces something that does not parse, let alone run.")
(p :class "text-stone-600"
"S-expressions have one rule: parentheses match. The syntax tax is zero. An AI that can count parentheses can produce syntactically valid SX. This means the AI spends its entire capacity on " (em "what") " to generate — the semantics, the structure, the intent — rather than on formatting, escaping, and syntactic bookkeeping.")
(p :class "text-stone-600"
"The combination is more than additive. SX is deliberately spartan for hand-editing — all those parentheses, no syntax sugar, no operator precedence. Developers who see it for the first time recoil. But AI does not recoil. AI does not care about parentheses. It sees a minimal, regular, unambiguous grammar and produces correct output reliably. Meanwhile, the languages that are comfortable for humans — with their rich syntax, implicit rules, and contextual parsing — are exactly the ones where AI makes the most mistakes.")
(p :class "text-stone-600"
"SX is optimized for the agent, not the typist. This turns out to be the right trade-off when the agent is doing the typing."))
(~doc-section :title "What zero-tooling actually means" :id "what-it-means"
(p :class "text-stone-600"
"Zero-tooling does not mean zero software. The SX evaluator exists. The server exists. The browser runtime exists. These are " (em "the system") ", not tools for building the system. The distinction matters.")
(p :class "text-stone-600"
"A carpenter's hammer is a tool. The house is not a tool. In web development, this distinction has been lost. The bundler, the transpiler, the linter, the formatter, the type checker, the dev server — these are all tools for building the application. The application itself is the server, the evaluator, the renderer, the browser runtime. Traditional web development has accumulated so many tools-for-building-the-thing that the tools-for-building-the-thing often have more code, more configuration, and more failure modes than the thing itself.")
(p :class "text-stone-600"
"SX has the thing. It does not have tools for building the thing. You write " (code ".sx") " files. They run. That is the entire workflow.")
(p :class "text-stone-600"
"Add an agentic AI, and you do not even write the " (code ".sx") " files. You describe what you want. The AI writes the files. They run. The workflow is: intent → code → execution, with no intermediate tooling layer and no manual editing step."))
(~doc-section :title "The proof" :id "proof"
(p :class "text-stone-600"
"The evidence for zero-tooling development is not a benchmark or a whitepaper. It is this website.")
(p :class "text-stone-600"
"Every page you are reading was produced through conversation with an agentic AI. The SX evaluator — a self-hosting interpreter with tail-call optimization, delimited continuations, macro expansion, and three rendering backends — was developed without opening a code editor. The specification files that define the language were written without an IDE. The bootstrappers that compile the spec to JavaScript and Python were produced without syntax highlighting or autocomplete. The test suite — hundreds of tests across evaluator, parser, renderer, router, dependency analyzer, and engine — was written without a test runner GUI. This documentation site — with its navigation, its code examples, its live demos — was built without a web development framework's CLI.")
(p :class "text-stone-600"
"The developer sat in a terminal. They described what they wanted. The AI produced the code. When something was wrong, they described what was wrong. The AI fixed it. When something needed to change, they described the change. The AI made the change. Across thousands of files, tens of thousands of lines of code, and months of development. Even the jokes — the " (a :href "/essays/sx-sucks" :class "text-violet-600 hover:underline" "self-deprecating essay") " about everything wrong with SX, the deadpan tone of the documentation, the essay you are reading right now — all produced through conversation, not through typing.")
(p :class "text-stone-600"
"No build step. No bundler. No transpiler. No package manager. No CSS preprocessor. No dev server. No linter. No formatter. No type checker. No framework CLI. No code editor.")
(p :class "text-stone-600"
"Zero tools."))))