Rename all 1,169 components to path-based names with namespace support
Component names now reflect filesystem location using / as path separator and : as namespace separator for shared components: ~sx-header → ~layouts/header ~layout-app-body → ~shared:layout/app-body ~blog-admin-dashboard → ~admin/dashboard 209 files, 4,941 replacements across all services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,11 +2,11 @@
|
||||
;; The Hegelian Synthesis of Hypertext and Reactivity
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~essay-hegelian-synthesis ()
|
||||
(~doc-page :title "The Hegelian Synthesis"
|
||||
(defcomp ~essays/hegelian-synthesis/essay-hegelian-synthesis ()
|
||||
(~docs/page :title "The Hegelian Synthesis"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
"On the dialectical resolution of the hypertext/reactive contradiction.")
|
||||
(~doc-section :title "I. Thesis: The server renders" :id "thesis"
|
||||
(~docs/section :title "I. Thesis: The server renders" :id "thesis"
|
||||
(p :class "text-stone-600"
|
||||
"In the beginning was the hyperlink. The web was born as a system of documents connected by references. A page was a " (em "representation") " — complete, self-contained, delivered whole by the server. The browser was a thin client. It received, it rendered, it followed links. The server was the sole author of state.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -19,7 +19,7 @@
|
||||
"The beauty of the thesis is its simplicity. One source of truth. One rendering pipeline. No synchronisation problems. No stale state. No " (code "useEffect") " cleanup. Every request produces a fresh representation, and the representation " (em "is") " the application. There is nothing hidden behind it, no shadow state, no ghost in the machine.")
|
||||
(p :class "text-stone-600"
|
||||
"But the thesis has a limit. The server cannot know everything the client experiences. It cannot know that the user's mouse is hovering over a button. It cannot know that a drag is in progress. It cannot know that a counter should increment " (em "now") ", this millisecond, without a round trip. The thesis renders the world from the server's perspective — and the client's perspective, its " (em "Erlebnis") ", its lived experience, is absent."))
|
||||
(~doc-section :title "II. Antithesis: The client reacts" :id "antithesis"
|
||||
(~docs/section :title "II. Antithesis: The client reacts" :id "antithesis"
|
||||
(p :class "text-stone-600"
|
||||
"React arrived as the negation of the server-rendered web. Where the thesis said " (em "the server knows") ", React said " (em "the client knows better") ". Where the thesis treated the browser as a display surface, React treated it as an application runtime. Where the thesis sent documents, React sent " (em "programs") ".")
|
||||
(p :class "text-stone-600"
|
||||
@@ -32,7 +32,7 @@
|
||||
"Worse, the antithesis destroys what the thesis had achieved. The representation is no longer self-contained. A React SPA sends a JavaScript bundle — a program, not a document. The server sends an empty " (code "<div id=\"root\">") " and a prayer. The browser must compile, execute, fetch data, and construct the interface from scratch. The document — the web's primordial unit of meaning — is hollowed out. What arrives is not a representation but an " (em "instruction to construct one") ".")
|
||||
(p :class "text-stone-600"
|
||||
"Hegel would diagnose this as the antithesis's characteristic failure: it achieves freedom (client autonomy) at the cost of substance (server authority). The SPA is the " (em "beautiful soul") " of web development — pure subjectivity that has cut itself off from the objective world and wonders why everything is so complicated."))
|
||||
(~doc-section :title "III. The contradiction in practice" :id "contradiction"
|
||||
(~docs/section :title "III. The contradiction in practice" :id "contradiction"
|
||||
(p :class "text-stone-600"
|
||||
"The practical manifestation of the dialectic is visible in every web team's daily life. The server-rendered camp says: " (em "just use HTML and htmx, it's simpler") ". The React camp says: " (em "you can't build a real app without client state") ". Both are correct. Both are incomplete.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -41,7 +41,7 @@
|
||||
"The React camp cannot deliver a page without JavaScript. Cannot render on first load without a server-rendering framework bolted on top. Cannot cache a representation because there is no stable representation to cache. Cannot inspect a page without devtools because the document is an empty shell. Every improvement — server components, streaming SSR, partial hydration — is an attempt to recover what the thesis already had: server-authored, self-contained documents.")
|
||||
(p :class "text-stone-600"
|
||||
"The two camps are not in disagreement about different things. They are in disagreement about " (em "the same thing") ": where should state live? The thesis says: on the server. The antithesis says: on the client. Neither can accommodate the obvious truth that " (strong "some state belongs on the server and some belongs on the client") ", and that a coherent architecture must handle both without privileging either."))
|
||||
(~doc-section :title "IV. Synthesis: The island in the lake" :id "synthesis"
|
||||
(~docs/section :title "IV. Synthesis: The island in the lake" :id "synthesis"
|
||||
(p :class "text-stone-600"
|
||||
"Hegel's dialectic does not end in compromise. The synthesis is not half-thesis, half-antithesis. It is a new category that " (em "sublates") " — " (em "aufhebt") " — both: preserving what is true in each while resolving the contradiction between them. The synthesis contains the thesis and antithesis as " (em "moments") " within a higher unity.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -50,12 +50,12 @@
|
||||
"But the crucial move — the one that makes this a genuine Hegelian synthesis rather than a mere juxtaposition — is " (strong "the morph") ". When the server sends new content and the client merges it into the existing DOM, hydrated islands are " (em "preserved") ". The server updates the lake. The islands keep their state. The server's new representation flows around the islands like water around rocks. The client's interiority survives the server's authority.")
|
||||
(p :class "text-stone-600"
|
||||
"This is " (em "Aufhebung") " in its precise meaning: cancellation, preservation, and elevation. The thesis (server authority) is " (em "cancelled") " — the server no longer has total control over the page. It is " (em "preserved") " — the server still renders the document, still determines structure, still delivers representations. It is " (em "elevated") " — the server now renders " (em "around") " reactive islands, acknowledging their autonomy. Simultaneously, the antithesis (client autonomy) is cancelled (the client no longer controls the whole page), preserved (islands keep their state), and elevated (client state now coexists with server-driven updates).")
|
||||
(~doc-code :code (highlight ";; The island: reactive state coexists with server lakes.\n;; Lakes are server-morphable slots — the water within the island.\n\n(defisland ~sx-header ()\n (let ((families (list \"violet\" \"rose\" \"blue\" \"emerald\"))\n (idx (signal 0))\n (current (computed (fn ()\n (nth families (mod (deref idx) (len families)))))))\n (a :href \"/\" :sx-get \"/\" :sx-target \"#main-panel\"\n ;; Lake: server can update the logo\n (lake :id \"logo\"\n (span :style (cssx ...) \"(<sx>)\"))\n ;; Reactive: signal-bound, NOT in a lake\n (span :style (cssx (:text (colour (deref current) 500)))\n :on-click (fn (e) (swap! idx inc))\n \"reactive\")\n ;; Lake: server can update the copyright\n (lake :id \"copyright\"\n (p \"© 2026\")))))\n\n;; Click: colour changes (client state)\n;; Server sends new page — morph enters the island\n;; Lakes update from server content\n;; Reactive span keeps its colour — state survives" "lisp"))
|
||||
(~docs/code :code (highlight ";; The island: reactive state coexists with server lakes.\n;; Lakes are server-morphable slots — the water within the island.\n\n(defisland ~essays/hegelian-synthesis/header ()\n (let ((families (list \"violet\" \"rose\" \"blue\" \"emerald\"))\n (idx (signal 0))\n (current (computed (fn ()\n (nth families (mod (deref idx) (len families)))))))\n (a :href \"/\" :sx-get \"/\" :sx-target \"#main-panel\"\n ;; Lake: server can update the logo\n (lake :id \"logo\"\n (span :style (cssx ...) \"(<sx>)\"))\n ;; Reactive: signal-bound, NOT in a lake\n (span :style (cssx (:text (colour (deref current) 500)))\n :on-click (fn (e) (swap! idx inc))\n \"reactive\")\n ;; Lake: server can update the copyright\n (lake :id \"copyright\"\n (p \"© 2026\")))))\n\n;; Click: colour changes (client state)\n;; Server sends new page — morph enters the island\n;; Lakes update from server content\n;; Reactive span keeps its colour — state survives" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
"The " (code "lake") " tag is the key. Inside the island, " (code "(lake :id \"logo\" ...)") " marks a region as server territory — the server can update its content during a morph. The reactive " (code "span") " with its signal-bound style is " (em "not") " in a lake — it is island territory, untouchable by the morph. The morph enters the island, finds the lakes, updates them from the server's new representation, and " (em "flows around") " the reactive nodes like water around rocks.")
|
||||
(p :class "text-stone-600"
|
||||
"Click the word " (em "reactive") " in the header. The colour changes. Navigate to another page. The morph enters the island, updates the lakes (logo, copyright), but the reactive span — with its colour signal — " (em "persists") ". The client's inner life survives the server's outer renewal."))
|
||||
(~doc-section :title "V. Spirit: The self-knowing page" :id "spirit"
|
||||
(~docs/section :title "V. Spirit: The self-knowing page" :id "spirit"
|
||||
(p :class "text-stone-600"
|
||||
"Hegel's system does not end with synthesis. Synthesis becomes a new thesis, which generates its own antithesis, and the dialectic continues. The island architecture is not a final resting place. It is a " (em "moment") " in the self-development of the web.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -66,7 +66,7 @@
|
||||
"The morph algorithm is the phenomenological crux. It is the mechanism by which the page achieves continuity of experience through discontinuity of content. The server sends a completely new representation — new HTML, new structure, new text. The morph walks the old and new DOM trees, reconciling them. Where it finds an island — a locus of client subjectivity — it preserves it. Where it finds static content — server substance — it updates it. The result is a page that is simultaneously the same (the island's state persists) and different (the surrounding content has changed).")
|
||||
(p :class "text-stone-600"
|
||||
"This is Hegel's " (em "identity of identity and difference") ". The page after the morph is the same page (same islands, same signals, same DOM nodes) and a different page (new server content, new navigation state, new URL). The dialectic is not resolved by eliminating one side. It is resolved by maintaining both simultaneously — and the morph is the concrete mechanism that achieves this."))
|
||||
(~doc-section :title "VI. The speculative proposition" :id "speculative"
|
||||
(~docs/section :title "VI. The speculative proposition" :id "speculative"
|
||||
(p :class "text-stone-600"
|
||||
"Hegel distinguished " (em "ordinary") " propositions from " (em "speculative") " ones. An ordinary proposition has a fixed subject and a predicate attached to it from outside: " (em "the rose is red") ". A speculative proposition is one where the predicate reflects back on the subject and transforms it: " (em "the actual is the rational") ".")
|
||||
(p :class "text-stone-600"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(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 ~essays/htmx-react-hybrid/essay-htmx-react-hybrid ()
|
||||
(~docs/page :title "The htmx/React Hybrid" (~docs/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.")) (~docs/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"))) (~docs/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"))) (~docs/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")))))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(defcomp ~essays-index-content ()
|
||||
(~doc-page :title "Essays"
|
||||
(defcomp ~essays/index/essays-index-content ()
|
||||
(~docs/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.")
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
(defcomp ~essay-no-alternative ()
|
||||
(~doc-page :title "There Is No Alternative"
|
||||
(defcomp ~essays/no-alternative/essay-no-alternative ()
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
@@ -30,9 +30,9 @@
|
||||
(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"
|
||||
(~docs/section :title "The candidates" :id "candidates"
|
||||
|
||||
(~doc-subsection :title "XML / HTML"
|
||||
(~docs/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"
|
||||
@@ -42,16 +42,16 @@
|
||||
(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"
|
||||
(~docs/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"))
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
@@ -59,7 +59,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -67,7 +67,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -75,7 +75,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -83,7 +83,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -91,7 +91,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -163,7 +163,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -177,7 +177,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -187,7 +187,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(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 ~essays/on-demand-css/essay-on-demand-css ()
|
||||
(~docs/page :title "On-Demand CSS: Killing the Tailwind Bundle" (~docs/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.")) (~docs/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.")) (~docs/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.")) (~docs/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."))))
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
;; Philosophy section content
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~philosophy-index-content ()
|
||||
(~doc-page :title "Philosophy"
|
||||
(defcomp ~essays/philosophy-index/content ()
|
||||
(~docs/page :title "Philosophy"
|
||||
(div :class "space-y-4"
|
||||
(p :class "text-lg text-stone-600 mb-4"
|
||||
"The deeper ideas behind SX — manifestos, self-reference, and the philosophical traditions that shaped the language.")
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
;; React is Hypermedia
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~essay-react-is-hypermedia ()
|
||||
(~doc-page :title "React is Hypermedia"
|
||||
(defcomp ~essays/react-is-hypermedia/essay-react-is-hypermedia ()
|
||||
(~docs/page :title "React is Hypermedia"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
"A React Island is a hypermedia control. Its behavior is specified in SX.")
|
||||
(~doc-section :title "I. The argument" :id "argument"
|
||||
(~docs/section :title "I. The argument" :id "argument"
|
||||
(p :class "text-stone-600"
|
||||
"React is not hypermedia. Everyone knows this. React is a JavaScript UI library. It renders components to a virtual DOM. It diffs. It patches. It manages state. It does none of the things that define " (a :href "https://en.wikipedia.org/wiki/Hypermedia" :class "text-violet-600 hover:underline" "hypermedia") " — server-driven content, links as the primary interaction mechanism, representations that carry their own controls.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -19,7 +19,7 @@
|
||||
(li "It does not fetch data. It does not route. It does not manage application state outside its boundary."))
|
||||
(p :class "text-stone-600"
|
||||
"This is a " (a :href "https://en.wikipedia.org/wiki/Hypermedia#Controls" :class "text-violet-600 hover:underline" "hypermedia control") ". It is a region of a hypermedia document that responds to user input. Like a " (code "<form>") ". Like an " (code "<a>") ". Like an " (code "<input>") ". The difference is that a form's behavior is specified by the browser and the HTTP protocol. An island's behavior is specified in SX."))
|
||||
(~doc-section :title "II. What makes something hypermedia" :id "hypermedia"
|
||||
(~docs/section :title "II. What makes something hypermedia" :id "hypermedia"
|
||||
(p :class "text-stone-600"
|
||||
"Roy " (a :href "https://en.wikipedia.org/wiki/Roy_Fielding" :class "text-violet-600 hover:underline" "Fielding") "'s " (a :href "https://en.wikipedia.org/wiki/Representational_state_transfer" :class "text-violet-600 hover:underline" "REST") " thesis defines hypermedia by a constraint: " (em "hypermedia as the engine of application state") " (HATEOAS). The server sends representations that include controls — links, forms — and the client's state transitions are driven by those controls. The client does not need out-of-band knowledge of what actions are available. The representation " (em "is") " the interface.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -28,12 +28,12 @@
|
||||
"An SX page does not violate this. The server sends a complete representation — an s-expression tree — that includes all controls. Some controls are plain HTML: " (code "(a :href \"/about\" :sx-get \"/about\")") ". Some controls are reactive islands: " (code "(defisland counter (let ((count (signal 0))) ...))") ". Both are embedded in the representation. Both are delivered by the server. The client does not decide what controls exist — the server does, by including them in the document.")
|
||||
(p :class "text-stone-600"
|
||||
"The island is not separate from the hypermedia. The island " (em "is") " part of the hypermedia. It is a control that the server chose to include, whose behavior the server specified, in the same format as the rest of the page."))
|
||||
(~doc-section :title "III. The SX specification layer" :id "spec-layer"
|
||||
(~docs/section :title "III. The SX specification layer" :id "spec-layer"
|
||||
(p :class "text-stone-600"
|
||||
"A " (code "<form>") "'s behavior is specified in HTML + HTTP: " (code "method=\"POST\"") ", " (code "action=\"/submit\"") ". The browser reads the specification and executes it — serialise the inputs, make the request, handle the response. The form does not contain JavaScript. Its behavior is declared.")
|
||||
(p :class "text-stone-600"
|
||||
"An SX island's behavior is specified in SX:")
|
||||
(~doc-code :lang "lisp" :code
|
||||
(~docs/code :lang "lisp" :code
|
||||
"(defisland todo-adder\n (let ((text (signal \"\")))\n (form :on-submit (fn (e)\n (prevent-default e)\n (emit-event \"todo:add\" (deref text))\n (reset! text \"\"))\n (input :type \"text\"\n :bind text\n :placeholder \"What needs doing?\")\n (button :type \"submit\" \"Add\"))))")
|
||||
(p :class "text-stone-600"
|
||||
"This is a " (em "declaration") ", not a program. It declares: there is a signal holding text. There is a form. When submitted, it emits an event and resets the signal. There is an input bound to the signal. There is a button.")
|
||||
@@ -41,7 +41,7 @@
|
||||
"The s-expression " (em "is") " the specification. It is not compiled to JavaScript and then executed as an opaque blob. It is parsed, evaluated, and rendered by a transparent evaluator whose own semantics are specified in the same format (" (code "eval.sx") "). The island's behavior is as inspectable as a form's " (code "action") " attribute — you can read it, quote it, transform it, analyse it. You can even send it over the wire and have a different client render it.")
|
||||
(p :class "text-stone-600"
|
||||
"A form says " (em "what to do") " in HTML attributes. An island says " (em "what to do") " in s-expressions. Both are declarative. Both are part of the hypermedia document. The difference is expressiveness: forms can collect inputs and POST them. Islands can maintain local state, compute derived values, animate transitions, handle errors, and render dynamic lists — all declared in the same markup language as the page that contains them."))
|
||||
(~doc-section :title "IV. The four levels" :id "four-levels"
|
||||
(~docs/section :title "IV. The four levels" :id "four-levels"
|
||||
(p :class "text-stone-600"
|
||||
"SX reactive islands exist at four levels of complexity, from pure hypermedia to full client reactivity. Each level is a superset of the one before:")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-600"
|
||||
@@ -51,7 +51,7 @@
|
||||
(li (span :class "font-semibold" "L3 — Island communication.") " Islands talk to each other and to the htmx-like \"lake\" via DOM events. " (code "(emit-event \"cart:updated\" count)") " and " (code "(on-event \"cart:updated\" handler)") ". Still no global state. Still no client-side routing. The page is still a server document with embedded controls."))
|
||||
(p :class "text-stone-600"
|
||||
"At every level, the architecture is hypermedia. The server produces the document. The document contains controls. The controls are specified in SX. The jump from L1 to L2 is not a jump from hypermedia to SPA — it is a jump from " (em "simple controls") " (links and forms) to " (em "richer controls") " (reactive islands). The paradigm does not change. The expressiveness does."))
|
||||
(~doc-section :title "V. Why not just React?" :id "why-not-react"
|
||||
(~docs/section :title "V. Why not just React?" :id "why-not-react"
|
||||
(p :class "text-stone-600"
|
||||
"If an island behaves like a React component — local state, event handlers, conditional rendering — why not use React?")
|
||||
(p :class "text-stone-600"
|
||||
@@ -62,18 +62,18 @@
|
||||
"This matters because hypermedia's core property is " (em "self-description") ". A hypermedia representation carries its own controls and its own semantics. An HTML form is self-describing: the browser reads the " (code "action") " and " (code "method") " and knows what to do. A compiled React component is not self-describing: it is a function that was once source code, compiled away into instructions that only the React runtime can interpret.")
|
||||
(p :class "text-stone-600"
|
||||
"SX islands are self-describing. The source is the artifact. The representation carries its own semantics. This is what makes them hypermedia controls — not because they avoid JavaScript (they don't), but because the behavior specification travels with the document, in the same format as the document."))
|
||||
(~doc-section :title "VI. The bridge pattern" :id "bridge"
|
||||
(~docs/section :title "VI. The bridge pattern" :id "bridge"
|
||||
(p :class "text-stone-600"
|
||||
"In practice, the hypermedia and the islands coexist through a pattern: the htmx \"lake\" surrounds the reactive \"islands.\" The lake handles navigation, form submission, content loading — classic hypermedia. The islands handle local interaction — counters, toggles, filters, input validation, animations.")
|
||||
(p :class "text-stone-600"
|
||||
"Communication between lake and islands uses DOM events. An island can " (code "emit-event") " to tell the page something happened. A server-rendered button can " (code "bridge-event") " to poke an island when clicked. The DOM — the shared medium — is the only coupling.")
|
||||
(~doc-code :lang "lisp" :code
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; Server-rendered lake button dispatches to island\n(button :sx-get \"/api/refresh\"\n :sx-target \"#results\"\n :on-click (bridge-event \"search:clear\")\n \"Reset\")\n\n;; Island listens for the event\n(defisland search-filter\n (let ((query (signal \"\")))\n (on-event \"search:clear\" (fn () (reset! query \"\")))\n (input :bind query :placeholder \"Filter...\")))")
|
||||
(p :class "text-stone-600"
|
||||
"The lake button does its hypermedia thing — fetches HTML, swaps it in. Simultaneously, it dispatches a DOM event. The island hears the event and clears its state. Neither knows about the other's implementation. They communicate through the hypermedia document's event system — the DOM.")
|
||||
(p :class "text-stone-600"
|
||||
"This is not a hybrid architecture bolting two incompatible models together. It is a single model — hypermedia — with controls of varying complexity. Some controls are links. Some are forms. Some are reactive islands. All are specified in the document. All are delivered by the server."))
|
||||
(~doc-section :title "VII. The specification is the specification" :id "specification"
|
||||
(~docs/section :title "VII. The specification is the specification" :id "specification"
|
||||
(p :class "text-stone-600"
|
||||
"The deepest claim is not architectural but philosophical. A React Island — the kind with signals and effects and computed values — is a " (em "behavior specification") ". It specifies: when this signal changes, recompute this derived value, re-render this DOM subtree. When this event fires, update this state. When this input changes, validate against this pattern.")
|
||||
(p :class "text-stone-600"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(defcomp ~essay-reflexive-web ()
|
||||
(~doc-page :title "The Reflexive Web"
|
||||
(defcomp ~essays/reflexive-web/essay-reflexive-web ()
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
@@ -11,9 +11,9 @@
|
||||
(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 "/sx/(language.(spec.core))" :class "text-violet-600 hover:underline" "self-hosting specification") " — SX defined in SX. It has " (code "eval") " and " (code "quote") " and " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" :class "text-violet-600 hover:underline" "macros") ". And it runs on the wire — the format that travels between server and client IS the language. This combination has consequences."))
|
||||
|
||||
(~doc-section :title "What homoiconicity changes" :id "homoiconicity"
|
||||
(~docs/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.")
|
||||
(code "(defcomp ~essays/reflexive-web/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"
|
||||
@@ -21,7 +21,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -29,9 +29,9 @@
|
||||
(p :class "text-stone-600"
|
||||
"An AI that understands SX understands the " (a :href "/sx/(language.(spec.core))" :class "text-violet-600 hover:underline" "spec") ". And the spec is written in SX. So the AI understands the definition of the language it is using, in the language it is using. This " (a :href "https://en.wikipedia.org/wiki/Reflexivity_(social_theory)" :class "text-violet-600 hover:underline" "reflexive") " property means the AI does not need a separate mental model for \"the web\" and \"the language\" — they are the same thing."))
|
||||
|
||||
(~doc-section :title "Live system modification" :id "live-modification"
|
||||
(~docs/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.")
|
||||
"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 ~essays/reflexive-web/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"
|
||||
@@ -39,7 +39,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -47,7 +47,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -59,7 +59,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -69,7 +69,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -79,7 +79,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -89,7 +89,7 @@
|
||||
(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"
|
||||
(~docs/section :title "What this opens up" :id "possibilities"
|
||||
(p :class "text-stone-600"
|
||||
"Concretely:")
|
||||
(ul :class "space-y-3 text-stone-600 mt-2"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
(defcomp ~essay-s-existentialism ()
|
||||
(~doc-page :title "S-Existentialism"
|
||||
(defcomp ~essays/s-existentialism/essay-s-existentialism ()
|
||||
(~docs/page :title "S-Existentialism"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
"Existence precedes essence — and s-expressions exist before anything gives them meaning.")
|
||||
(~doc-section :title "I. Existence precedes essence" :id "existence-precedes-essence"
|
||||
(~docs/section :title "I. Existence precedes essence" :id "existence-precedes-essence"
|
||||
(p :class "text-stone-600"
|
||||
"In 1946, Jean-Paul " (a :href "https://en.wikipedia.org/wiki/Jean-Paul_Sartre" :class "text-violet-600 hover:underline" "Sartre") " gave a lecture called \"" (a :href "https://en.wikipedia.org/wiki/Existentialism_Is_a_Humanism" :class "text-violet-600 hover:underline" "Existentialism Is a Humanism") ".\" Its central claim: " (em "existence precedes essence") ". A paper knife is designed before it exists — someone conceives its purpose, then builds it. A human being is the opposite — we exist first, then define ourselves through our choices. There is no blueprint. There is no human nature that precedes the individual human.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -11,9 +11,9 @@
|
||||
"An s-expression exists before any essence is assigned to it. " (code "(div :class \"card\" (h2 title))") " is a list. That is all it is. It has no inherent meaning. It is not a component, not a template, not a function call — not yet. It is raw existence: a nested structure of symbols, keywords, and other lists, waiting.")
|
||||
(p :class "text-stone-600"
|
||||
"The evaluator gives it essence. " (code "render-to-html") " makes it HTML. " (code "render-to-dom") " makes it DOM nodes. " (code "aser") " makes it wire format. " (code "quote") " keeps it as data. The same expression, the same existence, can receive different essences depending on what acts on it. The expression does not know what it is. It becomes what it is used for.")
|
||||
(~doc-code :lang "lisp" :code
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; The same existence, different essences:\n(define expr '(div :class \"card\" (h2 \"Hello\")))\n\n(render-to-html expr) ;; → <div class=\"card\"><h2>Hello</h2></div>\n(render-to-dom expr) ;; → [DOM Element]\n(aser expr) ;; → (div :class \"card\" (h2 \"Hello\"))\n(length expr) ;; → 4 (it's just a list)\n\n;; The expression existed before any of these.\n;; It has no essence until you give it one."))
|
||||
(~doc-section :title "II. Condemned to be free" :id "condemned"
|
||||
(~docs/section :title "II. Condemned to be free" :id "condemned"
|
||||
(p :class "text-stone-600"
|
||||
"\"Man is condemned to be free,\" Sartre wrote in " (a :href "https://en.wikipedia.org/wiki/Being_and_Nothingness" :class "text-violet-600 hover:underline" "Being and Nothingness") ". Not free as a gift. Free as a sentence. You did not choose to be free. You cannot escape it. Every attempt to deny your freedom — by deferring to authority, convention, or nature — is " (a :href "https://en.wikipedia.org/wiki/Bad_faith_(existentialism)" :class "text-violet-600 hover:underline" "bad faith") ". You are responsible for everything you make of yourself, and the weight of that responsibility is the human condition.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -24,7 +24,7 @@
|
||||
"The SX developer has no commandments. " (code "defcomp") " is a suggestion, not a requirement — you can build components with raw lambdas if you prefer. " (code "defmacro") " gives you the power to reshape the language itself. There are no rules of hooks because there are no hooks. There are no lifecycle methods because there is no lifecycle. There is only evaluation: an expression goes in, a value comes out. What the expression contains, how the value is used — that is up to you.")
|
||||
(p :class "text-stone-600"
|
||||
"This is not comfortable. Freedom never is. Sartre did not say freedom was pleasant. He said it was inescapable."))
|
||||
(~doc-section :title "III. Bad faith" :id "bad-faith"
|
||||
(~docs/section :title "III. Bad faith" :id "bad-faith"
|
||||
(p :class "text-stone-600"
|
||||
(a :href "https://en.wikipedia.org/wiki/Bad_faith_(existentialism)" :class "text-violet-600 hover:underline" "Bad faith") " is Sartre's term for the lie you tell yourself to escape freedom. The waiter who plays at being a waiter — performing the role so thoroughly that he forgets he chose it. The woman who pretends not to notice a man's intentions — denying her own awareness to avoid making a decision. Bad faith is not deception of others. It is self-deception about one's own freedom.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -37,7 +37,7 @@
|
||||
"\"Nobody uses s-expressions for web development.\" Bad faith. " (em "You") " do not use s-expressions for web development. That is a fact about you, not a fact about web development. Transforming your personal preference into a universal law is the quintessential act of bad faith.")
|
||||
(p :class "text-stone-600"
|
||||
"SX does not prevent bad faith — nothing can. But it makes bad faith harder. When the entire language is fifty primitives and a page of special forms, you cannot pretend that the complexity is necessary. When there is no build step, you cannot pretend that the build step is inevitable. When the same source runs on server and client, you cannot pretend that the server-client divide is ontological. SX strips away the excuses. What remains is your choices."))
|
||||
(~doc-section :title "IV. Nausea" :id "nausea"
|
||||
(~docs/section :title "IV. Nausea" :id "nausea"
|
||||
(p :class "text-stone-600"
|
||||
"In " (a :href "https://en.wikipedia.org/wiki/Nausea_(novel)" :class "text-violet-600 hover:underline" "Nausea") " (1938), Sartre's Roquentin sits in a park and stares at the root of a chestnut tree. He sees it — really sees it — stripped of all the concepts and categories that normally make it comprehensible. It is not a \"root.\" It is not \"brown.\" It is not \"gnarled.\" It simply " (em "is") " — a brute, opaque, superfluous existence. The nausea is the vertigo of confronting existence without essence.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -46,7 +46,7 @@
|
||||
"SX has its own nausea. Stare at a page of s-expressions long enough and the same vertigo hits. Parentheses. Symbols. Lists inside lists inside lists. There is nothing behind them — no hidden runtime, no compiled intermediate form, no framework magic. Just parentheses. The s-expression is Roquentin's chestnut root: it simply " (em "is") ". You cannot unsee it.")
|
||||
(p :class "text-stone-600"
|
||||
"But SX's nausea is honest. The chestnut root is really there — it exists, bare and exposed. The " (code "node_modules") " nausea is different: it is nausea at something that should not exist, that has no reason to exist, that exists only because of accumulated accidents of dependency resolution. SX's nausea is existential — the dizziness of confronting raw structure. The JavaScript ecosystem's nausea is absurd — the dizziness of confronting unnecessary complexity that no one chose but everyone maintains."))
|
||||
(~doc-section :title "V. The absurd" :id "absurd"
|
||||
(~docs/section :title "V. The absurd" :id "absurd"
|
||||
(p :class "text-stone-600"
|
||||
(a :href "https://en.wikipedia.org/wiki/Albert_Camus" :class "text-violet-600 hover:underline" "Camus") " defined the " (a :href "https://en.wikipedia.org/wiki/Absurdism" :class "text-violet-600 hover:underline" "absurd") " as the gap between human longing for meaning and the universe's silence. We want the world to make sense. It does not. The absurd is not in us or in the world — it is in the confrontation between the two.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -59,7 +59,7 @@
|
||||
"Most developers commit philosophical suicide. They adopt a framework, declare it The Way, and stop questioning. React is the truth. TypeScript is salvation. The build step is destiny. The absurd disappears — not because it has been resolved, but because they have stopped looking at it.")
|
||||
(p :class "text-stone-600"
|
||||
"SX is revolt. It does not resolve the absurd. It does not pretend that s-expressions are the answer, that parentheses will save the web, that the industry will come around. It simply continues — writing components, specifying evaluators, bootstrapping to new targets — with full awareness that the project may never matter to anyone. This is the only honest response to the absurd."))
|
||||
(~doc-section :title "VI. Sisyphus" :id "sisyphus"
|
||||
(~docs/section :title "VI. Sisyphus" :id "sisyphus"
|
||||
(p :class "text-stone-600"
|
||||
"\"" (a :href "https://en.wikipedia.org/wiki/The_Myth_of_Sisyphus" :class "text-violet-600 hover:underline" "One must imagine Sisyphus happy") ".\"")
|
||||
(p :class "text-stone-600"
|
||||
@@ -70,7 +70,7 @@
|
||||
"The SX developer is conscious Sisyphus. The boulder is obvious: writing a Lisp for the web is absurd. The hill is obvious: nobody will use it. But consciousness changes everything. Camus's Sisyphus is happy not because the task has meaning but because " (em "he") " has chosen it. The choice — the revolt — is the meaning. Not the outcome.")
|
||||
(p :class "text-stone-600"
|
||||
"One must imagine the s-expressionist happy."))
|
||||
(~doc-section :title "VII. Thrownness" :id "thrownness"
|
||||
(~docs/section :title "VII. Thrownness" :id "thrownness"
|
||||
(p :class "text-stone-600"
|
||||
(a :href "https://en.wikipedia.org/wiki/Martin_Heidegger" :class "text-violet-600 hover:underline" "Heidegger's") " " (a :href "https://en.wikipedia.org/wiki/Thrownness" :class "text-violet-600 hover:underline" "Geworfenheit") " — thrownness — describes the condition of finding yourself already in a world you did not choose. You did not pick your language, your culture, your body, your historical moment. You were " (em "thrown") " into them. Authenticity is not escaping thrownness but owning it — relating to your situation as yours, rather than pretending it was inevitable or that you could have been elsewhere.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -81,7 +81,7 @@
|
||||
"SX owns its thrownness. It runs in the browser's JavaScript engine — not because JavaScript is good, but because the browser is the world it was thrown into. It produces DOM nodes — not because the DOM is elegant, but because the DOM is what exists. It sends HTTP responses — not because HTTP is ideal, but because HTTP is the wire. SX does not build a virtual DOM to escape the real DOM. It does not invent a type system to escape JavaScript's types. It evaluates s-expressions in the given environment and produces what the environment requires.")
|
||||
(p :class "text-stone-600"
|
||||
"The s-expression is itself a kind of primordial thrownness. It did not choose to be the minimal recursive data structure. It simply is. Open paren, atoms, close paren. It was not designed by committee, not optimised by industry, not evolved through market pressure. It was " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" :class "text-violet-600 hover:underline" "discovered in 1958") " as a notational convenience and turned out to be the bedrock. SX was thrown into s-expressions the way humans are thrown into bodies — not by choice, but by the nature of what it is."))
|
||||
(~doc-section :title "VIII. The Other" :id "the-other"
|
||||
(~docs/section :title "VIII. The Other" :id "the-other"
|
||||
(p :class "text-stone-600"
|
||||
"Sartre's account of " (a :href "https://en.wikipedia.org/wiki/Being_and_Nothingness#The_Other_and_the_Look" :class "text-violet-600 hover:underline" "the Other") " in Being and Nothingness: I am alone in a park. I am the centre of my world. Then I see another person. Suddenly I am seen. I am no longer just a subject — I am an object in someone else's world. The Other's gaze transforms me. \"Hell is other people,\" Sartre wrote in " (a :href "https://en.wikipedia.org/wiki/No_Exit" :class "text-violet-600 hover:underline" "No Exit") " — not because others are cruel, but because they see you, and their seeing limits your freedom to define yourself.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -90,7 +90,7 @@
|
||||
"SX has no Other. There is no competing s-expression web framework to define itself against. There is no benchmark to win, no market to capture, no conference talk to rebut. This is either pathetic (no ecosystem, no community, no relevance) or liberating (no gaze, no comparison, no borrowed identity). Sartre would say it is both.")
|
||||
(p :class "text-stone-600"
|
||||
"But there is another sense of the Other that matters more. The Other " (em "evaluator") ". SX's self-hosting spec means that the language encounters itself as Other. " (code "eval.sx") " is written in SX — the language looking at itself, seeing itself from outside. The bootstrap compiler reads this self-description and produces a working evaluator. The language has been seen by its own gaze, and the seeing has made it real. This is Sartre's intersubjectivity turned reflexive: the subject and the Other are the same entity."))
|
||||
(~doc-section :title "IX. Authenticity" :id "authenticity"
|
||||
(~docs/section :title "IX. Authenticity" :id "authenticity"
|
||||
(p :class "text-stone-600"
|
||||
"For both Heidegger and Sartre, " (a :href "https://en.wikipedia.org/wiki/Authenticity_(philosophy)" :class "text-violet-600 hover:underline" "authenticity") " means facing your situation — your freedom, your thrownness, your mortality — without evasion. The inauthentic person hides in the crowd, adopts the crowd's values, speaks the crowd's language. Heidegger called this " (a :href "https://en.wikipedia.org/wiki/Heideggerian_terminology#Das_Man" :class "text-violet-600 hover:underline" "das Man") " — the \"They.\" \"They say React is best.\" \"They use TypeScript.\" \"They have build steps.\" The They is not a conspiracy. It is the comfortable anonymity of doing what everyone does.")
|
||||
(p :class "text-stone-600"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(defcomp ~essay-self-defining-medium ()
|
||||
(~doc-page :title "The True Hypermedium Must Define Itself With Itself"
|
||||
(defcomp ~essays/self-defining-medium/essay-self-defining-medium ()
|
||||
(~docs/page :title "The True Hypermedium Must Define Itself With Itself"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
"On ontological uniformity, the metacircular web, and why the address of a thing and the thing itself should be made of the same stuff.")
|
||||
|
||||
(~doc-section :title "The test" :id "the-test"
|
||||
(~docs/section :title "The test" :id "the-test"
|
||||
(p :class "text-stone-600"
|
||||
"There is a simple test for whether a medium is truly a medium or merely a carrier. Can it define itself? Can it describe its own semantics, in its own language, and have that description be executable?")
|
||||
(p :class "text-stone-600"
|
||||
@@ -11,7 +11,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"This is not a mere inconvenience. It is a structural fact that determines what is and is not possible. A medium that cannot describe itself cannot reason about itself, cannot modify itself, cannot generate itself from itself. It is " (em "inert") " — a carrying wave, not a thinking substance."))
|
||||
|
||||
(~doc-section :title "What Lisp solved" :id "what-lisp-solved"
|
||||
(~docs/section :title "What Lisp solved" :id "what-lisp-solved"
|
||||
(p :class "text-stone-600"
|
||||
(a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" :class "text-violet-600 hover:underline" "McCarthy") " solved this for computation in 1960. The " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#Connection_to_artificial_intelligence" :class "text-violet-600 hover:underline" "metacircular evaluator") " is an evaluator written in the language it evaluates. " (code "eval") " takes a list and returns a value. The definition of " (code "eval") " is itself a list. So " (code "eval") " can evaluate its own definition. The serpent eats its tail.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -19,7 +19,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"The consequence was immediate and permanent: Lisp became the only language family where the specification, the implementation, the IDE, and the program are all the same kind of thing. Every other language has a boundary between the language and its meta-description. Lisp has none."))
|
||||
|
||||
(~doc-section :title "The web failed the test" :id "web-failed"
|
||||
(~docs/section :title "The web failed the test" :id "web-failed"
|
||||
(p :class "text-stone-600"
|
||||
"The web has never had this property. Consider:")
|
||||
(ul :class "space-y-2 text-stone-600 mt-2"
|
||||
@@ -31,7 +31,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"Every layer of the web stack requires stepping outside the medium to define the medium. This is ontological heterogeneity: the thing and the description of the thing are made of different stuff. The map is not the territory, and the map cannot even be " (em "drawn") " in the territory."))
|
||||
|
||||
(~doc-section :title "Ontological uniformity" :id "ontological-uniformity"
|
||||
(~docs/section :title "Ontological uniformity" :id "ontological-uniformity"
|
||||
(p :class "text-stone-600"
|
||||
"The property we need has a name: " (strong "ontological uniformity") ". Address, verb, query, response, rendering instruction, and specification are all the same kind of thing.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -39,23 +39,23 @@
|
||||
(ul :class "space-y-2 text-stone-600 mt-2"
|
||||
(li (strong "A URL is an s-expression.") " " (code "/sx/(language.(doc.introduction))") " is not an opaque string — it is a parseable, composable expression. It can be decomposed into parts, transformed by functions, and reasoned about by the same evaluator that renders pages.")
|
||||
(li (strong "A response is an s-expression.") " What the server sends is " (code "(div :class \"p-4\" (h2 \"Hello\"))") " — the same notation as the component that produced it. The wire format is the language.")
|
||||
(li (strong "A component is an s-expression.") " " (code "(defcomp ~card (&key title) (div (h2 title)))") " is simultaneously a definition, a value, and data that can be inspected and transformed.")
|
||||
(li (strong "A component is an s-expression.") " " (code "(defcomp ~essays/self-defining-medium/card (&key title) (div (h2 title)))") " is simultaneously a definition, a value, and data that can be inspected and transformed.")
|
||||
(li (strong "A query is an s-expression.") " The URL " (code "/sx/(language.(spec.core))") " is a function call. The response is the return value. Routing is evaluation.")
|
||||
(li (strong "The specification is s-expressions.") " The " (a :href "/sx/(language.(spec.core))" :class "text-violet-600 hover:underline" "SX spec") " is written in SX. The evaluator is defined in the language it evaluates. The parser is defined in the language it parses."))
|
||||
(p :class "text-stone-600"
|
||||
"There is " (em "one") " kind of stuff. Everything is made of it. The address of a thing and the thing itself are the same kind of thing."))
|
||||
|
||||
(~doc-section :title "What this makes possible" :id "what-this-enables"
|
||||
(~docs/section :title "What this makes possible" :id "what-this-enables"
|
||||
(p :class "text-stone-600"
|
||||
"When the medium is uniform, operations that were impossible become trivial:")
|
||||
(ul :class "space-y-3 text-stone-600 mt-2"
|
||||
(li (strong "URLs compose.") " If " (code "(language (doc introduction))") " and " (code "(language (spec core))") " are both expressions, then " (code "(diff (language (spec signals)) (language (spec eval)))") " is a natural composition. Two queries, side by side. The URL algebra falls out of the expression algebra. You do not need to design it separately — it was always there.")
|
||||
(li (strong "The site can show its own source.") " " (code "/sx/(source.(~essay-self-defining-medium))") " returns the component definition of this essay. Not a screenshot. Not a prettified view. The actual s-expression that, when evaluated, produces what you are reading now. The page and its source code are the same kind of thing, so displaying one as the other is just evaluation.")
|
||||
(li (strong "The site can show its own source.") " " (code "/sx/(source.(~essays/self-defining-medium/essay-self-defining-medium))") " returns the component definition of this essay. Not a screenshot. Not a prettified view. The actual s-expression that, when evaluated, produces what you are reading now. The page and its source code are the same kind of thing, so displaying one as the other is just evaluation.")
|
||||
(li (strong "The spec is executable documentation.") " The " (a :href "/sx/(language.(bootstrapper.self-hosting))" :class "text-violet-600 hover:underline" "self-hosting bootstrapper") " reads the SX spec (written in SX) and produces a working evaluator. The documentation is the implementation. The implementation is the documentation. There is no drift because there is no gap.")
|
||||
(li (strong "Inspection is free.") " " (code "/sx/(inspect.(language.(doc.primitives)))") " can show the dependency graph, CSS footprint, and render plan of any page — because the page is data, and data can be walked, analysed, and reported on by the same system that renders it.")
|
||||
(li (strong "AI is a native speaker.") " An AI reading SX reads the same notation as the server, the client, the wire, and the spec. There is no translation layer. The AI does not generate code that must be compiled and deployed — it generates expressions that are evaluated. The medium is shared between human, machine, and network.")))
|
||||
|
||||
(~doc-section :title "The metacircular web" :id "metacircular-web"
|
||||
(~docs/section :title "The metacircular web" :id "metacircular-web"
|
||||
(p :class "text-stone-600"
|
||||
"McCarthy's " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#Connection_to_artificial_intelligence" :class "text-violet-600 hover:underline" "metacircular evaluator") " proved that a computing language can define itself. SX extends this proof to a networked hypermedium:")
|
||||
(ul :class "space-y-2 text-stone-600 mt-2"
|
||||
@@ -67,17 +67,17 @@
|
||||
(p :class "text-stone-600"
|
||||
"At every level, the description and the described are the same kind of thing. The specification is not " (em "about") " the system — it " (em "is") " the system. This is not metaphor. It is the literal architecture."))
|
||||
|
||||
(~doc-section :title "Why it matters" :id "why-it-matters"
|
||||
(~docs/section :title "Why it matters" :id "why-it-matters"
|
||||
(p :class "text-stone-600"
|
||||
"A hypermedium that cannot define itself with itself is a hypermedium that depends on something else for its definition. It is parasitic on external authority — standards bodies, specification documents, reference implementations in foreign languages. Every layer of indirection is a layer where the medium's identity is borrowed rather than intrinsic.")
|
||||
(p :class "text-stone-600"
|
||||
"This dependency has practical consequences. When HTML needs a new element, a committee must convene, a specification must be written (in English, in a PDF), browser vendors must implement it (in C++), and the ecosystem must wait. The medium cannot extend itself. It is extended " (em "by others") ", in " (em "other languages") ", on " (em "other timescales") ".")
|
||||
(p :class "text-stone-600"
|
||||
"A self-defining medium extends itself by evaluating new definitions. " (code "(defcomp ~new-element (&key attrs children) ...)") " — this is not a proposal to a standards body. It is an expression that, when evaluated, adds a new element to the medium. The medium grows by the same mechanism it operates: evaluation of expressions.")
|
||||
"A self-defining medium extends itself by evaluating new definitions. " (code "(defcomp ~essays/self-defining-medium/new-element (&key attrs children) ...)") " — this is not a proposal to a standards body. It is an expression that, when evaluated, adds a new element to the medium. The medium grows by the same mechanism it operates: evaluation of expressions.")
|
||||
(p :class "text-stone-600"
|
||||
"This is the deepest consequence of ontological uniformity. The medium is not just " (em "described by") " itself — it " (em "grows from") " itself. New components, new routing patterns, new wire formats, new rendering modes — all are expressions evaluated by the evaluator that is itself an expression. The system is " (a :href "https://en.wikipedia.org/wiki/Autopoiesis" :class "text-violet-600 hover:underline" "autopoietic") ": it produces and maintains itself through the same operations it performs."))
|
||||
|
||||
(~doc-section :title "The test, revisited" :id "test-revisited"
|
||||
(~docs/section :title "The test, revisited" :id "test-revisited"
|
||||
(p :class "text-stone-600"
|
||||
"Can the medium define itself with itself?")
|
||||
(p :class "text-stone-600"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
(defcomp ~essay-separation-of-concerns ()
|
||||
(~doc-page :title "Separate your Own Concerns"
|
||||
(defcomp ~essays/separation-of-concerns/essay-separation-of-concerns ()
|
||||
(~docs/page :title "Separate your Own 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"
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
@@ -17,7 +17,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -25,7 +25,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -33,7 +33,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -41,10 +41,10 @@
|
||||
(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"
|
||||
(~docs/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)
|
||||
(~docs/code :code "(defcomp ~essays/separation-of-concerns/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\")
|
||||
@@ -64,7 +64,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -75,7 +75,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -86,7 +86,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(defcomp ~essay-server-architecture ()
|
||||
(~doc-page :title "Server Architecture"
|
||||
(defcomp ~essays/server-architecture/essay-server-architecture ()
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
@@ -11,7 +11,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -28,16 +28,16 @@
|
||||
(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"
|
||||
(~docs/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"))
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
@@ -53,15 +53,15 @@
|
||||
(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"
|
||||
(~docs/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")))
|
||||
(~docs/code :code (highlight ";; CORRECT: .sx file composes markup from data\n(defcomp ~essays/server-architecture/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"))
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
@@ -96,7 +96,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(defcomp ~essay-sx-and-ai ()
|
||||
(~doc-page :title "SX and AI"
|
||||
(defcomp ~essays/sx-and-ai/essay-sx-and-ai ()
|
||||
(~docs/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"
|
||||
(~docs/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"
|
||||
@@ -13,16 +13,16 @@
|
||||
(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"
|
||||
(~docs/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"))
|
||||
(~docs/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 ~essays/sx-and-ai/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"
|
||||
(~docs/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"
|
||||
@@ -30,7 +30,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -38,37 +38,37 @@
|
||||
(p :class "text-stone-600"
|
||||
"Beyond parsing, the SX " (a :href "/sx/(language.(spec.primitives))" :class "text-violet-600 hover:underline" "boundary system") " provides semantic validation. A pure component cannot call IO primitives — not by convention, but by the evaluator refusing to resolve them. An AI generating a component can produce whatever expressions it wants; the sandbox ensures only permitted operations execute. Validation is not a separate step bolted onto the pipeline. It is the language."))
|
||||
|
||||
(~doc-section :title "Components are self-documenting" :id "self-documenting"
|
||||
(~docs/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"))
|
||||
(~docs/code :code (highlight "(defcomp ~essays/sx-and-ai/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"
|
||||
(~docs/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"))
|
||||
(~docs/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"))
|
||||
(~docs/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"
|
||||
(~docs/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"))
|
||||
(~docs/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 ~essays/sx-and-ai/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"
|
||||
(~docs/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"
|
||||
@@ -82,17 +82,17 @@
|
||||
(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"
|
||||
(~docs/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.")
|
||||
"That this works at all is a testament to s-expressions. The AI generates " (code "(defcomp ~essays/sx-and-ai/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"
|
||||
(~docs/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"
|
||||
@@ -100,7 +100,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"The " (a :href "/sx/(etc.(essay.sx-sucks))" :class "text-violet-600 hover:underline" "sx sucks") " essay copped to the AI authorship and framed it as a weakness — microwave dinner on a nice plate. But the framing was wrong. If a language is so well-suited to machine generation that one person with no Lisp experience can build a self-hosting language, a multi-target bootstrapper, a reactive component framework, and a full documentation site through pure agentic AI — that is not a weakness of the language. That is the language working exactly as it should."))
|
||||
|
||||
(~doc-section :title "What this changes" :id "what-changes"
|
||||
(~docs/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"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
(defcomp ~essay-sx-and-dennett ()
|
||||
(~doc-page :title "SX and Dennett"
|
||||
(defcomp ~essays/sx-and-dennett/essay-sx-and-dennett ()
|
||||
(~docs/page :title "SX and Dennett"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
"Real patterns, multiple drafts, and the intentional stance — a philosopher of mind meets a language that thinks about itself.")
|
||||
(~doc-section :title "I. The intentional stance" :id "intentional-stance"
|
||||
(~docs/section :title "I. The intentional stance" :id "intentional-stance"
|
||||
(p :class "text-stone-600"
|
||||
"Daniel " (a :href "https://en.wikipedia.org/wiki/Daniel_Dennett" :class "text-violet-600 hover:underline" "Dennett") " spent fifty years arguing that the mind is not what it seems. His central method is the " (a :href "https://en.wikipedia.org/wiki/Intentional_stance" :class "text-violet-600 hover:underline" "intentional stance") " — a strategy for predicting a system's behaviour by treating it " (em "as if") " it has beliefs, desires, and intentions, whether or not it \"really\" does.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -10,25 +10,25 @@
|
||||
(p :class "text-stone-600"
|
||||
"Web frameworks enforce a single stance. React's mental model is the design stance: components are functions, props go in, JSX comes out. You reason about the system by reasoning about its design. If you need the physical stance (what is actually in the DOM right now?), you reach for " (code "useRef") ". If you need the intentional stance (what does this component " (em "mean") "?), you read the documentation. Each stance requires a different tool, a different context switch.")
|
||||
(p :class "text-stone-600"
|
||||
"SX lets you shift stances without shifting languages. The physical stance: " (code "(div :class \"card\" (h2 \"Title\"))") " — this is exactly the DOM structure that will be produced. One list, one element. The design stance: " (code "(defcomp ~card (&key title) (div title))") " — this is how the component is built, its contract. The intentional stance: " (code "(~card :title \"Hello\")") " — this " (em "means") " \"render a card with this title,\" and you can reason about it at that level without knowing the implementation.")
|
||||
(~doc-code :lang "lisp" :code
|
||||
";; Physical stance — the literal structure\n(div :class \"card\" (h2 \"Title\"))\n\n;; Design stance — how it's built\n(defcomp ~card (&key title) (div :class \"card\" (h2 title)))\n\n;; Intentional stance — what it means\n(~card :title \"Title\")\n\n;; All three are s-expressions.\n;; All three can be inspected, transformed, quoted.\n;; Shifting stance = changing which expression you look at.")
|
||||
"SX lets you shift stances without shifting languages. The physical stance: " (code "(div :class \"card\" (h2 \"Title\"))") " — this is exactly the DOM structure that will be produced. One list, one element. The design stance: " (code "(defcomp ~essays/sx-and-dennett/card (&key title) (div title))") " — this is how the component is built, its contract. The intentional stance: " (code "(~essays/sx-and-dennett/card :title \"Hello\")") " — this " (em "means") " \"render a card with this title,\" and you can reason about it at that level without knowing the implementation.")
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; Physical stance — the literal structure\n(div :class \"card\" (h2 \"Title\"))\n\n;; Design stance — how it's built\n(defcomp ~essays/sx-and-dennett/card (&key title) (div :class \"card\" (h2 title)))\n\n;; Intentional stance — what it means\n(~essays/sx-and-dennett/card :title \"Title\")\n\n;; All three are s-expressions.\n;; All three can be inspected, transformed, quoted.\n;; Shifting stance = changing which expression you look at.")
|
||||
(p :class "text-stone-600"
|
||||
"The key insight: all three stances are expressed in the same medium. You do not need a debugger for the physical stance, a type system for the design stance, and documentation for the intentional stance. You need lists. The stances are not different tools — they are different ways of reading the same data."))
|
||||
(~doc-section :title "II. Real patterns" :id "real-patterns"
|
||||
(~docs/section :title "II. Real patterns" :id "real-patterns"
|
||||
(p :class "text-stone-600"
|
||||
"Dennett's 1991 paper \"" (a :href "https://en.wikipedia.org/wiki/Real_Patterns" :class "text-violet-600 hover:underline" "Real Patterns") "\" makes a deceptively simple argument: a pattern is real if it lets you compress data — if recognising the pattern gives you predictive leverage that you would not have otherwise. Patterns are not " (em "in the mind") " of the observer. They are not " (em "in the object") " independently of any observer. They are real features of the world that exist at a particular level of description.")
|
||||
(p :class "text-stone-600"
|
||||
"Consider a bitmap of noise. If you describe it pixel by pixel, the description is as long as the image. No compression. No pattern. Now consider a bitmap of a checkerboard. You can say \"alternating black and white squares, 8x8\" — vastly shorter than the pixel-by-pixel description. The checkerboard pattern is " (em "real") ". It exists in the data. Recognising it gives you compression.")
|
||||
(p :class "text-stone-600"
|
||||
"Components are real patterns. " (code "(~card :title \"Hello\")") " compresses " (code "(div :class \"card\" (h2 \"Hello\"))") " — and more importantly, it compresses every instance of card-like structure across the application into a single abstraction. The component is not a convenient fiction. It is a real pattern in the codebase: a regularity that gives you predictive power. When you see " (code "~card") ", you know the structure, the styling, the contract — without expanding the definition.")
|
||||
"Components are real patterns. " (code "(~essays/sx-and-dennett/card :title \"Hello\")") " compresses " (code "(div :class \"card\" (h2 \"Hello\"))") " — and more importantly, it compresses every instance of card-like structure across the application into a single abstraction. The component is not a convenient fiction. It is a real pattern in the codebase: a regularity that gives you predictive power. When you see " (code "~essays/sx-and-dennett/card") ", you know the structure, the styling, the contract — without expanding the definition.")
|
||||
(p :class "text-stone-600"
|
||||
"Macros are real patterns at a higher level. A macro like " (code "defcomp") " captures the pattern of \"name, parameters, body\" that every component shares. It compresses the regularity of component definition itself. The macro is real in exactly Dennett's sense — it captures a genuine pattern, and that pattern gives you leverage.")
|
||||
(p :class "text-stone-600"
|
||||
"Now here is where SX makes Dennett's argument concrete. In most languages, the reality of patterns is debatable — are classes real? Are interfaces real? Are design patterns real? You can argue either way because the patterns exist at a different level from the code. In SX, patterns " (em "are") " code. A component is a list. A macro is a function over lists. The pattern and the data it describes are the same kind of thing — s-expressions. There is no level-of-description gap. The pattern is as real as the data it compresses, because they inhabit the same ontological plane.")
|
||||
(~doc-code :lang "lisp" :code
|
||||
";; The data (expanded)\n(div :class \"card\"\n (h2 \"Pattern\")\n (p \"A real one.\"))\n\n;; The pattern (compressed)\n(~card :title \"Pattern\" (p \"A real one.\"))\n\n;; The meta-pattern (the definition)\n(defcomp ~card (&key title &rest children)\n (div :class \"card\" (h2 title) children))\n\n;; All three levels: data, pattern, meta-pattern.\n;; All three are lists. All three are real."))
|
||||
(~doc-section :title "III. Multiple Drafts" :id "multiple-drafts"
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; The data (expanded)\n(div :class \"card\"\n (h2 \"Pattern\")\n (p \"A real one.\"))\n\n;; The pattern (compressed)\n(~essays/sx-and-dennett/card :title \"Pattern\" (p \"A real one.\"))\n\n;; The meta-pattern (the definition)\n(defcomp ~essays/sx-and-dennett/card (&key title &rest children)\n (div :class \"card\" (h2 title) children))\n\n;; All three levels: data, pattern, meta-pattern.\n;; All three are lists. All three are real."))
|
||||
(~docs/section :title "III. Multiple Drafts" :id "multiple-drafts"
|
||||
(p :class "text-stone-600"
|
||||
"In " (a :href "https://en.wikipedia.org/wiki/Consciousness_Explained" :class "text-violet-600 hover:underline" "Consciousness Explained") " (1991), Dennett proposed the " (a :href "https://en.wikipedia.org/wiki/Multiple_drafts_model" :class "text-violet-600 hover:underline" "Multiple Drafts model") " of consciousness. There is no " (a :href "https://en.wikipedia.org/wiki/Cartesian_theater" :class "text-violet-600 hover:underline" "Cartesian theater") " — no single place in the brain where \"it all comes together\" for a central observer. Instead, multiple parallel processes generate content simultaneously. Various drafts of narrative are in process at any time, some getting revised, some abandoned, some incorporated into the ongoing story. There is no master draft. There is no final audience. There is just the process of revision itself.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -44,51 +44,51 @@
|
||||
"Each draft is a complete s-expression. Each is meaningful on its own terms. No single process \"sees\" the whole page — the server doesn't see the DOM, the client doesn't see the Python context, the browser's layout engine doesn't see the s-expressions. The page emerges from the drafting process, not from a central reconciler.")
|
||||
(p :class "text-stone-600"
|
||||
"This is not a metaphor stretched over engineering. It is the actual architecture. There is no virtual DOM because there is no need for a Cartesian theater. The multiple drafts model works because each draft is in the same format — s-expressions — so revision is natural. A draft can be inspected, compared, serialised, sent somewhere else, and revised further. Dennett's insight was that consciousness works this way. SX's insight is that rendering can too."))
|
||||
(~doc-section :title "IV. Heterophenomenology" :id "heterophenomenology"
|
||||
(~docs/section :title "IV. Heterophenomenology" :id "heterophenomenology"
|
||||
(p :class "text-stone-600"
|
||||
(a :href "https://en.wikipedia.org/wiki/Heterophenomenology" :class "text-violet-600 hover:underline" "Heterophenomenology") " is Dennett's method for studying consciousness. Instead of asking \"what is it like to be a bat?\" — a question we cannot answer — we ask the bat to tell us, and then we study " (em "the report") ". We take the subject's testimony seriously, catalogue it rigorously, but we do not take it as infallible. The report is data. We are scientists of the report.")
|
||||
(p :class "text-stone-600"
|
||||
"Most programming languages cannot report on themselves. JavaScript can " (code "toString()") " a function, but the result is a string — opaque, unparseable, implementation-dependent. Python can inspect a function's AST via " (code "ast.parse(inspect.getsource(f))") " — but the AST is a separate data structure, disconnected from the running code. The language's self-report is in a different format from the language itself. Studying it requires tools, transformations, bridges.")
|
||||
(p :class "text-stone-600"
|
||||
"SX is natively heterophenomenological. The language's self-report " (em "is") " the language. " (code "eval.sx") " is the evaluator reporting on how evaluation works — in the same s-expressions that it evaluates. " (code "parser.sx") " is the parser reporting on how parsing works — in the same syntax it parses. You study the report by reading it. You verify the report by running it. The report and the reality are the same object.")
|
||||
(~doc-code :lang "lisp" :code
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; The evaluator's self-report (from eval.sx):\n(define eval-expr\n (fn (expr env)\n (cond\n (number? expr) expr\n (string? expr) expr\n (symbol? expr) (env-get env expr)\n (list? expr) (eval-list expr env)\n :else (error \"Unknown expression type\"))))\n\n;; This is simultaneously:\n;; 1. A specification (what eval-expr does)\n;; 2. A program (it runs)\n;; 3. A report (the evaluator describing itself)\n;; Heterophenomenology without the hetero.")
|
||||
(p :class "text-stone-600"
|
||||
"Dennett insisted that heterophenomenology is the only honest method. First-person reports are unreliable — introspection gets things wrong. Third-person observation misses the subject's perspective. The middle path is to take the report as data and study it rigorously. SX's self-hosting spec is this middle path enacted in code: neither a first-person account (\"trust me, this is how it works\") nor a third-person observation (English prose describing the implementation), but a structured report that can be verified, compiled, and run."))
|
||||
(~doc-section :title "V. Where am I?" :id "where-am-i"
|
||||
(~docs/section :title "V. Where am I?" :id "where-am-i"
|
||||
(p :class "text-stone-600"
|
||||
"Dennett's thought experiment \"" (a :href "https://en.wikipedia.org/wiki/Where_Am_I%3F_(Dennett)" :class "text-violet-600 hover:underline" "Where Am I?") "\" imagines his brain removed from his body, connected by radio. His body walks around; his brain sits in a vat. Where is Dennett? Where the brain is? Where the body is? The question has no clean answer because identity is not located in a single place — it is distributed across the system.")
|
||||
(p :class "text-stone-600"
|
||||
"Where is an SX component? On the server, it is a Python object — a closure with a body and bound environment. On the wire, it is text: " (code "(~card :title \"Hello\")") ". In the browser, it is a JavaScript function registered in the component environment. In the DOM, it is a tree of elements. Which one is the \"real\" component? All of them. None of them. The component is not located in one runtime — it is the pattern that persists across all of them.")
|
||||
"Where is an SX component? On the server, it is a Python object — a closure with a body and bound environment. On the wire, it is text: " (code "(~essays/sx-and-dennett/card :title \"Hello\")") ". In the browser, it is a JavaScript function registered in the component environment. In the DOM, it is a tree of elements. Which one is the \"real\" component? All of them. None of them. The component is not located in one runtime — it is the pattern that persists across all of them.")
|
||||
(p :class "text-stone-600"
|
||||
"This is Dennett's point about personal identity applied to software identity. The SX component " (code "~card") " is defined in a " (code ".sx") " file, compiled by the Python bootstrapper into the server evaluator, transmitted as SX wire format to the browser, compiled by the JavaScript bootstrapper into the client evaluator, and rendered into DOM. At every stage, it is " (code "~card") ". At no single stage is it " (em "the") " " (code "~card") ". The identity is the pattern, not the substrate.")
|
||||
"This is Dennett's point about personal identity applied to software identity. The SX component " (code "~essays/sx-and-dennett/card") " is defined in a " (code ".sx") " file, compiled by the Python bootstrapper into the server evaluator, transmitted as SX wire format to the browser, compiled by the JavaScript bootstrapper into the client evaluator, and rendered into DOM. At every stage, it is " (code "~essays/sx-and-dennett/card") ". At no single stage is it " (em "the") " " (code "~essays/sx-and-dennett/card") ". The identity is the pattern, not the substrate.")
|
||||
(p :class "text-stone-600"
|
||||
"Most frameworks bind component identity to a substrate. A React component is a JavaScript function. Full stop. It cannot exist outside the JavaScript runtime. Its identity is its implementation. SX components have substrate independence — the same definition runs on any host that implements the SX platform interface. The component's identity is its specification, not its execution."))
|
||||
(~doc-section :title "VI. Competence without comprehension" :id "competence"
|
||||
(~docs/section :title "VI. Competence without comprehension" :id "competence"
|
||||
(p :class "text-stone-600"
|
||||
"Dennett argued in " (a :href "https://en.wikipedia.org/wiki/From_Bacteria_to_Bach_and_Back" :class "text-violet-600 hover:underline" "From Bacteria to Bach and Back") " (2017) that evolution produces " (em "competence without comprehension") ". Termites build elaborate mounds without understanding architecture. Neurons produce consciousness without understanding thought. The competence is real — the mound regulates temperature, the brain solves problems — but there is no comprehension anywhere in the system. No termite has a blueprint. No neuron knows it is thinking.")
|
||||
(p :class "text-stone-600"
|
||||
"A macro is competence without comprehension. " (code "defcomp") " expands into a component registration — it " (em "does") " the right thing — but it does not \"know\" what a component is. It is a pattern-matching function on lists that produces other lists. The expansion is mechanical, local, uncomprehending. Yet the result is a fully functional component that participates in the rendering pipeline, responds to props, composes with other components. Competence. No comprehension.")
|
||||
(~doc-code :lang "lisp" :code
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; defcomp is a macro — mechanical list transformation\n(defmacro defcomp (name params &rest body)\n `(define ,name (make-component ,params ,@body)))\n\n;; It does not \"understand\" components.\n;; It rearranges symbols according to a rule.\n;; The resulting component works perfectly.\n;; Competence without comprehension.")
|
||||
(p :class "text-stone-600"
|
||||
"The bootstrap compiler is another level of the same phenomenon. " (code "bootstrap_js.py") " reads " (code "eval.sx") " and emits JavaScript. It does not understand SX semantics — it applies mechanical transformation rules to s-expression ASTs. Yet its output is a correct, complete SX evaluator. The compiler is competent (it produces working code) without being comprehending (it has no model of what SX expressions mean).")
|
||||
(p :class "text-stone-600"
|
||||
"Dennett used this insight to deflate the mystery of intelligence: you do not need a homunculus — a little man inside the machine who \"really\" understands — you just need enough competence at each level. SX embodies this architecturally. No part of the system comprehends the whole. The parser does not know about rendering. The evaluator does not know about HTTP. The bootstrap compiler does not know about the DOM. Each part is a competent specialist. The system works because the parts compose, not because any part understands the composition."))
|
||||
(~doc-section :title "VII. Intuition pumps" :id "intuition-pumps"
|
||||
(~docs/section :title "VII. Intuition pumps" :id "intuition-pumps"
|
||||
(p :class "text-stone-600"
|
||||
"Dennett called his thought experiments \"" (a :href "https://en.wikipedia.org/wiki/Intuition_pump" :class "text-violet-600 hover:underline" "intuition pumps") "\" — devices for moving your intuitions from one place to another, making the unfamiliar familiar by analogy. Not proofs. Not arguments. Machines for changing how you see.")
|
||||
(p :class "text-stone-600"
|
||||
"SX components are intuition pumps. A " (code "defcomp") " definition is not just executable code — it is a device for showing someone what a piece of UI " (em "is") ". Reading " (code "(defcomp ~card (&key title &rest children) (div :class \"card\" (h2 title) children))") " tells you the contract, the structure, and the output in a single expression. It pumps your intuition about what \"card\" means in this application.")
|
||||
"SX components are intuition pumps. A " (code "defcomp") " definition is not just executable code — it is a device for showing someone what a piece of UI " (em "is") ". Reading " (code "(defcomp ~essays/sx-and-dennett/card (&key title &rest children) (div :class \"card\" (h2 title) children))") " tells you the contract, the structure, and the output in a single expression. It pumps your intuition about what \"card\" means in this application.")
|
||||
(p :class "text-stone-600"
|
||||
"Compare this to a React component:")
|
||||
(~doc-code :lang "lisp" :code
|
||||
";; React: you must simulate the runtime in your head\nfunction Card({ title, children }) {\n return (\n <div className=\"card\">\n <h2>{title}</h2>\n {children}\n </div>\n );\n}\n\n;; SX: the definition IS the output\n(defcomp ~card (&key title &rest children)\n (div :class \"card\"\n (h2 title)\n children))")
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; React: you must simulate the runtime in your head\nfunction Card({ title, children }) {\n return (\n <div className=\"card\">\n <h2>{title}</h2>\n {children}\n </div>\n );\n}\n\n;; SX: the definition IS the output\n(defcomp ~essays/sx-and-dennett/card (&key title &rest children)\n (div :class \"card\"\n (h2 title)\n children))")
|
||||
(p :class "text-stone-600"
|
||||
"The React version requires you to know that JSX compiles to createElement calls, that className maps to the class attribute, that curly braces switch to JavaScript expressions, and that the function return value becomes the rendered output. You must simulate a compiler in your head. The SX version requires you to know that lists are expressions and keywords are attributes. The gap between the definition and what it produces is smaller. The intuition pump is more efficient — fewer moving parts, less machinery between the reader and the meaning.")
|
||||
(p :class "text-stone-600"
|
||||
"Dennett valued intuition pumps because philosophy is full of false intuitions. The Cartesian theater feels right — of course there is a place where consciousness happens. But it is wrong. Intuition pumps help you " (em "see") " that it is wrong by giving you a better picture. SX is an intuition pump for web development: of course you need a build step, of course you need a virtual DOM, of course you need separate languages for structure and style and behaviour. But you don't. The s-expression is the better picture."))
|
||||
(~doc-section :title "VIII. The Joycean machine" :id "joycean-machine"
|
||||
(~docs/section :title "VIII. The Joycean machine" :id "joycean-machine"
|
||||
(p :class "text-stone-600"
|
||||
"In Consciousness Explained, Dennett describes the brain as a \"" (a :href "https://en.wikipedia.org/wiki/Consciousness_Explained" :class "text-violet-600 hover:underline" "Joycean machine") "\" — a virtual machine running on the parallel hardware of the brain, producing the serial narrative of conscious experience. Just as a word processor is a virtual machine running on silicon, consciousness is a virtual machine running on neurons. The virtual machine is real — it does real work, produces real effects — even though it is implemented in a substrate that knows nothing about it.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -97,7 +97,7 @@
|
||||
"The deeper parallel: Dennett argued that the Joycean machine is " (em "not an illusion") ". The serial narrative of consciousness is not fake — it is the real output of real processing, even though the underlying hardware is parallel and narrativeless. Similarly, SX's component model is not a convenient fiction layered over \"real\" HTML. It is the real structure of the application. The components are the thing. The HTML is the substrate, not the reality.")
|
||||
(p :class "text-stone-600"
|
||||
"And like Dennett's Joycean machine, SX's virtual machine can reflect on itself. It can inspect its own running code, define its own evaluator, test its own semantics. The virtual machine is aware of itself — not in the sense of consciousness, but in the functional sense of self-modelling. The spec models the evaluator. The evaluator runs the spec. The virtual machine contains a description of itself, and that description works."))
|
||||
(~doc-section :title "IX. Quining" :id "quining"
|
||||
(~docs/section :title "IX. Quining" :id "quining"
|
||||
(p :class "text-stone-600"
|
||||
"Dennett borrowed the term \"" (a :href "https://en.wikipedia.org/wiki/Qualia#Dennett's_criticism" :class "text-violet-600 hover:underline" "quining") "\" from the logician " (a :href "https://en.wikipedia.org/wiki/Willard_Van_Orman_Quine" :class "text-violet-600 hover:underline" "W. V. O. Quine") " — a philosopher who argued that many seemingly deep concepts dissolve under scrutiny. Dennett \"quined\" qualia — the supposedly irreducible subjective qualities of experience — arguing that they are not what they seem, that the intuition of an inner experiential essence is a philosophical illusion.")
|
||||
(p :class "text-stone-600"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
(defcomp ~essay-sx-and-wittgenstein ()
|
||||
(~doc-page :title "SX and Wittgenstein"
|
||||
(defcomp ~essays/sx-and-wittgenstein/essay-sx-and-wittgenstein ()
|
||||
(~docs/page :title "SX and Wittgenstein"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
"The limits of my language are the limits of my world.")
|
||||
(~doc-section :title "I. Language games" :id "language-games"
|
||||
(~docs/section :title "I. Language games" :id "language-games"
|
||||
(p :class "text-stone-600"
|
||||
"In 1953, Ludwig " (a :href "https://en.wikipedia.org/wiki/Ludwig_Wittgenstein" :class "text-violet-600 hover:underline" "Wittgenstein") " published " (a :href "https://en.wikipedia.org/wiki/Philosophical_Investigations" :class "text-violet-600 hover:underline" "Philosophical Investigations") " — a book that dismantled the theory of language he had built in his own earlier work. The " (a :href "https://en.wikipedia.org/wiki/Tractatus_Logico-Philosophicus" :class "text-violet-600 hover:underline" "Tractatus") " had argued that language pictures the world: propositions mirror facts, and the structure of a sentence corresponds to the structure of reality. The Investigations abandoned this. Language does not picture anything. Language is " (em "used") ".")
|
||||
(p :class "text-stone-600"
|
||||
@@ -11,7 +11,7 @@
|
||||
"Web development is a proliferation of language games. HTML is one game — a markup game where tags denote structure. CSS is another — a declaration game where selectors denote style. JavaScript is a third — an imperative game where statements denote behaviour. JSX is a fourth game layered on top of the third, pretending to be the first. TypeScript is a fifth game that annotates the third. Each has its own grammar, its own rules, its own way of meaning.")
|
||||
(p :class "text-stone-600"
|
||||
"SX collapses these into a single game. " (code "(div :class \"p-4\" (h2 title))") " is simultaneously structure (a div containing an h2), style (the class attribute), and behaviour (the symbol " (code "title") " is evaluated). There is one syntax, one set of rules, one way of meaning. Not because the distinctions between structure, style, and behaviour have been erased — they haven't — but because they are all expressed in the same language game."))
|
||||
(~doc-section :title "II. The limits of my language" :id "limits"
|
||||
(~docs/section :title "II. The limits of my language" :id "limits"
|
||||
(p :class "text-stone-600"
|
||||
"\"" (a :href "https://en.wikipedia.org/wiki/Tractatus_Logico-Philosophicus" :class "text-violet-600 hover:underline" "Die Grenzen meiner Sprache bedeuten die Grenzen meiner Welt") "\" — the limits of my language mean the limits of my world. This is proposition 5.6 of the Tractatus, and it is the most important sentence Wittgenstein ever wrote.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -20,31 +20,31 @@
|
||||
"If your language is React, your world is components that re-render. You can compose components. You can pass props. You cannot inspect a component's structure at runtime without React DevTools. You cannot serialize a component tree to a format another framework can consume. You cannot send a component over HTTP and have it work on the other side without the same React runtime. The language has composition but not portability, so your world has composition but not portability.")
|
||||
(p :class "text-stone-600"
|
||||
"If your language is s-expressions, your world is " (em "expressions") ". An expression can represent a DOM node, a function call, a style declaration, a macro transformation, a component definition, a wire-format payload, or a specification of the evaluator itself. The language has no built-in limits on what can be expressed, because the syntax — the list — can represent anything. The limits of the language are only the limits of what you choose to evaluate.")
|
||||
(~doc-code :lang "lisp" :code
|
||||
";; The same syntax expresses everything:\n(div :class \"card\" (h2 \"Title\")) ;; structure\n(css :flex :gap-4 :p-2) ;; style\n(defcomp ~card (&key title) (div title)) ;; abstraction\n(defmacro ~log (x) `(console.log ,x)) ;; metaprogramming\n(quote (div :class \"card\" (h2 \"Title\"))) ;; data about structure")
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; The same syntax expresses everything:\n(div :class \"card\" (h2 \"Title\")) ;; structure\n(css :flex :gap-4 :p-2) ;; style\n(defcomp ~essays/sx-and-wittgenstein/card (&key title) (div title)) ;; abstraction\n(defmacro ~log (x) `(console.log ,x)) ;; metaprogramming\n(quote (div :class \"card\" (h2 \"Title\"))) ;; data about structure")
|
||||
(p :class "text-stone-600"
|
||||
"Wittgenstein's proposition cuts both ways. A language that limits you to documents limits your world to documents. A language that can express anything — because its syntax is the minimal recursive structure — limits your world to " (em "everything") "."))
|
||||
(~doc-section :title "III. Whereof one cannot speak" :id "silence"
|
||||
(~docs/section :title "III. Whereof one cannot speak" :id "silence"
|
||||
(p :class "text-stone-600"
|
||||
"The Tractatus ends: \"" (a :href "https://en.wikipedia.org/wiki/Tractatus_Logico-Philosophicus#Proposition_7" :class "text-violet-600 hover:underline" "Whereof one cannot speak, thereof one must be silent") ".\" Proposition 7. The things that cannot be said in a language simply do not exist within that language's world.")
|
||||
(p :class "text-stone-600"
|
||||
"HTML cannot speak of composition. It is silent on components. You cannot define " (code "~card") " in HTML. You can define " (code "<template>") " and " (code "<slot>") " in Web Components, but that requires JavaScript — you have left HTML's language game and entered another.")
|
||||
"HTML cannot speak of composition. It is silent on components. You cannot define " (code "~essays/sx-and-wittgenstein/card") " in HTML. You can define " (code "<template>") " and " (code "<slot>") " in Web Components, but that requires JavaScript — you have left HTML's language game and entered another.")
|
||||
(p :class "text-stone-600"
|
||||
"CSS cannot speak of conditions. It is silent on logic. You cannot say \"if the user is logged in, use this colour.\" You can use " (code ":has()") " and " (code "@container") " queries, but these are conditions about " (em "the document") ", not conditions about " (em "the application") ". CSS can only speak of what CSS can see.")
|
||||
(p :class "text-stone-600"
|
||||
"JavaScript can speak of almost everything — but it speaks in statements, not expressions. The difference matters. A statement executes and is gone. An expression evaluates to a value. Values compose. Statements require sequencing. React discovered this when it moved from class components (imperative, statement-oriented) to hooks (closer to expressions, but not quite — hence the rules of hooks).")
|
||||
(p :class "text-stone-600"
|
||||
"S-expressions are pure expression. Every form evaluates to a value. There is nothing that cannot be spoken, because lists can nest arbitrarily and symbols can name anything. There is no proposition 7 for s-expressions — no enforced silence, no boundary where the language gives out. The programmer decides where to draw the line, not the syntax."))
|
||||
(~doc-section :title "IV. Family resemblance" :id "family-resemblance"
|
||||
(~docs/section :title "IV. Family resemblance" :id "family-resemblance"
|
||||
(p :class "text-stone-600"
|
||||
"Wittgenstein argued that concepts do not have sharp definitions. What is a \"" (a :href "https://en.wikipedia.org/wiki/Family_resemblance" :class "text-violet-600 hover:underline" "game") "\"? Chess, football, solitaire, ring-a-ring-o'-roses — they share no single essential feature. Instead, they form a network of overlapping similarities. A " (a :href "https://en.wikipedia.org/wiki/Family_resemblance" :class "text-violet-600 hover:underline" "family resemblance") ".")
|
||||
(p :class "text-stone-600"
|
||||
"Web frameworks have this property. What is a \"component\"? In React, it is a function that returns JSX. In Vue, it is an object with a template property. In Svelte, it is a " (code ".svelte") " file. In Web Components, it is a class that extends HTMLElement. In Angular, it is a TypeScript class with a decorator. These are not the same thing. They share a family resemblance — they all produce reusable UI — but their definitions are incompatible. A React component cannot be used in Vue. A Svelte component cannot be used in Angular. The family does not communicate.")
|
||||
(p :class "text-stone-600"
|
||||
"In SX, a component is a list whose first element is a symbol beginning with " (code "~") ". That is the complete definition. It is not a function, not a class, not a file, not a decorator. It is a " (em "naming convention on a data structure") ". Any system that can process lists can process SX components. Python evaluates them on the server. JavaScript evaluates them in the browser. A future Rust evaluator could evaluate them on an embedded device. The family resemblance sharpens into actual identity: a component is a component is a component, because the representation is the same everywhere.")
|
||||
(~doc-code :lang "lisp" :code
|
||||
";; This is a component in every SX evaluator:\n(defcomp ~greeting (&key name)\n (div :class \"p-4\"\n (h2 (str \"Hello, \" name))))\n\n;; The same s-expression is:\n;; - parsed by the same parser\n;; - evaluated by the same eval rules\n;; - rendered by the same render spec\n;; - on every host, in every context"))
|
||||
(~doc-section :title "V. Private language" :id "private-language"
|
||||
(~docs/code :lang "lisp" :code
|
||||
";; This is a component in every SX evaluator:\n(defcomp ~essays/sx-and-wittgenstein/greeting (&key name)\n (div :class \"p-4\"\n (h2 (str \"Hello, \" name))))\n\n;; The same s-expression is:\n;; - parsed by the same parser\n;; - evaluated by the same eval rules\n;; - rendered by the same render spec\n;; - on every host, in every context"))
|
||||
(~docs/section :title "V. Private language" :id "private-language"
|
||||
(p :class "text-stone-600"
|
||||
"The " (a :href "https://en.wikipedia.org/wiki/Private_language_argument" :class "text-violet-600 hover:underline" "private language argument") " is one of Wittgenstein's most provocative claims: there can be no language whose words refer to the speaker's private sensations and nothing else. Language requires public criteria — shared rules that others can check. A word that means something only to you is not a word at all.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -53,7 +53,7 @@
|
||||
"S-expressions are radically public. The syntax is universal: open paren, atoms, close paren. Any Lisp, any s-expression processor, any JSON-to-sexp converter can read them. The SX evaluator adds meaning — " (code "defcomp") ", " (code "defmacro") ", " (code "if") ", " (code "let") " — but these meanings are specified in s-expressions themselves (" (code "eval.sx") "), readable by anyone. There is no private knowledge. There is no compilation step that transforms the public syntax into a private intermediate form. The source is the artefact.")
|
||||
(p :class "text-stone-600"
|
||||
"This is why SX can be self-hosting. A private language cannot define itself — it would need a second private language to define the first, and a third to define the second. A public language, one whose rules are expressible in its own terms, can close the loop. " (code "eval.sx") " defines SX in SX. The language defines itself publicly, in a form that any reader can inspect."))
|
||||
(~doc-section :title "VI. Showing and saying" :id "showing-saying"
|
||||
(~docs/section :title "VI. Showing and saying" :id "showing-saying"
|
||||
(p :class "text-stone-600"
|
||||
"The Tractatus makes a crucial distinction between what can be " (em "said") " and what can only be " (em "shown") ". Logic, Wittgenstein argued, cannot be said — it can only be shown by the structure of propositions. You cannot step outside logic to make statements about logic; you can only exhibit logical structure by using it.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -62,14 +62,14 @@
|
||||
"SX " (em "shows") " its semantics. The specification is not a description of the language — it is the language operating on itself. " (code "eval.sx") " does not say \"the evaluator dispatches on the type of expression.\" It " (em "is") " an evaluator that dispatches on the type of expression. " (code "parser.sx") " does not say \"strings are delimited by double quotes.\" It " (em "is") " a parser that recognises double-quoted strings.")
|
||||
(p :class "text-stone-600"
|
||||
"This is exactly the distinction Wittgenstein drew. What can be said (described, documented, specified in English) is limited. What can be shown (exhibited, demonstrated, enacted) goes further. SX's self-hosting spec shows its semantics by " (em "being") " them — the strongest form of specification possible. The spec cannot be wrong, because the spec runs."))
|
||||
(~doc-section :title "VII. The beetle in the box" :id "beetle"
|
||||
(~docs/section :title "VII. The beetle in the box" :id "beetle"
|
||||
(p :class "text-stone-600"
|
||||
"Wittgenstein's " (a :href "https://en.wikipedia.org/wiki/Private_language_argument#The_beetle_in_a_box" :class "text-violet-600 hover:underline" "beetle-in-a-box") " thought experiment: suppose everyone has a box, and everyone calls what's inside their box a \"beetle.\" No one can look in anyone else's box. The word \"beetle\" refers to whatever is in the box — but since no one can check, the contents might be different for each person, or the box might even be empty. The word gets its meaning from the " (em "game") " it plays in public, not from the private contents of the box.")
|
||||
(p :class "text-stone-600"
|
||||
"A web component is a beetle in a box. You call it " (code "<my-button>") " but what's inside — Shadow DOM, event listeners, internal state, style encapsulation — is private. Two components with the same tag name might do completely different things. Two frameworks with the same concept of \"component\" might mean completely different things by it. The word \"component\" functions in the language game of developer conversation, but the actual contents are private to each implementation.")
|
||||
(p :class "text-stone-600"
|
||||
"In SX, you can open the box. Components are data. " (code "(defcomp ~card (&key title) (div title))") " — the entire definition is visible, inspectable, serializable. There is no shadow DOM, no hidden state machine, no encapsulated runtime. The component's body is an s-expression. You can quote it, transform it, analyse it, send it over HTTP, evaluate it in a different context. The beetle is on the table."))
|
||||
(~doc-section :title "VIII. The fly-bottle" :id "fly-bottle"
|
||||
"In SX, you can open the box. Components are data. " (code "(defcomp ~essays/sx-and-wittgenstein/card (&key title) (div title))") " — the entire definition is visible, inspectable, serializable. There is no shadow DOM, no hidden state machine, no encapsulated runtime. The component's body is an s-expression. You can quote it, transform it, analyse it, send it over HTTP, evaluate it in a different context. The beetle is on the table."))
|
||||
(~docs/section :title "VIII. The fly-bottle" :id "fly-bottle"
|
||||
(p :class "text-stone-600"
|
||||
"\"What is your aim in philosophy?\" Wittgenstein was asked. \"" (a :href "https://en.wikipedia.org/wiki/Philosophical_Investigations#Meaning_and_definition" :class "text-violet-600 hover:underline" "To show the fly the way out of the fly-bottle") ".\"")
|
||||
(p :class "text-stone-600"
|
||||
@@ -80,7 +80,7 @@
|
||||
"\"How do we share state between server and client?\" Fly-bottle. This is hard when the server speaks one language (Python templates) and the client speaks another (JavaScript). When both speak s-expressions, and the wire format IS the source syntax, state transfer is serialisation — which is identity for s-expressions. " (code "aser") " serialises an SX expression as an SX expression. The server and client share state by sharing code.")
|
||||
(p :class "text-stone-600"
|
||||
"SX does not solve these problems. It dissolves them — by removing the language confusion that created them. Wittgenstein's method, applied to web development: the problems were never real. They were artefacts of speaking in the wrong language."))
|
||||
(~doc-section :title "IX. Back to rough ground" :id "rough-ground"
|
||||
(~docs/section :title "IX. Back to rough ground" :id "rough-ground"
|
||||
(p :class "text-stone-600"
|
||||
"\"We have got on to slippery ice where there is no friction and so in a certain sense the conditions are ideal, but also, just because of that, we are unable to walk. We want to walk: so we need " (a :href "https://en.wikipedia.org/wiki/Philosophical_Investigations" :class "text-violet-600 hover:underline" "friction") ". Back to the rough ground!\"")
|
||||
(p :class "text-stone-600"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
(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 ~essays/sx-sucks/essay-sx-sucks ()
|
||||
(~docs/page :title "sx sucks" (~docs/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.")) (~docs/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.")) (~docs/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.")) (~docs/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.")) (~docs/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.")) (~docs/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.")) (~docs/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."))))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,12 +2,12 @@
|
||||
;; The Art Chain
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~essay-the-art-chain ()
|
||||
(~doc-page :title "The Art Chain"
|
||||
(defcomp ~essays/the-art-chain/essay-the-art-chain ()
|
||||
(~docs/page :title "The Art Chain"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
"On making, self-making, and the chain of artifacts that produces itself.")
|
||||
|
||||
(~doc-section :title "I. Ars" :id "ars"
|
||||
(~docs/section :title "I. Ars" :id "ars"
|
||||
(p :class "text-stone-600"
|
||||
"The Latin word " (em "ars") " means something made with skill. Not art as in paintings on gallery walls. Art as in " (em "artifice") ", " (em "artifact") ", " (em "artisan") ". The made thing. The Greek " (em "techne") " is the same word — craft, skill, the knowledge of how to make. There was no distinction between art and engineering because there was no distinction to make.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -15,16 +15,16 @@
|
||||
(p :class "text-stone-600"
|
||||
"Software is " (em "ars") ". Obviously. It is the most " (em "ars") " thing we have ever built — pure made-ness, structure conjured from nothing, shaped entirely by the maker's skill and intent. There is no raw material. No marble to chisel, no pigment to mix. Just thought, made concrete in symbols."))
|
||||
|
||||
(~doc-section :title "II. The spec at the centre" :id "spec"
|
||||
(~docs/section :title "II. The spec at the centre" :id "spec"
|
||||
(p :class "text-stone-600"
|
||||
"SX has a peculiar architecture. At its centre sits a specification — a set of s-expression files that define the language. Not a description of the language. Not documentation " (em "about") " the language. The specification " (em "is") " the language. It is simultaneously a formal definition and executable code. You can read it as a document or run it as a program. It does not describe how to build an SX evaluator; it " (em "is") " an SX evaluator, expressed in the language it defines.")
|
||||
(p :class "text-stone-600"
|
||||
"This is the nucleus. Everything else radiates outward from it.")
|
||||
(~doc-code :code (highlight ";; The spec defines eval-expr\n;; eval-expr evaluates the spec\n;; The spec is an artifact that makes itself\n\n(define eval-expr\n (fn (expr env)\n (cond\n (number? expr) expr\n (string? expr) expr\n (symbol? expr) (env-get env (symbol-name expr))\n (list? expr) (eval-list expr env)\n :else expr)))" "lisp"))
|
||||
(~docs/code :code (highlight ";; The spec defines eval-expr\n;; eval-expr evaluates the spec\n;; The spec is an artifact that makes itself\n\n(define eval-expr\n (fn (expr env)\n (cond\n (number? expr) expr\n (string? expr) expr\n (symbol? expr) (env-get env (symbol-name expr))\n (list? expr) (eval-list expr env)\n :else expr)))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
"From this nucleus, concentric rings unfurl:"))
|
||||
|
||||
(~doc-section :title "III. The rings" :id "rings"
|
||||
(~docs/section :title "III. The rings" :id "rings"
|
||||
(p :class "text-stone-600"
|
||||
"The first ring is the " (strong "bootstrapper") ". It reads the spec and emits a native implementation — JavaScript, Python, or any other target. The bootstrapper is a translator: it takes the made thing (the spec) and makes another thing (an implementation) that behaves identically. The spec's knowledge is preserved in the translation. Nothing is added, nothing is lost.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -36,7 +36,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"The fifth ring is " (strong "this website") " — which renders the spec's source code using the runtime the spec produced, displayed in components written in the language the spec defines, navigated by an engine the spec specifies. The documentation is the thing documenting itself."))
|
||||
|
||||
(~doc-section :title "IV. The chain" :id "chain"
|
||||
(~docs/section :title "IV. The chain" :id "chain"
|
||||
(p :class "text-stone-600"
|
||||
"Each ring is an artifact — a made thing. And each artifact is made " (em "by") " the artifact inside it. The spec makes the bootstrapper's output. The runtime makes the application's output. The application makes the page the user sees. It is a chain of making.")
|
||||
(p :class "text-stone-600"
|
||||
@@ -50,7 +50,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"These three properties together — content addressing, deterministic derivation, self-verification — are what a blockchain provides. But here there is no proof-of-work, no tokens, no artificial scarcity, no consensus mechanism between untrusted parties. The \"mining\" is bootstrapping. The \"consensus\" is mathematical proof. The \"value\" is that anyone can take the spec, derive an implementation, and " (em "know") " it is correct."))
|
||||
|
||||
(~doc-section :title "V. Universal analysis" :id "analysis"
|
||||
(~docs/section :title "V. Universal analysis" :id "analysis"
|
||||
(p :class "text-stone-600"
|
||||
"Here is the consequence that takes time to absorb: any tool that can analyse the spec can analyse " (em "everything the spec produces") ".")
|
||||
(p :class "text-stone-600"
|
||||
@@ -60,7 +60,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"And the analysis tools are " (em "inside") " the chain. They are artifacts too, written in SX, subject to the same analysis they perform. The type checker can type-check itself. The prover can prove properties about itself. This is not a bug or a curiosity — it is the point. A system that cannot reason about itself is a system that must be reasoned about from outside, by tools written in other languages, maintained by other processes, trusted for other reasons. A self-analysing system closes the loop."))
|
||||
|
||||
(~doc-section :title "VI. The art in the chain" :id "art"
|
||||
(~docs/section :title "VI. The art in the chain" :id "art"
|
||||
(p :class "text-stone-600"
|
||||
"So what is the art chain? It is a chain of artifacts — made things — where each link produces the next, the whole chain can verify itself, and the chain's identity is its content.")
|
||||
(p :class "text-stone-600"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(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 ~essays/why-sexps/essay-why-sexps ()
|
||||
(~docs/page :title "Why S-Expressions Over HTML Attributes" (~docs/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.")) (~docs/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.")) (~docs/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."))))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
(defcomp ~essay-zero-tooling ()
|
||||
(~doc-page :title "Tools for Fools"
|
||||
(defcomp ~essays/zero-tooling/essay-zero-tooling ()
|
||||
(~docs/page :title "Tools for Fools"
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -11,7 +11,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -30,7 +30,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"SX uses " (strong "none of them") "."))
|
||||
|
||||
(~doc-section :title "Why each tool is unnecessary" :id "why-unnecessary"
|
||||
(~docs/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.")
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -81,7 +81,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -99,7 +99,7 @@
|
||||
(p :class "text-stone-600"
|
||||
"What pushed him to use agentic AI was when several of the keys on his keyboard stopped working. Too much coding! AI LLMs don't mind typos."))
|
||||
|
||||
(~doc-section :title "Why this only works with s-expressions" :id "why-sexps"
|
||||
(~docs/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"
|
||||
@@ -111,7 +111,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
@@ -121,7 +121,7 @@
|
||||
(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"
|
||||
(~docs/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"
|
||||
|
||||
Reference in New Issue
Block a user