Files
rose-ash/sx/sx/essays/react-is-hypermedia.sx
giles 3ca89ef765 Self-hosted z3.sx translator, prove.sx prover, parser unicode, auto reader macros
- z3.sx: SX-to-SMT-LIB translator written in SX (359 lines), replaces Python translation logic
- prove.sx: SMT-LIB satisfiability checker in SX — proves all 91 primitives sat by construction
- Parser: support unicode characters (em-dash, accented letters) in symbols
- Auto-resolve reader macros: #name finds name-translate in component env, no Python registration
- Platform primitives: type-of, symbol-name, keyword-name, sx-parse registered in primitives.py
- Cond heuristic: predicates ending in ? recognized as Clojure-style tests
- Library loading: z3.sx loaded at startup with reload callbacks for hot-reload ordering
- reader_z3.py: rewritten as thin shell delegating to z3.sx
- Split monolithic .sx files: essays (22), plans (13), reactive-islands (6) into separate files

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

87 lines
13 KiB
Plaintext

;; ---------------------------------------------------------------------------
;; React is Hypermedia
;; ---------------------------------------------------------------------------
(defcomp ~essay-react-is-hypermedia ()
(~doc-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"
(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"
"And yet. Consider what a React Island actually is:")
(ul :class "list-disc pl-6 space-y-1 text-stone-600"
(li "It is embedded in a server-rendered page.")
(li "Its initial content is delivered as HTML (or as serialised SX, which the client renders to DOM).")
(li "It occupies a region of the page — a bounded area with a defined boundary.")
(li "It responds to user interaction by mutating its own DOM.")
(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"
(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"
"A traditional SPA violates this. The client has its own router, its own state machine, its own API client that knows the server's URL structure. The HTML is a shell; the actual interface is constructed from JavaScript and API calls. The representation is not the interface — the representation is a loading spinner while the real interface builds itself.")
(p :class "text-stone-600"
"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"
(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
"(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.")
(p :class "text-stone-600"
"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"
(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"
(li (span :class "font-semibold" "L0 — Static server rendering.") " No client interactivity. The server evaluates the full component tree and sends HTML. Pure hypermedia. " (code "(div :class \"card\" (h2 title))") ".")
(li (span :class "font-semibold" "L1 — Hypermedia attributes.") " Server-rendered content with htmx-style attributes. " (code "(button :sx-get \"/items\" :sx-target \"#list\")") ". Still server-driven. The client swaps HTML fragments. Classic hypermedia with AJAX.")
(li (span :class "font-semibold" "L2 — Reactive islands.") " Self-contained client-side state within a server-rendered page. " (code "(defisland counter ...)") ". The island is a hypermedia control: the server delivers it, the client executes it. Signals, computed values, effects — all inside the island boundary.")
(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"
(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"
"Because React requires a " (em "build") ". JSX must be compiled. Modules must be bundled. The result is an opaque JavaScript blob that the server cannot inspect, the wire format cannot represent, and the client must execute before anything is visible. The component's specification — its source code — is lost by the time it reaches the browser.")
(p :class "text-stone-600"
"An SX island arrives at the browser as source. The same s-expression that defined the island on the server is the s-expression that the client parses and evaluates. There is no compilation, no bundling, no build step. The specification " (em "is") " the artifact.")
(p :class "text-stone-600"
"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"
(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
";; 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"
(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"
"In React, this specification is written in JavaScript and destroyed by compilation. The specification exists only in the developer's source file. The user receives a bundle.")
(p :class "text-stone-600"
"In SX, this specification is written in s-expressions, transmitted as s-expressions, parsed as s-expressions, and evaluated as s-expressions. The specification exists at every stage of the pipeline. It is never destroyed. It is never transformed into something else. It arrives at the browser intact, readable, inspectable.")
(p :class "text-stone-600"
"And the evaluator that interprets this specification? It is itself specified in s-expressions (" (code "eval.sx") "). And the renderer? Specified in s-expressions (" (code "render.sx") "). And the parser? Specified in s-expressions (" (code "parser.sx") "). The specification language specifies itself. The island's behavior is specified in a language whose behavior is specified in itself.")
(p :class "text-stone-600"
"A React Island is a hypermedia control. Its behavior is specified in SX. And SX is specified in SX. There is no layer beneath. The specification goes all the way down."))))