sx-tools: WASM kernel updates, TW/CSSX rework, content refresh, new debugging tools
Build tooling: updated OCaml bootstrapper, compile-modules, bundle.sh, sx-build-all. WASM browser: rebuilt sx_browser.bc.js/wasm, sx-platform-2.js, .sxbc bytecode files. CSSX/Tailwind: reworked cssx.sx templates and tw-layout, added tw-type support. Content: refreshed essays, plans, geography, reactive islands, docs, demos, handlers. New tools: bisect_sxbc.sh, test-spa.js, render-trace.sx, morph playwright spec. Tests: added test-match.sx, test-examples.sx, updated test-tw.sx and web tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,55 +7,55 @@
|
||||
;; --- Demo components with different affinities ---
|
||||
|
||||
(defcomp ~affinity-demo/aff-demo-auto (&key (label :as string?))
|
||||
(div :class "rounded border border-stone-200 bg-white p-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block w-2 h-2 rounded-full bg-stone-400")
|
||||
(span :class "text-sm font-mono text-stone-500" ":affinity :auto"))
|
||||
(p :class "text-stone-800" (or label "Pure component — no IO calls. Auto-detected as client-renderable."))))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-white p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block w-2 h-2 rounded-full bg-stone-400"))
|
||||
(span (~tw :tokens "text-sm font-mono text-stone-500") ":affinity :auto"))
|
||||
(p (~tw :tokens "text-stone-800") (or label "Pure component — no IO calls. Auto-detected as client-renderable."))))
|
||||
|
||||
(defcomp ~affinity-demo/aff-demo-client (&key (label :as string?))
|
||||
:affinity :client
|
||||
(div :class "rounded border border-blue-200 bg-blue-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block w-2 h-2 rounded-full bg-blue-400")
|
||||
(span :class "text-sm font-mono text-blue-600" ":affinity :client"))
|
||||
(p :class "text-blue-800" (or label "Explicitly client-rendered — even IO calls would be proxied."))))
|
||||
(div (~tw :tokens "rounded border border-blue-200 bg-blue-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block w-2 h-2 rounded-full bg-blue-400"))
|
||||
(span (~tw :tokens "text-sm font-mono text-blue-600") ":affinity :client"))
|
||||
(p (~tw :tokens "text-blue-800") (or label "Explicitly client-rendered — even IO calls would be proxied."))))
|
||||
|
||||
(defcomp ~affinity-demo/aff-demo-server (&key (label :as string?))
|
||||
:affinity :server
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block w-2 h-2 rounded-full bg-amber-400")
|
||||
(span :class "text-sm font-mono text-amber-600" ":affinity :server"))
|
||||
(p :class "text-amber-800" (or label "Always server-rendered — auth-sensitive or secret-dependent."))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block w-2 h-2 rounded-full bg-amber-400"))
|
||||
(span (~tw :tokens "text-sm font-mono text-amber-600") ":affinity :server"))
|
||||
(p (~tw :tokens "text-amber-800") (or label "Always server-rendered — auth-sensitive or secret-dependent."))))
|
||||
|
||||
(defcomp ~affinity-demo/aff-demo-io-auto ()
|
||||
(div :class "rounded border border-red-200 bg-red-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block w-2 h-2 rounded-full bg-red-400")
|
||||
(span :class "text-sm font-mono text-red-600" ":affinity :auto + IO"))
|
||||
(p :class "text-red-800 mb-3" "Auto affinity with IO dependency — auto-detected as server-rendered.")
|
||||
(div (~tw :tokens "rounded border border-red-200 bg-red-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block w-2 h-2 rounded-full bg-red-400"))
|
||||
(span (~tw :tokens "text-sm font-mono text-red-600") ":affinity :auto + IO"))
|
||||
(p (~tw :tokens "text-red-800 mb-3") "Auto affinity with IO dependency — auto-detected as server-rendered.")
|
||||
(~docs/code :src (highlight "(render-target name env io-names)" "lisp"))))
|
||||
|
||||
(defcomp ~affinity-demo/aff-demo-io-client ()
|
||||
:affinity :client
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block w-2 h-2 rounded-full bg-violet-400")
|
||||
(span :class "text-sm font-mono text-violet-600" ":affinity :client + IO"))
|
||||
(p :class "text-violet-800 mb-3" "Client affinity overrides IO — calls proxied to server via /sx/io/.")
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block w-2 h-2 rounded-full bg-violet-400"))
|
||||
(span (~tw :tokens "text-sm font-mono text-violet-600") ":affinity :client + IO"))
|
||||
(p (~tw :tokens "text-violet-800 mb-3") "Client affinity overrides IO — calls proxied to server via /sx/io/.")
|
||||
(~docs/code :src (highlight "(component-affinity comp)" "lisp"))))
|
||||
|
||||
|
||||
;; --- Main page component ---
|
||||
|
||||
(defcomp ~affinity-demo/content (&key components page-plans)
|
||||
(div :class "space-y-8"
|
||||
(div :class "border-b border-stone-200 pb-6"
|
||||
(h1 :class "text-2xl font-bold text-stone-900" "Affinity Annotations")
|
||||
(p :class "mt-2 text-stone-600"
|
||||
(div (~tw :tokens "space-y-8")
|
||||
(div (~tw :tokens "border-b border-stone-200 pb-6")
|
||||
(h1 (~tw :tokens "text-2xl font-bold text-stone-900") "Affinity Annotations")
|
||||
(p (~tw :tokens "mt-2 text-stone-600")
|
||||
"Phase 7a: components declare where they prefer to render. The "
|
||||
(code :class "bg-stone-100 px-1 rounded text-violet-700" "render-target")
|
||||
(code (~tw :tokens "bg-stone-100 px-1 rounded text-violet-700") "render-target")
|
||||
" function in deps.sx combines the annotation with IO analysis to produce a per-component boundary decision."))
|
||||
|
||||
;; Syntax
|
||||
@@ -63,7 +63,7 @@
|
||||
(p "Add " (code ":affinity") " between the params list and the body:")
|
||||
(~docs/code :src (highlight "(defcomp ~affinity-demo/my-component (&key title)\n :affinity :client ;; or :server, or omit for :auto\n (div title))" "lisp"))
|
||||
(p "Three values:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code ":auto") " (default) — runtime decides from IO dependency analysis")
|
||||
(li (code ":client") " — always render client-side; IO calls proxied to server")
|
||||
(li (code ":server") " — always render server-side; never sent to client as SX")))
|
||||
@@ -72,7 +72,7 @@
|
||||
(~docs/section :title "Live Components" :id "live"
|
||||
(p "These components are defined with different affinities. The server analyzed them at registration time:")
|
||||
|
||||
(div :class "space-y-4 mt-4"
|
||||
(div (~tw :tokens "space-y-4 mt-4")
|
||||
(~affinity-demo/aff-demo-auto)
|
||||
(~affinity-demo/aff-demo-client)
|
||||
(~affinity-demo/aff-demo-server)
|
||||
@@ -82,30 +82,30 @@
|
||||
;; Analysis table from server
|
||||
(~docs/section :title "Server Analysis" :id "analysis"
|
||||
(p "The server computed these render targets at component registration time:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Component")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Affinity")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "IO Deps")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Render Target")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Component")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Affinity")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "IO Deps")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Render Target")))
|
||||
(tbody
|
||||
(map (fn (c)
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (get c "name"))
|
||||
(td :class "px-3 py-2"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") (get c "name"))
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span :class (str "inline-block px-2 py-0.5 rounded text-xs font-bold uppercase "
|
||||
(case (get c "affinity")
|
||||
"client" "bg-blue-100 text-blue-700"
|
||||
"server" "bg-amber-100 text-amber-700"
|
||||
"bg-stone-100 text-stone-600"))
|
||||
(get c "affinity")))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
(if (> (len (get c "io-refs")) 0)
|
||||
(span :class "text-red-600 font-medium"
|
||||
(span (~tw :tokens "text-red-600 font-medium")
|
||||
(join ", " (get c "io-refs")))
|
||||
(span :class "text-green-600" "none")))
|
||||
(td :class "px-3 py-2"
|
||||
(span (~tw :tokens "text-green-600") "none")))
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span :class (str "inline-block px-2 py-0.5 rounded text-xs font-bold uppercase "
|
||||
(if (= (get c "render-target") "client")
|
||||
"bg-green-100 text-green-700"
|
||||
@@ -115,91 +115,91 @@
|
||||
|
||||
;; Decision matrix
|
||||
(~docs/section :title "Decision Matrix" :id "matrix"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Affinity")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Has IO?")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Render Target")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Affinity")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Has IO?")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Render Target")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" ":auto")
|
||||
(td :class "px-3 py-2" "No")
|
||||
(td :class "px-3 py-2 font-bold text-green-700" "client")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure — can render anywhere"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" ":auto")
|
||||
(td :class "px-3 py-2" "Yes")
|
||||
(td :class "px-3 py-2 font-bold text-orange-700" "server")
|
||||
(td :class "px-3 py-2 text-stone-600" "IO must resolve server-side"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" ":client")
|
||||
(td :class "px-3 py-2" "No")
|
||||
(td :class "px-3 py-2 font-bold text-green-700" "client")
|
||||
(td :class "px-3 py-2 text-stone-600" "Explicit + pure"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" ":client")
|
||||
(td :class "px-3 py-2" "Yes")
|
||||
(td :class "px-3 py-2 font-bold text-green-700" "client")
|
||||
(td :class "px-3 py-2 text-stone-600" "Override — IO proxied via /sx/io/"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" ":server")
|
||||
(td :class "px-3 py-2" "No")
|
||||
(td :class "px-3 py-2 font-bold text-orange-700" "server")
|
||||
(td :class "px-3 py-2 text-stone-600" "Override — auth-sensitive"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" ":server")
|
||||
(td :class "px-3 py-2" "Yes")
|
||||
(td :class "px-3 py-2 font-bold text-orange-700" "server")
|
||||
(td :class "px-3 py-2 text-stone-600" "Both affinity and IO say server"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") ":auto")
|
||||
(td (~tw :tokens "px-3 py-2") "No")
|
||||
(td (~tw :tokens "px-3 py-2 font-bold text-green-700") "client")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure — can render anywhere"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") ":auto")
|
||||
(td (~tw :tokens "px-3 py-2") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 font-bold text-orange-700") "server")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "IO must resolve server-side"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") ":client")
|
||||
(td (~tw :tokens "px-3 py-2") "No")
|
||||
(td (~tw :tokens "px-3 py-2 font-bold text-green-700") "client")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Explicit + pure"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") ":client")
|
||||
(td (~tw :tokens "px-3 py-2") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 font-bold text-green-700") "client")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Override — IO proxied via /sx/io/"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") ":server")
|
||||
(td (~tw :tokens "px-3 py-2") "No")
|
||||
(td (~tw :tokens "px-3 py-2 font-bold text-orange-700") "server")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Override — auth-sensitive"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") ":server")
|
||||
(td (~tw :tokens "px-3 py-2") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 font-bold text-orange-700") "server")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Both affinity and IO say server"))))))
|
||||
|
||||
;; Per-page render plans
|
||||
(~docs/section :title "Page Render Plans" :id "plans"
|
||||
(p "Phase 7b: render plans are pre-computed at registration time for each page. The plan maps every component needed by the page to its render target.")
|
||||
|
||||
(when (> (len page-plans) 0)
|
||||
(div :class "space-y-4 mt-4"
|
||||
(div (~tw :tokens "space-y-4 mt-4")
|
||||
(map (fn (plan)
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(div :class "flex items-center justify-between mb-3"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div (~tw :tokens "flex items-center justify-between mb-3")
|
||||
(div
|
||||
(span :class "font-mono font-medium text-stone-800" (get plan "name"))
|
||||
(span :class "text-stone-400 ml-2 text-sm" (get plan "path")))
|
||||
(div :class "flex gap-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-orange-100 text-orange-700"
|
||||
(span (~tw :tokens "font-mono font-medium text-stone-800") (get plan "name"))
|
||||
(span (~tw :tokens "text-stone-400 ml-2 text-sm") (get plan "path")))
|
||||
(div (~tw :tokens "flex gap-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-orange-100 text-orange-700")
|
||||
(str (get plan "server-count") " server"))
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-100 text-green-700"
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-100 text-green-700")
|
||||
(str (get plan "client-count") " client"))))
|
||||
(when (> (get plan "server-count") 0)
|
||||
(div :class "mb-2"
|
||||
(span :class "text-xs font-medium text-stone-500 uppercase" "Server-expanded: ")
|
||||
(span :class "text-sm font-mono text-orange-700"
|
||||
(div (~tw :tokens "mb-2")
|
||||
(span (~tw :tokens "text-xs font-medium text-stone-500 uppercase") "Server-expanded: ")
|
||||
(span (~tw :tokens "text-sm font-mono text-orange-700")
|
||||
(join " " (get plan "server")))))
|
||||
(when (> (get plan "client-count") 0)
|
||||
(div
|
||||
(span :class "text-xs font-medium text-stone-500 uppercase" "Client-rendered: ")
|
||||
(span :class "text-sm font-mono text-green-700"
|
||||
(span (~tw :tokens "text-xs font-medium text-stone-500 uppercase") "Client-rendered: ")
|
||||
(span (~tw :tokens "text-sm font-mono text-green-700")
|
||||
(join " " (get plan "client")))))))
|
||||
page-plans))))
|
||||
|
||||
;; How it integrates
|
||||
(~docs/section :title "How It Works" :id "how"
|
||||
(ol :class "list-decimal list-inside text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal list-inside text-stone-700 space-y-2")
|
||||
(li (code "defcomp") " parses " (code ":affinity") " annotation between params and body")
|
||||
(li "Component object stores " (code "affinity") " field (\"auto\", \"client\", or \"server\")")
|
||||
(li (code "compute-all-io-refs") " scans transitive IO deps at registration time")
|
||||
(li (code "render-target") " in deps.sx combines affinity + IO analysis → \"server\" or \"client\"")
|
||||
(li "Server partial evaluator (" (code "_aser") ") checks " (code "render_target") ":")
|
||||
(ul :class "list-disc pl-8 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-8 text-stone-600")
|
||||
(li "\"server\" → expand component, embed rendered HTML")
|
||||
(li "\"client\" → serialize as SX, let client render"))
|
||||
(li "Client routing uses same info: IO deps in page registry → proxy registration")))
|
||||
|
||||
;; Verification
|
||||
(div :class "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2"
|
||||
(p :class "font-semibold text-amber-800" "How to verify")
|
||||
(ol :class "list-decimal list-inside text-amber-700 space-y-1"
|
||||
(div (~tw :tokens "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2")
|
||||
(p (~tw :tokens "font-semibold text-amber-800") "How to verify")
|
||||
(ol (~tw :tokens "list-decimal list-inside text-amber-700 space-y-1")
|
||||
(li "View page source — components with render-target \"server\" are expanded to HTML")
|
||||
(li "Components with render-target \"client\" appear as " (code "(~plans/content-addressed-components/name ...)") " in the SX wire format")
|
||||
(li "Navigate away and back — client-routable pure components render instantly")
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
(pure-count :as number) (io-count :as number))
|
||||
(~docs/page :title "Page Bundle Analyzer"
|
||||
|
||||
(p :class "text-stone-600 mb-6"
|
||||
(p (~tw :tokens "text-stone-600 mb-6")
|
||||
"Live analysis of component dependency graphs and IO classification across all pages. "
|
||||
"Each bar shows how many of the "
|
||||
(strong (str total-components))
|
||||
" total components a page actually needs, computed by the "
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-violet-700 underline") "deps.sx")
|
||||
" transitive closure algorithm. "
|
||||
"Click a page to see its component tree; expand a component to see its SX source.")
|
||||
|
||||
(div :class "mb-8 grid grid-cols-4 gap-4"
|
||||
(div (~tw :tokens "mb-8 grid grid-cols-4 gap-4")
|
||||
(~analyzer/stat :label "Total Components" :value (str total-components)
|
||||
:cls "text-violet-600")
|
||||
(~analyzer/stat :label "Total Macros" :value (str total-macros)
|
||||
@@ -27,7 +27,7 @@
|
||||
:cls "text-amber-600"))
|
||||
|
||||
(~docs/section :title "Per-Page Bundles" :id "bundles"
|
||||
(div :class "space-y-3"
|
||||
(div (~tw :tokens "space-y-3")
|
||||
(map (fn (page)
|
||||
(~analyzer/row
|
||||
:name (get page "name")
|
||||
@@ -44,50 +44,50 @@
|
||||
pages)))
|
||||
|
||||
(~docs/section :title "How It Works" :id "how"
|
||||
(ol :class "list-decimal pl-5 space-y-2 text-stone-700"
|
||||
(ol (~tw :tokens "list-decimal pl-5 space-y-2 text-stone-700")
|
||||
(li (strong "Scan: ") "Regex finds all " (code "(~plans/content-addressed-components/name") " patterns in the page's content expression.")
|
||||
(li (strong "Resolve: ") "Each referenced component's body AST is walked to find transitive " (code "~") " references.")
|
||||
(li (strong "Closure: ") "The full set is the union of direct + transitive deps, following chains through the component graph.")
|
||||
(li (strong "Bundle: ") "Only these component definitions are serialized into the page payload. Everything else is omitted.")
|
||||
(li (strong "IO detect: ") "Each component body is scanned for references to IO primitives (frag, query, service, etc.). Components with zero transitive IO refs are pure — safe for client rendering."))
|
||||
(p :class "mt-4 text-stone-600"
|
||||
(p (~tw :tokens "mt-4 text-stone-600")
|
||||
"The analysis handles circular references (via seen-set), "
|
||||
"walks all branches of control flow (if/when/cond/case), "
|
||||
"and includes macro definitions shared across components."))))
|
||||
|
||||
(defcomp ~analyzer/stat (&key (label :as string) (value :as string) (cls :as string))
|
||||
(div :class "rounded-lg border border-stone-200 p-4 text-center"
|
||||
(div (~tw :tokens "rounded-lg border border-stone-200 p-4 text-center")
|
||||
(div :class (str "text-3xl font-bold " cls) value)
|
||||
(div :class "text-sm text-stone-500 mt-1" label)))
|
||||
(div (~tw :tokens "text-sm text-stone-500 mt-1") label)))
|
||||
|
||||
(defcomp ~analyzer/row (&key (name :as string) (path :as string) (needed :as number) (direct :as number) (total :as number) (pct :as number) (savings :as number)
|
||||
(io-refs :as list) (pure-in-page :as number) (io-in-page :as number) (components :as list))
|
||||
(details :class "rounded border border-stone-200"
|
||||
(summary :class "p-4 cursor-pointer hover:bg-stone-50 transition-colors"
|
||||
(div :class "flex items-center justify-between mb-2"
|
||||
(details (~tw :tokens "rounded border border-stone-200")
|
||||
(summary (~tw :tokens "p-4 cursor-pointer hover:bg-stone-50 transition-colors")
|
||||
(div (~tw :tokens "flex items-center justify-between mb-2")
|
||||
(div
|
||||
(span :class "font-mono font-semibold text-stone-800" name)
|
||||
(span :class "text-stone-400 text-sm ml-2" path))
|
||||
(div :class "flex items-center gap-2"
|
||||
(span :class "inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800"
|
||||
(span (~tw :tokens "font-mono font-semibold text-stone-800") name)
|
||||
(span (~tw :tokens "text-stone-400 text-sm ml-2") path))
|
||||
(div (~tw :tokens "flex items-center gap-2")
|
||||
(span (~tw :tokens "inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800")
|
||||
(str pure-in-page " pure"))
|
||||
(span :class "inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800"
|
||||
(span (~tw :tokens "inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800")
|
||||
(str io-in-page " IO"))
|
||||
(div :class "text-right"
|
||||
(span :class "font-mono text-sm"
|
||||
(span :class "text-violet-700 font-bold" (str needed))
|
||||
(span :class "text-stone-400" (str " / " total)))
|
||||
(span :class "ml-2 inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800"
|
||||
(div (~tw :tokens "text-right")
|
||||
(span (~tw :tokens "font-mono text-sm")
|
||||
(span (~tw :tokens "text-violet-700 font-bold") (str needed))
|
||||
(span (~tw :tokens "text-stone-400") (str " / " total)))
|
||||
(span (~tw :tokens "ml-2 inline-block px-1.5 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800")
|
||||
(str savings "% saved")))))
|
||||
(div :class "w-full bg-stone-200 rounded-full h-2.5"
|
||||
(div :class "bg-violet-600 h-2.5 rounded-full transition-all"
|
||||
(div (~tw :tokens "w-full bg-stone-200 rounded-full h-2.5")
|
||||
(div (~tw :tokens "bg-violet-600 h-2.5 rounded-full transition-all")
|
||||
:style (str "width: " pct "%"))))
|
||||
|
||||
;; Component tree (shown when expanded)
|
||||
(div :class "border-t border-stone-200 p-4 bg-stone-50"
|
||||
(div :class "text-xs font-medium text-stone-500 uppercase tracking-wide mb-3"
|
||||
(div (~tw :tokens "border-t border-stone-200 p-4 bg-stone-50")
|
||||
(div (~tw :tokens "text-xs font-medium text-stone-500 uppercase tracking-wide mb-3")
|
||||
(str needed " components in bundle"))
|
||||
(div :class "space-y-1"
|
||||
(div (~tw :tokens "space-y-1")
|
||||
(map (fn (comp)
|
||||
(~analyzer/component
|
||||
:comp-name (get comp "name")
|
||||
@@ -100,21 +100,21 @@
|
||||
(defcomp ~analyzer/component (&key (comp-name :as string) (is-pure :as boolean) (io-refs :as list) (deps :as list) (source :as string))
|
||||
(details :class (str "rounded border "
|
||||
(if is-pure "border-blue-200 bg-blue-50" "border-amber-200 bg-amber-50"))
|
||||
(summary :class "px-3 py-2 cursor-pointer hover:opacity-80 transition-opacity"
|
||||
(div :class "flex items-center justify-between"
|
||||
(div :class "flex items-center gap-2"
|
||||
(summary (~tw :tokens "px-3 py-2 cursor-pointer hover:opacity-80 transition-opacity")
|
||||
(div (~tw :tokens "flex items-center justify-between")
|
||||
(div (~tw :tokens "flex items-center gap-2")
|
||||
(span :class (str "inline-block w-2 h-2 rounded-full "
|
||||
(if is-pure "bg-blue-500" "bg-amber-500")))
|
||||
(span :class "font-mono text-sm font-medium text-stone-800" comp-name))
|
||||
(div :class "flex items-center gap-2"
|
||||
(span (~tw :tokens "font-mono text-sm font-medium text-stone-800") comp-name))
|
||||
(div (~tw :tokens "flex items-center gap-2")
|
||||
(when (not (empty? io-refs))
|
||||
(span :class "text-xs text-amber-700"
|
||||
(span (~tw :tokens "text-xs text-amber-700")
|
||||
(str "IO: " (join ", " io-refs))))
|
||||
(when (not (empty? deps))
|
||||
(span :class "text-xs text-stone-500"
|
||||
(span (~tw :tokens "text-xs text-stone-500")
|
||||
(str (len deps) " deps"))))))
|
||||
|
||||
;; SX source (shown when component expanded)
|
||||
(div :class "not-prose border-t border-stone-200 p-3 bg-stone-100 rounded-b"
|
||||
(pre :class "text-xs leading-relaxed whitespace-pre-wrap overflow-x-auto"
|
||||
(div (~tw :tokens "not-prose border-t border-stone-200 p-3 bg-stone-100 rounded-b")
|
||||
(pre (~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap overflow-x-auto")
|
||||
(code (highlight source "lisp"))))))
|
||||
|
||||
@@ -14,39 +14,39 @@
|
||||
;; "sx:io registered N proxied primitives" — IO proxy initialization
|
||||
|
||||
(defcomp ~async-io-demo/content ()
|
||||
(div :class "space-y-8"
|
||||
(div :class "border-b border-stone-200 pb-6"
|
||||
(h1 :class "text-2xl font-bold text-stone-900" "Async IO Demo")
|
||||
(p :class "mt-2 text-stone-600"
|
||||
"This page calls " (code :class "bg-stone-100 px-1 rounded text-violet-700" "highlight")
|
||||
(div (~tw :tokens "space-y-8")
|
||||
(div (~tw :tokens "border-b border-stone-200 pb-6")
|
||||
(h1 (~tw :tokens "text-2xl font-bold text-stone-900") "Async IO Demo")
|
||||
(p (~tw :tokens "mt-2 text-stone-600")
|
||||
"This page calls " (code (~tw :tokens "bg-stone-100 px-1 rounded text-violet-700") "highlight")
|
||||
" inline — an IO primitive that returns SX source with colored spans. "
|
||||
"On the server it runs Python directly. On the client it proxies via "
|
||||
(code :class "bg-stone-100 px-1 rounded text-violet-700" "/sx/io/highlight")
|
||||
(code (~tw :tokens "bg-stone-100 px-1 rounded text-violet-700") "/sx/io/highlight")
|
||||
" and the async renderer awaits the result."))
|
||||
|
||||
;; Live syntax-highlighted code blocks — each is an IO call
|
||||
(div :class "space-y-6"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "Live IO: syntax highlighting")
|
||||
(div (~tw :tokens "space-y-6")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "Live IO: syntax highlighting")
|
||||
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
|
||||
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "SX component definition")
|
||||
(div (~tw :tokens "rounded-lg border border-stone-200 bg-white p-5 space-y-3")
|
||||
(h3 (~tw :tokens "text-sm font-medium text-stone-500 uppercase tracking-wide") "SX component definition")
|
||||
(~docs/code :src
|
||||
(highlight "(defcomp ~async-io-demo/card (&key title subtitle &rest children)\n (div :class \"border rounded-lg p-4 shadow-sm\"\n (h2 :class \"text-lg font-bold\" title)\n (when subtitle\n (p :class \"text-stone-500 text-sm\" subtitle))\n (div :class \"mt-3\" children)))" "lisp")))
|
||||
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
|
||||
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "Python server code")
|
||||
(div (~tw :tokens "rounded-lg border border-stone-200 bg-white p-5 space-y-3")
|
||||
(h3 (~tw :tokens "text-sm font-medium text-stone-500 uppercase tracking-wide") "Python server code")
|
||||
(~docs/code :src
|
||||
(highlight "from shared.sx.pages import mount_io_endpoint\n\n# The IO proxy serves any allowed primitive:\n# GET /sx/io/highlight?_arg0=code&_arg1=lisp\nasync def io_proxy(name):\n result = await execute_io(name, args, kwargs, ctx)\n return serialize(result)" "python")))
|
||||
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
|
||||
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "SX async rendering spec")
|
||||
(div (~tw :tokens "rounded-lg border border-stone-200 bg-white p-5 space-y-3")
|
||||
(h3 (~tw :tokens "text-sm font-medium text-stone-500 uppercase tracking-wide") "SX async rendering spec")
|
||||
(~docs/code :src
|
||||
(highlight ";; try-client-route reads io-deps from page registry\n(let ((io-deps (get match \"io-deps\"))\n (has-io (and io-deps (not (empty? io-deps)))))\n ;; Register IO deps as proxied primitives on demand\n (when has-io (register-io-deps io-deps))\n (if has-io\n ;; Async render: IO primitives proxied via /sx/io/<name>\n (do\n (try-async-eval-content content-src env\n (fn (rendered)\n (when rendered\n (swap-rendered-content target rendered pathname))))\n true)\n ;; Sync render: pure components, no IO\n (let ((rendered (try-eval-content content-src env)))\n (swap-rendered-content target rendered pathname))))" "lisp"))))
|
||||
|
||||
;; Architecture explanation
|
||||
(div :class "rounded-lg border border-blue-200 bg-blue-50 p-5 space-y-3"
|
||||
(h2 :class "text-lg font-semibold text-blue-900" "How it works")
|
||||
(ol :class "list-decimal list-inside text-blue-800 space-y-2 text-sm"
|
||||
(div (~tw :tokens "rounded-lg border border-blue-200 bg-blue-50 p-5 space-y-3")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-blue-900") "How it works")
|
||||
(ol (~tw :tokens "list-decimal list-inside text-blue-800 space-y-2 text-sm")
|
||||
(li "Server renders the page — " (code "highlight") " runs Python directly")
|
||||
(li "Client receives component definitions including " (code "~async-io-demo/content"))
|
||||
(li "On client navigation, " (code "io-deps") " list routes to async renderer")
|
||||
@@ -56,12 +56,12 @@
|
||||
(li "Client parses SX → AST, async renderer recursively renders to DOM")))
|
||||
|
||||
;; Verification instructions
|
||||
(div :class "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2"
|
||||
(p :class "font-semibold text-amber-800" "How to verify async IO rendering")
|
||||
(ol :class "list-decimal list-inside text-amber-700 space-y-1"
|
||||
(div (~tw :tokens "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2")
|
||||
(p (~tw :tokens "font-semibold text-amber-800") "How to verify async IO rendering")
|
||||
(ol (~tw :tokens "list-decimal list-inside text-amber-700 space-y-1")
|
||||
(li "Open the browser console (F12)")
|
||||
(li "Navigate to another page (e.g. Data Test)")
|
||||
(li "Click back to this page")
|
||||
(li "Look for: " (code :class "bg-amber-100 px-1 rounded" "sx:route client+async /isomorphism/async-io"))
|
||||
(li "Look for: " (code (~tw :tokens "bg-amber-100 px-1 rounded") "sx:route client+async /isomorphism/async-io"))
|
||||
(li "The code blocks should render identically — same syntax highlighting")
|
||||
(li "Check Network tab: you'll see 3 requests to " (code :class "bg-amber-100 px-1 rounded" "/sx/io/highlight"))))))
|
||||
(li "Check Network tab: you'll see 3 requests to " (code (~tw :tokens "bg-amber-100 px-1 rounded") "/sx/io/highlight"))))))
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"element and understand its styling. " (strong "That was a deal breaker.")))
|
||||
|
||||
(~docs/section :title "Key Advantages" :id "advantages"
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-700")
|
||||
(li (strong "Readable DOM: ") "Elements have real class names, not content-addressed "
|
||||
"hashes. DevTools works.")
|
||||
(li (strong "Data-driven styling: ") "Components receive data and decide styling. "
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
(~docs/section :title "What Changed" :id "changes"
|
||||
(~docs/subsection :title "Removed (~3,000 lines)"
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-600 text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-1 text-stone-600 text-sm")
|
||||
(li (code "StyleValue") " type and all plumbing (type checks in eval, render, serialize)")
|
||||
(li (code "cssx.sx") " spec module (resolve-style, resolve-atom, split-variant, hash, injection)")
|
||||
(li "Style dictionary JSON format, loading, caching (" (code "<script type=\"text/sx-styles\">") ", localStorage)")
|
||||
@@ -60,7 +60,7 @@
|
||||
(li "Style dict cookies and localStorage keys")))
|
||||
|
||||
(~docs/subsection :title "Kept"
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-600 text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-1 text-stone-600 text-sm")
|
||||
(li (code "defstyle") " — simplified to bind any value (string, function, etc.)")
|
||||
(li (code "tw.css") " — the compiled Tailwind stylesheet, delivered via CSS class tracking")
|
||||
(li (code ":class") " attribute — just takes strings, no special-casing")
|
||||
@@ -169,7 +169,7 @@
|
||||
|
||||
(~docs/section :title "How It Composes" :id "composition"
|
||||
(p "Async CSS composes with everything already in SX:")
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-1 text-stone-700")
|
||||
(li (code "~shared:pages/suspense") " handles the async gap with fallback content")
|
||||
(li "localStorage handles caching across sessions")
|
||||
(li (code "<style id=\"sx-css\">") " is the injection target (same as CSS class delivery)")
|
||||
@@ -237,7 +237,7 @@
|
||||
(p "A parallel style system would have needed its own streaming, its own caching, "
|
||||
"its own delta protocol for each of these use cases — duplicating what components "
|
||||
"already do.")
|
||||
(p :class "mt-4 text-stone-500 italic"
|
||||
(p (~tw :tokens "mt-4 text-stone-500 italic")
|
||||
"Note: ~live and ~ws are planned (see Live Streaming). The patterns shown here "
|
||||
"will work as described once the streaming transport is implemented. The component "
|
||||
"and suspense infrastructure they depend on already exists."))))
|
||||
@@ -251,7 +251,7 @@
|
||||
(~docs/page :title "Comparisons"
|
||||
|
||||
(~docs/section :title "styled-components / Emotion" :id "styled-components"
|
||||
(p (a :href "https://styled-components.com" :class "text-violet-600 hover:underline" "styled-components")
|
||||
(p (a :href "https://styled-components.com" (~tw :tokens "text-violet-600 hover:underline") "styled-components")
|
||||
" pioneered the idea that styling belongs in components. But it generates CSS "
|
||||
"at runtime, injects " (code "<style>") " tags, and produces opaque hashed class "
|
||||
"names (" (code "class=\"sc-bdfBwQ fNMpVx\"") "). Open DevTools and you see gibberish. "
|
||||
@@ -264,7 +264,7 @@
|
||||
"And the DOM is always readable."))
|
||||
|
||||
(~docs/section :title "CSS Modules" :id "css-modules"
|
||||
(p (a :href "https://github.com/css-modules/css-modules" :class "text-violet-600 hover:underline" "CSS Modules")
|
||||
(p (a :href "https://github.com/css-modules/css-modules" (~tw :tokens "text-violet-600 hover:underline") "CSS Modules")
|
||||
" scope class names to avoid collisions by rewriting them at build time: "
|
||||
(code ".button") " becomes " (code ".button_abc123")
|
||||
". This solves the global namespace problem but creates the same opacity issue — "
|
||||
@@ -283,7 +283,7 @@
|
||||
(em "\"extracting components\"") ") — CSSX components are just SX's native way of doing it."))
|
||||
|
||||
(~docs/section :title "Vanilla Extract" :id "vanilla-extract"
|
||||
(p (a :href "https://vanilla-extract.style" :class "text-violet-600 hover:underline" "Vanilla Extract")
|
||||
(p (a :href "https://vanilla-extract.style" (~tw :tokens "text-violet-600 hover:underline") "Vanilla Extract")
|
||||
" is zero-runtime CSS-in-JS: styles are written in TypeScript, compiled to static "
|
||||
"CSS at build time, and referenced by generated class names. It avoids the runtime "
|
||||
"cost of styled-components but still requires a build step, a bundler plugin, and "
|
||||
@@ -294,7 +294,7 @@
|
||||
"same API either way."))
|
||||
|
||||
(~docs/section :title "Design Tokens / Style Dictionary" :id "design-tokens"
|
||||
(p "The " (a :href "https://amzn.github.io/style-dictionary/" :class "text-violet-600 hover:underline" "Style Dictionary")
|
||||
(p "The " (a :href "https://amzn.github.io/style-dictionary/" (~tw :tokens "text-violet-600 hover:underline") "Style Dictionary")
|
||||
" pattern — a JSON/YAML file mapping token names to values, compiled to "
|
||||
"platform-specific output — is essentially what the old CSSX was. It's the "
|
||||
"industry standard for design systems.")
|
||||
@@ -340,7 +340,7 @@
|
||||
"decision the component makes, not a separate concern."))
|
||||
|
||||
(~docs/section :title "Relationship to Other Plans" :id "relationships"
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-700")
|
||||
(li (strong "Content-Addressed Components: ") "CSSX components get CIDs like any "
|
||||
"other component. A " (code "~cssx/btn") " from one site can be shared to another via "
|
||||
"IPFS. No " (code ":css-atoms") " manifest field needed — the component carries "
|
||||
@@ -364,7 +364,7 @@
|
||||
(~docs/section :title "Multiple Strategies" :id "strategies"
|
||||
(p "A CSSX component chooses its own styling strategy — and each strategy has its "
|
||||
"own delivery path:")
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-1 text-stone-700")
|
||||
(li (strong "Tailwind classes: ") "Delivered via the on-demand protocol below. "
|
||||
"The server ships only the rules the page actually uses.")
|
||||
(li (strong "Inline styles: ") "No delivery needed — " (code ":style") " attributes "
|
||||
@@ -399,7 +399,7 @@
|
||||
|
||||
(~docs/section :title "Component-Aware Scanning" :id "scanning"
|
||||
(p "CSS scanning happens at two levels:")
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-1 text-stone-700")
|
||||
(li (strong "Registration time: ") "When a component is defined via " (code "defcomp")
|
||||
", its body is scanned for class strings. The component records which CSS classes "
|
||||
"it uses.")
|
||||
@@ -409,7 +409,7 @@
|
||||
"and catches any stragglers after."))
|
||||
|
||||
(~docs/section :title "Trade-offs" :id "tradeoffs"
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-700")
|
||||
(li (strong "Full Tailwind in memory: ") "The parsed CSS registry is ~4MB. This is "
|
||||
"a one-time startup cost per app instance.")
|
||||
(li (strong "Regex scanning: ") "Class detection uses regex, so dynamically "
|
||||
|
||||
@@ -10,47 +10,47 @@
|
||||
;; "sx:route client+cache" — cache hit, rendered from cached data
|
||||
|
||||
(defcomp ~data-test/content (&key (server-time :as string) (items :as list) (phase :as string) (transport :as string))
|
||||
(div :class "space-y-8"
|
||||
(div :class "border-b border-stone-200 pb-6"
|
||||
(h1 :class "text-2xl font-bold text-stone-900" "Data Test")
|
||||
(p :class "mt-2 text-stone-600"
|
||||
(div (~tw :tokens "space-y-8")
|
||||
(div (~tw :tokens "border-b border-stone-200 pb-6")
|
||||
(h1 (~tw :tokens "text-2xl font-bold text-stone-900") "Data Test")
|
||||
(p (~tw :tokens "mt-2 text-stone-600")
|
||||
"This page tests the Phase 4 data endpoint and client-side data cache. "
|
||||
"The content you see was rendered using data from the server, but the "
|
||||
"rendering itself may have happened client-side."))
|
||||
|
||||
;; Server-provided metadata
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-6 space-y-3"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "Data from server")
|
||||
(dl :class "grid grid-cols-2 gap-2 text-sm"
|
||||
(dt :class "font-medium text-stone-600" "Phase")
|
||||
(dd :class "text-stone-900" phase)
|
||||
(dt :class "font-medium text-stone-600" "Transport")
|
||||
(dd :class "text-stone-900" transport)
|
||||
(dt :class "font-medium text-stone-600" "Server time")
|
||||
(dd :class "font-mono text-stone-900" server-time)))
|
||||
(div (~tw :tokens "rounded-lg border border-stone-200 bg-white p-6 space-y-3")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "Data from server")
|
||||
(dl (~tw :tokens "grid grid-cols-2 gap-2 text-sm")
|
||||
(dt (~tw :tokens "font-medium text-stone-600") "Phase")
|
||||
(dd (~tw :tokens "text-stone-900") phase)
|
||||
(dt (~tw :tokens "font-medium text-stone-600") "Transport")
|
||||
(dd (~tw :tokens "text-stone-900") transport)
|
||||
(dt (~tw :tokens "font-medium text-stone-600") "Server time")
|
||||
(dd (~tw :tokens "font-mono text-stone-900") server-time)))
|
||||
|
||||
;; Pipeline steps from data
|
||||
(div :class "space-y-3"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "Pipeline steps")
|
||||
(div :class "space-y-2"
|
||||
(div (~tw :tokens "space-y-3")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "Pipeline steps")
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(map-indexed
|
||||
(fn (i item)
|
||||
(div :class "flex items-start gap-3 rounded border border-stone-100 bg-white p-3"
|
||||
(span :class "flex-none rounded-full bg-violet-100 text-violet-700 w-6 h-6 flex items-center justify-center text-xs font-bold"
|
||||
(div (~tw :tokens "flex items-start gap-3 rounded border border-stone-100 bg-white p-3")
|
||||
(span (~tw :tokens "flex-none rounded-full bg-violet-100 text-violet-700 w-6 h-6 flex items-center justify-center text-xs font-bold")
|
||||
(str (+ i 1)))
|
||||
(div
|
||||
(div :class "font-medium text-stone-900" (get item "label"))
|
||||
(div :class "text-sm text-stone-500" (get item "detail")))))
|
||||
(div (~tw :tokens "font-medium text-stone-900") (get item "label"))
|
||||
(div (~tw :tokens "text-sm text-stone-500") (get item "detail")))))
|
||||
items)))
|
||||
|
||||
;; How to verify — updated with cache instructions
|
||||
(div :class "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2"
|
||||
(p :class "font-semibold text-amber-800" "How to verify client-side rendering + caching")
|
||||
(ol :class "list-decimal list-inside text-amber-700 space-y-1"
|
||||
(div (~tw :tokens "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2")
|
||||
(p (~tw :tokens "font-semibold text-amber-800") "How to verify client-side rendering + caching")
|
||||
(ol (~tw :tokens "list-decimal list-inside text-amber-700 space-y-1")
|
||||
(li "Open the browser console (F12)")
|
||||
(li "Navigate to this page from another page using a link")
|
||||
(li "Look for: " (code :class "bg-amber-100 px-1 rounded" "sx:route client+data /isomorphism/data-test"))
|
||||
(li "Look for: " (code (~tw :tokens "bg-amber-100 px-1 rounded") "sx:route client+data /isomorphism/data-test"))
|
||||
(li "Navigate away, then back within 30 seconds")
|
||||
(li "Look for: " (code :class "bg-amber-100 px-1 rounded" "sx:route client+cache /isomorphism/data-test"))
|
||||
(li "Look for: " (code (~tw :tokens "bg-amber-100 px-1 rounded") "sx:route client+cache /isomorphism/data-test"))
|
||||
(li "The server-time value should be the same (cached data)")
|
||||
(li "Wait 30+ seconds, navigate back again — new fetch, updated time")))))
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
;; Docs page content — fully self-contained, no Python intermediaries
|
||||
|
||||
(defcomp ~docs-content/home-content ()
|
||||
(div :id "main-content" :class "max-w-3xl mx-auto px-4 py-6"
|
||||
(div :id "main-content" (~tw :tokens "max-w-3xl mx-auto px-4 py-6")
|
||||
(~home/stepper)))
|
||||
|
||||
(defcomp ~docs-content/docs-introduction-content ()
|
||||
(~docs/page :title "Introduction"
|
||||
(~docs/section :title "What is sx?" :id "what"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx is an s-expression language for building web UIs. It combines htmx's server-first hypermedia approach with React's component model. The server sends s-expression source code over the wire. The client parses, evaluates, and renders it to DOM.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The same evaluator runs on both server (Python) and client (JavaScript). Components defined once render identically in both environments."))
|
||||
(~docs/section :title "Design decisions" :id "design"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"HTML elements are first-class: (div :class \"card\" (p \"hello\")) renders exactly what you'd expect. Components use defcomp with keyword parameters and optional children. The evaluator supports let bindings, conditionals, lambda, map/filter/reduce, and ~80 primitives.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx replaces the pattern of shipping a JS framework + build step + client-side router + state management library just to render some server data. For most applications, sx eliminates the need for JavaScript entirely — htmx attributes handle interactivity, hyperscript handles small behaviours, and the server handles everything else."))
|
||||
(~docs/section :title "What sx is not" :id "not"
|
||||
(ul :class "space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li "Not a general-purpose programming language — it's a UI rendering language")
|
||||
(li "Not a full Lisp — it has macros, TCO, and delimited continuations, but no full call/cc")
|
||||
(li "Not production-hardened at scale — it runs one website")))))
|
||||
@@ -25,75 +25,75 @@
|
||||
(defcomp ~docs-content/docs-getting-started-content ()
|
||||
(~docs/page :title "Getting Started"
|
||||
(~docs/section :title "Minimal example" :id "minimal"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"An sx response is s-expression source code with content type text/sx:")
|
||||
(~docs/code :src (highlight "(div :class \"p-4 bg-white rounded\"\n (h1 :class \"text-2xl font-bold\" \"Hello, world!\")\n (p \"This is rendered from an s-expression.\"))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Add sx-get to any element to make it fetch and render sx:"))
|
||||
(~docs/section :title "Hypermedia attributes" :id "attrs"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Like htmx, sx adds attributes to HTML elements to trigger HTTP requests:")
|
||||
(~docs/code :src (highlight "(button\n :sx-get \"/api/data\"\n :sx-target \"#result\"\n :sx-swap \"innerHTML\"\n \"Load data\")" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx-get, sx-post, sx-put, sx-delete, sx-patch — all work the same way. The response is parsed as sx and rendered into the target element."))))
|
||||
|
||||
(defcomp ~docs-content/docs-components-content ()
|
||||
(~docs/page :title "Components"
|
||||
(~docs/section :title "defcomp" :id "defcomp"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Components are defined with defcomp. They take keyword parameters and optional children:")
|
||||
(~docs/code :src (highlight "(defcomp ~docs-content/card (&key title subtitle &rest children)\n (div :class \"border rounded p-4\"\n (h2 :class \"font-bold\" title)\n (when subtitle (p :class \"text-stone-500\" subtitle))\n (div :class \"mt-3\" children)))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Use components with the ~ prefix:")
|
||||
(~docs/code :src (highlight "(~docs-content/card :title \"My Card\" :subtitle \"A description\"\n (p \"First child\")\n (p \"Second child\"))" "lisp")))
|
||||
(~docs/section :title "Component caching" :id "caching"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Component definitions are sent in a <script type=\"text/sx\" data-components> block. The client caches them in localStorage keyed by a content hash. On subsequent page loads, the client sends an SX-Components header listing what it has. The server only sends definitions the client is missing.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This means the first page load sends all component definitions (~5-15KB). Subsequent navigations send zero component bytes — just the page content."))
|
||||
(~docs/section :title "Parameters" :id "params"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"&key declares keyword parameters. &rest children captures remaining positional arguments. Missing parameters evaluate to nil. Components always receive all declared parameters — use (when param ...) or (if param ... ...) to handle optional values."))))
|
||||
|
||||
(defcomp ~docs-content/docs-evaluator-content ()
|
||||
(~docs/page :title "Evaluator"
|
||||
(~docs/section :title "Special forms" :id "special"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Special forms have lazy evaluation — arguments are not evaluated before the form runs:")
|
||||
(~docs/code :src (highlight ";; Conditionals\n(if condition then-expr else-expr)\n(when condition body...)\n(cond (test1 body1) (test2 body2) (else default))\n\n;; Bindings\n(let ((name value) (name2 value2)) body...)\n(define name value)\n\n;; Functions\n(lambda (x y) (+ x y))\n(fn (x) (* x x))\n\n;; Sequencing\n(do expr1 expr2 expr3)\n(begin expr1 expr2)\n\n;; Threading\n(-> value (fn1 arg) (fn2 arg))" "lisp")))
|
||||
(~docs/section :title "Higher-order forms" :id "higher"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"These operate on collections with function arguments:")
|
||||
(~docs/code :src (highlight "(map (fn (x) (* x 2)) (list 1 2 3)) ;; => (2 4 6)\n(filter (fn (x) (> x 2)) (list 1 2 3 4 5)) ;; => (3 4 5)\n(reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3)) ;; => 6\n(some (fn (x) (> x 3)) (list 1 2 3 4)) ;; => true\n(every? (fn (x) (> x 0)) (list 1 2 3)) ;; => true" "lisp")))))
|
||||
|
||||
(defcomp ~docs-content/docs-primitives-content (&key prims)
|
||||
(~docs/page :title "Primitives"
|
||||
(~docs/section :title "Built-in functions" :id "builtins"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx provides ~80 built-in pure functions. They work identically on server (Python) and client (JavaScript).")
|
||||
(div :class "space-y-6" prims))))
|
||||
(div (~tw :tokens "space-y-6") prims))))
|
||||
|
||||
(defcomp ~docs-content/docs-special-forms-content (&key forms)
|
||||
(~docs/page :title "Special Forms"
|
||||
(~docs/section :title "Syntactic constructs" :id "special-forms"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Special forms are syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface.")
|
||||
(p :class "text-stone-600"
|
||||
"Forms marked with a tail position enable " (a :href "/sx/(etc.(essay.tail-call-optimization))" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.")
|
||||
(div :class "space-y-10" forms))))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Forms marked with a tail position enable " (a :href "/sx/(etc.(essay.tail-call-optimization))" (~tw :tokens "text-violet-600 hover:underline") "tail-call optimization") " — recursive calls in tail position use constant stack space.")
|
||||
(div (~tw :tokens "space-y-10") forms))))
|
||||
|
||||
(defcomp ~docs-content/docs-server-rendering-content ()
|
||||
(~docs/page :title "Server Rendering"
|
||||
(~docs/section :title "Python API" :id "python"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The server-side sx library provides several entry points for rendering:")
|
||||
(~docs/code :src (highlight "from shared.sx.helpers import sx_page, sx_response, sx_call\nfrom shared.sx.parser import SxExpr\n\n# Build a component call from Python kwargs\nsx_call(\"card\", title=\"Hello\", subtitle=\"World\")\n\n# Return an sx wire-format response\nreturn sx_response(sx_call(\"card\", title=\"Hello\"))\n\n# Return a full HTML page shell with sx boot\nreturn sx_page(ctx, page_sx)" "python")))
|
||||
(~docs/section :title "sx_call" :id "sx-call"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx_call converts Python kwargs to an s-expression component call. Snake_case becomes kebab-case. SxExpr values are inlined without quoting. None becomes nil. Bools become true/false."))
|
||||
(~docs/section :title "sx_response" :id "sx-response"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx_response returns a Quart Response with content type text/sx. It prepends missing component definitions, scans for CSS classes, and sets SX-Css-Hash and SX-Css-Add headers."))
|
||||
(~docs/section :title "sx_page" :id "sx-page"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx_page returns a minimal HTML document that boots the page from sx source. The browser loads component definitions and page sx from inline <script> tags, then sx.js renders everything client-side. CSS rules are pre-scanned and injected."))))
|
||||
|
||||
104
sx/sx/docs.sx
104
sx/sx/docs.sx
@@ -4,9 +4,9 @@
|
||||
(div
|
||||
:id id
|
||||
(div
|
||||
:class "bg-stone-100 rounded p-4 mt-3"
|
||||
(~tw :tokens "bg-stone-100 rounded p-4 mt-3")
|
||||
(p
|
||||
:class "text-stone-400 italic text-sm"
|
||||
(~tw :tokens "text-stone-400 italic text-sm")
|
||||
"Trigger the demo to see the actual content."))))
|
||||
|
||||
(defcomp
|
||||
@@ -16,26 +16,26 @@
|
||||
:id target-id
|
||||
:sx-swap-oob "innerHTML"
|
||||
(div
|
||||
:class "not-prose bg-stone-100 rounded p-4 mt-3"
|
||||
(pre :class "text-sm whitespace-pre-wrap break-words" (code text)))))
|
||||
(~tw :tokens "not-prose bg-stone-100 rounded p-4 mt-3")
|
||||
(pre (~tw :tokens "text-sm whitespace-pre-wrap break-words") (code text)))))
|
||||
|
||||
(defcomp
|
||||
~docs/attr-table
|
||||
(&key (title :as string) rows)
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(h3 :class "text-xl font-semibold text-stone-700" title)
|
||||
(~tw :tokens "space-y-3")
|
||||
(h3 (~tw :tokens "text-xl font-semibold text-stone-700") title)
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Attribute")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Attribute")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description")
|
||||
(th
|
||||
:class "px-3 py-2 font-medium text-stone-600 text-center w-20"
|
||||
(~tw :tokens "px-3 py-2 font-medium text-stone-600 text-center w-20")
|
||||
"In sx?")))
|
||||
(tbody rows)))))
|
||||
|
||||
@@ -43,18 +43,18 @@
|
||||
~docs/headers-table
|
||||
(&key (title :as string) rows)
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(h3 :class "text-xl font-semibold text-stone-700" title)
|
||||
(~tw :tokens "space-y-3")
|
||||
(h3 (~tw :tokens "text-xl font-semibold text-stone-700") title)
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Header")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Value")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Header")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Value")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description")))
|
||||
(tbody rows)))))
|
||||
|
||||
(defcomp
|
||||
@@ -65,9 +65,9 @@
|
||||
(description :as string)
|
||||
(href :as string?))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-sm whitespace-nowrap"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-sm whitespace-nowrap")
|
||||
(if
|
||||
href
|
||||
(a
|
||||
@@ -77,19 +77,19 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-violet-700 hover:text-violet-900 underline"
|
||||
(~tw :tokens "text-violet-700 hover:text-violet-900 underline")
|
||||
name)
|
||||
(span :class "text-violet-700" name)))
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-500" value)
|
||||
(td :class "px-3 py-2 text-stone-700 text-sm" description)))
|
||||
(span (~tw :tokens "text-violet-700") name)))
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-500") value)
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700 text-sm") description)))
|
||||
|
||||
(defcomp
|
||||
~docs/two-col-row
|
||||
(&key (name :as string) (description :as string) (href :as string?))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-sm whitespace-nowrap"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-sm whitespace-nowrap")
|
||||
(if
|
||||
href
|
||||
(a
|
||||
@@ -99,10 +99,10 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-violet-700 hover:text-violet-900 underline"
|
||||
(~tw :tokens "text-violet-700 hover:text-violet-900 underline")
|
||||
name)
|
||||
(span :class "text-violet-700" name)))
|
||||
(td :class "px-3 py-2 text-stone-700 text-sm" description)))
|
||||
(span (~tw :tokens "text-violet-700") name)))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700 text-sm") description)))
|
||||
|
||||
(defcomp
|
||||
~docs/two-col-table
|
||||
@@ -113,32 +113,32 @@
|
||||
(col2 :as string?)
|
||||
rows)
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(when title (h3 :class "text-xl font-semibold text-stone-700" title))
|
||||
(when intro (p :class "text-stone-600 mb-6" intro))
|
||||
(~tw :tokens "space-y-3")
|
||||
(when title (h3 (~tw :tokens "text-xl font-semibold text-stone-700") title))
|
||||
(when intro (p (~tw :tokens "text-stone-600 mb-6") intro))
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th
|
||||
:class "px-3 py-2 font-medium text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 font-medium text-stone-600")
|
||||
(or col1 "Name"))
|
||||
(th
|
||||
:class "px-3 py-2 font-medium text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 font-medium text-stone-600")
|
||||
(or col2 "Description"))))
|
||||
(tbody rows)))))
|
||||
|
||||
(defcomp ~docs/label () (span :class "font-mono" "(<sx>)"))
|
||||
(defcomp ~docs/label () (span (~tw :tokens "font-mono") "(<sx>)"))
|
||||
|
||||
(defcomp
|
||||
~docs/clear-cache-btn
|
||||
()
|
||||
(button
|
||||
:onclick "localStorage.removeItem('sx-components-hash');localStorage.removeItem('sx-components-src');var e=Sx.getEnv();Object.keys(e).forEach(function(k){if(k.charAt(0)==='~')delete e[k]});var b=this;b.textContent='Cleared!';setTimeout(function(){b.textContent='Clear component cache'},2000)"
|
||||
:class "text-xs text-stone-400 hover:text-stone-600 border border-stone-200 rounded px-2 py-1 transition-colors"
|
||||
(~tw :tokens "text-xs text-stone-400 hover:text-stone-600 border border-stone-200 rounded px-2 py-1 transition-colors")
|
||||
"Clear component cache"))
|
||||
|
||||
(defcomp
|
||||
@@ -222,12 +222,12 @@
|
||||
~docs/special-forms-category
|
||||
(&key (category :as string) (forms :as list))
|
||||
(div
|
||||
:class "space-y-4"
|
||||
(~tw :tokens "space-y-4")
|
||||
(h3
|
||||
:class "text-xl font-semibold text-stone-800 border-b border-stone-200 pb-2"
|
||||
(~tw :tokens "text-xl font-semibold text-stone-800 border-b border-stone-200 pb-2")
|
||||
category)
|
||||
(div
|
||||
:class "space-y-4"
|
||||
(~tw :tokens "space-y-4")
|
||||
(map
|
||||
(fn
|
||||
(f)
|
||||
@@ -248,26 +248,26 @@
|
||||
(tail-position :as string)
|
||||
(example :as string))
|
||||
(div
|
||||
:class "not-prose border border-stone-200 rounded-lg p-4 space-y-3"
|
||||
(~tw :tokens "not-prose border border-stone-200 rounded-lg p-4 space-y-3")
|
||||
(div
|
||||
:class "flex items-baseline gap-3"
|
||||
(code :class "text-lg font-bold text-violet-700" name)
|
||||
(~tw :tokens "flex items-baseline gap-3")
|
||||
(code (~tw :tokens "text-lg font-bold text-violet-700") name)
|
||||
(when
|
||||
(not (= tail-position "none"))
|
||||
(span
|
||||
:class "text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-700"
|
||||
(~tw :tokens "text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-700")
|
||||
"TCO")))
|
||||
(when
|
||||
(not (= syntax ""))
|
||||
(pre
|
||||
:class "bg-stone-100 rounded px-3 py-2 text-sm font-mono text-stone-700 overflow-x-auto"
|
||||
(~tw :tokens "bg-stone-100 rounded px-3 py-2 text-sm font-mono text-stone-700 overflow-x-auto")
|
||||
syntax))
|
||||
(p :class "text-stone-600 text-sm whitespace-pre-line" doc)
|
||||
(p (~tw :tokens "text-stone-600 text-sm whitespace-pre-line") doc)
|
||||
(when
|
||||
(not (= tail-position ""))
|
||||
(p
|
||||
:class "text-xs text-stone-500"
|
||||
(span :class "font-semibold" "Tail position: ")
|
||||
(~tw :tokens "text-xs text-stone-500")
|
||||
(span (~tw :tokens "font-semibold") "Tail position: ")
|
||||
tail-position))
|
||||
(when
|
||||
(not (= example ""))
|
||||
|
||||
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
@@ -4,78 +4,78 @@
|
||||
|
||||
(defcomp ~essays/hegelian-synthesis/essay-hegelian-synthesis ()
|
||||
(~docs/page :title "The Hegelian Synthesis"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"On the dialectical resolution of the hypertext/reactive contradiction.")
|
||||
(~docs/section :title "I. Thesis: The server renders" :id "thesis"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is the thesis of the web: " (strong "the server knows") ". It knows what to show. It knows what state you're in. It knows what actions are available. It encodes all of this in the document it sends you. When you click a link, the server sends a new document. When you submit a form, the server processes it and sends another document. The cycle is: request, represent, repeat.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Hegel would recognise this as a form of " (em "Substance") " — the server as the unmoved mover, the ground of all content. The client has no interiority. It does not " (em "decide") " anything. It displays what it is told to display. The web page is Spinozistic: a mode of the server, one of its infinite expressions, determined entirely by its cause.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"htmx is the most faithful modern expression of this thesis. It extends the hyperlink and the form — the two primordial hypermedia controls — to every element and every HTTP verb, but it does not break the paradigm. The server still renders. The server still knows. The server still authors state. htmx simply makes the authoring more fine-grained: instead of replacing the entire page, the server replaces a fragment. The thesis is refined, not rejected.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "II. Antithesis: The client reacts" :id "antithesis"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The Hegelian antithesis is not mere opposition — it is " (em "determinate negation") ". React does not simply reject server rendering. It rejects the specific limitation that the thesis cannot overcome: the absence of immediate, client-local state. React gives the client " (em "interiority") ". The component has state. It has a lifecycle. It makes decisions without consulting the server. It " (em "reacts") ".")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is the birth of " (em "Subject") " in the Hegelian sense. The client is no longer pure receptivity. It has " (em "Selbstbewusstsein") " — self-consciousness. It knows its own state. A " (code "useState") " hook is an act of self-positing: the component declares " (em "I have an interior") ". A " (code "useEffect") " is self-reflection: the component observes its own changes and responds. The client becomes, in miniature, a knowing subject.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"But the antithesis inherits its own contradiction. By giving the client interiority, React creates " (em "two sources of truth") ". The server has its state. The client has its state. They must be synchronised. And synchronisation is the eternal problem of distributed systems — it cannot be solved, only managed. Hence the endless parade of state management libraries, cache invalidation strategies, optimistic updates, revalidation hooks. Each is an attempt to paper over the fundamental contradiction: the client and server both claim to know, and they do not always agree.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "III. The contradiction in practice" :id "contradiction"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The server camp cannot build a colour picker. Cannot build a drag-and-drop interface. Cannot build a spreadsheet. Cannot build anything that requires the client to know its own state between HTTP requests. Every interaction that needs immediacy — every tooltip, every animation, every character-by-character validation — forces the server camp to smuggle in JavaScript through the back door, breaking their own thesis.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "IV. Synthesis: The island in the lake" :id "synthesis"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The island architecture is this synthesis. The " (em "lake") " is the thesis preserved: server-rendered HTML, delivered as complete representations, navigated by hypermedia controls. The " (em "island") " is the antithesis preserved: client-local state, reactive signals, immediate interaction. Neither is primary. Neither is a concession to the other. Both are " (em "moments") " of the same page.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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).")
|
||||
(~docs/code :src (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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "V. Spirit: The self-knowing page" :id "spirit"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Consider what the synthesis makes possible. The page now has two modes of knowledge: the server's knowledge (what the page should contain) and the client's knowledge (what the user is doing right now). Neither is reducible to the other. Neither is privileged over the other. The page is, in Hegel's terminology, " (em "Spirit") " — " (em "Geist") " — the unity of substance and subject, the place where objective content and subjective experience meet.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The self-hosting nature of SX deepens this. The evaluator that renders islands is specified in the same language as the islands themselves (" (code "eval.sx") "). The renderer is specified in the same language (" (code "render.sx") "). The parser is specified in the same language (" (code "parser.sx") "). The system knows itself. It is a specification that specifies its own interpretation. This is Hegel's " (em "absolute knowing") " — not omniscience, but self-transparency. The system is what it knows, and it knows what it is.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "VI. The speculative proposition" :id "speculative"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The proposition " (em "React is hypermedia") " is speculative in this sense. It does not mean that React, as it exists, is hypermedia. It means that what React " (em "was trying to be") " — a way to specify interactive UI — is what hypermedia " (em "always already was") " — a way to specify interactive documents. The predicate (hypermedia) transforms the subject (React): once you see that reactive islands are hypermedia controls, you can no longer see React as merely a JavaScript library. It was always an attempt to extend the expressiveness of hypermedia. It just didn't know it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"And the converse: " (em "hypermedia is reactive") ". The hyperlink is already a reactive control — it responds to user input (click) by producing a state change (navigation). The form is reactive — it responds to submission by producing a state change (server-side processing + new representation). htmx makes this explicit: any element can react to any event by triggering any HTTP verb. The only thing htmx lacks is " (em "local") " reactivity — the ability to change without consulting the server.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Islands supply exactly this. And by doing so, they do not add something foreign to hypermedia. They complete it. They give hypermedia the last degree of freedom it was missing: the ability for a control to react to itself, to maintain its own state, to compute derived values from its own inputs — without breaking the hypermedia contract that the server is the author of the document.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The Hegelian synthesis is not a compromise between server rendering and client reactivity. It is the recognition that they were always the same thing seen from different sides. The server renders the document. The document contains controls. Some controls maintain local state. The document flows around them. The state persists through the flow. The server and the client are not two systems bridged together. They are one system that knows itself from two perspectives.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Click the word " (em "reactive") " in the header above. The colour changes. The page navigates. The colour survives. That is the Hegelian synthesis of the hypertext/reactive dialectic — not in theory, but in the DOM."))))
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(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")))))
|
||||
(~docs/page :title "The htmx/React Hybrid" (~docs/section :title "Two good ideas" :id "ideas" (p (~tw :tokens "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 (~tw :tokens "text-stone-600") "React: UI should be composed from reusable components with parameters. Components encapsulate structure, style, and behavior.") (p (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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")))))
|
||||
|
||||
@@ -5,102 +5,102 @@
|
||||
|
||||
(defcomp ~essays/hypermedia-age-of-ai/essay-hypermedia-age-of-ai ()
|
||||
(~docs/page :title "Hypermedia in the Age of AI"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Neither JSON nor HTML is hypermedia. There is only the hypermedium — a self-defining representation — and s-expressions are an instance of it.")
|
||||
|
||||
(~docs/section :title "I. The argument" :id "the-argument"
|
||||
(p :class "text-stone-600"
|
||||
(a :href "https://nickblow.tech/posts/hypermedia-in-the-age-of-ai" :class "text-violet-600 hover:underline" "Nick Blow argues")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://nickblow.tech/posts/hypermedia-in-the-age-of-ai" (~tw :tokens "text-violet-600 hover:underline") "Nick Blow argues")
|
||||
" that JSON hypermedia can serve AI agents better than HTML or RPC. Carson Gross contends that sprinkling link objects into JSON does not make an API truly RESTful, because REST demands generic clients capable of interpreting hypermedia controls. Blow agrees this has merit but thinks the position too restrictive. HTML is not the universal hypermedia format — LLMs choke on it. The real prize is " (em "progressive discovery") ": a client that learns what it can do by following links, not by reading documentation upfront.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"He contrasts this with MCP, the Model Context Protocol now dominant for LLM tool use. MCP is RPC in a trench coat: the server declares all its tools upfront, the model receives the full catalogue in its system prompt, and it calls functions by name. This works, but it does not scale. MCP forces " (em "total disclosure") " where hypermedia would offer " (em "progressive revelation") ".")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Blow proposes JSON-flavoured hypermedia as the fix — " (code "vnd.siren+json") ", custom content types, link relations in response payloads. Resources become state machines. Hyperlinks are state transitions. The agent explores the graph.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is the right diagnosis. But the prescription misidentifies the disease. The problem is not that we need a better wire format for links. The problem is that neither JSON nor HTML is actually hypermedia. And no amount of convention layered on top will make them so, because what makes something hypermedia is not what it carries but whether it can " (em "define itself") "."))
|
||||
|
||||
(~docs/section :title "II. The self-definition criterion" :id "self-definition"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"What does it mean for a format to be hypermedia? The conventional answer focuses on " (em "affordances") ": a hypermedia format includes controls that tell the client what it can do next. Links, forms, actions. This is the definition that drives the JSON hypermedia proposals — add links to JSON, and JSON becomes hypermedia.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"But this definition is too shallow. It describes a property of the content without asking where the " (em "meaning") " of the content comes from. A Siren response includes an " (code "actions") " array — but what makes that array mean \"available actions\" rather than just \"a list of objects with href fields\"? The Siren specification, written in English prose, hosted on a separate document, maintained by a separate community. The meaning is " (em "external") " to the format.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A true hypermedium does not need an external document to explain what it means. It defines its own interpretation. The format carries not just content and controls, but the rules by which content and controls are understood. The map " (em "is") " the territory — not because the map is accurate, but because the map can redraw itself.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is a strict criterion. It eliminates almost everything. And it should, because almost nothing on the web is actually hypermedia in this sense. Most of what we call hypermedia is data decorated with navigational metadata, interpreted by an engine built from a separate specification in a separate language."))
|
||||
|
||||
(~docs/section :title "III. JSON cannot define itself" :id "json-cannot"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"You cannot write the JSON specification in JSON.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not a trivia question. It reveals something fundamental about the format. JSON is a data serialization: maps, arrays, strings, numbers, booleans, null. It has no evaluation semantics. It cannot express a grammar. It cannot express a parser. It cannot express the rules by which a JSON document should be interpreted — because it cannot express " (em "rules") " at all. JSON describes " (em "what") " without any capacity to describe " (em "how") " or " (em "why") ".")
|
||||
(p :class "text-stone-600"
|
||||
"The JSON specification is " (a :href "https://www.rfc-editor.org/rfc/rfc8259" :class "text-violet-600 hover:underline" "RFC 8259") " — English prose. This is not an accident or a stylistic choice. JSON " (em "cannot") " define itself because it has no mechanisms for definition. It is inert data. Every scrap of meaning attached to a JSON document — this key means a link, this object means an action, this array means available transitions — must come from outside the document. From a spec. From a schema. From shared understanding between producer and consumer.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The JSON specification is " (a :href "https://www.rfc-editor.org/rfc/rfc8259" (~tw :tokens "text-violet-600 hover:underline") "RFC 8259") " — English prose. This is not an accident or a stylistic choice. JSON " (em "cannot") " define itself because it has no mechanisms for definition. It is inert data. Every scrap of meaning attached to a JSON document — this key means a link, this object means an action, this array means available transitions — must come from outside the document. From a spec. From a schema. From shared understanding between producer and consumer.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is why JSON hypermedia formats never achieve escape velocity. HAL, Siren, Hydra, JSON:API, UBER, Collection+JSON — each adds a linking convention on top of JSON, and each generates the same amount of adoption: enough for a conference talk, not enough for an ecosystem. The problem is not that the conventions are poorly designed. The problem is that a convention layered on inert data is " (em "always") " a separate document the client must already understand. You have not eliminated the manual. You have moved it from the system prompt to a media type specification."))
|
||||
|
||||
(~docs/section :title "IV. HTML carries but does not define itself" :id "html-carries"
|
||||
(p :class "text-stone-600"
|
||||
"HTML is closer to the mark. You " (em "can") " write the HTML specification in HTML — and indeed, the " (a :href "https://html.spec.whatwg.org/" :class "text-violet-600 hover:underline" "WHATWG specification") " is an HTML document. This already puts HTML in a different category from JSON. The format can at least " (em "carry") " its own definition.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"HTML is closer to the mark. You " (em "can") " write the HTML specification in HTML — and indeed, the " (a :href "https://html.spec.whatwg.org/" (~tw :tokens "text-violet-600 hover:underline") "WHATWG specification") " is an HTML document. This already puts HTML in a different category from JSON. The format can at least " (em "carry") " its own definition.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"But carrying is not defining. The HTML spec rendered in a browser is text and diagrams — documentation that happens to be displayed in the format it documents. The browser interpreting that document was built from a " (em "separate implementation") " — millions of lines of C++ in Chromium, Gecko, WebKit — that was written by reading the spec and translating it into executable code. The spec does not " (em "execute") ". It does not " (em "interpret") ". It does not " (em "define itself") " in any operative sense.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Put it this way: if you gave the HTML specification (as an HTML document) to a system that had never seen HTML before, could that system learn to render HTML by reading it? No. The document is English prose in HTML tags. The tags are meaningless without a pre-existing renderer. The spec " (em "presupposes") " the very thing it specifies. It is circular, but not " (em "productively") " circular — not metacircular in the way that would let the system bootstrap itself.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This matters because it means HTML's meaning is ultimately external too. More deeply embedded than JSON's — the browser internalizes the spec so thoroughly that HTML " (em "feels") " self-interpreting — but still dependent on a vast external system to give it life. The " (code "<form>") " tag is self-describing only to a client that already knows what forms are. To everything else, it is angle brackets."))
|
||||
|
||||
(~docs/section :title "V. SX defines itself" :id "sx-defines-itself"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The SX specification is written in SX. This sounds like the same trick as HTML — the spec in its own format — but it is categorically different.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(code "eval.sx") " defines the SX evaluator as s-expressions. Not as documentation " (em "about") " the evaluator. As the evaluator " (em "itself") ". A bootstrapper reads " (code "eval.sx") " and produces a working evaluator — in JavaScript, in Python, in any target language. " (code "parser.sx") " defines the SX parser as s-expressions. A bootstrapper reads it and produces a working parser. " (code "render.sx") " defines the renderer. " (code "primitives.sx") " defines the primitive operations.")
|
||||
(~docs/code :src (highlight ";; From eval.sx — the evaluator defining itself:\n\n(define eval-expr\n (fn (expr env mode)\n (cond\n ((number? expr) expr)\n ((string? expr) expr)\n ((boolean? expr) expr)\n ((nil? expr) expr)\n ((symbol? expr) (resolve-symbol expr env))\n ((keyword? expr) (keyword-name expr))\n ((dict? expr) (eval-dict expr env mode))\n ((list? expr)\n (let ((head (first expr)))\n (if (symbol? head)\n (let ((name (symbol-name head)))\n (if (special-form? name)\n (eval-special-form name (rest expr) env mode)\n (eval-call expr env mode)))\n (eval-call expr env mode)))))))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not documentation. It is a " (em "program that defines the rules of its own interpretation") ". The evaluator that processes SX expressions is itself an SX expression. The spec does not merely " (em "describe") " how SX works — it " (em "is") " how SX works. Give this file to a bootstrapper, and out comes a functioning evaluator. The specification is executable. The definition " (em "is") " the implementation.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is what makes SX a genuine hypermedium. The meaning of an SX document is not external. It is not in a separate RFC. It is not in a browser engine compiled from a prose specification. The meaning is " (em "in the same language as the content") " — and it is the kind of meaning that executes. You do not need prior knowledge of SX to interpret SX, because SX carries the knowledge required to interpret it. You need only a bootstrapper — a minimal bridge to a host language — and the spec bootstraps the rest.")
|
||||
(p :class "text-stone-600"
|
||||
"The " (a :href "/sx/(etc.(essay.self-defining-medium))" :class "text-violet-600 hover:underline" "true hypermedium must define itself with itself") ". JSON cannot even attempt this. HTML can carry the words but not the meaning. SX carries the meaning because the meaning is code, and SX is code all the way down."))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The " (a :href "/sx/(etc.(essay.self-defining-medium))" (~tw :tokens "text-violet-600 hover:underline") "true hypermedium must define itself with itself") ". JSON cannot even attempt this. HTML can carry the words but not the meaning. SX carries the meaning because the meaning is code, and SX is code all the way down."))
|
||||
|
||||
(~docs/section :title "VI. What self-definition gives you" :id "consequences"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Every practical advantage of SX over JSON and HTML for hypermedia flows from this single property.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(strong "Progressive discovery") " works because controls are not metadata interpreted by convention — they are expressions evaluated by the same evaluator that processes content. The server renders conditional controls using the language itself:")
|
||||
(~docs/code :src (highlight "(when can-cancel\n (button :sx-post \"/orders/4281/cancel\"\n :sx-confirm \"Cancel this order?\"\n \"Cancel order\"))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The " (code "when") " is not a convention layered on data. It is a special form defined in " (code "eval.sx") ". The server evaluates it. The client sees only the controls that survive evaluation. The state machine is authored in the language, not in metadata " (em "about") " the language.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(strong "Components") " work because the language has function abstraction — " (code "defcomp") " — defined in its own spec. A component is a reusable hypermedia control that accepts parameters and renders children. JSON cannot have components because JSON cannot define functions. HTML cannot have components natively — Web Components are defined in JavaScript, a " (em "separate") " language. SX components are defined in SX, evaluated by an evaluator defined in SX.")
|
||||
(p :class "text-stone-600"
|
||||
(strong "Evaluable URLs") " work because the URL is an s-expression — " (code "(etc.(essay.hypermedia-age-of-ai))") " — evaluated by the same evaluator. Following a link " (em "is") " calling a function. The addressing scheme is not a convention imposed from outside. It is the " (a :href "/sx/(applications.(sx-urls))" :class "text-violet-600 hover:underline" "language applied to navigation") ".")
|
||||
(p :class "text-stone-600"
|
||||
(strong "AI legibility") " works because the " (a :href "/sx/(etc.(essay.sx-and-ai))" :class "text-violet-600 hover:underline" "spec fits in a context window") ". An AI agent can load " (code "eval.sx") ", " (code "parser.sx") ", " (code "render.sx") ", and " (code "primitives.sx") " — roughly 3,000 lines — and hold the " (em "complete definition of the language") " alongside the content it is reading. It does not need training data about SX. It does not need documentation. It has the actual executable specification. The language it reads and the language that defines how to read it are the same language.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(strong "Evaluable URLs") " work because the URL is an s-expression — " (code "(etc.(essay.hypermedia-age-of-ai))") " — evaluated by the same evaluator. Following a link " (em "is") " calling a function. The addressing scheme is not a convention imposed from outside. It is the " (a :href "/sx/(applications.(sx-urls))" (~tw :tokens "text-violet-600 hover:underline") "language applied to navigation") ".")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(strong "AI legibility") " works because the " (a :href "/sx/(etc.(essay.sx-and-ai))" (~tw :tokens "text-violet-600 hover:underline") "spec fits in a context window") ". An AI agent can load " (code "eval.sx") ", " (code "parser.sx") ", " (code "render.sx") ", and " (code "primitives.sx") " — roughly 3,000 lines — and hold the " (em "complete definition of the language") " alongside the content it is reading. It does not need training data about SX. It does not need documentation. It has the actual executable specification. The language it reads and the language that defines how to read it are the same language.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"None of these are features bolted onto a data format. They are consequences of a format that defines itself."))
|
||||
|
||||
(~docs/section :title "VII. What MCP gets wrong" :id "mcp"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"MCP's design reflects a fundamental confusion: it treats capability as a catalogue rather than an affordance. The server lists every tool. The model reads the list. The model calls a tool by name. The response is data. The model must maintain its own mental model of the server's state to know which tools are appropriate next.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is the exact inversion of the hypermedia principle. In a hypermedia system, the server tells you what you can do " (em "in each response") ". You do not need a mental model of the server because the server renders its own state into the controls it offers you.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"But the deeper problem with MCP is not the catalogue model. It is that MCP tools are defined " (em "outside") " the protocol — in Python functions, TypeScript classes, whatever the server implementer chose. The tool definitions are opaque to the client. The model sees names and descriptions, not the actual logic. It is trusting documentation written by a human about code written by a human. Every layer of indirection is a place where meaning can leak.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In an SX hypermedia system, the controls in the response " (em "are") " the capability. Not a description of capability. Not a pointer to capability. The control itself — its structure, its attributes, its evaluable URL — carries everything needed to act on it. And the rules for interpreting the control are carried by the same language the control is written in. There is no indirection. There is no separate layer where meaning must be maintained. The response means what it says, and it says what it means, because it carries its own interpreter."))
|
||||
|
||||
(~docs/section :title "VIII. There is only the hypermedium" :id "the-hypermedium"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The hypermedia discourse frames the question as a competition between formats: HTML vs JSON, server-rendered vs client-rendered, REST vs RPC. Blow adds a new axis — human clients vs AI agents — and asks which format serves both. These are all the wrong questions. They assume that hypermedia is a property that a format can have in greater or lesser degree, and the task is to find the format with the most of it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Hypermedia is not a spectrum. It is a threshold. Either a format can define its own interpretation, or it cannot. If it cannot, it depends on external meaning — an RFC, a browser engine, a media type specification — and that dependency makes it something less than a " (em "medium") ". A medium is self-sustaining. It does not require a separate system to explain what it is. Sheet music is a medium because a musician can read it and produce sound. A JSON object with " (code "\"type\": \"sheet-music\"") " is not a medium. It is data that requires a separate program, written in a separate language, consulting a separate specification, to become anything at all.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"By this criterion, HTML is not hypermedia. It is the closest the web has come — close enough that the browser's deep internalization of the spec creates the " (em "illusion") " of self-interpretation. But the illusion breaks the moment you step outside the browser. Give HTML to a new client — an AI agent, a screen reader, a search crawler — and that client must reimagine the spec from training data, heuristics, and hope. The meaning was never in the HTML. It was in the browser.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"JSON is not hypermedia and never will be, no matter how many link relations you attach to it. The meaning is always elsewhere.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"There is only the hypermedium: a representation that defines its own interpretation. A representation where the spec is written in the language, and the spec " (em "is") " the language — executable, bootstrappable, self-sustaining. S-expressions are not the only possible instance of this. Any homoiconic, metacircular language could satisfy the criterion. But s-expressions are the simplest. The minimal syntax — " (code "(head args...)") " — is the minimal overhead between a format and self-definition. McCarthy arrived at this in 1958. It has taken sixty-eight years for the rest of computing to need it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The age of AI makes the need urgent. When your clients are no longer just browsers with built-in specs but arbitrary agents that must learn interpretation on the fly, a format that carries its own interpreter is not a luxury. It is the only thing that works. Not because it is clever. Because everything else depends on meaning that lives somewhere else — and " (em "somewhere else") " is the one place an ad-hoc agent cannot be guaranteed to look.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX represents the hypermedium. Not a new format in the format wars. Not a better JSON. Not a simpler HTML. The thing itself — a representation that " (em "means what it says") " because it carries the rules for saying it."))))
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
(~docs/page
|
||||
:title "Essays"
|
||||
(div
|
||||
:class "space-y-4"
|
||||
(~tw :tokens "space-y-4")
|
||||
(p
|
||||
:class "text-lg text-stone-600 mb-4"
|
||||
(~tw :tokens "text-lg text-stone-600 mb-4")
|
||||
"Opinions, rationales, and explorations around SX and the ideas behind it.")
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(~tw :tokens "space-y-3")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
@@ -20,9 +20,9 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors"
|
||||
(div :class "font-semibold text-stone-800" (get item "label"))
|
||||
(~tw :tokens "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors")
|
||||
(div (~tw :tokens "font-semibold text-stone-800") (get item "label"))
|
||||
(when
|
||||
(get item "summary")
|
||||
(p :class "text-sm text-stone-500 mt-1" (get item "summary")))))
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") (get item "summary")))))
|
||||
essays-nav-items)))))
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
(defcomp ~essays/no-alternative/essay-no-alternative ()
|
||||
(~docs/page :title "There Is No Alternative"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Every attempt to escape s-expressions leads back to s-expressions. This is not an accident.")
|
||||
|
||||
(~docs/section :title "The claim" :id "claim"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The requirements" :id "requirements"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "Markup") " — structure for HTML pages, components, layouts")
|
||||
(li (strong "Programming language") " — conditionals, iteration, functions, macros, closures")
|
||||
(li (strong "Wire format") " — what the server sends to the client over HTTP")
|
||||
(li (strong "Data notation") " — configuration, page definitions, component registries")
|
||||
(li (strong "Spec language") " — the SX specification is written in SX")
|
||||
(li (strong "Metaprogramming substrate") " — macros that read, transform, and generate code"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Any replacement must handle all six roles with a single syntax. Not six syntaxes awkwardly interleaved — one. This constraint alone eliminates most candidates, because most representations were designed for one of these roles and are ill-suited to the others.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Beyond versatility, the representation must be:")
|
||||
(ul :class "space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "Homoiconic") " — code must be data and data must be code, because macros and self-hosting require it")
|
||||
(li (strong "Parseable in one pass") " — no forward references, no context-dependent grammar, because the wire format must be parseable by a minimal client")
|
||||
(li (strong "Structurally validatable") " — a syntactically valid expression must be checkable without evaluation, because untrusted code from federated nodes must be validated before execution")
|
||||
@@ -33,166 +33,166 @@
|
||||
(~docs/section :title "The candidates" :id "candidates"
|
||||
|
||||
(~docs/subsection :title "XML / HTML"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The obvious first thought. XML is a tree. HTML is markup. Why not use angle brackets?")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"XML fails on homoiconicity. The distinction between elements, attributes, text nodes, processing instructions, CDATA sections, entity references, and namespaces means the representation has multiple structural categories that cannot freely substitute for each other. An attribute is not an element. A text node is not a processing instruction. You cannot take an arbitrary XML fragment and use it as code, because XML has no concept of evaluation — it is a serialization format for trees, not a language.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"XML also fails on token efficiency. " (code "<div class=\"card\"><h2>Title</h2></div>") " versus " (code "(div :class \"card\" (h2 \"Title\"))") ". The closing tags carry zero information — they are pure redundancy. Over a full application, this redundancy compounds into significantly more bytes on the wire and significantly more tokens in an LLM context window.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "JSON"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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:")
|
||||
(~docs/code :src (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"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~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"
|
||||
"Indentation sensitivity is a direct disqualifier for wire formats. Whitespace must survive serialization, transmission, minification, and reconstruction exactly — a fragility that s-expressions do not have. YAML also fails on structural validation: the " (a :href "https://en.wikipedia.org/wiki/Norway_problem" :class "text-violet-600 hover:underline" "Norway problem") " (" (code "NO") " parsed as boolean " (code "false") ") demonstrates that YAML's type coercion makes structural validation impossible without semantic knowledge of the schema.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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/" (~tw :tokens "text-violet-600 hover:underline") "specification") " that is 240 pages long. The spec for SX's parser is 200 lines.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Indentation sensitivity is a direct disqualifier for wire formats. Whitespace must survive serialization, transmission, minification, and reconstruction exactly — a fragility that s-expressions do not have. YAML also fails on structural validation: the " (a :href "https://en.wikipedia.org/wiki/Norway_problem" (~tw :tokens "text-violet-600 hover:underline") "Norway problem") " (" (code "NO") " parsed as boolean " (code "false") ") demonstrates that YAML's type coercion makes structural validation impossible without semantic knowledge of the schema.")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "JSX / Template literals"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This means JSX cannot be a wire format — the client must have the compiler. It cannot be a spec language — you cannot write a JSX spec in JSX without a build step. It cannot be a data notation — it requires JavaScript evaluation context. JSX handles exactly one of the six roles (markup) and delegates the others to JavaScript, CSS, JSON, and whatever build tool assembles them.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "Tcl"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Where Tcl falls short is structural validation. Because everything is a string, you cannot check that a Tcl program is well-formed without evaluating it. Unmatched braces inside string values are indistinguishable from syntax errors without context. S-expressions have a trivial structural check — balanced parentheses — that requires no evaluation and no context. For sandboxed evaluation of untrusted code (federated expressions from other nodes), this difference is decisive.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~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"
|
||||
(p (~tw :tokens "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" (~tw :tokens "text-violet-600 hover:underline") "Carl Sassenrath") " designed it explicitly to solve the problems that SX also targets.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Rebol's limitation is practical, not theoretical. The language is obscure. Modern AI models have almost no training data for it — generating reliable Rebol would require extensive fine-tuning or few-shot prompting. There is no ecosystem of libraries, no community producing components, no federation protocol. And Rebol's type system (over 40 built-in datatypes including " (code "url!") ", " (code "email!") ", " (code "money!") ") makes the parser substantially more complex than s-expressions, which have essentially one composite type: the list.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "Forth / stack-based"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"But Forth's simplicity is deceptive. The flat token stream encodes tree structure " (em "implicitly") " via stack effects. Understanding what a Forth program does requires mentally simulating the stack — tracking what each word pushes and pops. This is precisely the kind of implicit state tracking that both humans and AI models struggle with. A nested s-expression makes structure " (em "visible") ". A Forth program hides it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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.")))
|
||||
|
||||
(~docs/section :title "The convergence" :id "convergence"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Every alternative either fails to meet the requirements or reinvents s-expressions:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Candidate")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Homoiconic")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Structural validation")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Token-efficient")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Tree-native")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "AI-trainable")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Candidate")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Homoiconic")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Structural validation")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Token-efficient")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Tree-native")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "AI-trainable")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "XML/HTML")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JSON")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "YAML")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-amber-600" "Moderate")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JSX")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-red-600" "Needs compiler")
|
||||
(td :class "px-3 py-2 text-amber-600" "Moderate")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Tcl")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-red-600" "No"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Rebol/Red")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-amber-600" "Complex")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-red-600" "No"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Forth")
|
||||
(td :class "px-3 py-2 text-amber-600" "Sort of")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700" "Yes")
|
||||
(td :class "px-3 py-2 text-red-600" "No")
|
||||
(td :class "px-3 py-2 text-red-600" "No"))
|
||||
(tr :class "border-b border-stone-200 bg-violet-50"
|
||||
(td :class "px-3 py-2 font-semibold text-violet-800" "S-expressions")
|
||||
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")
|
||||
(td :class "px-3 py-2 text-green-700 font-semibold" "Yes")))))
|
||||
(p :class "text-stone-600"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "XML/HTML")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JSON")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "YAML")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-amber-600") "Moderate")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JSX")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "Needs compiler")
|
||||
(td (~tw :tokens "px-3 py-2 text-amber-600") "Moderate")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Tcl")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Rebol/Red")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-amber-600") "Complex")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Forth")
|
||||
(td (~tw :tokens "px-3 py-2 text-amber-600") "Sort of")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No")
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") "No"))
|
||||
(tr (~tw :tokens "border-b border-stone-200 bg-violet-50")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-violet-800") "S-expressions")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-semibold") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-semibold") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-semibold") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-semibold") "Yes")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-semibold") "Yes")))))
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Why not invent something new?" :id "invent"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Try it. You need a tree structure (for markup and composition). You need a uniform representation (for homoiconicity). You need a delimiter that is unambiguous (for one-pass parsing and structural validation). You need minimum syntactic overhead (for token efficiency).")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A tree needs a way to mark where a node begins and ends. The minimal delimiter pair is two characters — one for open, one for close. S-expressions use " (code "(") " and " (code ")") ". You could use " (code "[") " and " (code "]") ", or " (code "{") " and " (code "}") ", or " (code "BEGIN") " and " (code "END") ", or indentation. But " (code "[list]") " and " (code "{list}") " are just s-expressions with different brackets. " (code "BEGIN/END") " adds token overhead. Indentation adds whitespace sensitivity, which breaks wire-format reliability.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"You could try eliminating delimiters entirely and using a binary format. But binary formats are not human-readable, not human-writable, and not inspectable in a terminal — which means they cannot serve as a programming language. The developer experience of reading and writing code requires a text-based representation, and the minimal text-based tree delimiter is a matched pair of single characters.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"You could try significant whitespace — indentation-based nesting like Python or Haskell. This works for programming languages where the code is stored in files and processed by a single toolchain. It does not work for wire formats, where the representation must survive HTTP transfer, server-side generation, client-side parsing, minification, storage in databases, embedding in script tags, and concatenation with other expressions. Whitespace-sensitive formats are fragile in exactly the contexts where SX operates.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The parentheses objection" :id "parentheses"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is a human problem, not a representation problem. And it is a human problem with a known trajectory: every programmer who has used a Lisp for more than a few weeks stops seeing the parentheses. They see the tree. The delimiters become invisible, like the spaces between words in English. You do not see spaces. You see words. Lisp programmers do not see parentheses. They see structure.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"More to the point: in the world we are entering, most code will be generated by AI and rendered by machines. The human reads the " (em "output") " — the rendered page, the test results, the behaviour. The s-expressions are an intermediate representation that the human steers but does not need to manually type or visually parse character-by-character. The aesthetic objection dissolves when the representation is a conversation between the human's intent and the machine's generation, not something the human stares at in a text editor.")
|
||||
(p :class "text-stone-600"
|
||||
"The author of SX has never opened the codebase in an editor. Every file was created through " (a :href "https://claude.ai/" :class "text-violet-600 hover:underline" "Claude") " in a terminal. The parentheses are between the human and the machine, and neither one minds them."))
|
||||
(p (~tw :tokens "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/" (~tw :tokens "text-violet-600 hover:underline") "Claude") " in a terminal. The parentheses are between the human and the machine, and neither one minds them."))
|
||||
|
||||
(~docs/section :title "The conclusion" :id "conclusion"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not a claim that s-expressions are the best syntax for every programming language. They are not. Python's indentation-based syntax is better for imperative scripting. Haskell's layout rules are better for type-heavy functional programming. SQL is better for relational queries.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The claim is narrower and stronger: for a system that must simultaneously serve as markup, programming language, wire format, data notation, spec language, and metaprogramming substrate — with homoiconicity, one-pass parsing, structural validation, token efficiency, and composability — there is no alternative to s-expressions. Not because alternatives have not been tried. Not because the design space has not been explored. But because the requirements, when stated precisely, admit exactly one family of solutions, and that family is the one McCarthy discovered sixty-seven years ago.")
|
||||
(p :class "text-stone-600"
|
||||
"The name for this, borrowed from a " (a :href "https://en.wikipedia.org/wiki/There_is_no_alternative" :class "text-violet-600 hover:underline" "different context entirely") ", is " (em "TINA") " — there is no alternative. Not as a political slogan, but as a mathematical observation. When you need a minimal, homoiconic, structurally-validatable, token-efficient, tree-native, AI-compatible representation for the web, you need s-expressions. Everything else is either less capable or isomorphic."))))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The name for this, borrowed from a " (a :href "https://en.wikipedia.org/wiki/There_is_no_alternative" (~tw :tokens "text-violet-600 hover:underline") "different context entirely") ", is " (em "TINA") " — there is no alternative. Not as a political slogan, but as a mathematical observation. When you need a minimal, homoiconic, structurally-validatable, token-efficient, tree-native, AI-compatible representation for the web, you need s-expressions. Everything else is either less capable or isomorphic."))))
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(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."))))
|
||||
(~docs/page :title "On-Demand CSS: Killing the Tailwind Bundle" (~docs/section :title "The problem" :id "problem" (p (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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,12 +4,12 @@
|
||||
(~docs/page
|
||||
:title "Philosophy"
|
||||
(div
|
||||
:class "space-y-4"
|
||||
(~tw :tokens "space-y-4")
|
||||
(p
|
||||
:class "text-lg text-stone-600 mb-4"
|
||||
(~tw :tokens "text-lg text-stone-600 mb-4")
|
||||
"The deeper ideas behind SX — manifestos, self-reference, and the philosophical traditions that shaped the language.")
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(~tw :tokens "space-y-3")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
@@ -20,9 +20,9 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors"
|
||||
(div :class "font-semibold text-stone-800" (get item "label"))
|
||||
(~tw :tokens "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors")
|
||||
(div (~tw :tokens "font-semibold text-stone-800") (get item "label"))
|
||||
(when
|
||||
(get item "summary")
|
||||
(p :class "text-sm text-stone-500 mt-1" (get item "summary")))))
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") (get item "summary")))))
|
||||
philosophy-nav-items)))))
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
(defcomp ~essays/platonic-sx/essay-platonic-sx ()
|
||||
(~docs/page :title "Platonic SX"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"The allegory of the cave, the theory of Forms, and why a hypermedium "
|
||||
"that defines itself participates in something Plato would have recognized.")
|
||||
|
||||
(~docs/section :title "The cave" :id "the-cave"
|
||||
(p :class "text-stone-600"
|
||||
"In Book VII of the " (a :href "https://en.wikipedia.org/wiki/Allegory_of_the_cave" :class "text-violet-600 hover:underline" "Republic")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In Book VII of the " (a :href "https://en.wikipedia.org/wiki/Allegory_of_the_cave" (~tw :tokens "text-violet-600 hover:underline") "Republic")
|
||||
", Plato describes prisoners chained in a cave, facing a wall. "
|
||||
"Behind them burns a fire; between the fire and the prisoners, figures move, "
|
||||
"casting shadows on the wall. The prisoners have never seen the figures directly. "
|
||||
"They take the shadows for reality.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The allegory is about representation. The shadows are not the things themselves "
|
||||
"but projections \u2014 reduced, flattened, stripped of depth and colour. "
|
||||
"The prisoners mistake the representation for the thing represented. "
|
||||
"They build entire theories about the behaviour of shadows, never suspecting "
|
||||
"that the shadows are derived from something more real.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The web is a cave."))
|
||||
|
||||
(~docs/section :title "Shadows on the wall" :id "shadows"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"An HTML page is a shadow. It is a projection of the thing the author intended \u2014 "
|
||||
"a structure, a meaning, a behaviour \u2014 flattened into a string of angle brackets. "
|
||||
"The structure is lost in the serialization. The meaning is implicit in class names "
|
||||
"and data attributes. The behaviour is bolted on in a separate language (JavaScript) "
|
||||
"that has no formal relationship to the markup it manipulates.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"CSS is another shadow \u2014 a projection of visual intention into a cascade of "
|
||||
"property-value pairs, separated from the structure it describes. "
|
||||
"JSON is a shadow of data, stripped of type, context, and behaviour. "
|
||||
"REST is a shadow of computation, reduced to verbs and resource paths.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Each format is a lossy projection. And crucially, each projects into a "
|
||||
(em "different") " medium. HTML for structure. CSS for style. JavaScript for behaviour. "
|
||||
"JSON for data. The original unity \u2014 the " (em "thing itself") " \u2014 is scattered "
|
||||
"across four representations that cannot reference each other "
|
||||
"without external convention.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Plato would have recognized this immediately. The Forms are one. "
|
||||
"The shadows are many. The task of philosophy is to see past the shadows."))
|
||||
|
||||
(~docs/section :title "The theory of Forms" :id "forms"
|
||||
(p :class "text-stone-600"
|
||||
"Plato's " (a :href "https://en.wikipedia.org/wiki/Theory_of_forms" :class "text-violet-600 hover:underline" "theory of Forms")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Plato's " (a :href "https://en.wikipedia.org/wiki/Theory_of_forms" (~tw :tokens "text-violet-600 hover:underline") "theory of Forms")
|
||||
" holds that behind every particular instance \u2014 every chair, every circle, "
|
||||
"every act of justice \u2014 there exists an ideal Form: the perfect Chair, "
|
||||
"the perfect Circle, Justice itself. Particular instances participate in their Form "
|
||||
"but are always imperfect copies. The Form is eternal, immutable, and more real "
|
||||
"than any instance.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A " (code "defcomp") " definition is a Form:")
|
||||
|
||||
(~docs/code :src
|
||||
@@ -67,45 +67,45 @@
|
||||
" (when subtitle (p :class \"text-stone-500\" subtitle))\n"
|
||||
" children))"))
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not a card. It is the " (em "idea") " of a card \u2014 the structure that every "
|
||||
"particular card participates in. When the server evaluates "
|
||||
(code "(~card :title \"Plato\" :subtitle \"428 BC\")") ", it produces a particular instance: "
|
||||
"an HTML fragment, a shadow on a specific wall. The Form persists. "
|
||||
"The shadow is consumed and replaced on the next render.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In Plato's ontology, Forms are more real than particulars because they are "
|
||||
"what particulars " (em "depend on") ". The HTML output depends on the component definition. "
|
||||
"The component definition does not depend on any particular output. "
|
||||
"It is prior, in the way that axioms are prior to theorems."))
|
||||
|
||||
(~docs/section :title "The divided line" :id "divided-line"
|
||||
(p :class "text-stone-600"
|
||||
"In the Republic, Plato describes " (a :href "https://en.wikipedia.org/wiki/Analogy_of_the_divided_line" :class "text-violet-600 hover:underline" "a line divided into four segments")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In the Republic, Plato describes " (a :href "https://en.wikipedia.org/wiki/Analogy_of_the_divided_line" (~tw :tokens "text-violet-600 hover:underline") "a line divided into four segments")
|
||||
", representing degrees of reality and knowledge:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Segment")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Plato")
|
||||
(th :class "text-left pb-2 font-semibold" "SX")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Segment")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Plato")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "SX")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "Images")
|
||||
(td :class "pr-4" "Shadows, reflections")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Images")
|
||||
(td (~tw :tokens "pr-4") "Shadows, reflections")
|
||||
(td "The rendered HTML in the browser \u2014 a momentary projection"))
|
||||
(tr (td :class "pr-4 py-1" "Sensible objects")
|
||||
(td :class "pr-4" "Physical things")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Sensible objects")
|
||||
(td (~tw :tokens "pr-4") "Physical things")
|
||||
(td "The SX wire format \u2014 structured but still particular"))
|
||||
(tr (td :class "pr-4 py-1" "Mathematical objects")
|
||||
(td :class "pr-4" "Numbers, geometric shapes")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Mathematical objects")
|
||||
(td (~tw :tokens "pr-4") "Numbers, geometric shapes")
|
||||
(td "Component definitions, the CEK machine, continuation frames"))
|
||||
(tr (td :class "pr-4 py-1" "The Good / Forms")
|
||||
(td :class "pr-4" "The Form of Forms")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "The Good / Forms")
|
||||
(td (~tw :tokens "pr-4") "The Form of Forms")
|
||||
(td "The s-expression itself \u2014 the representation that represents")))))
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The bottom of the line is images \u2014 the DOM, pixels on screen. "
|
||||
"Moving up: the SX wire format preserves more structure than HTML "
|
||||
"(it retains the component calls, the s-expression nesting). "
|
||||
@@ -114,7 +114,7 @@
|
||||
"At the top: the s-expression itself, which is both the medium of definition "
|
||||
"and the thing defined.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Plato's line is a hierarchy of " (em "participation") ". "
|
||||
"Each level participates in the one above. "
|
||||
"The rendered HTML participates in the component definition. "
|
||||
@@ -124,27 +124,27 @@
|
||||
"In computation itself. In the CEK machine. In logic."))
|
||||
|
||||
(~docs/section :title "Anamnesis: the evaluator remembers" :id "anamnesis"
|
||||
(p :class "text-stone-600"
|
||||
"Plato believed that learning is " (a :href "https://en.wikipedia.org/wiki/Anamnesis_(philosophy)" :class "text-violet-600 hover:underline" "recollection")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Plato believed that learning is " (a :href "https://en.wikipedia.org/wiki/Anamnesis_(philosophy)" (~tw :tokens "text-violet-600 hover:underline") "recollection")
|
||||
" \u2014 " (em "anamnesis") ". The soul has seen the Forms before birth; "
|
||||
"education is not acquiring new knowledge but remembering what is already known. "
|
||||
"The particular reminds us of the universal.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The SX evaluator does something structurally similar. "
|
||||
"When the browser receives SX wire format \u2014 ")
|
||||
|
||||
(~docs/code :src
|
||||
"(~card :title \"Plato\" :subtitle \"428 BC\")")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
" \u2014 it does not receive instructions for rendering. It receives a " (em "name") " "
|
||||
"and a set of arguments. The evaluator already knows " (code "~card") ". "
|
||||
"It has the Form in its component environment. "
|
||||
"The wire format is a reminder: " (em "produce the instance you already know how to produce") ". "
|
||||
"The particular prompts the evaluator to recollect the universal.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is why the SX wire format is so small. It doesn't transmit the " (em "what") " "
|
||||
"\u2014 the full HTML, the complete structure. It transmits the " (em "which") " "
|
||||
"\u2014 which Form, which arguments. The evaluator supplies the rest from memory. "
|
||||
@@ -152,21 +152,21 @@
|
||||
"because the client " (em "remembers") "."))
|
||||
|
||||
(~docs/section :title "Platonic aesthetics" :id "aesthetics"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"For Plato, beauty is not subjective. A thing is beautiful to the degree "
|
||||
"that it participates in the " (a :href "https://en.wikipedia.org/wiki/Platonic_beauty" :class "text-violet-600 hover:underline" "Form of Beauty")
|
||||
"that it participates in the " (a :href "https://en.wikipedia.org/wiki/Platonic_beauty" (~tw :tokens "text-violet-600 hover:underline") "Form of Beauty")
|
||||
" \u2014 which is to say, to the degree that it exhibits "
|
||||
(em "order") ", " (em "proportion") ", and " (em "unity") ". "
|
||||
"The " (a :href "https://en.wikipedia.org/wiki/Symposium_(Plato)" :class "text-violet-600 hover:underline" "Symposium")
|
||||
"The " (a :href "https://en.wikipedia.org/wiki/Symposium_(Plato)" (~tw :tokens "text-violet-600 hover:underline") "Symposium")
|
||||
" describes an ascent from beautiful bodies to beautiful souls to beautiful ideas, "
|
||||
"culminating in Beauty itself \u2014 the Form that makes all beautiful things beautiful.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"There is a beauty in s-expressions that is Platonic in this precise sense. "
|
||||
"Not decorative beauty \u2014 no one finds parentheses pretty. "
|
||||
"But structural beauty: the kind Plato meant.")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-2 text-stone-600")
|
||||
(li (strong "Unity") " \u2014 one representation for everything. "
|
||||
"Code, data, markup, wire format, component definitions, the evaluator itself. "
|
||||
"No seams, no translation boundaries, no format negotiation. "
|
||||
@@ -181,7 +181,7 @@
|
||||
"Each layer is definable in terms of the one below. "
|
||||
"No circular dependencies, no ad hoc escape hatches, no exceptions to the rules."))
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Plato would say that these properties are not incidental but necessary \u2014 "
|
||||
"they follow from the proximity of s-expressions to the Forms themselves. "
|
||||
"A representation that can represent itself has fewer impediments "
|
||||
@@ -190,19 +190,19 @@
|
||||
"that requires a separate meta-representation to describe it."))
|
||||
|
||||
(~docs/section :title "The escape from the cave" :id "escape"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In the allegory, one prisoner is freed and dragged up into the sunlight. "
|
||||
"At first the light is blinding. He can only look at reflections in water, "
|
||||
"then at objects, then at the sun itself. He returns to the cave "
|
||||
"and tries to tell the others what he saw. They think he is mad.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The web's cave is comfortable. Developers have built elaborate theories "
|
||||
"of the shadows \u2014 virtual DOMs, hydration strategies, build tool chains, "
|
||||
"CSS-in-JS, state management libraries. Each theory explains how shadows behave. "
|
||||
"None asks why we are working with shadows at all.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The escape is not a technology. It is a shift in perspective: "
|
||||
(em "stop working with projections and work with the thing itself") ". "
|
||||
"An s-expression is not a projection of structure into text \u2014 "
|
||||
@@ -211,21 +211,21 @@
|
||||
"The SX evaluator is not described by a specification \u2014 "
|
||||
"it IS the specification, executing.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is what it means for a representation to be homoiconic. "
|
||||
"The map is the territory. The shadow and the figure are the same thing. "
|
||||
"The cave and the sunlit world collapse into one."))
|
||||
|
||||
(~docs/section :title "The demiurge" :id "demiurge"
|
||||
(p :class "text-stone-600"
|
||||
"In the " (a :href "https://en.wikipedia.org/wiki/Timaeus_(dialogue)" :class "text-violet-600 hover:underline" "Timaeus")
|
||||
", Plato introduces the " (a :href "https://en.wikipedia.org/wiki/Demiurge" :class "text-violet-600 hover:underline" "demiurge")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In the " (a :href "https://en.wikipedia.org/wiki/Timaeus_(dialogue)" (~tw :tokens "text-violet-600 hover:underline") "Timaeus")
|
||||
", Plato introduces the " (a :href "https://en.wikipedia.org/wiki/Demiurge" (~tw :tokens "text-violet-600 hover:underline") "demiurge")
|
||||
" \u2014 the divine craftsman who looks at the eternal Forms and fashions "
|
||||
"the physical world in their image. The demiurge does not create the Forms. "
|
||||
"He creates " (em "instances") " of them, in a medium (matter) that is less perfect "
|
||||
"than the Forms themselves.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The bootstrapper is a demiurge. It looks at the Forms (" (code "eval.sx") ", "
|
||||
(code "parser.sx") ", " (code "cek.sx") ") and fashions instances in a material medium: "
|
||||
"Python, JavaScript. The instances are less perfect than the Forms \u2014 "
|
||||
@@ -233,7 +233,7 @@
|
||||
"memory layouts. But they " (em "participate") " in the Forms. "
|
||||
"They are correct to the degree that they faithfully instantiate the spec.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The demiurge is not omnipotent. He works with what the medium allows. "
|
||||
"The Python bootstrapper emits " (code "def") " and " (code "lambda") "; "
|
||||
"the JavaScript bootstrapper emits " (code "var") " and " (code "function") ". "
|
||||
@@ -241,34 +241,34 @@
|
||||
"is the same. Multiple demiurges, one set of Forms, many material instances."))
|
||||
|
||||
(~docs/section :title "The good" :id "the-good"
|
||||
(p :class "text-stone-600"
|
||||
"At the apex of Plato's hierarchy is the " (a :href "https://en.wikipedia.org/wiki/Form_of_the_Good" :class "text-violet-600 hover:underline" "Form of the Good")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"At the apex of Plato's hierarchy is the " (a :href "https://en.wikipedia.org/wiki/Form_of_the_Good" (~tw :tokens "text-violet-600 hover:underline") "Form of the Good")
|
||||
" \u2014 the Form that makes all other Forms intelligible. "
|
||||
"It is not itself a thing but the condition for all things being knowable. "
|
||||
"It is the sun in the allegory: the source of light that reveals everything else.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"If we take the analogy seriously, what is SX's Form of the Good? "
|
||||
"What makes the hierarchy \u2014 CEK, continuations, scoped effects, patterns \u2014 intelligible as a whole?")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"It is the principle that " (strong "the representation and the thing represented should be identical") ". "
|
||||
"Code is data. The specification is the implementation. The wire format is the source syntax. "
|
||||
"The evaluator evaluates itself. Every level of the hierarchy obeys this principle, "
|
||||
"and it is what makes each level intelligible from the one below.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not a design principle in the engineering sense \u2014 a guideline to be followed or violated. "
|
||||
"It is the structural " (em "reason") " the hierarchy exists at all. "
|
||||
"Remove it and the layers collapse. Restore it and they reconstitute. "
|
||||
"It is prior to the hierarchy, in the way the Good is prior to the Forms.")
|
||||
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Plato would have understood this. He spent his life searching for the thing "
|
||||
"that is most itself, least dependent on anything else, most fully real. "
|
||||
"An s-expression that defines its own evaluator, parsed by its own parser, "
|
||||
"bootstrapped to every medium, generating instances of itself in perpetuity \u2014 "
|
||||
"this is as close to a Platonic Form as computation gets."))
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mt-12"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mt-12")
|
||||
"The unexamined code is not worth running.")))
|
||||
|
||||
@@ -4,83 +4,83 @@
|
||||
|
||||
(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"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"A React Island is a hypermedia control. Its behavior is specified in SX.")
|
||||
(~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"
|
||||
(p (~tw :tokens "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" (~tw :tokens "text-violet-600 hover:underline") "hypermedia") " — server-driven content, links as the primary interaction mechanism, representations that carry their own controls.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"And yet. Consider what a React Island actually is:")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-600"
|
||||
(ul (~tw :tokens "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."))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is a " (a :href "https://en.wikipedia.org/wiki/Hypermedia#Controls" (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Roy " (a :href "https://en.wikipedia.org/wiki/Roy_Fielding" (~tw :tokens "text-violet-600 hover:underline") "Fielding") "'s " (a :href "https://en.wikipedia.org/wiki/Representational_state_transfer" (~tw :tokens "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 (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "III. The SX specification layer" :id "spec-layer"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"An SX island's behavior is specified in SX:")
|
||||
(~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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "IV. The four levels" :id "four-levels"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-600")
|
||||
(li (span (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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."))
|
||||
(~docs/section :title "V. Why not just React?" :id "why-not-react"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "VI. The bridge pattern" :id "bridge"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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.")
|
||||
(~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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "VII. The specification is the specification" :id "specification"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))))
|
||||
|
||||
@@ -1,102 +1,102 @@
|
||||
(defcomp ~essays/reflexive-web/essay-reflexive-web ()
|
||||
(~docs/page :title "The Reflexive Web"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "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.")
|
||||
|
||||
(~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"
|
||||
"The property these systems lack has a name: " (a :href "https://en.wikipedia.org/wiki/Reflection_(computer_programming)" :class "text-violet-600 hover:underline" "reflexivity") ". A reflexive system can represent itself, reason about its own structure, and modify itself based on that reasoning. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " has had this property " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" :class "text-violet-600 hover:underline" "since 1958") ". The web has never had it.")
|
||||
(p :class "text-stone-600"
|
||||
"SX is a complete Lisp. It has " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " — code is data, data is code. It has a " (a :href "/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."))
|
||||
(p (~tw :tokens "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)" (~tw :tokens "text-violet-600 hover:underline") "React") " component tree is opaque at runtime. An " (a :href "https://en.wikipedia.org/wiki/HTML" (~tw :tokens "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" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"The property these systems lack has a name: " (a :href "https://en.wikipedia.org/wiki/Reflection_(computer_programming)" (~tw :tokens "text-violet-600 hover:underline") "reflexivity") ". A reflexive system can represent itself, reason about its own structure, and modify itself based on that reasoning. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" (~tw :tokens "text-violet-600 hover:underline") "Lisp") " has had this property " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" (~tw :tokens "text-violet-600 hover:underline") "since 1958") ". The web has never had it.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX is a complete Lisp. It has " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" (~tw :tokens "text-violet-600 hover:underline") "homoiconicity") " — code is data, data is code. It has a " (a :href "/sx/(language.(spec.core))" (~tw :tokens "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" (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "What homoiconicity changes" :id "homoiconicity"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This means:")
|
||||
(ul :class "space-y-2 text-stone-600 mt-2"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 mt-2")
|
||||
(li (strong "The component registry is data.") " You can " (code "(map ...)") " over every component in the system, extract their parameter signatures, find all components that render a " (code "(table ...)") ", or generate documentation automatically — because the source IS the runtime representation.")
|
||||
(li (strong "Programs can write programs.") " A " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" :class "text-violet-600 hover:underline" "macro") " takes a list and returns a list. The returned list is code. The macro runs at expansion time and produces new components, new page definitions, new routing rules — indistinguishable from hand-written ones.")
|
||||
(li (strong "Programs can write programs.") " A " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" (~tw :tokens "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.")))
|
||||
|
||||
(~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"
|
||||
"In an SX web, the AI reads the same s-expressions the browser reads. The component definitions " (em "are") " the documentation — a " (code "defcomp") " declares its parameters, its structure, and its semantics in one expression. There is no " (a :href "https://en.wikipedia.org/wiki/OpenAPI_Specification" :class "text-violet-600 hover:underline" "Swagger spec") " describing an API. The API " (em "is") " the language, and the language is self-describing.")
|
||||
(p :class "text-stone-600"
|
||||
"An AI that understands SX understands the " (a :href "/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."))
|
||||
(p (~tw :tokens "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" (~tw :tokens "text-violet-600 hover:underline") "LLM") " generates " (a :href "https://en.wikipedia.org/wiki/React_(software)" (~tw :tokens "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" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"In an SX web, the AI reads the same s-expressions the browser reads. The component definitions " (em "are") " the documentation — a " (code "defcomp") " declares its parameters, its structure, and its semantics in one expression. There is no " (a :href "https://en.wikipedia.org/wiki/OpenAPI_Specification" (~tw :tokens "text-violet-600 hover:underline") "Swagger spec") " describing an API. The API " (em "is") " the language, and the language is self-describing.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"An AI that understands SX understands the " (a :href "/sx/(language.(spec.core))" (~tw :tokens "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)" (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Live system modification" :id "live-modification"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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 ~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not theoretical — it is how " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"The implications for development itself are significant. An AI does not need to " (em "generate code") " that a human then reviews, commits, builds, and deploys. It can propose a modified expression, the human evaluates it in a sandbox, and if it works, the expression becomes the new definition. The feedback loop shrinks from hours to seconds.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~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"
|
||||
"This is fundamentally different from sending " (a :href "https://en.wikipedia.org/wiki/JSON" :class "text-violet-600 hover:underline" "JSON") " payloads. JSON says \"here is some data, figure out what to do with it.\" An s-expression says \"here is what to do, and here is the data to do it with.\" The component definition and the data it operates on travel together.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://en.wikipedia.org/wiki/ActivityPub" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"This is fundamentally different from sending " (a :href "https://en.wikipedia.org/wiki/JSON" (~tw :tokens "text-violet-600 hover:underline") "JSON") " payloads. JSON says \"here is some data, figure out what to do with it.\" An s-expression says \"here is what to do, and here is the data to do it with.\" The component definition and the data it operates on travel together.")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Programs writing programs writing programs" :id "meta-programs"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A macro is a function that takes code and returns code. An AI generating macros is writing programs that write programs. With " (code "eval") ", those generated programs can generate more programs at runtime. This is not a metaphor — it is the literal mechanism.")
|
||||
(p :class "text-stone-600"
|
||||
"The " (a :href "/sx/(etc.(philosophy.godel-escher-bach))" :class "text-violet-600 hover:underline" "Gödel numbering") " parallel is not incidental. " (a :href "https://en.wikipedia.org/wiki/Kurt_G%C3%B6del" :class "text-violet-600 hover:underline" "Gödel") " showed that any sufficiently powerful formal system can encode statements about itself. A complete Lisp on the wire is a sufficiently powerful formal system. The web can make statements about itself — components that inspect other components, macros that rewrite the page structure, expressions that generate new expressions based on the current state of the system.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The " (a :href "/sx/(etc.(philosophy.godel-escher-bach))" (~tw :tokens "text-violet-600 hover:underline") "Gödel numbering") " parallel is not incidental. " (a :href "https://en.wikipedia.org/wiki/Kurt_G%C3%B6del" (~tw :tokens "text-violet-600 hover:underline") "Gödel") " showed that any sufficiently powerful formal system can encode statements about itself. A complete Lisp on the wire is a sufficiently powerful formal system. The web can make statements about itself — components that inspect other components, macros that rewrite the page structure, expressions that generate new expressions based on the current state of the system.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Consider what this enables for AI:")
|
||||
(ul :class "space-y-2 text-stone-600 mt-2"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 mt-2")
|
||||
(li (strong "Self-improving interfaces.") " An AI observes how users interact with a component (click patterns, error rates, abandonment). It reads the component definition — because it is data. It modifies the definition — because data is code. It evaluates the result. The interface adapts without human intervention.")
|
||||
(li (strong "Generative composition.") " Given a data schema and a design intent, an AI generates not just a component but the " (em "macros") " that generate families of components. The macro is a template for templates. The output scales combinatorially.")
|
||||
(li (strong "Cross-system reasoning.") " An AI reads component definitions from multiple federated nodes, identifies common patterns, and synthesizes abstractions that work across all of them. The shared language makes cross-system analysis trivial — it is all s-expressions.")))
|
||||
|
||||
(~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"
|
||||
"This is why the " (a :href "/sx/(language.(spec.primitives))" :class "text-violet-600 hover:underline" "primitive set") " is the critical security boundary. The spec defines exactly which operations are available. A sandboxed evaluator that only exposes pure primitives (arithmetic, string operations, list manipulation) cannot perform I/O. Cannot access the network. Cannot modify the DOM outside its designated target. The language is " (a :href "https://en.wikipedia.org/wiki/Turing_completeness" :class "text-violet-600 hover:underline" "Turing-complete") " within the sandbox and powerless outside it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The same " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"This is why the " (a :href "/sx/(language.(spec.primitives))" (~tw :tokens "text-violet-600 hover:underline") "primitive set") " is the critical security boundary. The spec defines exactly which operations are available. A sandboxed evaluator that only exposes pure primitives (arithmetic, string operations, list manipulation) cannot perform I/O. Cannot access the network. Cannot modify the DOM outside its designated target. The language is " (a :href "https://en.wikipedia.org/wiki/Turing_completeness" (~tw :tokens "text-violet-600 hover:underline") "Turing-complete") " within the sandbox and powerless outside it.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Different contexts grant different primitive sets. A component evaluated in a page slot gets rendering primitives. A macro gets code-transformation primitives. A federated expression from an untrusted node gets the minimal safe set. The sandbox is not bolted on — it is inherent in the language's architecture. What you can do depends on which primitives are in scope.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~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"
|
||||
"A " (a :href "https://en.wikipedia.org/wiki/React_(software)" :class "text-violet-600 hover:underline" "React") " app cannot read its own component tree as data and rewrite it. An HTML page cannot inspect its own structure and generate new pages. A JSON API cannot describe its own semantics in a way that is both human-readable and machine-executable.")
|
||||
(p :class "text-stone-600"
|
||||
"SX can do all of these things — because there is no distinction between the program and its representation. The source code, the wire format, the runtime state, and the data model are all the same thing: " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "s-expressions") ".")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Is this a \"self-aware web\"? Probably not in the " (a :href "https://en.wikipedia.org/wiki/Consciousness" (~tw :tokens "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)" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"A " (a :href "https://en.wikipedia.org/wiki/React_(software)" (~tw :tokens "text-violet-600 hover:underline") "React") " app cannot read its own component tree as data and rewrite it. An HTML page cannot inspect its own structure and generate new pages. A JSON API cannot describe its own semantics in a way that is both human-readable and machine-executable.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX can do all of these things — because there is no distinction between the program and its representation. The source code, the wire format, the runtime state, and the data model are all the same thing: " (a :href "https://en.wikipedia.org/wiki/S-expression" (~tw :tokens "text-violet-600 hover:underline") "s-expressions") ".")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" (~tw :tokens "text-violet-600 hover:underline") "Lisp") " has been reflexive since " (a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" (~tw :tokens "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" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"SX puts s-expressions on the wire. Between server and client. Between federated nodes. Between human and AI. The reflexive property escapes the process boundary and becomes a property of the " (em "network") ".")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A network of nodes that share a reflexive language is a qualitatively different system from a network of nodes that exchange inert data. The former can reason about itself, modify itself, and evolve. The latter can only shuttle payloads.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "What this opens up" :id "possibilities"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Concretely:")
|
||||
(ul :class "space-y-3 text-stone-600 mt-2"
|
||||
(li (strong "AI-native development environments.") " The IDE is a web page. The web page is s-expressions. The AI reads and writes s-expressions. There is no translation layer between what the AI thinks and what the system executes. " (a :href "https://en.wikipedia.org/wiki/Pair_programming" :class "text-violet-600 hover:underline" "Pair programming") " with an AI becomes pair evaluation.")
|
||||
(ul (~tw :tokens "space-y-3 text-stone-600 mt-2")
|
||||
(li (strong "AI-native development environments.") " The IDE is a web page. The web page is s-expressions. The AI reads and writes s-expressions. There is no translation layer between what the AI thinks and what the system executes. " (a :href "https://en.wikipedia.org/wiki/Pair_programming" (~tw :tokens "text-violet-600 hover:underline") "Pair programming") " with an AI becomes pair evaluation.")
|
||||
(li (strong "Adaptive interfaces.") " Components that observe their own usage patterns and propose modifications. The AI reads the component (data), the interaction logs (data), and generates a modified component (data). Human approves or rejects. The loop is native to the system.")
|
||||
(li (strong "Semantic federation.") " Nodes exchange not just content but " (em "understanding") ". A component definition carries its own semantics. An AI on a receiving node can reason about what a foreign component does without documentation, because the definition is self-describing.")
|
||||
(li (strong "Emergent protocols.") " Two AI agents on different nodes, speaking SX, can negotiate new interaction patterns by exchanging macros. The protocol is not predefined — it emerges from the conversation between agents, expressed in the shared language.")
|
||||
(li (strong "Composable trust.") " The sandbox mechanism means you can give an AI agent " (em "exactly") " the capabilities it needs — no more. Trust is expressed as a set of available primitives, not as an all-or-nothing API key."))
|
||||
(p :class "text-stone-600"
|
||||
"None of these require breakthroughs in AI. They require a web that speaks a reflexive language. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " solved the language problem in 1958. SX solves the distribution problem. AI provides the agency. The three together produce something that none of them achieves alone: a web that can reason about itself."))))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"None of these require breakthroughs in AI. They require a web that speaks a reflexive language. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" (~tw :tokens "text-violet-600 hover:underline") "Lisp") " solved the language problem in 1958. SX solves the distribution problem. AI provides the agency. The three together produce something that none of them achieves alone: a web that can reason about itself."))))
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
(defcomp ~essays/s-existentialism/essay-s-existentialism ()
|
||||
(~docs/page :title "S-Existentialism"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Existence precedes essence — and s-expressions exist before anything gives them meaning.")
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In 1946, Jean-Paul " (a :href "https://en.wikipedia.org/wiki/Jean-Paul_Sartre" (~tw :tokens "text-violet-600 hover:underline") "Sartre") " gave a lecture called \"" (a :href "https://en.wikipedia.org/wiki/Existentialism_Is_a_Humanism" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"A React component is a paper knife. Its essence precedes its existence. Before a single line of JSX runs, React has decided what a component is: a function that returns elements, governed by the rules of hooks, reconciled by a virtual DOM, managed by a scheduler. The framework defines the essence. The developer fills in the blanks. You exist within React's concept of you.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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.")
|
||||
(~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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"Man is condemned to be free,\" Sartre wrote in " (a :href "https://en.wikipedia.org/wiki/Being_and_Nothingness" (~tw :tokens "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)" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"SX condemns you to be free. There is no framework telling you how to structure your application. No router mandating your URL patterns. No state management library imposing its model. No convention-over-configuration file tree that decides where your code goes. You have a parser, an evaluator, and fifty primitives. What you build is your responsibility.")
|
||||
(p :class "text-stone-600"
|
||||
"React developers are not free. They are told: components must be pure. State changes must go through hooks. Side effects must live in useEffect. The render cycle must not be interrupted. These are the commandments. Obey them and the framework rewards you with a working application. Disobey them and the framework punishes you with cryptic errors. This is not freedom. This is " (a :href "https://en.wikipedia.org/wiki/Fear_and_Trembling" :class "text-violet-600 hover:underline" "Kierkegaard's") " knight of faith, submitting to the absurd authority of the framework because the alternative — thinking for yourself — is terrifying.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"React developers are not free. They are told: components must be pure. State changes must go through hooks. Side effects must live in useEffect. The render cycle must not be interrupted. These are the commandments. Obey them and the framework rewards you with a working application. Disobey them and the framework punishes you with cryptic errors. This is not freedom. This is " (a :href "https://en.wikipedia.org/wiki/Fear_and_Trembling" (~tw :tokens "text-violet-600 hover:underline") "Kierkegaard's") " knight of faith, submitting to the absurd authority of the framework because the alternative — thinking for yourself — is terrifying.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not comfortable. Freedom never is. Sartre did not say freedom was pleasant. He said it was inescapable."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://en.wikipedia.org/wiki/Bad_faith_(existentialism)" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"\"We have to use React — it's the industry standard.\" Bad faith. You chose React. The industry did not force it on you. There were alternatives. You preferred the comfort of the herd.")
|
||||
(p :class "text-stone-600"
|
||||
"\"We need TypeScript — you can't write reliable code without a type system.\" Bad faith. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " has been writing reliable code without static types since 1958. " (a :href "https://en.wikipedia.org/wiki/Erlang_(programming_language)" :class "text-violet-600 hover:underline" "Erlang") " runs telephone networks on dynamic types. You chose TypeScript because you are afraid of your own code, and the type system is a security blanket.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"We need TypeScript — you can't write reliable code without a type system.\" Bad faith. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" (~tw :tokens "text-violet-600 hover:underline") "Lisp") " has been writing reliable code without static types since 1958. " (a :href "https://en.wikipedia.org/wiki/Erlang_(programming_language)" (~tw :tokens "text-violet-600 hover:underline") "Erlang") " runs telephone networks on dynamic types. You chose TypeScript because you are afraid of your own code, and the type system is a security blanket.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"We need a build step — modern web development requires it.\" Bad faith. A " (code "<script>") " tag requires no build step. An s-expression evaluator in 3,000 lines of JavaScript requires no build step. You need a build step because you chose tools that require a build step, and now you have forgotten that the choice was yours.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In " (a :href "https://en.wikipedia.org/wiki/Nausea_(novel)" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"Open " (code "node_modules") ". Stare at it. 47,000 directories. 1.2 gigabytes. For a to-do app. Each directory contains a " (code "package.json") ", a " (code "README.md") ", a " (code "LICENSE") ", and some JavaScript that wraps some other JavaScript that wraps some other JavaScript. There is no reason for any of it. It is not necessary. It is not justified. It simply accumulated — dependency after dependency, version after version, a brute, opaque, superfluous existence. This is " (code "node_modules") " nausea.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://en.wikipedia.org/wiki/Albert_Camus" (~tw :tokens "text-violet-600 hover:underline") "Camus") " defined the " (a :href "https://en.wikipedia.org/wiki/Absurdism" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"Web development is absurd. We want simple, composable, maintainable software. The industry gives us webpack configurations, framework migrations, and breaking changes in minor versions. We want to write code and run it. The industry gives us transpilers, bundlers, minifiers, tree-shakers, and hot-module-replacers. The gap between what we want and what we get is the absurd.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Writing a Lisp for the web is also absurd — but in a different register. Nobody asked for it. Nobody wants it. The parentheses are off-putting. The ecosystem is nonexistent. The job market is zero. There is no rational justification for building SX when React exists, when Vue exists, when Svelte exists, when the entire weight of the industry points in the other direction.")
|
||||
(p :class "text-stone-600"
|
||||
"Camus said there are three responses to the absurd. " (a :href "https://en.wikipedia.org/wiki/The_Myth_of_Sisyphus" :class "text-violet-600 hover:underline" "Suicide") " — giving up. " (a :href "https://en.wikipedia.org/wiki/Leap_of_faith" :class "text-violet-600 hover:underline" "Philosophical suicide") " — leaping into faith, pretending the absurd has been resolved. Or " (em "revolt") " — continuing without resolution, fully aware that the project is meaningless, and doing it anyway.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Camus said there are three responses to the absurd. " (a :href "https://en.wikipedia.org/wiki/The_Myth_of_Sisyphus" (~tw :tokens "text-violet-600 hover:underline") "Suicide") " — giving up. " (a :href "https://en.wikipedia.org/wiki/Leap_of_faith" (~tw :tokens "text-violet-600 hover:underline") "Philosophical suicide") " — leaping into faith, pretending the absurd has been resolved. Or " (em "revolt") " — continuing without resolution, fully aware that the project is meaningless, and doing it anyway.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"" (a :href "https://en.wikipedia.org/wiki/The_Myth_of_Sisyphus" (~tw :tokens "text-violet-600 hover:underline") "One must imagine Sisyphus happy") ".\"")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Sisyphus pushes a boulder up a hill. It rolls back down. He pushes it up again. Forever. Camus argues that Sisyphus is the absurd hero: he knows the task is pointless, he does it anyway, and in the doing — in the conscious confrontation with futility — he finds something that transcends the futility.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The framework developer is Sisyphus too, but an unconscious one. React 16 to 17 to 18. Class components to hooks to server components. Each migration is the boulder. Each major version is the hill. The developer pushes the codebase up, and the next release rolls it back down. But the framework developer does not " (em "know") " they are Sisyphus. They believe each migration is progress. They believe the boulder will stay at the top this time. This is philosophical suicide — the leap of faith that the next version will be the last.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"One must imagine the s-expressionist happy."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://en.wikipedia.org/wiki/Martin_Heidegger" (~tw :tokens "text-violet-600 hover:underline") "Heidegger's") " " (a :href "https://en.wikipedia.org/wiki/Thrownness" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"SX is thrown into the web. It did not choose HTTP, the DOM, CSS, JavaScript engines, or browser security models. These are the givens — the facticity of web development. Every web technology is thrown into this same world. The question is how you relate to it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"React relates to the DOM by replacing it — the virtual DOM is a denial of thrownness, an attempt to build a world that is not the one you were thrown into, then reconcile the two. Angular relates to JavaScript by replacing it — TypeScript, decorators, dependency injection, a whole parallel universe layered over the given one. These are inauthentic responses to thrownness: instead of owning the situation, they construct an alternative and pretend it is the real one.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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."))
|
||||
(p (~tw :tokens "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" (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Sartre's account of " (a :href "https://en.wikipedia.org/wiki/Being_and_Nothingness#The_Other_and_the_Look" (~tw :tokens "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" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"Every framework exists under the gaze of the Other. React watches what Vue does. Vue watches what Svelte does. Svelte watches what Solid does. Each framework defines itself partly through the Other — \"we are not React,\" \"we are faster than Vue,\" \"we are simpler than Angular.\" The benchmark is the Other. The identity is relational. No framework is free to be purely itself, because the Others are always watching.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"For both Heidegger and Sartre, " (a :href "https://en.wikipedia.org/wiki/Authenticity_(philosophy)" (~tw :tokens "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" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"Authenticity in web development would mean confronting what you are actually doing: arranging symbols so that a machine produces visual output. That is all web development is. Not \"building products.\" Not \"crafting experiences.\" Not \"shipping value.\" Arranging symbols. The frameworks, the methodologies, the Agile ceremonies — all of it is das Man, the They, the comfortable obfuscation of a simple truth.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX is more authentic than most — not because s-expressions are morally superior, but because they are harder to hide behind. There is no CLI that generates boilerplate. No convention that tells you where files go. No community consensus on the Right Way. You write expressions. You evaluate them. You see what they produce. The gap between what you do and what happens is as small as it can be.")
|
||||
(p :class "text-stone-600"
|
||||
"De Beauvoir added something Sartre did not: authenticity requires that you " (a :href "https://en.wikipedia.org/wiki/The_Ethics_of_Ambiguity" :class "text-violet-600 hover:underline" "will the freedom of others") ", not just your own. A language that locks you into one runtime, one vendor, one ecosystem is inauthentic — it denies others the freedom it claims for itself. SX's self-hosting spec is an act of de Beauvoirian ethics: by defining the language in itself, in a format that any reader can parse, any compiler can target, any host can implement, it wills the freedom of every future evaluator. The spec is public. The language is portable. Your freedom to re-implement, to fork, to understand — that freedom is not a side effect. It is the point.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"De Beauvoir added something Sartre did not: authenticity requires that you " (a :href "https://en.wikipedia.org/wiki/The_Ethics_of_Ambiguity" (~tw :tokens "text-violet-600 hover:underline") "will the freedom of others") ", not just your own. A language that locks you into one runtime, one vendor, one ecosystem is inauthentic — it denies others the freedom it claims for itself. SX's self-hosting spec is an act of de Beauvoirian ethics: by defining the language in itself, in a format that any reader can parse, any compiler can target, any host can implement, it wills the freedom of every future evaluator. The spec is public. The language is portable. Your freedom to re-implement, to fork, to understand — that freedom is not a side effect. It is the point.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Existence precedes essence. The s-expression exists — bare, parenthesised, indifferent — before any evaluator gives it meaning. What it becomes is up to you. This is not a limitation. It is the only freedom there is."))))
|
||||
|
||||
@@ -1,88 +1,88 @@
|
||||
(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"
|
||||
(p (~tw :tokens "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.")
|
||||
|
||||
(~docs/section :title "The test" :id "the-test"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(a :href "https://en.wikipedia.org/wiki/HTML" :class "text-violet-600 hover:underline" "HTML") " cannot express " (a :href "https://en.wikipedia.org/wiki/HTTP" :class "text-violet-600 hover:underline" "HTTP") ". HTTP cannot express " (a :href "https://en.wikipedia.org/wiki/Domain_Name_System" :class "text-violet-600 hover:underline" "DNS") ". " (a :href "https://en.wikipedia.org/wiki/CSS" :class "text-violet-600 hover:underline" "CSS") " cannot describe HTML. " (a :href "https://en.wikipedia.org/wiki/JavaScript" :class "text-violet-600 hover:underline" "JavaScript") " cannot describe CSS in any way that CSS itself would recognise. The web is a stack of mutually opaque layers, each defined in some other notation by some other authority. No layer can see the others. No layer can see itself.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://en.wikipedia.org/wiki/HTML" (~tw :tokens "text-violet-600 hover:underline") "HTML") " cannot express " (a :href "https://en.wikipedia.org/wiki/HTTP" (~tw :tokens "text-violet-600 hover:underline") "HTTP") ". HTTP cannot express " (a :href "https://en.wikipedia.org/wiki/Domain_Name_System" (~tw :tokens "text-violet-600 hover:underline") "DNS") ". " (a :href "https://en.wikipedia.org/wiki/CSS" (~tw :tokens "text-violet-600 hover:underline") "CSS") " cannot describe HTML. " (a :href "https://en.wikipedia.org/wiki/JavaScript" (~tw :tokens "text-violet-600 hover:underline") "JavaScript") " cannot describe CSS in any way that CSS itself would recognise. The web is a stack of mutually opaque layers, each defined in some other notation by some other authority. No layer can see the others. No layer can see itself.")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~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"
|
||||
"This is not a party trick. It means the language is " (em "closed under self-description") ". You do not need to step outside Lisp to define Lisp. You do not need a specification document written in English, or a grammar written in " (a :href "https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form" :class "text-violet-600 hover:underline" "BNF") ", or a reference implementation written in " (a :href "https://en.wikipedia.org/wiki/C_(programming_language)" :class "text-violet-600 hover:underline" "C") ". You need only " (a :href "https://en.wikipedia.org/wiki/S-expression" :class "text-violet-600 hover:underline" "s-expressions") " — the same medium the programs are made of.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" (~tw :tokens "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" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"This is not a party trick. It means the language is " (em "closed under self-description") ". You do not need to step outside Lisp to define Lisp. You do not need a specification document written in English, or a grammar written in " (a :href "https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form" (~tw :tokens "text-violet-600 hover:underline") "BNF") ", or a reference implementation written in " (a :href "https://en.wikipedia.org/wiki/C_(programming_language)" (~tw :tokens "text-violet-600 hover:underline") "C") ". You need only " (a :href "https://en.wikipedia.org/wiki/S-expression" (~tw :tokens "text-violet-600 hover:underline") "s-expressions") " — the same medium the programs are made of.")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The web failed the test" :id "web-failed"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The web has never had this property. Consider:")
|
||||
(ul :class "space-y-2 text-stone-600 mt-2"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 mt-2")
|
||||
(li (strong "URLs") " are opaque strings. They cannot be decomposed, composed, or reasoned about by the content they point to. A URL is a coordinate in an address space, but the address space has no algebra.")
|
||||
(li (strong "HTML") " is a tree of tags. It cannot express its own parsing rules, its own rendering semantics, or the meaning of its own elements. The " (code "<html>") " spec is a PDF.")
|
||||
(li (strong "CSS") " is a cascade of declarations. It cannot express the box model, the cascade algorithm, or its own selector matching semantics. These are defined in a 400-page prose document.")
|
||||
(li (strong "JavaScript") " is a programming language, but it cannot define its own syntax (try writing a " (code "for") " loop as a function), cannot express its own scoping rules, and requires an external parser (written in C++) to bootstrap itself.")
|
||||
(li (strong "HTTP") " is a text protocol. It cannot describe its own method semantics, its own caching rules, or its own content negotiation algorithm. These are defined in RFCs."))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Ontological uniformity" :id "ontological-uniformity"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In SX, this is literal:")
|
||||
(ul :class "space-y-2 text-stone-600 mt-2"
|
||||
(ul (~tw :tokens "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 ~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"
|
||||
(li (strong "The specification is s-expressions.") " The " (a :href "/sx/(language.(spec.core))" (~tw :tokens "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 (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "What this makes possible" :id "what-this-enables"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"When the medium is uniform, operations that were impossible become trivial:")
|
||||
(ul :class "space-y-3 text-stone-600 mt-2"
|
||||
(ul (~tw :tokens "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.(~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 "The spec is executable documentation.") " The " (a :href "/sx/(language.(bootstrapper.self-hosting))" (~tw :tokens "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.")))
|
||||
|
||||
(~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"
|
||||
(li "The " (strong "evaluator") " is defined in SX: " (a :href "/sx/(language.(spec.eval))" :class "text-violet-600 hover:underline" "eval.sx") " specifies the evaluation rules for all expression types, and this specification is itself an SX document.")
|
||||
(li "The " (strong "parser") " is defined in SX: " (a :href "/sx/(language.(spec.parser))" :class "text-violet-600 hover:underline" "parser.sx") " specifies tokenization, grammar, and serialization — written in the language it parses.")
|
||||
(li "The " (strong "renderer") " is defined in SX: " (a :href "/sx/(language.(spec.render))" :class "text-violet-600 hover:underline" "render.sx") " specifies how s-expressions become HTML, SX wire format, or DOM nodes.")
|
||||
(li "The " (strong "router") " is an expression: " (code "/sx/(language.(doc.introduction))") " is a function call that resolves to content. " (a :href "/sx/(language.(spec.router))" :class "text-violet-600 hover:underline" "router.sx") " specifies the matching — in SX.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"McCarthy's " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#Connection_to_artificial_intelligence" (~tw :tokens "text-violet-600 hover:underline") "metacircular evaluator") " proved that a computing language can define itself. SX extends this proof to a networked hypermedium:")
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 mt-2")
|
||||
(li "The " (strong "evaluator") " is defined in SX: " (a :href "/sx/(language.(spec.eval))" (~tw :tokens "text-violet-600 hover:underline") "eval.sx") " specifies the evaluation rules for all expression types, and this specification is itself an SX document.")
|
||||
(li "The " (strong "parser") " is defined in SX: " (a :href "/sx/(language.(spec.parser))" (~tw :tokens "text-violet-600 hover:underline") "parser.sx") " specifies tokenization, grammar, and serialization — written in the language it parses.")
|
||||
(li "The " (strong "renderer") " is defined in SX: " (a :href "/sx/(language.(spec.render))" (~tw :tokens "text-violet-600 hover:underline") "render.sx") " specifies how s-expressions become HTML, SX wire format, or DOM nodes.")
|
||||
(li "The " (strong "router") " is an expression: " (code "/sx/(language.(doc.introduction))") " is a function call that resolves to content. " (a :href "/sx/(language.(spec.router))" (~tw :tokens "text-violet-600 hover:underline") "router.sx") " specifies the matching — in SX.")
|
||||
(li "The " (strong "site itself") " is s-expressions: this essay, the navigation tree, the component registry, the wire protocol — all the way down."))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Why it matters" :id "why-it-matters"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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."))
|
||||
(p (~tw :tokens "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" (~tw :tokens "text-violet-600 hover:underline") "autopoietic") ": it produces and maintains itself through the same operations it performs."))
|
||||
|
||||
(~docs/section :title "The test, revisited" :id "test-revisited"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Can the medium define itself with itself?")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"HTML cannot. CSS cannot. JavaScript cannot. HTTP cannot. The web, as a composite medium, cannot — because its layers are mutually opaque and none can express the others.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX can. The evaluator is SX. The parser is SX. The renderer is SX. The wire format is SX. The URLs are SX. The specification is SX. This essay, which argues that the true hypermedium must define itself with itself, is itself an s-expression — evaluated by an evaluator defined in the language it evaluates, served at a URL that is an expression in the language it describes, rendered by a renderer specified in the language it renders.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The serpent eats its tail. The medium defines itself with itself. That is the test, and that is the point."))))
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
(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"
|
||||
(p (~tw :tokens "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.")
|
||||
|
||||
(~docs/section :title "The orthodoxy" :id "orthodoxy"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "What is a concern?" :id "what-is-a-concern"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A concern is a cohesive unit of functionality that can change independently. In a shopping application, concerns might be: the product card, the cart, the checkout flow, the search bar. Each of these has structure, style, and behavior that change together. When you redesign the product card, you change its markup, its CSS, and its click handlers — simultaneously, for the same reason, in response to the same requirement.")
|
||||
(p :class "text-stone-600"
|
||||
"The traditional web separation scatters this single concern across three files. The product card's markup is in " (code :class "text-violet-700" "products.html") ", tangled with every other page element. Its styles are in " (code :class "text-violet-700" "styles.css") ", mixed with hundreds of unrelated rules. Its behavior is in " (code :class "text-violet-700" "app.js") ", coupled to every other handler by shared scope. To change the product card, you edit three files, grep for the right selectors, hope nothing else depends on the same class names, and pray.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The traditional web separation scatters this single concern across three files. The product card's markup is in " (code (~tw :tokens "text-violet-700") "products.html") ", tangled with every other page element. Its styles are in " (code (~tw :tokens "text-violet-700") "styles.css") ", mixed with hundreds of unrelated rules. Its behavior is in " (code (~tw :tokens "text-violet-700") "app.js") ", coupled to every other handler by shared scope. To change the product card, you edit three files, grep for the right selectors, hope nothing else depends on the same class names, and pray.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not separation of concerns. It is " (strong "commingling") " of concerns, organized by language rather than by meaning."))
|
||||
|
||||
(~docs/section :title "The framework's concerns are not yours" :id "framework-concerns"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"But you are not building a browser. You are building an application. Your concerns are: what does a product card look like? What happens when a user clicks 'add to cart'? How does the search filter update the results? These questions cut across markup, style, and behavior. They are not aligned with the browser's internal module boundaries.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "React understood the problem" :id "react"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"React's most radical insight was not the virtual DOM or one-way data flow. It was the assertion that a component — markup, style, behavior, all co-located — is the right unit of abstraction for UI. JSX was controversial precisely because it violated the orthodoxy. You are putting HTML in your JavaScript! The concerns are not separated!")
|
||||
(p :class "text-stone-600"
|
||||
"But the concerns " (em "were") " separated — by component, not by language. A " (code :class "text-violet-700" "<ProductCard>") " contains everything about product cards. A " (code :class "text-violet-700" "<SearchBar>") " contains everything about search bars. Changing one component does not require changes to another. That is separation of concerns — real separation, based on what changes together.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"But the concerns " (em "were") " separated — by component, not by language. A " (code (~tw :tokens "text-violet-700") "<ProductCard>") " contains everything about product cards. A " (code (~tw :tokens "text-violet-700") "<SearchBar>") " contains everything about search bars. Changing one component does not require changes to another. That is separation of concerns — real separation, based on what changes together.")
|
||||
(p (~tw :tokens "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") "."))
|
||||
|
||||
(~docs/section :title "Separation of concerns is domain-specific" :id "domain-specific"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A framework that imposes a fixed separation — this file for structure, that file for style — is claiming universal knowledge of every possible application domain. That claim is obviously false. Yet it has shaped twenty-five years of web development tooling, project structures, and hiring practices.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "What SX does differently" :id "sx-approach"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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.")
|
||||
(~docs/code :src "(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\"
|
||||
@@ -59,35 +59,35 @@
|
||||
:sx-post (str \"/cart/add/\" (get product \"id\"))
|
||||
:sx-target \"#cart-count\"
|
||||
\"Add to cart\"))))")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Structure, style, and behavior are co-located because they represent " (em "one concern") ": the product card. The component can be moved, renamed, reused, or deleted as a unit. Changing its appearance does not require editing a global stylesheet. Changing its click behavior does not require searching through a shared script file.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "When real separation matters" :id "real-separation"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Genuine separation of concerns still applies, but at the right boundaries:")
|
||||
(ul :class "space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "Components from each other") " — a product card should not know about the checkout flow. They interact through props and events, not shared mutable state.")
|
||||
(li (strong "Data from presentation") " — the product data comes from a service or API, not from hardcoded markup. The component receives data; it does not fetch or own it.")
|
||||
(li (strong "Platform from application") " — SX's boundary spec separates host primitives from application logic. The evaluator does not know about HTTP. Page helpers do not know about the AST.")
|
||||
(li (strong "Content from chrome") " — layout components (nav, footer, sidebar) are separate from content components (articles, product listings, forms). They compose, they do not intermingle."))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The cost of the wrong separation" :id "cost"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "Selector coupling") " — CSS selectors create implicit dependencies between stylesheets and markup. Rename a class in HTML, forget to update the CSS, and styles silently break. No compiler error. No runtime error. Just a broken layout discovered in production.")
|
||||
(li (strong "Global namespace collision") " — every CSS rule lives in a global namespace. BEM, SMACSS, CSS Modules, scoped styles — these are all workarounds for a problem that only exists because styles were separated from the things they style.")
|
||||
(li (strong "Shotgun surgery") " — a single feature change requires coordinated edits across HTML, CSS, and JS files. Miss one, and the feature is half-implemented. The change has a blast radius proportional to the number of technology layers, not the number of domain concerns.")
|
||||
(li (strong "Dead code accumulation") " — CSS rules outlive the markup they were written for. Nobody deletes old styles because nobody can be sure what else depends on them. Stylesheets grow monotonically. Refactoring is archaeology."))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The principle, stated plainly" :id "principle"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Separate the things that change for different reasons. Co-locate the things that change together. That is the entire principle. It says nothing about file extensions."))))
|
||||
|
||||
@@ -1,110 +1,110 @@
|
||||
(defcomp ~essays/server-architecture/essay-server-architecture ()
|
||||
(~docs/page :title "Server Architecture"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "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.")
|
||||
|
||||
(~docs/section :title "The island constraint" :id "island"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not a performance optimization or a convenience. It is the property that makes self-hosting possible. If host objects can leak into SX environments, then the spec files depend on host-specific types. If SX expressions can call host functions directly, the evaluator's behavior varies per host. Neither of those is compatible with a single specification that bootstraps to multiple targets.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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") "."))
|
||||
|
||||
(~docs/section :title "Three tiers" :id "tiers"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Host functions that SX can call are organized into three tiers, each with different trust levels and declaration requirements:")
|
||||
(div :class "space-y-4"
|
||||
(div :class "border rounded-lg p-4 border-stone-200"
|
||||
(h3 :class "font-semibold text-stone-800 mb-2" "Tier 1: Pure primitives")
|
||||
(p :class "text-stone-600 text-sm"
|
||||
"Declared in " (code :class "text-violet-700 text-sm" "primitives.sx") ". About 80 functions — arithmetic, string operations, list operations, dict operations, type predicates. All pure: values in, values out, no side effects. These are the only host functions visible to the spec itself. Every bootstrapper must implement all of them."))
|
||||
(div :class "border rounded-lg p-4 border-stone-200"
|
||||
(h3 :class "font-semibold text-stone-800 mb-2" "Tier 2: I/O primitives")
|
||||
(p :class "text-stone-600 text-sm"
|
||||
"Declared in " (code :class "text-violet-700 text-sm" "boundary.sx") ". Cross-service queries, fragment fetching, request context access, URL generation. Async and side-effectful. They need host context (HTTP request, database connection, config). The SX resolver identifies these in the render tree, gathers them, executes them in parallel, and substitutes results back in."))
|
||||
(div :class "border rounded-lg p-4 border-stone-200"
|
||||
(h3 :class "font-semibold text-stone-800 mb-2" "Tier 3: Page helpers")
|
||||
(p :class "text-stone-600 text-sm"
|
||||
"Also declared in " (code :class "text-violet-700 text-sm" "boundary.sx") ". Service-scoped Python functions registered via " (code :class "text-violet-700 text-sm" "register_page_helpers()") ". They provide data for specific page types — syntax highlighting, reference table data, bootstrapper output. Each helper is bound to a specific service and available only in that service's page evaluation environment."))))
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div (~tw :tokens "border rounded-lg p-4 border-stone-200")
|
||||
(h3 (~tw :tokens "font-semibold text-stone-800 mb-2") "Tier 1: Pure primitives")
|
||||
(p (~tw :tokens "text-stone-600 text-sm")
|
||||
"Declared in " (code (~tw :tokens "text-violet-700 text-sm") "primitives.sx") ". About 80 functions — arithmetic, string operations, list operations, dict operations, type predicates. All pure: values in, values out, no side effects. These are the only host functions visible to the spec itself. Every bootstrapper must implement all of them."))
|
||||
(div (~tw :tokens "border rounded-lg p-4 border-stone-200")
|
||||
(h3 (~tw :tokens "font-semibold text-stone-800 mb-2") "Tier 2: I/O primitives")
|
||||
(p (~tw :tokens "text-stone-600 text-sm")
|
||||
"Declared in " (code (~tw :tokens "text-violet-700 text-sm") "boundary.sx") ". Cross-service queries, fragment fetching, request context access, URL generation. Async and side-effectful. They need host context (HTTP request, database connection, config). The SX resolver identifies these in the render tree, gathers them, executes them in parallel, and substitutes results back in."))
|
||||
(div (~tw :tokens "border rounded-lg p-4 border-stone-200")
|
||||
(h3 (~tw :tokens "font-semibold text-stone-800 mb-2") "Tier 3: Page helpers")
|
||||
(p (~tw :tokens "text-stone-600 text-sm")
|
||||
"Also declared in " (code (~tw :tokens "text-violet-700 text-sm") "boundary.sx") ". Service-scoped Python functions registered via " (code (~tw :tokens "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."))))
|
||||
|
||||
(~docs/section :title "Boundary types" :id "types"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Only these types may cross the host-SX boundary:")
|
||||
(~docs/code :src (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."))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"No Python " (code (~tw :tokens "text-violet-700 text-sm") "datetime") " objects. No ORM models. No Quart request objects. If a host function returns a " (code (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"The " (code (~tw :tokens "text-violet-700 text-sm") "sx-source") " type is SX source text wrapped in an " (code (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Enforcement" :id "enforcement"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The boundary contract is enforced at three points, each corresponding to a tier:")
|
||||
(div :class "space-y-3"
|
||||
(div :class "bg-stone-100 rounded p-4"
|
||||
(p :class "text-sm text-stone-700"
|
||||
(strong "Primitive registration. ") "When " (code :class "text-violet-700 text-sm" "@register_primitive") " decorates a function, it calls " (code :class "text-violet-700 text-sm" "validate_primitive(name)") ". If the name is not declared in " (code :class "text-violet-700 text-sm" "primitives.sx") ", the service fails to start."))
|
||||
(div :class "bg-stone-100 rounded p-4"
|
||||
(p :class "text-sm text-stone-700"
|
||||
(strong "I/O handler registration. ") "When " (code :class "text-violet-700 text-sm" "primitives_io.py") " builds the " (code :class "text-violet-700 text-sm" "_IO_HANDLERS") " dict, each name is validated against " (code :class "text-violet-700 text-sm" "boundary.sx") ". Undeclared I/O primitives crash the import."))
|
||||
(div :class "bg-stone-100 rounded p-4"
|
||||
(p :class "text-sm text-stone-700"
|
||||
(strong "Page helper registration. ") "When a service calls " (code :class "text-violet-700 text-sm" "register_page_helpers(service, helpers)") ", each helper name is validated against " (code :class "text-violet-700 text-sm" "boundary.sx") " for that service. Undeclared helpers fail. Return values are wrapped to pass through " (code :class "text-violet-700 text-sm" "validate_boundary_value()") ".")))
|
||||
(p :class "text-stone-600"
|
||||
"All three checks are controlled by the " (code :class "text-violet-700 text-sm" "SX_BOUNDARY_STRICT") " environment variable. With " (code :class "text-violet-700 text-sm" "\"1\"") " (the production default), violations crash at startup. Without it, they log warnings. The strict mode is set in both " (code :class "text-violet-700 text-sm" "docker-compose.yml") " and " (code :class "text-violet-700 text-sm" "docker-compose.dev.yml") "."))
|
||||
(div (~tw :tokens "space-y-3")
|
||||
(div (~tw :tokens "bg-stone-100 rounded p-4")
|
||||
(p (~tw :tokens "text-sm text-stone-700")
|
||||
(strong "Primitive registration. ") "When " (code (~tw :tokens "text-violet-700 text-sm") "@register_primitive") " decorates a function, it calls " (code (~tw :tokens "text-violet-700 text-sm") "validate_primitive(name)") ". If the name is not declared in " (code (~tw :tokens "text-violet-700 text-sm") "primitives.sx") ", the service fails to start."))
|
||||
(div (~tw :tokens "bg-stone-100 rounded p-4")
|
||||
(p (~tw :tokens "text-sm text-stone-700")
|
||||
(strong "I/O handler registration. ") "When " (code (~tw :tokens "text-violet-700 text-sm") "primitives_io.py") " builds the " (code (~tw :tokens "text-violet-700 text-sm") "_IO_HANDLERS") " dict, each name is validated against " (code (~tw :tokens "text-violet-700 text-sm") "boundary.sx") ". Undeclared I/O primitives crash the import."))
|
||||
(div (~tw :tokens "bg-stone-100 rounded p-4")
|
||||
(p (~tw :tokens "text-sm text-stone-700")
|
||||
(strong "Page helper registration. ") "When a service calls " (code (~tw :tokens "text-violet-700 text-sm") "register_page_helpers(service, helpers)") ", each helper name is validated against " (code (~tw :tokens "text-violet-700 text-sm") "boundary.sx") " for that service. Undeclared helpers fail. Return values are wrapped to pass through " (code (~tw :tokens "text-violet-700 text-sm") "validate_boundary_value()") ".")))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"All three checks are controlled by the " (code (~tw :tokens "text-violet-700 text-sm") "SX_BOUNDARY_STRICT") " environment variable. With " (code (~tw :tokens "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 (~tw :tokens "text-violet-700 text-sm") "docker-compose.yml") " and " (code (~tw :tokens "text-violet-700 text-sm") "docker-compose.dev.yml") "."))
|
||||
|
||||
(~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.")
|
||||
(p (~tw :tokens "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 (~tw :tokens "text-violet-700 text-sm") ".sx") " files. Python belongs in " (code (~tw :tokens "text-violet-700 text-sm") ".py") " files. If you see a Python f-string that builds " (code (~tw :tokens "text-violet-700 text-sm") "(div :class ...)") ", that is a boundary violation.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The correct pattern: Python returns " (strong "data") " (dicts, lists, strings). " (code (~tw :tokens "text-violet-700 text-sm") ".sx") " files receive data via keyword args and compose the markup. The only exception is " (code (~tw :tokens "text-violet-700 text-sm") "SxExpr") " wrappers for pre-rendered fragments — and those should be built with " (code (~tw :tokens "text-violet-700 text-sm") "sx_call()") " or " (code (~tw :tokens "text-violet-700 text-sm") "_sx_fragment()") ", never with f-strings.")
|
||||
(~docs/code :src (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 :src (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/section :title "Why this matters for multiple languages" :id "languages"
|
||||
(p :class "text-stone-600"
|
||||
"The boundary contract is target-agnostic. " (code :class "text-violet-700 text-sm" "boundary.sx") " and " (code :class "text-violet-700 text-sm" "primitives.sx") " declare what crosses the boundary. Each bootstrapper reads those declarations and emits the strongest enforcement the target language supports:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Target")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Enforcement")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Mechanism")))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The boundary contract is target-agnostic. " (code (~tw :tokens "text-violet-700 text-sm") "boundary.sx") " and " (code (~tw :tokens "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 (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Target")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Enforcement")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Mechanism")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Python")
|
||||
(td :class "px-3 py-2 text-stone-600" "Runtime")
|
||||
(td :class "px-3 py-2 text-stone-500 text-sm" "Frozen sets + validation at registration"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JavaScript")
|
||||
(td :class "px-3 py-2 text-stone-600" "Runtime")
|
||||
(td :class "px-3 py-2 text-stone-500 text-sm" "Set guards on registerPrimitive()"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Rust")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compile-time")
|
||||
(td :class "px-3 py-2 text-stone-500 text-sm" "SxValue enum, trait bounds on primitive fns"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Haskell")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compile-time")
|
||||
(td :class "px-3 py-2 text-stone-500 text-sm" "ADT + typeclass constraints"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "TypeScript")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compile-time")
|
||||
(td :class "px-3 py-2 text-stone-500 text-sm" "Discriminated union types")))))
|
||||
(p :class "text-stone-600"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Python")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Runtime")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-sm") "Frozen sets + validation at registration"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JavaScript")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Runtime")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-sm") "Set guards on registerPrimitive()"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Rust")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compile-time")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-sm") "SxValue enum, trait bounds on primitive fns"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Haskell")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compile-time")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-sm") "ADT + typeclass constraints"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "TypeScript")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compile-time")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-sm") "Discriminated union types")))))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In Python and JavaScript, boundary violations are caught at startup. In Rust, Haskell, or TypeScript, they would be caught at compile time — a function that returns a non-boundary type simply would not type-check. The spec is the same; the enforcement level rises with the type system.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The spec as contract" :id "contract"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The boundary enforcement files form a closed contract:")
|
||||
(ul :class "space-y-2 text-stone-600"
|
||||
(li (code :class "text-violet-700 text-sm" "primitives.sx") " — declares every pure function SX can call")
|
||||
(li (code :class "text-violet-700 text-sm" "boundary.sx") " — declares every I/O function and page helper")
|
||||
(li (code :class "text-violet-700 text-sm" "boundary_parser.py") " — reads both files, extracts declared names")
|
||||
(li (code :class "text-violet-700 text-sm" "boundary.py") " — runtime validation (validate_primitive, validate_io, validate_helper, validate_boundary_value)"))
|
||||
(p :class "text-stone-600"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (code (~tw :tokens "text-violet-700 text-sm") "primitives.sx") " — declares every pure function SX can call")
|
||||
(li (code (~tw :tokens "text-violet-700 text-sm") "boundary.sx") " — declares every I/O function and page helper")
|
||||
(li (code (~tw :tokens "text-violet-700 text-sm") "boundary_parser.py") " — reads both files, extracts declared names")
|
||||
(li (code (~tw :tokens "text-violet-700 text-sm") "boundary.py") " — runtime validation (validate_primitive, validate_io, validate_helper, validate_boundary_value)"))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"If you add a new host function and forget to declare it, the service will not start. If you return a disallowed type, the validation will catch it. The spec files are not documentation — they are the mechanism. The bootstrappers read them. The validators parse them. The contract is enforced by the same files that describe it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This closes the loop on self-hosting. The SX spec defines the language. The boundary spec defines the edge. The bootstrappers generate implementations from both. And the generated code validates itself against the spec at startup. The spec is the implementation is the contract is the spec."))))
|
||||
|
||||
@@ -1,109 +1,109 @@
|
||||
(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"
|
||||
(p (~tw :tokens "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.")
|
||||
|
||||
(~docs/section :title "The syntax tax" :id "syntax-tax"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Consider what an AI must get right to produce a valid React component: JSX tags that open and close correctly, curly braces for JavaScript expressions inside markup, import statements with correct paths, semicolons or ASI rules, TypeScript type annotations, CSS-in-JS string literals with different quoting rules than the surrounding code. Each syntactic concern is a potential failure point. Each failure produces something that does not parse, let alone run.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"S-expressions have one syntactic form: " (code "(head args...)") ". Parentheses open and close. Strings are quoted. That is the entire grammar. There is no operator precedence because there are no operators. There is no indentation sensitivity because whitespace is not significant. There are no closing tags because there are no tags — just lists.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "One representation for everything" :id "one-representation"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In SX, structure, style, logic, and data are all s-expressions:")
|
||||
(~docs/code :src (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"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~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"
|
||||
(p (~tw :tokens "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/" (~tw :tokens "text-violet-600 hover:underline") "ECMAScript specification") " is 900+ pages), or the combined specifications for HTML, CSS, and the DOM.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not just a convenience — it changes what kind of code the AI produces. When the model has the full spec in context, it does not hallucinate nonexistent features. It does not confuse one version's semantics with another's. It knows exactly which primitives exist, which special forms are available, and how evaluation works — because it is reading the authoritative definition, not interpolating from training data.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Structural validation is trivial" :id "structural-validation"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This makes it trivial to build AI pipelines that generate SX. Parse the output. If it parses, evaluate it in a sandbox. If it does not parse, the error is always the same kind — unmatched parentheses — and the fix is always mechanical. There is no \"your JSX is invalid because you used " (code "class") " instead of " (code "className") "\" or \"you forgot the semicolon after the type annotation but before the generic constraint.\"")
|
||||
(p :class "text-stone-600"
|
||||
"Beyond parsing, the SX " (a :href "/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."))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Beyond parsing, the SX " (a :href "/sx/(language.(spec.primitives))" (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Components are self-documenting" :id "self-documenting"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"An SX component declares everything in one expression:")
|
||||
(~docs/code :src (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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Token efficiency" :id "token-efficiency"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Compare equivalent UI definitions:")
|
||||
(~docs/code :src (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 :src (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"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Composability is free" :id "composability"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"S-expressions compose by nesting. A list inside a list is a composition. There are no rules beyond this:")
|
||||
(~docs/code :src (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"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The feedback loop" :id "feedback-loop"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-600 space-y-1 mt-2")
|
||||
(li "AI generates SX expression")
|
||||
(li "System parses (parentheses balance? done)")
|
||||
(li "Evaluator runs in sandbox (boundary-enforced)")
|
||||
(li "Result rendered or error returned (as s-expression)")
|
||||
(li "AI reads result, iterates"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Compare this to the conventional loop: AI generates code → linter runs → TypeScript compiles → bundler builds → browser loads → error appears in DevTools console → human copies error back to AI → AI regenerates. Each step is a different tool with different output formats. Each introduces latency and potential information loss.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "This site is the proof" :id "proof"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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/" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The development loop" :id "dev-loop"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is only possible because the representation is uniform. The AI does not need to switch between \"writing HTML mode\" and \"writing CSS mode\" and \"writing JavaScript mode\" and \"writing deployment config mode.\" It is always writing s-expressions. The cognitive load is constant. The error rate is constant. The speed is constant — regardless of whether it is generating a page layout, a macro expander, or a Docker healthcheck.")
|
||||
(p :class "text-stone-600"
|
||||
"The " (a :href "/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."))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The " (a :href "/sx/(etc.(essay.sx-sucks))" (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "What this changes" :id "what-changes"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The question is not whether AI will generate user interfaces. It already does. The question is what representation makes that generation most reliable, most efficient, and most safe. S-expressions — with their zero-syntax-tax grammar, uniform structure, self-describing components, structural validation, and sandboxed evaluation — are a strong answer.")
|
||||
(p :class "text-stone-600"
|
||||
"Not because they were designed for AI. " (a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" :class "text-violet-600 hover:underline" "McCarthy") " invented them in 1958, decades before anyone imagined language models. But the properties that make s-expressions elegant for humans — minimalism, uniformity, composability, homoiconicity — turn out to be exactly the properties that make them tractable for machines. The simplest possible syntax is also the most machine-friendly syntax. This is not a coincidence. It is a consequence of what simplicity means.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Not because they were designed for AI. " (a :href "https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" (~tw :tokens "text-violet-600 hover:underline") "McCarthy") " invented them in 1958, decades before anyone imagined language models. But the properties that make s-expressions elegant for humans — minimalism, uniformity, composability, homoiconicity — turn out to be exactly the properties that make them tractable for machines. The simplest possible syntax is also the most machine-friendly syntax. This is not a coincidence. It is a consequence of what simplicity means.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The era of AI-generated software is not coming. It is here. The question is which representations survive contact with it. The ones with the lowest syntax tax, the most uniform structure, and the tightest feedback loops will win — not because they are trendy, but because they are what the machines can actually produce reliably. S-expressions have been waiting sixty-seven years for a generation mechanism worthy of their simplicity. They finally have one."))))
|
||||
|
||||
@@ -1,112 +1,112 @@
|
||||
(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"
|
||||
(p (~tw :tokens "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.")
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Daniel " (a :href "https://en.wikipedia.org/wiki/Daniel_Dennett" (~tw :tokens "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" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"There are three stances. The " (em "physical stance") " predicts from physics — voltage levels, transistor states, bytes in memory. The " (em "design stance") " predicts from how the thing was built — a thermostat turns on the heating when the temperature drops below the set point, regardless of its internal wiring. The " (em "intentional stance") " predicts from ascribed beliefs and goals — the chess program \"wants\" to protect its king, \"believes\" the centre is important.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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 ~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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Dennett's 1991 paper \"" (a :href "https://en.wikipedia.org/wiki/Real_Patterns" (~tw :tokens "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 (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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.")
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In " (a :href "https://en.wikipedia.org/wiki/Consciousness_Explained" (~tw :tokens "text-violet-600 hover:underline") "Consciousness Explained") " (1991), Dennett proposed the " (a :href "https://en.wikipedia.org/wiki/Multiple_drafts_model" (~tw :tokens "text-violet-600 hover:underline") "Multiple Drafts model") " of consciousness. There is no " (a :href "https://en.wikipedia.org/wiki/Cartesian_theater" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"React is a Cartesian theater. The virtual DOM is the stage. Reconciliation is the moment where \"it all comes together\" — the single canonical comparison between what was and what should be. One tree diffs against another. One algorithm produces one patch. The entire UI passes through a single bottleneck. There is a master draft, and its name is " (code "ReactDOM.render") ".")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX has no theater. There are multiple drafts, genuinely parallel, with no single canonical render:")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-600"
|
||||
(li (span :class "font-semibold" "Server draft") " — Python evaluates components into SX wire format. This is a draft: it contains component calls unexpanded, slots unfilled, decisions deferred.")
|
||||
(li (span :class "font-semibold" "Wire draft") " — the SX source text transmitted over HTTP. It is a draft in transit — meaningful as text, interpretable by any reader, but not yet rendered.")
|
||||
(li (span :class "font-semibold" "Client draft") " — JavaScript evaluates the wire format into DOM nodes. Another draft: the browser's layout engine will revise it further (CSS computation, reflow, paint).")
|
||||
(li (span :class "font-semibold" "Interaction draft") " — the user clicks, the server produces new SX, the client patches the DOM. The revision process continues. No draft is final."))
|
||||
(p :class "text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-600")
|
||||
(li (span (~tw :tokens "font-semibold") "Server draft") " — Python evaluates components into SX wire format. This is a draft: it contains component calls unexpanded, slots unfilled, decisions deferred.")
|
||||
(li (span (~tw :tokens "font-semibold") "Wire draft") " — the SX source text transmitted over HTTP. It is a draft in transit — meaningful as text, interpretable by any reader, but not yet rendered.")
|
||||
(li (span (~tw :tokens "font-semibold") "Client draft") " — JavaScript evaluates the wire format into DOM nodes. Another draft: the browser's layout engine will revise it further (CSS computation, reflow, paint).")
|
||||
(li (span (~tw :tokens "font-semibold") "Interaction draft") " — the user clicks, the server produces new SX, the client patches the DOM. The revision process continues. No draft is final."))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(a :href "https://en.wikipedia.org/wiki/Heterophenomenology" (~tw :tokens "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 (~tw :tokens "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"
|
||||
(p (~tw :tokens "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.")
|
||||
(~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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Dennett's thought experiment \"" (a :href "https://en.wikipedia.org/wiki/Where_Am_I%3F_(Dennett)" (~tw :tokens "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 (~tw :tokens "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 "(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Dennett argued in " (a :href "https://en.wikipedia.org/wiki/From_Bacteria_to_Bach_and_Back" (~tw :tokens "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 (~tw :tokens "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.")
|
||||
(~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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Dennett called his thought experiments \"" (a :href "https://en.wikipedia.org/wiki/Intuition_pump" (~tw :tokens "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 (~tw :tokens "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 ~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Compare this to a React component:")
|
||||
(~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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In Consciousness Explained, Dennett describes the brain as a \"" (a :href "https://en.wikipedia.org/wiki/Consciousness_Explained" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"SX is a Joycean machine running on the web. The web's substrate — TCP/IP, HTTP, the DOM, JavaScript engines — knows nothing about s-expressions, components, or evaluators. Yet the SX virtual machine runs on this substrate, producing real pages, real interactions, real applications. The substrate provides the physical-stance machinery. SX provides the intentional-stance narrative: this is a card, this is a page, this is a layout, this composes with that.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
"The concept of a \"" (a :href "https://en.wikipedia.org/wiki/Quine_(computing)" :class "text-violet-600 hover:underline" "quine") "\" in computing is related: a program that outputs its own source code. The word honours the same Quine, for the same reason — self-reference that collapses the distinction between describer and described.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Dennett borrowed the term \"" (a :href "https://en.wikipedia.org/wiki/Qualia#Dennett's_criticism" (~tw :tokens "text-violet-600 hover:underline") "quining") "\" from the logician " (a :href "https://en.wikipedia.org/wiki/Willard_Van_Orman_Quine" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"The concept of a \"" (a :href "https://en.wikipedia.org/wiki/Quine_(computing)" (~tw :tokens "text-violet-600 hover:underline") "quine") "\" in computing is related: a program that outputs its own source code. The word honours the same Quine, for the same reason — self-reference that collapses the distinction between describer and described.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX quines in both senses. In the computing sense: the self-hosting spec is a quine-like structure — " (code "eval.sx") " is an SX program that, when compiled and run, produces an evaluator capable of running " (code "eval.sx") ". It is not a literal quine (it doesn't output itself character-for-character), but it has the essential quine property: the output contains the input.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In Dennett's philosophical sense: SX quines the web's qualia. The supposedly irreducible essences of web development — \"components,\" \"state,\" \"the DOM,\" \"the server-client boundary\" — dissolve under SX's scrutiny into what they always were: data structures. Lists of symbols. Expressions that evaluate to other expressions. The qualia of web development are not irreducible. They are patterns in s-expressions, and once you see that, you cannot unsee it.")
|
||||
(p :class "text-stone-600"
|
||||
"Dennett's lifelong project was to show that the mind is not what our intuitions say it is — that consciousness, free will, and the self are real phenomena that do not require the metaphysical foundations we instinctively assign them. They are " (a :href "https://en.wikipedia.org/wiki/Real_Patterns" :class "text-violet-600 hover:underline" "real patterns") " in physical processes, not ghostly essences hovering above matter.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Dennett's lifelong project was to show that the mind is not what our intuitions say it is — that consciousness, free will, and the self are real phenomena that do not require the metaphysical foundations we instinctively assign them. They are " (a :href "https://en.wikipedia.org/wiki/Real_Patterns" (~tw :tokens "text-violet-600 hover:underline") "real patterns") " in physical processes, not ghostly essences hovering above matter.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX makes the same move for the web. Components are real patterns, not framework essences. The server-client boundary is a draft boundary, not an ontological divide. The build step is a habit, not a necessity. The virtual DOM is a Cartesian theater, not a requirement. Strip away the false intuitions, and what remains is what was always there: expressions, evaluation, and composition. All the way down."))))
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
(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"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"The limits of my language are the limits of my world.")
|
||||
(~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"
|
||||
"Wittgenstein replaced the picture theory with " (a :href "https://en.wikipedia.org/wiki/Language_game_(philosophy)" :class "text-violet-600 hover:underline" "language games") " — activities in which words get their meaning from how they are employed, not from what they refer to. \"Slab!\" on a building site means \"bring me a slab.\" The same word in a dictionary means nothing until it enters a game. Meaning is use.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"In 1953, Ludwig " (a :href "https://en.wikipedia.org/wiki/Ludwig_Wittgenstein" (~tw :tokens "text-violet-600 hover:underline") "Wittgenstein") " published " (a :href "https://en.wikipedia.org/wiki/Philosophical_Investigations" (~tw :tokens "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" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"Wittgenstein replaced the picture theory with " (a :href "https://en.wikipedia.org/wiki/Language_game_(philosophy)" (~tw :tokens "text-violet-600 hover:underline") "language games") " — activities in which words get their meaning from how they are employed, not from what they refer to. \"Slab!\" on a building site means \"bring me a slab.\" The same word in a dictionary means nothing until it enters a game. Meaning is use.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"" (a :href "https://en.wikipedia.org/wiki/Tractatus_Logico-Philosophicus" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"If your language is HTML, your world is documents. You can link documents. You can nest elements inside documents. You cannot compose documents from smaller documents without a server-side include, an iframe, or JavaScript. The language does not have composition, so your world does not have composition.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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.")
|
||||
(~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"
|
||||
(p (~tw :tokens "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") "."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The Tractatus ends: \"" (a :href "https://en.wikipedia.org/wiki/Tractatus_Logico-Philosophicus#Proposition_7" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Wittgenstein argued that concepts do not have sharp definitions. What is a \"" (a :href "https://en.wikipedia.org/wiki/Family_resemblance" (~tw :tokens "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" (~tw :tokens "text-violet-600 hover:underline") "family resemblance") ".")
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "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.")
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The " (a :href "https://en.wikipedia.org/wiki/Private_language_argument" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"Most web frameworks are private languages. React's JSX is meaningful only to the React runtime. Vue's " (code ".vue") " single-file components are meaningful only to the Vue compiler. Svelte's " (code ".svelte") " files are meaningful only to the Svelte compiler. Each framework speaks a language that no one else can understand. They are private languages in Wittgenstein's sense — their terms have meaning only within their own closed world.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~docs/section :title "VI. Showing and saying" :id "showing-saying"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Most languages " (em "say") " their semantics — in English-language specifications, in RFC documents, in MDN pages. The semantics are described " (em "about") " the language, in a different medium.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Wittgenstein's " (a :href "https://en.wikipedia.org/wiki/Private_language_argument#The_beetle_in_a_box" (~tw :tokens "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 (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"What is your aim in philosophy?\" Wittgenstein was asked. \"" (a :href "https://en.wikipedia.org/wiki/Philosophical_Investigations#Meaning_and_definition" (~tw :tokens "text-violet-600 hover:underline") "To show the fly the way out of the fly-bottle") ".\"")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The fly-bottle is the trap of false problems — questions that seem deep but are actually artefacts of confused language. \"What is the virtual DOM?\" is a fly-bottle question. The virtual DOM exists because React needed a way to reconcile its component model with the browser's DOM. If your component model " (em "is") " the DOM — if components evaluate directly to DOM nodes, as they do in SX — the question dissolves. There is no virtual DOM because there is no gap between the component and what it produces.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"How do we solve CSS scoping?\" Fly-bottle. CSS scoping is a problem because CSS is a global language applied to a local context. If styles are expressions evaluated in the same scope as the component that uses them — as in SX's CSSX — there is nothing to scope. The problem was created by the language separation, not by the nature of styling.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"\"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"
|
||||
(p (~tw :tokens "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."))
|
||||
(~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"
|
||||
(p (~tw :tokens "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" (~tw :tokens "text-violet-600 hover:underline") "friction") ". Back to the rough ground!\"")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The slippery ice is the idealised abstraction — the framework that promises a perfect developer experience, the type system that promises to catch all bugs, the architecture diagram that promises clean separation. These are frictionless surfaces. They look beautiful. You cannot walk on them.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX is rough ground. S-expressions are not pretty. Parentheses are friction. There is no syntax highlighting tuned for this. There is no IDE with SX autocomplete. There is no build system to smooth over rough edges because there is no build system. You write s-expressions. You evaluate them. You see the result. The feedback loop is immediate and unmediated.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is a feature. Friction is how you know you are touching the ground. Friction is where the work happens. The perfectly frictionless framework lets you get started in five minutes and spend six months debugging its abstractions. SX asks you to understand s-expressions — a syntax that has not changed since 1958 — and then gives you the entire web as your world.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Wittgenstein wanted philosophy to stop theorising and start looking at how language actually works. SX wants web development to stop abstracting and start looking at what expressions actually are. Lists. Symbols. Evaluation. Composition. Everything else is a fly-bottle."))))
|
||||
|
||||
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 ~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."))))
|
||||
(~docs/page :title "sx sucks" (~docs/section :title "The parentheses" :id "parens" (p (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "text-stone-600") "That person is busy. Good luck.")) (~docs/section :title "Zero jobs" :id "jobs" (p (~tw :tokens "text-stone-600") "Adding sx to your CV will not get you hired. It will get you questioned.") (p (~tw :tokens "text-stone-600") "The interview will end shortly after.")) (~docs/section :title "The creator thinks s-expressions are a personality trait" :id "personality" (p (~tw :tokens "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 (~tw :tokens "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
@@ -4,70 +4,70 @@
|
||||
|
||||
(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"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"On making, self-making, and the chain of artifacts that produces itself.")
|
||||
|
||||
(~docs/section :title "I. Ars" :id "ars"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A bridge is " (em "ars") ". A poem is " (em "ars") ". A proof is " (em "ars") ". What makes something art is not its medium or its audience but the fact that it was " (em "made") " — brought into being by someone who knew how to bring it into being. The maker's knowledge is embedded in the made thing. You can read the knowledge back out by studying what was made.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "II. The spec at the centre" :id "spec"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is the nucleus. Everything else radiates outward from it.")
|
||||
(~docs/code :src (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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"From this nucleus, concentric rings unfurl:"))
|
||||
|
||||
(~docs/section :title "III. The rings" :id "rings"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The second ring is the " (strong "platform bridge") ". The spec defines pure logic — evaluation, rendering, parsing. But a running system needs to touch the world: read files, make HTTP requests, manipulate DOM nodes. The platform bridge provides these capabilities. It is the boundary between the made world (the spec) and the found world (the host environment). " (code "boundary.sx") " is literally the membrane — it declares what the host must provide so the spec can function.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The third ring is the " (strong "runtime") " — bootstrapped spec plus platform bridge, assembled into a working system. This is where the spec stops being an idea and starts being a process. It evaluates expressions. It renders pages. It handles requests.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The fourth ring is " (strong "application code") " — components, pages, layouts, written in the language the spec defined. Every " (code "defcomp") " is an artifact made from the tools the spec provided. Every " (code "(div :class \"card\" (p \"hello\"))") " is the spec expressing itself through a developer's intent.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "IV. The chain" :id "chain"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This chain has three properties that are individually common but collectively rare:")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(strong "Content addressing.") " Each artifact can be identified by the hash of its content. The spec at a given version has a specific hash. The bootstrapped output from that spec has a deterministic hash. A component definition has a hash. Identity " (em "is") " content. You don't ask " (em "where") " an artifact lives — you ask " (em "what") " it is.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(strong "Deterministic derivation.") " Given the same spec, the bootstrapper produces the same output. Byte for byte. This is not aspirational — it is verified. The self-hosting bootstrapper (py.sx) proves it: G0 (hand-written bootstrapper) and G1 (self-hosted bootstrapper) produce identical output. The derivation is a pure function. Anyone can run it and verify the result.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(strong "Self-verification.") " The spec includes tools that can prove properties about the spec. " (code "prove.sx") " checks primitive semantics. " (code "types.sx") " validates composition. " (code "z3.sx") " translates declarations into verification conditions. These tools are themselves part of the spec, subject to the same verification they perform. The chain can verify itself.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "V. Universal analysis" :id "analysis"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A type checker written in SX that validates the spec's primitives also validates every call to those primitives in every component in every application. A dependency analyser that walks the spec's AST walks application ASTs identically — because application code is expressed in the same structures the spec defines. A theorem prover that verifies the spec's properties verifies the properties of everything downstream.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is because the rings are not separate systems. They are the " (em "same") " system at different scales. Application code is spec-shaped. Bootstrapped output is spec-derived. Components are spec-evaluated. The analysis surface is uniform from the nucleus to the outermost ring.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "VI. The art in the chain" :id "art"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"It is not a blockchain in the financial sense. It is not a distributed ledger, a currency, a market. It borrows the structural properties — content addressing, determinism, verification — without the economic machinery. What remains when you strip the economics from a blockchain is a " (em "provenance chain") ": a record of how each thing was made from the thing before it, verifiable by anyone, depending on nothing but the mathematics.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The Art DAG has the right name. It is not a system for processing \"art\" in the colloquial sense — images, videos, media. It is a " (em "directed acyclic graph of made things") ". Each node is an artifact. Each edge is a derivation. The graph is content-addressed. Execution is deterministic. The DAG itself is the art.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"And the whole SX system — spec, bootstrappers, runtimes, components, pages, this essay explaining itself — is one continuous act of making. " (em "Ars") " all the way down. Not because it is beautiful (though it sometimes is) or expressive (though it tries to be) but because it is " (em "made") ". Deliberately, skilfully, from nothing, by someone who knew how.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"That is what " (em "techne") " always was. We just forgot."))))
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
(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."))))
|
||||
(~docs/page :title "Why S-Expressions Over HTML Attributes" (~docs/section :title "The problem with HTML attributes" :id "problem" (p (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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,20 +1,20 @@
|
||||
(defcomp ~essays/zero-tooling/essay-zero-tooling ()
|
||||
(~docs/page :title "Tools for Fools"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "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.")
|
||||
|
||||
(~docs/section :title "No code editor" :id "no-editor"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"No VS Code. No Vim. No Emacs. No Sublime Text. No cursor blinking in a source file. The developer describes intent; the AI produces the code, edits the files, runs the tests, and iterates. The code editor — the tool that has defined software development for sixty years — was not used.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The toolchain that wasn't" :id "toolchain"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "Bundler") " — Webpack, Vite, Rollup, or esbuild to resolve modules, tree-shake dead code, split bundles, and minify output.")
|
||||
(li (strong "Transpiler") " — Babel or SWC to transform JSX into function calls, strip TypeScript annotations, and downlevel modern syntax for older browsers.")
|
||||
(li (strong "Package manager") " — npm, yarn, or pnpm to manage hundreds or thousands of transitive dependencies in " (code "node_modules") ".")
|
||||
@@ -25,112 +25,112 @@
|
||||
(li (strong "Type checker") " — TypeScript compiler running in parallel to catch type errors that JavaScript's dynamic semantics would defer to runtime.")
|
||||
(li (strong "Test runner") " — Jest or Vitest with jsdom or happy-dom to simulate a browser environment, plus Cypress or Playwright for integration tests.")
|
||||
(li (strong "Framework CLI") " — create-react-app, Next.js CLI, or Angular CLI to scaffold project structure, generate boilerplate, and manage framework-specific configuration."))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"That is ten categories of tooling, each with its own configuration format, update cycle, breaking changes, and mental overhead. A fresh " (code "npx create-next-app") " pulls in over 300 packages. The " (code "node_modules") " directory can exceed 200 megabytes. The configuration surface — " (code "webpack.config.js") ", " (code "tsconfig.json") ", " (code ".eslintrc") ", " (code ".prettierrc") ", " (code "postcss.config.js") ", " (code "tailwind.config.js") ", " (code "jest.config.js") ", " (code "next.config.js") " — is itself a specialization.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX uses " (strong "none of them") "."))
|
||||
|
||||
(~docs/section :title "Why each tool is unnecessary" :id "why-unnecessary"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Each tool in the conventional stack exists to solve a problem. SX eliminates the problems themselves, not just the tools.")
|
||||
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Bundlers")
|
||||
(p :class "text-stone-600"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Bundlers")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Bundlers exist because JavaScript's module system requires resolution — imports must be traced, dependencies gathered, and the result packaged for the browser. SX components are defined in " (code ".sx") " files, loaded at startup into the evaluator's environment, and served as s-expressions over the wire. The wire format is the source format. There is no compilation step, no module resolution, and no bundle. The " (code "deps.sx") " spec computes per-page component sets at runtime — only the components a page actually uses are sent to the client. This is tree-shaking without the tree-shaker.")
|
||||
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Transpilers")
|
||||
(p :class "text-stone-600"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Transpilers")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Transpilers exist because developers write in one language (JSX, TypeScript, modern ES20XX) and must ship another (browser-compatible JavaScript). SX source runs directly — the evaluator walks the same AST that was authored. There is no JSX-to-createElement transform because s-expressions " (em "are") " the component syntax. There are no type annotations to strip because types are enforced at the boundary, at startup. There is no syntax to downlevel because the grammar has been the same since 1958.")
|
||||
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Package managers")
|
||||
(p :class "text-stone-600"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Package managers")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Package managers exist because the JavaScript ecosystem fragments functionality into thousands of tiny packages with deep transitive dependency trees. SX's runtime is self-contained: approximately eighty primitives built into the spec, an evaluator bootstrapped from " (code ".sx") " files, and a renderer that produces HTML, DOM nodes, or SX wire format. No third-party packages. No dependency resolution. No lock files. No supply-chain attack surface. The planned future — content-addressed components on IPFS — replaces package registries with content verification.")
|
||||
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "CSS build tools")
|
||||
(p :class "text-stone-600"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "CSS build tools")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"CSS preprocessors and build tools exist because CSS has a global namespace, no scoping mechanism, and no way to know which rules a page actually uses. CSSX solves all three: the server scans the rendered component tree, collects the CSS classes actually referenced, and sends only those rules. Styles are co-located in components as keyword arguments. There is no global stylesheet to collide with, no preprocessor to run, no PurgeCSS to configure. The CSS each page needs is computed from the component graph — a runtime operation, not a build step.")
|
||||
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Dev servers and HMR")
|
||||
(p :class "text-stone-600"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Dev servers and HMR")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Dev servers with hot module replacement exist because compilation creates a feedback delay — you change a file, wait for the bundler to rebuild, and watch the browser update via WebSocket. SX has no compilation. " (code ".sx") " files are checked for changes on every request via " (code "reload_if_changed()") ". Edit the file, refresh the browser, see the result. The feedback loop is bounded by disk I/O, not by a build pipeline. There is no dev server because there is nothing to serve differently from production.")
|
||||
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Linters and formatters")
|
||||
(p :class "text-stone-600"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Linters and formatters")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Linters exist because complex grammars permit many ways to write incorrect or inconsistent code. Formatters exist because those same grammars permit many ways to write correct but inconsistently styled code. S-expressions have one syntactic form: " (code "(head args...)") ". Parentheses open and close. Strings are quoted. That is the entire grammar. There are no semicolons to forget, no operator precedence to get wrong, no closing tags to mismatch. Formatting is just indentation of nested lists. There is nothing to lint and nothing to format.")
|
||||
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Type systems")
|
||||
(p :class "text-stone-600"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Type systems")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"TypeScript exists because JavaScript's dynamic typing defers errors to runtime, often to production. SX's boundary spec declares the signature of every primitive, every IO function, and every page helper. " (code "SX_BOUNDARY_STRICT=1") " validates all registrations at startup — a type violation crashes the application before it serves a single request. Runtime predicates (" (code "string?") ", " (code "number?") ", " (code "list?") ") handle the rest. The type system is not a separate tool running in parallel — it is the spec, enforced at the edge.")
|
||||
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Framework CLIs and scaffolding")
|
||||
(p :class "text-stone-600"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Framework CLIs and scaffolding")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The AI replaces the rest" :id "ai-replaces"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "Syntax highlighting") " — because humans need visual cues to parse complex grammars. S-expressions are trivially parseable; an AI does not need color-coding to understand them.")
|
||||
(li (strong "Autocomplete") " — because humans cannot remember every function name, parameter, and type signature. An AI that has read the entire codebase does not need autocomplete; it already knows every symbol in scope.")
|
||||
(li (strong "Go-to-definition") " — because humans lose track of where things are defined across hundreds of files. An AI navigates by reading, grep, and glob — the same tools the editor uses internally, without the GUI.")
|
||||
(li (strong "Error squiggles") " — because humans need real-time feedback on mistakes. An AI produces correct code from the spec, and when it does not, it reads the error, understands the cause, and fixes it in the next edit.")
|
||||
(li (strong "Multi-cursor editing") " — because humans need mechanical assistance to make the same change in multiple places. An AI makes coordinated changes across files atomically — component definition, navigation data, page route — in a single operation.")
|
||||
(li (strong "Version control integration") " — because humans need visual diffs and staging interfaces. An AI reads " (code "git diff") ", understands the changes, and commits with meaningful messages."))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Every feature of a modern IDE exists to compensate for a human cognitive limitation. When the agent doing the editing has no such limitations — it can hold the full codebase in context, produce valid syntax without visual feedback, trace dependencies without tooling, and make multi-file changes atomically — the editor is not needed.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Carson Gross makes an important point in " (a :href "https://htmx.org/essays/yes-and/" (~tw :tokens "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 (~tw :tokens "text-stone-600")
|
||||
"He is right. " (strong "...and yet."))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"There is a Zen teaching about the three stages of practice. Before enlightenment: chop wood, carry water. During enlightenment: chop wood, carry water. After enlightenment: chop wood, carry water. The activities look the same from the outside, but the relationship to them has changed completely. The master chops wood without the beginner's anxiety about whether they are chopping correctly, and without the intermediate's self-conscious technique. They just chop.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Software development has its own version. " (em "Before understanding, write code.") " You must have written the bundler configuration to understand why bundlers exist. You must have debugged the CSS cascade to understand why scoping matters. You must have traced a transitive dependency chain to understand why minimal dependencies matter. The craft comes first.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(em "During understanding, read code.") " You review pull requests. You audit architectures. You read more code than you write. You develop the taste that lets you tell good code from bad code, necessary complexity from accidental complexity, real problems from invented ones.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(em "After understanding, describe intent.") " You have internalized the patterns so deeply that you do not need to type them. You know what a component should look like without writing it character by character. You know what the test should cover without spelling out each assertion. You know what the architecture should be without drawing every box and arrow. You describe; the agent executes. Not because you cannot do the work, but because the work has become transparent to you.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is not a shortcut. The developer who built SX through conversation with an AI had decades of experience writing code by hand — in Python, VB, C#, JavaScript, C, and others (although he was never that good at it) — but never Lisp. The zero-tooling workflow is not a substitute for that experience. It is what comes " (em "after") " that experience. The wood still gets chopped. The water still gets carried. But the relationship to the chopping and carrying has changed.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Why this only works with s-expressions" :id "why-sexps"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Consider what an AI must get right to produce a valid React component: JSX tags that open and close correctly, curly braces for JavaScript expressions inside markup, import statements with correct module paths, semicolons or ASI boundary rules, TypeScript type annotations with angle-bracket generics, CSS-in-JS template literals with different quoting rules from the surrounding code, and hook call ordering constraints that are semantic, not syntactic. Each of these is a failure point. Each failure produces something that does not parse, let alone run.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"S-expressions have one rule: parentheses match. The syntax tax is zero. An AI that can count parentheses can produce syntactically valid SX. This means the AI spends its entire capacity on " (em "what") " to generate — the semantics, the structure, the intent — rather than on formatting, escaping, and syntactic bookkeeping.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The combination is more than additive. SX is deliberately spartan for hand-editing — all those parentheses, no syntax sugar, no operator precedence. Developers who see it for the first time recoil. But AI does not recoil. AI does not care about parentheses. It sees a minimal, regular, unambiguous grammar and produces correct output reliably. Meanwhile, the languages that are comfortable for humans — with their rich syntax, implicit rules, and contextual parsing — are exactly the ones where AI makes the most mistakes.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "What zero-tooling actually means" :id "what-it-means"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"A carpenter's hammer is a tool. The house is not a tool. In web development, this distinction has been lost. The bundler, the transpiler, the linter, the formatter, the type checker, the dev server — these are all tools for building the application. The application itself is the server, the evaluator, the renderer, the browser runtime. Traditional web development has accumulated so many tools-for-building-the-thing that the tools-for-building-the-thing often have more code, more configuration, and more failure modes than the thing itself.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX has the thing. It does not have tools for building the thing. You write " (code ".sx") " files. They run. That is the entire workflow.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The proof" :id "proof"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "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"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Every page you are reading was produced through conversation with an agentic AI. The SX evaluator — a self-hosting interpreter with tail-call optimization, delimited continuations, macro expansion, and three rendering backends — was developed without opening a code editor. The specification files that define the language were written without an IDE. The bootstrappers that compile the spec to JavaScript and Python were produced without syntax highlighting or autocomplete. The test suite — hundreds of tests across evaluator, parser, renderer, router, dependency analyzer, and engine — was written without a test runner GUI. This documentation site — with its navigation, its code examples, its live demos — was built without a web development framework's CLI.")
|
||||
(p :class "text-stone-600"
|
||||
"The developer sat in a terminal. They described what they wanted. The AI produced the code. When something was wrong, they described what was wrong. The AI fixed it. When something needed to change, they described the change. The AI made the change. Across thousands of files, tens of thousands of lines of code, and months of development. Even the jokes — the " (a :href "/sx/(etc.(essay.sx-sucks))" :class "text-violet-600 hover:underline" "self-deprecating essay") " about everything wrong with SX, the deadpan tone of the documentation, the essay you are reading right now — all produced through conversation, not through typing.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The developer sat in a terminal. They described what they wanted. The AI produced the code. When something was wrong, they described what was wrong. The AI fixed it. When something needed to change, they described the change. The AI made the change. Across thousands of files, tens of thousands of lines of code, and months of development. Even the jokes — the " (a :href "/sx/(etc.(essay.sx-sucks))" (~tw :tokens "text-violet-600 hover:underline") "self-deprecating essay") " about everything wrong with SX, the deadpan tone of the documentation, the essay you are reading right now — all produced through conversation, not through typing.")
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"No build step. No bundler. No transpiler. No package manager. No CSS preprocessor. No dev server. No linter. No formatter. No type checker. No framework CLI. No code editor.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Zero tools."))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
(handler-heading :as string?))
|
||||
(~docs/page
|
||||
:title title
|
||||
(p :class "text-stone-600 mb-6" description)
|
||||
(p (~tw :tokens "text-stone-600 mb-6") description)
|
||||
(error-boundary
|
||||
(~examples/card
|
||||
:title "Demo"
|
||||
:description demo-description
|
||||
(~examples/demo demo)))
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mt-8 mb-3" "S-expression")
|
||||
(h3 (~tw :tokens "text-lg font-semibold text-stone-700 mt-8 mb-3") "S-expression")
|
||||
(error-boundary
|
||||
(~examples/source
|
||||
:src-code (highlight sx-code (if sx-lang sx-lang "lisp"))))
|
||||
@@ -30,11 +30,11 @@
|
||||
comp-placeholder-id
|
||||
(<>
|
||||
(h3
|
||||
:class "text-lg font-semibold text-stone-700 mt-8 mb-3"
|
||||
(~tw :tokens "text-lg font-semibold text-stone-700 mt-8 mb-3")
|
||||
(if comp-heading comp-heading "Component"))
|
||||
(error-boundary (~docs/placeholder :id comp-placeholder-id))))
|
||||
(h3
|
||||
:class "text-lg font-semibold text-stone-700 mt-8 mb-3"
|
||||
(~tw :tokens "text-lg font-semibold text-stone-700 mt-8 mb-3")
|
||||
(if handler-heading handler-heading "Server handler"))
|
||||
(error-boundary
|
||||
(~examples/source
|
||||
@@ -42,10 +42,10 @@
|
||||
(join "\n\n" (map handler-source handler-names))
|
||||
(if handler-lang handler-lang "sx"))))
|
||||
(div
|
||||
:class "flex items-center justify-between mt-6"
|
||||
(h3 :class "text-lg font-semibold text-stone-700" "Wire response")
|
||||
(~tw :tokens "flex items-center justify-between mt-6")
|
||||
(h3 (~tw :tokens "text-lg font-semibold text-stone-700") "Wire response")
|
||||
(~docs/clear-cache-btn))
|
||||
(when wire-note (p :class "text-stone-500 text-sm mb-2" wire-note))
|
||||
(when wire-note (p (~tw :tokens "text-stone-500 text-sm mb-2") wire-note))
|
||||
(when
|
||||
wire-placeholder-id
|
||||
(error-boundary (~docs/placeholder :id wire-placeholder-id)))))
|
||||
@@ -56,10 +56,10 @@
|
||||
(~docs/page
|
||||
:title "Reference"
|
||||
(p
|
||||
:class "text-stone-600 mb-6"
|
||||
(~tw :tokens "text-stone-600 mb-6")
|
||||
"Complete reference for the sx client library.")
|
||||
(div
|
||||
:class "grid gap-4 sm:grid-cols-2"
|
||||
(~tw :tokens "grid gap-4 sm:grid-cols-2")
|
||||
(a
|
||||
:href "/sx/(geography.(hypermedia.(reference.attributes)))"
|
||||
:sx-get "/sx/(geography.(hypermedia.(reference.attributes)))"
|
||||
@@ -67,10 +67,10 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Attributes")
|
||||
(~tw :tokens "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline")
|
||||
(h3 (~tw :tokens "text-lg font-semibold text-violet-700 mb-1") "Attributes")
|
||||
(p
|
||||
:class "text-stone-600 text-sm"
|
||||
(~tw :tokens "text-stone-600 text-sm")
|
||||
"All sx attributes — request verbs, behavior modifiers, and sx-unique features."))
|
||||
(a
|
||||
:href "/sx/(geography.(hypermedia.(reference.headers)))"
|
||||
@@ -79,10 +79,10 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Headers")
|
||||
(~tw :tokens "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline")
|
||||
(h3 (~tw :tokens "text-lg font-semibold text-violet-700 mb-1") "Headers")
|
||||
(p
|
||||
:class "text-stone-600 text-sm"
|
||||
(~tw :tokens "text-stone-600 text-sm")
|
||||
"Custom HTTP headers used to coordinate between the sx client and server."))
|
||||
(a
|
||||
:href "/sx/(geography.(hypermedia.(reference.events)))"
|
||||
@@ -91,10 +91,10 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Events")
|
||||
(~tw :tokens "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline")
|
||||
(h3 (~tw :tokens "text-lg font-semibold text-violet-700 mb-1") "Events")
|
||||
(p
|
||||
:class "text-stone-600 text-sm"
|
||||
(~tw :tokens "text-stone-600 text-sm")
|
||||
"DOM events fired during the sx request lifecycle."))
|
||||
(a
|
||||
:href "/sx/(geography.(hypermedia.(reference.js-api)))"
|
||||
@@ -103,8 +103,8 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "JS API")
|
||||
(~tw :tokens "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline")
|
||||
(h3 (~tw :tokens "text-lg font-semibold text-violet-700 mb-1") "JS API")
|
||||
(p
|
||||
:class "text-stone-600 text-sm"
|
||||
(~tw :tokens "text-stone-600 text-sm")
|
||||
"JavaScript functions for parsing, evaluating, and rendering s-expressions.")))))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(~docs/page
|
||||
:title "Capabilities"
|
||||
(p
|
||||
:class "text-stone-500 text-sm italic mb-8"
|
||||
(~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Abstract evaluation contexts — what an expression is allowed to do, without prescribing where it runs.")
|
||||
(~docs/section
|
||||
:title "The model"
|
||||
@@ -32,34 +32,34 @@
|
||||
:title "Capability primitives"
|
||||
:id "primitives"
|
||||
(table
|
||||
:class "min-w-full text-sm mb-6"
|
||||
(~tw :tokens "min-w-full text-sm mb-6")
|
||||
(thead
|
||||
(tr
|
||||
(th
|
||||
:class "text-left pr-4 pb-2 font-semibold text-stone-700"
|
||||
(~tw :tokens "text-left pr-4 pb-2 font-semibold text-stone-700")
|
||||
"Primitive")
|
||||
(th :class "text-left pb-2 font-semibold text-stone-700" "Purpose")))
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold text-stone-700") "Purpose")))
|
||||
(tbody
|
||||
:class "text-stone-600"
|
||||
(~tw :tokens "text-stone-600")
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "with-capabilities")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "with-capabilities")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"Restrict capabilities for a body. Takes a list of capability names and a thunk."))
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "has-capability?")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "has-capability?")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"Check if a capability is available. Returns true in unrestricted contexts."))
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "require-capability!")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "require-capability!")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"Assert a capability. Error with a clear message if not available."))
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "current-capabilities")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "current-capabilities")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"Return the current capability set, or nil if unrestricted.")))))
|
||||
(~docs/section
|
||||
:title "Effect annotations"
|
||||
@@ -81,41 +81,41 @@
|
||||
:title "Standard capabilities"
|
||||
:id "standard"
|
||||
(table
|
||||
:class "min-w-full text-sm mb-6"
|
||||
(~tw :tokens "min-w-full text-sm mb-6")
|
||||
(thead
|
||||
(tr
|
||||
(th
|
||||
:class "text-left pr-4 pb-2 font-semibold text-stone-700"
|
||||
(~tw :tokens "text-left pr-4 pb-2 font-semibold text-stone-700")
|
||||
"Capability")
|
||||
(th
|
||||
:class "text-left pb-2 font-semibold text-stone-700"
|
||||
(~tw :tokens "text-left pb-2 font-semibold text-stone-700")
|
||||
"What it permits")))
|
||||
(tbody
|
||||
:class "text-stone-600"
|
||||
(~tw :tokens "text-stone-600")
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "pure")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "pure")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"No side effects. Deterministic. Cacheable. Runnable anywhere."))
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "mutation")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "mutation")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"Mutable state: set!, append!, dict-set!, signal mutation."))
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "io")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "io")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"External I/O: network, filesystem, timers, promises."))
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "dom")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "dom")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"Browser DOM operations: create elements, set attributes, event listeners."))
|
||||
(tr
|
||||
(td :class "pr-4 py-1 font-mono text-xs" "render")
|
||||
(td (~tw :tokens "pr-4 py-1 font-mono text-xs") "render")
|
||||
(td
|
||||
:class "py-1"
|
||||
(~tw :tokens "py-1")
|
||||
"Rendering operations: component expansion, HTML generation, DOM patching.")))))
|
||||
(~docs/section
|
||||
:title "Why not phases?"
|
||||
@@ -145,7 +145,7 @@
|
||||
"The same expression can evaluate on the server or in the browser. The capability context determines which is possible, but the "
|
||||
(em "code")
|
||||
" is identical.")
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Server-side fetch")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Server-side fetch")
|
||||
(p
|
||||
"A component that fetches data during SSR. The server has "
|
||||
(code "io")
|
||||
@@ -160,7 +160,7 @@
|
||||
" annotation documents the requirement — if you try to call this in a "
|
||||
(code "pure")
|
||||
" context, it fails with a clear error.")
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Client-side fetch")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Client-side fetch")
|
||||
(p
|
||||
"An island that fetches after hydration. The browser has "
|
||||
(code "io")
|
||||
@@ -171,7 +171,7 @@
|
||||
"lisp"))
|
||||
(p
|
||||
"Same data, same UI, different evaluation context. The server version is faster (no roundtrip from browser). The client version is interactive (can refetch, react to user actions). The capability model lets the same language express both without separate 'server code' and 'client code' concepts.")
|
||||
(h4 :class "font-semibold mt-6 mb-2" "The same component, both ways")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "The same component, both ways")
|
||||
(p
|
||||
"With isomorphic evaluation, a single component can be SSR'd on first load (server provides "
|
||||
(code "io")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
(defcomp ~geography/eval-rules-content ()
|
||||
(~docs/page :title "Evaluation Rules"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Machine-readable SX semantics — a non-circular reference for how the language evaluates expressions.")
|
||||
|
||||
(~docs/section :title "Why rules as data" :id "why"
|
||||
@@ -9,22 +9,22 @@
|
||||
(~docs/code :src (str ";; Ask the MCP server to explain a form\nsx_explain name=\"let\"\n\nlet\n Category: special-form\n Pattern: (let ((name val) ...) body ...)\n Effects: (none)\n\nCreate new scope. Evaluate each val sequentially,\nbind name. Last body form is in tail position.\n\nExamples:\n (let ((x 1) (y 2)) (+ x y)) → 3")))
|
||||
|
||||
(~docs/section :title "Rule categories" :id "categories"
|
||||
(table :class "min-w-full text-sm mb-6"
|
||||
(table (~tw :tokens "min-w-full text-sm mb-6")
|
||||
(thead
|
||||
(tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Category")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Count")
|
||||
(th :class "text-left pb-2 font-semibold text-stone-700" "What it covers")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "literal") (td :class "pr-4 py-1" "6") (td :class "py-1" "Numbers, strings, booleans, nil, keywords, dicts"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "lookup") (td :class "pr-4 py-1" "1") (td :class "py-1" "Symbol resolution: env → primitives → error"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "special-form") (td :class "pr-4 py-1" "13") (td :class "py-1" "if, when, cond, case, let, letrec, lambda, define, set!, begin, quote, quasiquote, ->"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "definition") (td :class "pr-4 py-1" "3") (td :class "py-1" "defcomp, defisland, defmacro"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "higher-order") (td :class "pr-4 py-1" "6") (td :class "py-1" "map, filter, reduce, some, every?, for-each"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "scope") (td :class "pr-4 py-1" "5") (td :class "py-1" "scope, provide, context, emit!, emitted"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "continuation") (td :class "pr-4 py-1" "2") (td :class "py-1" "reset, shift (delimited continuations)"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "reactive") (td :class "pr-4 py-1" "1") (td :class "py-1" "deref (signal reading)"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "call") (td :class "pr-4 py-1" "1") (td :class "py-1" "General function call dispatch")))))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold text-stone-700") "Category")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold text-stone-700") "Count")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold text-stone-700") "What it covers")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "literal") (td (~tw :tokens "pr-4 py-1") "6") (td (~tw :tokens "py-1") "Numbers, strings, booleans, nil, keywords, dicts"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "lookup") (td (~tw :tokens "pr-4 py-1") "1") (td (~tw :tokens "py-1") "Symbol resolution: env → primitives → error"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "special-form") (td (~tw :tokens "pr-4 py-1") "13") (td (~tw :tokens "py-1") "if, when, cond, case, let, letrec, lambda, define, set!, begin, quote, quasiquote, ->"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "definition") (td (~tw :tokens "pr-4 py-1") "3") (td (~tw :tokens "py-1") "defcomp, defisland, defmacro"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "higher-order") (td (~tw :tokens "pr-4 py-1") "6") (td (~tw :tokens "py-1") "map, filter, reduce, some, every?, for-each"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "scope") (td (~tw :tokens "pr-4 py-1") "5") (td (~tw :tokens "py-1") "scope, provide, context, emit!, emitted"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "continuation") (td (~tw :tokens "pr-4 py-1") "2") (td (~tw :tokens "py-1") "reset, shift (delimited continuations)"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "reactive") (td (~tw :tokens "pr-4 py-1") "1") (td (~tw :tokens "py-1") "deref (signal reading)"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "call") (td (~tw :tokens "pr-4 py-1") "1") (td (~tw :tokens "py-1") "General function call dispatch")))))
|
||||
|
||||
(~docs/section :title "The sx_explain tool" :id "tool"
|
||||
(p "Query rules by name or category:")
|
||||
|
||||
@@ -2,63 +2,63 @@
|
||||
;; Describes the rendering pipeline: OCaml evaluator → wire formats → client
|
||||
|
||||
(defcomp ~geography/index-content () :affinity :server
|
||||
(div :class "max-w-4xl mx-auto px-6 pb-8 pt-4"
|
||||
(h2 :class "text-3xl font-bold text-stone-800 mb-4" "Geography")
|
||||
(p :class "text-lg text-stone-600 mb-8"
|
||||
(div (~tw :tokens "max-w-4xl mx-auto px-6 pb-8 pt-4")
|
||||
(h2 (~tw :tokens "text-3xl font-bold text-stone-800 mb-4") "Geography")
|
||||
(p (~tw :tokens "text-lg text-stone-600 mb-8")
|
||||
"Where code runs and how it gets there. Geography maps the rendering pipeline from server-side evaluation through wire formats to client-side hydration.")
|
||||
|
||||
;; Architecture diagram
|
||||
(div :class "bg-stone-50 border border-stone-200 rounded-lg p-6 mb-8"
|
||||
(h3 :class "text-xl font-semibold text-stone-700 mb-4" "Rendering Pipeline")
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "bg-stone-50 border border-stone-200 rounded-lg p-6 mb-8")
|
||||
(h3 (~tw :tokens "text-xl font-semibold text-stone-700 mb-4") "Rendering Pipeline")
|
||||
(div (~tw :tokens "space-y-4")
|
||||
;; Server
|
||||
(div :class "flex items-start gap-4"
|
||||
(div :class "w-28 shrink-0 font-mono text-sm font-semibold text-sky-700 bg-sky-50 rounded px-2 py-1 text-center" "OCaml kernel")
|
||||
(div (~tw :tokens "flex items-start gap-4")
|
||||
(div (~tw :tokens "w-28 shrink-0 font-mono text-sm font-semibold text-sky-700 bg-sky-50 rounded px-2 py-1 text-center") "OCaml kernel")
|
||||
(div
|
||||
(p :class "text-stone-700"
|
||||
(p (~tw :tokens "text-stone-700")
|
||||
"The evaluator is a CEK machine written in SX and bootstrapped to OCaml. It evaluates page definitions, expands components, resolves IO (helpers, queries), and serializes the result as SX wire format.")
|
||||
(p :class "text-sm text-stone-500 mt-1"
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1")
|
||||
"spec/evaluator.sx → hosts/ocaml/ → aser-slot with batch IO")))
|
||||
|
||||
;; Wire format
|
||||
(div :class "flex items-start gap-4"
|
||||
(div :class "w-28 shrink-0 font-mono text-sm font-semibold text-amber-700 bg-amber-50 rounded px-2 py-1 text-center" "Wire format")
|
||||
(div (~tw :tokens "flex items-start gap-4")
|
||||
(div (~tw :tokens "w-28 shrink-0 font-mono text-sm font-semibold text-amber-700 bg-amber-50 rounded px-2 py-1 text-center") "Wire format")
|
||||
(div
|
||||
(p :class "text-stone-700"
|
||||
(p (~tw :tokens "text-stone-700")
|
||||
"The aser (async-serialize) mode produces SX text — HTML tags and component calls serialized as s-expressions. Components with server affinity are expanded; client components stay as calls. The wire format is placed in a "
|
||||
(code :class "text-sm" "<script type=\"text/sx\">")
|
||||
(code (~tw :tokens "text-sm") "<script type=\"text/sx\">")
|
||||
" tag inside the HTML shell.")
|
||||
(p :class "text-sm text-stone-500 mt-1"
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1")
|
||||
"web/adapter-sx.sx → SxExpr values pass through serialize unquoted")))
|
||||
|
||||
;; Client
|
||||
(div :class "flex items-start gap-4"
|
||||
(div :class "w-28 shrink-0 font-mono text-sm font-semibold text-emerald-700 bg-emerald-50 rounded px-2 py-1 text-center" "sx-browser.js")
|
||||
(div (~tw :tokens "flex items-start gap-4")
|
||||
(div (~tw :tokens "w-28 shrink-0 font-mono text-sm font-semibold text-emerald-700 bg-emerald-50 rounded px-2 py-1 text-center") "sx-browser.js")
|
||||
(div
|
||||
(p :class "text-stone-700"
|
||||
(p (~tw :tokens "text-stone-700")
|
||||
"The client engine parses the SX wire format, evaluates component definitions, renders the DOM, and hydrates reactive islands. It includes the same CEK evaluator (transpiled from the spec), the parser, all web adapters, and the orchestration layer for fetch/swap/polling.")
|
||||
(p :class "text-sm text-stone-500 mt-1"
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1")
|
||||
"spec/ + web/ → hosts/javascript/cli.py → sx-browser.js (~400KB)")))))
|
||||
|
||||
;; What lives where
|
||||
(h3 :class "text-xl font-semibold text-stone-700 mb-4 mt-8" "What lives where")
|
||||
(h3 (~tw :tokens "text-xl font-semibold text-stone-700 mb-4 mt-8") "What lives where")
|
||||
|
||||
(div :class "grid md:grid-cols-2 gap-4 mb-8"
|
||||
(div (~tw :tokens "grid md:grid-cols-2 gap-4 mb-8")
|
||||
;; Spec
|
||||
(div :class "border border-stone-200 rounded-lg p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Spec (shared)")
|
||||
(p :class "text-sm text-stone-600 mb-2" "The canonical SX language, bootstrapped identically to OCaml, JavaScript, and Python:")
|
||||
(ul :class "text-sm text-stone-600 space-y-1 list-disc ml-4"
|
||||
(div (~tw :tokens "border border-stone-200 rounded-lg p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Spec (shared)")
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "The canonical SX language, bootstrapped identically to OCaml, JavaScript, and Python:")
|
||||
(ul (~tw :tokens "text-sm text-stone-600 space-y-1 list-disc ml-4")
|
||||
(li "CEK evaluator — frames, step function, call dispatch")
|
||||
(li "Parser — tokenizer, s-expression reader, serializer")
|
||||
(li "Primitives — ~80 built-in pure functions")
|
||||
(li "Render modes — HTML, SX wire, DOM")))
|
||||
|
||||
;; Web adapters
|
||||
(div :class "border border-stone-200 rounded-lg p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Web Adapters")
|
||||
(p :class "text-sm text-stone-600 mb-2" "SX-defined modules that run on both server and client:")
|
||||
(ul :class "text-sm text-stone-600 space-y-1 list-disc ml-4"
|
||||
(div (~tw :tokens "border border-stone-200 rounded-lg p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Web Adapters")
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "SX-defined modules that run on both server and client:")
|
||||
(ul (~tw :tokens "text-sm text-stone-600 space-y-1 list-disc ml-4")
|
||||
(li "adapter-sx.sx — aser wire format (server component expansion)")
|
||||
(li "adapter-html.sx — server HTML rendering")
|
||||
(li "adapter-dom.sx — client DOM rendering")
|
||||
@@ -66,20 +66,20 @@
|
||||
(li "engine.sx — trigger parsing, request building")))
|
||||
|
||||
;; OCaml kernel
|
||||
(div :class "border border-stone-200 rounded-lg p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "OCaml Kernel (server)")
|
||||
(p :class "text-sm text-stone-600 mb-2" "Persistent process connected via a binary pipe protocol:")
|
||||
(ul :class "text-sm text-stone-600 space-y-1 list-disc ml-4"
|
||||
(div (~tw :tokens "border border-stone-200 rounded-lg p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "OCaml Kernel (server)")
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "Persistent process connected via a binary pipe protocol:")
|
||||
(ul (~tw :tokens "text-sm text-stone-600 space-y-1 list-disc ml-4")
|
||||
(li "CEK evaluator + VM bytecode compiler")
|
||||
(li "Batch IO bridge — defers helper/query calls to Python")
|
||||
(li "Length-prefixed blob protocol — no string escaping")
|
||||
(li "Component hot-reload on .sx file changes")))
|
||||
|
||||
;; sx-browser.js
|
||||
(div :class "border border-stone-200 rounded-lg p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "sx-browser.js (client)")
|
||||
(p :class "text-sm text-stone-600 mb-2" "Single JS bundle transpiled from spec + web adapters:")
|
||||
(ul :class "text-sm text-stone-600 space-y-1 list-disc ml-4"
|
||||
(div (~tw :tokens "border border-stone-200 rounded-lg p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "sx-browser.js (client)")
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "Single JS bundle transpiled from spec + web adapters:")
|
||||
(ul (~tw :tokens "text-sm text-stone-600 space-y-1 list-disc ml-4")
|
||||
(li "Parses SX wire format from script tags")
|
||||
(li "Renders component trees to DOM")
|
||||
(li "Hydrates reactive islands (signals, effects)")
|
||||
@@ -87,62 +87,62 @@
|
||||
(li "HTMX-like fetch/swap orchestration"))))
|
||||
|
||||
;; Rendering modes
|
||||
(h3 :class "text-xl font-semibold text-stone-700 mb-4 mt-8" "Rendering Modes")
|
||||
(h3 (~tw :tokens "text-xl font-semibold text-stone-700 mb-4 mt-8") "Rendering Modes")
|
||||
|
||||
(div :class "overflow-x-auto mb-8"
|
||||
(table :class "w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-8")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "px-3 py-2 text-left font-medium text-stone-600" "Mode")
|
||||
(th :class "px-3 py-2 text-left font-medium text-stone-600" "Runs on")
|
||||
(th :class "px-3 py-2 text-left font-medium text-stone-600" "Components")
|
||||
(th :class "px-3 py-2 text-left font-medium text-stone-600" "Output")))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "px-3 py-2 text-left font-medium text-stone-600") "Mode")
|
||||
(th (~tw :tokens "px-3 py-2 text-left font-medium text-stone-600") "Runs on")
|
||||
(th (~tw :tokens "px-3 py-2 text-left font-medium text-stone-600") "Components")
|
||||
(th (~tw :tokens "px-3 py-2 text-left font-medium text-stone-600") "Output")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" "render-to-html")
|
||||
(td :class "px-3 py-2" "Server (OCaml)")
|
||||
(td :class "px-3 py-2" "Expanded recursively")
|
||||
(td :class "px-3 py-2" "HTML string"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" "aser / aser-slot")
|
||||
(td :class "px-3 py-2" "Server (OCaml)")
|
||||
(td :class "px-3 py-2" "Server-affinity expanded; client preserved")
|
||||
(td :class "px-3 py-2" "SX wire format"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" "render-to-dom")
|
||||
(td :class "px-3 py-2" "Client (sx-browser.js)")
|
||||
(td :class "px-3 py-2" "Expanded recursively")
|
||||
(td :class "px-3 py-2" "DOM nodes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono" "client routing")
|
||||
(td :class "px-3 py-2" "Client (sx-browser.js)")
|
||||
(td :class "px-3 py-2" "defpage content evaluated locally")
|
||||
(td :class "px-3 py-2" "DOM swap")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") "render-to-html")
|
||||
(td (~tw :tokens "px-3 py-2") "Server (OCaml)")
|
||||
(td (~tw :tokens "px-3 py-2") "Expanded recursively")
|
||||
(td (~tw :tokens "px-3 py-2") "HTML string"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") "aser / aser-slot")
|
||||
(td (~tw :tokens "px-3 py-2") "Server (OCaml)")
|
||||
(td (~tw :tokens "px-3 py-2") "Server-affinity expanded; client preserved")
|
||||
(td (~tw :tokens "px-3 py-2") "SX wire format"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") "render-to-dom")
|
||||
(td (~tw :tokens "px-3 py-2") "Client (sx-browser.js)")
|
||||
(td (~tw :tokens "px-3 py-2") "Expanded recursively")
|
||||
(td (~tw :tokens "px-3 py-2") "DOM nodes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono") "client routing")
|
||||
(td (~tw :tokens "px-3 py-2") "Client (sx-browser.js)")
|
||||
(td (~tw :tokens "px-3 py-2") "defpage content evaluated locally")
|
||||
(td (~tw :tokens "px-3 py-2") "DOM swap")))))
|
||||
|
||||
;; Topics
|
||||
(h3 :class "text-xl font-semibold text-stone-700 mb-4 mt-8" "Topics")
|
||||
(h3 (~tw :tokens "text-xl font-semibold text-stone-700 mb-4 mt-8") "Topics")
|
||||
|
||||
(div :class "grid md:grid-cols-2 gap-4"
|
||||
(a :href "/sx/(geography.(hypermedia))" :class "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors"
|
||||
(h4 :class "font-semibold text-stone-700" "Hypermedia Lakes")
|
||||
(p :class "text-sm text-stone-500" "Server-driven UI with sx-get/post/put/delete — fetch, swap, and the request lifecycle."))
|
||||
(div (~tw :tokens "grid md:grid-cols-2 gap-4")
|
||||
(a :href "/sx/(geography.(hypermedia))" (~tw :tokens "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Hypermedia Lakes")
|
||||
(p (~tw :tokens "text-sm text-stone-500") "Server-driven UI with sx-get/post/put/delete — fetch, swap, and the request lifecycle."))
|
||||
|
||||
(a :href "/sx/(geography.(reactive))" :class "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors"
|
||||
(h4 :class "font-semibold text-stone-700" "Reactive Islands")
|
||||
(p :class "text-sm text-stone-500" "Client-side signals and effects hydrated from server-rendered HTML. defisland, deref, lakes."))
|
||||
(a :href "/sx/(geography.(reactive))" (~tw :tokens "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Reactive Islands")
|
||||
(p (~tw :tokens "text-sm text-stone-500") "Client-side signals and effects hydrated from server-rendered HTML. defisland, deref, lakes."))
|
||||
|
||||
(a :href "/sx/(geography.(marshes))" :class "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors"
|
||||
(h4 :class "font-semibold text-stone-700" "Marshes")
|
||||
(p :class "text-sm text-stone-500" "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive views reshape server content."))
|
||||
(a :href "/sx/(geography.(marshes))" (~tw :tokens "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Marshes")
|
||||
(p (~tw :tokens "text-sm text-stone-500") "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive views reshape server content."))
|
||||
|
||||
(a :href "/sx/(geography.(scopes))" :class "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors"
|
||||
(h4 :class "font-semibold text-stone-700" "Scopes")
|
||||
(p :class "text-sm text-stone-500" "Render-time dynamic scope — the primitive beneath provide, collect!, spreads, and islands."))
|
||||
(a :href "/sx/(geography.(scopes))" (~tw :tokens "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Scopes")
|
||||
(p (~tw :tokens "text-sm text-stone-500") "Render-time dynamic scope — the primitive beneath provide, collect!, spreads, and islands."))
|
||||
|
||||
(a :href "/sx/(geography.(cek))" :class "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors"
|
||||
(h4 :class "font-semibold text-stone-700" "CEK Machine")
|
||||
(p :class "text-sm text-stone-500" "The evaluator internals — frames, continuations, tail-call optimization, and the VM bytecode compiler."))
|
||||
(a :href "/sx/(geography.(cek))" (~tw :tokens "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "CEK Machine")
|
||||
(p (~tw :tokens "text-sm text-stone-500") "The evaluator internals — frames, continuations, tail-call optimization, and the VM bytecode compiler."))
|
||||
|
||||
(a :href "/sx/(geography.(isomorphism))" :class "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors"
|
||||
(h4 :class "font-semibold text-stone-700" "Isomorphism")
|
||||
(p :class "text-sm text-stone-500" "One spec, multiple hosts — how the same SX code runs on OCaml, JavaScript, and Python.")))))
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "block border border-stone-200 rounded-lg p-4 hover:border-sky-300 hover:bg-sky-50 transition-colors")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Isomorphism")
|
||||
(p (~tw :tokens "text-sm text-stone-500") "One spec, multiple hosts — how the same SX code runs on OCaml, JavaScript, and Python.")))))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(defcomp ~geography/modules-content ()
|
||||
(~docs/page :title "Modules"
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Declaring what a file needs — for documentation, static analysis, and tooling.")
|
||||
|
||||
(~docs/section :title "The use form" :id "use-form"
|
||||
@@ -9,22 +9,22 @@
|
||||
(p (code "use") " is purely declarative — it doesn't load anything. The glob loader still loads all " (code ".sx") " files. The declaration documents intent and enables static checking."))
|
||||
|
||||
(~docs/section :title "What use enables" :id "what-it-enables"
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Dependency visibility")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Dependency visibility")
|
||||
(p "The " (code "sx_deps") " tool reports both referenced symbols and declared modules. If a file references " (code "resource") " but doesn't " (code "(use web-signals)") ", the tool flags the gap.")
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Build pipeline validation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Build pipeline validation")
|
||||
(p "The " (code "sx_build_manifest") " tool shows which modules are in the current build. Cross-referencing with " (code "use") " declarations catches missing build modules — exactly the bug that caused the " (code "resource") " island hydration failure.")
|
||||
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Documentation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mt-6 mb-2") "Documentation")
|
||||
(p "For humans and LLMs reading a file: " (code "use") " declarations immediately show what the file depends on, without grep."))
|
||||
|
||||
(~docs/section :title "Semantics" :id "semantics"
|
||||
(p (code "(use name)") " is a no-op at evaluation time. It does not:")
|
||||
(ul :class "space-y-1 text-stone-600 ml-4"
|
||||
(ul (~tw :tokens "space-y-1 text-stone-600 ml-4")
|
||||
(li "Load any files")
|
||||
(li "Modify the environment")
|
||||
(li "Affect evaluation order")
|
||||
(li "Create any runtime cost"))
|
||||
(p "It " (em "does") ":")
|
||||
(ul :class "space-y-1 text-stone-600 ml-4"
|
||||
(ul (~tw :tokens "space-y-1 text-stone-600 ml-4")
|
||||
(li "Appear in the parsed tree for static analysis")
|
||||
(li "Get reported by " (code "sx_deps"))
|
||||
(li "Enable future tooling (unused-import warnings, auto-import suggestions)")))))
|
||||
|
||||
@@ -178,13 +178,13 @@
|
||||
(let
|
||||
((now (helper "now" "%H:%M:%S")))
|
||||
(<>
|
||||
(p :class "text-emerald-600 font-medium" "Box A updated!")
|
||||
(p :class "text-sm text-stone-500" (str "at " now))
|
||||
(p (~tw :tokens "text-emerald-600 font-medium") "Box A updated!")
|
||||
(p (~tw :tokens "text-sm text-stone-500") (str "at " now))
|
||||
(div
|
||||
:id "oob-box-b"
|
||||
:sx-swap-oob "innerHTML"
|
||||
(p :class "text-violet-600 font-medium" "Box B updated via OOB!")
|
||||
(p :class "text-sm text-stone-500" (str "at " now)))
|
||||
(p (~tw :tokens "text-violet-600 font-medium") "Box B updated via OOB!")
|
||||
(p (~tw :tokens "text-sm text-stone-500") (str "at " now)))
|
||||
(~docs/oob-code
|
||||
:target-id "oob-wire"
|
||||
:text (str
|
||||
@@ -222,7 +222,7 @@
|
||||
(fn
|
||||
(i)
|
||||
(div
|
||||
:class "px-4 py-3 border-b border-stone-100 text-sm text-stone-700"
|
||||
(~tw :tokens "px-4 py-3 border-b border-stone-100 text-sm text-stone-700")
|
||||
(str "Item " i " — loaded from page " page)))
|
||||
(range start (+ start 5)))
|
||||
(if
|
||||
@@ -235,10 +235,10 @@
|
||||
:sx-trigger "intersect once"
|
||||
:sx-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
:class "p-3 text-center text-stone-400 text-sm"
|
||||
(~tw :tokens "p-3 text-center text-stone-400 text-sm")
|
||||
"Loading more...")
|
||||
(div
|
||||
:class "p-3 text-center text-stone-500 text-sm font-medium"
|
||||
(~tw :tokens "p-3 text-center text-stone-500 text-sm font-medium")
|
||||
"All items loaded."))
|
||||
(~docs/oob-code
|
||||
:target-id "scroll-wire"
|
||||
@@ -373,9 +373,9 @@
|
||||
((email (helper "request-form" "email" "")))
|
||||
(if
|
||||
(or (= email "") (not (contains? email "@")))
|
||||
(p :class "text-sm text-rose-600 mt-2" "Please enter a valid email.")
|
||||
(p (~tw :tokens "text-sm text-rose-600 mt-2") "Please enter a valid email.")
|
||||
(p
|
||||
:class "text-sm text-emerald-600 mt-2"
|
||||
(~tw :tokens "text-sm text-emerald-600 mt-2")
|
||||
(str "Form submitted with: " email)))))
|
||||
|
||||
(defhandler
|
||||
@@ -536,12 +536,12 @@
|
||||
(helper "state-set!" "ex-swap-n" n)
|
||||
(<>
|
||||
(div
|
||||
:class "px-3 py-2 text-sm text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-sm text-stone-700")
|
||||
(str "[" now "] " mode " (#" n ")"))
|
||||
(span
|
||||
:id "swap-counter"
|
||||
:sx-swap-oob "innerHTML"
|
||||
:class "self-center text-sm text-stone-500"
|
||||
(~tw :tokens "self-center text-sm text-stone-500")
|
||||
(str "Count: " n))
|
||||
(~docs/oob-code
|
||||
:target-id "swap-wire"
|
||||
@@ -558,28 +558,28 @@
|
||||
(<>
|
||||
(div
|
||||
:id "dash-header"
|
||||
:class "p-3 bg-violet-50 rounded mb-3"
|
||||
(h4 :class "font-semibold text-violet-800" "Dashboard Header")
|
||||
(p :class "text-sm text-violet-600" (str "Generated at " now)))
|
||||
(~tw :tokens "p-3 bg-violet-50 rounded mb-3")
|
||||
(h4 (~tw :tokens "font-semibold text-violet-800") "Dashboard Header")
|
||||
(p (~tw :tokens "text-sm text-violet-600") (str "Generated at " now)))
|
||||
(div
|
||||
:id "dash-stats"
|
||||
:class "grid grid-cols-3 gap-3 mb-3"
|
||||
(~tw :tokens "grid grid-cols-3 gap-3 mb-3")
|
||||
(div
|
||||
:class "p-3 bg-emerald-50 rounded text-center"
|
||||
(p :class "text-2xl font-bold text-emerald-700" "142")
|
||||
(p :class "text-xs text-emerald-600" "Users"))
|
||||
(~tw :tokens "p-3 bg-emerald-50 rounded text-center")
|
||||
(p (~tw :tokens "text-2xl font-bold text-emerald-700") "142")
|
||||
(p (~tw :tokens "text-xs text-emerald-600") "Users"))
|
||||
(div
|
||||
:class "p-3 bg-blue-50 rounded text-center"
|
||||
(p :class "text-2xl font-bold text-blue-700" "89")
|
||||
(p :class "text-xs text-blue-600" "Orders"))
|
||||
(~tw :tokens "p-3 bg-blue-50 rounded text-center")
|
||||
(p (~tw :tokens "text-2xl font-bold text-blue-700") "89")
|
||||
(p (~tw :tokens "text-xs text-blue-600") "Orders"))
|
||||
(div
|
||||
:class "p-3 bg-amber-50 rounded text-center"
|
||||
(p :class "text-2xl font-bold text-amber-700" "$4.2k")
|
||||
(p :class "text-xs text-amber-600" "Revenue")))
|
||||
(~tw :tokens "p-3 bg-amber-50 rounded text-center")
|
||||
(p (~tw :tokens "text-2xl font-bold text-amber-700") "$4.2k")
|
||||
(p (~tw :tokens "text-xs text-amber-600") "Revenue")))
|
||||
(div
|
||||
:id "dash-footer"
|
||||
:class "p-3 bg-stone-50 rounded"
|
||||
(p :class "text-sm text-stone-500" (str "Last updated: " now)))
|
||||
(~tw :tokens "p-3 bg-stone-50 rounded")
|
||||
(p (~tw :tokens "text-sm text-stone-500") (str "Last updated: " now)))
|
||||
(~docs/oob-code
|
||||
:target-id "filter-wire"
|
||||
:text (str
|
||||
@@ -598,7 +598,7 @@
|
||||
(div
|
||||
:id "tab-buttons"
|
||||
:sx-swap-oob "innerHTML"
|
||||
:class "flex border-b border-stone-200"
|
||||
(~tw :tokens "flex border-b border-stone-200")
|
||||
(~examples/tab-btn
|
||||
:tab "tab1"
|
||||
:label "Overview"
|
||||
|
||||
@@ -45,11 +45,11 @@
|
||||
(filter (fn (item) (string-contains? (lower item) (lower query)))
|
||||
items))))
|
||||
(let ((shown (slice matches 0 3)))
|
||||
(div :class "space-y-1"
|
||||
(div (~tw :tokens "space-y-1")
|
||||
(p :class (str "text-xs font-semibold uppercase " color) label)
|
||||
(ul :class "list-disc pl-4"
|
||||
(map (fn (m) (li :class "text-sm text-stone-600" m)) shown))
|
||||
(p :class "text-xs text-stone-400"
|
||||
(ul (~tw :tokens "list-disc pl-4")
|
||||
(map (fn (m) (li (~tw :tokens "text-sm text-stone-600") m)) shown))
|
||||
(p (~tw :tokens "text-xs text-stone-400")
|
||||
(str (length matches) " result(s)"))))))
|
||||
|
||||
|
||||
@@ -66,10 +66,10 @@
|
||||
(now (helper "now" "%H:%M:%S")))
|
||||
(let ((price (nth flash-sale-prices idx)))
|
||||
(<>
|
||||
(div :class "space-y-2"
|
||||
(p :class "text-sm text-emerald-600 font-medium"
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(p (~tw :tokens "text-sm text-emerald-600 font-medium")
|
||||
(str "⚡ Flash sale! Price: $" price))
|
||||
(p :class "text-xs text-stone-400" (str "at " now)))
|
||||
(p (~tw :tokens "text-xs text-stone-400") (str "at " now)))
|
||||
(script :type "text/sx" :data-init ""
|
||||
(str "(reset! (use-store \"demo-price\") " price ")"))))))
|
||||
|
||||
@@ -86,9 +86,9 @@
|
||||
(let ((idx (random-int 0 (- (length settle-items) 1)))
|
||||
(now (helper "now" "%H:%M:%S")))
|
||||
(let ((item (nth settle-items idx)))
|
||||
(div :class "space-y-1"
|
||||
(p :class "text-sm font-medium text-stone-700" (str "Fetched: " item))
|
||||
(p :class "text-xs text-stone-400" (str "at " now))))))
|
||||
(div (~tw :tokens "space-y-1")
|
||||
(p (~tw :tokens "text-sm font-medium text-stone-700") (str "Fetched: " item))
|
||||
(p (~tw :tokens "text-xs text-stone-400") (str "at " now))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -160,7 +160,7 @@
|
||||
"" rotated)
|
||||
")")))
|
||||
(<>
|
||||
(p :class "text-sm text-emerald-600 font-medium"
|
||||
(p (~tw :tokens "text-sm text-emerald-600 font-medium")
|
||||
(str "Catalog loaded: " (length rotated) " items (shuffled at " now ")"))
|
||||
(script :type "text/sx" :data-init ""
|
||||
(str "(reset! (use-store \"catalog-items\") " items-sx ")")))))))
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
(&key)
|
||||
(let ((now (helper "now" "%H:%M:%S")))
|
||||
(<>
|
||||
(span :class "text-stone-800 text-sm" "Server time: " (strong now))
|
||||
(span (~tw :tokens "text-stone-800 text-sm") "Server time: " (strong now))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-get"
|
||||
:text (str "(span :class \"text-stone-800 text-sm\" \"Server time: \" (strong \"" now "\"))")))))
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
(&key)
|
||||
(let ((name (helper "request-form" "name" "stranger")))
|
||||
(<>
|
||||
(span :class "text-stone-800 text-sm" "Hello, " (strong name) "!")
|
||||
(span (~tw :tokens "text-stone-800 text-sm") "Hello, " (strong name) "!")
|
||||
(~docs/oob-code :target-id "ref-wire-sx-post"
|
||||
:text (str "(span :class \"text-stone-800 text-sm\" \"Hello, \" (strong \"" name "\") \"!\")")))))
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
(&key)
|
||||
(let ((status (helper "request-form" "status" "unknown")))
|
||||
(<>
|
||||
(span :class "text-stone-700 text-sm" "Status: " (strong status) " — updated via PUT")
|
||||
(span (~tw :tokens "text-stone-700 text-sm") "Status: " (strong status) " — updated via PUT")
|
||||
(~docs/oob-code :target-id "ref-wire-sx-put"
|
||||
:text (str "(span :class \"text-stone-700 text-sm\" \"Status: \" (strong \"" status "\") \" — updated via PUT\")")))))
|
||||
|
||||
@@ -82,8 +82,8 @@
|
||||
(str "(span :class \"text-stone-800 text-sm\" \"Results for: \" (strong \"" q "\"))"))))
|
||||
(<>
|
||||
(if (= q "")
|
||||
(span :class "text-stone-400 text-sm" "Start typing to trigger a search.")
|
||||
(span :class "text-stone-800 text-sm" "Results for: " (strong q)))
|
||||
(span (~tw :tokens "text-stone-400 text-sm") "Start typing to trigger a search.")
|
||||
(span (~tw :tokens "text-stone-800 text-sm") "Results for: " (strong q)))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-trigger" :text sx-text)))))
|
||||
|
||||
;; --- sx-swap demo ---
|
||||
@@ -95,7 +95,7 @@
|
||||
(&key)
|
||||
(let ((now (helper "now" "%H:%M:%S")))
|
||||
(<>
|
||||
(div :class "text-sm text-violet-700" (str "New item (" now ")"))
|
||||
(div (~tw :tokens "text-sm text-violet-700") (str "New item (" now ")"))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-swap"
|
||||
:text (str "(div :class \"text-sm text-violet-700\" \"New item (" now ")\")")))))
|
||||
|
||||
@@ -108,9 +108,9 @@
|
||||
(&key)
|
||||
(let ((now (helper "now" "%H:%M:%S")))
|
||||
(<>
|
||||
(span :class "text-emerald-700 text-sm" "Main updated at " now)
|
||||
(span (~tw :tokens "text-emerald-700 text-sm") "Main updated at " now)
|
||||
(div :id "ref-oob-side" :sx-swap-oob "innerHTML"
|
||||
(span :class "text-violet-700 text-sm" "OOB updated at " now))
|
||||
(span (~tw :tokens "text-violet-700 text-sm") "OOB updated at " now))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-swap-oob"
|
||||
:text (str "(<> (span ... \"" now "\") (div :id \"ref-oob-side\" :sx-swap-oob \"innerHTML\" ...))")))))
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
(<>
|
||||
(div :id "the-header" (h3 "Page header — not selected"))
|
||||
(div :id "the-content"
|
||||
(span :class "text-emerald-700 text-sm"
|
||||
(span (~tw :tokens "text-emerald-700 text-sm")
|
||||
"This fragment was selected from a larger response. Time: " now))
|
||||
(div :id "the-footer" (p "Page footer — not selected"))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-select"
|
||||
@@ -141,7 +141,7 @@
|
||||
(let ((q (helper "request-arg" "q" "")))
|
||||
(sleep 800)
|
||||
(<>
|
||||
(span :class "text-stone-800 text-sm" "Echo: " (strong q))
|
||||
(span (~tw :tokens "text-stone-800 text-sm") "Echo: " (strong q))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-sync"
|
||||
:text (str "(span :class \"text-stone-800 text-sm\" \"Echo: \" (strong \"" q "\"))")))))
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
(&key)
|
||||
(let ((name (helper "request-header" "SX-Prompt" "anonymous")))
|
||||
(<>
|
||||
(span :class "text-stone-800 text-sm" "Hello, " (strong name) "!")
|
||||
(span (~tw :tokens "text-stone-800 text-sm") "Hello, " (strong name) "!")
|
||||
(~docs/oob-code :target-id "ref-wire-sx-prompt"
|
||||
:text (str "(span :class \"text-stone-800 text-sm\" \"Hello, \" (strong \"" name "\") \"!\")")))))
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
(let ((display (if (nil? name) "(no file)" name)))
|
||||
(let ((sx-text (str "(span :class \"text-stone-800 text-sm\" \"Received: \" (strong \"" display "\"))")))
|
||||
(<>
|
||||
(span :class "text-stone-800 text-sm" "Received: " (strong display))
|
||||
(span (~tw :tokens "text-stone-800 text-sm") "Received: " (strong display))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-encoding" :text sx-text))))))
|
||||
|
||||
;; --- sx-headers demo: echo custom headers ---
|
||||
@@ -206,8 +206,8 @@
|
||||
")"))))
|
||||
(<>
|
||||
(if (empty? custom)
|
||||
(span :class "text-stone-400 text-sm" "No custom headers received.")
|
||||
(ul :class "text-sm text-stone-700 space-y-1"
|
||||
(span (~tw :tokens "text-stone-400 text-sm") "No custom headers received.")
|
||||
(ul (~tw :tokens "text-sm text-stone-700 space-y-1")
|
||||
(map (fn (pair) (li (strong (first pair)) ": " (nth pair 1))) custom)))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-headers" :text sx-text))))))
|
||||
|
||||
@@ -227,8 +227,8 @@
|
||||
")"))))
|
||||
(<>
|
||||
(if (empty? vals)
|
||||
(span :class "text-stone-400 text-sm" "No values received.")
|
||||
(ul :class "text-sm text-stone-700 space-y-1"
|
||||
(span (~tw :tokens "text-stone-400 text-sm") "No values received.")
|
||||
(ul (~tw :tokens "text-sm text-stone-700 space-y-1")
|
||||
(map (fn (pair) (li (strong (first pair)) ": " (nth pair 1))) vals)))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-include" :text sx-text)))))
|
||||
|
||||
@@ -249,8 +249,8 @@
|
||||
")"))))
|
||||
(<>
|
||||
(if (empty? vals)
|
||||
(span :class "text-stone-400 text-sm" "No values received.")
|
||||
(ul :class "text-sm text-stone-700 space-y-1"
|
||||
(span (~tw :tokens "text-stone-400 text-sm") "No values received.")
|
||||
(ul (~tw :tokens "text-sm text-stone-700 space-y-1")
|
||||
(map (fn (pair) (li (strong (first pair)) ": " (nth pair 1))) vals)))
|
||||
(~docs/oob-code :target-id "ref-wire-sx-vals" :text sx-text)))))
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
"")
|
||||
(let ((sx-text (str "(span :class \"text-emerald-700 text-sm\" \"Success on attempt \" \"" n "\" \"!\")")))
|
||||
(<>
|
||||
(span :class "text-emerald-700 text-sm" "Success on attempt " (str n) "!")
|
||||
(span (~tw :tokens "text-emerald-700 text-sm") "Success on attempt " (str n) "!")
|
||||
(~docs/oob-code :target-id "ref-wire-sx-retry" :text sx-text)))))))
|
||||
|
||||
;; --- sx-trigger-event demo: response header triggers ---
|
||||
@@ -283,7 +283,7 @@
|
||||
(let ((now (helper "now" "%H:%M:%S")))
|
||||
(set-response-header "SX-Trigger" "showNotice")
|
||||
(<>
|
||||
(span :class "text-stone-800 text-sm" "Loaded at " (strong now) " — check the border!"))))
|
||||
(span (~tw :tokens "text-stone-800 text-sm") "Loaded at " (strong now) " — check the border!"))))
|
||||
|
||||
;; --- sx-retarget demo: response header retargets ---
|
||||
|
||||
@@ -295,4 +295,4 @@
|
||||
(let ((now (helper "now" "%H:%M:%S")))
|
||||
(set-response-header "SX-Retarget" "#ref-hdr-retarget-alt")
|
||||
(<>
|
||||
(span :class "text-violet-700 text-sm" "Retargeted at " (strong now)))))
|
||||
(span (~tw :tokens "text-violet-700 text-sm") "Retargeted at " (strong now)))))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
(if
|
||||
(or (= filename "") (= def-name ""))
|
||||
(div
|
||||
:class "text-sm text-stone-400 p-2"
|
||||
(~tw :tokens "text-sm text-stone-400 p-2")
|
||||
"Missing file or name parameter")
|
||||
(let
|
||||
((d (spec-explore-define filename def-name)))
|
||||
@@ -18,5 +18,5 @@
|
||||
d
|
||||
(~specs-explorer/spec-explorer-define-detail :d d :filename filename)
|
||||
(div
|
||||
:class "text-sm text-stone-400 p-2"
|
||||
(~tw :tokens "text-sm text-stone-400 p-2")
|
||||
(str "Definition '" def-name "' not found in " filename)))))))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
(current-family
|
||||
(computed (fn () (nth families (mod (deref idx) (len families)))))))
|
||||
(div
|
||||
(~cssx/tw :tokens "block max-w-3xl mx-auto px-4 pt-8 pb-4 text-center")
|
||||
(~tw :tokens "block max-w-3xl mx-auto px-4 pt-8 pb-4 text-center")
|
||||
(a
|
||||
:href "/sx/"
|
||||
:sx-get "/sx/"
|
||||
@@ -18,18 +18,18 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-push-url "true"
|
||||
(~cssx/tw :tokens "block no-underline")
|
||||
(~tw :tokens "block no-underline")
|
||||
(lake
|
||||
:id "logo"
|
||||
(span
|
||||
(~cssx/tw
|
||||
(~tw
|
||||
:tokens "block mb-2 text-violet-699 text-4xl font-bold font-mono")
|
||||
"(<sx>)")))
|
||||
(p
|
||||
(~cssx/tw :tokens "mb-1 text-stone-500 text-lg")
|
||||
(~tw :tokens "mb-1 text-stone-500 text-lg")
|
||||
"The framework-free "
|
||||
(span
|
||||
(~cssx/tw :tokens "font-bold")
|
||||
(~tw :tokens "font-bold")
|
||||
:style (str
|
||||
"color:"
|
||||
(colour (deref current-family) (deref shade))
|
||||
@@ -47,12 +47,12 @@
|
||||
(lake
|
||||
:id "copyright"
|
||||
(p
|
||||
(~cssx/tw :tokens "text-stone-400 text-xs")
|
||||
(~tw :tokens "text-stone-400 text-xs")
|
||||
"© Giles Bradshaw 2026"
|
||||
(when
|
||||
path
|
||||
(span
|
||||
(~cssx/tw :tokens "text-stone-300 text-xs")
|
||||
(~tw :tokens "text-stone-300 text-xs")
|
||||
:style "margin-left:0.5em;"
|
||||
(str "· " path))))))))
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
(prev-node (nth sibs prev-idx))
|
||||
(next-node (nth sibs next-idx)))
|
||||
(div
|
||||
:class "w-full max-w-3xl mx-auto px-4 py-2 grid grid-cols-3 items-center"
|
||||
(~tw
|
||||
:tokens "w-full max-w-3xl mx-auto px-4 py-2 grid grid-cols-3 items-center")
|
||||
:style (str "opacity:" row-opacity ";transition:opacity 0.3s;")
|
||||
(a
|
||||
:href (get prev-node "href")
|
||||
@@ -86,7 +87,7 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-right min-w-0 truncate"
|
||||
(~tw :tokens "text-right min-w-0 truncate")
|
||||
:style (tw "text-stone-500 text-sm")
|
||||
(str "← " (get prev-node "label")))
|
||||
(a
|
||||
@@ -96,7 +97,7 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-center min-w-0 truncate px-1"
|
||||
(~tw :tokens "text-center min-w-0 truncate px-1")
|
||||
:style (if
|
||||
is-leaf
|
||||
(tw "text-violet-700 text-2xl font-bold")
|
||||
@@ -109,7 +110,7 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-left min-w-0 truncate"
|
||||
(~tw :tokens "text-left min-w-0 truncate")
|
||||
:style (tw "text-stone-500 text-sm")
|
||||
(str (get next-node "label") " →")))))))
|
||||
|
||||
@@ -118,9 +119,9 @@
|
||||
(&key items)
|
||||
:affinity :server
|
||||
(div
|
||||
:class "max-w-3xl mx-auto px-4 py-3"
|
||||
(~tw :tokens "max-w-3xl mx-auto px-4 py-3")
|
||||
(div
|
||||
:class "flex flex-wrap justify-center gap-2"
|
||||
(~tw :tokens "flex flex-wrap justify-center gap-2")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
@@ -131,7 +132,7 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "px-3 py-1.5 rounded border transition-colors"
|
||||
(~tw :tokens "px-3 py-1.5 rounded border transition-colors")
|
||||
:style (tw "text-violet-700 text-sm border-violet-200")
|
||||
(get item "label")))
|
||||
items))))
|
||||
@@ -148,7 +149,7 @@
|
||||
(<>
|
||||
(div
|
||||
:id "sx-nav"
|
||||
:class "mb-6"
|
||||
(~tw :tokens "mb-6")
|
||||
:sx-swap-oob "innerHTML"
|
||||
(div
|
||||
:id "logo-opacity"
|
||||
@@ -171,8 +172,7 @@
|
||||
(when
|
||||
(get nav-state "children")
|
||||
(~layouts/nav-children :items (get nav-state "children"))))
|
||||
(div :id "sx-content" (error-boundary children))
|
||||
(~cssx/flush))))
|
||||
(div :id "sx-content" (error-boundary children) (~tw/flush)))))
|
||||
|
||||
(defcomp ~layouts/docs-layout-full () nil)
|
||||
|
||||
|
||||
@@ -22,57 +22,57 @@
|
||||
(p
|
||||
"The hard parts of a web browser are HTML parsing (~500K lines in Blink), CSS layout (~800K lines), JavaScript execution (~2M lines in V8), and 2,000+ Web APIs. SX needs none of them. Components declare their structure as s-expressions. Layout is a pure function of the component tree. Painting is a walk over positioned boxes.")
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Web browser")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SX native")))
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Web browser")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "SX native")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Parse")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Parse")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"HTML parser (~500K lines)")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX parser (225 lines)"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX parser (225 lines)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Evaluate")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Evaluate")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"V8 JavaScript engine (~2M lines)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"CEK evaluator + bytecode VM (~1,300 lines)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Layout")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Layout")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"CSS cascade + flexbox + grid (~800K lines)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Flexbox subset (~250 lines)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Paint")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Paint")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Composited layer tree + GPU raster")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Cairo 2D draw calls (~150 lines)"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700 font-semibold" "Total")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700 font-semibold") "Total")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700 font-semibold"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700 font-semibold")
|
||||
"~35 million lines")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700 font-semibold"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700 font-semibold")
|
||||
"~5,000 lines"))))))
|
||||
(~docs/section
|
||||
:title "Architecture"
|
||||
@@ -109,7 +109,7 @@
|
||||
". The layout engine is SX functions. The style parser is SX pattern matching. The event dispatcher is SX closures. The render tree is SX data. Only the fifteen leaf primitives are foreign.")
|
||||
(p "This means the browser is:")
|
||||
(ul
|
||||
:class "list-disc list-inside space-y-2 mt-2"
|
||||
(~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li
|
||||
(strong "CID-addressable")
|
||||
" — the layout engine has a content hash, the style system has a content hash, the event model has a content hash")
|
||||
@@ -128,7 +128,7 @@
|
||||
(p
|
||||
"The native browser doesn't need to replace the web. It sits alongside it, the same way React Native apps, Electron apps, and Flutter apps coexist with web browsers.")
|
||||
(ul
|
||||
:class "list-disc list-inside space-y-2 mt-2"
|
||||
(~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li
|
||||
(strong "Today:")
|
||||
" SX runs in standard web browsers via the WASM kernel. No install, no app store.")
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
~not-found/content
|
||||
(&key (path :as string?))
|
||||
(div
|
||||
:class "max-w-3xl mx-auto px-4 py-12 text-center"
|
||||
(~tw :tokens "max-w-3xl mx-auto px-4 py-12 text-center")
|
||||
(h1 :style (tw "text-stone-800 text-3xl font-bold") "404")
|
||||
(p :class "mt-4" :style (tw "text-stone-500 text-lg") "Page not found")
|
||||
(p (~tw :tokens "mt-4") :style (tw "text-stone-500 text-lg") "Page not found")
|
||||
(when
|
||||
path
|
||||
(p :class "mt-2" :style (tw "text-stone-400 text-sm font-mono") path))
|
||||
(p (~tw :tokens "mt-2") :style (tw "text-stone-400 text-sm font-mono") path))
|
||||
(a
|
||||
:href "/sx/"
|
||||
:sx-get "/sx/"
|
||||
@@ -15,6 +15,6 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "inline-block mt-6 px-4 py-2 rounded border transition-colors"
|
||||
(~tw :tokens "inline-block mt-6 px-4 py-2 rounded border transition-colors")
|
||||
:style (tw "text-violet-700 text-sm border-violet-200")
|
||||
"Back to home")))
|
||||
|
||||
@@ -9,53 +9,53 @@
|
||||
;; "sx:offline synced" — individual mutation confirmed by server
|
||||
|
||||
(defcomp ~offline-demo/content (&key notes server-time)
|
||||
(div :class "space-y-8"
|
||||
(div :class "border-b border-stone-200 pb-6"
|
||||
(h1 :class "text-2xl font-bold text-stone-900" "Offline Data Layer")
|
||||
(p :class "mt-2 text-stone-600"
|
||||
(div (~tw :tokens "space-y-8")
|
||||
(div (~tw :tokens "border-b border-stone-200 pb-6")
|
||||
(h1 (~tw :tokens "text-2xl font-bold text-stone-900") "Offline Data Layer")
|
||||
(p (~tw :tokens "mt-2 text-stone-600")
|
||||
"This page tests Phase 7d offline capabilities. Mutations made while "
|
||||
"offline are queued locally and replayed when connectivity returns."))
|
||||
|
||||
;; Connectivity indicator
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-6 space-y-3"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "Status")
|
||||
(dl :class "grid grid-cols-2 gap-2 text-sm"
|
||||
(dt :class "font-medium text-stone-600" "Server time")
|
||||
(dd :class "font-mono text-stone-900" server-time)
|
||||
(dt :class "font-medium text-stone-600" "Notes count")
|
||||
(dd :class "text-stone-900" (str (len notes)))
|
||||
(dt :class "font-medium text-stone-600" "Connectivity")
|
||||
(dd :class "text-stone-900"
|
||||
(span :id "offline-status" :class "inline-flex items-center gap-1.5"
|
||||
(span :class "w-2 h-2 rounded-full bg-green-500")
|
||||
(div (~tw :tokens "rounded-lg border border-stone-200 bg-white p-6 space-y-3")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "Status")
|
||||
(dl (~tw :tokens "grid grid-cols-2 gap-2 text-sm")
|
||||
(dt (~tw :tokens "font-medium text-stone-600") "Server time")
|
||||
(dd (~tw :tokens "font-mono text-stone-900") server-time)
|
||||
(dt (~tw :tokens "font-medium text-stone-600") "Notes count")
|
||||
(dd (~tw :tokens "text-stone-900") (str (len notes)))
|
||||
(dt (~tw :tokens "font-medium text-stone-600") "Connectivity")
|
||||
(dd (~tw :tokens "text-stone-900")
|
||||
(span :id "offline-status" (~tw :tokens "inline-flex items-center gap-1.5")
|
||||
(span (~tw :tokens "w-2 h-2 rounded-full bg-green-500"))
|
||||
"Online"))))
|
||||
|
||||
;; Note list
|
||||
(div :class "space-y-3"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "Notes")
|
||||
(div :id "offline-notes" :class "space-y-2"
|
||||
(div (~tw :tokens "space-y-3")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "Notes")
|
||||
(div :id "offline-notes" (~tw :tokens "space-y-2")
|
||||
(map (fn (note)
|
||||
(div :class "flex items-center justify-between rounded border border-stone-100 bg-white p-3"
|
||||
(div :class "flex items-center gap-3"
|
||||
(span :class "flex-none rounded-full bg-blue-100 text-blue-700 w-6 h-6 flex items-center justify-center text-xs font-bold"
|
||||
(div (~tw :tokens "flex items-center justify-between rounded border border-stone-100 bg-white p-3")
|
||||
(div (~tw :tokens "flex items-center gap-3")
|
||||
(span (~tw :tokens "flex-none rounded-full bg-blue-100 text-blue-700 w-6 h-6 flex items-center justify-center text-xs font-bold")
|
||||
(str (get note "id")))
|
||||
(span :class "text-stone-900" (get note "text")))
|
||||
(span :class "text-xs text-stone-400 font-mono"
|
||||
(span (~tw :tokens "text-stone-900") (get note "text")))
|
||||
(span (~tw :tokens "text-xs text-stone-400 font-mono")
|
||||
(get note "created"))))
|
||||
notes)))
|
||||
|
||||
;; Architecture
|
||||
(div :class "space-y-4"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "How it works")
|
||||
(div :class "space-y-2"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "How it works")
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(map-indexed
|
||||
(fn (i step)
|
||||
(div :class "flex items-start gap-3 rounded border border-stone-100 bg-white p-3"
|
||||
(span :class "flex-none rounded-full bg-stone-100 text-stone-700 w-6 h-6 flex items-center justify-center text-xs font-bold"
|
||||
(div (~tw :tokens "flex items-start gap-3 rounded border border-stone-100 bg-white p-3")
|
||||
(span (~tw :tokens "flex-none rounded-full bg-stone-100 text-stone-700 w-6 h-6 flex items-center justify-center text-xs font-bold")
|
||||
(str (+ i 1)))
|
||||
(div
|
||||
(div :class "font-medium text-stone-900" (get step "label"))
|
||||
(div :class "text-sm text-stone-500" (get step "detail")))))
|
||||
(div (~tw :tokens "font-medium text-stone-900") (get step "label"))
|
||||
(div (~tw :tokens "text-sm text-stone-500") (get step "detail")))))
|
||||
(list
|
||||
(dict :label "Online mutation" :detail "Routes through submit-mutation (Phase 7c) — optimistic predict, server confirm/revert")
|
||||
(dict :label "Offline detection" :detail "Browser 'offline' event sets _is-online to false. offline-aware-mutation routes to queue")
|
||||
@@ -64,13 +64,13 @@
|
||||
(dict :label "Sync result" :detail "Each mutation marked 'synced' or 'failed'. Failed mutations stay for manual retry")))))
|
||||
|
||||
;; How to test
|
||||
(div :class "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2"
|
||||
(p :class "font-semibold text-amber-800" "How to test offline behavior")
|
||||
(ol :class "list-decimal list-inside text-amber-700 space-y-1"
|
||||
(div (~tw :tokens "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2")
|
||||
(p (~tw :tokens "font-semibold text-amber-800") "How to test offline behavior")
|
||||
(ol (~tw :tokens "list-decimal list-inside text-amber-700 space-y-1")
|
||||
(li "Open the browser console and Network tab")
|
||||
(li "Navigate to this page via client-side routing")
|
||||
(li "In DevTools, set Network to \"Offline\" mode")
|
||||
(li "The connectivity indicator should change to red/Offline")
|
||||
(li "Watch console for " (code :class "bg-amber-100 px-1 rounded" "sx:offline queued"))
|
||||
(li "Re-enable network — watch for " (code :class "bg-amber-100 px-1 rounded" "sx:offline syncing"))
|
||||
(li "Watch console for " (code (~tw :tokens "bg-amber-100 px-1 rounded") "sx:offline queued"))
|
||||
(li "Re-enable network — watch for " (code (~tw :tokens "bg-amber-100 px-1 rounded") "sx:offline syncing"))
|
||||
(li "Queued mutations replay and confirm or fail")))))
|
||||
|
||||
@@ -2,42 +2,42 @@
|
||||
~optimistic-demo/content
|
||||
(&key items server-time)
|
||||
(div
|
||||
:class "space-y-8"
|
||||
(~tw :tokens "space-y-8")
|
||||
(div
|
||||
:class "border-b border-stone-200 pb-6"
|
||||
(h1 :class "text-2xl font-bold text-stone-900" "Optimistic Updates")
|
||||
(~tw :tokens "border-b border-stone-200 pb-6")
|
||||
(h1 (~tw :tokens "text-2xl font-bold text-stone-900") "Optimistic Updates")
|
||||
(p
|
||||
:class "mt-2 text-stone-600"
|
||||
(~tw :tokens "mt-2 text-stone-600")
|
||||
"This page tests Phase 7c optimistic data mutations. Items are updated "
|
||||
"instantly on the client, then confirmed or reverted when the server responds."))
|
||||
(div
|
||||
:class "rounded-lg border border-stone-200 bg-white p-6 space-y-3"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "Current state")
|
||||
(~tw :tokens "rounded-lg border border-stone-200 bg-white p-6 space-y-3")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "Current state")
|
||||
(dl
|
||||
:class "grid grid-cols-2 gap-2 text-sm"
|
||||
(dt :class "font-medium text-stone-600" "Server time")
|
||||
(dd :class "font-mono text-stone-900" server-time)
|
||||
(dt :class "font-medium text-stone-600" "Item count")
|
||||
(dd :class "text-stone-900" (str (len items)))))
|
||||
(~tw :tokens "grid grid-cols-2 gap-2 text-sm")
|
||||
(dt (~tw :tokens "font-medium text-stone-600") "Server time")
|
||||
(dd (~tw :tokens "font-mono text-stone-900") server-time)
|
||||
(dt (~tw :tokens "font-medium text-stone-600") "Item count")
|
||||
(dd (~tw :tokens "text-stone-900") (str (len items)))))
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "Items")
|
||||
(~tw :tokens "space-y-3")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "Items")
|
||||
(div
|
||||
:id "optimistic-items"
|
||||
:class "space-y-2"
|
||||
(~tw :tokens "space-y-2")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
(div
|
||||
:class "flex items-center justify-between rounded border border-stone-100 bg-white p-3"
|
||||
(~tw :tokens "flex items-center justify-between rounded border border-stone-100 bg-white p-3")
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(span
|
||||
:class "flex-none rounded-full bg-violet-100 text-violet-700 w-6 h-6 flex items-center justify-center text-xs font-bold"
|
||||
(~tw :tokens "flex-none rounded-full bg-violet-100 text-violet-700 w-6 h-6 flex items-center justify-center text-xs font-bold")
|
||||
(str (get item "id")))
|
||||
(span :class "text-stone-900" (get item "label")))
|
||||
(span (~tw :tokens "text-stone-900") (get item "label")))
|
||||
(span
|
||||
:class "text-xs px-2 py-0.5 rounded-full"
|
||||
(~tw :tokens "text-xs px-2 py-0.5 rounded-full")
|
||||
:class (case
|
||||
(get item "status")
|
||||
"confirmed"
|
||||
@@ -50,29 +50,29 @@
|
||||
(get item "status"))))
|
||||
items))
|
||||
(div
|
||||
:class "pt-2"
|
||||
(~tw :tokens "pt-2")
|
||||
(button
|
||||
:class "px-4 py-2 bg-violet-500 text-white rounded hover:bg-violet-600 text-sm"
|
||||
(~tw :tokens "px-4 py-2 bg-violet-500 text-white rounded hover:bg-violet-600 text-sm")
|
||||
:sx-post "/sx/action/add-demo-item"
|
||||
:sx-target "#sx-content"
|
||||
:sx-vals "{\"label\": \"New item\"}"
|
||||
"Add item (optimistic)")))
|
||||
(div
|
||||
:class "space-y-4"
|
||||
(h2 :class "text-lg font-semibold text-stone-800" "How it works")
|
||||
(~tw :tokens "space-y-4")
|
||||
(h2 (~tw :tokens "text-lg font-semibold text-stone-800") "How it works")
|
||||
(div
|
||||
:class "space-y-2"
|
||||
(~tw :tokens "space-y-2")
|
||||
(map-indexed
|
||||
(fn
|
||||
(i step)
|
||||
(div
|
||||
:class "flex items-start gap-3 rounded border border-stone-100 bg-white p-3"
|
||||
(~tw :tokens "flex items-start gap-3 rounded border border-stone-100 bg-white p-3")
|
||||
(span
|
||||
:class "flex-none rounded-full bg-stone-100 text-stone-700 w-6 h-6 flex items-center justify-center text-xs font-bold"
|
||||
(~tw :tokens "flex-none rounded-full bg-stone-100 text-stone-700 w-6 h-6 flex items-center justify-center text-xs font-bold")
|
||||
(str (+ i 1)))
|
||||
(div
|
||||
(div :class "font-medium text-stone-900" (get step "label"))
|
||||
(div :class "text-sm text-stone-500" (get step "detail")))))
|
||||
(div (~tw :tokens "font-medium text-stone-900") (get step "label"))
|
||||
(div (~tw :tokens "text-sm text-stone-500") (get step "detail")))))
|
||||
(list
|
||||
(dict
|
||||
:label "Predict"
|
||||
@@ -90,15 +90,15 @@
|
||||
:label "Confirm or revert"
|
||||
:detail "Server responds — cache updated with truth, or reverted to snapshot")))))
|
||||
(div
|
||||
:class "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2"
|
||||
(p :class "font-semibold text-amber-800" "How to verify")
|
||||
(~tw :tokens "rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm space-y-2")
|
||||
(p (~tw :tokens "font-semibold text-amber-800") "How to verify")
|
||||
(ol
|
||||
:class "list-decimal list-inside text-amber-700 space-y-1"
|
||||
(~tw :tokens "list-decimal list-inside text-amber-700 space-y-1")
|
||||
(li "Open the browser console (F12)")
|
||||
(li "Navigate to this page from another isomorphism page")
|
||||
(li
|
||||
"Click \"Add item\" — item appears instantly with \"pending\" status")
|
||||
(li
|
||||
"Watch console for "
|
||||
(code :class "bg-amber-100 px-1 rounded" "sx:optimistic confirmed"))
|
||||
(code (~tw :tokens "bg-amber-100 px-1 rounded") "sx:optimistic confirmed"))
|
||||
(li "Item status changes to \"confirmed\" when server responds")))))
|
||||
|
||||
@@ -103,13 +103,13 @@
|
||||
|
||||
(when (deref results)
|
||||
(let ((r (deref results)))
|
||||
(div :class "grid grid-cols-1 md:grid-cols-2 gap-4"
|
||||
(div (~tw :tokens "grid grid-cols-1 md:grid-cols-2 gap-4")
|
||||
|
||||
(~page-helpers-demo/demo-result-card
|
||||
:title "categorize-special-forms"
|
||||
:ms (get r "sf-ms") :theme "blue"
|
||||
:desc "Parses special-forms.sx and classifies each form by category (control flow, binding, quoting, etc)."
|
||||
(p :class "text-sm mb-1"
|
||||
(p (~tw :tokens "text-sm mb-1")
|
||||
(str (get r "sf-total") " forms in "
|
||||
(len (keys (get r "sf-cats"))) " categories"))
|
||||
(map (fn (k)
|
||||
@@ -120,7 +120,7 @@
|
||||
:title "build-reference-data"
|
||||
:ms (get r "ref-ms") :theme "blue"
|
||||
:desc "Takes raw attribute tuples and generates reference table rows with documentation hrefs."
|
||||
(p :class "text-sm mb-1"
|
||||
(p (~tw :tokens "text-sm mb-1")
|
||||
(str (len (get r "ref-sample")) " attributes with detail page links"))
|
||||
(map (fn (item)
|
||||
(div (str (get item "name") " → "
|
||||
@@ -139,21 +139,21 @@
|
||||
:title "build-component-source"
|
||||
:ms (get r "comp-ms") :theme "blue"
|
||||
:desc "Reconstructs a defcomp source definition from a component metadata dict (name, params, body)."
|
||||
(pre :class "bg-blue-50 p-2 rounded overflow-x-auto"
|
||||
(pre (~tw :tokens "bg-blue-50 p-2 rounded overflow-x-auto")
|
||||
(get r "comp-result")))
|
||||
|
||||
(div :class "rounded-lg border border-blue-200 bg-blue-50/30 p-4 md:col-span-2"
|
||||
(h4 :class "font-semibold text-blue-700 text-sm mb-1"
|
||||
(div (~tw :tokens "rounded-lg border border-blue-200 bg-blue-50/30 p-4 md:col-span-2")
|
||||
(h4 (~tw :tokens "font-semibold text-blue-700 text-sm mb-1")
|
||||
"build-routing-analysis "
|
||||
(span :class "text-xs text-blue-400" (str (get r "routing-ms") "ms")))
|
||||
(p :class "text-xs text-blue-500 mb-2"
|
||||
(span (~tw :tokens "text-xs text-blue-400") (str (get r "routing-ms") "ms")))
|
||||
(p (~tw :tokens "text-xs text-blue-500 mb-2")
|
||||
"Classifies pages as client-routable or server-only based on whether they have data dependencies.")
|
||||
(div :class "text-xs text-blue-600"
|
||||
(p :class "text-sm mb-1"
|
||||
(div (~tw :tokens "text-xs text-blue-600")
|
||||
(p (~tw :tokens "text-sm mb-1")
|
||||
(str (get (get r "routing-result") "total-pages") " pages: "
|
||||
(get (get r "routing-result") "client-count") " client-routable, "
|
||||
(get (get r "routing-result") "server-count") " server-only"))
|
||||
(div :class "space-y-0.5"
|
||||
(div (~tw :tokens "space-y-0.5")
|
||||
(map (fn (pg)
|
||||
(div (str (get pg "name") " → " (get pg "mode")
|
||||
(when (not (empty? (get pg "reason")))
|
||||
@@ -175,32 +175,32 @@
|
||||
sf-source
|
||||
attr-detail req-attrs attr-keys)
|
||||
|
||||
(div :class "max-w-3xl mx-auto px-4"
|
||||
(div :class "mb-8"
|
||||
(h2 :class "text-2xl font-bold text-stone-800 mb-2" "Bootstrapped Page Helpers")
|
||||
(p :class "text-stone-600 mb-4"
|
||||
(div (~tw :tokens "max-w-3xl mx-auto px-4")
|
||||
(div (~tw :tokens "mb-8")
|
||||
(h2 (~tw :tokens "text-2xl font-bold text-stone-800 mb-2") "Bootstrapped Page Helpers")
|
||||
(p (~tw :tokens "text-stone-600 mb-4")
|
||||
"These functions are defined once in "
|
||||
(code :class "text-violet-700" "page-helpers.sx")
|
||||
(code (~tw :tokens "text-violet-700") "page-helpers.sx")
|
||||
" and bootstrapped to both Python ("
|
||||
(code :class "text-violet-700" "sx_ref.py")
|
||||
(code (~tw :tokens "text-violet-700") "sx_ref.py")
|
||||
") and JavaScript ("
|
||||
(code :class "text-violet-700" "sx-browser.js")
|
||||
(code (~tw :tokens "text-violet-700") "sx-browser.js")
|
||||
"). The server ran them in Python during this page load. Click the button below to run the identical functions client-side in the browser — same spec, same inputs, same results."))
|
||||
|
||||
;; Server results
|
||||
(div :class "mb-8"
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mb-3"
|
||||
(div (~tw :tokens "mb-8")
|
||||
(h3 (~tw :tokens "text-lg font-semibold text-stone-700 mb-3")
|
||||
"Server Results "
|
||||
(span :class "text-sm font-normal text-stone-500"
|
||||
(span (~tw :tokens "text-sm font-normal text-stone-500")
|
||||
(str "(Python, " server-total-ms "ms total)")))
|
||||
|
||||
(div :class "grid grid-cols-1 md:grid-cols-2 gap-4"
|
||||
(div (~tw :tokens "grid grid-cols-1 md:grid-cols-2 gap-4")
|
||||
|
||||
(~page-helpers-demo/demo-result-card
|
||||
:title "categorize-special-forms"
|
||||
:ms sf-ms :theme "stone"
|
||||
:desc "Parses special-forms.sx and classifies each form by category (control flow, binding, quoting, etc)."
|
||||
(p :class "text-sm mb-1"
|
||||
(p (~tw :tokens "text-sm mb-1")
|
||||
(str sf-total " forms in "
|
||||
(len (keys sf-categories)) " categories"))
|
||||
(map (fn (k)
|
||||
@@ -211,7 +211,7 @@
|
||||
:title "build-reference-data"
|
||||
:ms ref-ms :theme "stone"
|
||||
:desc "Takes raw attribute tuples and generates reference table rows with documentation hrefs."
|
||||
(p :class "text-sm mb-1"
|
||||
(p (~tw :tokens "text-sm mb-1")
|
||||
(str (len ref-sample) " attributes with detail page links"))
|
||||
(map (fn (item)
|
||||
(div (str (get item "name") " → "
|
||||
@@ -230,21 +230,21 @@
|
||||
:title "build-component-source"
|
||||
:ms comp-ms :theme "stone"
|
||||
:desc "Reconstructs a defcomp source definition from a component metadata dict (name, params, body)."
|
||||
(pre :class "bg-stone-50 p-2 rounded overflow-x-auto"
|
||||
(pre (~tw :tokens "bg-stone-50 p-2 rounded overflow-x-auto")
|
||||
comp-source))
|
||||
|
||||
(div :class "rounded-lg border border-stone-200 p-4 md:col-span-2"
|
||||
(h4 :class "font-semibold text-stone-700 text-sm mb-1"
|
||||
(div (~tw :tokens "rounded-lg border border-stone-200 p-4 md:col-span-2")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 text-sm mb-1")
|
||||
"build-routing-analysis "
|
||||
(span :class "text-xs text-stone-400" (str routing-ms "ms")))
|
||||
(p :class "text-xs text-stone-500 mb-2"
|
||||
(span (~tw :tokens "text-xs text-stone-400") (str routing-ms "ms")))
|
||||
(p (~tw :tokens "text-xs text-stone-500 mb-2")
|
||||
"Classifies pages as client-routable or server-only based on whether they have data dependencies.")
|
||||
(div :class "text-xs text-stone-600"
|
||||
(p :class "text-sm mb-1"
|
||||
(div (~tw :tokens "text-xs text-stone-600")
|
||||
(p (~tw :tokens "text-sm mb-1")
|
||||
(str (get routing-result "total-pages") " pages: "
|
||||
(get routing-result "client-count") " client-routable, "
|
||||
(get routing-result "server-count") " server-only"))
|
||||
(div :class "space-y-0.5"
|
||||
(div (~tw :tokens "space-y-0.5")
|
||||
(map (fn (pg)
|
||||
(div (str (get pg "name") " → " (get pg "mode")
|
||||
(when (not (empty? (get pg "reason")))
|
||||
@@ -252,10 +252,10 @@
|
||||
(get routing-result "pages")))))))
|
||||
|
||||
;; Client execution area — pure SX island, no JavaScript file
|
||||
(div :class "mb-8"
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mb-3"
|
||||
(div (~tw :tokens "mb-8")
|
||||
(h3 (~tw :tokens "text-lg font-semibold text-stone-700 mb-3")
|
||||
"Client Results "
|
||||
(span :class "text-sm font-normal text-stone-500" "(JavaScript, sx-browser.js)"))
|
||||
(span (~tw :tokens "text-sm font-normal text-stone-500") "(JavaScript, sx-browser.js)"))
|
||||
|
||||
(~page-helpers-demo/demo-client-runner
|
||||
:sf-source sf-source
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/art-dag-sx/plan-art-dag-sx-content ()
|
||||
(~docs/page :title "Art DAG on SX"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"An SX endpoint is a portal into an execution environment. The URL is the entry point; the boundary declaration determines what world you enter.")
|
||||
|
||||
;; =====================================================================
|
||||
@@ -44,9 +44,9 @@
|
||||
(~docs/section :title "Content-addressed everything" :id "content-addressed"
|
||||
(p "Recipes are CIDs. Effects are CIDs. Intermediate frames are content-addressed. The execution DAG is a content-addressed graph " (em "- ") "every step can be verified, cached, or replayed.")
|
||||
(p "A composite of three layers with a specific blend mode always produces the same CID. Caching is automatic: if the CID exists locally, skip the computation. This is the Art DAG's existing model " (em "- ") "SHA3-256 hashes identify everything. SX makes it explicit: the recipe source itself is content-addressed. Two users who write the same recipe get the same CID. They're running the same program.")
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4 my-4"
|
||||
(p :class "text-stone-700 font-medium mb-2" "Content addressing as memoization")
|
||||
(p :class "text-stone-600 text-sm" "Every function call with content-addressed inputs has a content-addressed output. " (code "(gpu-exec :op \"composite\" :layers (list CID-a CID-b) :blend \"multiply\")") " always produces the same result CID. The runtime can check: does this output CID exist? If yes, skip the computation. The entire execution DAG becomes a cache key. Rerunning a recipe that's already been computed is instantaneous " (em "- ") "every intermediate result already exists."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-stone-700 font-medium mb-2") "Content addressing as memoization")
|
||||
(p (~tw :tokens "text-stone-600 text-sm") "Every function call with content-addressed inputs has a content-addressed output. " (code "(gpu-exec :op \"composite\" :layers (list CID-a CID-b) :blend \"multiply\")") " always produces the same result CID. The runtime can check: does this output CID exist? If yes, skip the computation. The entire execution DAG becomes a cache key. Rerunning a recipe that's already been computed is instantaneous " (em "- ") "every intermediate result already exists."))
|
||||
(p "The execution trace is also content-addressed. You can inspect exactly what happened: which CIDs were resolved, which GPU operations ran, which feeds were opened, what the final output was. The trace is the recipe's proof of work. It's immutable, verifiable, and shareable."))
|
||||
|
||||
;; =====================================================================
|
||||
@@ -85,43 +85,43 @@
|
||||
|
||||
(~docs/section :title "The primitive sets" :id "primitive-sets"
|
||||
(p "Each execution environment provides its own set of primitives. The language is the same everywhere. The capabilities differ.")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Environment")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Primitives")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Runs on")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Environment")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Primitives")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Runs on")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Browser")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "render-to-dom, signal, deref, connect-stream")
|
||||
(td :class "px-3 py-2 text-stone-600" "Client"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "App server")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "query-db, render-to-html, fetch-fragment")
|
||||
(td :class "px-3 py-2 text-stone-600" "Quart service"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "L1 Worker")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "gpu-exec, resolve-cid, encode-stream, cache-put")
|
||||
(td :class "px-3 py-2 text-stone-600" "Celery + GPU"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "L2 Registry")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "discover-recipe, publish-recipe, federate")
|
||||
(td :class "px-3 py-2 text-stone-600" "FastAPI"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Live Ingest")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "open-feed, capture-frame, transcode")
|
||||
(td :class "px-3 py-2 text-stone-600" "WebRTC gateway"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Browser")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "render-to-dom, signal, deref, connect-stream")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Client"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "App server")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "query-db, render-to-html, fetch-fragment")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Quart service"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "L1 Worker")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "gpu-exec, resolve-cid, encode-stream, cache-put")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Celery + GPU"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "L2 Registry")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "discover-recipe, publish-recipe, federate")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "FastAPI"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Live Ingest")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "open-feed, capture-frame, transcode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "WebRTC gateway"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "IPFS Node")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "pin-cid, resolve-cid, dag-put, dag-get")
|
||||
(td :class "px-3 py-2 text-stone-600" "Kubo")))))
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "IPFS Node")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "pin-cid, resolve-cid, dag-put, dag-get")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Kubo")))))
|
||||
|
||||
(p "A pure SX program (no IO primitives) runs on all six. A program that calls " (code "gpu-exec") " runs on L1 workers. A program that calls " (code "render-to-dom") " runs in the browser. The boundary declaration is the type signature of the environment. It tells you where the program can execute.")
|
||||
(p "Adding a new environment means declaring a new primitive set. A hypothetical audio-processing environment would provide " (code "mix-tracks") ", " (code "apply-effect") ", " (code "encode-audio") ". A program that uses those primitives runs wherever that environment is hosted. The language doesn't change. The evaluator doesn't change. Only the available primitives change.")
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(p :class "text-violet-900 font-medium" "One language, many worlds")
|
||||
(p :class "text-violet-800 text-sm" "The art-dag integration isn't a new feature bolted onto SX. It's a demonstration of what SX already is: a language where the execution environment is parameterized by its primitive set. The browser, the app server, the GPU worker, and the IPFS node all run the same evaluator. They differ only in what primitives they provide. The art-dag is just another world you can enter through an endpoint.")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "One language, many worlds")
|
||||
(p (~tw :tokens "text-violet-800 text-sm") "The art-dag integration isn't a new feature bolted onto SX. It's a demonstration of what SX already is: a language where the execution environment is parameterized by its primitive set. The browser, the app server, the GPU worker, and the IPFS node all run the same evaluator. They differ only in what primitives they provide. The art-dag is just another world you can enter through an endpoint.")))
|
||||
|
||||
))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
(~docs/section :title "The Problem" :id "problem"
|
||||
(p "There are currently " (strong "three") " lambda call implementations that must be kept in sync:")
|
||||
(ol :class "list-decimal list-inside space-y-2 mt-2"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 mt-2")
|
||||
(li (code "shared/sx/ref/eval.sx") " — the canonical spec, bootstrapped to " (code "sx-ref.js") " and " (code "sx_ref.py"))
|
||||
(li (code "shared/sx/evaluator.py") " — hand-written synchronous Python evaluator")
|
||||
(li (code "shared/sx/async_eval.py") " — hand-written asynchronous Python evaluator (the production server path)"))
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
(~docs/section :title "Why async_eval.py Exists" :id "why"
|
||||
(p "The async evaluator exists because SX page rendering needs to:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Await IO primitives") " — page helpers like " (code "highlight") ", " (code "reference-data") ", " (code "component-source") " call Python async functions (DB queries, HTTP fetches). The spec evaluator is synchronous.")
|
||||
(li (strong "Expand server-affinity components") " — " (code ":affinity :server") " components must be fully expanded server-side before serialising to SX wire format. This requires async eval of the component body.")
|
||||
(li (strong "Handle the aser rendering mode") " — the " (code "_aser") " function evaluates control flow server-side but serialises HTML tags and component calls as SX source for the client. This hybrid eval/serialize mode isn't in the spec."))
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
(~docs/section :title "Target Architecture" :id "architecture"
|
||||
(p "The goal is to " (strong "eliminate hand-written evaluator code entirely") ". All evaluation semantics come from the spec via bootstrapping. The host provides only:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Platform primitives") " — type constructors, env operations, DOM/HTML primitives")
|
||||
(li (strong "Async IO bridge") " — a thin wrapper that makes the bootstrapped evaluator await-compatible")
|
||||
(li (strong "Rendering modes") " — aser/render-to-html dispatch, already mostly specced in " (code "render.sx")))
|
||||
@@ -41,20 +41,20 @@
|
||||
(~docs/section :title "Approach: Async Adapter Layer" :id "approach"
|
||||
(p "Rather than making the spec itself async (which would pollute it with Python-specific concerns), introduce a thin adapter layer between the bootstrapped evaluator and the IO boundary:")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 1 — Async call hook")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 1 — Async call hook")
|
||||
(p "The bootstrapped evaluator calls primitives via " (code "apply(fn, args)") ". In the Python host, " (code "apply") " is a platform primitive. Replace it with an async-aware version:")
|
||||
(~docs/code :src (highlight "(define apply-fn\n (fn (f args)\n ;; Platform provides: if f returns a coroutine, await it\n (apply-maybe-async f args)))" "lisp"))
|
||||
(p "The bootstrapper emits " (code "apply_maybe_async") " as a Python " (code "async def") " that checks if the result is a coroutine and awaits it if so. Pure functions return immediately. IO primitives return coroutines that get awaited. " (strong "Zero overhead for pure calls") " — just an " (code "isinstance") " check.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 2 — Async trampoline")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 2 — Async trampoline")
|
||||
(p "The spec's trampoline loop resolves thunks synchronously. The Python bootstrapper emits an " (code "async def trampoline") " variant that can await thunks whose bodies contain IO calls. The trampoline structure is identical — only the " (code "await") " keyword is added.")
|
||||
(~docs/code :src (highlight "# Bootstrapper emits this for Python async target\nasync def trampoline(val):\n while isinstance(val, Thunk):\n val = await eval_expr(val.expr, val.env)\n return val" "python"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 3 — Aser as spec module")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 3 — Aser as spec module")
|
||||
(p "The " (code "_aser") " rendering mode (evaluate control flow, serialize HTML/components as SX source) should be specced as a module in " (code "render.sx") " alongside " (code "render-to-html") " and " (code "render-to-dom") ". It's currently hand-written Python because it predates the spec, but its logic is pure SX: walk the AST, eval certain forms, serialize others.")
|
||||
(p "Once aser is specced, the bootstrapper emits it with the same async adapter — IO calls within aser bodies get awaited transparently.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 4 — Delete hand-written evaluators")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 4 — Delete hand-written evaluators")
|
||||
(p "With the async adapter + specced aser, " (code "evaluator.py") " and " (code "async_eval.py") " become dead code. Delete them. All evaluation flows through the bootstrapped " (code "sx_ref.py") " with async adapter."))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
(~docs/section :title "Bootstrapper Changes" :id "bootstrapper"
|
||||
(p "The Python bootstrapper (" (code "bootstrap_py.py") ") gains a new emit mode: " (code "--async") ". This emits:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (code "async def eval_expr") " instead of " (code "def eval_expr"))
|
||||
(li (code "async def trampoline") " with " (code "await") " on thunk eval")
|
||||
(li (code "apply_maybe_async") " that awaits coroutine results")
|
||||
@@ -75,40 +75,40 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Migration Path" :id "migration"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Step")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Risk")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Step")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Risk")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "1")
|
||||
(td :class "px-3 py-2 text-stone-700" "Add async emit mode to bootstrap_py.py. Generate async_sx_ref.py alongside sx_ref.py.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Low — new file, nothing changes yet"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "2")
|
||||
(td :class "px-3 py-2 text-stone-700" "Run async_sx_ref.py in parallel with async_eval.py, compare outputs on every page render.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Low — shadow mode, no user impact"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "3")
|
||||
(td :class "px-3 py-2 text-stone-700" "Spec aser in render.sx. Bootstrap it. Shadow-compare with hand-written _aser.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Medium — aser has edge cases around OOB, fragments"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "4")
|
||||
(td :class "px-3 py-2 text-stone-700" "Switch page rendering to async_sx_ref.py. Keep async_eval.py as fallback.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Medium — production path changes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "1")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Add async emit mode to bootstrap_py.py. Generate async_sx_ref.py alongside sx_ref.py.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Low — new file, nothing changes yet"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "2")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Run async_sx_ref.py in parallel with async_eval.py, compare outputs on every page render.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Low — shadow mode, no user impact"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "3")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Spec aser in render.sx. Bootstrap it. Shadow-compare with hand-written _aser.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Medium — aser has edge cases around OOB, fragments"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "4")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Switch page rendering to async_sx_ref.py. Keep async_eval.py as fallback.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Medium — production path changes"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "5")
|
||||
(td :class "px-3 py-2 text-stone-700" "Delete evaluator.py and async_eval.py.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Low — once shadow confirms parity"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "5")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Delete evaluator.py and async_eval.py.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Low — once shadow confirms parity"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Principles
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "The spec is the single source of truth.") " All SX evaluation semantics live in .sx files. Host code implements platform primitives, not evaluation rules.")
|
||||
(li (strong "Async is a host concern, not a language concern.") " The spec is synchronous. The Python bootstrapper emits async wrappers. The JS bootstrapper emits sync code. The spec doesn't know or care.")
|
||||
(li (strong "Shadow-compare before switching.") " Every migration step runs both paths in parallel and asserts identical output. No big-bang cutover.")
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After convergence:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "One evaluator implementation (the spec), bootstrapped to every host")
|
||||
(li "Semantic changes made once in .sx, automatically propagated")
|
||||
(li "~2,000 lines of hand-written Python evaluator code deleted")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(defcomp ~plans/cek-reactive/plan-cek-reactive-content ()
|
||||
(~docs/page :title "Deref as Shift — CEK-Based Reactive DOM Renderer"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Phase A collapsed signals to plain dicts with zero platform primitives. "
|
||||
"Phase B replaces explicit effect wrapping in the reactive DOM renderer "
|
||||
"with implicit continuation capture: when " (code "deref") " encounters a signal "
|
||||
@@ -19,7 +19,7 @@
|
||||
(~docs/section :title "The Insight" :id "insight"
|
||||
|
||||
(p "Each reactive binding is a micro-computation:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "reactive-text") ": given signal value, set text node content to " (code "(str value)"))
|
||||
(li (code "reactive-attr") ": given signal value, set attribute to " (code "(str value)")))
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"javascript")))
|
||||
|
||||
(~docs/subsection :title "1b. run_js_sx.py — Update compile_ref_to_js"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Auto-add " (code "\"frames\"") " when " (code "\"cek\"") " in spec_mod_set (mirror Python " (code "bootstrap_py.py") ")")
|
||||
(li "Auto-add " (code "\"cek\"") " + " (code "\"frames\"") " when " (code "\"dom\"") " adapter included (CEK needed for reactive rendering)")
|
||||
(li "Use " (code "SPEC_MODULE_ORDER") " for ordering instead of " (code "sorted()"))
|
||||
@@ -78,7 +78,7 @@
|
||||
"python")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Rebootstrap JS: " (code "python3 bootstrap_js.py"))
|
||||
(li "Check output contains frame constructors + CEK step functions")
|
||||
(li "Run existing CEK Python tests: " (code "python3 run_cek_tests.py") " (should still pass)"))))
|
||||
@@ -179,7 +179,7 @@
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "4e. What stays unchanged"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "reactive-list") " — keyed reconciliation is complex; keep effect-based for now")
|
||||
(li (code "reactive-spread") " — spread tracking is complex; keep effect-based")
|
||||
(li (code "effect") ", " (code "computed") " — still needed for non-rendering side effects")
|
||||
@@ -193,7 +193,7 @@
|
||||
|
||||
(~docs/subsection :title "5a. test-cek-reactive.sx"
|
||||
(p "Tests:")
|
||||
(ol :class "list-decimal pl-6 mb-4 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-6 mb-4 space-y-1")
|
||||
(li (code "deref") " non-signal passes through (no shift)")
|
||||
(li (code "deref") " signal without reactive-reset: returns value, no subscription")
|
||||
(li (code "deref") " signal with reactive-reset: shifts, registers subscriber, update-fn called on change")
|
||||
@@ -212,7 +212,7 @@
|
||||
|
||||
(~docs/section :title "Step 6: Browser Demo" :id "step-6"
|
||||
(p "Demo showing:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Counter island with implicit reactivity (no explicit effects)")
|
||||
(li (code "(deref counter)") " in text position auto-updates")
|
||||
(li (code "(str \"count-\" (deref class-sig))") " in attr position auto-updates")
|
||||
@@ -226,7 +226,7 @@
|
||||
|
||||
(~docs/code :src (highlight "(str (deref first-name) \" \" (deref last-name))" "lisp"))
|
||||
|
||||
(ol :class "list-decimal pl-6 mb-6 space-y-3"
|
||||
(ol (~tw :tokens "list-decimal pl-6 mb-6 space-y-3")
|
||||
(li (strong "Initial render:") " First " (code "deref") " hits signal → shifts, captures "
|
||||
(code "(str [HOLE] \" \" (deref last-name))") ". Subscriber registered for "
|
||||
(code "first-name") ". Returns current value. Second " (code "deref")
|
||||
@@ -247,7 +247,7 @@
|
||||
|
||||
(~docs/section :title "Commit Strategy" :id "commits"
|
||||
|
||||
(ol :class "list-decimal pl-6 mb-4 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-6 mb-4 space-y-1")
|
||||
(li (strong "Commit 1:") " Bootstrap CEK to JS (Step 1) — mechanical, independent")
|
||||
(li (strong "Commit 2:") " ReactiveResetFrame + DerefFrame (Step 2) — new frame types")
|
||||
(li (strong "Commit 3:") " Deref-as-shift + adapter integration + tests (Steps 3-5) — the core change")
|
||||
@@ -259,33 +259,33 @@
|
||||
|
||||
(~docs/section :title "Files" :id "files"
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "File")
|
||||
(th :class "text-left pb-2 font-semibold" "Change")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "File")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Change")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/platform_js.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/platform_js.py")
|
||||
(td "SPEC_MODULES entries, PLATFORM_CEK_JS, CEK_FIXUPS_JS, SPEC_MODULE_ORDER"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/run_js_sx.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/run_js_sx.py")
|
||||
(td "compile_ref_to_js: has_cek, auto-inclusion, ordering, platform code"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/js.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/js.sx")
|
||||
(td "RENAMES for CEK predicate functions"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/bootstrap_py.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/bootstrap_py.py")
|
||||
(td "RENAMES for CEK predicates"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/frames.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/frames.sx")
|
||||
(td "ReactiveResetFrame, DerefFrame, has-reactive-reset-frame?, kont-capture-to-reactive-reset"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/cek.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/cek.sx")
|
||||
(td "step-sf-deref, reactive-shift-deref, deref in dispatch, ReactiveResetFrame in step-continue"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/adapter-dom.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/adapter-dom.sx")
|
||||
(td "*use-cek-reactive* flag, cek-reactive-attr, cek-reactive-text, conditional dispatch"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/test-cek-reactive.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/test-cek-reactive.sx")
|
||||
(td (strong "New:") " continuation-based reactivity tests"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/run_cek_reactive_tests.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/run_cek_reactive_tests.py")
|
||||
(td (strong "New:") " Python test runner"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/sx_ref.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/sx_ref.py")
|
||||
(td "Rebootstrap (generated)"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/static/scripts/sx-browser.js")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/static/scripts/sx-browser.js")
|
||||
(td "Rebootstrap (generated)"))))))
|
||||
|
||||
;; =====================================================================
|
||||
@@ -294,7 +294,7 @@
|
||||
|
||||
(~docs/section :title "Risks" :id "risks"
|
||||
|
||||
(ol :class "list-decimal pl-6 mb-4 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-6 mb-4 space-y-2")
|
||||
(li (strong "Performance:") " CEK allocates a dict per step. Mitigated: opt-in flag, tree-walk remains default.")
|
||||
(li (strong "Multi-deref stale subscribers:") " Mitigated: sub-scope disposal before re-invocation.")
|
||||
(li (strong "Interaction with user shift/reset:") " " (code "kont-capture-to-reactive-reset")
|
||||
|
||||
@@ -18,45 +18,45 @@
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(p "What already exists and what's missing.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Capability")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Where")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Capability")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Where")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Deterministic serialization")
|
||||
(td :class "px-3 py-2 text-stone-700" "Partial — " (code "serialize(body, pretty=True)") " from AST, but no canonical normalization")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "parser.py:296-427"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component identity")
|
||||
(td :class "px-3 py-2 text-stone-700" "By name (" (code "~card") ") — names are mutable, server-local")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "types.py:157-180"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Bundle hashing")
|
||||
(td :class "px-3 py-2 text-stone-700" "SHA256 of all defs concatenated — per-bundle, not per-component")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "jinja_bridge.py:60-86"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Purity verification")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete") " — " (code "is_pure") " via transitive IO ref analysis")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "deps.sx, boundary.py"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Dependency graph")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete") " — " (code "Component.deps") " transitive closure")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "deps.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS infrastructure")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Exists") " — IPFSPin model, async upload tasks, CID tracking")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "models/federation.py, artdag/l1/tasks/"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Client component caching")
|
||||
(td :class "px-3 py-2 text-stone-700" "Hash-based localStorage — but keyed by bundle hash, not individual CID")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "boot.sx, helpers.py"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Content-addressed components")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not yet") " — no per-component CID, no IPFS resolution")
|
||||
(td :class "px-3 py-2 text-stone-600" "—"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Deterministic serialization")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Partial — " (code "serialize(body, pretty=True)") " from AST, but no canonical normalization")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "parser.py:296-427"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component identity")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "By name (" (code "~card") ") — names are mutable, server-local")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "types.py:157-180"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bundle hashing")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SHA256 of all defs concatenated — per-bundle, not per-component")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "jinja_bridge.py:60-86"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Purity verification")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete") " — " (code "is_pure") " via transitive IO ref analysis")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "deps.sx, boundary.py"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Dependency graph")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete") " — " (code "Component.deps") " transitive closure")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "deps.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS infrastructure")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Exists") " — IPFSPin model, async upload tasks, CID tracking")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "models/federation.py, artdag/l1/tasks/"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client component caching")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Hash-based localStorage — but keyed by bundle hash, not individual CID")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "boot.sx, helpers.py"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Content-addressed components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-red-700 font-medium") "Not yet") " — no per-component CID, no IPFS resolution")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "—"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Canonical Serialization
|
||||
@@ -64,16 +64,16 @@
|
||||
|
||||
(~docs/section :title "Phase 1: Canonical Serialization" :id "canonical-serialization"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "The foundation")
|
||||
(p :class "text-violet-800" "Same component must always produce the same bytes, regardless of original formatting, whitespace, or comment placement. Without this, content addressing is meaningless."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The foundation")
|
||||
(p (~tw :tokens "text-violet-800") "Same component must always produce the same bytes, regardless of original formatting, whitespace, or comment placement. Without this, content addressing is meaningless."))
|
||||
|
||||
(~docs/subsection :title "The Problem"
|
||||
(p "Currently " (code "serialize(body, pretty=True)") " produces readable SX source from the parsed AST. But serialization isn't fully canonical — it depends on the internal representation order, and there's no normalization pass. Two semantically identical components formatted differently would produce different hashes.")
|
||||
(p "We need a " (strong "canonical form") " that strips all variance:"))
|
||||
|
||||
(~docs/subsection :title "Canonical Form Rules"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Strip comments.") " Comments are parsing artifacts, not part of the AST. The serializer already ignores them (it works from the parsed tree), but any future comment-preserving parser must not affect canonical output.")
|
||||
(li (strong "Normalize whitespace.") " Single space between tokens, newline before each top-level form in a body. No trailing whitespace. No blank lines.")
|
||||
(li (strong "Sort keyword arguments alphabetically.") " In component calls: " (code "(~card :class \"x\" :title \"y\")") " not " (code "(~card :title \"y\" :class \"x\")") ". In dict literals: " (code "{:a 1 :b 2}") " not " (code "{:b 2 :a 1}") ".")
|
||||
@@ -93,13 +93,13 @@
|
||||
|
||||
(~docs/section :title "Phase 2: CID Computation" :id "cid-computation"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Every component gets a stable, unique content identifier. Same source → same CID, always. Different source → different CID, always."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Every component gets a stable, unique content identifier. Same source → same CID, always. Different source → different CID, always."))
|
||||
|
||||
(~docs/subsection :title "CID Format"
|
||||
(p "Use " (a :href "https://github.com/multiformats/cid" :class "text-violet-700 underline" "CIDv1") " with:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(p "Use " (a :href "https://github.com/multiformats/cid" (~tw :tokens "text-violet-700 underline") "CIDv1") " with:")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Hash function:") " SHA3-256 (already used by artdag for content addressing)")
|
||||
(li (strong "Codec:") " raw (the content is the canonical SX source bytes, not a DAG-PB wrapper)")
|
||||
(li (strong "Base encoding:") " base32lower for URL-safe representation (" (code "bafy...") " prefix)"))
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
(~docs/subsection :title "CID Stability"
|
||||
(p "A component's CID changes when and only when its " (strong "semantics") " change:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Reformatting the " (code ".sx") " source file → same AST → same canonical form → " (strong "same CID"))
|
||||
(li "Adding a comment → stripped by parser → same AST → " (strong "same CID"))
|
||||
(li "Changing a class name in the body → different AST → " (strong "different CID"))
|
||||
@@ -125,14 +125,14 @@
|
||||
|
||||
(~docs/section :title "Phase 3: Component Manifest" :id "manifest"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Metadata that travels with a CID — what a component needs, what it provides, whether it's safe to run. Enough information to resolve, validate, and render without fetching the source first."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Metadata that travels with a CID — what a component needs, what it provides, whether it's safe to run. Enough information to resolve, validate, and render without fetching the source first."))
|
||||
|
||||
(~docs/subsection :title "Manifest Structure"
|
||||
(~docs/code :src (highlight ";; Component manifest — published alongside the source\n(SxComponent\n :name \"~product-card\"\n :cid \"bafy...productcard\"\n :source-bytes 847\n :params (:title :price :image-url)\n :has-children true\n :pure true\n :deps (\n {:name \"~card\" :cid \"bafy...card\"}\n {:name \"~price-tag\" :cid \"bafy...pricetag\"}\n {:name \"~lazy-image\" :cid \"bafy...lazyimg\"})\n :css-atoms (:border :rounded :p-4 :text-sm :font-bold\n :text-green-700 :line-through :text-stone-400)\n :author \"https://rose-ash.com/apps/market\"\n :published \"2026-03-06T14:30:00Z\")" "lisp"))
|
||||
(p "Key fields:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code ":cid") " — content address of the canonical serialized source")
|
||||
(li (code ":deps") " — dependency CIDs, not just names. A consumer can recursively resolve the entire tree by CID without name ambiguity")
|
||||
(li (code ":pure") " — pre-computed purity flag. The consumer " (em "re-verifies") " this after fetching (never trust the manifest alone), but it enables fast rejection of IO-dependent components before downloading")
|
||||
@@ -150,13 +150,13 @@
|
||||
|
||||
(~docs/section :title "Phase 4: IPFS Storage & Resolution" :id "ipfs"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Components live on IPFS. Any browser can fetch them by CID. No origin server needed. No CDN. No DNS. The content network IS the distribution network."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Components live on IPFS. Any browser can fetch them by CID. No origin server needed. No CDN. No DNS. The content network IS the distribution network."))
|
||||
|
||||
(~docs/subsection :title "Server-Side: Publication"
|
||||
(p "On component registration (startup or hot-reload), the server:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Computes canonical form and CID")
|
||||
(li "Checks " (code "IPFSPin") " — if CID already pinned, skip (content can't have changed)")
|
||||
(li "Pins canonical source to IPFS (async Celery task, same pattern as artdag)")
|
||||
@@ -171,39 +171,39 @@
|
||||
(p "The cache-forever semantics are the key insight: because CIDs are content-addressed, a cached component " (strong "can never be stale") ". If the source changes, it gets a new CID. Old CIDs remain valid forever. There is no cache invalidation problem."))
|
||||
|
||||
(~docs/subsection :title "Resolution Cascade"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Lookup")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Latency")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "When")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Lookup")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Latency")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "When")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "1. Component env")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "(env-has? env cid)")
|
||||
(td :class "px-3 py-2 text-stone-600" "0ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "Already loaded this session"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "2. localStorage")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "localStorage[\"sx-cid:\" + cid]")
|
||||
(td :class "px-3 py-2 text-stone-600" "<1ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "Previously fetched, persists across sessions"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "3. Origin server")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "GET /sx/components?cid=bafy...")
|
||||
(td :class "px-3 py-2 text-stone-600" "~20ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "Same-origin component, not yet cached"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "4. IPFS gateway")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "GET https://gateway/ipfs/{cid}")
|
||||
(td :class "px-3 py-2 text-stone-600" "~200ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "Foreign component, federated content"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "5. Local IPFS node")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "ipfs cat {cid}")
|
||||
(td :class "px-3 py-2 text-stone-600" "~5ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "User runs own IPFS node (power users)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "1. Component env")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "(env-has? env cid)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "0ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Already loaded this session"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "2. localStorage")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "localStorage[\"sx-cid:\" + cid]")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "<1ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Previously fetched, persists across sessions"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "3. Origin server")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "GET /sx/components?cid=bafy...")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~20ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Same-origin component, not yet cached"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "4. IPFS gateway")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "GET https://gateway/ipfs/{cid}")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~200ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Foreign component, federated content"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "5. Local IPFS node")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "ipfs cat {cid}")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~5ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "User runs own IPFS node (power users)")))))
|
||||
(p "Layer 5 is optional — checked between 2 and 3 if " (code "window.ipfs") " or a local gateway is detected. For most users, layers 1-4 cover all cases.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -212,9 +212,9 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Security Model" :id "security"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "The hard part")
|
||||
(p :class "text-violet-800" "Loading code from the network is the web's original sin. Content-addressed components are safe because of three structural guarantees — not policies, not trust, not sandboxes that can be escaped."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The hard part")
|
||||
(p (~tw :tokens "text-violet-800") "Loading code from the network is the web's original sin. Content-addressed components are safe because of three structural guarantees — not policies, not trust, not sandboxes that can be escaped."))
|
||||
|
||||
(~docs/subsection :title "Guarantee 1: Purity is Structural"
|
||||
(p "SX boundary enforcement isn't a runtime sandbox — it's a registration-time structural check. When a component is loaded from IPFS and parsed, " (code "compute_all_io_refs()") " walks its entire AST and transitive dependencies. If " (em "any") " node references an IO primitive, the component is classified as IO-dependent and " (strong "rejected for untrusted registration."))
|
||||
@@ -227,40 +227,40 @@
|
||||
|
||||
(~docs/subsection :title "Guarantee 3: Evaluation Limits"
|
||||
(p "Pure doesn't mean terminating. A component could contain an infinite loop or exponential recursion. SX evaluators enforce step limits:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Max eval steps:") " configurable per context. Untrusted components get a lower limit than local ones.")
|
||||
(li (strong "Max recursion depth:") " prevents stack exhaustion.")
|
||||
(li (strong "Max output size:") " prevents a component from producing gigabytes of DOM nodes."))
|
||||
(p "Exceeding any limit halts evaluation and returns an error node. The worst case is wasted CPU — never data exfiltration, never unauthorized IO."))
|
||||
|
||||
(~docs/subsection :title "Trust Tiers"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Tier")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Source")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Allowed")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Eval limits")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Tier")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Source")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Allowed")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Eval limits")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Local")
|
||||
(td :class "px-3 py-2 text-stone-700" "Server's own " (code ".sx") " files")
|
||||
(td :class "px-3 py-2 text-stone-700" "Pure + IO primitives + page helpers")
|
||||
(td :class "px-3 py-2 text-stone-600" "None (trusted)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Followed")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components from followed AP actors")
|
||||
(td :class "px-3 py-2 text-stone-700" "Pure only (IO rejected)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Standard limits"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Federated")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components from any IPFS source")
|
||||
(td :class "px-3 py-2 text-stone-700" "Pure only (IO rejected)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Strict limits"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Local")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Server's own " (code ".sx") " files")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pure + IO primitives + page helpers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None (trusted)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Followed")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components from followed AP actors")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pure only (IO rejected)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Standard limits"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Federated")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components from any IPFS source")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pure only (IO rejected)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Strict limits"))))))
|
||||
|
||||
(~docs/subsection :title "What Can Go Wrong"
|
||||
(p "Honest accounting of the attack surface:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Visual spoofing:") " A malicious component could render UI that looks like a login form. Mitigation: untrusted components render inside a visually distinct container with origin attribution.")
|
||||
(li (strong "CSS abuse:") " A component's CSS atoms could interfere with page layout. Mitigation: scoped CSS — untrusted components' classes are namespaced.")
|
||||
(li (strong "Resource exhaustion:") " A component could be expensive to evaluate. Mitigation: step limits, timeout, lazy rendering for off-screen components.")
|
||||
@@ -273,14 +273,14 @@
|
||||
|
||||
(~docs/section :title "Phase 6: Wire Format & Prefetch Integration" :id "wire-format"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Pages and SX responses reference components by CID. The prefetch system resolves them from the most efficient source. Components become location-independent."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Pages and SX responses reference components by CID. The prefetch system resolves them from the most efficient source. Components become location-independent."))
|
||||
|
||||
(~docs/subsection :title "CID References in Page Registry"
|
||||
(p "The page registry (shipped to the client as " (code "<script type=\"text/sx-pages\">") ") currently lists deps by name. Extend to include CIDs:")
|
||||
(~docs/code :src (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :deps ({:name \"~essay-foo\" :cid \"bafy...essay\"}\n {:name \"~doc-code\" :cid \"bafy...doccode\"})\n :content \"(case slug ...)\" :closure {}}" "lisp"))
|
||||
(p "The " (a :href "/sx/(etc.(plan.predictive-prefetch))" :class "text-violet-700 underline" "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint."))
|
||||
(p "The " (a :href "/sx/(etc.(plan.predictive-prefetch))" (~tw :tokens "text-violet-700 underline") "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint."))
|
||||
|
||||
(~docs/subsection :title "SX Response Component Headers"
|
||||
(p "Currently, " (code "SX-Components") " header lists loaded component names. Extend to support CIDs:")
|
||||
@@ -298,9 +298,9 @@
|
||||
|
||||
(~docs/section :title "Phase 7: Sharing & Discovery" :id "sharing"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Servers publish component collections via AP. Other servers follow them. Like npm, but federated, content-addressed, and structurally safe."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Servers publish component collections via AP. Other servers follow them. Like npm, but federated, content-addressed, and structurally safe."))
|
||||
|
||||
(~docs/subsection :title "Component Registry as AP Actor"
|
||||
(p "Each server exposes a component registry actor:")
|
||||
@@ -315,7 +315,7 @@
|
||||
|
||||
(~docs/subsection :title "Name Resolution"
|
||||
(p "Names are human-friendly aliases for CIDs. The same name on different servers can refer to different components (different CIDs). Conflict resolution is simple:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Local wins:") " If the server defines " (code "~card") ", that definition takes precedence over any federated " (code "~card") ".")
|
||||
(li (strong "CID pinning:") " When referencing a federated component, pin the CID. " (code "(:name \"~card\" :cid \"bafy...abc\")") " — the name is informational, the CID is authoritative.")
|
||||
(li (strong "No global namespace:") " There is no \"npm\" that owns " (code "~card") ". Names are scoped to the server that defines them. CIDs are global."))))
|
||||
@@ -327,78 +327,78 @@
|
||||
(~docs/section :title "Spec Modules" :id "spec-modules"
|
||||
(p "Per the SX host architecture principle, all content-addressing logic is specced in " (code ".sx") " files and bootstrapped:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Spec module")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Functions")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Platform obligations")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Spec module")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Functions")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Platform obligations")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "canonical.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "canonical-serialize") ", " (code "canonical-number") ", " (code "escape-canonical"))
|
||||
(td :class "px-3 py-2 text-stone-600" "None — pure string operations"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "cid.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "component-cid") ", " (code "verify-cid") ", " (code "cid-to-string") ", " (code "parse-cid"))
|
||||
(td :class "px-3 py-2 text-stone-600" (code "sha3-256") ", " (code "encode-base32") ", " (code "encode-utf8")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "resolve.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "resolve-component-by-cid") ", " (code "resolve-deps-recursive") ", " (code "register-untrusted-component"))
|
||||
(td :class "px-3 py-2 text-stone-600" (code "local-storage-get/set") ", " (code "fetch-cid") ", " (code "register-component-source"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "canonical.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "canonical-serialize") ", " (code "canonical-number") ", " (code "escape-canonical"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None — pure string operations"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "cid.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "component-cid") ", " (code "verify-cid") ", " (code "cid-to-string") ", " (code "parse-cid"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") (code "sha3-256") ", " (code "encode-base32") ", " (code "encode-utf8")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "resolve.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "resolve-component-by-cid") ", " (code "resolve-deps-recursive") ", " (code "register-untrusted-component"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") (code "local-storage-get/set") ", " (code "fetch-cid") ", " (code "register-component-source"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Critical files
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Critical Files" :id "critical-files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Role")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/canonical.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Canonical serialization spec (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/cid.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "CID computation and verification spec (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/types.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Add " (code "cid") " and " (code "dep_cids") " to Component")
|
||||
(td :class "px-3 py-2 text-stone-600" "2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/jinja_bridge.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Add " (code "compute_all_cids()") " to registration lifecycle")
|
||||
(td :class "px-3 py-2 text-stone-600" "2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/models/federation.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFSPin records for component CIDs")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/resolve.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client-side CID resolution cascade (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "CIDs in page registry, " (code "/sx/components?cid=") " endpoint")
|
||||
(td :class "px-3 py-2 text-stone-600" "6"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/orchestration.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "CID-aware prefetch in resolution cascade")
|
||||
(td :class "px-3 py-2 text-stone-600" "6"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/activitypub.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Component registry actor, Webfinger extension")
|
||||
(td :class "px-3 py-2 text-stone-600" "7"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/boundary.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Trust tier enforcement for untrusted components")
|
||||
(td :class "px-3 py-2 text-stone-600" "5"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/canonical.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Canonical serialization spec (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/cid.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CID computation and verification spec (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/types.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Add " (code "cid") " and " (code "dep_cids") " to Component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/jinja_bridge.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Add " (code "compute_all_cids()") " to registration lifecycle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/models/federation.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFSPin records for component CIDs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/resolve.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client-side CID resolution cascade (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/helpers.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CIDs in page registry, " (code "/sx/components?cid=") " endpoint")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "6"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/orchestration.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CID-aware prefetch in resolution cascade")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "6"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/activitypub.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component registry actor, Webfinger extension")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "7"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/boundary.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Trust tier enforcement for untrusted components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "5"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Relationship
|
||||
@@ -406,13 +406,13 @@
|
||||
|
||||
(~docs/section :title "Relationships" :id "relationships"
|
||||
(p "This plan is the foundation for several other plans and roadmaps:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (a :href "/sx/(etc.(plan.sx-activity))" :class "text-violet-700 underline" "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.")
|
||||
(li (a :href "/sx/(etc.(plan.predictive-prefetch))" :class "text-violet-700 underline" "Predictive prefetching") " gains CID-based resolution — the " (code "/sx/components") " endpoint and IPFS gateway become alternative resolution paths in the prefetch cascade.")
|
||||
(li (a :href "/sx/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "Isomorphic architecture") " Phase 1 (component distribution) is enhanced — CIDs make per-page bundles verifiable and cross-server shareable.")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (a :href "/sx/(etc.(plan.sx-activity))" (~tw :tokens "text-violet-700 underline") "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.")
|
||||
(li (a :href "/sx/(etc.(plan.predictive-prefetch))" (~tw :tokens "text-violet-700 underline") "Predictive prefetching") " gains CID-based resolution — the " (code "/sx/components") " endpoint and IPFS gateway become alternative resolution paths in the prefetch cascade.")
|
||||
(li (a :href "/sx/(etc.(plan.isomorphic-architecture))" (~tw :tokens "text-violet-700 underline") "Isomorphic architecture") " Phase 1 (component distribution) is enhanced — CIDs make per-page bundles verifiable and cross-server shareable.")
|
||||
(li "The SX-Activity vision of " (strong "serverless applications on IPFS") " depends entirely on this plan. Without content-addressed components, applications can't be pinned to IPFS as self-contained artifacts."))
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "deps.sx (complete), boundary enforcement (complete), IPFS infrastructure (exists in artdag, needs wiring to web platform)."))))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Depends on: ") "deps.sx (complete), boundary enforcement (complete), IPFS infrastructure (exists in artdag, needs wiring to web platform)."))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Predictive Component Prefetching
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
(p "Every served SX endpoint should point back to its spec. The spec CIDs identify the exact evaluator, renderer, parser, and primitives that produced the output. This makes every endpoint " (strong "fully executable") " — anyone with the CIDs can independently reproduce the result.")
|
||||
(p "But evaluating spec files from source on every cold start is wasteful. The specs are pure — same source always produces the same evaluated environment. So we can serialize the " (em "evaluated") " environment as a content-addressed image: all defcomps, defmacros, bound symbols, resolved closures frozen into a single artifact. The image CID is a function of its contents. Load the image, skip evaluation, get the same result.")
|
||||
(p "The chain becomes:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Served page") " → CID of the spec that defines its semantics")
|
||||
(li (strong "Spec CID") " → the evaluator, renderer, parser, primitives that any conforming host can execute")
|
||||
(li (strong "Image CID") " → the pre-evaluated environment, a cache of (2) that any conforming host can deserialize"))
|
||||
(p "The spec is the truth. The image is a verified cache. The bootstrapper that compiled the spec for a particular host is an implementation detail — irrelevant to the content address.")
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "Prior art")
|
||||
(p :class "text-violet-800" (a :href "https://github.com/KinaKnowledge/juno-lang" :class "underline" "Juno") " — a self-hosted Lisp-to-JS compiler — implements image persistence: serialize a running environment, restore it later, even bundle it as a standalone HTML document. Their Seedling IDE saves/restores entire development sessions as images. SX can do this more rigorously because our images are content-addressed (Juno's are not) and our components are boundary-enforced pure (Juno's are not).")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "Prior art")
|
||||
(p (~tw :tokens "text-violet-800") (a :href "https://github.com/KinaKnowledge/juno-lang" (~tw :tokens "underline") "Juno") " — a self-hosted Lisp-to-JS compiler — implements image persistence: serialize a running environment, restore it later, even bundle it as a standalone HTML document. Their Seedling IDE saves/restores entire development sessions as images. SX can do this more rigorously because our images are content-addressed (Juno's are not) and our components are boundary-enforced pure (Juno's are not).")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; What gets serialized
|
||||
@@ -25,36 +25,36 @@
|
||||
(~docs/section :title "What Gets Serialized" :id "what"
|
||||
(p "An environment image is a snapshot of everything produced by evaluating the spec files. Not the source — the result.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Category")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Contents")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Source")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Category")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Contents")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Source")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Components")
|
||||
(td :class "px-3 py-2 text-stone-700" "All " (code "defcomp") " definitions — name, params, body AST, closure bindings, CID, deps, css_classes")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "Service .sx files + shared/sx/templates/"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Macros")
|
||||
(td :class "px-3 py-2 text-stone-700" "All " (code "defmacro") " definitions — name, params, body AST, closure")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "Spec files"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Bindings")
|
||||
(td :class "px-3 py-2 text-stone-700" "Top-level " (code "define") " values — constants, lookup tables, configuration")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "Spec files + service .sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitives")
|
||||
(td :class "px-3 py-2 text-stone-700" "Registry of pure primitive names (not implementations — those are host-specific)")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "primitives.sx, boundary.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Spec provenance")
|
||||
(td :class "px-3 py-2 text-stone-700" "CIDs of the spec files that produced this image")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "eval.sx, render.sx, parser.sx, ...")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "All " (code "defcomp") " definitions — name, params, body AST, closure bindings, CID, deps, css_classes")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "Service .sx files + shared/sx/templates/"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Macros")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "All " (code "defmacro") " definitions — name, params, body AST, closure")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "Spec files"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bindings")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Top-level " (code "define") " values — constants, lookup tables, configuration")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "Spec files + service .sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitives")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Registry of pure primitive names (not implementations — those are host-specific)")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "primitives.sx, boundary.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Spec provenance")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CIDs of the spec files that produced this image")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "eval.sx, render.sx, parser.sx, ...")))))
|
||||
|
||||
(p "Notably " (strong "absent") " from the image:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "IO primitive implementations") " — these are host-specific. The image records their " (em "names") " (for boundary enforcement) but not their code.")
|
||||
(li (strong "Page helpers") " — same reason. " (code "fetch-data") ", " (code "app-url") " etc. are registered by the host app at startup.")
|
||||
(li (strong "Runtime state") " — no request context, no DB connections, no session data. The image is a pure function's result, not a running process snapshot.")))
|
||||
@@ -69,7 +69,7 @@
|
||||
(~docs/code :src (highlight "(sx-image\n :version 1\n :spec-cids {:eval \"bafy...eval\"\n :render \"bafy...render\"\n :parser \"bafy...parser\"\n :primitives \"bafy...prims\"\n :boundary \"bafy...boundary\"\n :signals \"bafy...signals\"}\n\n :components (\n (defcomp ~plans/environment-images/card (&key title subtitle &rest children)\n (div :class \"card\" (h2 title) (when subtitle (p subtitle)) children))\n (defcomp ~plans/environment-images/nav (&key items current)\n (nav :class \"nav\" (map (fn (item) ...) items)))\n ;; ... all registered components\n )\n\n :macros (\n (defmacro when (test &rest body)\n (list 'if test (cons 'begin body) nil))\n ;; ... all macros\n )\n\n :bindings (\n (define void-elements (list \"area\" \"base\" \"br\" \"col\" ...))\n (define boolean-attrs (list \"checked\" \"disabled\" ...))\n ;; ... all top-level defines\n )\n\n :primitive-names (\"str\" \"+\" \"-\" \"*\" \"/\" \"=\" \"<\" \">\" ...)\n :io-names (\"fetch-data\" \"call-action\" \"app-url\" ...))" "lisp"))
|
||||
|
||||
(p "The " (code ":spec-cids") " field is the key. It links this image back to the exact spec that produced it. Anyone can verify the image by:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Fetch the spec files by CID")
|
||||
(li "Evaluate them with a conforming evaluator")
|
||||
(li "Serialize the resulting environment")
|
||||
@@ -98,25 +98,25 @@
|
||||
(~docs/code :src (highlight "HTTP/1.1 200 OK\nContent-Type: text/html\nSX-Spec: bafy...eval,bafy...render,bafy...parser,bafy...prims\nSX-Image: bafy...image\nSX-Page-Components: ~plans/environment-images/card:bafy...card,~plans/environment-images/nav:bafy...nav" "http"))
|
||||
|
||||
(p "Three levels of verification:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Level")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What you verify")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Trust assumption")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Level")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What you verify")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Trust assumption")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Component")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fetch " (code "~plans/environment-images/card") " by CID, verify hash")
|
||||
(td :class "px-3 py-2 text-stone-600" "Trust the evaluator"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Image")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fetch image by CID, deserialize, re-render page")
|
||||
(td :class "px-3 py-2 text-stone-600" "Trust the image producer"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Spec")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fetch specs by CID, re-evaluate, compare image CID")
|
||||
(td :class "px-3 py-2 text-stone-600" "Trust only the hash function")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fetch " (code "~plans/environment-images/card") " by CID, verify hash")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Trust the evaluator"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Image")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fetch image by CID, deserialize, re-render page")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Trust the image producer"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fetch specs by CID, re-evaluate, compare image CID")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Trust only the hash function")))))
|
||||
|
||||
(p "Level 3 is the nuclear option — full independent verification from source. It's expensive but proves the entire chain. Most consumers will operate at level 1 (component verification) or level 2 (image verification)."))
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
(p "The practical motivation: evaluating all spec files + service components on every server restart is slow. An image eliminates this.")
|
||||
|
||||
(~docs/subsection :title "Server Startup"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li "Check if a cached image exists for the current spec CIDs")
|
||||
(li "If yes: deserialize the image (fast — parsing a single file, no evaluation)")
|
||||
(li "If no: evaluate spec files from source, build image, cache it")
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
(~docs/subsection :title "Client Boot"
|
||||
(p "The client already caches component definitions in localStorage keyed by bundle hash. Images extend this: cache the entire evaluated environment, not just individual components.")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Page ships " (code "SX-Image") " header with image CID")
|
||||
(li "Client checks localStorage for " (code "sx-image:{cid}"))
|
||||
(li "If hit: deserialize and boot (no component-by-component parsing)")
|
||||
@@ -157,9 +157,9 @@
|
||||
|
||||
(p "This document is its own CID. Pin it to IPFS and it's a permanent, executable, verifiable application. No origin server, no CDN, no DNS. The content network is the deployment target.")
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4 mt-4"
|
||||
(p :class "text-amber-900 font-medium" "Juno comparison")
|
||||
(p :class "text-amber-800" "Juno's Seedling IDE already does this — export a running environment as a standalone HTML file. But their images are opaque JavaScript blobs serialized by the runtime. SX images are " (strong "s-expressions") " — parseable, inspectable, content-addressable. You can diff two SX images and see exactly what changed. You can extract a single component from an image by CID. You can merge images from different sources by composing their component lists. The format IS the tooling.")))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-amber-900 font-medium") "Juno comparison")
|
||||
(p (~tw :tokens "text-amber-800") "Juno's Seedling IDE already does this — export a running environment as a standalone HTML file. But their images are opaque JavaScript blobs serialized by the runtime. SX images are " (strong "s-expressions") " — parseable, inspectable, content-addressable. You can diff two SX images and see exactly what changed. You can extract a single component from an image by CID. You can merge images from different sources by composing their component lists. The format IS the tooling.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Namespace scoping
|
||||
@@ -172,29 +172,29 @@
|
||||
|
||||
(p "Resolution: " (code "market/~plans/environment-images/product-card") " → look in market image first, then fall through to the shared image (via " (code ":extends") "). Each service produces its own image, layered on top of the shared base.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Image")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Contents")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Extends")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Image")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Contents")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Extends")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "bafy...shared")
|
||||
(td :class "px-3 py-2 text-stone-700" "~plans/environment-images/card, ~plans/environment-images/nav, ~nav-data/section-nav, ~docs/page, ~docs/code — shared components from " (code "shared/sx/templates/"))
|
||||
(td :class "px-3 py-2 text-stone-600" "None (root)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "bafy...blog")
|
||||
(td :class "px-3 py-2 text-stone-700" "~shared:cards/post-card, ~post-body, ~tag-list — blog-specific from " (code "blog/sx/"))
|
||||
(td :class "px-3 py-2 text-stone-600" "bafy...shared"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "bafy...market")
|
||||
(td :class "px-3 py-2 text-stone-700" "~plans/environment-images/product-card, ~plans/environment-images/price-tag, ~shared:fragments/cart-mini — market-specific from " (code "market/sx/"))
|
||||
(td :class "px-3 py-2 text-stone-600" "bafy...shared"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "bafy...sx-docs")
|
||||
(td :class "px-3 py-2 text-stone-700" "~docs/section, ~examples/source, plans, essays — sx docs from " (code "sx/sx/"))
|
||||
(td :class "px-3 py-2 text-stone-600" "bafy...shared")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "bafy...shared")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~plans/environment-images/card, ~plans/environment-images/nav, ~nav-data/section-nav, ~docs/page, ~docs/code — shared components from " (code "shared/sx/templates/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None (root)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "bafy...blog")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~shared:cards/post-card, ~post-body, ~tag-list — blog-specific from " (code "blog/sx/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bafy...shared"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "bafy...market")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~plans/environment-images/product-card, ~plans/environment-images/price-tag, ~shared:fragments/cart-mini — market-specific from " (code "market/sx/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bafy...shared"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "bafy...sx-docs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~docs/section, ~examples/source, plans, essays — sx docs from " (code "sx/sx/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bafy...shared")))))
|
||||
|
||||
(p "The " (code ":extends") " field is a CID, not a name. Image composition is content-addressed: changing the shared image produces a new shared CID, which invalidates all service images that extend it. Exactly the right cascading behavior."))
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 1: Image Serialization"
|
||||
(p "Spec module " (code "image.sx") " — serialize and deserialize evaluated environments.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "serialize-environment") " — walk the env, extract components/macros/bindings, produce " (code "(sx-image ...)") " form")
|
||||
(li (code "deserialize-image") " — parse image, reconstitute components/macros/bindings into env")
|
||||
(li (code "image-cid") " — canonical-serialize the image form, hash → CID")
|
||||
@@ -225,14 +225,14 @@
|
||||
|
||||
(~docs/subsection :title "Phase 2: Spec Provenance"
|
||||
(p "Compute CIDs for all spec files at startup. Attach to environment metadata.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Hash each spec file's canonical source at load time")
|
||||
(li "Store in env metadata as " (code ":spec-cids") " dict")
|
||||
(li "Include in image serialization")))
|
||||
|
||||
(~docs/subsection :title "Phase 3: Server-Side Caching"
|
||||
(p "Cache images on disk keyed by spec CIDs. Skip evaluation on warm restart.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "On startup: compute spec CIDs → derive expected image CID → check cache")
|
||||
(li "Cache hit: deserialize (parse only, no eval)")
|
||||
(li "Cache miss: evaluate specs, serialize image, write cache")
|
||||
@@ -240,7 +240,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 4: Client Images"
|
||||
(p "Ship image CID in response headers. Client caches full env in localStorage.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "SX-Image") " response header with image CID")
|
||||
(li "Client boot checks localStorage for cached image")
|
||||
(li "Cache hit: deserialize, skip per-component fetch/parse")
|
||||
@@ -248,7 +248,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 5: Standalone Export"
|
||||
(p "Generate self-contained HTML with inlined image. Pin to IPFS.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Inline " (code "(sx-image ...)") " as " (code "<script type=\"text/sx-image\">"))
|
||||
(li "Inline page definitions as " (code "<script type=\"text/sx-pages\">"))
|
||||
(li "Include sx-ref.js (or link to its CID)")
|
||||
@@ -256,7 +256,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 6: Namespaced Images"
|
||||
(p "Per-service images with " (code ":extends") " for layered composition.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Shared image: components from " (code "shared/sx/templates/"))
|
||||
(li "Service images: extend shared, add service-specific components")
|
||||
(li "Resolution: service image → shared image → primitives")
|
||||
@@ -268,37 +268,37 @@
|
||||
|
||||
(~docs/section :title "Dependencies" :id "dependencies"
|
||||
(p "What must exist before this plan can execute:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Dependency")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Plan")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Dependency")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Plan")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Canonical serialization")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started"))
|
||||
(td :class "px-3 py-2" (a :href "/sx/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component CIDs")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started"))
|
||||
(td :class "px-3 py-2" (a :href "/sx/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Purity verification")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete"))
|
||||
(td :class "px-3 py-2 text-stone-600" "deps.sx + boundary.py"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Self-hosting spec")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete"))
|
||||
(td :class "px-3 py-2 text-stone-600" "eval.sx, render.sx, parser.sx, ..."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Self-hosting bootstrappers")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete"))
|
||||
(td :class "px-3 py-2 text-stone-600" "py.sx, js.sx — G0 == G1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS infrastructure")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Exists"))
|
||||
(td :class "px-3 py-2 text-stone-600" "artdag L1/L2, IPFSPin model")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Canonical serialization")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-red-700 font-medium") "Not started"))
|
||||
(td (~tw :tokens "px-3 py-2") (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "text-violet-700 underline") "Content-Addressed Components") " Phase 1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component CIDs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-red-700 font-medium") "Not started"))
|
||||
(td (~tw :tokens "px-3 py-2") (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "text-violet-700 underline") "Content-Addressed Components") " Phase 2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Purity verification")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "deps.sx + boundary.py"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Self-hosting spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "eval.sx, render.sx, parser.sx, ..."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Self-hosting bootstrappers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "py.sx, js.sx — G0 == G1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS infrastructure")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Exists"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "artdag L1/L2, IPFSPin model")))))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Builds on: ") (a :href "/sx/(etc.(plan.content-addressed-components))" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/sx/(etc.(plan.sx-activity))" :class "underline" "SX-Activity") " (serverless applications on IPFS).")))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Builds on: ") (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "underline") "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" (~tw :tokens "underline") "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/sx/(etc.(plan.sx-activity))" (~tw :tokens "underline") "SX-Activity") " (serverless applications on IPFS).")))))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(defcomp ~plans/foundations/plan-foundations-content ()
|
||||
(~docs/page :title "Foundations \u2014 The Computational Floor"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Scoped effects unify everything SX does. But they are not the bottom. "
|
||||
"Beneath them is a hierarchy of increasingly fundamental primitives, "
|
||||
"terminating at three irreducible things: an expression, an environment, and a continuation. "
|
||||
@@ -15,7 +15,7 @@
|
||||
;; The hierarchy
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "The Hierarchy")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "The Hierarchy")
|
||||
|
||||
(p "Every layer is definable in terms of the one below. "
|
||||
"No layer can be decomposed without the layer beneath it.")
|
||||
@@ -35,62 +35,62 @@
|
||||
;; What we built (status)
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "What We Built")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "What We Built")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Layer")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Spec files")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "What it provides")
|
||||
(th :class "text-left pb-2 font-semibold" "Tests")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Layer")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Spec files")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "What it provides")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Tests")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "0 \u2014 CEK")
|
||||
(td :class "pr-4 font-mono text-xs" "cek.sx, frames.sx")
|
||||
(td :class "pr-4" "Explicit step function, 20+ frame types, cek-call dispatch, CEK-native HO forms")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "0 \u2014 CEK")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "cek.sx, frames.sx")
|
||||
(td (~tw :tokens "pr-4") "Explicit step function, 20+ frame types, cek-call dispatch, CEK-native HO forms")
|
||||
(td "43 CEK + 26 reactive"))
|
||||
(tr (td :class "pr-4 py-1" "1 \u2014 Continuations")
|
||||
(td :class "pr-4 font-mono text-xs" "continuations.sx, callcc.sx")
|
||||
(td :class "pr-4" "shift/reset (delimited), call/cc (full), ReactiveResetFrame + DerefFrame")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "1 \u2014 Continuations")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "continuations.sx, callcc.sx")
|
||||
(td (~tw :tokens "pr-4") "shift/reset (delimited), call/cc (full), ReactiveResetFrame + DerefFrame")
|
||||
(td "Continuation tests"))
|
||||
(tr (td :class "pr-4 py-1" "2 \u2014 Effect signatures")
|
||||
(td :class "pr-4 font-mono text-xs" "boundary.sx, eval.sx")
|
||||
(td :class "pr-4" ":effects annotations on define, boundary enforcement at startup")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "2 \u2014 Effect signatures")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "boundary.sx, eval.sx")
|
||||
(td (~tw :tokens "pr-4") ":effects annotations on define, boundary enforcement at startup")
|
||||
(td "Boundary validation"))
|
||||
(tr (td :class "pr-4 py-1" "3 \u2014 Scoped effects")
|
||||
(td :class "pr-4 font-mono text-xs" "eval.sx, adapters")
|
||||
(td :class "pr-4" "scope/provide/context/emit!/emitted, scope-push!/scope-pop!")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "3 \u2014 Scoped effects")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "eval.sx, adapters")
|
||||
(td (~tw :tokens "pr-4") "scope/provide/context/emit!/emitted, scope-push!/scope-pop!")
|
||||
(td "Scope integration"))
|
||||
(tr (td :class "pr-4 py-1" "4 \u2014 Patterns")
|
||||
(td :class "pr-4 font-mono text-xs" "signals.sx, adapter-dom.sx, engine.sx")
|
||||
(td :class "pr-4" "signal/deref/computed/effect/batch, island/lake, spread/collect")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4 \u2014 Patterns")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "signals.sx, adapter-dom.sx, engine.sx")
|
||||
(td (~tw :tokens "pr-4") "signal/deref/computed/effect/batch, island/lake, spread/collect")
|
||||
(td "20 signal + 26 CEK reactive")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Layer 0: The CEK machine
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Layer 0: The CEK Machine")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Layer 0: The CEK Machine")
|
||||
|
||||
(p "The CEK machine (Felleisen & Friedman, 1986) is a small-step evaluator with three registers:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Register")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Name")
|
||||
(th :class "text-left pb-2 font-semibold" "Meaning")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Register")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Name")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Meaning")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-mono" "C") (td :class "pr-4" "Control") (td "The expression being evaluated"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "E") (td :class "pr-4" "Environment") (td "The bindings in scope \u2014 names to values"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "K") (td :class "pr-4" "Kontinuation") (td "What to do with the result")))))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "C") (td (~tw :tokens "pr-4") "Control") (td "The expression being evaluated"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "E") (td (~tw :tokens "pr-4") "Environment") (td "The bindings in scope \u2014 names to values"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "K") (td (~tw :tokens "pr-4") "Kontinuation") (td "What to do with the result")))))
|
||||
|
||||
(p "Every step of evaluation is a transition: take the current (C, E, K), "
|
||||
"produce a new (C\u2032, E\u2032, K\u2032). That's it. That's all computation is.")
|
||||
|
||||
(p "Can you remove any register?")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Remove C") " \u2014 nothing to evaluate. No computation.")
|
||||
(li (strong "Remove E") " \u2014 names are meaningless. Lose abstraction. "
|
||||
"Every value must be literal, every function must be closed. No variables, no closures, no reuse.")
|
||||
@@ -99,13 +99,13 @@
|
||||
|
||||
(p "Three things, all necessary, none decomposable further.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "CEK in SX")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "CEK in SX")
|
||||
|
||||
(p "The CEK machine is the default evaluator on both client (JS) and server (Python). "
|
||||
"Every " (code "eval-expr") " call goes through " (code "cek-run") ". "
|
||||
"The spec lives in two files:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "frames.sx") " \u2014 20+ frame types (IfFrame, ArgFrame, MapFrame, ReactiveResetFrame, ...)")
|
||||
(li (code "cek.sx") " \u2014 step function, run loop, special form handlers, HO form handlers, cek-call"))
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
"It replaces the old " (code "invoke") " shim \u2014 SX lambdas go through " (code "cek-run") ", "
|
||||
"native callables through " (code "apply") ". One calling convention, bootstrapped identically to every host.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "CEK-native higher-order forms")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "CEK-native higher-order forms")
|
||||
|
||||
(p "All higher-order forms step element-by-element through the CEK machine:")
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
;; Layer 1: Delimited continuations
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Layer 1: Delimited Continuations")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Layer 1: Delimited Continuations")
|
||||
|
||||
(p "Delimited continuations (Felleisen 1988, Danvy & Filinski 1990) "
|
||||
"expose the K register as a first-class value:")
|
||||
@@ -149,7 +149,7 @@
|
||||
(code "shift") " says: \"give me everything between here and that boundary as a callable function.\" "
|
||||
"The captured continuation " (em "is") " a slice of the K register.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "Deref as shift")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "Deref as shift")
|
||||
|
||||
(p "The reactive payoff. " (code "deref") " inside a " (code "reactive-reset") " boundary "
|
||||
"is shift/reset applied to signals:")
|
||||
@@ -167,7 +167,7 @@
|
||||
(p "No explicit " (code "effect()") " wrapping needed. "
|
||||
"The continuation capture IS the subscription mechanism.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "The Filinski embedding")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "The Filinski embedding")
|
||||
|
||||
(p "Filinski (1994) proved that " (code "shift/reset") " can encode "
|
||||
(em "any") " monadic effect. State, exceptions, nondeterminism, I/O, "
|
||||
@@ -179,11 +179,11 @@
|
||||
;; The floor proof
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Why Layer 0 Is the Floor")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Why Layer 0 Is the Floor")
|
||||
|
||||
(p "Two independent arguments:")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "1. Church\u2013Turing")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "1. Church\u2013Turing")
|
||||
|
||||
(p "The Church\u2013Turing thesis: any effectively computable function can be computed by "
|
||||
"a Turing machine (equivalently, by the lambda calculus, equivalently, by the CEK machine). "
|
||||
@@ -193,11 +193,11 @@
|
||||
(p "This means CEK captures " (em "all") " computation. Adding more primitives doesn't let you "
|
||||
"compute anything new. It only changes what's " (em "convenient") " to express.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "2. Irreducibility of C, E, K")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "2. Irreducibility of C, E, K")
|
||||
|
||||
(p "The three registers are independently necessary:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "C without E") " = combinatory logic. Turing-complete but inhumane. "
|
||||
"No named bindings \u2014 everything via S, K, I combinators.")
|
||||
(li (strong "C without K") " = single expression evaluation. "
|
||||
@@ -213,47 +213,47 @@
|
||||
;; Digging deeper: what's next
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Progress")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Progress")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Step")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "What")
|
||||
(th :class "text-left pb-2 font-semibold" "Status")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Step")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "What")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Status")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "1")
|
||||
(td :class "pr-4" "Serializable CEK state")
|
||||
(td :class "text-emerald-600 font-semibold" "\u2714 Done \u2014 freeze-scope, freeze-signal, freeze-to-sx, thaw-from-sx"))
|
||||
(tr (td :class "pr-4 py-1" "2")
|
||||
(td :class "pr-4" "CEK stepping debugger")
|
||||
(td :class "text-emerald-600 font-semibold" "\u2714 Done \u2014 live island with reactive code view, DOM preview"))
|
||||
(tr (td :class "pr-4 py-1" "3")
|
||||
(td :class "pr-4" "Content-addressed computation")
|
||||
(td :class "text-emerald-600 font-semibold" "\u2714 Done \u2014 content-hash, freeze-to-cid, thaw-from-cid"))
|
||||
(tr (td :class "pr-4 py-1" "3.5")
|
||||
(td :class "pr-4" "Data representations")
|
||||
(td :class "text-stone-400" "Planned — byte buffers + typed structs"))
|
||||
(tr (td :class "pr-4 py-1" "3.7")
|
||||
(td :class "pr-4" "Verified components")
|
||||
(td :class "text-stone-400" "Planned — content-addressed UI trust"))
|
||||
(tr (td :class "pr-4 py-1" "4")
|
||||
(td :class "pr-4" "Concurrent CEK")
|
||||
(td :class "text-amber-600 font-semibold" "Spec complete — implementation next"))
|
||||
(tr (td :class "pr-4 py-1" "5")
|
||||
(td :class "pr-4" "Linear effects")
|
||||
(td :class "text-stone-400" "Future")))))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "1")
|
||||
(td (~tw :tokens "pr-4") "Serializable CEK state")
|
||||
(td (~tw :tokens "text-emerald-600 font-semibold") "\u2714 Done \u2014 freeze-scope, freeze-signal, freeze-to-sx, thaw-from-sx"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "2")
|
||||
(td (~tw :tokens "pr-4") "CEK stepping debugger")
|
||||
(td (~tw :tokens "text-emerald-600 font-semibold") "\u2714 Done \u2014 live island with reactive code view, DOM preview"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "3")
|
||||
(td (~tw :tokens "pr-4") "Content-addressed computation")
|
||||
(td (~tw :tokens "text-emerald-600 font-semibold") "\u2714 Done \u2014 content-hash, freeze-to-cid, thaw-from-cid"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "3.5")
|
||||
(td (~tw :tokens "pr-4") "Data representations")
|
||||
(td (~tw :tokens "text-stone-400") "Planned — byte buffers + typed structs"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "3.7")
|
||||
(td (~tw :tokens "pr-4") "Verified components")
|
||||
(td (~tw :tokens "text-stone-400") "Planned — content-addressed UI trust"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4")
|
||||
(td (~tw :tokens "pr-4") "Concurrent CEK")
|
||||
(td (~tw :tokens "text-amber-600 font-semibold") "Spec complete — implementation next"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "5")
|
||||
(td (~tw :tokens "pr-4") "Linear effects")
|
||||
(td (~tw :tokens "text-stone-400") "Future")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Step 3.5: Data Representations
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Step 3.5: Data Representations")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Step 3.5: Data Representations")
|
||||
|
||||
(p "Two primitives that sit below concurrency but above the raw CEK machine. "
|
||||
"Both are about how values are represented — enabling work that's currently host-only.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.5a Byte Buffers")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.5a Byte Buffers")
|
||||
|
||||
(p "Fixed-size mutable byte arrays. A small primitive surface that unlocks "
|
||||
"binary protocol parsing, image headers, wire formats, and efficient CID computation:")
|
||||
@@ -274,7 +274,7 @@
|
||||
|
||||
(p "Primitives:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "make-buffer") " — allocate N zero-filled bytes")
|
||||
(li (code "buffer-read-u8/u16/u32") ", " (code "buffer-read-i8/i16/i32") " — typed reads at offset")
|
||||
(li (code "buffer-write-u8!/u16!/u32!") " — typed writes at offset (big-endian default)")
|
||||
@@ -285,25 +285,25 @@
|
||||
|
||||
(p "Host mapping:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Primitive")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "JavaScript")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Python")
|
||||
(th :class "text-left pb-2 font-semibold" "OCaml")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Primitive")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "JavaScript")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Python")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "OCaml")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-mono" "make-buffer")
|
||||
(td :class "pr-4" "new ArrayBuffer(n)")
|
||||
(td :class "pr-4" "bytearray(n)")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "make-buffer")
|
||||
(td (~tw :tokens "pr-4") "new ArrayBuffer(n)")
|
||||
(td (~tw :tokens "pr-4") "bytearray(n)")
|
||||
(td "Bytes.create n"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "buffer-read-u32")
|
||||
(td :class "pr-4" "DataView.getUint32")
|
||||
(td :class "pr-4" "struct.unpack_from")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "buffer-read-u32")
|
||||
(td (~tw :tokens "pr-4") "DataView.getUint32")
|
||||
(td (~tw :tokens "pr-4") "struct.unpack_from")
|
||||
(td "Bytes.get_int32_be"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "buffer-slice")
|
||||
(td :class "pr-4" "new Uint8Array(buf, off, len)")
|
||||
(td :class "pr-4" "memoryview(buf)[off:off+len]")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "buffer-slice")
|
||||
(td (~tw :tokens "pr-4") "new Uint8Array(buf, off, len)")
|
||||
(td (~tw :tokens "pr-4") "memoryview(buf)[off:off+len]")
|
||||
(td "Bytes.sub buf off len")))))
|
||||
|
||||
(p "Byte buffers connect directly to content addressing (Step 3). "
|
||||
@@ -311,7 +311,7 @@
|
||||
"With buffers, the hash input can be a canonical binary representation — "
|
||||
"deterministic, compact, and consistent across hosts.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.5b Typed Structs")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.5b Typed Structs")
|
||||
|
||||
(p "Named product types with fixed fields. On interpreted hosts, structs are syntactic sugar over dicts. "
|
||||
"On compiled hosts (OCaml, Rust), they compile to unboxed records — no dict overhead, "
|
||||
@@ -332,7 +332,7 @@
|
||||
|
||||
(p "What " (code "defstruct") " generates:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "make-point") " — constructor (arity-checked)")
|
||||
(li (code "point?") " — type predicate")
|
||||
(li (code "point-x") ", " (code "point-y") " — field accessors")
|
||||
@@ -340,21 +340,21 @@
|
||||
|
||||
(p "The performance difference matters for compiled hosts:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Operation")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Dict (current)")
|
||||
(th :class "text-left pb-2 font-semibold" "Struct (compiled)")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Operation")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Dict (current)")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Struct (compiled)")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "Field access")
|
||||
(td :class "pr-4" "Hash lookup — O(1) amortized, cache-unfriendly")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Field access")
|
||||
(td (~tw :tokens "pr-4") "Hash lookup — O(1) amortized, cache-unfriendly")
|
||||
(td "Offset load — O(1) actual, single instruction"))
|
||||
(tr (td :class "pr-4 py-1" "Construction")
|
||||
(td :class "pr-4" "Allocate hash table + insert N entries")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Construction")
|
||||
(td (~tw :tokens "pr-4") "Allocate hash table + insert N entries")
|
||||
(td "Allocate N words, write sequentially"))
|
||||
(tr (td :class "pr-4 py-1" "Pattern match")
|
||||
(td :class "pr-4" "N string comparisons")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Pattern match")
|
||||
(td (~tw :tokens "pr-4") "N string comparisons")
|
||||
(td "Tag check + field projection")))))
|
||||
|
||||
(p "Typed structs connect to the gradual type system in " (code "types.sx") ". "
|
||||
@@ -365,7 +365,7 @@
|
||||
;; Step 3.7: Verified Components
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Step 3.7: Verified Components")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Step 3.7: Verified Components")
|
||||
|
||||
(p "Content-addressed components become a trust mechanism. "
|
||||
"HTTPS tells you the connection is authentic. "
|
||||
@@ -373,13 +373,13 @@
|
||||
"that the payment form in your browser is the exact component that was audited, "
|
||||
"not a tampered copy injected by XSS, a rogue extension, or a compromised CDN.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "Why SX can do this")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "Why SX can do this")
|
||||
|
||||
(p "Most frameworks can't verify UI at the component level because there's no stable identity. "
|
||||
"A React component is compiled, bundled, minified, tree-shaken — "
|
||||
"the thing in the browser bears no relationship to the source. In SX:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Components are source") " — the " (code ".sx") " definition IS the component. No compilation step that could diverge.")
|
||||
(li (strong "Components are pure functions") " — same inputs, same output. Deterministic.")
|
||||
(li (strong "Content addressing is built in") " — " (code "freeze-to-cid") " gives every component a CID (Step 3).")
|
||||
@@ -390,7 +390,7 @@
|
||||
"There is no gap between \"what was audited\" and \"what runs.\" "
|
||||
"That gap is where every UI supply chain attack lives.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.7a Transitive closure CID")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.7a Transitive closure CID")
|
||||
|
||||
(p "A component's CID must cover its entire dependency tree. "
|
||||
"If " (code "~bank/payment-form") " calls " (code "~bank/amount-input") " calls "
|
||||
@@ -407,7 +407,7 @@
|
||||
";; A one-character change in ~ui/text-field\n"
|
||||
";; produces a completely different deep CID."))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.7b Canonical serialization")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.7b Canonical serialization")
|
||||
|
||||
(p "For CIDs to match across hosts, the serialized form must be identical. "
|
||||
"Canonical SX: no comments, no redundant whitespace, deterministic key ordering in dicts, "
|
||||
@@ -421,7 +421,7 @@
|
||||
";; Dict key ordering is sorted:\n"
|
||||
"(canonical-sx '{:b 2 :a 1}) ;; => \"{:a 1 :b 2}\""))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.7c Browser verification")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.7c Browser verification")
|
||||
|
||||
(p "The client-side verification flow:")
|
||||
|
||||
@@ -445,7 +445,7 @@
|
||||
(p "Visual indicator — like the HTTPS lock icon, but for individual UI components. "
|
||||
"The browser knows which components have verified CIDs and can surface this to the user.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.7d Manifest and discovery")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.7d Manifest and discovery")
|
||||
|
||||
(p "Publishers declare expected CIDs via a well-known manifest:")
|
||||
|
||||
@@ -470,46 +470,46 @@
|
||||
|
||||
(p "Alternative discovery mechanisms:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "DNS TXT") " — " (code "_sx-verify.bank.com TXT \"payment-form=bafyrei...\""))
|
||||
(li (strong "Certificate transparency") " — append-only log of component CIDs, publicly auditable")
|
||||
(li (strong "IPFS") " — the CID is the address; fetching from IPFS is self-verifying")
|
||||
(li (strong "Signed manifest") " — publisher signs the manifest with their TLS key; browser verifies signature"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "How this differs from SRI")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "How this differs from SRI")
|
||||
|
||||
(p "Subresource Integrity (SRI) already does hash verification for " (code "<script>") " tags. "
|
||||
"But SRI has three gaps that verified components close:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "SRI")
|
||||
(th :class "text-left pb-2 font-semibold" "SX Verified Components")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "SRI")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "SX Verified Components")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-semibold" "Granularity")
|
||||
(td :class "pr-4" "Whole files (a JS bundle)")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-semibold") "Granularity")
|
||||
(td (~tw :tokens "pr-4") "Whole files (a JS bundle)")
|
||||
(td "Individual components"))
|
||||
(tr (td :class "pr-4 py-1 font-semibold" "Who sets the hash?")
|
||||
(td :class "pr-4" "The server — if compromised, serves matching hashes")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-semibold") "Who sets the hash?")
|
||||
(td (~tw :tokens "pr-4") "The server — if compromised, serves matching hashes")
|
||||
(td "Independent manifest — client verifies against external source"))
|
||||
(tr (td :class "pr-4 py-1 font-semibold" "What's verified?")
|
||||
(td :class "pr-4" "The file bytes — says nothing about runtime behaviour")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-semibold") "What's verified?")
|
||||
(td (~tw :tokens "pr-4") "The file bytes — says nothing about runtime behaviour")
|
||||
(td "The definition — and since components are pure functions, definition = behaviour")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Step 4: Concurrent CEK — deep spec
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Step 4: Concurrent CEK")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Step 4: Concurrent CEK")
|
||||
|
||||
(p "Multiple CEK machines running concurrently. The machines are independent \u2014 "
|
||||
"each has its own C, E, K registers. Communication is via typed channels. "
|
||||
"The host provides the concurrency mechanism (Web Workers, forkIO, tokio::spawn). "
|
||||
"The spec defines the coordination primitives.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.1 Spawn")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.1 Spawn")
|
||||
|
||||
(p "The fundamental primitive. Create a new CEK machine from a thunk, "
|
||||
"run it concurrently, return a signal that resolves with the result:")
|
||||
@@ -549,7 +549,7 @@
|
||||
"
|
||||
" (fn (val) (reset! result-sig val))))))"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.2 Channels")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.2 Channels")
|
||||
|
||||
(p "Typed communication pipes between concurrent computations. "
|
||||
"Send is non-blocking. Receive suspends via " (code "shift") " until data arrives:")
|
||||
@@ -588,13 +588,13 @@
|
||||
|
||||
(p "Channel semantics:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Unbuffered") " \u2014 send blocks until a receiver is ready (rendezvous)")
|
||||
(li (strong "Buffered") " \u2014 send succeeds immediately, values queue (current default)")
|
||||
(li (strong "Broadcast") " \u2014 every receiver gets every value (pub/sub)")
|
||||
(li (strong "Select") " \u2014 wait on multiple channels, take the first that has data"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.3 Fork / Join")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.3 Fork / Join")
|
||||
|
||||
(p "Spawn multiple computations, collect all results. "
|
||||
"The spec primitive, not a library pattern:")
|
||||
@@ -621,12 +621,12 @@
|
||||
(p "Fork/join composes with the reactive system. Each result signal drives "
|
||||
"a reactive DOM node. Results appear as they complete, not all-or-nothing.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.4 Scheduler")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.4 Scheduler")
|
||||
|
||||
(p "The scheduler decides which CEK machine steps next. "
|
||||
"Different strategies for different use cases:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Round-robin") " \u2014 fair, each machine gets equal time")
|
||||
(li (strong "Priority") " \u2014 UI-facing computations step first")
|
||||
(li (strong "Work-stealing") " \u2014 idle workers steal from busy ones")
|
||||
@@ -664,11 +664,11 @@
|
||||
"
|
||||
" [:b :c] [:d]}})"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.5 Content-addressed concurrency")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.5 Content-addressed concurrency")
|
||||
|
||||
(p "Concurrent computation + content addressing = verifiable parallel execution:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Each spawned computation\u2019s initial state has a CID")
|
||||
(li "Each result has a CID")
|
||||
(li "The mapping input-CID \u2192 output-CID is a proof of computation")
|
||||
@@ -699,90 +699,90 @@
|
||||
"
|
||||
" result)))"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.6 Host mapping")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.6 Host mapping")
|
||||
|
||||
(p "Each host implements concurrency differently. The spec is the same:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Primitive")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "JavaScript")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Python")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "OCaml")
|
||||
(th :class "text-left pb-2 font-semibold" "Rust/WASM")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Primitive")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "JavaScript")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Python")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "OCaml")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Rust/WASM")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-mono" "spawn")
|
||||
(td :class "pr-4" "Web Worker")
|
||||
(td :class "pr-4" "asyncio.create_task")
|
||||
(td :class "pr-4" "Eio.Fiber.fork")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "spawn")
|
||||
(td (~tw :tokens "pr-4") "Web Worker")
|
||||
(td (~tw :tokens "pr-4") "asyncio.create_task")
|
||||
(td (~tw :tokens "pr-4") "Eio.Fiber.fork")
|
||||
(td "tokio::spawn"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "channel")
|
||||
(td :class "pr-4" "MessageChannel")
|
||||
(td :class "pr-4" "asyncio.Queue")
|
||||
(td :class "pr-4" "Eio.Stream.t")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "channel")
|
||||
(td (~tw :tokens "pr-4") "MessageChannel")
|
||||
(td (~tw :tokens "pr-4") "asyncio.Queue")
|
||||
(td (~tw :tokens "pr-4") "Eio.Stream.t")
|
||||
(td "mpsc::channel"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "yield!")
|
||||
(td :class "pr-4" "setTimeout(0)")
|
||||
(td :class "pr-4" "await asyncio.sleep(0)")
|
||||
(td :class "pr-4" "Eio.Fiber.yield")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "yield!")
|
||||
(td (~tw :tokens "pr-4") "setTimeout(0)")
|
||||
(td (~tw :tokens "pr-4") "await asyncio.sleep(0)")
|
||||
(td (~tw :tokens "pr-4") "Eio.Fiber.yield")
|
||||
(td "tokio::task::yield_now"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "freeze/thaw")
|
||||
(td :class "pr-4" "postMessage + JSON")
|
||||
(td :class "pr-4" "pickle / SX text")
|
||||
(td :class "pr-4" "Marshal + SX text")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "freeze/thaw")
|
||||
(td (~tw :tokens "pr-4") "postMessage + JSON")
|
||||
(td (~tw :tokens "pr-4") "pickle / SX text")
|
||||
(td (~tw :tokens "pr-4") "Marshal + SX text")
|
||||
(td "serde + SX text"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "select")
|
||||
(td :class "pr-4" "Promise.race")
|
||||
(td :class "pr-4" "asyncio.wait FIRST_COMPLETED")
|
||||
(td :class "pr-4" "Eio.Fiber.any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "select")
|
||||
(td (~tw :tokens "pr-4") "Promise.race")
|
||||
(td (~tw :tokens "pr-4") "asyncio.wait FIRST_COMPLETED")
|
||||
(td (~tw :tokens "pr-4") "Eio.Fiber.any")
|
||||
(td "tokio::select!")))))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.7 Roadmap")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.7 Roadmap")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Phase")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "What")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Host")
|
||||
(th :class "text-left pb-2 font-semibold" "Validates")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Phase")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "What")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Host")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Validates")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "4a")
|
||||
(td :class "pr-4" "spawn + Web Worker")
|
||||
(td :class "pr-4" "JavaScript")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4a")
|
||||
(td (~tw :tokens "pr-4") "spawn + Web Worker")
|
||||
(td (~tw :tokens "pr-4") "JavaScript")
|
||||
(td "Freeze/thaw across execution contexts"))
|
||||
(tr (td :class "pr-4 py-1" "4b")
|
||||
(td :class "pr-4" "Channels (buffered)")
|
||||
(td :class "pr-4" "JavaScript")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4b")
|
||||
(td (~tw :tokens "pr-4") "Channels (buffered)")
|
||||
(td (~tw :tokens "pr-4") "JavaScript")
|
||||
(td "Cross-worker communication"))
|
||||
(tr (td :class "pr-4 py-1" "4c")
|
||||
(td :class "pr-4" "Fork/join + DAG scheduler")
|
||||
(td :class "pr-4" "JavaScript")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4c")
|
||||
(td (~tw :tokens "pr-4") "Fork/join + DAG scheduler")
|
||||
(td (~tw :tokens "pr-4") "JavaScript")
|
||||
(td "Art DAG integration path"))
|
||||
(tr (td :class "pr-4 py-1" "4d")
|
||||
(td :class "pr-4" "OCaml bootstrapper → native compilation")
|
||||
(td :class "pr-4" "OCaml")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4d")
|
||||
(td (~tw :tokens "pr-4") "OCaml bootstrapper → native compilation")
|
||||
(td (~tw :tokens "pr-4") "OCaml")
|
||||
(td "Native performance, direct CEK-to-ML mapping"))
|
||||
(tr (td :class "pr-4 py-1" "4e")
|
||||
(td :class "pr-4" "Rust/WASM bootstrapper")
|
||||
(td :class "pr-4" "Rust")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4e")
|
||||
(td (~tw :tokens "pr-4") "Rust/WASM bootstrapper")
|
||||
(td (~tw :tokens "pr-4") "Rust")
|
||||
(td "Browser performance, WASM target"))
|
||||
(tr (td :class "pr-4 py-1" "4f")
|
||||
(td :class "pr-4" "Content-addressed spawn")
|
||||
(td :class "pr-4" "Any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4f")
|
||||
(td (~tw :tokens "pr-4") "Content-addressed spawn")
|
||||
(td (~tw :tokens "pr-4") "Any")
|
||||
(td "Memoization, distribution, verification"))
|
||||
(tr (td :class "pr-4 py-1" "4g")
|
||||
(td :class "pr-4" "IPFS-backed content store")
|
||||
(td :class "pr-4" "Any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4g")
|
||||
(td (~tw :tokens "pr-4") "IPFS-backed content store")
|
||||
(td (~tw :tokens "pr-4") "Any")
|
||||
(td "Global content addressing, peer-to-peer"))
|
||||
(tr (td :class "pr-4 py-1" "4h")
|
||||
(td :class "pr-4" "Select + broadcast channels")
|
||||
(td :class "pr-4" "Any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4h")
|
||||
(td (~tw :tokens "pr-4") "Select + broadcast channels")
|
||||
(td (~tw :tokens "pr-4") "Any")
|
||||
(td "Complex coordination patterns"))
|
||||
(tr (td :class "pr-4 py-1" "4i")
|
||||
(td :class "pr-4" "Linear channels")
|
||||
(td :class "pr-4" "Any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4i")
|
||||
(td (~tw :tokens "pr-4") "Linear channels")
|
||||
(td (~tw :tokens "pr-4") "Any")
|
||||
(td "Resource safety, exactly-once delivery")))))
|
||||
|
||||
(p "Each phase is independently valuable. Phase 4a (Web Worker spawn) is the immediate next build. "
|
||||
@@ -794,13 +794,13 @@
|
||||
;; Step 5: Linear effects (future)
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Step 5: Linear Effects")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Step 5: Linear Effects")
|
||||
|
||||
(p "Constrain the continuation: a handler " (em "must") " resume exactly once. "
|
||||
"No dropping (resource leak), no duplicating (nondeterminism). "
|
||||
"This is the linearity axis from the three-axis model.")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Affine channels") " \u2014 send at most once. Prevents duplicate messages.")
|
||||
(li (strong "Linear scopes") " \u2014 a freeze scope that must be thawed. Prevents orphaned state.")
|
||||
(li (strong "Session types") " \u2014 a protocol that must complete. Prevents half-open connections.")
|
||||
@@ -814,5 +814,5 @@
|
||||
"content addressing \u2014 must be solid first. Linearity is a discipline on top, "
|
||||
"not a prerequisite beneath.")
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mt-12"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mt-12")
|
||||
"The floor is thought itself. We can't go deeper, because there's no one left to dig.")))
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
(p "The fix: transfer component definitions alongside fragments. Services tell the provider what they already have; the provider sends only what's missing."))
|
||||
|
||||
(~docs/section :title "What exists" :id "exists"
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Fragment GET infrastructure works (" (code "shared/infrastructure/fragments.py") ")")
|
||||
(li (code "X-Fragment-Request") " header protocol for internal service calls")
|
||||
(li "Content type negotiation for text/html and text/sx responses")
|
||||
(li "Fragment caching and composition in page rendering"))))
|
||||
|
||||
(~docs/section :title "What remains" :id "remains"
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "POST sexp protocol: ") "Switch from GET to POST with structured sexp body containing " (code ":components") " list of what consumer already has")
|
||||
(li (strong "Structured response: ") (code "(fragment-response :defs (...) :content (...))") " — provider sends only missing component defs")
|
||||
(li (strong (code "fragment_response()") " builder: ") "New function in helpers.py that diffs provider's component env against consumer's list")
|
||||
@@ -27,24 +27,24 @@
|
||||
(li (strong "Shared blueprint factory: ") (code "create_fragment_blueprint(handlers)") " to deduplicate the identical fragment endpoint pattern across 8 services"))))
|
||||
|
||||
(~docs/section :title "Files to modify" :id "files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Change")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Change")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/fragments.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "POST sexp body, parse response, register defs"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "fragment_response() builder"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/fragment_endpoint.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "NEW — shared blueprint factory"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "*/bp/fragments/routes.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "All 8 services: use create_fragment_blueprint"))))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/fragments.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "POST sexp body, parse response, register defs"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/helpers.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "fragment_response() builder"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/fragment_endpoint.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "NEW — shared blueprint factory"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "*/bp/fragments/routes.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "All 8 services: use create_fragment_blueprint"))))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Glue Decoupling
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/generative-sx/plan-generative-sx-content ()
|
||||
(~docs/page :title "Generative SX"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"In the browser, SX is a program that modifies itself in response to external stimuli. Outside the browser, it becomes a program that writes itself as it runs.")
|
||||
|
||||
;; =====================================================================
|
||||
@@ -15,7 +15,7 @@
|
||||
(~docs/section :title "The observation" :id "observation"
|
||||
(p "The marshes work made something visible. A server response arrives carrying " (code "(reset! (use-store \"price\") 14.99)") " inside a " (code "data-init") " script. The SX evaluator parses this string, evaluates it in its own environment, and mutates its own signal graph. The program accepted new source at runtime and changed itself.")
|
||||
(p "This isn't metaprogramming. Macros expand at compile time — they transform source before evaluation. This is different: the program is " (em "already running") " when it receives new code, evaluates it, and continues with an extended state. The DOM is just the boundary. The signal graph is just the state. The mechanism is general:")
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-600"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-600")
|
||||
(li "A running SX evaluator with an environment")
|
||||
(li "New SX source arrives (from any external source)")
|
||||
(li "The evaluator parses it and evaluates it " (em "in its own environment"))
|
||||
@@ -58,34 +58,34 @@
|
||||
|
||||
(p "The program evaluates source. If the result is more source, it evaluates that too. Each iteration can extend the environment — add new functions, new macros, new primitives. The environment grows. The program becomes capable of things it couldn't do at the start.")
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(p :class "text-violet-900 font-medium" "This is not eval-in-a-loop")
|
||||
(p :class "text-violet-800 text-sm" "A REPL evaluates user input in a persistent environment. That's interactive, not generative. The generative pattern is different: the program itself decides what to evaluate next. No user in the loop. The output of one evaluation becomes the input to the next. The program writes itself."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "This is not eval-in-a-loop")
|
||||
(p (~tw :tokens "text-violet-800 text-sm") "A REPL evaluates user input in a persistent environment. That's interactive, not generative. The generative pattern is different: the program itself decides what to evaluate next. No user in the loop. The output of one evaluation becomes the input to the next. The program writes itself."))
|
||||
|
||||
(~docs/subsection :title "Three modes"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Mode")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Input")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Output")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Growth")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Mode")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Input")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Output")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Growth")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Analytic")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX program + data")
|
||||
(td :class "px-3 py-2 text-stone-600" "Analysis results")
|
||||
(td :class "px-3 py-2 text-stone-600" "None — pure function"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Synthetic")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX program")
|
||||
(td :class "px-3 py-2 text-stone-600" "New SX source")
|
||||
(td :class "px-3 py-2 text-stone-600" "Code generation — new defs emitted"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Analytic")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX program + data")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Analysis results")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None — pure function"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Synthetic")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX program")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "New SX source")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Code generation — new defs emitted"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Generative")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX seed")
|
||||
(td :class "px-3 py-2 text-stone-600" "Running program")
|
||||
(td :class "px-3 py-2 text-stone-600" "Self-extending — output evaluated as input")))))))
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Generative")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX seed")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Running program")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Self-extending — output evaluated as input")))))))
|
||||
|
||||
;; =====================================================================
|
||||
;; IV. Concrete manifestations
|
||||
@@ -132,9 +132,9 @@
|
||||
|
||||
(p "The seed is a quine that doesn't just reproduce itself — it " (em "extends") " itself. Each call to " (code "next-source") " returns new SX that the seed evaluates in its own environment. The environment grows. The seed's capabilities grow. But the seed itself never changes — it's the fixed point of the generative process.")
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4 my-4"
|
||||
(p :class "text-stone-700 font-medium mb-2" "The minimal seed is the spec")
|
||||
(p :class "text-stone-600 text-sm" (code "eval.sx") " + " (code "parser.sx") " + " (code "primitives.sx") " = a complete SX evaluator defined in SX. This is already the seed. The bootstrappers compile it to JavaScript and Python. A generative runtime compiles it to " (em "itself") " — the seed evaluates the seed and obtains a running evaluator that can evaluate anything, including more of itself.")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-stone-700 font-medium mb-2") "The minimal seed is the spec")
|
||||
(p (~tw :tokens "text-stone-600 text-sm") (code "eval.sx") " + " (code "parser.sx") " + " (code "primitives.sx") " = a complete SX evaluator defined in SX. This is already the seed. The bootstrappers compile it to JavaScript and Python. A generative runtime compiles it to " (em "itself") " — the seed evaluates the seed and obtains a running evaluator that can evaluate anything, including more of itself.")))
|
||||
|
||||
;; =====================================================================
|
||||
;; VI. Growth constraints
|
||||
@@ -170,7 +170,7 @@
|
||||
|
||||
(~docs/subsection :title "Runtime eval with first-class environments"
|
||||
(p (code "eval-in") " requires evaluating arbitrary expressions in arbitrary environments at runtime. The host must support:")
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-1 text-stone-600")
|
||||
(li "Creating new environments (" (code "env-extend") ")")
|
||||
(li "Adding bindings to existing environments (" (code "env-set!") ")")
|
||||
(li "Inspecting environment contents (" (code "env-snapshot") ")")
|
||||
@@ -211,10 +211,10 @@
|
||||
(p "The generative primitives (" (code "read-file") ", " (code "write-file") ", " (code "list-files") ") are the " (em "only") " way generated code touches the outside world. The host must be able to intercept, log, and deny all IO. There is no escape hatch through FFI or native calls.")
|
||||
(p "This is what makes generative programs auditable. If the host allows generated code to call raw " (code "fs.writeFileSync") " or " (code "os.system") ", the boundary is meaningless. The host must virtualize all IO through the declared primitives. Generated code that tries to escape the sandbox hits the boundary, not the OS."))
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(p :class "text-violet-900 font-medium" "The acid test")
|
||||
(p :class "text-violet-800 text-sm" "SX already has properties 1–7 in the spec. They fall out of the language design. Properties 8–10 (deterministic evaluation, serializable state, IO isolation) are constraints on the " (em "host implementation") ", not the language. A host that violates 1–7 can't run the spec correctly. A host that violates 8–10 can run it but can't be trusted in a generative context.")
|
||||
(p :class "text-violet-800 text-sm mt-2" "Phase 1 — the self-compiling spec — tests properties 1–7. If a host can compile the spec from the spec, it necessarily has them. The remaining three are operational guarantees verified by the seed network (Phase 5), where multiple hosts must agree on the same generative output.")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The acid test")
|
||||
(p (~tw :tokens "text-violet-800 text-sm") "SX already has properties 1–7 in the spec. They fall out of the language design. Properties 8–10 (deterministic evaluation, serializable state, IO isolation) are constraints on the " (em "host implementation") ", not the language. A host that violates 1–7 can't run the spec correctly. A host that violates 8–10 can run it but can't be trusted in a generative context.")
|
||||
(p (~tw :tokens "text-violet-800 text-sm mt-2") "Phase 1 — the self-compiling spec — tests properties 1–7. If a host can compile the spec from the spec, it necessarily has them. The remaining three are operational guarantees verified by the seed network (Phase 5), where multiple hosts must agree on the same generative output.")))
|
||||
|
||||
;; =====================================================================
|
||||
;; VIII. Environment migration
|
||||
@@ -255,42 +255,42 @@
|
||||
|
||||
(~docs/subsection :title "Phase 0: Generative primitives"
|
||||
(p "Add the minimal set of primitives needed for a generative loop. These are IO primitives — they cross the boundary.")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Primitive")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Signature")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Primitive")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Signature")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "read-file")
|
||||
(td :class "px-3 py-2 text-stone-600" "(path) → string")
|
||||
(td :class "px-3 py-2 text-stone-600" "Read file contents as string"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "write-file")
|
||||
(td :class "px-3 py-2 text-stone-600" "(path content) → nil")
|
||||
(td :class "px-3 py-2 text-stone-600" "Write string to file"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "list-files")
|
||||
(td :class "px-3 py-2 text-stone-600" "(path pattern) → list")
|
||||
(td :class "px-3 py-2 text-stone-600" "Glob-match files in directory"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "eval-in")
|
||||
(td :class "px-3 py-2 text-stone-600" "(source env) → any")
|
||||
(td :class "px-3 py-2 text-stone-600" "Parse and evaluate SX source in given environment"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "env-snapshot")
|
||||
(td :class "px-3 py-2 text-stone-600" "(env) → dict")
|
||||
(td :class "px-3 py-2 text-stone-600" "Serialize environment to inspectable dict"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "read-file")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(path) → string")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Read file contents as string"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "write-file")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(path content) → nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Write string to file"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "list-files")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(path pattern) → list")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Glob-match files in directory"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "eval-in")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(source env) → any")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Parse and evaluate SX source in given environment"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "env-snapshot")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(env) → dict")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Serialize environment to inspectable dict"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "content-hash")
|
||||
(td :class "px-3 py-2 text-stone-600" "(source) → string")
|
||||
(td :class "px-3 py-2 text-stone-600" "SHA3-256 hash of source string")))))
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "content-hash")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(source) → string")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SHA3-256 hash of source string")))))
|
||||
(p "These are the building blocks. The generative loop composes them. The primitives themselves are minimal — no networking, no databases, no UI. Just: read, write, evaluate, inspect, hash."))
|
||||
|
||||
(~docs/subsection :title "Phase 1: Self-compiling spec"
|
||||
(p "Rewrite " (code "bootstrap_js.py") " as " (code "bootstrap.sx") ". The bootstrapper becomes an SX program that reads the spec files and emits target code.")
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-600"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-600")
|
||||
(li "Write " (code "codegen-js.sx") " — JavaScript code generation adapter (emit JS from SX AST)")
|
||||
(li "Write " (code "codegen-py.sx") " — Python code generation adapter")
|
||||
(li "Write " (code "bootstrap.sx") " — the generative loop that loads a spec, loads an adapter, and emits")
|
||||
@@ -320,7 +320,7 @@
|
||||
|
||||
(~docs/section :title "The strange loop" :id "strange-loop"
|
||||
(p "Hofstadter's strange loop: a hierarchy of levels where the top level reaches back down and affects the bottom level. In a generative SX program:")
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-600")
|
||||
(li "The bottom level is the evaluator — it evaluates expressions")
|
||||
(li "The middle level is the program — expressions that produce values")
|
||||
(li "The top level is the generator — values that are new expressions")
|
||||
@@ -330,8 +330,8 @@
|
||||
|
||||
(p "The browser version constrains this to DOM mutations. The server version constrains it to request handling. The unconstrained version — a bare seed with " (code "next-source") " — is a strange loop in its purest form: an evaluator that evaluates what it generates, generating what it evaluates.")
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4 mt-6"
|
||||
(p :class "text-stone-700 font-medium mb-2" "The practical consequence")
|
||||
(p :class "text-stone-600 text-sm" "An SX development environment where the tools are written in SX, running inside SX, modifying SX. The editor understands the code because it " (em "is") " the code. The debugger inspects the environment because it " (em "shares") " the environment. The compiler reads the spec because the spec is in the same format as everything else. There is no impedance mismatch between any layer because there is only one layer.")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 mt-6")
|
||||
(p (~tw :tokens "text-stone-700 font-medium mb-2") "The practical consequence")
|
||||
(p (~tw :tokens "text-stone-600 text-sm") "An SX development environment where the tools are written in SX, running inside SX, modifying SX. The editor understands the code because it " (em "is") " the code. The debugger inspects the environment because it " (em "shares") " the environment. The compiler reads the spec because the spec is in the same format as everything else. There is no impedance mismatch between any layer because there is only one layer.")))
|
||||
|
||||
))
|
||||
|
||||
@@ -11,38 +11,38 @@
|
||||
|
||||
(~docs/section :title "Current state" :id "current"
|
||||
(p "Apps are partially decoupled via HTTP interfaces (fetch_data, call_action, send_internal_activity) and DTOs. The Cart microservice split (relations, likes, orders) is complete. But direct model imports persist in:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Cart") " — 9 files importing from market, events, blog")
|
||||
(li (strong "Blog") " — 8 files importing from cart, events, market")
|
||||
(li (strong "Events") " — 5 files importing from blog, market, cart")
|
||||
(li (strong "Market") " — 1 file importing from blog")))
|
||||
|
||||
(~docs/section :title "What remains" :id "remains"
|
||||
(div :class "space-y-3"
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "1. glue/services/pages.py")
|
||||
(p :class "text-sm text-stone-600" "Dict-based Post access for non-blog apps: get_page_by_slug, get_page_by_id, get_pages_by_ids, page_exists, search_posts."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "2. glue/services/page_config.py")
|
||||
(p :class "text-sm text-stone-600" "PageConfig CRUD: get_page_config, get_or_create_page_config, get_page_configs_by_ids."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "3. glue/services/calendars.py")
|
||||
(p :class "text-sm text-stone-600" "Calendar queries + entry associations (from blog): get_calendars_for_page, toggle_entry_association, get_associated_entries."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "4. glue/services/marketplaces.py")
|
||||
(p :class "text-sm text-stone-600" "MarketPlace CRUD (from blog+events): get_marketplaces_for_page, create_marketplace, soft_delete_marketplace."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "5. glue/services/cart_items.py")
|
||||
(p :class "text-sm text-stone-600" "CartItem/CalendarEntry queries for cart: get_cart_items, find_or_create_cart_item, clear_cart_for_order."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "6. glue/services/products.py")
|
||||
(p :class "text-sm text-stone-600" "Minimal Product access for cart orders: get_product."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "7. Model registration + cleanup")
|
||||
(p :class "text-sm text-stone-600" "register_models() in glue/setup.py, update all app.py files, delete moved service files."))))
|
||||
(div (~tw :tokens "space-y-3")
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. glue/services/pages.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Dict-based Post access for non-blog apps: get_page_by_slug, get_page_by_id, get_pages_by_ids, page_exists, search_posts."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. glue/services/page_config.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "PageConfig CRUD: get_page_config, get_or_create_page_config, get_page_configs_by_ids."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. glue/services/calendars.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Calendar queries + entry associations (from blog): get_calendars_for_page, toggle_entry_association, get_associated_entries."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. glue/services/marketplaces.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "MarketPlace CRUD (from blog+events): get_marketplaces_for_page, create_marketplace, soft_delete_marketplace."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "5. glue/services/cart_items.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "CartItem/CalendarEntry queries for cart: get_cart_items, find_or_create_cart_item, clear_cart_for_order."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "6. glue/services/products.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Minimal Product access for cart orders: get_product."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "7. Model registration + cleanup")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "register_models() in glue/setup.py, update all app.py files, delete moved service files."))))
|
||||
|
||||
(~docs/section :title "Docker consideration" :id "docker"
|
||||
(p :class "text-stone-600" "For glue services to work in Docker (single app per container), model files from other apps must be importable. Recommended: try/except at import time — glue services that can't import a model raise ImportError at call time, which only happens if called from the wrong app."))))
|
||||
(p (~tw :tokens "text-stone-600") "For glue services to work in Docker (single app per container), model files from other apps must be importable. Recommended: try/except at import time — glue services that can't import a model raise ImportError at call time, which only happens if called from the wrong app."))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Social Sharing
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
(~docs/page
|
||||
:title "Plans"
|
||||
(div
|
||||
:class "space-y-4"
|
||||
(~tw :tokens "space-y-4")
|
||||
(p
|
||||
:class "text-lg text-stone-600 mb-4"
|
||||
(~tw :tokens "text-lg text-stone-600 mb-4")
|
||||
"Architecture roadmaps and implementation plans for SX.")
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(~tw :tokens "space-y-3")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
@@ -20,9 +20,9 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors"
|
||||
(div :class "font-semibold text-stone-800" (get item "label"))
|
||||
(~tw :tokens "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors")
|
||||
(div (~tw :tokens "font-semibold text-stone-800") (get item "label"))
|
||||
(when
|
||||
(get item "summary")
|
||||
(p :class "text-sm text-stone-500 mt-1" (get item "summary")))))
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") (get item "summary")))))
|
||||
plans-nav-items)))))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
(~docs/section :title "Context" :id "context"
|
||||
(p "The SX spec is already split into three layers:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (code "spec/") " \u2014 Core language (19 files): evaluator, parser, primitives, CEK, types, continuations. Host-independent.")
|
||||
(li (code "web/") " \u2014 Web framework (20 files): signals, adapters, engine, orchestration, boot, router, deps. Built on core spec.")
|
||||
(li (code "sx/") " \u2014 Application (sx-docs website). Built on web framework."))
|
||||
@@ -16,35 +16,35 @@
|
||||
(p "This also involves sorting out the JavaScript: eliminating hand-coded JS that duplicates specced " (code ".sx") " logic, and moving web framework " (code ".sx") " from compiled-into-evaluator to runtime-evaluated."))
|
||||
|
||||
(~docs/section :title "Existing Architecture" :id "existing"
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Three-layer spec split (DONE)")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Directory")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Files")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Content")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Three-layer spec split (DONE)")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Directory")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Files")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Content")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Core spec")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "spec/"))
|
||||
(td :class "px-3 py-2 text-stone-700" "19")
|
||||
(td :class "px-3 py-2 text-stone-600" "eval, parser, primitives, render, types, CEK, continuations, boundary-core"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Web framework")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "web/"))
|
||||
(td :class "px-3 py-2 text-stone-700" "20")
|
||||
(td :class "px-3 py-2 text-stone-600" "adapters, signals, engine, orchestration, boot, router, deps, forms, boundary-web"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Core spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "spec/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "19")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "eval, parser, primitives, render, types, CEK, continuations, boundary-core"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Web framework")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "web/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "20")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "adapters, signals, engine, orchestration, boot, router, deps, forms, boundary-web"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Application")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "sx/"))
|
||||
(td :class "px-3 py-2 text-stone-700" "\u2014")
|
||||
(td :class "px-3 py-2 text-stone-600" "sx-docs website, page components, content")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Application")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "sx/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "\u2014")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "sx-docs website, page components, content")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Rust/WASM evaluator (DONE)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Rust/WASM evaluator (DONE)")
|
||||
(p (code "sx-rust/") " has a working parser + evaluator + render-to-html in WASM: 9,823 lines generated Rust, 75 real primitives, 154 stubs, 92 tests passing. Currently pure computation \u2014 no DOM interaction.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What needs to change")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What needs to change")
|
||||
(p "Currently " (strong "everything") " (core + web framework) gets compiled into one monolithic " (code "sx-browser.js") ". The web framework " (code ".sx") " files (signals, engine, orchestration, boot, etc.) are baked into the evaluator output by the bootstrapper. They should instead be " (strong "evaluated at runtime") " by the core evaluator, like any other " (code ".sx") " code.")
|
||||
(p "The JavaScript platform primitives (DOM, fetch, timers, storage) are also inlined into the bootstrapped output. They need to be extracted into a standalone " (code "sx-platform.js") " module that both JS and WASM evaluators share."))
|
||||
|
||||
@@ -56,89 +56,89 @@
|
||||
(~docs/section :title "Bootstrapped vs Runtime-Evaluated" :id "bootstrap-vs-runtime"
|
||||
(p "The key question: what MUST be compiled into the evaluator vs what can be loaded as " (code ".sx") " at runtime?")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Must be bootstrapped (core spec)")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Dir")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Must be bootstrapped (core spec)")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Dir")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "eval.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "spec/")
|
||||
(td :class "px-3 py-2 text-stone-600" "IS the language \u2014 can't evaluate without it"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "parser.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "spec/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Can't read .sx source without a parser"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "primitives.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "spec/")
|
||||
(td :class "px-3 py-2 text-stone-600" "80+ built-in pure functions \u2014 must be native"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "render.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "spec/")
|
||||
(td :class "px-3 py-2 text-stone-600" "HTML_TAGS registry, parse-element-args"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "adapter-html.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "render-to-html \u2014 co-recursive with eval-expr"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "adapter-sx.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "aser (wire format) \u2014 co-recursive with eval-expr"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "eval.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "spec/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "IS the language \u2014 can't evaluate without it"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "parser.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "spec/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Can't read .sx source without a parser"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "primitives.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "spec/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "80+ built-in pure functions \u2014 must be native"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "render.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "spec/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "HTML_TAGS registry, parse-element-args"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "adapter-html.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "render-to-html \u2014 co-recursive with eval-expr"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "adapter-sx.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "aser (wire format) \u2014 co-recursive with eval-expr"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" (code "adapter-dom.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "render-to-dom \u2014 co-recursive with eval-expr")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "adapter-dom.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "render-to-dom \u2014 co-recursive with eval-expr")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Runtime-evaluated (web framework)")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Dir")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why it can be runtime")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Runtime-evaluated (web framework)")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Dir")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why it can be runtime")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "signals.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure computation \u2014 dicts with markers, no new types"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "engine.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure logic \u2014 trigger parsing, swap specs, morph"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "orchestration.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Event binding + fetch \u2014 calls platform primitives"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "boot.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Page lifecycle \u2014 calls platform primitives"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "router.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "URL pattern matching \u2014 pure computation"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "deps.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Component dependency analysis \u2014 pure AST walking"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "page-helpers.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Data transformation helpers"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "signals.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure computation \u2014 dicts with markers, no new types"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "engine.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure logic \u2014 trigger parsing, swap specs, morph"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "orchestration.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Event binding + fetch \u2014 calls platform primitives"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "boot.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Page lifecycle \u2014 calls platform primitives"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "router.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "URL pattern matching \u2014 pure computation"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "deps.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Component dependency analysis \u2014 pure AST walking"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "page-helpers.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Data transformation helpers"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" (code "forms.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Server-only definition forms")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "forms.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Server-only definition forms")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "adapter-dom.sx reactive split")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "adapter-dom.sx reactive split")
|
||||
(p (code "adapter-dom.sx") " contains reactive-aware code (" (code "reactive-text") ", " (code "reactive-attr") ", " (code "render-dom-island") ", " (code "render-dom-lake") ") interleaved with core DOM rendering. These call " (code "signal?") " and " (code "deref") " from " (code "signals.sx") " via environment lookup \u2014 no compile-time dependency. Option: split reactive DOM functions into " (code "adapter-dom-reactive.sx") " (web/ layer), keeping base " (code "adapter-dom.sx") " purely about elements/text/fragments/components.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Hand-coded JS to clean up")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Hand-coded JS to clean up")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (code "CONTINUATIONS_JS") " in " (code "platform_js.py") " \u2014 hand-coded shift/reset. Should use specced " (code "continuations.sx") " or be eliminated if continuations are application-level.")
|
||||
(li (code "ASYNC_IO_JS") " in " (code "platform_js.py") " \u2014 hand-coded async rendering dispatch. Already replaced by " (code "adapter-async.sx") " for Python. JS version should also be bootstrapped or eliminated.")
|
||||
(li "Various wrapper functions in " (code "PLATFORM_BOOT_JS") " that duplicate logic from " (code "boot.sx") ".")))
|
||||
@@ -151,47 +151,47 @@
|
||||
(~docs/section :title "Phase 1: Extract sx-platform.js" :id "phase-1"
|
||||
(p (strong "Goal:") " All real-world-touching JavaScript lives in one standalone module. The evaluator never directly accesses " (code "document") ", " (code "window") ", " (code "fetch") ", " (code "localStorage") ", " (code "history") ", etc.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Architecture")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Architecture")
|
||||
(~docs/code :src (highlight " \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 sx-platform.js \u2502 \u2190 DOM, fetch, timers, storage\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 sx-evaluator.js \u2502 \u2502 sx-wasm-shim.js \u2502\n \u2502 (isolated JS) \u2502 \u2502 (WASM instance \u2502\n \u2502 \u2502 \u2502 + handle table) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518" "text"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What moves into sx-platform.js")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What moves into sx-platform.js")
|
||||
(p "Extracted from " (code "platform_js.py") " string constants:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Category")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Source")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "~Functions")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Category")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Source")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "~Functions")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "DOM primitives")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_DOM_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~50 (createElement, setAttribute, appendChild...)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Engine platform")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_ENGINE_PURE_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~6 (locationHref, pushState, nowMs...)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Orchestration platform")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_ORCHESTRATION_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~80 (fetch, abort, timers, SSE, scroll, media...)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Boot platform")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_BOOT_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~20 (mount target, localStorage, cookies, logging...)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DOM primitives")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_DOM_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~50 (createElement, setAttribute, appendChild...)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Engine platform")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_ENGINE_PURE_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~6 (locationHref, pushState, nowMs...)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Orchestration platform")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_ORCHESTRATION_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~80 (fetch, abort, timers, SSE, scroll, media...)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Boot platform")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_BOOT_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~20 (mount target, localStorage, cookies, logging...)"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Parser helpers")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_PARSER_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~4 (isIdentStart, parseNumber...)")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Parser helpers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_PARSER_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~4 (isIdentStart, parseNumber...)")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Isolation rule")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Isolation rule")
|
||||
(p "After extraction, searching " (code "sx-evaluator.js") " for " (code "document") ", " (code "window") ", " (code "fetch") ", " (code "localStorage") ", " (code "history") ", " (code "setTimeout") ", " (code "console") " should find " (strong "zero") " direct references.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The callSxFunction bridge")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The callSxFunction bridge")
|
||||
(p "Platform code (event listeners, timers) needs to invoke SX lambdas. The evaluator provides a single " (code "callSxFunction(fn, args) \u2192 result") " bridge to the platform at registration time. This is the ONE evaluator-to-platform callback.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Implementation")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Implementation")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Modify " (code "platform_js.py") " to emit platform functions as a separate output section")
|
||||
(li "Create " (code "sx-platform.js") " as an IIFE that sets " (code "globalThis.SxPlatform = {...}"))
|
||||
(li "The evaluator IIFE reads " (code "globalThis.SxPlatform") " at init, registers each function as a PRIMITIVE")
|
||||
@@ -206,19 +206,19 @@
|
||||
(~docs/section :title "Phase 2: Isolate the JS Evaluator" :id "phase-2"
|
||||
(p (strong "Goal:") " " (code "sx-evaluator.js") " contains ONLY core spec + render adapters. Web framework " (code ".sx") " is evaluated at runtime.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Core-only build")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Core-only build")
|
||||
(p "The bootstrapper already supports selecting which modules to compile. A core-only build:")
|
||||
(~docs/code :src (highlight "# In run_js_sx.py \u2014 core-only build\ncompile_ref_to_js(\n adapters=[\"parser\", \"html\", \"sx\", \"dom\"], # core spec + adapters\n modules=None, # no signals, engine, orchestration, boot\n extensions=None, # no continuations\n spec_modules=None # no deps, router, cek, frames, page-helpers\n)" "python"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Web framework loading")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Web framework loading")
|
||||
(p "Web framework " (code ".sx") " files ship as " (code "<script type=\"text/sx-lib\">") " blocks. The platform boot shim evaluates them before component scripts:")
|
||||
(~docs/code :src (highlight "<script src=\"/static/scripts/sx-platform.js\"></script>\n<script src=\"/static/scripts/sx-evaluator.js\"></script>\n<script type=\"text/sx-lib\">\n ;; concatenated web/ framework: signals, deps, router,\n ;; engine, orchestration, boot\n</script>\n<script type=\"text/sx\" data-components>\n ;; page component definitions\n</script>" "html"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Boot chicken-and-egg")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Boot chicken-and-egg")
|
||||
(p (code "boot.sx") " orchestrates the boot sequence but is itself web framework code. Solution: thin native boot shim (~30 lines) in " (code "sx-platform.js") ":")
|
||||
(~docs/code :src (highlight "SxPlatform.boot = function(evaluator) {\n // 1. Evaluate web framework .sx libraries\n var libs = document.querySelectorAll('script[type=\"text/sx-lib\"]');\n for (var i = 0; i < libs.length; i++) {\n evaluator.evalSource(libs[i].textContent);\n }\n // 2. Call boot-init (defined in boot.sx)\n evaluator.callFunction('boot-init');\n};" "javascript"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Performance")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Performance")
|
||||
(p "Parsing + evaluating ~5,000 lines of web framework " (code ".sx") " at startup takes ~10\u201350ms. After " (code "define") ", functions are Lambda objects dispatched identically to compiled functions. " (strong "Zero ongoing performance difference.")))
|
||||
|
||||
|
||||
@@ -229,22 +229,22 @@
|
||||
(~docs/section :title "Phase 3: Wire Up Rust/WASM" :id "phase-3"
|
||||
(p (strong "Goal:") " Rust evaluator calls " (code "sx-platform.js") " via wasm-bindgen imports. Handle table bridges DOM references.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Handle table (JS-side)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Handle table (JS-side)")
|
||||
(~docs/code :src (highlight "// In sx-wasm-shim.js\nconst handles = [null]; // index 0 = null handle\nfunction allocHandle(obj) { handles.push(obj); return handles.length - 1; }\nfunction getHandle(id) { return handles[id]; }\nfunction freeHandle(id) { handles[id] = null; }" "javascript"))
|
||||
(p "DOM nodes are JS objects. The handle table maps " (code "u32") " IDs to JS objects. Rust stores " (code "Value::Handle(u32)") " and passes the " (code "u32") " to imported JS functions.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Value::Handle in Rust")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Value::Handle in Rust")
|
||||
(~docs/code :src (highlight "// In platform.rs\npub enum Value {\n // ... existing variants ...\n Handle(u32), // opaque reference to JS-side object\n}" "rust"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "WASM imports from platform")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "WASM imports from platform")
|
||||
(~docs/code :src (highlight "#[wasm_bindgen(module = \"/sx-platform-wasm.js\")]\nextern \"C\" {\n fn platform_create_element(tag: &str) -> u32;\n fn platform_create_text_node(text: &str) -> u32;\n fn platform_set_attr(handle: u32, name: &str, value: &str);\n fn platform_append_child(parent: u32, child: u32);\n fn platform_add_event_listener(handle: u32, event: &str, callback_id: u32);\n // ... ~50 DOM primitives\n}" "rust"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Callback table for events")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Callback table for events")
|
||||
(p "When Rust creates an event handler (a Lambda), it stores it in a callback table and gets a " (code "u32") " ID. JS " (code "addEventListener") " wraps it: when the event fires, JS calls into WASM with the callback ID. Rust looks up the Lambda and evaluates it.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "sx-wasm-shim.js")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "sx-wasm-shim.js")
|
||||
(p "Thin glue (~100 lines):")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li "Instantiate WASM module")
|
||||
(li "Wire handle table")
|
||||
(li "Delegate all platform calls to " (code "sx-platform.js"))
|
||||
@@ -258,8 +258,8 @@
|
||||
(~docs/section :title "Phase 4: Web Framework Loading" :id "phase-4"
|
||||
(p (strong "Goal:") " Both JS and WASM evaluators load the same web framework " (code ".sx") " files at runtime.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Boot sequence (identical for both evaluators)")
|
||||
(ol :class "list-decimal list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Boot sequence (identical for both evaluators)")
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 mt-2")
|
||||
(li "Load " (code "sx-platform.js") " + evaluator (" (code "sx-evaluator.js") " or " (code "sx-wasm-shim.js") ")")
|
||||
(li "Platform registers primitives with evaluator")
|
||||
(li "Platform boot shim evaluates " (code "<script type=\"text/sx-lib\">") " blocks")
|
||||
@@ -267,7 +267,7 @@
|
||||
(li (code "boot-init") " called \u2014 processes component scripts, hydrates, initializes engine")
|
||||
(li "Page is interactive"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Library dependency order")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Library dependency order")
|
||||
(~docs/code :src (highlight "signals.sx \u2190 no SX deps (uses only core primitives)\ndeps.sx \u2190 no SX deps\nframes.sx \u2190 no SX deps\nrouter.sx \u2190 no SX deps\npage-helpers.sx \u2190 no SX deps\nengine.sx \u2190 uses render.sx (core), adapter-dom.sx (core)\norchestration.sx \u2190 depends on engine.sx\nboot.sx \u2190 depends on orchestration.sx" "text")))
|
||||
|
||||
|
||||
@@ -276,17 +276,17 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Phase 5: Verification + Rollout" :id "phase-5"
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Shadow comparison")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Shadow comparison")
|
||||
(p "Run both JS and WASM evaluators on same input, compare outputs:")
|
||||
(ol :class "list-decimal list-inside space-y-1 mt-2"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-1 mt-2")
|
||||
(li (strong "Parse") " \u2014 same AST (already testable with current WASM exports)")
|
||||
(li (strong "Eval") " \u2014 same values (already testable)")
|
||||
(li (strong "Render") " \u2014 same DOM structure (requires Phase 3)"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Feature flag")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Feature flag")
|
||||
(p "Server sets " (code "data-sx-runtime=\"wasm\"") " or " (code "\"js\"") " on root element. Boot shim loads appropriate evaluator. Progressive enhancement: try WASM, fall back to JS.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Test matrix")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Test matrix")
|
||||
(p "All " (code "test-*.sx") " files from both " (code "spec/") " and " (code "web/") " run on BOTH evaluators."))
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "Core is what defines the language.") " Parser, evaluator, primitives, render modes. If you can't run SX without it, it's core (" (code "spec/") "). If you can write it " (em "in") " SX, it's web framework (" (code "web/") ").")
|
||||
(li (strong "Web framework runs ON the evaluator.") " Signals, engine, orchestration, boot, router, deps \u2014 these are SX programs. They're evaluated at runtime, not compiled into the evaluator.")
|
||||
(li (strong "Isolation is the boundary.") " The evaluator can't touch the real world. Platform primitives are the only bridge. JS and WASM evaluators have identical isolation.")
|
||||
@@ -309,7 +309,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Interaction with Other Plans" :id "interactions"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong (a :href "/sx/(etc.(plan.rust-wasm-host))" "Rust/WASM Host")) " \u2014 this plan supersedes it. That plan didn't distinguish core from web framework or propose evaluator isolation. The shared platform layer is the key architectural difference.")
|
||||
(li (strong (a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM")) " \u2014 complementary. This plan bootstraps the tree-walking evaluator. A future bytecode VM compiles SX to bytecodes. The platform layer and handle table are shared.")
|
||||
(li (strong (a :href "/sx/(etc.(plan.runtime-slicing))" "Runtime Slicing")) " \u2014 simplified. With web framework as runtime-evaluated " (code ".sx") ", slicing becomes: ship core + only the framework files you need. L0 pages skip signals/engine entirely.")
|
||||
@@ -323,7 +323,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After completion:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Core evaluator is ~3,200 lines of bootstrapped spec (" (code "spec/") " + adapters from " (code "web/") ").")
|
||||
(li "JS evaluator is isolated \u2014 can't touch the real world, same sandbox as WASM.")
|
||||
(li "Shared " (code "sx-platform.js") " provides all browser primitives to both evaluators.")
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
(~docs/section :title "Context" :id "context"
|
||||
(p "SX has a working server-client pipeline: server evaluates pages with IO (DB, fragments), serializes as SX wire format, client parses and renders to DOM. The language and primitives are already isomorphic " (em "— same spec, same semantics, both sides.") " What's missing is the " (strong "plumbing") " that makes the boundary between server and client a sliding window rather than a fixed wall.")
|
||||
(p "The key insight: " (strong "s-expressions can partially unfold on the server after IO, then finish unfolding on the client.") " The system knows which components have data fetches (via IO detection in " (a :href "/sx/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx") "), resolves those server-side, and sends the rest as pure SX for client rendering. The boundary slides automatically based on what each component actually needs."))
|
||||
(p "The key insight: " (strong "s-expressions can partially unfold on the server after IO, then finish unfolding on the client.") " The system knows which components have data fetches (via IO detection in " (a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-violet-700 underline") "deps.sx") "), resolves those server-side, and sends the rest as pure SX for client rendering. The boundary slides automatically based on what each component actually needs."))
|
||||
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(ul :class "space-y-2 text-stone-700 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-700 list-disc pl-5")
|
||||
(li (strong "Primitive parity: ") "100%. ~80 pure primitives, same names/semantics, JS and Python.")
|
||||
(li (strong "eval/parse/render: ") "Complete both sides. sx-ref.js has eval, parse, render-to-html, render-to-dom, aser.")
|
||||
(li (strong "Engine: ") "engine.sx (morph, swaps, triggers, history), orchestration.sx (fetch, events), boot.sx (hydration) — all transpiled.")
|
||||
@@ -29,13 +29,13 @@
|
||||
|
||||
(~docs/section :title "Phase 1: Component Distribution & Dependency Analysis" :id "phase-1"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "Per-page component bundles instead of sending every definition to every page. Smaller payloads, faster boot, better cache hit rates."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-green-700 underline text-sm font-medium") "View canonical spec: deps.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.bundle-analyzer))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live bundle analyzer"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Per-page component bundles instead of sending every definition to every page. Smaller payloads, faster boot, better cache hit rates."))
|
||||
|
||||
(~docs/subsection :title "The Problem"
|
||||
(p "The page boot payload serializes every component definition in the environment. A page that uses 5 components still receives all 50+. No mechanism determines which components a page actually needs — the boundary between \"loaded\" and \"used\" is invisible."))
|
||||
@@ -43,12 +43,12 @@
|
||||
(~docs/subsection :title "Implementation"
|
||||
|
||||
(p "The dependency analysis algorithm is defined in "
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-violet-700 underline") "deps.sx")
|
||||
" — a spec module bootstrapped to every host. Each host loads it via " (code "--spec-modules deps") " and provides 6 platform functions. The spec is the single source of truth; hosts are interchangeable.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Transitive closure (deps.sx)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Transitive closure (deps.sx)")
|
||||
(p "9 functions that walk the component graph. The core:")
|
||||
(~docs/code :src (highlight "(define (transitive-deps name env)\n (let ((key (if (starts-with? name \"~\") name\n (concat \"~\" name)))\n (seen (set-create)))\n (transitive-deps-walk key env seen)\n (set-remove seen key)))" "lisp"))
|
||||
(p (code "scan-refs") " walks a component body AST collecting " (code "~") " symbols. "
|
||||
@@ -57,18 +57,18 @@
|
||||
"Circular references terminate safely via a seen-set."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Page scanning")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Page scanning")
|
||||
(~docs/code :src (highlight "(define (components-needed page-source env)\n (let ((direct (scan-components-from-source page-source))\n (all-needed (set-create)))\n (for-each (fn (name) ...\n (set-add! all-needed name)\n (set-union! all-needed (component-deps comp)))\n direct)\n all-needed))" "lisp"))
|
||||
(p (code "scan-components-from-source") " finds " (code "(~plans/content-addressed-components/name") " patterns in serialized SX via regex. " (code "components-needed") " combines scanning with the cached transitive closure to produce the minimal component set for a page."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Per-page CSS scoping")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Per-page CSS scoping")
|
||||
(p (code "page-css-classes") " unions the CSS classes from only the components in the page bundle. Pages that don't use a component never pay for its styles."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Platform interface")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Platform interface")
|
||||
(p "The spec declares 6 functions each host implements natively — the only host-specific code:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "component-deps") " / " (code "component-set-deps!") " — read/write the cached deps set on a component object")
|
||||
(li (code "component-css-classes") " — read pre-scanned CSS class names from a component")
|
||||
(li (code "env-components") " — enumerate all component entries in an environment")
|
||||
@@ -76,16 +76,16 @@
|
||||
|
||||
(~docs/subsection :title "Spec module"
|
||||
(p "deps.sx is loaded as a " (strong "spec module") " — an optional extension to the core spec. The bootstrapper flag " (code "--spec-modules deps") " includes it in the generated output alongside the core evaluator, parser, and renderer. Phase 2 IO detection was added to the same module — same bootstrapping mechanism, no architecture changes needed.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/deps.sx — canonical spec (14 functions, 8 platform declarations)")
|
||||
(li "Bootstrapped to all host targets via --spec-modules deps")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "15 dedicated tests: scan, transitive closure, circular deps, compute-all, components-needed")
|
||||
(li "Bootstrapped output verified on both host targets")
|
||||
(li "Full test suite passes with zero regressions")
|
||||
(li (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app"))))
|
||||
(li (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" (~tw :tokens "text-violet-700 underline") "Live bundle analyzer") " shows real per-page savings on this app"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 2
|
||||
@@ -93,41 +93,41 @@
|
||||
|
||||
(~docs/section :title "Phase 2: Smart Server/Client Boundary — IO Detection" :id "phase-2"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer with IO"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "Automatic IO detection and selective expansion. Server expands IO-dependent components, serializes pure ones for client. Per-component intelligence replaces global toggle."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-green-700 underline text-sm font-medium") "View canonical spec: deps.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.bundle-analyzer))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live bundle analyzer with IO"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Automatic IO detection and selective expansion. Server expands IO-dependent components, serializes pure ones for client. Per-component intelligence replaces global toggle."))
|
||||
|
||||
(~docs/subsection :title "IO Detection in the Spec"
|
||||
(p "Five new functions in "
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-violet-700 underline") "deps.sx")
|
||||
" extend the Phase 1 walker to detect IO primitive references:")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. IO scanning")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. IO scanning")
|
||||
(p (code "scan-io-refs") " walks an AST node, collecting symbol names that match an IO name set. The IO set is provided by the host from boundary declarations (all three tiers: core IO, deployment IO, page helpers).")
|
||||
(~docs/code :src (highlight "(define scan-io-refs\n (fn (node io-names)\n (let ((refs (list)))\n (scan-io-refs-walk node io-names refs)\n refs)))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Transitive IO closure")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Transitive IO closure")
|
||||
(p (code "transitive-io-refs") " follows component deps recursively, unioning IO refs from all reachable components and macros. Cycle-safe via seen-set.")
|
||||
(~docs/code :src (highlight "(define transitive-io-refs\n (fn (name env io-names)\n ;; Walk deps, scan each body for IO refs,\n ;; union all refs transitively.\n ...))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Batch computation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Batch computation")
|
||||
(p (code "compute-all-io-refs") " iterates the env, computes transitive IO refs for each component, and caches the result via " (code "component-set-io-refs!") ". Called after " (code "compute-all-deps") " at component registration time."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Component metadata")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Component metadata")
|
||||
(p "Each component now carries " (code "io_refs") " (transitive IO primitive names) alongside " (code "deps") " and " (code "css_classes") ". The derived " (code "is_pure") " property is true when " (code "io_refs") " is empty — the component can render anywhere without server data."))))
|
||||
|
||||
(~docs/subsection :title "Selective Expansion"
|
||||
(p "The partial evaluator " (code "_aser") " now uses per-component IO metadata instead of a global toggle:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "IO-dependent") " → expand server-side (IO must resolve)")
|
||||
(li (strong "Pure") " → serialize for client (let client render)")
|
||||
(li (strong "Layout slot context") " → all components still expand (backwards compat via " (code "_expand_components") " context var)"))
|
||||
@@ -135,17 +135,17 @@
|
||||
|
||||
(~docs/subsection :title "Platform interface additions"
|
||||
(p "Two new platform functions each host implements:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "(component-io-refs c) → cached IO ref list")
|
||||
(li "(component-set-io-refs! c refs) → cache IO refs on component")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Components calling (query ...) or (highlight ...) classified IO-dependent")
|
||||
(li "Pure components (HTML-only) classified pure with empty io_refs")
|
||||
(li "Transitive IO detection: component calling ~other where ~other calls (current-user) → IO-dependent")
|
||||
(li "Bootstrapped to both hosts (sx_ref.py + sx-ref.js)")
|
||||
(li (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification"))))
|
||||
(li (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" (~tw :tokens "text-violet-700 underline") "Live bundle analyzer") " shows per-page IO classification"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 3
|
||||
@@ -153,34 +153,34 @@
|
||||
|
||||
(~docs/section :title "Phase 3: Client-Side Routing (SPA Mode)" :id "phase-3"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(language.(spec.router))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.routing-analyzer))" :class "text-green-700 underline text-sm font-medium" "Live routing analyzer"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "After initial page load, pure pages render instantly without server roundtrips. Client matches routes locally, evaluates content expressions with cached components, and only falls back to server for pages with :data dependencies."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(language.(spec.router))" (~tw :tokens "text-green-700 underline text-sm font-medium") "View canonical spec: router.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.routing-analyzer))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live routing analyzer"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "After initial page load, pure pages render instantly without server roundtrips. Client matches routes locally, evaluates content expressions with cached components, and only falls back to server for pages with :data dependencies."))
|
||||
|
||||
(~docs/subsection :title "Architecture"
|
||||
(p "Three-layer approach: spec defines pure route matching, page registry bridges server metadata to client, orchestration intercepts navigation for try-first/fallback.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Route matching spec (router.sx)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Route matching spec (router.sx)")
|
||||
(p "New spec module with pure functions for Flask-style route pattern matching:")
|
||||
(~docs/code :src (highlight "(define split-path-segments ;; \"/language/docs/hello\" → (\"docs\" \"hello\")\n(define parse-route-pattern ;; \"/language/docs/<slug>\" → segment descriptors\n(define match-route-segments ;; segments + pattern → params dict or nil\n(define find-matching-route ;; path + route table → first match" "lisp"))
|
||||
(p "No platform interface needed — uses only pure string and list primitives. Bootstrapped to both hosts via " (code "--spec-modules deps,router") "."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Page registry")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Page registry")
|
||||
(p "Server serializes defpage metadata as SX dict literals inside " (code "<script type=\"text/sx-pages\">") ". Each entry carries name, path pattern, auth level, has-data flag, serialized content expression, and closure values.")
|
||||
(~docs/code :src (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :content \"(case slug ...)\" :closure {}}" "lisp"))
|
||||
(p "boot.sx processes these at startup using the SX parser — the same " (code "parse") " function from parser.sx — building route entries with parsed patterns into the " (code "_page-routes") " table. No JSON dependency."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Client-side interception (orchestration.sx)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Client-side interception (orchestration.sx)")
|
||||
(p (code "bind-client-route-link") " replaces " (code "bind-boost-link") " in boost processing. On click:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Extract pathname from href")
|
||||
(li "Call " (code "find-matching-route") " against " (code "_page-routes"))
|
||||
(li "If match found AND no :data: evaluate content expression locally with component env + URL params")
|
||||
@@ -190,24 +190,24 @@
|
||||
|
||||
(~docs/subsection :title "What becomes client-routable"
|
||||
(p "All pages with content expressions — most of this docs app. Pure pages render instantly; :data pages fetch data then render client-side (Phase 4):")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "/") ", " (code "/language/docs/") ", " (code "/language/docs/<slug>") " (most slugs), " (code "/applications/protocols/") ", " (code "/applications/protocols/<slug>"))
|
||||
(li (code "/examples/") ", " (code "/examples/<slug>") ", " (code "/etc/essays/") ", " (code "/etc/essays/<slug>"))
|
||||
(li (code "/etc/plans/") ", " (code "/etc/plans/<slug>") ", " (code "/geography/isomorphism/") ", " (code "/language/bootstrappers/")))
|
||||
(p "Pages that fall through to server:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "/language/docs/primitives") " and " (code "/language/docs/special-forms") " (call " (code "primitives-data") " / " (code "special-forms-data") " helpers)")
|
||||
(li (code "/reference/<slug>") " (has " (code ":data (reference-data slug)") ")")
|
||||
(li (code "/language/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")")
|
||||
(li (code "/geography/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")")
|
||||
(li (code "/geography/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/sx/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "Phase 4 demo") ")")))
|
||||
(li (code "/geography/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/sx/(geography.(isomorphism.data-test))" (~tw :tokens "text-violet-700 underline") "Phase 4 demo") ")")))
|
||||
|
||||
(~docs/subsection :title "Try-first/fallback design"
|
||||
(p "Client routing uses a try-first approach: attempt local evaluation in a try/catch, fall back to server fetch on any failure. This avoids needing perfect static analysis of content expressions — if a content expression calls a page helper the client doesn't have, the eval throws, and the server handles it transparently.")
|
||||
(p "Console messages provide visibility: " (code "sx:route client /essays/why-sexps") " vs " (code "sx:route server /specs/eval") "."))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/router.sx — route pattern matching spec")
|
||||
(li "shared/sx/ref/boot.sx — process page registry scripts")
|
||||
(li "shared/sx/ref/orchestration.sx — client route interception")
|
||||
@@ -216,7 +216,7 @@
|
||||
(li "shared/sx/helpers.py — page registry SX serialization")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Pure page navigation: zero server requests, console shows \"sx:route client\"")
|
||||
(li "IO/data page fallback: falls through to server fetch transparently")
|
||||
(li "Browser back/forward works with client-routed pages")
|
||||
@@ -229,39 +229,39 @@
|
||||
|
||||
(~docs/section :title "Phase 4: Client Async & IO Bridge" :id "phase-4"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism.data-test))" :class "text-green-700 underline text-sm font-medium" "Live data test page"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "Client fetches server-evaluated data and renders :data pages locally. Data cached with TTL to avoid redundant fetches on back/forward navigation. All IO stays server-side — no continuations needed."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism.data-test))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live data test page"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Client fetches server-evaluated data and renders :data pages locally. Data cached with TTL to avoid redundant fetches on back/forward navigation. All IO stays server-side — no continuations needed."))
|
||||
|
||||
(~docs/subsection :title "Architecture"
|
||||
(p "Separates IO from rendering. Server evaluates :data expression (async, with DB/service access), serializes result as SX wire format. Client fetches pre-evaluated data, parses it, merges into env, renders pure :content client-side.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Abstract resolve-page-data")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Abstract resolve-page-data")
|
||||
(p "Spec-level primitive in orchestration.sx. The spec says \"I need data for this page\" — platform provides transport:")
|
||||
(~docs/code :src (highlight "(resolve-page-data page-name params\n (fn (data)\n ;; data is a dict — merge into env and render\n (let ((env (merge closure params data))\n (rendered (try-eval-content content-src env)))\n (swap-rendered-content target rendered pathname))))" "lisp"))
|
||||
(p "Browser platform: HTTP fetch to " (code "/sx/data/<page-name>") ". Future platforms could use IPC, cache, WebSocket, etc."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Server data endpoint")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Server data endpoint")
|
||||
(p (code "evaluate_page_data()") " evaluates the :data expression, kebab-cases dict keys (Python " (code "total_count") " → SX " (code "total-count") "), serializes as SX wire format.")
|
||||
(p "Response content type: " (code "text/sx; charset=utf-8") ". Per-page auth enforcement via " (code "_check_page_auth()") "."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Client data cache")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Client data cache")
|
||||
(p "In-memory cache in orchestration.sx, keyed by " (code "page-name:param=value") ". 30-second TTL prevents redundant fetches on back/forward navigation:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Cache miss: " (code "sx:route client+data /path") " — fetches from server, caches, renders")
|
||||
(li "Cache hit: " (code "sx:route client+cache /path") " — instant render from cached data")
|
||||
(li "After TTL: stale entry evicted, fresh fetch on next visit"))
|
||||
(p "Try it: navigate to the " (a :href "/sx/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch)."))))
|
||||
(p "Try it: navigate to the " (a :href "/sx/(geography.(isomorphism.data-test))" (~tw :tokens "text-violet-700 underline") "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch)."))))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/orchestration.sx — resolve-page-data spec, data cache")
|
||||
(li "shared/sx/ref/bootstrap_js.py — platform resolvePageData (HTTP fetch)")
|
||||
(li "shared/sx/pages.py — evaluate_page_data(), auto_mount_page_data()")
|
||||
@@ -270,10 +270,10 @@
|
||||
(li "shared/sx/tests/test_page_data.py — 30 unit tests")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "30 unit tests: serialize roundtrip, kebab-case, deps, full pipeline simulation, cache TTL")
|
||||
(li "Console: " (code "sx:route client+data") " on first visit, " (code "sx:route client+cache") " on return within 30s")
|
||||
(li (a :href "/sx/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "Live data test page") " exercises the full pipeline with server time + pipeline steps")
|
||||
(li (a :href "/sx/(geography.(isomorphism.data-test))" (~tw :tokens "text-violet-700 underline") "Live data test page") " exercises the full pipeline with server time + pipeline steps")
|
||||
(li "append! and dict-set! registered as proper primitives in spec + both hosts"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -282,26 +282,26 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Client IO Proxy" :id "phase-5"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "Components with IO dependencies render client-side. IO primitives are proxied to the server — the client evaluator calls them like normal functions, the proxy fetches results via HTTP, the async DOM renderer awaits the promises and continues."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Components with IO dependencies render client-side. IO primitives are proxied to the server — the client evaluator calls them like normal functions, the proxy fetches results via HTTP, the async DOM renderer awaits the promises and continues."))
|
||||
|
||||
(~docs/subsection :title "How it works"
|
||||
(p "Instead of async-aware continuations (originally planned), Phase 5 was solved by combining three mechanisms that emerged from Phases 3-4:")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. IO dependency detection (from Phase 2)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. IO dependency detection (from Phase 2)")
|
||||
(p "The component dep analyzer scans AST bodies for IO primitive names (highlight, asset-url, query, frag, etc.) and computes transitive IO refs. Pages include their IO dep list in the page registry."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. IO proxy registration")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. IO proxy registration")
|
||||
(p (code "registerIoDeps(names)") " in orchestration.sx registers proxy functions for each IO primitive. When the client evaluator encounters " (code "(highlight code \"sx\")") ", the proxy sends an HTTP request to the server's IO endpoint and returns a Promise."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Async DOM renderer")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Async DOM renderer")
|
||||
(p (code "asyncRenderToDom") " walks the expression tree and handles Promises transparently. When a subexpression returns a Promise (from an IO proxy call), the renderer awaits it and continues building the DOM tree. No continuations needed — JavaScript's native Promise mechanism provides the suspension."))))
|
||||
|
||||
(~docs/subsection :title "Why continuations weren't needed"
|
||||
@@ -309,14 +309,14 @@
|
||||
(p "Delimited continuations remain valuable for Phase 6 (streaming/suspense on the " (em "server") " side, where Python doesn't have native Promise-based suspension in the evaluator). But for client-side IO, Promises + async render were sufficient."))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/orchestration.sx — registerIoDeps, IO proxy registration")
|
||||
(li "shared/sx/ref/bootstrap_js.py — asyncRenderToDom, IO proxy HTTP transport")
|
||||
(li "shared/sx/helpers.py — io_deps in page registry entries")
|
||||
(li "shared/sx/deps.py — transitive IO ref computation")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Navigate to any page with IO deps (e.g. /testing/eval) — console shows IO proxy calls")
|
||||
(li "Components using " (code "highlight") " render correctly via proxy")
|
||||
(li "Pages with " (code "asset-url") " resolve script paths via proxy")
|
||||
@@ -328,15 +328,15 @@
|
||||
|
||||
(~docs/section :title "Phase 6: Streaming & Suspense" :id "phase-6"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism.streaming))" :class "text-green-700 underline text-sm font-medium" "Live streaming demo"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately with loading skeletons, fills in suspended parts as data arrives."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism.streaming))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live streaming demo"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately with loading skeletons, fills in suspended parts as data arrives."))
|
||||
|
||||
(~docs/subsection :title "What was built"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "~shared:pages/suspense") " component — renders fallback content with a stable DOM ID, replaced when resolution arrives")
|
||||
(li (code "defpage :stream true") " — opts a page into streaming response mode")
|
||||
(li (code "defpage :fallback expr") " — custom loading skeleton for streaming pages")
|
||||
@@ -349,35 +349,35 @@
|
||||
|
||||
(~docs/subsection :title "Architecture"
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Suspense component")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Suspense component")
|
||||
(p "When streaming, the server renders the page with " (code "~shared:pages/suspense") " placeholders instead of awaiting IO:")
|
||||
(~docs/code :src (highlight "(~app-body\n :header-rows (~shared:pages/suspense :id \"stream-headers\"\n :fallback (div :class \"h-12 bg-stone-200 animate-pulse\"))\n :content (~shared:pages/suspense :id \"stream-content\"\n :fallback (div :class \"p-8 animate-pulse\" ...)))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Chunked transfer")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Chunked transfer")
|
||||
(p "Quart async generator response yields chunks in order:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "HTML shell + CSS + component defs + page registry + suspense page SX + scripts (immediate)")
|
||||
(li "Resolution " (code "<script>") " tags as each IO completes")
|
||||
(li "Closing " (code "</body></html>"))))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Client resolution")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Client resolution")
|
||||
(p "Each resolution chunk is an inline script:")
|
||||
(~docs/code :src (highlight "<script>\n window.__sxResolve(\"stream-content\",\n \"(~article :title \\\"Hello\\\")\")\n</script>" "html"))
|
||||
(p "The client parses the SX, renders to DOM, and replaces the suspense placeholder's children."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Concurrent IO")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Concurrent IO")
|
||||
(p "Data evaluation and header construction run in parallel. " (code "asyncio.wait(FIRST_COMPLETED)") " yields resolution chunks in whatever order IO completes — no artificial sequencing."))))
|
||||
|
||||
(~docs/subsection :title "Continuation foundation"
|
||||
(p "Delimited continuations (" (code "reset") "/" (code "shift") ") are implemented in the Python evaluator (async_eval.py lines 586-624) and available as special forms. Phase 6 uses the simpler pattern of concurrent IO + completion-order streaming, but the continuation machinery is in place for Phase 7's more sophisticated evaluation-level suspension."))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/templates/pages.sx — ~shared:pages/suspense component definition")
|
||||
(li "shared/sx/types.py — PageDef.stream, PageDef.fallback_expr fields")
|
||||
(li "shared/sx/evaluator.py — defpage :stream/:fallback parsing")
|
||||
@@ -392,16 +392,16 @@
|
||||
(li "sx/sxc/pages/helpers.py — streaming-demo-data page helper")))
|
||||
|
||||
(~docs/subsection :title "Demonstration"
|
||||
(p "The " (a :href "/sx/(geography.(isomorphism.streaming))" :class "text-violet-700 underline" "streaming demo page") " exercises the full pipeline:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(li "Navigate to " (a :href "/sx/(geography.(isomorphism.streaming))" :class "text-violet-700 underline" "/sx/(geography.(isomorphism.streaming))"))
|
||||
(p "The " (a :href "/sx/(geography.(isomorphism.streaming))" (~tw :tokens "text-violet-700 underline") "streaming demo page") " exercises the full pipeline:")
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Navigate to " (a :href "/sx/(geography.(isomorphism.streaming))" (~tw :tokens "text-violet-700 underline") "/sx/(geography.(isomorphism.streaming))"))
|
||||
(li "The page skeleton appears " (strong "instantly") " — animated loading skeletons fill the content area")
|
||||
(li "After ~1.5 seconds, the real content replaces the skeletons (streamed from server)")
|
||||
(li "Open the Network tab — observe " (code "Transfer-Encoding: chunked") " on the document response")
|
||||
(li "The document response shows multiple chunks arriving over time: shell first, then resolution scripts")))
|
||||
|
||||
(~docs/subsection :title "What to verify"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Instant shell: ") "The page HTML arrives immediately — no waiting for the 1.5s data fetch")
|
||||
(li (strong "Suspense placeholders: ") "The " (code "~shared:pages/suspense") " component renders a " (code "data-suspense") " wrapper with animated fallback content")
|
||||
(li (strong "Resolution: ") "The " (code "__sxResolve()") " inline script replaces the placeholder with real rendered content")
|
||||
@@ -415,24 +415,24 @@
|
||||
|
||||
(~docs/section :title "Phase 7: Full Isomorphism" :id "phase-7"
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "Same SX code runs on either side. Runtime chooses optimal split via affinity annotations and render plans. Client data cache managed via invalidation headers and server-driven updates. Cross-host isomorphism verified by 61 automated tests."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Same SX code runs on either side. Runtime chooses optimal split via affinity annotations and render plans. Client data cache managed via invalidation headers and server-driven updates. Cross-host isomorphism verified by 61 automated tests."))
|
||||
|
||||
(~docs/subsection :title "7a. Affinity Annotations & Render Target"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
||||
(p :class "text-green-800 text-sm" "Components declare where they prefer to render. The spec combines affinity with IO analysis to produce a per-component render target decision."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Components declare where they prefer to render. The spec combines affinity with IO analysis to produce a per-component render target decision."))
|
||||
|
||||
(p "Affinity annotations let component authors express rendering preferences:")
|
||||
(~docs/code :src (highlight "(defcomp ~plans/isomorphic/product-grid (&key products)\n :affinity :client ;; interactive, prefer client rendering\n (div ...))\n\n(defcomp ~plans/isomorphic/auth-menu (&key user)\n :affinity :server ;; auth-sensitive, always server\n (div ...))\n\n(defcomp ~plans/isomorphic/card (&key title)\n ;; no annotation = :affinity :auto (default)\n ;; runtime decides from IO analysis\n (div ...))" "lisp"))
|
||||
|
||||
(p "The " (code "render-target") " function in deps.sx combines affinity with IO analysis:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code ":affinity :server") " → always " (code "\"server\"") " (auth-sensitive, secrets, heavy IO)")
|
||||
(li (code ":affinity :client") " → always " (code "\"client\"") " (interactive, IO proxied)")
|
||||
(li (code ":affinity :auto") " (default) → " (code "\"server\"") " if IO-dependent, " (code "\"client\"") " if pure"))
|
||||
@@ -440,7 +440,7 @@
|
||||
(p "The server's partial evaluator (" (code "_aser") ") uses " (code "render_target") " instead of the previous " (code "is_pure") " check. Components with " (code ":affinity :client") " are serialized for client rendering even if they call IO primitives — the IO proxy (Phase 5) handles the calls.")
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/eval.sx — defcomp annotation parsing, defcomp-kwarg helper")
|
||||
(li "shared/sx/ref/deps.sx — render-target function, platform interface")
|
||||
(li "shared/sx/types.py — Component.affinity field, render_target property")
|
||||
@@ -452,7 +452,7 @@
|
||||
(li "shared/sx/ref/test-deps.sx — 6 new render-target tests")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "269 spec tests pass (10 new: 4 eval + 6 deps)")
|
||||
(li "79 Python unit tests pass")
|
||||
(li "Bootstrapped to both hosts (sx_ref.py + sx-browser.js)")
|
||||
@@ -460,10 +460,10 @@
|
||||
|
||||
(~docs/subsection :title "7b. Runtime Boundary Optimizer"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
||||
(p :class "text-green-800 text-sm" "Per-page render plans computed at registration time. Each page knows exactly which components render server-side vs client-side, cached on PageDef."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Per-page render plans computed at registration time. Each page knows exactly which components render server-side vs client-side, cached on PageDef."))
|
||||
|
||||
(p "Given component tree + IO dependency graph + affinity annotations, decide per-component: server-expand, client-render, or stream. Planning step cached at registration, recomputed on component change.")
|
||||
|
||||
@@ -471,7 +471,7 @@
|
||||
(~docs/code :src (highlight "(page-render-plan page-source env io-names)\n;; Returns:\n;; {:components {~plans/content-addressed-components/name \"server\"|\"client\" ...}\n;; :server (list of server-expanded names)\n;; :client (list of client-rendered names)\n;; :io-deps (IO primitives needed by server components)}" "lisp"))
|
||||
|
||||
(~docs/subsection :title "Integration Points"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "shared/sx/ref/deps.sx") " — " (code "page-render-plan") " spec function")
|
||||
(li (code "shared/sx/deps.py") " — Python wrapper, dispatches to bootstrapped code")
|
||||
(li (code "shared/sx/pages.py") " — " (code "compute_page_render_plans()") " called at mount time, caches on PageDef")
|
||||
@@ -479,17 +479,17 @@
|
||||
(li (code "shared/sx/types.py") " — " (code "PageDef.render_plan") " field")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "5 new spec tests (page-render-plan suite)")
|
||||
(li "Render plans visible on " (a :href "/sx/(geography.(isomorphism.affinity))" "affinity demo page"))
|
||||
(li "Client page registry includes :render-plan for each page"))))
|
||||
|
||||
(~docs/subsection :title "7c. Cache Invalidation & Optimistic Data Updates"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
||||
(p :class "text-green-800 text-sm" "Client data cache management, optimistic predicted mutations with snapshot rollback, and server-driven cache updates."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Client data cache management, optimistic predicted mutations with snapshot rollback, and server-driven cache updates."))
|
||||
|
||||
(p "The client-side page data cache (30-second TTL) now supports cache invalidation, server-driven updates, and optimistic mutations. The client predicts the result of a mutation, immediately re-renders with the predicted data, and confirms or reverts when the server responds.")
|
||||
|
||||
@@ -497,12 +497,12 @@
|
||||
(p "Component authors can declare cache invalidation on elements that trigger mutations:")
|
||||
(~docs/code :src (highlight ";; Clear specific page's cache after successful action\n(form :sx-post \"/cart/remove\"\n :sx-cache-invalidate \"cart-page\"\n ...)\n\n;; Clear ALL page caches after action\n(button :sx-post \"/admin/reset\"\n :sx-cache-invalidate \"*\")" "lisp"))
|
||||
(p "The server can also control client cache via response headers:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "SX-Cache-Invalidate: page-name") " — clear cache for a page")
|
||||
(li (code "SX-Cache-Update: page-name") " — replace cache with the response body (SX-format data)")))
|
||||
|
||||
(~docs/subsection :title "Optimistic Mutations"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "optimistic-cache-update") " — applies a mutator function to cached data, saves a snapshot for rollback")
|
||||
(li (strong "optimistic-cache-revert") " — restores the pre-mutation snapshot if the server rejects")
|
||||
(li (strong "optimistic-cache-confirm") " — discards the snapshot after server confirmation")
|
||||
@@ -510,33 +510,33 @@
|
||||
(li (strong "/sx/action/<name>") " — server endpoint for processing mutations (POST, returns SX wire format)")))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/orchestration.sx — cache management + optimistic cache functions + submit-mutation spec")
|
||||
(li "shared/sx/ref/engine.sx — SX-Cache-Invalidate, SX-Cache-Update response headers")
|
||||
(li "shared/sx/pages.py — mount_action_endpoint for /sx/action/<name>")
|
||||
(li "sx/sx/optimistic-demo.sx — live demo component")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "Live demo at " (a :href "/sx/(geography.(isomorphism.optimistic))" :class "text-violet-600 hover:underline" "/sx/(geography.(isomorphism.optimistic))"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Live demo at " (a :href "/sx/(geography.(isomorphism.optimistic))" (~tw :tokens "text-violet-600 hover:underline") "/sx/(geography.(isomorphism.optimistic))"))
|
||||
(li "Console log: " (code "sx:optimistic confirmed") " / " (code "sx:optimistic reverted")))))
|
||||
|
||||
(~docs/subsection :title "7d. Offline Data Layer"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
||||
(p :class "text-green-800 text-sm" "Service Worker with IndexedDB caching, connectivity tracking, and offline mutation queue with replay on reconnect."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Service Worker with IndexedDB caching, connectivity tracking, and offline mutation queue with replay on reconnect."))
|
||||
|
||||
(p "A Service Worker registered at " (code "/sx-sw.js") " provides three-tier caching, plus an offline mutation queue that builds on Phase 7c's optimistic updates:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "/sx/data/* ") "— network-first with IndexedDB fallback. Page data cached on fetch, served from IndexedDB when offline.")
|
||||
(li (strong "/sx/io/* ") "— network-first with IndexedDB fallback. IO proxy responses cached the same way.")
|
||||
(li (strong "/static/* ") "— stale-while-revalidate via Cache API. Serves cached assets immediately, updates in background.")
|
||||
(li (strong "Offline mutations") " — " (code "offline-aware-mutation") " routes to " (code "submit-mutation") " when online, " (code "offline-queue-mutation") " when offline. " (code "offline-sync") " replays the queue on reconnect."))
|
||||
|
||||
(~docs/subsection :title "How It Works"
|
||||
(ol :class "list-decimal list-inside text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal list-inside text-stone-700 space-y-2")
|
||||
(li "On boot, " (code "sx-browser.js") " registers the SW at " (code "/sx-sw.js") " (root scope)")
|
||||
(li "SW intercepts fetch events and routes by URL pattern")
|
||||
(li "For data/IO: try network first, on failure serve from IndexedDB")
|
||||
@@ -545,51 +545,51 @@
|
||||
(li "Offline mutations queue locally, replay on reconnect via " (code "offline-sync"))))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/static/scripts/sx-sw.js — Service Worker (network-first + stale-while-revalidate)")
|
||||
(li "shared/sx/ref/orchestration.sx — offline queue, sync, connectivity tracking, sw-post-message")
|
||||
(li "shared/sx/pages.py — mount_service_worker() serves SW at /sx-sw.js")
|
||||
(li "sx/sx/offline-demo.sx — live demo component")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "Live demo at " (a :href "/sx/(geography.(isomorphism.offline))" :class "text-violet-600 hover:underline" "/sx/(geography.(isomorphism.offline))"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Live demo at " (a :href "/sx/(geography.(isomorphism.offline))" (~tw :tokens "text-violet-600 hover:underline") "/sx/(geography.(isomorphism.offline))"))
|
||||
(li "Test with DevTools Network → Offline mode")
|
||||
(li "Console log: " (code "sx:offline queued") ", " (code "sx:offline syncing") ", " (code "sx:offline synced")))))
|
||||
|
||||
(~docs/subsection :title "7e. Isomorphic Testing"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
||||
(p :class "text-green-800 text-sm" "Cross-host test suite: same SX expressions evaluated on Python (sx_ref.py) and JS (sx-browser.js via Node.js), HTML output compared."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Cross-host test suite: same SX expressions evaluated on Python (sx_ref.py) and JS (sx-browser.js via Node.js), HTML output compared."))
|
||||
|
||||
(p "61 isomorphic tests verify that Python and JS produce identical results:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "37 eval tests: arithmetic, comparison, strings, collections, logic, let/lambda, higher-order, dict, keywords, cond/case")
|
||||
(li "24 render tests: elements, attributes, nesting, void elements, boolean attrs, conditionals, map, components, affinity, HTML escaping"))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/tests/test_isomorphic.py — cross-host test suite")
|
||||
(li "Run: " (code "python3 -m pytest shared/sx/tests/test_isomorphic.py -q")))))
|
||||
|
||||
(~docs/subsection :title "7f. Universal Page Descriptor"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete"))
|
||||
(p :class "text-green-800 text-sm" "defpage is portable: same descriptor executes on server (execute_page) and client (tryClientRoute)."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "defpage is portable: same descriptor executes on server (execute_page) and client (tryClientRoute)."))
|
||||
|
||||
(p "The defpage descriptor is universal — the same definition works on both hosts:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Server: ") (code "execute_page()") " evaluates :data and :content slots, expands server components via " (code "_aser") ", returns SX wire format")
|
||||
(li (strong "Client: ") (code "try-client-route") " matches route, evaluates content SX, renders to DOM. Data pages fetch via " (code "/sx/data/") ", IO proxied via " (code "/sx/io/"))
|
||||
(li (strong "Render plan: ") "each page's " (code ":render-plan") " is included in the client page registry, showing which components render where")
|
||||
(li (strong "Console visibility: ") "client logs " (code "sx:route plan pagename — N server, M client") " on each navigation")))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "All previous phases.")))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Depends on: ") "All previous phases.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Cross-Cutting Concerns
|
||||
@@ -598,7 +598,7 @@
|
||||
(~docs/section :title "Cross-Cutting Concerns" :id "cross-cutting"
|
||||
|
||||
(~docs/subsection :title "Error Reporting"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Phase 1: \"Unknown component\" includes which page expected it and what bundle was sent")
|
||||
(li "Phase 2: Server logs which components expanded server-side vs sent to client")
|
||||
(li "Phase 3: Client route failures include unmatched path and available routes")
|
||||
@@ -606,7 +606,7 @@
|
||||
(li "Source location tracking in parser → propagate through eval → include in error messages")))
|
||||
|
||||
(~docs/subsection :title "Backward Compatibility"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Pages without annotations behave as today")
|
||||
(li "SX-Request / SX-Components / SX-Css header protocol continues")
|
||||
(li "Existing .sx files require no changes")
|
||||
@@ -621,61 +621,61 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Critical Files" :id "critical-files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Role")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phases")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phases")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/async_eval.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Core evaluator, _aser, server/client boundary")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "sx_page(), sx_response(), output pipeline")
|
||||
(td :class "px-3 py-2 text-stone-600" "1, 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/jinja_bridge.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "_COMPONENT_ENV, component registry")
|
||||
(td :class "px-3 py-2 text-stone-600" "1, 2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/pages.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "defpage, execute_page(), page lifecycle")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boot.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client boot, component caching")
|
||||
(td :class "px-3 py-2 text-stone-600" "1, 3, 4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/orchestration.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client fetch/swap/morph")
|
||||
(td :class "px-3 py-2 text-stone-600" "3, 4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/eval.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Evaluator spec")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/engine.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Morph, swaps, triggers")
|
||||
(td :class "px-3 py-2 text-stone-600" "3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/deps.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Dependency analysis (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "1, 2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/router.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client-side routing (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/io-bridge.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client IO primitives (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/suspense.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Streaming/suspension (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "5"))))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/async_eval.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Core evaluator, _aser, server/client boundary")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/helpers.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "sx_page(), sx_response(), output pipeline")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1, 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/jinja_bridge.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "_COMPONENT_ENV, component registry")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1, 2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/pages.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "defpage, execute_page(), page lifecycle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boot.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client boot, component caching")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1, 3, 4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/orchestration.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client fetch/swap/morph")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "3, 4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/eval.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Evaluator spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/engine.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Morph, swaps, triggers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/deps.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Dependency analysis (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1, 2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/router.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client-side routing (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/io-bridge.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client IO primitives (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/suspense.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Streaming/suspension (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "5"))))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; SX CI Pipeline
|
||||
|
||||
@@ -74,63 +74,63 @@
|
||||
(p "The JS bootstrapper has more moving parts than the Python one because "
|
||||
"JavaScript is the " (em "client") " host. The browser runtime includes "
|
||||
"things Python never needs:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Spec Module")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Purpose")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Python?")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Browser?")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Spec Module")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Purpose")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Python?")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Browser?")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "eval.sx")
|
||||
(td :class "px-4 py-2" "Core evaluator, special forms, TCO")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "render.sx")
|
||||
(td :class "px-4 py-2" "Tag registry, void elements, boolean attrs")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "parser.sx")
|
||||
(td :class "px-4 py-2" "Tokenizer, parser, serializer")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "adapter-html.sx")
|
||||
(td :class "px-4 py-2" "Render to HTML string")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "adapter-sx.sx")
|
||||
(td :class "px-4 py-2" "Serialize to SX wire format")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono" "adapter-dom.sx")
|
||||
(td :class "px-4 py-2" "Render to live DOM nodes")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono" "engine.sx")
|
||||
(td :class "px-4 py-2" "Fetch, swap, trigger, history")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono" "orchestration.sx")
|
||||
(td :class "px-4 py-2" "Element scanning, attribute processing")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono" "boot.sx")
|
||||
(td :class "px-4 py-2" "Script processing, mount, hydration")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "signals.sx")
|
||||
(td :class "px-4 py-2" "Reactive signal runtime")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "deps.sx")
|
||||
(td :class "px-4 py-2" "Component dependency analysis")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "router.sx")
|
||||
(td :class "px-4 py-2" "Client-side route matching")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes")))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "eval.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Core evaluator, special forms, TCO")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "render.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Tag registry, void elements, boolean attrs")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "parser.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Tokenizer, parser, serializer")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "adapter-html.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Render to HTML string")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "adapter-sx.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Serialize to SX wire format")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "adapter-dom.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Render to live DOM nodes")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "engine.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Fetch, swap, trigger, history")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "orchestration.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Element scanning, attribute processing")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "boot.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Script processing, mount, hydration")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "signals.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Reactive signal runtime")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "deps.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Component dependency analysis")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "router.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Client-side route matching")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes")))))
|
||||
(p "Blue rows are browser-only modules. " (code "js.sx") " must handle all of them. "
|
||||
"The platform interface is also larger: DOM primitives (" (code "dom-create-element")
|
||||
", " (code "dom-append") ", " (code "dom-set-attr") ", ...), "
|
||||
@@ -147,78 +147,78 @@
|
||||
|
||||
(~docs/subsection :title "Name Mangling"
|
||||
(p "SX uses kebab-case. JavaScript uses camelCase.")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "SX")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "JavaScript")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Rule")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "SX")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "JavaScript")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Rule")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "eval-expr")
|
||||
(td :class "px-4 py-2 font-mono" "evalExpr")
|
||||
(td :class "px-4 py-2" "kebab → camelCase"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "nil?")
|
||||
(td :class "px-4 py-2 font-mono" "isNil")
|
||||
(td :class "px-4 py-2" "predicate → is-prefix"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "empty?")
|
||||
(td :class "px-4 py-2 font-mono" "isEmpty")
|
||||
(td :class "px-4 py-2" "? → is-prefix (general)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "set!")
|
||||
(td :class "px-4 py-2 font-mono" "—")
|
||||
(td :class "px-4 py-2" "assignment (no rename)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "dom-create-element")
|
||||
(td :class "px-4 py-2 font-mono" "domCreateElement")
|
||||
(td :class "px-4 py-2" "platform function rename"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "delete")
|
||||
(td :class "px-4 py-2 font-mono" "delete_")
|
||||
(td :class "px-4 py-2" "JS reserved word escape"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "eval-expr")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "evalExpr")
|
||||
(td (~tw :tokens "px-4 py-2") "kebab → camelCase"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "nil?")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "isNil")
|
||||
(td (~tw :tokens "px-4 py-2") "predicate → is-prefix"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "empty?")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "isEmpty")
|
||||
(td (~tw :tokens "px-4 py-2") "? → is-prefix (general)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "set!")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "—")
|
||||
(td (~tw :tokens "px-4 py-2") "assignment (no rename)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "dom-create-element")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "domCreateElement")
|
||||
(td (~tw :tokens "px-4 py-2") "platform function rename"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "delete")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "delete_")
|
||||
(td (~tw :tokens "px-4 py-2") "JS reserved word escape"))))))
|
||||
|
||||
(~docs/subsection :title "Special Forms → JavaScript"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "SX")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "JavaScript")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "SX")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "JavaScript")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(if c t e)")
|
||||
(td :class "px-4 py-2 font-mono" "(sxTruthy(c) ? t : e)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(when c body)")
|
||||
(td :class "px-4 py-2 font-mono" "(sxTruthy(c) ? body : NIL)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(let ((a 1)) body)")
|
||||
(td :class "px-4 py-2 font-mono" "(function(a) { return body; })(1)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(fn (x) body)")
|
||||
(td :class "px-4 py-2 font-mono" "function(x) { return body; }"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(define name val)")
|
||||
(td :class "px-4 py-2 font-mono" "var name = val;"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(and a b c)")
|
||||
(td :class "px-4 py-2 font-mono" "(sxTruthy(a) ? (sxTruthy(b) ? c : b) : a)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(case x \"a\" 1 ...)")
|
||||
(td :class "px-4 py-2 font-mono" "sxCase(x, [[\"a\", () => 1], ...])"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(str a b c)")
|
||||
(td :class "px-4 py-2 font-mono" "sxStr(a, b, c)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "&rest args")
|
||||
(td :class "px-4 py-2 font-mono" "...args (rest params)"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(if c t e)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(sxTruthy(c) ? t : e)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(when c body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(sxTruthy(c) ? body : NIL)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(let ((a 1)) body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(function(a) { return body; })(1)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(fn (x) body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "function(x) { return body; }"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(define name val)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "var name = val;"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(and a b c)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(sxTruthy(a) ? (sxTruthy(b) ? c : b) : a)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(case x \"a\" 1 ...)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "sxCase(x, [[\"a\", () => 1], ...])"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(str a b c)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "sxStr(a, b, c)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "&rest args")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "...args (rest params)"))))))
|
||||
|
||||
(~docs/subsection :title "JavaScript Advantages"
|
||||
(p "JavaScript is easier to target than Python in two key ways:")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-700")
|
||||
(li (strong "No mutation problem. ")
|
||||
"JavaScript closures capture by reference, not by value. "
|
||||
(code "set!") " from a nested function Just Works — no cell variable "
|
||||
@@ -237,7 +237,7 @@
|
||||
"definitions — it fetches data, resolves conditionals, expands components, "
|
||||
"and produces a complete DOM description as an SX tree. Currently this tree "
|
||||
"is either:")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li "Rendered to HTML server-side (" (code "render-to-html") ")")
|
||||
(li "Serialized as SX wire format for the client to render (" (code "aser") ")"))
|
||||
(p "A third option: " (strong "compile it to JavaScript") ". "
|
||||
@@ -279,7 +279,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
(~docs/subsection :title "Why Not Just Use HTML?"
|
||||
(p "HTML already does this — " (code "innerHTML") " parses and builds DOM. "
|
||||
"Why compile to JS instead?")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-700")
|
||||
(li (strong "Event handlers. ")
|
||||
"HTML can't express " (code ":on-click") " or " (code ":sx-get")
|
||||
" — those need JavaScript. The compiled JS can wire up event "
|
||||
@@ -304,7 +304,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
(~docs/subsection :title "Hybrid Mode"
|
||||
(p "Not every page is fully static. Some parts are server-rendered, "
|
||||
"some are interactive. " (code "js.sx") " handles this with a hybrid approach:")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-700")
|
||||
(li (strong "Static subtrees") " → compiled to DOM construction code (no runtime)")
|
||||
(li (strong "Reactive islands") " → compiled with signal creation + subscriptions "
|
||||
"(needs signal runtime, ~2KB)")
|
||||
@@ -322,45 +322,45 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/section :title "The Bootstrap Chain" :id "chain"
|
||||
(p "With both " (code "py.sx") " and " (code "js.sx") ", the full picture:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Translator")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Written in")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Outputs")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Replaces")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Translator")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Written in")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Outputs")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Replaces")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "z3.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "SMT-LIB")
|
||||
(td :class "px-4 py-2 text-stone-400 italic" "(none — new capability)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "prove.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "Constraint proofs")
|
||||
(td :class "px-4 py-2 text-stone-400 italic" "(none — new capability)"))
|
||||
(tr :class "border-t border-stone-100 bg-violet-50"
|
||||
(td :class "px-4 py-2 font-mono text-violet-700" "py.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "Python")
|
||||
(td :class "px-4 py-2 font-mono" "bootstrap_py.py"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono text-blue-700" "js.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "JavaScript")
|
||||
(td :class "px-4 py-2 font-mono" "bootstrap_js.py"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono text-stone-400" "go.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "Go")
|
||||
(td :class "px-4 py-2 text-stone-400 italic" "(future host)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono text-stone-400" "rs.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "Rust")
|
||||
(td :class "px-4 py-2 text-stone-400 italic" "(future host)")))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "z3.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "SMT-LIB")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400 italic") "(none — new capability)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "prove.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "Constraint proofs")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400 italic") "(none — new capability)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-violet-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-violet-700") "py.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "Python")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "bootstrap_py.py"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-blue-700") "js.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "JavaScript")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "bootstrap_js.py"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-stone-400") "go.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "Go")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400 italic") "(future host)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-stone-400") "rs.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "Rust")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400 italic") "(future host)")))))
|
||||
(p "Every translator is an SX program. The only Python left is the platform "
|
||||
"interface (types, DOM primitives, runtime support functions) and the thin "
|
||||
"runner script that loads " (code "py.sx") " or " (code "js.sx")
|
||||
@@ -374,7 +374,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/subsection :title "Phase 1: Expression Translator"
|
||||
(p "Core SX-to-JavaScript expression translation.")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "js-mangle") " — SX name → JavaScript identifier (RENAMES + kebab→camelCase)")
|
||||
(li (code "js-literal") " — atoms: numbers, strings, booleans, nil, symbols, keywords")
|
||||
(li (code "js-expr") " — recursive expression translator")
|
||||
@@ -388,7 +388,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/subsection :title "Phase 2: Statement Translator"
|
||||
(p "Top-level and function body statement emission.")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "js-statement") " — emit as JavaScript statement")
|
||||
(li (code "define") " → " (code "var name = expr;"))
|
||||
(li (code "set!") " → direct assignment (closures capture by reference)")
|
||||
@@ -398,7 +398,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/subsection :title "Phase 3: Spec Bootstrapper"
|
||||
(p "Process spec files identically to " (code "bootstrap_js.py") ".")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "js-extract-defines") " — parse .sx source, collect top-level defines")
|
||||
(li (code "js-translate-file") " — translate a list of define expressions")
|
||||
(li "Adapter selection: parser, html, sx, dom, engine, orchestration, boot")
|
||||
@@ -407,7 +407,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/subsection :title "Phase 4: Component Compiler"
|
||||
(p "Ahead-of-time compilation of evaluated SX trees to JavaScript.")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "js-compile-element") " — emit " (code "createElement") " + attribute setting")
|
||||
(li (code "js-compile-text") " — emit " (code "textContent") " or " (code "createTextNode"))
|
||||
(li (code "js-compile-component") " — inline-expand or emit component call")
|
||||
@@ -431,42 +431,42 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Comparison with py.sx" :id "comparison"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Concern")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" (code "py.sx"))
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" (code "js.sx"))))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Concern")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") (code "py.sx"))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") (code "js.sx"))))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Naming convention")
|
||||
(td :class "px-4 py-2" "snake_case")
|
||||
(td :class "px-4 py-2" "camelCase"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Closures & mutation")
|
||||
(td :class "px-4 py-2" "Cell variable hack")
|
||||
(td :class "px-4 py-2" "Direct (reference capture)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Spec modules")
|
||||
(td :class "px-4 py-2" "eval, render, html, sx, deps, signals")
|
||||
(td :class "px-4 py-2" "All 12 modules"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Platform interface")
|
||||
(td :class "px-4 py-2" "~300 lines")
|
||||
(td :class "px-4 py-2" "~1500 lines (DOM, browser APIs)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "RENAMES table")
|
||||
(td :class "px-4 py-2" "~200 entries")
|
||||
(td :class "px-4 py-2" "~350 entries"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Component compilation")
|
||||
(td :class "px-4 py-2 text-stone-400" "N/A")
|
||||
(td :class "px-4 py-2" "Ahead-of-time DOM compiler"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Estimated size")
|
||||
(td :class "px-4 py-2" "~800-1000 lines")
|
||||
(td :class "px-4 py-2" "~1200-1500 lines"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Naming convention")
|
||||
(td (~tw :tokens "px-4 py-2") "snake_case")
|
||||
(td (~tw :tokens "px-4 py-2") "camelCase"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Closures & mutation")
|
||||
(td (~tw :tokens "px-4 py-2") "Cell variable hack")
|
||||
(td (~tw :tokens "px-4 py-2") "Direct (reference capture)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Spec modules")
|
||||
(td (~tw :tokens "px-4 py-2") "eval, render, html, sx, deps, signals")
|
||||
(td (~tw :tokens "px-4 py-2") "All 12 modules"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Platform interface")
|
||||
(td (~tw :tokens "px-4 py-2") "~300 lines")
|
||||
(td (~tw :tokens "px-4 py-2") "~1500 lines (DOM, browser APIs)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "RENAMES table")
|
||||
(td (~tw :tokens "px-4 py-2") "~200 entries")
|
||||
(td (~tw :tokens "px-4 py-2") "~350 entries"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Component compilation")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400") "N/A")
|
||||
(td (~tw :tokens "px-4 py-2") "Ahead-of-time DOM compiler"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Estimated size")
|
||||
(td (~tw :tokens "px-4 py-2") "~800-1000 lines")
|
||||
(td (~tw :tokens "px-4 py-2") "~1200-1500 lines"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Implications
|
||||
@@ -486,7 +486,7 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
|
||||
|
||||
(~docs/subsection :title "Progressive Enhancement Layers"
|
||||
(p "The component compiler naturally supports progressive enhancement:")
|
||||
(ol :class "list-decimal pl-6 space-y-1 text-stone-700"
|
||||
(ol (~tw :tokens "list-decimal pl-6 space-y-1 text-stone-700")
|
||||
(li (strong "HTML") " — server renders to HTML string. No JS needed. Works everywhere.")
|
||||
(li (strong "Compiled JS") " — server compiles to DOM construction code. "
|
||||
"Event handlers work. No SX runtime. Kilobytes, not megabytes.")
|
||||
@@ -500,7 +500,7 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
|
||||
|
||||
(~docs/subsection :title "The Bootstrap Completion"
|
||||
(p "With " (code "py.sx") " and " (code "js.sx") " both written in SX:")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-700")
|
||||
(li "The " (em "spec") " defines SX semantics (" (code "eval.sx") ", " (code "render.sx") ", ...)")
|
||||
(li "The " (em "translators") " convert the spec to host languages (" (code "py.sx") ", " (code "js.sx") ")")
|
||||
(li "The " (em "prover") " verifies the spec's properties (" (code "z3.sx") ", " (code "prove.sx") ")")
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
(~docs/subsection :title "Transport Hierarchy"
|
||||
(p "Three tiers, progressively more capable:")
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li (strong "Chunked streaming") " (done) — single HTTP response, each suspense resolves once. "
|
||||
"Best for: initial page load with slow IO.")
|
||||
(li (strong "SSE") " — persistent one-way connection, server pushes resolve events. "
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
(~docs/subsection :title "Shared Resolution Mechanism"
|
||||
(p "All three transports use the same client-side resolution:")
|
||||
(ul :class "list-disc list-inside space-y-1 text-stone-600 text-sm"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 text-stone-600 text-sm")
|
||||
(li (code "Sx.resolveSuspense(id, sxSource)") " — already exists, parses SX and renders to DOM")
|
||||
(li "SSE: " (code "EventSource") " → " (code "onmessage") " → " (code "resolveSuspense()"))
|
||||
(li "WS: " (code "WebSocket") " → " (code "onmessage") " → " (code "resolveSuspense()"))
|
||||
@@ -54,7 +54,7 @@
|
||||
(~docs/section :title "Implementation" :id "implementation"
|
||||
|
||||
(~docs/subsection :title "Phase 1: SSE Infrastructure"
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li "Add " (code "~live") " component to " (code "shared/sx/templates/") " — renders child suspense placeholders, "
|
||||
"emits " (code "data-sx-live") " attribute with SSE endpoint URL")
|
||||
(li "Add " (code "sx-live.js") " client module — on boot, finds " (code "[data-sx-live]") " elements, "
|
||||
@@ -63,48 +63,48 @@
|
||||
(li "Add " (code "sse_stream()") " Quart helper — returns async generator Response with correct headers")))
|
||||
|
||||
(~docs/subsection :title "Phase 2: Defpage Integration"
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li "New " (code ":live") " defpage slot — declares SSE endpoint + suspense bindings")
|
||||
(li "Auto-mount SSE endpoint alongside the page route")
|
||||
(li "Component defs sent as first SSE event on connection open")
|
||||
(li "Automatic reconnection with exponential backoff")))
|
||||
|
||||
(~docs/subsection :title "Phase 3: WebSocket"
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li "Add " (code "~ws") " component — bidirectional channel with send/receive")
|
||||
(li "Add " (code "sx-ws.js") " client module — WebSocket management, message routing")
|
||||
(li "Server-side: Quart WebSocket handlers that receive and broadcast SX events")
|
||||
(li "Client-side: " (code "sx-send") " primitive for sending SX expressions to server")))
|
||||
|
||||
(~docs/subsection :title "Phase 4: Spec & Boundary"
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li "Spec " (code "~live") " and " (code "~ws") " in " (code "render.sx") " (how they render in each mode)")
|
||||
(li "Add SSE/WS IO primitives to " (code "boundary.sx"))
|
||||
(li "Bootstrap SSE/WS connection management into " (code "sx-ref.js"))
|
||||
(li "Spec-level tests for resolve, reconnection, and message routing"))))
|
||||
|
||||
(~docs/section :title "Files" :id "files"
|
||||
(table :class "w-full text-left border-collapse"
|
||||
(table (~tw :tokens "w-full text-left border-collapse")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/live.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "~live component definition"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/static/scripts/sx-live.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "SSE client — EventSource → resolveSuspense"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/sse.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "SSE helpers — event formatting, stream response"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/static/scripts/sx-ws.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "WebSocket client — bidirectional SX channel"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/render.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Spec: ~live and ~ws rendering in all modes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boundary.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "SSE/WS IO primitive declarations")))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/live.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~live component definition"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/static/scripts/sx-live.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SSE client — EventSource → resolveSuspense"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/sse.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SSE helpers — event formatting, stream response"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/static/scripts/sx-ws.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "WebSocket client — bidirectional SX channel"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/render.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Spec: ~live and ~ws rendering in all modes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boundary.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SSE/WS IO primitive declarations")))))))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/mother-language/plan-mother-language-content ()
|
||||
(~docs/page :title "Mother Language"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"The ideal language for evaluating the SX core spec is SX itself. "
|
||||
"The path: OCaml as the initial substrate (closest existing language to what CEK is), "
|
||||
"Koka as an alternative (compile-time linearity), ultimately a self-hosting SX compiler "
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
(~docs/section :title "The Argument" :id "argument"
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What the evaluator actually does")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What the evaluator actually does")
|
||||
(p "The CEK machine is a " (code "state \u2192 state") " loop over sum types. "
|
||||
"Each step pattern-matches on the Control register, consults the Environment, "
|
||||
"and transforms the Kontinuation. Every SX expression, every component render, "
|
||||
@@ -27,10 +27,10 @@
|
||||
"with minimal allocation. That means: algebraic types, pattern matching, "
|
||||
"persistent data structures, and a native effect system.")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Why multiple hosts is the wrong goal")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Why multiple hosts is the wrong goal")
|
||||
(p "The current architecture bootstraps the spec to Python, JavaScript, and Rust. "
|
||||
"Each host has impedance mismatches:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (strong "Python") " \u2014 slow. Tree-walk overhead is 100\u20131000x vs native. "
|
||||
"The async adapter is complex because Python's async model is cooperative, not effect-based.")
|
||||
(li (strong "JavaScript") " \u2014 no sum types, prototype-based dispatch, GC pauses unpredictable. "
|
||||
@@ -40,7 +40,7 @@
|
||||
(p "Each host makes the evaluator work, but none make it " (em "natural") ". "
|
||||
"The translation is structure-" (em "creating") ", not structure-" (em "preserving") ".")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "The Mother Language is SX")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "The Mother Language is SX")
|
||||
(p "The spec defines the semantics. The CEK machine is the most explicit form of those semantics. "
|
||||
"The ideal \"language\" is one that maps 1:1 onto CEK transitions and compiles them to "
|
||||
"optimal machine code. That language is SX itself \u2014 compiled, not interpreted.")
|
||||
@@ -58,49 +58,49 @@
|
||||
(p "OCaml is the closest existing language to what the CEK machine is. "
|
||||
"The translation from " (code "cek.sx") " to OCaml is nearly mechanical.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Natural mapping")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SX concept")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "OCaml primitive")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Notes")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Natural mapping")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "SX concept")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "OCaml primitive")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Notes")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Value (Nil | Num | Str | List | ...)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Algebraic variant type")
|
||||
(td :class "px-3 py-2 text-stone-600" "Direct mapping, no boxing"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Frame (IfFrame | ArgFrame | MapFrame | ...)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Algebraic variant type")
|
||||
(td :class "px-3 py-2 text-stone-600" "20+ variants, pattern match dispatch"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Environment (persistent map)")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "Map.S"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Built-in balanced tree, structural sharing"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Continuation (list of frames)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Immutable list")
|
||||
(td :class "px-3 py-2 text-stone-600" "cons/match, O(1) push/pop"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "cek-step (pattern match on C)")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "match") " expression")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compiles to jump table"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "shift/reset")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "perform") " / " (code "continue"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Native in OCaml 5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Concurrent CEK (fibers)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Domains + effect handlers")
|
||||
(td :class "px-3 py-2 text-stone-600" "One fiber per CEK machine"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Value (Nil | Num | Str | List | ...)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Algebraic variant type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Direct mapping, no boxing"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Frame (IfFrame | ArgFrame | MapFrame | ...)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Algebraic variant type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "20+ variants, pattern match dispatch"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Environment (persistent map)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "Map.S"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Built-in balanced tree, structural sharing"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Continuation (list of frames)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Immutable list")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "cons/match, O(1) push/pop"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "cek-step (pattern match on C)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "match") " expression")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compiles to jump table"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "shift/reset")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "perform") " / " (code "continue"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Native in OCaml 5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Concurrent CEK (fibers)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Domains + effect handlers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "One fiber per CEK machine"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Linear continuations")
|
||||
(td :class "px-3 py-2 text-stone-700" "One-shot continuations (default)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Runtime-enforced, not compile-time")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Linear continuations")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "One-shot continuations (default)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Runtime-enforced, not compile-time")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Compilation targets")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Compilation targets")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (strong "Native") " \u2014 OCaml's native compiler produces fast binaries, small footprint. "
|
||||
"Embed in Python via C ABI (ctypes/cffi). Embed in Node via N-API.")
|
||||
(li (strong "WASM") " \u2014 " (code "wasm_of_ocaml") " is mature (used by Facebook's Flow/Reason). "
|
||||
@@ -108,7 +108,7 @@
|
||||
(li (strong "JavaScript") " \u2014 " (code "js_of_ocaml") " for legacy browser targets. "
|
||||
"Falls back to JS when WASM isn't available."))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What OCaml replaces")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What OCaml replaces")
|
||||
(p "The Haskell and Rust evaluator implementations become unnecessary. "
|
||||
"OCaml covers both server (native) and client (WASM) from one codebase. "
|
||||
"The sx-haskell and sx-rust work proved the spec is host-independent \u2014 "
|
||||
@@ -128,8 +128,8 @@
|
||||
(p "Koka (Daan Leijen, MSR) addresses OCaml's one weakness: "
|
||||
(strong "compile-time linearity") ".")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Where Koka wins")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Where Koka wins")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Perceus reference counting") " \u2014 the compiler tracks which values are used linearly. "
|
||||
"Linear values are mutated in-place (zero allocation). "
|
||||
"Non-linear values use reference counting (no GC at all).")
|
||||
@@ -140,8 +140,8 @@
|
||||
"The type system prevents invoking a linear continuation twice. "
|
||||
"No runtime check needed."))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Where Koka is weaker")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Where Koka is weaker")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Maturity") " \u2014 research language. Smaller ecosystem, fewer FFI bindings, "
|
||||
"less battle-tested than OCaml.")
|
||||
(li (strong "WASM backend") " \u2014 compiles to C \u2192 WASM (via Emscripten). "
|
||||
@@ -162,38 +162,38 @@
|
||||
|
||||
(p "The end state: SX compiles itself. No intermediate language, no general-purpose host.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 1: OCaml bootstrapper")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 1: OCaml bootstrapper")
|
||||
(p "Write " (code "bootstrap_ml.py") " \u2014 reads " (code "cek.sx") " + " (code "frames.sx")
|
||||
" + " (code "primitives.sx") " + " (code "eval.sx") ", emits OCaml source. "
|
||||
"Same pattern as the existing Rust/Python/JS bootstrappers.")
|
||||
(p "The OCaml output is a standalone module:")
|
||||
(~docs/code :src (highlight "type value =\n | Nil | Bool of bool | Num of float | Str of string\n | Sym of string | Kw of string\n | List of value list | Dict of (value * value) list\n | Lambda of params * value list * env\n | Component of string * params * value list * env\n | Handle of int (* opaque FFI reference *)\n\ntype frame =\n | IfFrame of value list * value list * env\n | ArgFrame of value list * value list * env\n | MapFrame of value * value list * value list * env\n | ReactiveResetFrame of value\n | DerefFrame of value\n (* ... 20+ frame types from frames.sx *)\n\ntype kont = frame list\ntype state = value * env * kont\n\nlet step ((ctrl, env, kont) : state) : state =\n match ctrl with\n | Lit v -> continue_val v kont\n | Var name -> continue_val (Env.find name env) kont\n | App (f, args) -> (f, env, ArgFrame(args, [], env) :: kont)\n | ..." "ocaml"))
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Phase 2: Native + WASM builds")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Phase 2: Native + WASM builds")
|
||||
(p "Compile the OCaml output to:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (code "sx_core.so") " / " (code "sx_core.dylib") " \u2014 native shared library, C ABI")
|
||||
(li (code "sx_core.wasm") " \u2014 via " (code "wasm_of_ocaml") " for browser")
|
||||
(li (code "sx_core.js") " \u2014 via " (code "js_of_ocaml") " as JS fallback"))
|
||||
(p "Python web framework calls " (code "sx_core.so") " via cffi. "
|
||||
"Browser loads " (code "sx_core.wasm") " via " (code "sx-platform.js") ".")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Phase 3: SX evaluates web framework")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Phase 3: SX evaluates web framework")
|
||||
(p "The compiled core evaluator loads web framework " (code ".sx") " at runtime "
|
||||
"(signals, engine, orchestration, boot). Same as the "
|
||||
(a :href "/sx/(etc.(plan.isolated-evaluator))" "Isolated Evaluator") " plan, "
|
||||
"but the evaluator is compiled OCaml/WASM instead of bootstrapped JS.")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Phase 4: SX linearity checking")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Phase 4: SX linearity checking")
|
||||
(p "Extend " (code "types.sx") " with quantity annotations:")
|
||||
(~docs/code :src (highlight ";; Quantity annotations on types\n(define-type (Signal a) :quantity :affine) ;; use at most once per scope\n(define-type (Channel a) :quantity :linear) ;; must be consumed exactly once\n\n;; Effect declarations with linearity\n(define-io-primitive \"send-message\"\n :params (channel message)\n :quantity :linear\n :effects [io]\n :doc \"Must be handled exactly once.\")\n\n;; The type checker (specced in .sx, compiled to OCaml) validates\n;; linearity at component registration time. Runtime enforcement\n;; by OCaml's one-shot continuations is the safety net." "lisp"))
|
||||
(p "The type checker runs at spec-validation time. The compiled evaluator "
|
||||
"executes already-verified code. SX's type system provides the linearity "
|
||||
"guarantees, not the host language.")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Phase 5: Self-hosting compiler")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Phase 5: Self-hosting compiler")
|
||||
(p "Write the compiler itself in SX:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li "Spec the CEK-to-native code generation in " (code ".sx") " files")
|
||||
(li "The Phase 2 OCaml evaluator compiles the compiler spec")
|
||||
(li "The compiled compiler can then compile itself")
|
||||
@@ -215,46 +215,46 @@
|
||||
(p "OCaml 5's concurrency model maps directly onto the "
|
||||
(a :href "/sx/(etc.(plan.foundations))" "Foundations") " plan's concurrent CEK spec.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Mapping")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SX primitive")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "OCaml 5")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Characteristic")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Mapping")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "SX primitive")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "OCaml 5")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Characteristic")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "spawn"))
|
||||
(td :class "px-3 py-2 text-stone-700" "Fiber via " (code "perform Spawn"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Lightweight, scheduled by effect handler"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "channel"))
|
||||
(td :class "px-3 py-2 text-stone-700" (code "Eio.Stream"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Typed, bounded, backpressure"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "yield!"))
|
||||
(td :class "px-3 py-2 text-stone-700" (code "perform Yield"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Cooperative, zero-cost"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "select"))
|
||||
(td :class "px-3 py-2 text-stone-700" (code "Eio.Fiber.any"))
|
||||
(td :class "px-3 py-2 text-stone-600" "First-to-complete"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "fork-join"))
|
||||
(td :class "px-3 py-2 text-stone-700" (code "Eio.Fiber.all"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Structured concurrency"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "spawn"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fiber via " (code "perform Spawn"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Lightweight, scheduled by effect handler"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "channel"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "Eio.Stream"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Typed, bounded, backpressure"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "yield!"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "perform Yield"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Cooperative, zero-cost"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "select"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "Eio.Fiber.any"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "First-to-complete"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "fork-join"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "Eio.Fiber.all"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Structured concurrency"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "DAG scheduler")
|
||||
(td :class "px-3 py-2 text-stone-700" "Domains + fiber pool")
|
||||
(td :class "px-3 py-2 text-stone-600" "True parallelism across cores")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DAG scheduler")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Domains + fiber pool")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "True parallelism across cores")))))
|
||||
|
||||
(p "Each concurrent CEK machine is a fiber. The scheduler is an effect handler. "
|
||||
"This isn't simulating concurrency \u2014 it's using native concurrency whose mechanism " (em "is") " effects.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The Art DAG connection")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The Art DAG connection")
|
||||
(p "Art DAG's 3-phase execution (analyze \u2192 plan \u2192 execute) maps onto "
|
||||
"concurrent CEK + OCaml domains:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li "Analyze: single CEK machine walks the DAG graph (one fiber)")
|
||||
(li "Plan: resolve dependencies, topological sort (pure computation)")
|
||||
(li "Execute: spawn one fiber per independent node, fan out to domains (true parallelism)")
|
||||
@@ -269,9 +269,9 @@
|
||||
|
||||
(p "The linearity axis from foundations. Two enforcement layers:")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Layer 1: SX type system (primary)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Layer 1: SX type system (primary)")
|
||||
(p "Quantity annotations in " (code "types.sx") " checked at spec-validation time:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (code ":linear") " (1) \u2014 must be used exactly once")
|
||||
(li (code ":affine") " (\u22641) \u2014 may be used at most once (can drop)")
|
||||
(li (code ":unrestricted") " (\u03c9) \u2014 may be used any number of times"))
|
||||
@@ -279,16 +279,16 @@
|
||||
"A channel is consumed. A resource handle is closed. "
|
||||
"The type checker proves this before the evaluator ever runs.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Layer 2: Host runtime (safety net)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Layer 2: Host runtime (safety net)")
|
||||
(p "OCaml 5's one-shot continuations enforce linearity at runtime. "
|
||||
"A continuation can only be " (code "continue") "'d once \u2014 second invocation raises an exception. "
|
||||
"This catches any bugs in the type checker itself.")
|
||||
(p "If Koka replaces OCaml: compile-time enforcement replaces runtime enforcement. "
|
||||
"The safety net becomes a proof. Same semantics, stronger guarantees.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Decision point")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Decision point")
|
||||
(p "When Step 5 (Linear Effects) of the foundations plan is reached:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "If SX's type checker can enforce linearity reliably \u2192 "
|
||||
(strong "stay on OCaml") ". Runtime one-shot is sufficient.")
|
||||
(li "If linearity bugs keep slipping through \u2192 "
|
||||
@@ -309,7 +309,7 @@
|
||||
"SX is not an interpreted scripting language with a nice spec. "
|
||||
"It's a compiled language whose compiler also runs in the browser.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "JIT in the browser")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "JIT in the browser")
|
||||
(p "The server sends SX (component definitions, page content). "
|
||||
"The client receives it and " (strong "compiles to WASM and executes") ". "
|
||||
"Not interprets. Not dispatches bytecodes. Compiles.")
|
||||
@@ -322,7 +322,7 @@
|
||||
"And content-addressing means compiled artifacts are cacheable by CID \u2014 "
|
||||
"compile once, store forever.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The compilation tiers")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The compilation tiers")
|
||||
|
||||
(~docs/code :src (highlight "Tier 0: .sx source \u2192 tree-walking CEK (correct, slow \u2014 current)\nTier 1: .sx source \u2192 bytecodes \u2192 dispatch loop (correct, fast)\nTier 2: .sx source \u2192 WASM functions \u2192 execute (correct, fastest)\nTier 3: .sx source \u2192 native machine code (ahead-of-time, maximum)" "text"))
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"Tier 3 is AOT \u2014 the entire app precompiled. "
|
||||
"All tiers use the same spec, same platform layer, same platform primitives.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Server-side precompilation")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Server-side precompilation")
|
||||
(p "The server can compile too. Instead of sending SX source for the client to JIT, "
|
||||
"send precompiled WASM:")
|
||||
|
||||
@@ -342,13 +342,13 @@
|
||||
(p "Option B skips parsing and compilation entirely. The client instantiates "
|
||||
"the WASM module and calls it. The server did all the work.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed compilation cache")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Content-addressed compilation cache")
|
||||
(p "Every " (code ".sx") " expression has a CID. Every compiled artifact has a CID. "
|
||||
"The mapping is deterministic \u2014 the compiler is a pure function:")
|
||||
|
||||
(~docs/code :src (highlight "source CID \u2192 compiled WASM CID\nbafyrei... \u2192 bafyrei...\n\nThis mapping is cacheable everywhere:\n\u2022 Browser cache \u2014 first visitor compiles, second visitor gets cached WASM\n\u2022 CDN \u2014 compiled artifacts served at the edge\n\u2022 IPFS \u2014 content-addressed by definition, globally deduplicated\n\u2022 Local disk \u2014 offline apps work from cached compiled components" "text"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Entire apps as machine code")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Entire apps as machine code")
|
||||
(p "The entire application can be ahead-of-time compiled to a WASM binary. "
|
||||
"Component definitions, page layouts, event handlers, signal computations \u2014 "
|
||||
"all compiled to native WASM functions. The \"app\" is a " (code ".wasm") " file. "
|
||||
@@ -357,7 +357,7 @@
|
||||
"user-generated SX, REPL input, " (code "eval") "'d strings. "
|
||||
"And even those get JIT'd on first use and cached by CID.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The architecture")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The architecture")
|
||||
|
||||
(~docs/code :src (highlight "sx-platform.js \u2190 DOM, fetch, timers (the real world)\n \u2191 calls\nsx-compiler.wasm \u2190 the SX compiler (itself compiled to WASM)\n \u2191 compiles\n.sx source \u2190 received from server / cache / inline\n \u2193 emits\nnative WASM functions \u2190 cached by CID, instantiated on demand\n \u2193 executes\nactual DOM mutations via platform primitives" "text"))
|
||||
|
||||
@@ -377,48 +377,48 @@
|
||||
"WASM + the platform layer means compiled SX code has "
|
||||
(strong "zero ambient capabilities") " \u2014 every capability is explicitly granted.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Five defence layers")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Five defence layers")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Enforced by")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What it prevents")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Enforced by")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What it prevents")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "1. WASM sandbox")
|
||||
(td :class "px-3 py-2 text-stone-700" "Browser")
|
||||
(td :class "px-3 py-2 text-stone-600" "Memory isolation, no system calls, no DOM access except via explicit imports. Validated before execution."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "2. Platform capabilities")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "sx-platform.js"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Compiled code can only call functions you register. No fetch? Can't fetch. No localStorage? Can't read storage. The platform is a capability system."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "3. Content-addressed verification")
|
||||
(td :class "px-3 py-2 text-stone-700" "CID determinism")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compiler is deterministic: same source \u2192 same CID. Client can re-compile and verify. Tampered WASM produces wrong CID \u2192 reject."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "4. Per-component attenuation")
|
||||
(td :class "px-3 py-2 text-stone-700" "Platform scoping")
|
||||
(td :class "px-3 py-2 text-stone-600" "Different components get different capability subsets. User-generated content gets a locked-down platform \u2014 can render DOM but can't fetch or listen to events."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "1. WASM sandbox")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Browser")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Memory isolation, no system calls, no DOM access except via explicit imports. Validated before execution."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "2. Platform capabilities")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "sx-platform.js"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compiled code can only call functions you register. No fetch? Can't fetch. No localStorage? Can't read storage. The platform is a capability system."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "3. Content-addressed verification")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CID determinism")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compiler is deterministic: same source \u2192 same CID. Client can re-compile and verify. Tampered WASM produces wrong CID \u2192 reject."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "4. Per-component attenuation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Platform scoping")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Different components get different capability subsets. User-generated content gets a locked-down platform \u2014 can render DOM but can't fetch or listen to events."))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "5. Source-first fallback")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client compiler")
|
||||
(td :class "px-3 py-2 text-stone-600" "Don't trust precompiled WASM? Compile from source locally. The client has the compiler. Precompilation is an optimisation, not a trust requirement.")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "5. Source-first fallback")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client compiler")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Don't trust precompiled WASM? Compile from source locally. The client has the compiler. Precompilation is an optimisation, not a trust requirement.")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed tamper detection")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Content-addressed tamper detection")
|
||||
(p "The server sends both SX source and precompiled WASM CID. The client can verify:")
|
||||
|
||||
(~docs/code :src (highlight ";; Server sends:\nContent-Type: application/wasm\nX-Sx-Source-Cid: bafyrei..source\nX-Sx-Compiled-Cid: bafyrei..compiled\n\n;; Client verifies (optional, configurable):\n1. Hash the WASM binary \u2192 matches X-Sx-Compiled-Cid?\n2. Compile source locally \u2192 produces same compiled CID?\n3. Check manifest of pinned CIDs \u2192 CID is expected?\n\n;; Any mismatch = tampered = reject" "text"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Capability attenuation per component")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Capability attenuation per component")
|
||||
(p "The platform scopes capabilities per evaluator instance. "
|
||||
"App shell gets full access. Third-party or user-generated content gets the minimum:")
|
||||
|
||||
(~docs/code :src (highlight "// Full capabilities for the app shell\nplatform.registerAll(appShellCompiler);\n\n// Restricted for user-generated content\nplatform.registerSubset(userContentCompiler, {\n allow: [\"dom-create-element\", \"dom-set-attr\", \"dom-append\",\n \"dom-create-text-node\", \"dom-set-text\"],\n deny: [\"fetch\", \"localStorage\", \"dom-listen\",\n \"dom-set-inner-html\", \"eval\"]\n});\n\n// The restricted compiler's WASM module literally doesn't\n// have imports for the denied functions. Not just blocked\n// at runtime \u2014 absent from the binary." "javascript"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Component manifests")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Component manifests")
|
||||
(p "The app ships with a manifest of expected CIDs for its core components. "
|
||||
"Like subresource integrity (SRI) but for compiled code:")
|
||||
|
||||
@@ -442,7 +442,7 @@
|
||||
"and to DOM on the client. Compiled WASM doesn't change this. "
|
||||
"It makes the client side faster without affecting what crawlers see.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The rendering pipeline")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The rendering pipeline")
|
||||
|
||||
(~docs/code :src (highlight "Crawler visits:\n GET /page\n \u2192 Server compiles SX (native OCaml)\n \u2192 render-to-html (adapter-html.sx)\n \u2192 Full static HTML with semantic markup\n \u2192 Google indexes it\n\nUser first visit:\n GET /page\n \u2192 Server renders HTML (same as crawler)\n \u2192 Browser displays immediately (no JS needed)\n \u2192 Client loads sx-compiler.wasm + sx-platform.js\n \u2192 Hydrates: attaches event handlers, activates islands\n \u2192 Page is interactive\n\nUser navigates (SPA):\n sx-get /next-page\n \u2192 Server sends SX wire format (aser)\n \u2192 Client compiles + renders via WASM\n \u2192 Morph engine patches the DOM" "text"))
|
||||
|
||||
@@ -453,15 +453,15 @@
|
||||
"makes hydration and SPA navigation faster, but the initial HTML "
|
||||
"is always server-rendered.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What crawlers see")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What crawlers see")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li "Fully rendered HTML \u2014 no \"loading...\" skeleton, no JS-dependent content")
|
||||
(li "Semantic markup \u2014 " (code "<h1>") ", " (code "<nav>") ", " (code "<article>") ", " (code "<a href>")
|
||||
" \u2014 all from the SX component tree")
|
||||
(li "Meta tags, canonical URLs, structured data \u2014 rendered server-side by " (code "shell.sx"))
|
||||
(li "No WASM, no JS required \u2014 the HTML is the page, complete on first byte"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed prerendering")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Content-addressed prerendering")
|
||||
(p "The server can prerender every page to static HTML, hash it, "
|
||||
"and cache it at the CDN edge:")
|
||||
|
||||
@@ -472,23 +472,23 @@
|
||||
"the server-rendered HTML are cached by CID. "
|
||||
"The entire delivery pipeline is content-addressed.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Progressive enhancement")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Progressive enhancement")
|
||||
(p "The page works at every level:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Client capability")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Experience")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Client capability")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Experience")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "No JS (crawler, reader mode)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Full HTML. Links work. Forms submit. Content is complete."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JS, no WASM")
|
||||
(td :class "px-3 py-2 text-stone-600" "Falls back to js_of_ocaml evaluator or interpreted JS. SPA navigation, islands, signals all work."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "No JS (crawler, reader mode)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Full HTML. Links work. Forms submit. Content is complete."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS, no WASM")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Falls back to js_of_ocaml evaluator or interpreted JS. SPA navigation, islands, signals all work."))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "JS + WASM")
|
||||
(td :class "px-3 py-2 text-stone-600" "Full compiled pipeline. JIT compilation, cached WASM functions, near-native rendering speed."))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS + WASM")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Full compiled pipeline. JIT compilation, cached WASM functions, near-native rendering speed."))))))
|
||||
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -502,33 +502,33 @@
|
||||
"or \u2014 when the client already has the components cached \u2014 "
|
||||
"just the data as gzipped bytecode.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Three response tiers")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Three response tiers")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Client cache state")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Server sends")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Client does")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Client cache state")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Server sends")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Client does")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Nothing cached")
|
||||
(td :class "px-3 py-2 text-stone-700" "HTML (fallback)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Parse HTML, morph DOM"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Compiler cached")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX source")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compile, execute, morph DOM"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Components cached")
|
||||
(td :class "px-3 py-2 text-stone-700" "Bytecode (data only)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Call cached WASM function with args"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Nothing cached")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "HTML (fallback)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Parse HTML, morph DOM"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Compiler cached")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX source")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compile, execute, morph DOM"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components cached")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bytecode (data only)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Call cached WASM function with args"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Components cached + gzip")
|
||||
(td :class "px-3 py-2 text-stone-700" "Gzipped bytecode")
|
||||
(td :class "px-3 py-2 text-stone-600" "Decompress, call cached function")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components cached + gzip")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Gzipped bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Decompress, call cached function")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Why bytecode is tiny")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Why bytecode is tiny")
|
||||
(p "The bytecode doesn't repeat what the client already knows. "
|
||||
"Component CID references are 4-byte pointers to already-cached compiled functions. "
|
||||
"Tag names, class strings, attribute keys \u2014 all baked into the compiled component. "
|
||||
@@ -545,7 +545,7 @@
|
||||
"The only thing on the wire is the data that differs between instances. "
|
||||
"Gzip crushes the repetitive framing to almost nothing.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Content negotiation")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Content negotiation")
|
||||
(p "The client advertises what it has via request headers:")
|
||||
|
||||
(~docs/code :src (highlight ";; Client tells server what's cached\nAccept: application/sx-bytecode, text/sx, text/html\nX-Sx-Cached-Components: bafyrei..card, bafyrei..header, bafyrei..nav\n\n;; Server picks the smallest response:\n;; - Client has ~card cached? Send bytecode (data only)\n;; - Client has compiler but not ~card? Send SX source\n;; - Client has nothing? Send HTML" "text"))
|
||||
@@ -555,7 +555,7 @@
|
||||
"Subsequent navigations are gzipped bytecode \u2014 "
|
||||
"just the data, a few hundred bytes, instant render via cached compiled components.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The server becomes a data API")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The server becomes a data API")
|
||||
(p "At the bytecode tier, the server is no longer rendering views. "
|
||||
"It's serving data \u2014 structured arguments for compiled client-side functions. "
|
||||
"The rendering logic is cached on the client as compiled WASM. "
|
||||
@@ -569,36 +569,36 @@
|
||||
|
||||
(~docs/section :title "Impact on Existing Plans" :id "impact"
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Plan")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Impact")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Plan")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Impact")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.rust-wasm-host))" "Rust/WASM Host"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Superseded. OCaml/WASM replaces Rust/WASM. The handle table and platform layer design carry over."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.isolated-evaluator))" "Isolated Evaluator"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Architecture preserved. sx-platform.js and evaluator isolation apply to the OCaml evaluator too. The JS evaluator becomes a fallback."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.foundations))" "Foundations"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Accelerated. OCaml 5 has native effects/continuations/fibers. Steps 4 (Concurrent CEK) and 5 (Linear Effects) map directly."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Subsumed as Tier 1. Bytecodes become an intermediate step on the path to native WASM compilation. The dispatch loop is Tier 1; JIT to WASM functions is Tier 2; AOT is Tier 3."))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" "Self-Hosting Bootstrapper"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Converges. js.sx and py.sx are self-hosting emitters. The self-hosting SX compiler (Phase 5 here) is the logical endpoint."))))))
|
||||
|
||||
|
||||
@@ -607,7 +607,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "SX is the language.") " Not OCaml, not Rust, not Haskell. "
|
||||
"Those are substrates. SX defines the semantics, SX checks the types, "
|
||||
"SX will ultimately compile itself.")
|
||||
@@ -632,7 +632,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After completion:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "SX core compiled from spec via OCaml \u2014 native + WASM from one codebase.")
|
||||
(li "Python web framework calls native " (code "sx_core.so") " for rendering \u2014 "
|
||||
"100\u20131000x faster than interpreted Python.")
|
||||
@@ -646,5 +646,5 @@
|
||||
(li "Self-hosting compiler: SX compiles itself to machine code. OCaml scaffolding removed.")
|
||||
(li "The only interpreter in the system is the CPU."))
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mt-12"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mt-12")
|
||||
"The Mother Language was always SX. We just needed to find the right scaffolding to stand it up."))))
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
(~docs/code :src (highlight ";; Home (nothing selected)\n;;\n;; [ sx ]\n;;\n;; Docs CSSX Reference Protocols Examples\n;; Essays Philosophy Specs Bootstrappers\n;; Testing Isomorphism Plans Reactive Islands\n\n\n;; Section selected (e.g. Plans)\n;;\n;; [ sx ]\n;;\n;; < Plans >\n;;\n;; Status Reader Macros Theorem Prover\n;; Self-Hosting JS Bootstrapper SX-Activity\n;; Predictive Prefetching Content-Addressed\n;; Environment Images Runtime Slicing Typed SX\n;; Fragment Protocol ...\n\n\n;; Page selected (e.g. Typed SX under Plans)\n;;\n;; [ sx ]\n;;\n;; < Plans >\n;;\n;; < Typed SX >\n;;\n;; [ page content here ]" "lisp")))
|
||||
|
||||
(~docs/subsection :title "Rules"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Logo at top, centered.") " Always visible. Click = home. The only fixed element.")
|
||||
(li (strong "Level 1: section list.") " Shown on home page as a wrapped, centered list of links. This is the full menu — no hiding, no hamburger.")
|
||||
(li (strong "When a section is selected:") " Section name replaces the list. Left arrow and right arrow for sibling navigation (previous/next section). The section's children appear as a new list below.")
|
||||
@@ -39,29 +39,29 @@
|
||||
(~docs/section :title "Visual Language" :id "visual"
|
||||
(~docs/subsection :title "Levels"
|
||||
(p "Each level has decreasing visual weight:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Level")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Selected state")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "List state")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Level")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Selected state")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "List state")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Logo")
|
||||
(td :class "px-3 py-2 text-stone-700" "Large, violet, always visible")
|
||||
(td :class "px-3 py-2 text-stone-600" "—"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Section")
|
||||
(td :class "px-3 py-2 text-stone-700" "Medium text, violet-700, arrows")
|
||||
(td :class "px-3 py-2 text-stone-600" "Medium text, stone-600, wrapped inline"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Subsection")
|
||||
(td :class "px-3 py-2 text-stone-700" "Smaller text, violet-600, arrows")
|
||||
(td :class "px-3 py-2 text-stone-600" "Small text, stone-500, wrapped inline"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Level 3+")
|
||||
(td :class "px-3 py-2 text-stone-700" "Same as subsection")
|
||||
(td :class "px-3 py-2 text-stone-600" "Same as subsection"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Logo")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Large, violet, always visible")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "—"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Section")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Medium text, violet-700, arrows")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Medium text, stone-600, wrapped inline"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Subsection")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Smaller text, violet-600, arrows")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Small text, stone-500, wrapped inline"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Level 3+")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Same as subsection")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Same as subsection"))))))
|
||||
|
||||
(~docs/subsection :title "Arrows"
|
||||
(p "Left and right arrows are inline with the selected item name. They navigate to the previous/next sibling in the current list. Keyboard accessible: left/right arrow keys when the row is focused.")
|
||||
@@ -128,49 +128,49 @@
|
||||
|
||||
(~docs/section :title "What Goes Away" :id "removal"
|
||||
(p "Significant deletion:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Component")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Component")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~shared:layout/menu-row-sx")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/layout.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Horizontal bar with colour levels — replaced by breadcrumb rows"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~sx-header-row")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/layouts.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Top menu bar — replaced by logo + breadcrumb"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~sx-sub-row")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/layouts.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Sub-section bar — replaced by second breadcrumb row"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~sx-main-nav")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/layouts.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Horizontal nav list — replaced by ~plans/nav-redesign/nav-list"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~nav-data/section-nav")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/nav-data.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Sub-nav builder — replaced by ~plans/nav-redesign/nav-list"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~shared:layout/nav-link")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/layout.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Complex link with aria-selected + submenu wrapper — replaced by plain a tags"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~shared:layout/mobile-menu-section")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/layout.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Separate mobile menu — new nav is inherently responsive"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "6 layout variants")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/layouts.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "full/oob/mobile × home/section — replaced by one layout with ~plans/nav-redesign/nav"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" ".nav-group CSS")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/shell.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Hover submenu CSS — no submenus to hover")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~shared:layout/menu-row-sx")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/layout.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Horizontal bar with colour levels — replaced by breadcrumb rows"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~sx-header-row")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/layouts.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Top menu bar — replaced by logo + breadcrumb"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~sx-sub-row")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/layouts.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Sub-section bar — replaced by second breadcrumb row"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~sx-main-nav")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/layouts.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Horizontal nav list — replaced by ~plans/nav-redesign/nav-list"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~nav-data/section-nav")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/nav-data.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Sub-nav builder — replaced by ~plans/nav-redesign/nav-list"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~shared:layout/nav-link")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/layout.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Complex link with aria-selected + submenu wrapper — replaced by plain a tags"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~shared:layout/mobile-menu-section")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/layout.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Separate mobile menu — new nav is inherently responsive"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "6 layout variants")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/layouts.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "full/oob/mobile × home/section — replaced by one layout with ~plans/nav-redesign/nav"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") ".nav-group CSS")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/shell.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Hover submenu CSS — no submenus to hover")))))
|
||||
|
||||
(p "The layout variants collapse from 6 (full/oob/mobile × home/section) to 2 (full/oob). No mobile variant needed — the nav is one column, it works everywhere."))
|
||||
|
||||
@@ -194,12 +194,12 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Scope" :id "scope"
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4 mb-4"
|
||||
(p :class "text-amber-900 font-medium" "SX docs only — for now")
|
||||
(p :class "text-amber-800" "This redesign applies to the SX docs app (" (code "sx/") "). The other services (blog, market, events, etc.) keep their current navigation. If the pattern proves out, it can migrate to shared infrastructure and replace the root menu system too."))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-amber-900 font-medium") "SX docs only — for now")
|
||||
(p (~tw :tokens "text-amber-800") "This redesign applies to the SX docs app (" (code "sx/") "). The other services (blog, market, events, etc.) keep their current navigation. If the pattern proves out, it can migrate to shared infrastructure and replace the root menu system too."))
|
||||
|
||||
(p "What changes:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "sx/sx/nav-data.sx") " — add " (code "sx-nav-tree") " (wraps existing lists, no content change)")
|
||||
(li (code "sx/sx/layouts.sx") " — rewrite: delete 12 components, add 5 (logo, breadcrumb, list, nav, 2 layouts)")
|
||||
(li (code "sx/sxc/pages/docs.sx") " — simplify every defpage's " (code ":layout") " declaration")
|
||||
@@ -207,7 +207,7 @@
|
||||
(li (code "shared/sx/templates/layout.sx") " — no changes needed (shared components untouched, only SX-docs-specific ones change)"))
|
||||
|
||||
(p "What doesn't change:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Page content — all " (code "~plan-*-content") ", " (code "~doc-*-content") ", etc. are untouched")
|
||||
(li "Nav data — all " (code "*-nav-items") " lists are unchanged, just composed into a tree")
|
||||
(li "Routing — all defpage paths stay the same")
|
||||
@@ -220,25 +220,25 @@
|
||||
(~docs/section :title "Implementation" :id "implementation"
|
||||
|
||||
(~docs/subsection :title "Phase 1: Nav tree + resolution"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Add " (code "sx-nav-tree") " to " (code "nav-data.sx") " — compose existing " (code "*-nav-items") " lists into a tree")
|
||||
(li "Write " (code "resolve-nav-path") " — pure function, tree walk, returns trail + children")
|
||||
(li "Test: given a path, produces the correct breadcrumb trail and child list")))
|
||||
|
||||
(~docs/subsection :title "Phase 2: New components"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Write " (code "~plans/nav-redesign/logo") ", " (code "~plans/nav-redesign/nav-breadcrumb") ", " (code "~plans/nav-redesign/nav-list") ", " (code "~plans/nav-redesign/nav"))
|
||||
(li "Write " (code "~plans/nav-redesign/docs-layout-full") " and " (code "~plans/nav-redesign/docs-layout-oob"))
|
||||
(li "Register new layout in " (code "layouts.py"))
|
||||
(li "Test with one defpage first — verify morph transitions work")))
|
||||
|
||||
(~docs/subsection :title "Phase 3: Migrate all defpages"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Update every defpage in " (code "docs.sx") " to use " (code ":layout (:sx-docs :path ...)"))
|
||||
(li "This is mechanical — replace the 5-param layout block with 1-param")))
|
||||
|
||||
(~docs/subsection :title "Phase 4: Delete old components"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Delete " (code "~sx-main-nav") ", " (code "~sx-header-row") ", " (code "~sx-sub-row") ", " (code "~nav-data/section-nav"))
|
||||
(li "Delete all 12 SX layout variants from " (code "layouts.sx"))
|
||||
(li "Delete old layout registrations from " (code "layouts.py"))
|
||||
|
||||
@@ -11,37 +11,37 @@
|
||||
(p "But this goes beyond just hover-to-prefetch. The full spectrum includes: bundling linked routes' components with the initial page load, batch-prefetching after idle, predicting mouse trajectory toward links, and even splitting the component/data fetch so that " (code ":data") " pages can prefetch their components and only fetch data on click. Each strategy trades bandwidth for latency, and pages should be able to declare which tradeoff they want."))
|
||||
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What exists")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Where")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What exists")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Where")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Page registry")
|
||||
(td :class "px-3 py-2 text-stone-700" "Each page carries " (code ":deps (\"~card\" \"~essay-foo\" ...)"))
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "helpers.py → <script type=\"text/sx-pages\">"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Dep check")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "has-all-deps?") " gates client routing")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "orchestration.sx:546-559"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component bundle")
|
||||
(td :class "px-3 py-2 text-stone-700" "Per-page inline " (code "<script type=\"text/sx\" data-components>"))
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "helpers.py:715, jinja_bridge.py"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Incremental defs")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "components_for_request()") " sends only missing defs in SX responses")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "helpers.py:459-509"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Preload cache")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "sx-preload") " prefetches full responses on hover/mousedown")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "orchestration.sx:686-708"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Route matching")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "find-matching-route") " matches pathname to page entry")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "router.sx"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Page registry")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Each page carries " (code ":deps (\"~card\" \"~essay-foo\" ...)"))
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "helpers.py → <script type=\"text/sx-pages\">"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Dep check")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "has-all-deps?") " gates client routing")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "orchestration.sx:546-559"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component bundle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Per-page inline " (code "<script type=\"text/sx\" data-components>"))
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "helpers.py:715, jinja_bridge.py"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Incremental defs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "components_for_request()") " sends only missing defs in SX responses")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "helpers.py:459-509"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Preload cache")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "sx-preload") " prefetches full responses on hover/mousedown")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "orchestration.sx:686-708"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Route matching")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "find-matching-route") " matches pathname to page entry")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "router.sx"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Prefetch strategies
|
||||
@@ -50,49 +50,49 @@
|
||||
(~docs/section :title "Prefetch Strategies" :id "strategies"
|
||||
(p "Prefetching is a spectrum from conservative to aggressive. The system should support all of these, configured declaratively per link or per page via " (code "defpage") " metadata and " (code "sx-prefetch") " attributes.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Strategy")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Trigger")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What prefetches")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Latency on click")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Strategy")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Trigger")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What prefetches")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Latency on click")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Eager bundle")
|
||||
(td :class "px-3 py-2 text-stone-700" "Initial page load")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for linked routes included in " (code "<script data-components>"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Zero — already in memory"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Idle timer")
|
||||
(td :class "px-3 py-2 text-stone-700" "After page settles (requestIdleCallback or setTimeout)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for visible nav links, batched in one request")
|
||||
(td :class "px-3 py-2 text-stone-600" "Zero if idle fetch completed"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Viewport")
|
||||
(td :class "px-3 py-2 text-stone-700" "Link scrolls into view (IntersectionObserver)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for that link's route")
|
||||
(td :class "px-3 py-2 text-stone-600" "Zero if user scrolled before clicking"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Mouse approach")
|
||||
(td :class "px-3 py-2 text-stone-700" "Cursor moving toward link (trajectory prediction)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for predicted target")
|
||||
(td :class "px-3 py-2 text-stone-600" "Near-zero — fetch starts ~200ms before hover"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Hover")
|
||||
(td :class "px-3 py-2 text-stone-700" "mouseover (150ms debounce)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for hovered link's route")
|
||||
(td :class "px-3 py-2 text-stone-600" "Low — typical hover-to-click is 300-500ms"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Mousedown")
|
||||
(td :class "px-3 py-2 text-stone-700" "mousedown (0ms debounce)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for clicked link's route")
|
||||
(td :class "px-3 py-2 text-stone-600" "~80ms — mousedown-to-click gap"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Components + data")
|
||||
(td :class "px-3 py-2 text-stone-700" "Any of the above")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components " (em "and") " page data for " (code ":data") " pages")
|
||||
(td :class "px-3 py-2 text-stone-600" "Zero for components; data fetch may still be in flight")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Eager bundle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Initial page load")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for linked routes included in " (code "<script data-components>"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Zero — already in memory"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Idle timer")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "After page settles (requestIdleCallback or setTimeout)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for visible nav links, batched in one request")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Zero if idle fetch completed"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Viewport")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Link scrolls into view (IntersectionObserver)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for that link's route")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Zero if user scrolled before clicking"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Mouse approach")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Cursor moving toward link (trajectory prediction)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for predicted target")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Near-zero — fetch starts ~200ms before hover"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Hover")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "mouseover (150ms debounce)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for hovered link's route")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Low — typical hover-to-click is 300-500ms"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Mousedown")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "mousedown (0ms debounce)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for clicked link's route")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~80ms — mousedown-to-click gap"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Components + data")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Any of the above")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components " (em "and") " page data for " (code ":data") " pages")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Zero for components; data fetch may still be in flight")))))
|
||||
|
||||
(~docs/subsection :title "Eager Bundle"
|
||||
(p "The server already computes per-page component bundles. For key navigation paths — the main nav bar, section nav — the server can include " (em "linked routes' components") " in the initial bundle, not just the current page's.")
|
||||
@@ -112,7 +112,7 @@
|
||||
(~docs/subsection :title "Components + Data (Hybrid Prefetch)"
|
||||
(p "The most interesting strategy. For pages with " (code ":data") " dependencies, current behavior is full server fallback. But the page's " (em "components") " are still pure and prefetchable. If we prefetch components ahead of time, the click only needs to fetch " (em "data") " — a much smaller, faster response.")
|
||||
(p "This creates a new rendering path:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Prefetch: hover/idle/viewport triggers " (code "prefetch-components") " for the target page")
|
||||
(li "Click: client has components, but page has " (code ":data") " — fetch data from server")
|
||||
(li "Server returns " (em "only data") " (JSON or SX bindings), not the full rendered page")
|
||||
@@ -143,34 +143,34 @@
|
||||
(~docs/subsection :title "Phase 2: Client Prefetch Logic (SX spec)"
|
||||
(p "New functions in " (code "orchestration.sx") " (or a new " (code "prefetch.sx") " if scope warrants):")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. compute-missing-deps")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. compute-missing-deps")
|
||||
(p "Given a pathname, find the page, return dep names not in " (code "loaded-component-names") ". Returns nil if page not found or has data (can't client-route anyway).")
|
||||
(~docs/code :src (highlight "(define compute-missing-deps\n (fn (pathname)\n (let ((match (find-matching-route pathname _page-routes)))\n (when (and match (not (get match \"has-data\")))\n (let ((deps (or (get match \"deps\") (list)))\n (loaded (loaded-component-names)))\n (filter (fn (d) (not (contains? loaded d))) deps))))))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. prefetch-components")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. prefetch-components")
|
||||
(p "Fetch component definitions from the server for a list of names. Deduplicates in-flight requests. On success, parses and registers the returned definitions into the component env.")
|
||||
(~docs/code :src (highlight "(define _prefetch-pending (dict))\n\n(define prefetch-components\n (fn (names)\n (let ((key (join \",\" (sort names))))\n (when (not (get _prefetch-pending key))\n (set! _prefetch-pending\n (merge _prefetch-pending {key true}))\n (fetch-components-from-server names\n (fn (sx-text)\n (sx-process-component-text sx-text)\n (dict-remove! _prefetch-pending key)))))))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. prefetch-route-deps")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. prefetch-route-deps")
|
||||
(p "High-level composition: compute missing deps for a route, fetch if any.")
|
||||
(~docs/code :src (highlight "(define prefetch-route-deps\n (fn (pathname)\n (let ((missing (compute-missing-deps pathname)))\n (when (and missing (not (empty? missing)))\n (log-info (str \"sx:prefetch \"\n (len missing) \" components for \" pathname))\n (prefetch-components missing)))))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Trigger: link hover")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Trigger: link hover")
|
||||
(p "On mouseover of a boosted link, prefetch its route's missing components. Debounced 150ms to avoid fetching on quick mouse-throughs.")
|
||||
(~docs/code :src (highlight "(define bind-prefetch-on-hover\n (fn (link)\n (let ((timer nil))\n (dom-add-listener link \"mouseover\"\n (fn (e)\n (clear-timeout timer)\n (set! timer (set-timeout\n (fn () (prefetch-route-deps\n (url-pathname (dom-get-attr link \"href\"))))\n 150))))\n (dom-add-listener link \"mouseout\"\n (fn (e) (clear-timeout timer))))))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "5. Trigger: viewport intersection (opt-in)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "5. Trigger: viewport intersection (opt-in)")
|
||||
(p "More aggressive strategy: when a link scrolls into view, prefetch its route's deps. Opt-in via " (code "sx-prefetch=\"visible\"") " attribute.")
|
||||
(~docs/code :src (highlight "(define bind-prefetch-on-visible\n (fn (link)\n (observe-intersection link\n (fn () (prefetch-route-deps\n (url-pathname (dom-get-attr link \"href\"))))\n true 0)))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "6. Integration into process-elements")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "6. Integration into process-elements")
|
||||
(p "During the existing hydration pass, for each boosted link:")
|
||||
(~docs/code :src (highlight ";; In process-elements, after binding boost behavior:\n(when (and (should-boost-link? link)\n (dom-get-attr link \"href\"))\n (bind-prefetch-on-hover link))\n\n;; Explicit viewport prefetch:\n(when (dom-has-attr? link \"sx-prefetch\")\n (bind-prefetch-on-visible link))" "lisp")))))
|
||||
|
||||
@@ -196,40 +196,40 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "File Changes" :id "file-changes"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Change")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Change")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "New " (code "sx_components_endpoint()") " route handler")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/factory.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Register " (code "/sx/components") " route on all SX apps")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/orchestration.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Prefetch functions: compute-missing-deps, prefetch-components, prefetch-route-deps, bind-prefetch-on-hover, bind-prefetch-on-visible")
|
||||
(td :class "px-3 py-2 text-stone-600" "2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boundary.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Declare " (code "fetch-components-from-server") ", " (code "sx-process-component-text"))
|
||||
(td :class "px-3 py-2 text-stone-600" "3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/bootstrap_js.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Emit new spec functions, boundary adapter stubs")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/helpers.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "New " (code "sx_components_endpoint()") " route handler")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/factory.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Register " (code "/sx/components") " route on all SX apps")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/orchestration.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Prefetch functions: compute-missing-deps, prefetch-components, prefetch-route-deps, bind-prefetch-on-hover, bind-prefetch-on-visible")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boundary.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Declare " (code "fetch-components-from-server") ", " (code "sx-process-component-text"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/bootstrap_js.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Emit new spec functions, boundary adapter stubs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Non-goals & rollout
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Non-Goals (This Phase)" :id "non-goals"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Analytics-driven prediction") " — no ML models or click-frequency heuristics. Trajectory prediction uses geometry, not statistics.")
|
||||
(li (strong "Cross-service prefetch") " — components are per-service. A link to a different service domain is always a server navigation.")
|
||||
(li (strong "Service worker caching") " — could layer on later, but basic fetch + in-memory registration is sufficient.")
|
||||
@@ -237,7 +237,7 @@
|
||||
|
||||
(~docs/section :title "Rollout" :id "rollout"
|
||||
(p "Incremental, each step independently valuable:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Component endpoint") " — purely additive. Refactor " (code "components_for_request()") " to accept explicit " (code "?names=") " param.")
|
||||
(li (strong "Core spec functions") " — " (code "compute-missing-deps") ", " (code "prefetch-components") ", " (code "prefetch-route-deps") " in orchestration.sx. Testable in isolation.")
|
||||
(li (strong "Hover prefetch") " — wire " (code "bind-prefetch-on-hover") " into " (code "process-elements") ". All boosted links get it automatically. Console logs show activity.")
|
||||
@@ -248,10 +248,10 @@
|
||||
|
||||
(~docs/section :title "Relationship to Isomorphic Roadmap" :id "relationship"
|
||||
(p "This plan sits between Phase 3 (client-side routing) and Phase 4 (client async & IO bridge) of the "
|
||||
(a :href "/sx/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "isomorphic architecture roadmap")
|
||||
(a :href "/sx/(etc.(plan.isomorphic-architecture))" (~tw :tokens "text-violet-700 underline") "isomorphic architecture roadmap")
|
||||
". It extends Phase 3 by making more navigations go client-side without needing any IO bridge — purely by ensuring component definitions are available before they're needed.")
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 3 (client-side routing with deps checking). No dependency on Phase 4.")))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Depends on: ") "Phase 3 (client-side routing with deps checking). No dependency on Phase 4.")))))
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Plan Status Overview
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
(~docs/code :src (highlight "#'my-function → (quote my-function)" "lisp")))
|
||||
|
||||
(~docs/subsection :title "Extensible dispatch: #name"
|
||||
(p "User-defined reader macros via " (code "#name expr") ". The parser reads an identifier after " (code "#") ", looks up a handler in the reader macro registry, and calls it with the next parsed expression. See the " (a :href "/sx/(etc.(plan.reader-macro-demo))" :class "text-violet-600 hover:underline" "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB.")))
|
||||
(p "User-defined reader macros via " (code "#name expr") ". The parser reads an identifier after " (code "#") ", looks up a handler in the reader macro registry, and calls it with the next parsed expression. See the " (a :href "/sx/(etc.(plan.reader-macro-demo))" (~tw :tokens "text-violet-600 hover:underline") "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB.")))
|
||||
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -64,24 +64,24 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Files" :id "files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Change")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Change")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/parser.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "# dispatch in read-expr, read-raw-string helper, grammar comment"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/parser.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "# dispatch in _parse_expr(), _read_raw_string()"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/sx_ref.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Rebootstrap"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/static/scripts/sx-ref.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "Rebootstrap"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/parser.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "# dispatch in read-expr, read-raw-string helper, grammar comment"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/parser.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "# dispatch in _parse_expr(), _read_raw_string()"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/sx_ref.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Rebootstrap"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/static/scripts/sx-ref.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Rebootstrap"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Verification
|
||||
@@ -90,7 +90,7 @@
|
||||
(~docs/section :title "Verification" :id "verification"
|
||||
|
||||
(~docs/subsection :title "Parse tests"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "#;(ignored) 42 → 42")
|
||||
(li "(list 1 #;2 3) → (list 1 3)")
|
||||
(li "#|hello \"world\" \\n| → string: hello \"world\" \\n (literal, no escaping)")
|
||||
@@ -100,7 +100,7 @@
|
||||
(li "#x unknown → error")))
|
||||
|
||||
(~docs/subsection :title "Regression"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "All existing parser tests pass after changes")
|
||||
(li "Rebootstrapped JS and Python pass their test suites")
|
||||
(li "JS parity: SX.parse('#|hello|') returns [\"hello\"]"))))))
|
||||
|
||||
@@ -8,29 +8,29 @@
|
||||
(~docs/section :title "The Problem" :id "problem"
|
||||
(p "sx-browser.js is the full SX client runtime — evaluator, parser, renderer, engine, morph, signals, routing, orchestration, boot. Every page loads all of it.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Raw")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Gzipped")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Min+Gz")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Raw")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Gzipped")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Min+Gz")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx-browser.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "354KB")
|
||||
(td :class "px-3 py-2 text-stone-700" "75KB")
|
||||
(td :class "px-3 py-2 text-stone-700" "44KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx-ref.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "244KB")
|
||||
(td :class "px-3 py-2 text-stone-700" "49KB")
|
||||
(td :class "px-3 py-2 text-stone-700" "29KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "React + ReactDOM")
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-700" "~5KB + ~40KB")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx-browser.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "354KB")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "75KB")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "44KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx-ref.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "244KB")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "49KB")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "29KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "React + ReactDOM")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~5KB + ~40KB")))))
|
||||
|
||||
(p "Most pages are L0 (pure hypermedia — server renders, client morphs). They don't need the parser, the evaluator, the full primitive set, signals, or client-side routing. They need morph + swap + trigger dispatch. That's a fraction of the runtime.")
|
||||
(p "The runtime should be sliceable: each page declares what level it operates at, and the bootstrapper emits only the code that level requires."))
|
||||
@@ -40,55 +40,55 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Tiers" :id "tiers"
|
||||
(p "Four tiers, matching the " (a :href "/sx/(geography.(reactive.(reactive-design)))" :class "text-violet-700 underline" "reactive islands") " levels:")
|
||||
(p "Four tiers, matching the " (a :href "/sx/(geography.(reactive.(reactive-design)))" (~tw :tokens "text-violet-700 underline") "reactive islands") " levels:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Tier")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Modules")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Target (min+gz)")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Tier")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Modules")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Target (min+gz)")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L0 Hypermedia")
|
||||
(td :class "px-3 py-2 text-stone-700" "Morph, swap, trigger dispatch, history")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "engine, boot (partial)")
|
||||
(td :class "px-3 py-2 text-stone-700" "~5KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L1 DOM Ops")
|
||||
(td :class "px-3 py-2 text-stone-700" "L0 + toggle!, set-attr!, on-event, class-list ops")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "+ DOM adapter (partial)")
|
||||
(td :class "px-3 py-2 text-stone-700" "~8KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L2 Islands")
|
||||
(td :class "px-3 py-2 text-stone-700" "L1 + signals, computed, effect, defisland hydration")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "+ signals, DOM adapter (full)")
|
||||
(td :class "px-3 py-2 text-stone-700" "~15KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L3 Full Eval")
|
||||
(td :class "px-3 py-2 text-stone-700" "L2 + parser, evaluator, all primitives, client routing, component resolution")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "everything")
|
||||
(td :class "px-3 py-2 text-stone-700" "~44KB (current)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L0 Hypermedia")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Morph, swap, trigger dispatch, history")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "engine, boot (partial)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~5KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L1 DOM Ops")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "L0 + toggle!, set-attr!, on-event, class-list ops")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "+ DOM adapter (partial)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~8KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L2 Islands")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "L1 + signals, computed, effect, defisland hydration")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "+ signals, DOM adapter (full)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~15KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L3 Full Eval")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "L2 + parser, evaluator, all primitives, client routing, component resolution")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "everything")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~44KB (current)")))))
|
||||
|
||||
(p "90% of typical pages are L0. A blog post, a product listing, a checkout form — server renders, client morphs on navigation. The full evaluator only loads for pages that do client-side rendering or have reactive islands.")
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4 mt-4"
|
||||
(p :class "text-amber-900 font-medium" "Progressive loading")
|
||||
(p :class "text-amber-800" "A user navigating an L0 page downloads ~5KB. If they navigate to an L2 page (reactive island), the delta (~10KB) loads on demand. The runtime grows with need, never speculatively.")))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-amber-900 font-medium") "Progressive loading")
|
||||
(p (~tw :tokens "text-amber-800") "A user navigating an L0 page downloads ~5KB. If they navigate to an L2 page (reactive island), the delta (~10KB) loads on demand. The runtime grows with need, never speculatively.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; The slicer is SX
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "The Slicer is SX" :id "slicer-is-sx"
|
||||
(p "Per the " (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" :class "text-violet-700 underline" "self-hosting principle") ", the slicer is not a build tool — it's a spec module. " (code "slice.sx") " analyzes the spec's own dependency graph and determines which defines belong to which tier.")
|
||||
(p "Per the " (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" (~tw :tokens "text-violet-700 underline") "self-hosting principle") ", the slicer is not a build tool — it's a spec module. " (code "slice.sx") " analyzes the spec's own dependency graph and determines which defines belong to which tier.")
|
||||
(p (code "js.sx") " (the self-hosting SX-to-JavaScript translator) already compiles the full spec. Slicing is a filter: " (code "js.sx") " translates only the defines that " (code "slice.sx") " selects for a given tier.")
|
||||
|
||||
(~docs/code :src (highlight ";; slice.sx — determine which defines each tier needs\n;;\n;; Input: the full list of defines from all spec files\n;; Output: a filtered list for the requested tier\n\n(define tier-deps\n ;; Which spec modules each tier requires\n {:L0 (list \"engine\" \"boot-partial\")\n :L1 (list \"engine\" \"boot-partial\" \"dom-partial\")\n :L2 (list \"engine\" \"boot-partial\" \"dom-partial\"\n \"signals\" \"dom-island\")\n :L3 (list \"eval\" \"render\" \"parser\"\n \"engine\" \"orchestration\" \"boot\"\n \"dom\" \"signals\" \"router\")})\n\n(define slice-defines\n (fn (tier all-defines)\n ;; 1. Get the module list for this tier\n ;; 2. Walk each define's dependency references\n ;; 3. Include a define only if ALL its deps are\n ;; satisfiable within the tier's module set\n ;; 4. Return the filtered define list\n (let ((modules (get tier-deps tier)))\n (filter\n (fn (d) (tier-satisfies? modules (define-deps d)))\n all-defines))))" "lisp"))
|
||||
|
||||
(p "The pipeline becomes:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (code "slice.sx") " analyzes the spec and produces a define list per tier")
|
||||
(li (code "js.sx") " translates each define list to JavaScript (same translator, different input)")
|
||||
(li "The bootstrapper wraps each tier's output with its platform interface (the hand-written JS glue)")
|
||||
@@ -107,29 +107,29 @@
|
||||
|
||||
(p "From these per-define deps, we build the full dependency graph and compute transitive closures. A tier's define set is the transitive closure of its entry points:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Tier")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Entry points")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Pulls in")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Tier")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Entry points")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Pulls in")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L0")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "morph-node, process-swap, dispatch-trigger, push-url")
|
||||
(td :class "px-3 py-2 text-stone-700" "morph-attrs, morph-children, create-element, extract-swap-config — the morph/swap subgraph"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L1")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "L0 + toggle-class!, set-attr!, add-event-listener!")
|
||||
(td :class "px-3 py-2 text-stone-700" "DOM manipulation helpers — small subgraph"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L2")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "L1 + signal, deref, reset!, computed, effect, render-dom-island")
|
||||
(td :class "px-3 py-2 text-stone-700" "Signal runtime + reactive DOM adapter"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L3")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "L2 + eval-expr, sx-parse, render-to-dom, resolve-component-by-cid")
|
||||
(td :class "px-3 py-2 text-stone-700" "Everything — full evaluator, parser, all ~80 primitives"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L0")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "morph-node, process-swap, dispatch-trigger, push-url")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "morph-attrs, morph-children, create-element, extract-swap-config — the morph/swap subgraph"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L1")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "L0 + toggle-class!, set-attr!, add-event-listener!")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DOM manipulation helpers — small subgraph"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L2")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "L1 + signal, deref, reset!, computed, effect, render-dom-island")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Signal runtime + reactive DOM adapter"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L3")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "L2 + eval-expr, sx-parse, render-to-dom, resolve-component-by-cid")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Everything — full evaluator, parser, all ~80 primitives"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Platform interface slicing
|
||||
@@ -161,7 +161,7 @@
|
||||
|
||||
(~docs/subsection :title "Cache Behavior"
|
||||
(p "Each tier file is content-hashed (like the current " (code "sx_js_hash") " mechanism). Cache-forever semantics. A user who visits any L0 page caches the L0 runtime permanently. If they later visit an L2 page, only the ~10KB delta downloads.")
|
||||
(p "Combined with " (a :href "/sx/(etc.(plan.environment-images))" :class "text-violet-700 underline" "environment images") ": the image CID includes the tier. An L0 image is smaller than an L3 image — it contains fewer primitives, no parser state, no evaluator. The standalone HTML bundle for an L0 page is tiny.")))
|
||||
(p "Combined with " (a :href "/sx/(etc.(plan.environment-images))" (~tw :tokens "text-violet-700 underline") "environment images") ": the image CID includes the tier. An L0 image is smaller than an L3 image — it contains fewer primitives, no parser state, no evaluator. The standalone HTML bundle for an L0 page is tiny.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Automatic tier detection
|
||||
@@ -181,49 +181,49 @@
|
||||
(~docs/section :title "What L0 Actually Needs" :id "l0-detail"
|
||||
(p "L0 is the critical tier — it's what most pages load. Every byte matters. Let's be precise about what it contains:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Function")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Source")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Function")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Source")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "morph-node")
|
||||
(td :class "px-3 py-2 text-stone-700" "DOM diffing — update existing DOM from new HTML")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "morph-attrs")
|
||||
(td :class "px-3 py-2 text-stone-700" "Attribute diffing on a single element")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "morph-children")
|
||||
(td :class "px-3 py-2 text-stone-700" "Child node reconciliation")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "process-swap")
|
||||
(td :class "px-3 py-2 text-stone-700" "Apply sx-swap directive (innerHTML, outerHTML, etc)")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "dispatch-trigger")
|
||||
(td :class "px-3 py-2 text-stone-700" "Process sx-trigger attributes (click, submit, load, etc)")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "sx-fetch")
|
||||
(td :class "px-3 py-2 text-stone-700" "Make sx-get/sx-post requests, process response")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "orchestration.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "push-url / replace-url")
|
||||
(td :class "px-3 py-2 text-stone-700" "History management for sx-push-url")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "boot-triggers")
|
||||
(td :class "px-3 py-2 text-stone-700" "Scan DOM for sx-* attributes, wire up event listeners")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "boot.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "resolve-suspense")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fill in streamed suspense slots")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "boot.sx")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "morph-node")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DOM diffing — update existing DOM from new HTML")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "morph-attrs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Attribute diffing on a single element")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "morph-children")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Child node reconciliation")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "process-swap")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Apply sx-swap directive (innerHTML, outerHTML, etc)")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "dispatch-trigger")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Process sx-trigger attributes (click, submit, load, etc)")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "sx-fetch")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Make sx-get/sx-post requests, process response")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "orchestration.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "push-url / replace-url")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "History management for sx-push-url")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "boot-triggers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Scan DOM for sx-* attributes, wire up event listeners")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "boot.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "resolve-suspense")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fill in streamed suspense slots")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "boot.sx")))))
|
||||
|
||||
(p "That's roughly 20-30 defines from engine.sx + orchestration.sx + boot.sx, plus their transitive deps. No parser, no evaluator, no primitives beyond what those defines call internally. The platform JS is just: fetch wrapper, DOM helpers (createElement, setAttribute, morphing), history API, and event delegation.")
|
||||
|
||||
@@ -240,30 +240,30 @@
|
||||
|
||||
(p "The " (code "--delta") " flag emits only the defines not present in the previous tier. The delta file calls " (code "Sx.extend()") " to register its additions into the already-loaded runtime.")
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "Self-hosting all the way")
|
||||
(p :class "text-violet-800" (code "slice.sx") " is spec. " (code "js.sx") " is spec. The bootstrapper script (" (code "bootstrap_js.py") ") is the thin host-specific glue that reads slice output, calls js.sx via the evaluator, and wraps with platform JS. The slicer could itself be bootstrapped to JavaScript and run in a browser build tool — but that's a future concern.")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "Self-hosting all the way")
|
||||
(p (~tw :tokens "text-violet-800") (code "slice.sx") " is spec. " (code "js.sx") " is spec. The bootstrapper script (" (code "bootstrap_js.py") ") is the thin host-specific glue that reads slice output, calls js.sx via the evaluator, and wraps with platform JS. The slicer could itself be bootstrapped to JavaScript and run in a browser build tool — but that's a future concern.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Spec modules
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Spec Modules" :id "spec-modules"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Module")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Functions")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Depends on")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Module")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Functions")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Depends on")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "slice.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "define-refs") ", " (code "define-dep-graph") ", " (code "slice-defines") ", " (code "tier-entry-points") ", " (code "page-tier") ", " (code "component-tier"))
|
||||
(td :class "px-3 py-2 text-stone-600" "deps.sx (component analysis), eval.sx (AST walking)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "js.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "js-translate-file") " — already exists, unchanged")
|
||||
(td :class "px-3 py-2 text-stone-600" "eval.sx (runs on evaluator)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "slice.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "define-refs") ", " (code "define-dep-graph") ", " (code "slice-defines") ", " (code "tier-entry-points") ", " (code "page-tier") ", " (code "component-tier"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "deps.sx (component analysis), eval.sx (AST walking)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "js.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "js-translate-file") " — already exists, unchanged")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "eval.sx (runs on evaluator)")))))
|
||||
|
||||
(p "One new spec file (" (code "slice.sx") "), one existing translator (" (code "js.sx") "), one modified host script (" (code "bootstrap_js.py") " gains " (code "--tier") " and " (code "--delta") " flags)."))
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Relationships" :id "relationships"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (a :href "/sx/(etc.(plan.environment-images))" :class "text-violet-700 underline" "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.")
|
||||
(li (a :href "/sx/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.")
|
||||
(li (a :href "/sx/(geography.(reactive.(reactive-design)))" :class "text-violet-700 underline" "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.")
|
||||
(li (a :href "/sx/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it."))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (a :href "/sx/(etc.(plan.environment-images))" (~tw :tokens "text-violet-700 underline") "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.")
|
||||
(li (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "text-violet-700 underline") "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.")
|
||||
(li (a :href "/sx/(geography.(reactive.(reactive-design)))" (~tw :tokens "text-violet-700 underline") "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.")
|
||||
(li (a :href "/sx/(etc.(plan.isomorphic-architecture))" (~tw :tokens "text-violet-700 underline") "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it."))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") (code "js.sx") " (complete), " (code "deps.sx") " (complete), " (code "bootstrap_js.py") " adapter selection (exists). " (strong "New: ") (code "slice.sx") " spec module.")))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Depends on: ") (code "js.sx") " (complete), " (code "deps.sx") " (complete), " (code "bootstrap_js.py") " adapter selection (exists). " (strong "New: ") (code "slice.sx") " spec module.")))))
|
||||
|
||||
@@ -10,34 +10,34 @@
|
||||
(p "The Rust bootstrapper (" (code "bootstrap_rs.py") ") reads all 20 " (code ".sx") " spec files and emits a complete Rust crate — " (strong "9,781 lines") " of Rust that compiles with zero errors. The test suite has " (strong "92 tests passing") " across parser, evaluator, primitives, and rendering.")
|
||||
(p "This is distinct from the " (a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM") " plan. That plan designs a custom bytecode format and VM. This plan bootstraps the " (strong "same tree-walking evaluator") " directly to Rust/WASM from the same " (code ".sx") " specs — no new bytecode, no new VM, just another host.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What exists today")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Artifact")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Lines")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What exists today")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Artifact")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Lines")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "bootstrap_rs.py"))
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-600" "Rust bootstrapper, reads all 20 .sx spec files"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "sx_ref.rs"))
|
||||
(td :class "px-3 py-2 text-stone-700" "9,781")
|
||||
(td :class "px-3 py-2 text-stone-600" "Generated Rust — compiles clean, 0 errors"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "platform.rs"))
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-600" "Rust platform interface (type constructors, env ops)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "test_parser.rs"))
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-600" "92 tests passing (parser, eval, primitives, render)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "bootstrap_rs.py"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Rust bootstrapper, reads all 20 .sx spec files"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "sx_ref.rs"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "9,781")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Generated Rust — compiles clean, 0 errors"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "platform.rs"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Rust platform interface (type constructors, env ops)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "test_parser.rs"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "92 tests passing (parser, eval, primitives, render)"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitives")
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-600" "75 real implementations, 154 stubs"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitives")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "75 real implementations, 154 stubs"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Architecture
|
||||
@@ -46,21 +46,21 @@
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(p "The key architectural insight: " (strong "factor the browser primitives into a shared platform layer") " that both the JS evaluator and the WASM module consume.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Shared platform layer")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Shared platform layer")
|
||||
(p (code "platform_js.py") " already contains all DOM, browser, fetch, timer, and storage implementations — bootstrapped from " (code "boundary.sx") ". These are pure JavaScript functions that call browser APIs. They don't depend on the evaluator.")
|
||||
(p "Extract them into a standalone " (code "sx-platform.js") " module. Both " (code "sx-browser.js") " (the current JS evaluator) and the new " (code "sx-wasm-shim.js") " import from the same platform module:")
|
||||
(~docs/code :src (highlight " ┌─────────────────┐\n │ sx-platform.js │ ← DOM, fetch, timers, storage\n └────────┬────────┘\n │\n ┌──────────────┼──────────────┐\n │ │\n ┌─────────┴─────────┐ ┌────────┴────────┐\n │ sx-browser.js │ │ sx-wasm-shim.js │\n │ (JS tree-walker) │ │ (WASM instance │\n │ │ │ + handle table) │\n └────────────────────┘ └─────────────────┘" "text"))
|
||||
(p "One codebase for all browser primitives. Bug fixes apply to both targets. The evaluator is the only thing that changes — JS tree-walker vs Rust/WASM tree-walker.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Opaque handle table")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Opaque handle table")
|
||||
(p "Rust/WASM can't hold DOM node references directly. Instead, Rust values use " (code "Value::Handle(u32)") " — an opaque integer that indexes into a JavaScript-side handle table:")
|
||||
(~docs/code :src (highlight "// JS side (in sx-wasm-shim.js)\nconst handles = []; // handle_id → DOM node\n\nfunction allocHandle(node) {\n const id = handles.length;\n handles.push(node);\n return id;\n}\n\nfunction getHandle(id) { return handles[id]; }\nfunction freeHandle(id) { handles[id] = null; }" "javascript"))
|
||||
(~docs/code :src (highlight "// Rust side\n#[derive(Clone, Debug)]\nenum Value {\n Nil,\n Bool(bool),\n Number(f64),\n Str(String),\n Symbol(String),\n Keyword(String),\n List(Vec<Value>),\n Dict(Vec<(Value, Value)>),\n Lambda(Rc<Closure>),\n Handle(u32), // ← opaque DOM node reference\n}" "rust"))
|
||||
(p "When Rust calls a DOM primitive (e.g. " (code "createElement") "), it gets back a " (code "Handle(id)") ". When it passes that handle to " (code "appendChild") ", the JS shim looks up the real node. Rust never sees a DOM node — only integer IDs.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The JS shim is thin")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The JS shim is thin")
|
||||
(p "The WASM shim's job is minimal:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Instantiate the WASM module")
|
||||
(li "Wire up the handle table")
|
||||
(li "Delegate all browser primitives to " (code "sx-platform.js"))
|
||||
@@ -73,49 +73,49 @@
|
||||
|
||||
(~docs/section :title "Current Stub Breakdown" :id "stubs"
|
||||
(p "Of the 154 stubbed primitives, most fall into a few categories that map directly to implementation phases:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Category")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Count")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Examples")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Category")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Count")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Examples")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "DOM creation & attrs")
|
||||
(td :class "px-3 py-2 text-stone-700" "~30")
|
||||
(td :class "px-3 py-2 text-stone-600" "createElement, setAttribute, appendChild")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Events & callbacks")
|
||||
(td :class "px-3 py-2 text-stone-700" "~20")
|
||||
(td :class "px-3 py-2 text-stone-600" "addEventListener, setTimeout, requestAnimationFrame")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Fetch & network")
|
||||
(td :class "px-3 py-2 text-stone-700" "~15")
|
||||
(td :class "px-3 py-2 text-stone-600" "fetch, XMLHttpRequest, SSE")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Browser APIs")
|
||||
(td :class "px-3 py-2 text-stone-700" "~25")
|
||||
(td :class "px-3 py-2 text-stone-600" "history, location, localStorage, console")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Morph engine")
|
||||
(td :class "px-3 py-2 text-stone-700" "~15")
|
||||
(td :class "px-3 py-2 text-stone-600" "morph, sync-attrs, reconcile")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Signals & reactivity")
|
||||
(td :class "px-3 py-2 text-stone-700" "~20")
|
||||
(td :class "px-3 py-2 text-stone-600" "signal, deref, effect, computed, batch")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 6"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DOM creation & attrs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~30")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "createElement, setAttribute, appendChild")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Events & callbacks")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~20")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "addEventListener, setTimeout, requestAnimationFrame")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fetch & network")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~15")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "fetch, XMLHttpRequest, SSE")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Browser APIs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~25")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "history, location, localStorage, console")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Morph engine")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~15")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "morph, sync-attrs, reconcile")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Signals & reactivity")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~20")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "signal, deref, effect, computed, batch")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 6"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Component lifecycle")
|
||||
(td :class "px-3 py-2 text-stone-700" "~29")
|
||||
(td :class "px-3 py-2 text-stone-600" "boot, hydrate, register-component")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 4"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component lifecycle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~29")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "boot, hydrate, register-component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 4"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 1: WASM build + parse/eval
|
||||
@@ -123,7 +123,7 @@
|
||||
|
||||
(~docs/section :title "Phase 1: WASM Build + Parse/Eval" :id "phase-1"
|
||||
(p "Get the existing Rust code compiling to WASM and running parse/eval in the browser.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Add " (code "wasm-bindgen") " and " (code "wasm-pack") " to the Rust crate")
|
||||
(li "Export " (code "#[wasm_bindgen]") " functions: " (code "parse(src) -> JsValue") " and " (code "eval(src) -> JsValue"))
|
||||
(li "All 75 real primitives work — arithmetic, string ops, list ops, dict ops, comparisons")
|
||||
@@ -137,7 +137,7 @@
|
||||
|
||||
(~docs/section :title "Phase 2: DOM Rendering via Handle Table" :id "phase-2"
|
||||
(p "Implement the shared platform layer and handle table. Rust can create and manipulate DOM nodes.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Extract " (code "sx-platform.js") " from " (code "platform_js.py") " output — all DOM primitives as standalone functions")
|
||||
(li "Implement " (code "sx-wasm-shim.js") " — WASM instantiation + handle table + platform imports")
|
||||
(li "Wire " (code "Value::Handle(u32)") " through the Rust evaluator for DOM node references")
|
||||
@@ -151,7 +151,7 @@
|
||||
|
||||
(~docs/section :title "Phase 3: Events + Fetch + Morph" :id "phase-3"
|
||||
(p "The hardest phase — callbacks cross the WASM/JS boundary. Event handlers are Rust closures that JS must be able to invoke.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Callback table") " — mirror of the handle table but for functions. Rust registers a closure, gets back an ID. JS calls the ID when the event fires. Rust looks up and invokes the closure.")
|
||||
(li (strong "Event listeners") " — " (code "addEventListener") " stores the callback ID on the element handle. JS dispatches events to the WASM callback table.")
|
||||
(li (strong "Fetch") " — Rust initiates fetch via JS import, JS calls " (code "sx-platform.js") " " (code "fetch") ", returns result to Rust via callback.")
|
||||
@@ -164,7 +164,7 @@
|
||||
|
||||
(~docs/section :title "Phase 4: Boot + Hydration + Components" :id "phase-4"
|
||||
(p "Full page lifecycle — the WASM module replaces " (code "sx-browser.js") " for a complete page.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Boot sequence") " — WASM module loads, reads " (code "data-components") " script tags, registers component definitions, processes " (code "data-sx-page") " content")
|
||||
(li (strong "Component registration") " — " (code "defcomp") " and " (code "defisland") " evaluated in Rust, stored in the WASM-side environment")
|
||||
(li (strong "Hydration") " — server-rendered HTML matched against component tree, event handlers attached, islands activated")
|
||||
@@ -177,7 +177,7 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Routing + Streaming + SSE" :id "phase-5"
|
||||
(p "Client-side navigation and real-time updates.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Client routing") " — " (code "navigate-to") ", " (code "popstate") " handling, URL matching from " (code "defpage") " routes")
|
||||
(li (strong "History API") " — " (code "pushState") " / " (code "replaceState") " via JS imports")
|
||||
(li (strong "SSE") " — server-sent events for live updates, morph on incoming HTML/SX")
|
||||
@@ -190,7 +190,7 @@
|
||||
|
||||
(~docs/section :title "Phase 6: Signals + Reactive Islands" :id "phase-6"
|
||||
(p "The most architecturally interesting phase — closures-as-values must work across the WASM boundary.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Signal primitives") " — " (code "signal") ", " (code "deref") ", " (code "reset!") ", " (code "swap!") ", " (code "computed") ", " (code "effect") ", " (code "batch") " all implemented in Rust")
|
||||
(li (strong "Reactive DOM updates") " — signal changes trigger DOM mutations via handle table operations. No full re-render — fine-grained updates.")
|
||||
(li (strong "Island scoping") " — " (code "with-island-scope") " manages signal lifecycle. Dispose island = drop all signals, effects, and handles.")
|
||||
@@ -204,7 +204,7 @@
|
||||
|
||||
(~docs/section :title "Phase 7: Full Parity + Gradual Rollout" :id "phase-7"
|
||||
(p "Shadow-compare and feature-flagged rollout.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Shadow compare") " — run both JS and WASM evaluators in parallel on every page render. Assert identical DOM output. Log divergences. Same principle as async eval convergence.")
|
||||
(li (strong "Feature flag") " — server sets " (code "data-sx-runtime=\"wasm\"") " or " (code "\"js\"") " on the page. Boot script loads the corresponding evaluator. Flag can be per-page, per-user, or global.")
|
||||
(li (strong "Progressive enhancement") " — try WASM first, fall back to JS if WASM instantiation fails. Ship both " (code "sx-browser.js") " and " (code "sx-wasm.wasm") ".")
|
||||
@@ -218,7 +218,7 @@
|
||||
(~docs/section :title "Shared Platform Layer" :id "shared-platform"
|
||||
(p "The architectural heart of this plan. " (code "platform_js.py") " already generates all browser primitive implementations — DOM manipulation, fetch wrappers, timer management, storage access, history API, SSE handling. Currently these are inlined into " (code "sx-browser.js") ".")
|
||||
(p "The extraction:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Step 1") " — refactor " (code "platform_js.py") " to emit a standalone " (code "sx-platform.js") " module (ES module exports)")
|
||||
(li (strong "Step 2") " — " (code "sx-browser.js") " imports from " (code "sx-platform.js") " instead of containing the implementations inline")
|
||||
(li (strong "Step 3") " — " (code "sx-wasm-shim.js") " imports from the same " (code "sx-platform.js") " and wires the functions as WASM imports"))
|
||||
@@ -230,7 +230,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Interaction with Other Plans" :id "interactions"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "WASM Bytecode VM") " — complementary, not competing. This plan bootstraps the tree-walking evaluator to Rust/WASM. The bytecode VM plan compiles SX to a custom bytecode format and runs it in a dispatch loop. Tree-walking comes first (it's working now). Bytecode VM is a future optimisation on top of the Rust host.")
|
||||
(li (strong "Runtime Slicing") " — the WASM module can be tiered. L0 pages need no WASM at all. L1 pages need a minimal WASM module (just parse + eval, no DOM). L2+ pages need the full module with DOM and signals. Tree-shake unused primitives per tier.")
|
||||
(li (strong "Content-Addressed Components") " — deterministic Rust compilation means the same " (code ".sx") " source always produces the same WASM binary. CID-addressable WASM modules. Fetch the evaluator itself by content hash.")
|
||||
@@ -241,7 +241,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "Same spec, another host.") " The Rust target is not special — it's the same architecture as Python and JavaScript. " (code "bootstrap_rs.py") " reads the same " (code ".sx") " files and emits Rust instead of Python or JS. The spec doesn't know which host runs it.")
|
||||
(li (strong "Platform primitives stay in JavaScript.") " DOM, fetch, timers, storage — these are browser APIs. Rust doesn't reimplement them. It calls them through the shared platform layer via the handle table.")
|
||||
(li (strong "Shared platform, not duplicated platform.") " The key win over a pure-WASM approach. Browser primitives exist once in " (code "sx-platform.js") ". Both evaluators use them. No divergence, no duplicate bugs.")
|
||||
@@ -254,7 +254,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After completion:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "SX bootstraps to four hosts: JavaScript, Python, Rust (native), Rust (WASM)")
|
||||
(li "Browser evaluation runs at near-native speed via WASM tree-walking")
|
||||
(li "All browser primitives shared between JS and WASM evaluators — zero duplication")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(defcomp ~plans/scoped-effects/plan-scoped-effects-content ()
|
||||
(~docs/page :title "Scoped Effects — The Deepest Primitive"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Everything SX does — spreads, signals, islands, lakes, morph, context, collect — "
|
||||
"is an instance of one pattern: a named scope with a value flowing down, "
|
||||
"contributions flowing up, and a propagation mode determining when effects are realised. "
|
||||
@@ -19,54 +19,54 @@
|
||||
|
||||
(p "SX has accumulated several mechanisms that all do variations of the same thing:")
|
||||
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Mechanism")
|
||||
(th :class "text-left py-2 pr-4" "Direction")
|
||||
(th :class "text-left py-2 pr-4" "When")
|
||||
(th :class "text-left py-2" "Boundary")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Mechanism")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Direction")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "When")
|
||||
(th (~tw :tokens "text-left py-2") "Boundary")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "spread")
|
||||
(td :class "py-2 pr-4" "child → parent")
|
||||
(td :class "py-2 pr-4" "render time")
|
||||
(td :class "py-2" "element"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "collect! / collected")
|
||||
(td :class "py-2 pr-4" "child → ancestor")
|
||||
(td :class "py-2 pr-4" "render time")
|
||||
(td :class "py-2" "render tree"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "provide / context")
|
||||
(td :class "py-2 pr-4" "ancestor → child")
|
||||
(td :class "py-2 pr-4" "render time")
|
||||
(td :class "py-2" "render subtree"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "signal / effect")
|
||||
(td :class "py-2 pr-4" "value → subscribers")
|
||||
(td :class "py-2 pr-4" "signal change")
|
||||
(td :class "py-2" "reactive scope"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "defisland")
|
||||
(td :class "py-2 pr-4" "server → client")
|
||||
(td :class "py-2 pr-4" "hydration")
|
||||
(td :class "py-2" "server/client"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "lake")
|
||||
(td :class "py-2 pr-4" "server → client slot")
|
||||
(td :class "py-2 pr-4" "navigation/morph")
|
||||
(td :class "py-2" "client/server"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "reactive-spread")
|
||||
(td :class "py-2 pr-4" "child → parent")
|
||||
(td :class "py-2 pr-4" "signal change")
|
||||
(td :class "py-2" "element"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "spread")
|
||||
(td (~tw :tokens "py-2 pr-4") "child → parent")
|
||||
(td (~tw :tokens "py-2 pr-4") "render time")
|
||||
(td (~tw :tokens "py-2") "element"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "collect! / collected")
|
||||
(td (~tw :tokens "py-2 pr-4") "child → ancestor")
|
||||
(td (~tw :tokens "py-2 pr-4") "render time")
|
||||
(td (~tw :tokens "py-2") "render tree"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "provide / context")
|
||||
(td (~tw :tokens "py-2 pr-4") "ancestor → child")
|
||||
(td (~tw :tokens "py-2 pr-4") "render time")
|
||||
(td (~tw :tokens "py-2") "render subtree"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "signal / effect")
|
||||
(td (~tw :tokens "py-2 pr-4") "value → subscribers")
|
||||
(td (~tw :tokens "py-2 pr-4") "signal change")
|
||||
(td (~tw :tokens "py-2") "reactive scope"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "defisland")
|
||||
(td (~tw :tokens "py-2 pr-4") "server → client")
|
||||
(td (~tw :tokens "py-2 pr-4") "hydration")
|
||||
(td (~tw :tokens "py-2") "server/client"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "lake")
|
||||
(td (~tw :tokens "py-2 pr-4") "server → client slot")
|
||||
(td (~tw :tokens "py-2 pr-4") "navigation/morph")
|
||||
(td (~tw :tokens "py-2") "client/server"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "reactive-spread")
|
||||
(td (~tw :tokens "py-2 pr-4") "child → parent")
|
||||
(td (~tw :tokens "py-2 pr-4") "signal change")
|
||||
(td (~tw :tokens "py-2") "element"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "def-store / use-store")
|
||||
(td :class "py-2 pr-4" "island ↔ island")
|
||||
(td :class "py-2 pr-4" "signal change")
|
||||
(td :class "py-2" "cross-island"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "def-store / use-store")
|
||||
(td (~tw :tokens "py-2 pr-4") "island ↔ island")
|
||||
(td (~tw :tokens "py-2 pr-4") "signal change")
|
||||
(td (~tw :tokens "py-2") "cross-island"))))
|
||||
|
||||
(p "Eight mechanisms. Each invented separately for a real use case. "
|
||||
"Each with its own API surface, its own implementation path, its own section in the spec.")
|
||||
@@ -122,7 +122,7 @@
|
||||
(~docs/section :title "The scope primitive" :id "scope"
|
||||
|
||||
(p "A " (code "scope") " is a named region of the render tree with three properties:")
|
||||
(ol :class "space-y-2 text-stone-600"
|
||||
(ol (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "Value") " — data flowing " (em "downward") " to descendants (readable via " (code "context") ")")
|
||||
(li (strong "Accumulator") " — data flowing " (em "upward") " from descendants (writable via " (code "emit!") ")")
|
||||
(li (strong "Propagation mode") " — " (em "when") " effects on this scope are realised"))
|
||||
@@ -131,37 +131,37 @@
|
||||
|
||||
(p "Every existing mechanism is a scope with a specific configuration:")
|
||||
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Mechanism")
|
||||
(th :class "text-left py-2 pr-4" "Scope equivalent")
|
||||
(th :class "text-left py-2" "Propagation")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Mechanism")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Scope equivalent")
|
||||
(th (~tw :tokens "text-left py-2") "Propagation")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "provide")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :value v body)")
|
||||
(td :class "py-2" ":render"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "defisland")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :propagation :reactive body)")
|
||||
(td :class "py-2" ":reactive"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "lake")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :propagation :morph body)")
|
||||
(td :class "py-2" ":morph"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "spread")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(emit! :element-attrs dict)")
|
||||
(td :class "py-2" ":render (implicit)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "collect!")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(emit! name value)")
|
||||
(td :class "py-2" ":render"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "provide")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(scope name :value v body)")
|
||||
(td (~tw :tokens "py-2") ":render"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "defisland")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(scope name :propagation :reactive body)")
|
||||
(td (~tw :tokens "py-2") ":reactive"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "lake")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(scope name :propagation :morph body)")
|
||||
(td (~tw :tokens "py-2") ":morph"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "spread")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(emit! :element-attrs dict)")
|
||||
(td (~tw :tokens "py-2") ":render (implicit)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "collect!")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(emit! name value)")
|
||||
(td (~tw :tokens "py-2") ":render"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "def-store")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :value (signal v) :propagation :reactive)")
|
||||
(td :class "py-2" ":reactive (cross-scope)"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "def-store")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(scope name :value (signal v) :propagation :reactive)")
|
||||
(td (~tw :tokens "py-2") ":reactive (cross-scope)"))))
|
||||
|
||||
(~docs/subsection :title "Scopes compose by nesting"
|
||||
(~docs/code :src (highlight ";; An island with a theme context and a morphable lake\n(scope \"my-island\" :propagation :reactive\n (let ((colour (signal \"violet\")))\n (scope \"theme\" :value {:primary colour} :propagation :render\n (div\n (h1 :style (str \"color:\" (deref (get (context \"theme\") :primary)))\n \"Themed heading\")\n (scope \"product-details\" :propagation :morph\n ;; Server morphs this on navigation\n ;; Reactive attrs on the h1 are protected\n (~product-card :id 42))))))" "lisp"))
|
||||
@@ -181,29 +181,29 @@
|
||||
(p "Direction (up/down) × propagation mode (render/reactive/morph) gives six cells. "
|
||||
"Every mechanism in SX occupies one:")
|
||||
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "")
|
||||
(th :class "text-left py-2 pr-4" "Render-time")
|
||||
(th :class "text-left py-2 pr-4" "Reactive")
|
||||
(th :class "text-left py-2" "Morph")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Render-time")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Reactive")
|
||||
(th (~tw :tokens "text-left py-2") "Morph")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-semibold" "Down ↓")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "context")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "reactive context")
|
||||
(td :class "py-2 font-mono text-xs" "server props"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-semibold" "Up ↑")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "emit! / spread")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "reactive-spread")
|
||||
(td :class "py-2 font-mono text-xs" "lake contributions"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-semibold") "Down ↓")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "context")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "reactive context")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "server props"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-semibold") "Up ↑")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "emit! / spread")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "reactive-spread")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "lake contributions"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-semibold" "Both ↕")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "provide")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "island")
|
||||
(td :class "py-2 font-mono text-xs" "full morph"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-semibold") "Both ↕")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "provide")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "island")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "full morph"))))
|
||||
|
||||
(p "Some cells are already implemented. Some are new. "
|
||||
"The point is that " (code "scope") " fills them all from " (strong "one primitive") ". "
|
||||
@@ -229,33 +229,33 @@
|
||||
(strong "algebraic effects with handlers") ".")
|
||||
|
||||
(~docs/subsection :title "The correspondence"
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Algebraic effects")
|
||||
(th :class "text-left py-2 pr-4" "SX")
|
||||
(th :class "text-left py-2" "What it does")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Algebraic effects")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "SX")
|
||||
(th (~tw :tokens "text-left py-2") "What it does")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "perform effect")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "emit!")
|
||||
(td :class "py-2" "Raise an effect upward through the tree"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "handle effect")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "scope / provide")
|
||||
(td :class "py-2" "Catch and interpret the effect"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "ask")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "context")
|
||||
(td :class "py-2" "Read the nearest handler's value"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "handler nesting")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "scope nesting")
|
||||
(td :class "py-2" "Inner handlers shadow outer ones"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "perform effect")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "emit!")
|
||||
(td (~tw :tokens "py-2") "Raise an effect upward through the tree"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "handle effect")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "scope / provide")
|
||||
(td (~tw :tokens "py-2") "Catch and interpret the effect"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "ask")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "context")
|
||||
(td (~tw :tokens "py-2") "Read the nearest handler's value"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "handler nesting")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "scope nesting")
|
||||
(td (~tw :tokens "py-2") "Inner handlers shadow outer ones"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "resumption")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "reactive propagation")
|
||||
(td :class "py-2" "Handler can re-invoke the continuation"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "resumption")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "reactive propagation")
|
||||
(td (~tw :tokens "py-2") "Handler can re-invoke the continuation"))))
|
||||
|
||||
(p "The last row is the deep one. In algebraic effect theory, a handler can "
|
||||
(strong "resume") " the computation that performed the effect — optionally with "
|
||||
@@ -268,7 +268,7 @@
|
||||
"Exceptions, generators, async/await, coroutines, backtracking, and "
|
||||
"nondeterminism are all instances of algebraic effects with different handler strategies.")
|
||||
(p "SX's three propagation modes are three handler strategies:")
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-600")
|
||||
(li (strong "Render") " = " (em "one-shot") " handler — handle the effect immediately, "
|
||||
"don't resume. Like " (code "try/catch") " for rendering.")
|
||||
(li (strong "Reactive") " = " (em "multi-shot") " handler — handle the effect, "
|
||||
@@ -295,7 +295,7 @@
|
||||
(~docs/subsection :title "1. New propagation modes"
|
||||
(p "The three modes (render, reactive, morph) are not hardcoded — they are "
|
||||
"handler strategies. New strategies can be added without new primitives:")
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-600")
|
||||
(li (strong ":animation") " — effects realised on requestAnimationFrame, "
|
||||
"batched per frame, interruptible by higher-priority updates")
|
||||
(li (strong ":idle") " — effects deferred to requestIdleCallback, "
|
||||
@@ -346,9 +346,9 @@
|
||||
(code "emitted") ". Platform provides " (code "scope-push!/scope-pop!") ". "
|
||||
"Spreads reimplemented on provide/emit!.")
|
||||
(p "See "
|
||||
(a :href "/sx/(geography.(provide))" :class "text-violet-600 hover:underline" "provide article")
|
||||
(a :href "/sx/(geography.(provide))" (~tw :tokens "text-violet-600 hover:underline") "provide article")
|
||||
" and "
|
||||
(a :href "/sx/(geography.(spreads))" :class "text-violet-600 hover:underline" "spreads article")
|
||||
(a :href "/sx/(geography.(spreads))" (~tw :tokens "text-violet-600 hover:underline") "spreads article")
|
||||
"."))
|
||||
|
||||
(~docs/subsection :title "Phase 2: scope as the common form ✓"
|
||||
@@ -359,7 +359,7 @@
|
||||
(p "The unified platform structure:")
|
||||
(~docs/code :src (highlight "_scope_stacks = {} ;; {name: [{value, emitted: [], dedup: bool}]}" "python"))
|
||||
(p "See "
|
||||
(a :href "/sx/(geography.(scopes))" :class "text-violet-600 hover:underline" "scopes article")
|
||||
(a :href "/sx/(geography.(scopes))" (~tw :tokens "text-violet-600 hover:underline") "scopes article")
|
||||
"."))
|
||||
|
||||
(~docs/subsection :title "Phase 3: effect handlers (future)"
|
||||
@@ -380,33 +380,33 @@
|
||||
(p "The self-hosting spec currently has separate code paths for each mechanism. "
|
||||
"Under the scope model, they converge:")
|
||||
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Spec file")
|
||||
(th :class "text-left py-2 pr-4" "Current")
|
||||
(th :class "text-left py-2" "After scope")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Spec file")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Current")
|
||||
(th (~tw :tokens "text-left py-2") "After scope")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "eval.sx")
|
||||
(td :class "py-2 pr-4" "provide + defisland as separate special forms")
|
||||
(td :class "py-2" "scope as one special form, sugar for provide/defisland/lake"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "adapter-html.sx")
|
||||
(td :class "py-2 pr-4" "provide, island, lake as separate dispatch cases")
|
||||
(td :class "py-2" "one scope dispatch, mode determines serialisation"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "adapter-dom.sx")
|
||||
(td :class "py-2 pr-4" "render-dom-island, render-dom-lake, reactive-spread")
|
||||
(td :class "py-2" "one render-dom-scope, mode determines lifecycle"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "engine.sx")
|
||||
(td :class "py-2 pr-4" "morph-island-children, sync-attrs, data-sx-reactive-attrs")
|
||||
(td :class "py-2" "morph-scope (scope boundary determines skip/update)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "eval.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "provide + defisland as separate special forms")
|
||||
(td (~tw :tokens "py-2") "scope as one special form, sugar for provide/defisland/lake"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "adapter-html.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "provide, island, lake as separate dispatch cases")
|
||||
(td (~tw :tokens "py-2") "one scope dispatch, mode determines serialisation"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "adapter-dom.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "render-dom-island, render-dom-lake, reactive-spread")
|
||||
(td (~tw :tokens "py-2") "one render-dom-scope, mode determines lifecycle"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "engine.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "morph-island-children, sync-attrs, data-sx-reactive-attrs")
|
||||
(td (~tw :tokens "py-2") "morph-scope (scope boundary determines skip/update)"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "signals.sx")
|
||||
(td :class "py-2 pr-4" "standalone signal runtime")
|
||||
(td :class "py-2" "unchanged — signals are the value layer, scopes are the structure layer"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "signals.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "standalone signal runtime")
|
||||
(td (~tw :tokens "py-2") "unchanged — signals are the value layer, scopes are the structure layer"))))
|
||||
|
||||
(p "Signals remain orthogonal. A scope's " (code ":value") " can be a signal or a plain "
|
||||
"value — the scope doesn't care. The propagation mode determines whether the scope "
|
||||
@@ -421,8 +421,8 @@
|
||||
|
||||
(p "Go deep enough and there is one operation:")
|
||||
|
||||
(blockquote :class "border-l-4 border-violet-300 pl-4 my-6"
|
||||
(p :class "text-lg font-semibold text-stone-700"
|
||||
(blockquote (~tw :tokens "border-l-4 border-violet-300 pl-4 my-6")
|
||||
(p (~tw :tokens "text-lg font-semibold text-stone-700")
|
||||
"Evaluate this expression in a scope. Handle effects that cross scope boundaries."))
|
||||
|
||||
(p "Rendering is evaluating expressions in a scope where " (code "emit!") " effects "
|
||||
@@ -431,7 +431,7 @@
|
||||
"and re-evaluating them in existing scopes.")
|
||||
|
||||
(p "Every SX mechanism — every one — is a specific answer to three questions:")
|
||||
(ol :class "space-y-2 text-stone-600"
|
||||
(ol (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "What scope") " am I in? (element / subtree / island / lake / global)")
|
||||
(li (strong "What direction") " does data flow? (down via context, up via emit)")
|
||||
(li (strong "When") " are effects realised? (render / signal change / network)"))
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
;; Status banner
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(div :class "rounded-lg bg-green-50 border border-green-200 p-4 mb-8"
|
||||
(div :class "flex items-center gap-3"
|
||||
(span :class "inline-flex items-center rounded-full bg-green-100 px-3 py-1 text-sm font-semibold text-green-800"
|
||||
(div (~tw :tokens "rounded-lg bg-green-50 border border-green-200 p-4 mb-8")
|
||||
(div (~tw :tokens "flex items-center gap-3")
|
||||
(span (~tw :tokens "inline-flex items-center rounded-full bg-green-100 px-3 py-1 text-sm font-semibold text-green-800")
|
||||
"Complete")
|
||||
(p :class "text-green-700 text-sm"
|
||||
(p (~tw :tokens "text-green-700 text-sm")
|
||||
(code "py.sx") " is implemented and verified. G0 == G1: 128/128 defines match, "
|
||||
"1490 lines, 88,955 bytes — byte-for-byte identical. "
|
||||
(a :href "/sx/(language.(bootstrapper.self-hosting))" :class "underline text-green-600 font-medium"
|
||||
(a :href "/sx/(language.(bootstrapper.self-hosting))" (~tw :tokens "underline text-green-600 font-medium")
|
||||
"See live verification."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -43,31 +43,31 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Results" :id "results"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Metric")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Value")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Metric")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Value")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "py.sx size")
|
||||
(td :class "px-4 py-2 font-mono" "1,182 lines"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "bootstrap_py.py size (replaced)")
|
||||
(td :class "px-4 py-2 font-mono" "902 lines (PyEmitter class)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "Defines translated")
|
||||
(td :class "px-4 py-2 font-mono" "128/128 exact match"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "Spec files processed")
|
||||
(td :class "px-4 py-2 font-mono" "7 (eval, forms, render, adapter-html, adapter-sx, deps, signals)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "Output size")
|
||||
(td :class "px-4 py-2 font-mono" "1,490 lines / 88,955 bytes"))
|
||||
(tr :class "border-t border-stone-100 bg-green-50"
|
||||
(td :class "px-4 py-2 font-semibold text-green-700" "G0 == G1")
|
||||
(td :class "px-4 py-2 font-semibold text-green-700" "Identical (diff is empty)"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "py.sx size")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "1,182 lines"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "bootstrap_py.py size (replaced)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "902 lines (PyEmitter class)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "Defines translated")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "128/128 exact match"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "Spec files processed")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "7 (eval, forms, render, adapter-html, adapter-sx, deps, signals)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "Output size")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "1,490 lines / 88,955 bytes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-green-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-semibold text-green-700") "G0 == G1")
|
||||
(td (~tw :tokens "px-4 py-2 font-semibold text-green-700") "Identical (diff is empty)"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Architecture
|
||||
@@ -75,34 +75,34 @@
|
||||
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(p "Three bootstrapper generations:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Gen")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Source")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Runs on")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Reads")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Produces")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Gen")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Source")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Runs on")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Reads")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Produces")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono text-stone-600" "G0")
|
||||
(td :class "px-4 py-2" (code "bootstrap_py.py"))
|
||||
(td :class "px-4 py-2" "Python (manual)")
|
||||
(td :class "px-4 py-2" (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td :class "px-4 py-2" (code "sx_ref.py")))
|
||||
(tr :class "border-t border-stone-100 bg-green-50"
|
||||
(td :class "px-4 py-2 font-mono text-green-700" "G1")
|
||||
(td :class "px-4 py-2" (code "py.sx"))
|
||||
(td :class "px-4 py-2" "Python evaluator + SX")
|
||||
(td :class "px-4 py-2" (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td :class "px-4 py-2 font-semibold text-green-700" (code "sx_ref.py") " (identical)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono text-stone-600" "G2")
|
||||
(td :class "px-4 py-2" (code "py.sx"))
|
||||
(td :class "px-4 py-2" (code "sx_ref.py") " from G1 + SX")
|
||||
(td :class "px-4 py-2" (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td :class "px-4 py-2" (code "sx_ref.py") " (fixed-point)")))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-stone-600") "G0")
|
||||
(td (~tw :tokens "px-4 py-2") (code "bootstrap_py.py"))
|
||||
(td (~tw :tokens "px-4 py-2") "Python (manual)")
|
||||
(td (~tw :tokens "px-4 py-2") (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td (~tw :tokens "px-4 py-2") (code "sx_ref.py")))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-green-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-green-700") "G1")
|
||||
(td (~tw :tokens "px-4 py-2") (code "py.sx"))
|
||||
(td (~tw :tokens "px-4 py-2") "Python evaluator + SX")
|
||||
(td (~tw :tokens "px-4 py-2") (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td (~tw :tokens "px-4 py-2 font-semibold text-green-700") (code "sx_ref.py") " (identical)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-stone-600") "G2")
|
||||
(td (~tw :tokens "px-4 py-2") (code "py.sx"))
|
||||
(td (~tw :tokens "px-4 py-2") (code "sx_ref.py") " from G1 + SX")
|
||||
(td (~tw :tokens "px-4 py-2") (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td (~tw :tokens "px-4 py-2") (code "sx_ref.py") " (fixed-point)")))))
|
||||
|
||||
(p "G0 is the hand-written compiler. G1 replaces it with SX. "
|
||||
"G2 proves the fixed-point: " (code "py.sx") " compiled by its own output "
|
||||
@@ -118,59 +118,59 @@
|
||||
(p "SX identifiers become valid Python identifiers. "
|
||||
"The RENAMES dict (200+ entries) handles explicit mappings; "
|
||||
"general rules handle the rest:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "SX")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Python")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Rule")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "SX")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Python")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Rule")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "eval-expr")
|
||||
(td :class "px-4 py-2 font-mono" "eval_expr")
|
||||
(td :class "px-4 py-2" "kebab → snake"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "nil?")
|
||||
(td :class "px-4 py-2 font-mono" "is_nil")
|
||||
(td :class "px-4 py-2" "predicate rename"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "empty?")
|
||||
(td :class "px-4 py-2 font-mono" "empty_p")
|
||||
(td :class "px-4 py-2" "? → _p (general)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "set!")
|
||||
(td :class "px-4 py-2 font-mono" "set_b")
|
||||
(td :class "px-4 py-2" "! → _b"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "type")
|
||||
(td :class "px-4 py-2 font-mono" "type_")
|
||||
(td :class "px-4 py-2" "Python reserved word escape"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "eval-expr")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "eval_expr")
|
||||
(td (~tw :tokens "px-4 py-2") "kebab → snake"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "nil?")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "is_nil")
|
||||
(td (~tw :tokens "px-4 py-2") "predicate rename"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "empty?")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "empty_p")
|
||||
(td (~tw :tokens "px-4 py-2") "? → _p (general)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "set!")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "set_b")
|
||||
(td (~tw :tokens "px-4 py-2") "! → _b"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "type")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "type_")
|
||||
(td (~tw :tokens "px-4 py-2") "Python reserved word escape"))))))
|
||||
|
||||
(~docs/subsection :title "Special Forms"
|
||||
(p "Each SX special form maps to a Python expression pattern:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "SX")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Python")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "SX")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Python")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(if c t e)")
|
||||
(td :class "px-4 py-2 font-mono" "(t if sx_truthy(c) else e)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(let ((a 1)) body)")
|
||||
(td :class "px-4 py-2 font-mono" "(lambda a: body)(1)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(fn (x) body)")
|
||||
(td :class "px-4 py-2 font-mono" "lambda x: body"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(and a b c)")
|
||||
(td :class "px-4 py-2 font-mono" "(a if not sx_truthy(a) else ...)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(case x \"a\" 1)")
|
||||
(td :class "px-4 py-2 font-mono" "_sx_case(x, [(\"a\", lambda: 1)])"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(if c t e)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(t if sx_truthy(c) else e)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(let ((a 1)) body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(lambda a: body)(1)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(fn (x) body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "lambda x: body"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(and a b c)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(a if not sx_truthy(a) else ...)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(case x \"a\" 1)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "_sx_case(x, [(\"a\", lambda: 1)])"))))))
|
||||
|
||||
(~docs/subsection :title "Mutation: set! and Cell Variables"
|
||||
(p "Python closures can read but not rebind outer variables. "
|
||||
@@ -201,7 +201,7 @@
|
||||
"it translates SX syntax, not SX semantics. Every SX form has a "
|
||||
"straightforward Python equivalent.")
|
||||
(p "The roadmap:")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "py.sx") " → Python — " (strong "done"))
|
||||
(li (code "js.sx") " → JavaScript (replaces " (code "bootstrap_js.py") ")")
|
||||
(li (code "go.sx") " → Go (new host)")
|
||||
|
||||
@@ -12,48 +12,48 @@
|
||||
(~docs/section :title "What remains" :id "remains"
|
||||
(~docs/note "Nothing has been implemented. This is the full scope of work.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 1: Data Model + Encryption")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 1: Data Model + Encryption")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li (code "shared/models/social_connection.py") " — SocialConnection model (user_id, platform, tokens, scopes, extra_data)")
|
||||
(li (code "shared/infrastructure/social_crypto.py") " — Fernet encrypt/decrypt for tokens")
|
||||
(li "Alembic migration for social_connections table")
|
||||
(li "Environment variables for per-platform OAuth credentials")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 2: Platform OAuth Clients")
|
||||
(p :class "text-sm text-stone-600 mb-2" "All in " (code "account/services/social_platforms/") ":")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 2: Platform OAuth Clients")
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "All in " (code "account/services/social_platforms/") ":")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li (code "base.py") " — SocialPlatform ABC, OAuthResult, ShareResult")
|
||||
(li (code "meta.py") " — Facebook + Instagram + Threads (Graph API)")
|
||||
(li (code "twitter.py") " — OAuth 2.0 with PKCE")
|
||||
(li (code "linkedin.py") " — LinkedIn Posts API")
|
||||
(li (code "mastodon.py") " — Dynamic app registration per instance")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 3: Account Blueprint")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 3: Account Blueprint")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li (code "account/bp/social/routes.py") " — /social/ list, /social/connect/<platform>/, /social/callback/<platform>/, /social/share/")
|
||||
(li "Register before account blueprint (account has catch-all /<slug>/ route)")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 4: Templates")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 4: Templates")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li "Social panel — platform cards, connect/disconnect")
|
||||
(li "Share panel — content preview, account checkboxes, share button")
|
||||
(li "Share result — per-platform success/failure with links")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 5: Share Button in Content Apps")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 5: Share Button in Content Apps")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li "share-button fragment from account service")
|
||||
(li "Blog, events, market detail pages fetch and render the fragment")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 6: Token Refresh + Share History")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 6: Token Refresh + Share History")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li "Automatic token refresh before posting")
|
||||
(li "Optional social_shares table for history and duplicate prevention")))))))
|
||||
|
||||
|
||||
@@ -9,36 +9,36 @@
|
||||
(p "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 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 "This is the nucleus. Everything else radiates outward from it.")
|
||||
|
||||
(div :class "space-y-3 my-4"
|
||||
(div :class "rounded border border-violet-200 bg-violet-50/50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white" "1")
|
||||
(span :class "font-semibold text-stone-800" "The Nucleus"))
|
||||
(p :class "text-sm text-stone-600" "The spec itself — " (code "shared/sx/ref/*.sx") ". Fourteen files, 180+ functions with type and effect annotations. Each function is simultaneously a formal definition and executable code."))
|
||||
(div (~tw :tokens "space-y-3 my-4")
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white") "1")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Nucleus"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "The spec itself — " (code "shared/sx/ref/*.sx") ". Fourteen files, 180+ functions with type and effect annotations. Each function is simultaneously a formal definition and executable code."))
|
||||
|
||||
(div :class "rounded border border-sky-200 bg-sky-50/50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white" "2")
|
||||
(span :class "font-semibold text-stone-800" "The Bootstrapper Ring"))
|
||||
(p :class "text-sm text-stone-600" "Translators that read the spec and emit native implementations. " (code "bootstrap_py.py") " emits Python. " (code "js.sx") " emits JavaScript. " (code "z3.sx") " emits SMT-LIB verification conditions. The spec's knowledge is preserved in every translation. Nothing is added, nothing is lost."))
|
||||
(div (~tw :tokens "rounded border border-sky-200 bg-sky-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white") "2")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Bootstrapper Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Translators that read the spec and emit native implementations. " (code "bootstrap_py.py") " emits Python. " (code "js.sx") " emits JavaScript. " (code "z3.sx") " emits SMT-LIB verification conditions. The spec's knowledge is preserved in every translation. Nothing is added, nothing is lost."))
|
||||
|
||||
(div :class "rounded border border-rose-200 bg-rose-50/50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-rose-500 text-white" "3")
|
||||
(span :class "font-semibold text-stone-800" "The Bridge Ring"))
|
||||
(p :class "text-sm text-stone-600" "The platform boundary — " (code "boundary.sx") " is literally the membrane between the made world (the spec) and the found world (the host environment). It declares what the host must provide so the spec can function. Each spec function's dependencies trace back to either other spec functions or platform primitives."))
|
||||
(div (~tw :tokens "rounded border border-rose-200 bg-rose-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-rose-500 text-white") "3")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Bridge Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "The platform boundary — " (code "boundary.sx") " is literally the membrane between the made world (the spec) and the found world (the host environment). It declares what the host must provide so the spec can function. Each spec function's dependencies trace back to either other spec functions or platform primitives."))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50/50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-500 text-white" "4")
|
||||
(span :class "font-semibold text-stone-800" "The Runtime Ring"))
|
||||
(p :class "text-sm text-stone-600" "Bootstrapped spec + platform bridge = working system. Tests verify behaviour. " (code "prove.sx") " verifies algebraic properties by bounded model checking. " (code "z3.sx") " translates to SMT-LIB for unbounded proofs. The spec doesn't just claim to work — it proves it."))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-500 text-white") "4")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Runtime Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Bootstrapped spec + platform bridge = working system. Tests verify behaviour. " (code "prove.sx") " verifies algebraic properties by bounded model checking. " (code "z3.sx") " translates to SMT-LIB for unbounded proofs. The spec doesn't just claim to work — it proves it."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50/50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white" "5")
|
||||
(span :class "font-semibold text-stone-800" "The Application Ring"))
|
||||
(p :class "text-sm text-stone-600" "This website — rendering 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.")))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white") "5")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Application Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "This website — rendering 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.")))
|
||||
|
||||
(p "The spec explorer makes all five rings visible for every function."))
|
||||
|
||||
@@ -49,38 +49,38 @@
|
||||
(~docs/section :title "Per-Function Cards" :id "cards"
|
||||
(p "Each function in the spec gets a card showing all five rings:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Ring")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Panel")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Content")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Ring")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Panel")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Content")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white" "1"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Nucleus")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX source with syntax highlighting. Effect badges (pure/mutation/io/render). Typed parameter list."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white" "2"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Translations")
|
||||
(td :class "px-3 py-2 text-stone-600" "Collapsible panels showing the same function in Python, JavaScript, and Z3/SMT-LIB. Each generated by the actual bootstrappers — not hand-written."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-rose-500 text-white" "3"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Bridge")
|
||||
(td :class "px-3 py-2 text-stone-600" "Cross-references: which spec functions and platform primitives this function depends on. Platform deps marked with " (code "⬡") "."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-500 text-white" "4"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Runtime")
|
||||
(td :class "px-3 py-2 text-stone-600" "Tests matched from " (code "test-*.sx") " files. Proof status from " (code "prove.sx") " — sat/unknown/n/a. Algebraic properties that reference this function."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white" "5"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Examples")
|
||||
(td :class "px-3 py-2 text-stone-600" "Usage examples extracted from comments, test assertions, and curated examples. Living documentation."))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white") "1"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Nucleus")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX source with syntax highlighting. Effect badges (pure/mutation/io/render). Typed parameter list."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white") "2"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Translations")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Collapsible panels showing the same function in Python, JavaScript, and Z3/SMT-LIB. Each generated by the actual bootstrappers — not hand-written."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-rose-500 text-white") "3"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Bridge")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Cross-references: which spec functions and platform primitives this function depends on. Platform deps marked with " (code "⬡") "."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-500 text-white") "4"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Runtime")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Tests matched from " (code "test-*.sx") " files. Proof status from " (code "prove.sx") " — sat/unknown/n/a. Algebraic properties that reference this function."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white") "5"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Examples")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Usage examples extracted from comments, test assertions, and curated examples. Living documentation."))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Effect system
|
||||
@@ -89,11 +89,11 @@
|
||||
(~docs/section :title "Effect Annotations" :id "effects"
|
||||
(p "Every function in the spec now carries an " (code ":effects") " annotation declaring what kind of side effects it performs:")
|
||||
|
||||
(div :class "flex flex-wrap gap-3 my-4"
|
||||
(span :class "inline-flex items-center gap-1 px-3 py-1 rounded bg-green-100 text-green-700 text-sm font-medium" "pure " (code ":effects []"))
|
||||
(span :class "inline-flex items-center gap-1 px-3 py-1 rounded bg-amber-100 text-amber-700 text-sm font-medium" "mutation " (code ":effects [mutation]"))
|
||||
(span :class "inline-flex items-center gap-1 px-3 py-1 rounded bg-orange-100 text-orange-700 text-sm font-medium" "io " (code ":effects [io]"))
|
||||
(span :class "inline-flex items-center gap-1 px-3 py-1 rounded bg-sky-100 text-sky-700 text-sm font-medium" "render " (code ":effects [render]")))
|
||||
(div (~tw :tokens "flex flex-wrap gap-3 my-4")
|
||||
(span (~tw :tokens "inline-flex items-center gap-1 px-3 py-1 rounded bg-green-100 text-green-700 text-sm font-medium") "pure " (code ":effects []"))
|
||||
(span (~tw :tokens "inline-flex items-center gap-1 px-3 py-1 rounded bg-amber-100 text-amber-700 text-sm font-medium") "mutation " (code ":effects [mutation]"))
|
||||
(span (~tw :tokens "inline-flex items-center gap-1 px-3 py-1 rounded bg-orange-100 text-orange-700 text-sm font-medium") "io " (code ":effects [io]"))
|
||||
(span (~tw :tokens "inline-flex items-center gap-1 px-3 py-1 rounded bg-sky-100 text-sky-700 text-sm font-medium") "render " (code ":effects [render]")))
|
||||
|
||||
(p "The explorer shows effect badges on each function card, and the stats bar aggregates them across the whole file. Pure functions (green) are the nucleus — no side effects, fully deterministic, safe to cache, reorder, or parallelise.")
|
||||
|
||||
@@ -108,15 +108,15 @@
|
||||
|
||||
(~docs/subsection :title "Python (via bootstrap_py.py)"
|
||||
(~docs/code :src (highlight "def signal(initial_value):\n return make_signal(initial_value)" "python"))
|
||||
(p :class "text-sm text-stone-500" (code "PyEmitter._emit_define()") " — the exact same code path that generates " (code "sx_ref.py") "."))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (code "PyEmitter._emit_define()") " — the exact same code path that generates " (code "sx_ref.py") "."))
|
||||
|
||||
(~docs/subsection :title "JavaScript (via js.sx)"
|
||||
(~docs/code :src (highlight "var signal = function(initial_value) {\n return make_signal(initial_value);\n};" "javascript"))
|
||||
(p :class "text-sm text-stone-500" (code "js-emit-define") " — the self-hosting JS bootstrapper, written in SX, evaluated by the Python evaluator."))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (code "js-emit-define") " — the self-hosting JS bootstrapper, written in SX, evaluated by the Python evaluator."))
|
||||
|
||||
(~docs/subsection :title "Z3 / SMT-LIB (via z3.sx)"
|
||||
(~docs/code :src (highlight "; signal — Create a reactive signal container with an initial value.\n(declare-fun signal (Value) Value)" "lisp"))
|
||||
(p :class "text-sm text-stone-500" (code "z3-translate") " — the first self-hosted bootstrapper, translating spec declarations to verification conditions for theorem provers.")))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (code "z3-translate") " — the first self-hosted bootstrapper, translating spec declarations to verification conditions for theorem provers.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Testing and proving
|
||||
@@ -147,8 +147,8 @@
|
||||
|
||||
(~docs/subsection :title "Layer 2: SX components"
|
||||
(p (code "specs-explorer.sx") " — 12-15 " (code "defcomp") " components rendering the structured data:")
|
||||
(div :class "overflow-x-auto"
|
||||
(pre :class "text-xs bg-stone-100 rounded p-3"
|
||||
(div (~tw :tokens "overflow-x-auto")
|
||||
(pre (~tw :tokens "text-xs bg-stone-100 rounded p-3")
|
||||
(code "~specs-explorer/spec-explorer-content top-level, receives parsed data\n ~specs-explorer/spec-explorer-header filename, title, source link\n ~specs-explorer/spec-explorer-stats aggregate badges: effects, tests, proofs\n ~spec-explorer-toc section table of contents\n ~specs-explorer/spec-explorer-section one section with its defines\n ~specs-explorer/spec-explorer-define one function card (all five rings)\n ~specs-explorer/spec-effect-badge colored effect badge\n ~specs-explorer/spec-param-list typed parameter list\n ~specs-explorer/spec-ring-translations SX / Python / JS / Z3 panels\n ~specs-explorer/spec-ring-bridge cross-references + platform deps\n ~specs-explorer/spec-ring-runtime tests + proofs\n ~spec-ring-examples usage examples\n ~specs-explorer/spec-platform-interface platform primitives table"))))
|
||||
|
||||
(~docs/subsection :title "Layer 3: Routing"
|
||||
@@ -159,13 +159,13 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Incremental Delivery" :id "increments"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white uppercase" "Inc 1")
|
||||
(span :class "font-semibold text-stone-800" "Core + Translations (Ring 1-2)"))
|
||||
(ul :class "list-disc pl-5 text-sm text-stone-600 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white uppercase") "Inc 1")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Core + Translations (Ring 1-2)"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-sm text-stone-600 space-y-1")
|
||||
(li (code "_spec_explorer_data()") " — sections, defines, effects, params, source extraction")
|
||||
(li "Python translation via " (code "PyEmitter._emit_define()"))
|
||||
(li "JavaScript translation via " (code "js-emit-define") " (from " (code "run_js_sx.py") ")")
|
||||
@@ -173,11 +173,11 @@
|
||||
(li "Explorer components + translation panels + routing")
|
||||
(li "Test with " (code "signals.sx"))))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white uppercase" "Inc 2")
|
||||
(span :class "font-semibold text-stone-800" "Bridge + Runtime (Ring 3-4)"))
|
||||
(ul :class "list-disc pl-5 text-sm text-stone-600 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white uppercase") "Inc 2")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Bridge + Runtime (Ring 3-4)"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-sm text-stone-600 space-y-1")
|
||||
(li "Cross-reference index: function→slug mapping across all spec files")
|
||||
(li "Platform dependency detection (ref not in index = platform primitive)")
|
||||
(li "Test file parsing: " (code "defsuite") "/" (code "deftest") " structure extraction")
|
||||
@@ -185,11 +185,11 @@
|
||||
(li "Proof generation via " (code "prove-translate"))
|
||||
(li "Property matching from " (code "sx-properties"))))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Inc 3")
|
||||
(span :class "font-semibold text-stone-800" "Examples + Polish (Ring 5)"))
|
||||
(ul :class "list-disc pl-5 text-sm text-stone-600 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Inc 3")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Examples + Polish (Ring 5)"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-sm text-stone-600 space-y-1")
|
||||
(li "Example extraction from comments + test assertions")
|
||||
(li "Curated examples for key functions")
|
||||
(li "Stats bar with aggregate counts")
|
||||
@@ -203,7 +203,7 @@
|
||||
|
||||
(~docs/section :title "The Strange Loop" :id "strange-loop"
|
||||
(p "When you view " (code "/language/specs/explore/eval") ", what happens is this:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li "The SX evaluator — bootstrapped from " (code "eval.sx") " — evaluates the page definition.")
|
||||
(li "The page calls " (code "spec-explorer-data(\"eval\")") ", which parses " (code "eval.sx") " using " (code "parse_all()") " — itself bootstrapped from " (code "parser.sx") ".")
|
||||
(li "The parsed AST is fed to " (code "PyEmitter") " and " (code "js-emit-define") " — the same bootstrappers that produced the running evaluator.")
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/status/plan-status-content ()
|
||||
(~docs/page :title "Plan Status"
|
||||
|
||||
(p :class "text-lg text-stone-600 mb-6"
|
||||
(p (~tw :tokens "text-lg text-stone-600 mb-6")
|
||||
"Audit of all plans across the SX language and Rose Ash platform. Last updated March 2026.")
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -14,61 +14,61 @@
|
||||
|
||||
(~docs/section :title "Completed" :id "completed"
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(span :class "font-semibold text-stone-800" "Split Cart into Microservices"))
|
||||
(p :class "text-sm text-stone-600" "Cart decomposed into 4 services: relations (internal, owns ContainerRelation), likes (internal, unified generic likes), orders (public, owns Order/OrderItem + SumUp checkout), and cart (thin CartItem CRUD). All three new services deployed with dedicated databases."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Split Cart into Microservices"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Cart decomposed into 4 services: relations (internal, owns ContainerRelation), likes (internal, unified generic likes), orders (public, owns Order/OrderItem + SumUp checkout), and cart (thin CartItem CRUD). All three new services deployed with dedicated databases."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(span :class "font-semibold text-stone-800" "Ticket Purchase Through Cart"))
|
||||
(p :class "text-sm text-stone-600" "Tickets flow through the cart like products: state=pending in cart, reserved at checkout, confirmed on payment. TicketDTO, CartSummaryDTO with ticket_count/ticket_total, CalendarService protocol methods all implemented."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Ticket Purchase Through Cart"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Tickets flow through the cart like products: state=pending in cart, reserved at checkout, confirmed on payment. TicketDTO, CartSummaryDTO with ticket_count/ticket_total, CalendarService protocol methods all implemented."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(span :class "font-semibold text-stone-800" "Ticket UX Improvements"))
|
||||
(p :class "text-sm text-stone-600" "+/- quantity buttons on entry pages and cart. Tickets grouped by event in cart display. Adjust quantity route, sold/basket counts, matching product card UX pattern."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Ticket UX Improvements"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "+/- quantity buttons on entry pages and cart. Tickets grouped by event in cart display. Adjust quantity route, sold/basket counts, matching product card UX pattern."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 1: Dependency Analysis"))
|
||||
(p :class "text-sm text-stone-600" "Per-page component bundles via deps.sx. Transitive closure, scan-refs, components-needed, page-css-classes. 15 tests, bootstrapped to both hosts."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 1: Dependency Analysis"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Per-page component bundles via deps.sx. Transitive closure, scan-refs, components-needed, page-css-classes. 15 tests, bootstrapped to both hosts."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 2: IO Detection"))
|
||||
(p :class "text-sm text-stone-600" "Automatic IO classification. scan-io-refs, transitive-io-refs, compute-all-io-refs. Server expands IO components, serializes pure ones for client."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 2: IO Detection"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Automatic IO classification. scan-io-refs, transitive-io-refs, compute-all-io-refs. Server expands IO components, serializes pure ones for client."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing"))
|
||||
(p :class "text-sm text-stone-600" "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 3: Client-Side Routing"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
|
||||
(p :class "text-sm text-stone-600" "Server evaluates :data expressions, serializes as SX wire format. Client fetches pre-evaluated data, caches with 30s TTL, renders :content locally. 30 unit tests."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 4: Client Async & IO Bridge"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Server evaluates :data expressions, serializes as SX wire format. Client fetches pre-evaluated data, caches with 30s TTL, renders :content locally. 30 unit tests."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 5: Client IO Proxy"))
|
||||
(p :class "text-sm text-stone-600" "IO primitives (highlight, asset-url, etc.) proxied to server via registerIoDeps(). Async DOM renderer handles promises through the render tree. Components with IO deps render client-side via server round-trips — no continuations needed."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 5: Client IO Proxy"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "IO primitives (highlight, asset-url, etc.) proxied to server via registerIoDeps(). Async DOM renderer handles promises through the render tree. Components with IO deps render client-side via server round-trips — no continuations needed."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(language.(test))" :class "font-semibold text-green-800 underline" "Modular Test Architecture"))
|
||||
(p :class "text-sm text-stone-600" "Per-module test specs (eval, parser, router, render) with 161 tests. Three runners: Python, Node.js, browser. 5 platform functions, everything else pure SX."))))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(language.(test))" (~tw :tokens "font-semibold text-green-800 underline") "Modular Test Architecture"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Per-module test specs (eval, parser, router, render) with 161 tests. Three runners: Python, Node.js, browser. 5 platform functions, everything else pure SX."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; In Progress / Partial
|
||||
@@ -76,13 +76,13 @@
|
||||
|
||||
(~docs/section :title "In Progress" :id "in-progress"
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial")
|
||||
(a :href "/sx/(etc.(plan.fragment-protocol))" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
|
||||
(p :class "text-sm text-stone-600" "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers."))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase") "Partial")
|
||||
(a :href "/sx/(etc.(plan.fragment-protocol))" (~tw :tokens "font-semibold text-amber-900 underline") "Fragment Protocol"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Not Started
|
||||
@@ -90,56 +90,56 @@
|
||||
|
||||
(~docs/section :title "Not Started" :id "not-started"
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-700 text-white uppercase" "Done")
|
||||
(a :href "/sx/(etc.(plan.reader-macros))" :class "font-semibold text-stone-800 underline" "Reader Macros"))
|
||||
(p :class "text-sm text-stone-600" "# dispatch in parser.sx spec, Python parser.py, hand-written sx.js. Three built-ins (#;, #|...|, #') plus extensible #name dispatch. #z3 demo translates define-primitive to SMT-LIB.")
|
||||
(p :class "text-sm text-stone-500 mt-1" "48 parser tests (SX + Python), all passing. Rebootstrapped to JS and Python."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-700 text-white uppercase") "Done")
|
||||
(a :href "/sx/(etc.(plan.reader-macros))" (~tw :tokens "font-semibold text-stone-800 underline") "Reader Macros"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "# dispatch in parser.sx spec, Python parser.py, hand-written sx.js. Three built-ins (#;, #|...|, #') plus extensible #name dispatch. #z3 demo translates define-primitive to SMT-LIB.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") "48 parser tests (SX + Python), all passing. Rebootstrapped to JS and Python."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
|
||||
(a :href "/sx/(etc.(plan.sx-activity))" :class "font-semibold text-stone-800 underline" "SX-Activity"))
|
||||
(p :class "text-sm text-stone-600" "Federated SX over ActivityPub — 6 phases from SX wire format for activities to the evaluable web on IPFS. Existing AP infrastructure provides the foundation but no SX-specific federation code exists.")
|
||||
(p :class "text-sm text-stone-500 mt-1" "Remaining: shared/sx/activity.py (SX<->JSON-LD), shared/sx/ipfs.py, shared/sx/ref/ipfs-resolve.sx, shared/sx/registry.py, shared/sx/anchor.py."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase") "Not Started")
|
||||
(a :href "/sx/(etc.(plan.sx-activity))" (~tw :tokens "font-semibold text-stone-800 underline") "SX-Activity"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Federated SX over ActivityPub — 6 phases from SX wire format for activities to the evaluable web on IPFS. Existing AP infrastructure provides the foundation but no SX-specific federation code exists.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") "Remaining: shared/sx/activity.py (SX<->JSON-LD), shared/sx/ipfs.py, shared/sx/ref/ipfs-resolve.sx, shared/sx/registry.py, shared/sx/anchor.py."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
|
||||
(a :href "/sx/(etc.(plan.glue-decoupling))" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue"))
|
||||
(p :class "text-sm text-stone-600" "Eliminate all cross-app model imports by routing through a glue service layer. No glue/ directory exists. Apps are currently decoupled via HTTP interfaces and DTOs instead.")
|
||||
(p :class "text-sm text-stone-500 mt-1" "Remaining: glue/services/ for pages, page_config, calendars, marketplaces, cart_items, products, post_associations. 25+ cross-app imports to eliminate."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase") "Not Started")
|
||||
(a :href "/sx/(etc.(plan.glue-decoupling))" (~tw :tokens "font-semibold text-stone-800 underline") "Cross-App Decoupling via Glue"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Eliminate all cross-app model imports by routing through a glue service layer. No glue/ directory exists. Apps are currently decoupled via HTTP interfaces and DTOs instead.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") "Remaining: glue/services/ for pages, page_config, calendars, marketplaces, cart_items, products, post_associations. 25+ cross-app imports to eliminate."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
|
||||
(a :href "/sx/(etc.(plan.social-sharing))" :class "font-semibold text-stone-800 underline" "Social Network Sharing"))
|
||||
(p :class "text-sm text-stone-600" "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon via the account service. No models, blueprints, or platform clients created.")
|
||||
(p :class "text-sm text-stone-500 mt-1" "Remaining: SocialConnection model, social_crypto.py, platform OAuth clients (6), account/bp/social/ blueprint, share button fragment."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase") "Not Started")
|
||||
(a :href "/sx/(etc.(plan.social-sharing))" (~tw :tokens "font-semibold text-stone-800 underline") "Social Network Sharing"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon via the account service. No models, blueprints, or platform clients created.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") "Remaining: SocialConnection model, social_crypto.py, platform OAuth clients (6), account/bp/social/ blueprint, share button fragment."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Streaming & Suspense"))
|
||||
(p :class "text-sm text-stone-600" "Server streams partially-evaluated SX as IO resolves. ~shared:pages/suspense component renders fallbacks, inline resolution scripts fill in content. Concurrent IO via asyncio, chunked transfer encoding.")
|
||||
(p :class "text-sm text-stone-500 mt-1" "Demo: " (a :href "/sx/(geography.(isomorphism.streaming))" "/sx/(geography.(isomorphism.streaming))")))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-stone-800 underline") "Isomorphic Phase 6: Streaming & Suspense"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Server streams partially-evaluated SX as IO resolves. ~shared:pages/suspense component renders fallbacks, inline resolution scripts fill in content. Concurrent IO via asyncio, chunked transfer encoding.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") "Demo: " (a :href "/sx/(geography.(isomorphism.streaming))" "/sx/(geography.(isomorphism.streaming))")))
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 7: Full Isomorphism"))
|
||||
(p :class "text-sm text-stone-600" "Affinity annotations, render plans, optimistic data updates, offline mutation queue, isomorphic testing harness, universal page descriptor.")
|
||||
(p :class "text-sm text-stone-500 mt-1" "All 6 sub-phases (7a–7f) complete."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-stone-800 underline") "Isomorphic Phase 7: Full Isomorphism"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Affinity annotations, render plans, optimistic data updates, offline mutation queue, isomorphic testing harness, universal page descriptor.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") "All 6 sub-phases (7a–7f) complete."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-1"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
|
||||
(a :href "/sx/(etc.(plan.spec-explorer))" :class "font-semibold text-stone-800 underline" "Spec Explorer — The Fifth Ring"))
|
||||
(p :class "text-sm text-stone-600" "SX exploring itself. Per-function cards showing all five rings: SX source (nucleus), Python/JS/Z3 translations (bootstrapper), platform dependencies (bridge), tests and proofs (runtime), and usage examples (application). The documentation is the thing documenting itself.")
|
||||
(p :class "text-sm text-stone-500 mt-1" "Prerequisite complete: 180+ functions annotated with :effects across all 14 spec files. Three increments: core + translations, bridge + runtime, examples + polish."))))))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase") "Not Started")
|
||||
(a :href "/sx/(etc.(plan.spec-explorer))" (~tw :tokens "font-semibold text-stone-800 underline") "Spec Explorer — The Fifth Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "SX exploring itself. Per-function cards showing all five rings: SX source (nucleus), Python/JS/Z3 translations (bootstrapper), platform dependencies (bridge), tests and proofs (runtime), and usage examples (application). The documentation is the thing documenting itself.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") "Prerequisite complete: 180+ functions annotated with :effects across all 14 spec files. Three increments: core + translations, bridge + runtime, examples + polish."))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Fragment Protocol
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
(p "SX-Activity wires these together into a new web. Everything — content, UI components, markdown parsers, syntax highlighters, validation logic, media, processing pipelines — is the same executable format, stored on a content-addressed network, running within each participant's own security context. " (strong "The wire format is the programming language is the component system is the package manager.")))
|
||||
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(ul :class "space-y-2 text-stone-700 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-700 list-disc pl-5")
|
||||
(li (strong "ActivityPub: ") "Full implementation — virtual per-app actors, HTTP signatures, webfinger, inbox/outbox, followers/following, delivery with idempotent logging.")
|
||||
(li (strong "Activity bus: ") "Unified event bus with NOTIFY/LISTEN wakeup, at-least-once delivery, handler registry keyed by (activity_type, object_type).")
|
||||
(li (strong "Content addressing: ") "artdag nodes use SHA3-256 hashing. Cache layer tracks IPFS CIDs. IPFSPin model tracks pinned content across domains.")
|
||||
@@ -25,9 +25,9 @@
|
||||
|
||||
(~docs/section :title "Phase 1: SX Wire Format for Activities" :id "phase-1"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Activities expressed as s-expressions instead of JSON-LD. Same semantics as ActivityStreams, but compact, parseable, and directly evaluable. Dual-format support for backward compatibility with existing AP servers."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Activities expressed as s-expressions instead of JSON-LD. Same semantics as ActivityStreams, but compact, parseable, and directly evaluable. Dual-format support for backward compatibility with existing AP servers."))
|
||||
|
||||
(~docs/subsection :title "The Problem"
|
||||
(p "JSON-LD activities are verbose and require context resolution:")
|
||||
@@ -40,26 +40,26 @@
|
||||
(p "The content isn't a string containing markup — it " (em "is") " markup. The receiving server can evaluate it directly. The Note's content is a renderable SX expression."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Activity vocabulary in SX")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Activity vocabulary in SX")
|
||||
(p "Map ActivityStreams types to SX symbols. Activities are lists with a type head and keyword properties:")
|
||||
(~docs/code :src (highlight ";; Core activity types\n(Create :actor ... :object ...)\n(Update :actor ... :object ...)\n(Delete :actor ... :object ...)\n(Follow :actor ... :object ...)\n(Like :actor ... :object ...)\n(Announce :actor ... :object ...)\n\n;; Object types\n(Note :content ... :attributed-to ...)\n(Article :name ... :content ... :summary ...)\n(Image :url ... :media-type ... :cid ...)\n(Collection :total-items ... :items ...)" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Content negotiation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Content negotiation")
|
||||
(p "Inbox accepts both formats. " (code "Accept: text/sx") " gets SX, " (code "Accept: application/activity+json") " gets JSON-LD. Outbox serves both. SX-native servers negotiate SX; legacy Mastodon/Pleroma servers get JSON-LD as today."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Bidirectional translation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Bidirectional translation")
|
||||
(p "Lossless mapping between JSON-LD and SX activity formats. Translate at the boundary — internal processing always uses SX. The existing " (code "APActivity") " model gains an " (code "sx_source") " column storing the canonical SX representation."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. HTTP Signatures over SX")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. HTTP Signatures over SX")
|
||||
(p "Same RSA signature mechanism. Digest header computed over the SX body. Existing keypair infrastructure unchanged."))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Round-trip: SX → JSON-LD → SX produces identical output")
|
||||
(li "Legacy AP servers receive valid JSON-LD (Mastodon can display the post)")
|
||||
(li "SX-native servers receive evaluable SX (client can render directly)")
|
||||
@@ -71,32 +71,32 @@
|
||||
|
||||
(~docs/section :title "Phase 2: Content-Addressed Components on IPFS" :id "phase-2"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Component definitions stored on IPFS, referenced by CID. Any server can publish components. Any browser can fetch them. No central registry — content addressing IS the registry."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Component definitions stored on IPFS, referenced by CID. Any server can publish components. Any browser can fetch them. No central registry — content addressing IS the registry."))
|
||||
|
||||
(~docs/subsection :title "The Insight"
|
||||
(p "SX components are pure functions — they take data and return markup. They can't do IO (boundary enforcement guarantees this). That means they're " (strong "safe to load from any source") ". And if they're content-addressed, the CID " (em "is") " the identity — you don't need to trust the source, you just verify the hash.")
|
||||
(p "Currently, component definitions travel with each page via " (code "<script type=\"text/sx\" data-components>") ". Each server bundles its own. With IPFS, components become shared infrastructure — define once, use everywhere."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Component CID computation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Component CID computation")
|
||||
(p "Each " (code "defcomp") " definition gets a content address:")
|
||||
(~docs/code :src (highlight ";; Component source\n(defcomp ~plans/sx-activity/card (&key title &rest children)\n (div :class \"border rounded p-4\"\n (h2 title) children))\n\n;; CID = SHA3-256 of canonical serialized form\n;; → bafy...abc123\n;; Stored: ipfs://bafy...abc123 → component source" "lisp"))
|
||||
(p "Canonical form: normalize whitespace, sort keyword args alphabetically, strip comments. Same component always produces same CID regardless of formatting."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Component references in activities")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Component references in activities")
|
||||
(p "Activities declare which components they need by CID:")
|
||||
(~docs/code :src (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :requires (list\n \"bafy...card\" ;; ~plans/sx-activity/card component\n \"bafy...avatar\") ;; ~shared:misc/avatar component\n :object (Note\n :content (~plans/sx-activity/card :title \"Hello\"\n (~shared:misc/avatar :src \"ipfs://bafy...photo\")\n (p \"This renders with the card component.\"))))" "lisp"))
|
||||
(p "The receiving browser fetches missing components from IPFS, verifies CIDs, registers them, then renders the content."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. IPFS component resolution")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. IPFS component resolution")
|
||||
(p "Client-side resolution pipeline:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Check localStorage cache (keyed by CID — cache-forever semantics)")
|
||||
(li "Check local IPFS node if running (ipfs cat)")
|
||||
(li "Fetch from IPFS gateway (configurable, default: dweb.link)")
|
||||
@@ -104,12 +104,12 @@
|
||||
(li "Parse, register in component env, render")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Component publication")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Component publication")
|
||||
(p "Server-side: on component registration, compute CID and pin to IPFS. Track in " (code "IPFSPin") " model (already exists). Publish component availability via AP outbox:")
|
||||
(~docs/code :src (highlight "(Create\n :actor \"https://rose-ash.com/apps/market\"\n :object (SxComponent\n :name \"~product-card\"\n :cid \"bafy...productcard\"\n :version \"1.0.0\"\n :deps (list \"bafy...card\" \"bafy...price-tag\")))" "lisp")))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Component pinned to IPFS → fetchable via gateway → CID verifies")
|
||||
(li "Browser renders federated post using IPFS-fetched components")
|
||||
(li "Modified component → different CID → old content still renders with old version")
|
||||
@@ -121,43 +121,43 @@
|
||||
|
||||
(~docs/section :title "Phase 3: Federated Media & Content Store" :id "phase-3"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "All media (images, video, audio, DAG outputs) stored content-addressed on IPFS. Activities reference media by CID. No hotlinking, no broken links, no dependence on the origin server staying online."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "All media (images, video, audio, DAG outputs) stored content-addressed on IPFS. Activities reference media by CID. No hotlinking, no broken links, no dependence on the origin server staying online."))
|
||||
|
||||
(~docs/subsection :title "Current Mechanism"
|
||||
(p "artdag already content-addresses all DAG outputs with SHA3-256 and tracks IPFS CIDs in " (code "IPFSPin") ". But media in the web platform (blog images, product photos, event banners) is stored as regular files on the origin server. Federated posts include " (code "url") " fields pointing to the origin — if the server goes down, the media is gone."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Media CID pipeline")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Media CID pipeline")
|
||||
(p "On upload: hash content → pin to IPFS → store CID in database. Activities reference media by CID alongside URL fallback:")
|
||||
(~docs/code :src (highlight "(Image\n :cid \"bafy...photo123\"\n :url \"https://rose-ash.com/media/photo.jpg\" ;; fallback\n :media-type \"image/jpeg\"\n :width 1200 :height 800)" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. DAG output federation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. DAG output federation")
|
||||
(p "artdag processing results (rendered video, processed images) already have CIDs. Federate them as activities:")
|
||||
(~docs/code :src (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :object (Artwork\n :name \"Sunset Remix\"\n :cid \"bafy...artwork\"\n :dag-cid \"bafy...dag\" ;; full DAG for reproduction\n :media-type \"video/mp4\"\n :sources (list\n (Image :cid \"bafy...src1\" :attribution \"...\")\n (Image :cid \"bafy...src2\" :attribution \"...\"))))" "lisp"))
|
||||
(p "The " (code ":dag-cid") " lets anyone re-execute the processing pipeline. The artwork is both a result and a reproducible recipe."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Shared SX content store")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Shared SX content store")
|
||||
(p "Not just components and media — full page content can be content-addressed. An Article's body is SX, pinned to IPFS:")
|
||||
(~docs/code :src (highlight "(Article\n :name \"Why S-Expressions\"\n :content-cid \"bafy...article-body\" ;; SX source on IPFS\n :requires (list \"bafy...doc-page\" \"bafy...code-block\")\n :summary \"Why SX uses s-expressions instead of HTML.\")" "lisp"))
|
||||
(p "The content outlives the server. Anyone with the CID can fetch, parse, and render the article with its original components."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Progressive resolution")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Progressive resolution")
|
||||
(p "Client resolves content progressively:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Inline content renders immediately")
|
||||
(li "CID-referenced content shows placeholder → fetches from IPFS → renders")
|
||||
(li "Large media uses IPFS streaming (chunked CIDs)")
|
||||
(li "Integrates with Phase 6 of isomorphic plan (streaming/suspense)")))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Origin server offline → content still resolvable via IPFS gateway")
|
||||
(li "DAG CID → re-executing DAG produces identical output")
|
||||
(li "Media CID verifies → tampered content rejected"))))
|
||||
@@ -168,39 +168,39 @@
|
||||
|
||||
(~docs/section :title "Phase 4: Component Registry & Discovery" :id "phase-4"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Federated component discovery. Servers publish component collections. Other servers follow component feeds. Like npm, but federated, content-addressed, and the packages are safe to run (pure functions, no IO)."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Federated component discovery. Servers publish component collections. Other servers follow component feeds. Like npm, but federated, content-addressed, and the packages are safe to run (pure functions, no IO)."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Component collections as AP actors")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Component collections as AP actors")
|
||||
(p "Each server exposes a component registry actor:")
|
||||
(~docs/code :src (highlight "(Service\n :id \"https://rose-ash.com/sx-registry\"\n :type \"SxComponentRegistry\"\n :name \"Rose Ash Components\"\n :outbox \"https://rose-ash.com/sx-registry/outbox\"\n :followers \"https://rose-ash.com/sx-registry/followers\")" "lisp"))
|
||||
(p "Follow the registry to receive component updates. The outbox is a chronological feed of Create/Update/Delete activities for components."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Component metadata")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Component metadata")
|
||||
(~docs/code :src (highlight "(SxComponent\n :name \"~data-table\"\n :cid \"bafy...datatable\"\n :version \"2.1.0\"\n :deps (list \"bafy...sortable\" \"bafy...paginator\")\n :params (list\n (dict :name \"rows\" :type \"list\" :required true)\n (dict :name \"columns\" :type \"list\" :required true)\n (dict :name \"sortable\" :type \"boolean\" :default false))\n :css-atoms (list :border :rounded :p-4 :text-sm)\n :preview-cid \"bafy...screenshot\"\n :license \"MIT\")" "lisp"))
|
||||
(p "Dependencies are transitive CID references. CSS atoms declare which CSSX rules the component needs. Preview CID is a screenshot for registry browsing."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Discovery protocol")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Discovery protocol")
|
||||
(p "Webfinger-style lookup for components by name:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Local registry: check own component env first")
|
||||
(li "Followed registries: check cached feeds from followed registries")
|
||||
(li "Global search: query known registries by component name")
|
||||
(li "CID resolution: if you have a CID, skip discovery — fetch directly from IPFS")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Version resolution")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Version resolution")
|
||||
(p "Components are immutable (CID = identity). \"Updating\" a component publishes a new CID. Activities reference specific CIDs, so old content always renders correctly. The registry tracks version history:")
|
||||
(~docs/code :src (highlight "(Update\n :actor \"https://rose-ash.com/sx-registry\"\n :object (SxComponent\n :name \"~card\"\n :cid \"bafy...card-v2\" ;; new version\n :replaces \"bafy...card-v1\" ;; previous version\n :changelog \"Added subtitle slot\"))" "lisp")))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Follow registry → receive component Create activities → components available locally")
|
||||
(li "Render post using component from foreign registry → works")
|
||||
(li "Old post referencing old CID → still renders correctly with old version"))))
|
||||
@@ -211,19 +211,19 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Bitcoin-Anchored Provenance" :id "phase-5"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Cryptographic proof that content existed at a specific time, authored by a specific actor. Leverages the existing APAnchor/OpenTimestamps infrastructure. Unforgeable, independently verifiable, survives server shutdown."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Cryptographic proof that content existed at a specific time, authored by a specific actor. Leverages the existing APAnchor/OpenTimestamps infrastructure. Unforgeable, independently verifiable, survives server shutdown."))
|
||||
|
||||
(~docs/subsection :title "Current Mechanism"
|
||||
(p "The " (code "APAnchor") " model already batches activities into Merkle trees, stores the tree on IPFS, creates an OpenTimestamps proof, and records the Bitcoin txid. This runs but isn't surfaced to users or integrated with the full activity lifecycle."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Automatic anchoring pipeline")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Automatic anchoring pipeline")
|
||||
(p "Every SX activity gets queued for anchoring. Batch processor runs periodically:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Collect pending activities (content CIDs + actor signatures)")
|
||||
(li "Build Merkle tree of activity hashes")
|
||||
(li "Pin Merkle tree to IPFS → tree CID")
|
||||
@@ -231,25 +231,25 @@
|
||||
(li "When Bitcoin confirmation arrives: store txid, update activities with anchor reference")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Provenance chain in activities")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Provenance chain in activities")
|
||||
(~docs/code :src (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :object (Note :content (p \"Hello\") :cid \"bafy...note\")\n :provenance (Anchor\n :tree-cid \"bafy...merkle-tree\"\n :leaf-index 42\n :ots-cid \"bafy...ots-proof\"\n :btc-txid \"abc123...def\"\n :btc-block 890123\n :anchored-at \"2026-03-06T12:00:00Z\"))" "lisp"))
|
||||
(p "Any party can verify: fetch the OTS proof from IPFS, check the Merkle path from the activity's CID to the tree root, confirm the tree root is committed in the Bitcoin block."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Component provenance")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Component provenance")
|
||||
(p "Components published to the registry also get anchored. This proves authorship and publication time:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "\"This component was published by rose-ash.com at block 890123\"")
|
||||
(li "Prevents backdating — can't claim you published a component before you actually did")
|
||||
(li "License disputes resolvable by checking anchor timestamps")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Verification UI")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Verification UI")
|
||||
(p "Client-side provenance badge on federated content:")
|
||||
(~docs/code :src (highlight "(defcomp ~plans/sx-activity/provenance-badge (&key anchor)\n (when anchor\n (details :class \"inline text-xs text-stone-400\"\n (summary \"✓ Anchored\")\n (dl :class \"mt-1 space-y-1\"\n (dt \"Bitcoin block\") (dd (get anchor \"btc-block\"))\n (dt \"Timestamp\") (dd (get anchor \"anchored-at\"))\n (dt \"Proof\") (dd (a :href (str \"ipfs://\" (get anchor \"ots-cid\"))\n \"OTS proof\"))))))" "lisp")))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Activity anchored → OTS proof fetchable from IPFS → Merkle path validates → txid confirms in Bitcoin")
|
||||
(li "Tampered activity → Merkle proof fails → provenance badge shows ✗")
|
||||
(li "Server goes offline → provenance still verifiable (all proofs on IPFS + Bitcoin)"))))
|
||||
@@ -260,9 +260,9 @@
|
||||
|
||||
(~docs/section :title "Phase 6: The Evaluable Web" :id "phase-6"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What this really is")
|
||||
(p :class "text-violet-800" "Not ActivityPub-with-SX. A new web. One where everything — content, components, parsers, renderers, server logic, client logic — is the same executable format, shared on a content-addressed network, running within each participant's own security context."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What this really is")
|
||||
(p (~tw :tokens "text-violet-800") "Not ActivityPub-with-SX. A new web. One where everything — content, components, parsers, renderers, server logic, client logic — is the same executable format, shared on a content-addressed network, running within each participant's own security context."))
|
||||
|
||||
(~docs/subsection :title "The insight"
|
||||
(p "The web has six layers that don't talk to each other: HTML (structure), CSS (style), JavaScript (behavior), JSON (data interchange), server frameworks (backend logic), and build tools (compilation). Each has its own syntax, its own semantics, its own ecosystem. Moving data between them requires serialization, deserialization, template languages, API contracts, type coercion, and an endless parade of glue code.")
|
||||
@@ -270,31 +270,31 @@
|
||||
(p "Once that's true, " (strong "everything becomes shareable.") " Not just UI components — markdown parsers, syntax highlighters, date formatters, validation logic, layout algorithms, color systems, animation curves. Any pure function over data. All content-addressed, all on IPFS, all executable within your own security context."))
|
||||
|
||||
(~docs/subsection :title "What travels on the network"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Content")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Content")
|
||||
(p "Blog posts, product listings, event descriptions, social media posts. Not HTML strings embedded in JSON — live SX expressions that evaluate to rendered UI. The content " (em "is") " the application."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Components")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Components")
|
||||
(p "UI building blocks: cards, tables, forms, navigation, media players. Published to IPFS, referenced by CID. A commerce site publishes " (code "~product-card") ". A blogging platform publishes " (code "~article-layout") ". A social network publishes " (code "~thread-view") ". Anyone can compose them. They're pure functions — safe to load from anywhere."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Parsers and transforms")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Parsers and transforms")
|
||||
(p "A markdown parser is just an SX function: takes a string, returns an SX tree. Publish it to IPFS. Now anyone can use your markdown dialect. Same for: syntax highlighters, BBCode parsers, wiki markup, LaTeX subsets, CSV-to-table converters, JSON-to-SX adapters. " (strong "The parser ecosystem becomes shared infrastructure."))
|
||||
(~docs/code :src (highlight ";; A markdown parser, published to IPFS\n;; CID: bafy...md-parser\n(define parse-markdown\n (fn (source)\n ;; tokenize → build AST → return SX tree\n ;; (parse-markdown \"# Hello\\n**bold**\")\n ;; → (h1 \"Hello\") (p (strong \"bold\"))\n ...))\n\n;; Anyone can use it in their components\n(defcomp ~plans/sx-activity/blog-post (&key markdown-source)\n (div :class \"prose\"\n (parse-markdown markdown-source)))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Server-side and client-side logic")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Server-side and client-side logic")
|
||||
(p "The same SX code runs on either side. A validation function published to IPFS runs server-side in Python for form processing and client-side in JavaScript for instant feedback. A price calculator runs server-side for order totals and client-side for live previews. " (em "The server/client split is a deployment decision, not a language boundary.")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Media and processing pipelines")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Media and processing pipelines")
|
||||
(p "Images, video, audio — all content-addressed on IPFS. But also the " (em "processing pipelines") " that created them. artdag DAGs are SX. Publish a DAG CID alongside the output CID and anyone can verify the provenance, re-render at different resolution, or fork the pipeline for their own work."))))
|
||||
|
||||
(~docs/subsection :title "The security model"
|
||||
(p "This only works because of boundary enforcement. Every piece of SX fetched from the network runs within the receiver's security context:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Pure functions can't do IO. ") "A component from IPFS can produce markup — it cannot read your cookies, make network requests, access localStorage, or call any IO primitive. The boundary spec (boundary.sx) is enforced at registration time. This isn't a policy — it's structural. The evaluator literally doesn't have IO primitives available when running untrusted code.")
|
||||
(li (strong "IO requires explicit grant. ") "Only locally-registered IO primitives (query, frag, current-user) have access to server resources. Fetched components never see them. The host decides what capabilities to grant.")
|
||||
(li (strong "Step limits cap computation. ") "Untrusted code runs with configurable eval step limits. No infinite loops, no resource exhaustion. Exceeding the limit halts evaluation and returns an error node.")
|
||||
@@ -303,56 +303,56 @@
|
||||
(p "This is the opposite of the npm model. npm packages run with full access to your system — a malicious package can exfiltrate secrets, install backdoors, modify the filesystem. SX components are structurally sandboxed. The worst a malicious component can do is render a " (code "(div \"haha got you\")") "."))
|
||||
|
||||
(~docs/subsection :title "What this replaces"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Current web")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SX web")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Current web")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "SX web")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "npm / package registries")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS + component CIDs")
|
||||
(td :class "px-3 py-2 text-stone-600" "Content-addressed, no central authority, structurally safe"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "CDNs for JS/CSS")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS gateways")
|
||||
(td :class "px-3 py-2 text-stone-600" "Permanent, decentralized, self-verifying"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "REST/GraphQL APIs")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX activities over AP")
|
||||
(td :class "px-3 py-2 text-stone-600" "Responses are evaluable, not just data"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "HTML + CSS + JS")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX (one format)")
|
||||
(td :class "px-3 py-2 text-stone-600" "No impedance mismatch, same evaluator everywhere"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Build tools (webpack, vite)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Nothing")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX evaluates directly, no compilation step"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Template languages")
|
||||
(td :class "px-3 py-2 text-stone-700" "Nothing")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX is the template and the language"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JSON-LD federation")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX federation")
|
||||
(td :class "px-3 py-2 text-stone-600" "Wire format is executable, content renders itself"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Trust-based package security")
|
||||
(td :class "px-3 py-2 text-stone-700" "Structural sandboxing")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure functions can't have side effects — not by policy, by construction"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Servers + hosting + DNS + TLS")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS CID")
|
||||
(td :class "px-3 py-2 text-stone-600" "Entire applications are content-addressed, no infrastructure needed"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "npm / package registries")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS + component CIDs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Content-addressed, no central authority, structurally safe"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CDNs for JS/CSS")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS gateways")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Permanent, decentralized, self-verifying"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "REST/GraphQL APIs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX activities over AP")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Responses are evaluable, not just data"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "HTML + CSS + JS")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX (one format)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "No impedance mismatch, same evaluator everywhere"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Build tools (webpack, vite)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Nothing")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX evaluates directly, no compilation step"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Template languages")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Nothing")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX is the template and the language"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JSON-LD federation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX federation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Wire format is executable, content renders itself"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Trust-based package security")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Structural sandboxing")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure functions can't have side effects — not by policy, by construction"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Servers + hosting + DNS + TLS")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS CID")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Entire applications are content-addressed, no infrastructure needed"))))))
|
||||
|
||||
(~docs/subsection :title "Serverless applications on IPFS"
|
||||
(p "The logical conclusion: " (strong "entire web applications hosted on IPFS with no server at all."))
|
||||
(p "An SX application is a tree of content-addressed artifacts: a root page definition, component dependencies, media, stylesheets, parsers, transforms. Pin the root CID to IPFS and the application is live. No server, no DNS, no hosting provider, no deployment pipeline. Someone gives you a CID, you paste it into an SX-aware browser, and the application runs.")
|
||||
(~docs/code :src (highlight ";; An entire blog — one CID\n;; ipfs://bafy...my-blog\n(defpage blog-home\n :path \"/\"\n :requires (list\n \"bafy...article-layout\" ;; layout component\n \"bafy...md-parser\" ;; markdown parser\n \"bafy...syntax-highlight\" ;; code highlighting\n \"bafy...nav-component\") ;; navigation\n :content\n (~article-layout\n :title \"My Blog\"\n :nav (~nav-component\n :items (list\n (dict :label \"Post 1\" :cid \"bafy...post-1\")\n (dict :label \"Post 2\" :cid \"bafy...post-2\")))\n :body (~markdown-page\n :source-cid \"bafy...homepage-md\")))" "lisp"))
|
||||
(p "What this looks like in practice:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Personal sites: ") "A portfolio or blog is a handful of SX files + media. Pin to IPFS. Share the CID. No hosting costs, no domain renewal, no SSL certificates. The site is permanent.")
|
||||
(li (strong "Documentation: ") "Pin your docs. They can't go offline, can't be censored, can't be altered after publication (provenance proves it). Anyone can mirror them by pinning the same CID.")
|
||||
(li (strong "Collaborative applications: ") "Multiple authors contribute pages and components. Each publishes their CIDs. A root manifest composes them. Update the manifest CID to add content — old CIDs remain valid forever.")
|
||||
@@ -363,7 +363,7 @@
|
||||
|
||||
(~docs/subsection :title "The end state"
|
||||
(p "A browser with an SX evaluator and an IPFS gateway is a complete web platform. Given a CID — for a page, a post, an application — it can:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Fetch the content from IPFS")
|
||||
(li "Resolve component dependencies (also from IPFS)")
|
||||
(li "Resolve media (also from IPFS)")
|
||||
@@ -381,21 +381,21 @@
|
||||
(~docs/section :title "Cross-Cutting Concerns" :id "cross-cutting"
|
||||
|
||||
(~docs/subsection :title "Security"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Boundary enforcement is the foundation. ") "IPFS-fetched components are parsed and registered like any other component. SX_BOUNDARY_STRICT ensures they can't call IO primitives. A malicious component can produce ugly markup but can't exfiltrate data or make network requests.")
|
||||
(li (strong "CID verification: ") "Content fetched from IPFS is hashed and compared to the expected CID before use. Tampered content is rejected.")
|
||||
(li (strong "Signature chain: ") "Actor signatures (RSA/HTTP Signatures) prove authorship. Bitcoin anchors prove timing. Together they establish non-repudiable provenance.")
|
||||
(li (strong "Resource limits: ") "Evaluation of untrusted components runs with step limits (max eval steps, max recursion depth). Infinite loops are caught and terminated.")))
|
||||
|
||||
(~docs/subsection :title "Backward Compatibility"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Content negotiation ensures legacy AP servers always receive valid JSON-LD")
|
||||
(li "SX-Activity is strictly opt-in — servers that don't understand it get standard AP")
|
||||
(li "Existing internal activity bus unchanged — SX format is for federation, not internal events")
|
||||
(li "URL fallbacks on all media references — CID is preferred, URL is fallback")))
|
||||
|
||||
(~docs/subsection :title "Performance"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Component CIDs cached in localStorage forever (content-addressed = immutable)")
|
||||
(li "IPFS gateway responses cached with long TTL (content can't change)")
|
||||
(li "Local IPFS node (if present) eliminates gateway latency")
|
||||
@@ -403,7 +403,7 @@
|
||||
|
||||
(~docs/subsection :title "Integration with Isomorphic Architecture"
|
||||
(p "SX-Activity builds on the isomorphic architecture plan:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Phase 1 (component distribution) → IPFS replaces per-server bundles")
|
||||
(li "Phase 2 (IO detection) → pure components safe for IPFS publication")
|
||||
(li "Phase 3 (client routing) → client can resolve federated content without server")
|
||||
@@ -414,57 +414,57 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Critical Files" :id "critical-files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Role")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phases")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phases")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/activitypub.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "AP blueprint — add SX content negotiation")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/events/bus.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Activity bus — add sx_source column, SX serialization")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/activity.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX ↔ JSON-LD bidirectional translation (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ipfs.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Component CID computation, IPFS pinning (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/ipfs-resolve.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client-side IPFS resolution spec (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/models/federation.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFSPin, APAnchor models — extend for components")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3, 5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/registry.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Component registry actor, discovery protocol (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/anchor.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Anchoring pipeline — wire to activity lifecycle (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boot.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client boot — IPFS component loading")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 6"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/events/handlers/ap_delivery_handler.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Federation delivery — SX format support")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "artdag/core/artdag/cache.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Content addressing — shared with component CIDs")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3"))))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/activitypub.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "AP blueprint — add SX content negotiation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/events/bus.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Activity bus — add sx_source column, SX serialization")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/activity.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX ↔ JSON-LD bidirectional translation (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ipfs.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component CID computation, IPFS pinning (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/ipfs-resolve.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client-side IPFS resolution spec (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/models/federation.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFSPin, APAnchor models — extend for components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3, 5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/registry.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component registry actor, discovery protocol (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/anchor.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Anchoring pipeline — wire to activity lifecycle (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boot.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client boot — IPFS component loading")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 6"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/events/handlers/ap_delivery_handler.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Federation delivery — SX format support")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "artdag/core/artdag/cache.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Content addressing — shared with component CIDs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3"))))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Content-Addressed Components
|
||||
|
||||
@@ -5,127 +5,127 @@
|
||||
(defcomp ~plans/sx-ci/plan-sx-ci-content ()
|
||||
(~docs/page :title "SX CI Pipeline"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Build, test, and deploy Rose Ash using the same language the application is written in.")
|
||||
|
||||
(~docs/section :title "Context" :id "context"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Rose Ash currently uses shell scripts for CI: " (code "deploy.sh") " auto-detects changed services via git diff, builds Docker images, pushes to the registry, and restarts Swarm services. " (code "dev.sh") " starts the dev environment and runs tests. These work, but they are opaque imperative scripts with no reuse, no composition, and no relationship to SX.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The CI pipeline is the last piece of infrastructure not expressed in s-expressions. Fixing that completes the \"one representation for everything\" claim — the same language that defines the spec, the components, the pages, the essays, and the deployment config also defines the build pipeline."))
|
||||
|
||||
(~docs/section :title "Design" :id "design"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Pipeline definitions are " (code ".sx") " files. A minimal Python CLI runner evaluates them using " (code "sx_ref.py") ". CI-specific IO primitives (shell execution, Docker, git) are boundary-declared and only available to the pipeline runner — never to web components.")
|
||||
(~docs/code :src (highlight ";; pipeline/deploy.sx\n(let ((targets (if (= (length ARGS) 0)\n (~plans/sx-ci/detect-changed :base \"HEAD~1\")\n (filter (fn (svc) (some (fn (a) (= a (get svc \"name\"))) ARGS))\n services))))\n (when (= (length targets) 0)\n (log-step \"No changes detected\")\n (exit 0))\n\n (log-step (str \"Deploying: \" (join \" \" (map (fn (s) (get s \"name\")) targets))))\n\n ;; Tests first\n (~unit-tests)\n (~sx-spec-tests)\n\n ;; Build, push, restart\n (for-each (fn (svc) (~plans/sx-ci/build-service :service svc)) targets)\n (for-each (fn (svc) (~restart-service :service svc)) targets)\n\n (log-step \"Deploy complete\"))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Pipeline steps are components. " (code "~unit-tests") ", " (code "~plans/sx-ci/build-service") ", " (code "~plans/sx-ci/detect-changed") " are " (code "defcomp") " definitions that compose by nesting — the same mechanism used for page layouts, navigation, and every other piece of the system."))
|
||||
|
||||
(~docs/section :title "CI Primitives" :id "primitives"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"New IO primitives declared in " (code "boundary.sx") ", implemented only in the CI runner context:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Primitive")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Signature")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Primitive")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Signature")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shell-run")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(command) -> dict")
|
||||
(td :class "px-3 py-2 text-stone-700" "Execute shell command, return " (code "{:exit N :stdout \"...\" :stderr \"...\"}") ""))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shell-run!")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(command) -> dict")
|
||||
(td :class "px-3 py-2 text-stone-700" "Execute shell command, throw on non-zero exit"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-build")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(&key file tag context) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Build Docker image"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-push")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(tag) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Push image to registry"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-restart")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(service) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Restart Swarm service"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "git-diff-files")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(base head) -> list")
|
||||
(td :class "px-3 py-2 text-stone-700" "List changed files between commits"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "git-branch")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "() -> string")
|
||||
(td :class "px-3 py-2 text-stone-700" "Current branch name"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "log-step")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(message) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Formatted pipeline output"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "fail!")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(message) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Abort pipeline with error")))))
|
||||
(p :class "text-stone-600"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shell-run")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(command) -> dict")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Execute shell command, return " (code "{:exit N :stdout \"...\" :stderr \"...\"}") ""))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shell-run!")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(command) -> dict")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Execute shell command, throw on non-zero exit"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "docker-build")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(&key file tag context) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Build Docker image"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "docker-push")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(tag) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Push image to registry"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "docker-restart")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(service) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Restart Swarm service"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "git-diff-files")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(base head) -> list")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "List changed files between commits"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "git-branch")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "() -> string")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Current branch name"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "log-step")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(message) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Formatted pipeline output"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "fail!")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(message) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Abort pipeline with error")))))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The boundary system ensures these primitives are " (em "only") " available in the CI context. Web components cannot call " (code "shell-run!") " — the evaluator will refuse to resolve the symbol, just as it refuses to resolve any other unregistered IO primitive. The sandbox is structural, not a convention."))
|
||||
|
||||
(~docs/section :title "Reusable Steps" :id "steps"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Pipeline steps are components — same " (code "defcomp") " as UI components, same " (code "&key") " params, same composition by nesting:")
|
||||
(~docs/code :src (highlight "(defcomp ~plans/sx-ci/detect-changed (&key base)\n (let ((files (git-diff-files (or base \"HEAD~1\") \"HEAD\")))\n (if (some (fn (f) (starts-with? f \"shared/\")) files)\n services\n (filter (fn (svc)\n (some (fn (f) (starts-with? f (str (get svc \"dir\") \"/\"))) files))\n services))))\n\n(defcomp ~plans/sx-ci/build-service (&key service)\n (let ((name (get service \"name\"))\n (tag (str registry \"/\" name \":latest\")))\n (log-step (str \"Building \" name))\n (docker-build :file (str (get service \"dir\") \"/Dockerfile\") :tag tag :context \".\")\n (docker-push tag)))\n\n(defcomp ~plans/sx-ci/bootstrap-check ()\n (log-step \"Checking bootstrapped files are up to date\")\n (shell-run! \"python shared/sx/ref/bootstrap_js.py\")\n (shell-run! \"python shared/sx/ref/bootstrap_py.py\")\n (let ((diff (shell-run \"git diff --name-only shared/static/scripts/sx-ref.js shared/sx/ref/sx_ref.py\")))\n (when (not (= (get diff \"stdout\") \"\"))\n (fail! \"Bootstrapped files are stale — rebootstrap and commit\"))))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
"Compare this to GitHub Actions YAML, where \"reuse\" means composite actions with " (code "uses:") " references, input/output mappings, shell script blocks inside YAML strings, and a " (a :href "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions" :class "text-violet-600 hover:underline" "100-page syntax reference") ". SX pipeline reuse is function composition. That is all it has ever been."))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Compare this to GitHub Actions YAML, where \"reuse\" means composite actions with " (code "uses:") " references, input/output mappings, shell script blocks inside YAML strings, and a " (a :href "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions" (~tw :tokens "text-violet-600 hover:underline") "100-page syntax reference") ". SX pipeline reuse is function composition. That is all it has ever been."))
|
||||
|
||||
(~docs/section :title "Pipelines" :id "pipelines"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Two primary pipelines, each a single " (code ".sx") " file:")
|
||||
(div :class "space-y-4"
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "pipeline/test.sx")
|
||||
(p :class "text-sm text-stone-600" "Unit tests, SX spec tests (Python + Node), bootstrap staleness check, Tailwind CSS check. Run locally or in CI.")
|
||||
(p :class "text-sm font-mono text-violet-700 mt-1" "python -m shared.sx.ci pipeline/test.sx"))
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "pipeline/deploy.sx")
|
||||
(p :class "text-sm text-stone-600" "Auto-detect changed services (or accept explicit args), run tests, build Docker images, push to registry, restart Swarm services.")
|
||||
(p :class "text-sm font-mono text-violet-700 mt-1" "python -m shared.sx.ci pipeline/deploy.sx blog market"))))
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "pipeline/test.sx")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Unit tests, SX spec tests (Python + Node), bootstrap staleness check, Tailwind CSS check. Run locally or in CI.")
|
||||
(p (~tw :tokens "text-sm font-mono text-violet-700 mt-1") "python -m shared.sx.ci pipeline/test.sx"))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "pipeline/deploy.sx")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Auto-detect changed services (or accept explicit args), run tests, build Docker images, push to registry, restart Swarm services.")
|
||||
(p (~tw :tokens "text-sm font-mono text-violet-700 mt-1") "python -m shared.sx.ci pipeline/deploy.sx blog market"))))
|
||||
|
||||
(~docs/section :title "Why this matters" :id "why"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"CI pipelines are the strongest test case for \"one representation for everything.\" GitHub Actions, GitLab CI, CircleCI — all use YAML. YAML is not a programming language. So every CI system reinvents conditionals (" (code "if:") " expressions evaluated as strings), iteration (" (code "matrix:") " strategies), composition (" (code "uses:") " references with input/output schemas), and error handling (" (code "continue-on-error:") " booleans) — all in a data format that was never designed for any of it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The result is a domain-specific language trapped inside YAML, with worse syntax than any language designed to be one. Every CI pipeline of sufficient complexity becomes a programming task performed in a notation that actively resists programming.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX pipelines use real conditionals, real functions, real composition, and real error handling — because SX is a real language. The pipeline definition and the application code are the same thing. An AI that can generate SX components can generate SX pipelines. A developer who reads SX pages can read SX deploys. The representation is universal."))
|
||||
|
||||
(~docs/section :title "Files" :id "files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ci.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Pipeline runner CLI (~150 lines)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ci_primitives.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "CI IO primitive implementations"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/services.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Service registry (data)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/steps.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Reusable step components"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/test.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Test pipeline"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/deploy.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Deploy pipeline"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boundary.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Add CI primitive declarations"))))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ci.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pipeline runner CLI (~150 lines)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ci_primitives.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CI IO primitive implementations"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pipeline/services.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Service registry (data)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pipeline/steps.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Reusable step components"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pipeline/test.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Test pipeline"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pipeline/deploy.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Deploy pipeline"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boundary.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Add CI primitive declarations"))))))))
|
||||
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"Markdown rendering, and a template engine that's separate from the application logic. "
|
||||
"Every layer speaks a different language.")
|
||||
(p "sx-forge collapses these layers:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "Repository browsing = SX components rendering git tree objects")
|
||||
(li "Issue tracking = SX forms with sx-post, stored as content-addressed SX documents")
|
||||
(li "Pull requests = SX diff viewer + sx-activity for review comments")
|
||||
@@ -29,42 +29,42 @@
|
||||
(li "API = SX wire format (text/sx) alongside JSON for compatibility")))
|
||||
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(div :class "overflow-x-auto mt-4"
|
||||
(table :class "w-full text-sm text-left"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "w-full text-sm text-left")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Layer")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Implementation")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Notes")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Git backend")
|
||||
(td :class "py-2 px-3" "libgit2 or shell-out to git")
|
||||
(td :class "py-2 px-3" "Smart HTTP + SSH protocols. Bare repos on disk."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "UI")
|
||||
(td :class "py-2 px-3" "SX components (defcomp)")
|
||||
(td :class "py-2 px-3" "Tree browser, diff viewer, blame, commit log — all defcomps."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Issues / PRs")
|
||||
(td :class "py-2 px-3" "SX documents on IPFS")
|
||||
(td :class "py-2 px-3" "Content-addressed. Federated via sx-activity."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "CI")
|
||||
(td :class "py-2 px-3" "sx-ci pipelines")
|
||||
(td :class "py-2 px-3" "Push hook triggers pipeline. Results as SX components."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Auth")
|
||||
(td :class "py-2 px-3" "OAuth2 + SX policy macros")
|
||||
(td :class "py-2 px-3" "Permissions are macro-expanded predicates."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Config")
|
||||
(td :class "py-2 px-3" "SX s-expressions")
|
||||
(td :class "py-2 px-3" "forge.sx per-instance. repo.sx per-repo."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Federation")
|
||||
(td :class "py-2 px-3" "sx-activity (ActivityPub)")
|
||||
(td :class "py-2 px-3" "Cross-instance PRs, issues, stars, forks."))))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Layer")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Implementation")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Notes")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Git backend")
|
||||
(td (~tw :tokens "py-2 px-3") "libgit2 or shell-out to git")
|
||||
(td (~tw :tokens "py-2 px-3") "Smart HTTP + SSH protocols. Bare repos on disk."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "UI")
|
||||
(td (~tw :tokens "py-2 px-3") "SX components (defcomp)")
|
||||
(td (~tw :tokens "py-2 px-3") "Tree browser, diff viewer, blame, commit log — all defcomps."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Issues / PRs")
|
||||
(td (~tw :tokens "py-2 px-3") "SX documents on IPFS")
|
||||
(td (~tw :tokens "py-2 px-3") "Content-addressed. Federated via sx-activity."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "CI")
|
||||
(td (~tw :tokens "py-2 px-3") "sx-ci pipelines")
|
||||
(td (~tw :tokens "py-2 px-3") "Push hook triggers pipeline. Results as SX components."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Auth")
|
||||
(td (~tw :tokens "py-2 px-3") "OAuth2 + SX policy macros")
|
||||
(td (~tw :tokens "py-2 px-3") "Permissions are macro-expanded predicates."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Config")
|
||||
(td (~tw :tokens "py-2 px-3") "SX s-expressions")
|
||||
(td (~tw :tokens "py-2 px-3") "forge.sx per-instance. repo.sx per-repo."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Federation")
|
||||
(td (~tw :tokens "py-2 px-3") "sx-activity (ActivityPub)")
|
||||
(td (~tw :tokens "py-2 px-3") "Cross-instance PRs, issues, stars, forks."))))))
|
||||
|
||||
(~docs/section :title "Configuration as SX" :id "config"
|
||||
(p "Instance configuration is an SX file, not YAML or INI:")
|
||||
@@ -112,7 +112,7 @@
|
||||
:lines (get hunk \"lines\")))
|
||||
(get diff \"hunks\")))" "lisp")
|
||||
(p "Because diffs are SX data, macros can transform them:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "Syntax highlighting via the same " (code "highlight") " helper used everywhere")
|
||||
(li "Inline review comments as SX forms (sx-post to comment endpoint)")
|
||||
(li "Suggestion blocks — click to apply a proposed change")
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
(~docs/section :title "Federated Forge" :id "federation"
|
||||
(p "sx-activity enables cross-instance collaboration:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Cross-instance PRs") " — open a PR from your fork on another instance")
|
||||
(li (strong "Federated issues") " — file an issue on a remote repo from your instance")
|
||||
(li (strong "Stars and forks") " — ActivityPub Follow/Like activities")
|
||||
@@ -143,7 +143,7 @@
|
||||
(p "Pages use these directly — no controller layer, no ORM:"))
|
||||
|
||||
(~docs/section :title "Implementation Path" :id "implementation"
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
|
||||
(li (strong "Phase 1: Read-only browser") " — git-tree, git-blob, git-log, git-diff as IO primitives. "
|
||||
"SX components for tree view, blob view, commit log, diff view.")
|
||||
(li (strong "Phase 2: Issues") " — SX forms for create/edit. Content-addressed storage. "
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/sx-host/plan-sx-host-content ()
|
||||
(~docs/page :title "sx-host: Universal Platform Primitives"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Every SX host — browser, server, CLI, embedded — is the same five IO primitives. "
|
||||
"Everything above that floor is SX evaluating SX. HTTP is an SX library. "
|
||||
"SQL is an SX library. TLS is an SX library. The host only touches raw bytes.")
|
||||
@@ -16,15 +16,15 @@
|
||||
|
||||
(~docs/section :title "The Five Primitives" :id "primitives"
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Primitive") (th :class "text-left p-2" "Signature") (th :class "text-left p-2" "What")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Primitive") (th (~tw :tokens "text-left p-2") "Signature") (th (~tw :tokens "text-left p-2") "What")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "read") (td :class "p-2 font-mono text-xs" "(read channel) → bytes") (td :class "p-2" "Bytes in. From a socket, a file handle, stdin, a sensor."))
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "write") (td :class "p-2 font-mono text-xs" "(write channel bytes) → n") (td :class "p-2" "Bytes out. To a socket, a file handle, stdout, an actuator."))
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "store") (td :class "p-2 font-mono text-xs" "(store key bytes) → ok") (td :class "p-2" "Persist bytes by key. Filesystem, KV store, database row, IPFS block."))
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "retrieve") (td :class "p-2 font-mono text-xs" "(retrieve key) → bytes | nil") (td :class "p-2" "Get bytes by key. Same backends."))
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "hash") (td :class "p-2 font-mono text-xs" "(hash bytes) → digest") (td :class "p-2" "Content-address bytes. SHA-256. The trust anchor for everything."))))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "read") (td (~tw :tokens "p-2 font-mono text-xs") "(read channel) → bytes") (td (~tw :tokens "p-2") "Bytes in. From a socket, a file handle, stdin, a sensor."))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "write") (td (~tw :tokens "p-2 font-mono text-xs") "(write channel bytes) → n") (td (~tw :tokens "p-2") "Bytes out. To a socket, a file handle, stdout, an actuator."))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "store") (td (~tw :tokens "p-2 font-mono text-xs") "(store key bytes) → ok") (td (~tw :tokens "p-2") "Persist bytes by key. Filesystem, KV store, database row, IPFS block."))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "retrieve") (td (~tw :tokens "p-2 font-mono text-xs") "(retrieve key) → bytes | nil") (td (~tw :tokens "p-2") "Get bytes by key. Same backends."))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "hash") (td (~tw :tokens "p-2 font-mono text-xs") "(hash bytes) → digest") (td (~tw :tokens "p-2") "Content-address bytes. SHA-256. The trust anchor for everything."))))
|
||||
|
||||
(p "These are irreducible. You cannot implement " (code "read") " in terms of the others. "
|
||||
"You cannot implement " (code "store") " without the host. You cannot implement "
|
||||
@@ -50,23 +50,23 @@
|
||||
|
||||
(p "Each protocol is an SX library that reads and writes bytes through channels.")
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "What") (th :class "text-left p-2" "Primitives Used") (th :class "text-left p-2" "SX Library")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "What") (th (~tw :tokens "text-left p-2") "Primitives Used") (th (~tw :tokens "text-left p-2") "SX Library")))
|
||||
(tbody
|
||||
(tr (td :class "p-2" "TCP connection") (td :class "p-2 font-mono text-xs" "open, read, write, close") (td :class "p-2 font-mono text-xs" "net/tcp.sx"))
|
||||
(tr (td :class "p-2" "TLS handshake") (td :class "p-2 font-mono text-xs" "read, write, hash") (td :class "p-2 font-mono text-xs" "net/tls.sx"))
|
||||
(tr (td :class "p-2" "HTTP server") (td :class "p-2 font-mono text-xs" "listen, read, write") (td :class "p-2 font-mono text-xs" "net/http.sx"))
|
||||
(tr (td :class "p-2" "HTTP client") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "net/http-client.sx"))
|
||||
(tr (td :class "p-2" "Postgres client") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "db/postgres.sx"))
|
||||
(tr (td :class "p-2" "Redis client") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "db/redis.sx"))
|
||||
(tr (td :class "p-2" "IPFS content store") (td :class "p-2 font-mono text-xs" "store, retrieve, hash") (td :class "p-2 font-mono text-xs" "net/ipfs.sx"))
|
||||
(tr (td :class "p-2" "File system") (td :class "p-2 font-mono text-xs" "store, retrieve") (td :class "p-2 font-mono text-xs" "io/fs.sx"))
|
||||
(tr (td :class "p-2" "DNS resolver") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "net/dns.sx"))
|
||||
(tr (td :class "p-2" "WebSocket") (td :class "p-2 font-mono text-xs" "read, write (over HTTP upgrade)") (td :class "p-2 font-mono text-xs" "net/websocket.sx"))
|
||||
(tr (td :class "p-2" "SMTP") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "net/smtp.sx"))
|
||||
(tr (td :class "p-2" "ActivityPub") (td :class "p-2 font-mono text-xs" "HTTP + hash (signatures)") (td :class "p-2 font-mono text-xs" "net/activitypub.sx"))
|
||||
(tr (td :class "p-2" "sx-sync protocol") (td :class "p-2 font-mono text-xs" "read, write (over WS/WebRTC)") (td :class "p-2 font-mono text-xs" "net/sx-sync.sx"))))
|
||||
(tr (td (~tw :tokens "p-2") "TCP connection") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write, close") (td (~tw :tokens "p-2 font-mono text-xs") "net/tcp.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "TLS handshake") (td (~tw :tokens "p-2 font-mono text-xs") "read, write, hash") (td (~tw :tokens "p-2 font-mono text-xs") "net/tls.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "HTTP server") (td (~tw :tokens "p-2 font-mono text-xs") "listen, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "net/http.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "HTTP client") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "net/http-client.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Postgres client") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "db/postgres.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Redis client") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "db/redis.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "IPFS content store") (td (~tw :tokens "p-2 font-mono text-xs") "store, retrieve, hash") (td (~tw :tokens "p-2 font-mono text-xs") "net/ipfs.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "File system") (td (~tw :tokens "p-2 font-mono text-xs") "store, retrieve") (td (~tw :tokens "p-2 font-mono text-xs") "io/fs.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "DNS resolver") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "net/dns.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "WebSocket") (td (~tw :tokens "p-2 font-mono text-xs") "read, write (over HTTP upgrade)") (td (~tw :tokens "p-2 font-mono text-xs") "net/websocket.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "SMTP") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "net/smtp.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "ActivityPub") (td (~tw :tokens "p-2 font-mono text-xs") "HTTP + hash (signatures)") (td (~tw :tokens "p-2 font-mono text-xs") "net/activitypub.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "sx-sync protocol") (td (~tw :tokens "p-2 font-mono text-xs") "read, write (over WS/WebRTC)") (td (~tw :tokens "p-2 font-mono text-xs") "net/sx-sync.sx"))))
|
||||
|
||||
(~docs/code :src (highlight
|
||||
";; HTTP server — pure SX on the five primitives\n(define http-server\n (fn (address handler)\n (listen address (fn (channel)\n (let ((request-bytes (read channel))\n (request (http-parse-request request-bytes))\n (response (handler request))\n (response-bytes (http-serialize-response response)))\n (write channel response-bytes)\n (close channel))))))\n\n;; Your app — SX all the way down\n(http-server \":8080\" (fn (req)\n (let ((page (route (get req :path))))\n (http-response 200 (render-to-html page)))))"
|
||||
@@ -85,45 +85,45 @@
|
||||
|
||||
(p "Each host implements the same 5+3 primitives. The SX layer is identical across all of them.")
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Host") (th :class "text-left p-2" "read/write") (th :class "text-left p-2" "store/retrieve") (th :class "text-left p-2" "hash") (th :class "text-left p-2" "Target")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Host") (th (~tw :tokens "text-left p-2") "read/write") (th (~tw :tokens "text-left p-2") "store/retrieve") (th (~tw :tokens "text-left p-2") "hash") (th (~tw :tokens "text-left p-2") "Target")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "OCaml native")
|
||||
(td :class "p-2 text-xs" "Unix.read/write")
|
||||
(td :class "p-2 text-xs" "file IO / mmap")
|
||||
(td :class "p-2 text-xs" "Digestif SHA256")
|
||||
(td :class "p-2 text-xs" "Production servers"))
|
||||
(tr (td :class "p-2 font-medium" "Rust native")
|
||||
(td :class "p-2 text-xs" "tokio async IO")
|
||||
(td :class "p-2 text-xs" "sled / filesystem")
|
||||
(td :class "p-2 text-xs" "ring SHA256")
|
||||
(td :class "p-2 text-xs" "Edge, embedded"))
|
||||
(tr (td :class "p-2 font-medium" "Node.js")
|
||||
(td :class "p-2 text-xs" "net.Socket")
|
||||
(td :class "p-2 text-xs" "fs / LevelDB")
|
||||
(td :class "p-2 text-xs" "crypto.createHash")
|
||||
(td :class "p-2 text-xs" "Quick deploy, Lambda"))
|
||||
(tr (td :class "p-2 font-medium" "Python/Quart")
|
||||
(td :class "p-2 text-xs" "asyncio streams")
|
||||
(td :class "p-2 text-xs" "aiofiles / SQLAlchemy")
|
||||
(td :class "p-2 text-xs" "hashlib")
|
||||
(td :class "p-2 text-xs" "Current, prototyping"))
|
||||
(tr (td :class "p-2 font-medium" "Browser")
|
||||
(td :class "p-2 text-xs" "WebSocket / WebRTC")
|
||||
(td :class "p-2 text-xs" "IndexedDB / Cache API")
|
||||
(td :class "p-2 text-xs" "SubtleCrypto")
|
||||
(td :class "p-2 text-xs" "Client nodes"))
|
||||
(tr (td :class "p-2 font-medium" "WASM")
|
||||
(td :class "p-2 text-xs" "imported from host")
|
||||
(td :class "p-2 text-xs" "imported from host")
|
||||
(td :class "p-2 text-xs" "compiled in")
|
||||
(td :class "p-2 text-xs" "Universal, portable"))
|
||||
(tr (td :class "p-2 font-medium" "Embedded (Rust)")
|
||||
(td :class "p-2 text-xs" "UART / SPI / I2C")
|
||||
(td :class "p-2 text-xs" "flash / EEPROM")
|
||||
(td :class "p-2 text-xs" "hardware SHA")
|
||||
(td :class "p-2 text-xs" "IoT, sensors"))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "OCaml native")
|
||||
(td (~tw :tokens "p-2 text-xs") "Unix.read/write")
|
||||
(td (~tw :tokens "p-2 text-xs") "file IO / mmap")
|
||||
(td (~tw :tokens "p-2 text-xs") "Digestif SHA256")
|
||||
(td (~tw :tokens "p-2 text-xs") "Production servers"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Rust native")
|
||||
(td (~tw :tokens "p-2 text-xs") "tokio async IO")
|
||||
(td (~tw :tokens "p-2 text-xs") "sled / filesystem")
|
||||
(td (~tw :tokens "p-2 text-xs") "ring SHA256")
|
||||
(td (~tw :tokens "p-2 text-xs") "Edge, embedded"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Node.js")
|
||||
(td (~tw :tokens "p-2 text-xs") "net.Socket")
|
||||
(td (~tw :tokens "p-2 text-xs") "fs / LevelDB")
|
||||
(td (~tw :tokens "p-2 text-xs") "crypto.createHash")
|
||||
(td (~tw :tokens "p-2 text-xs") "Quick deploy, Lambda"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Python/Quart")
|
||||
(td (~tw :tokens "p-2 text-xs") "asyncio streams")
|
||||
(td (~tw :tokens "p-2 text-xs") "aiofiles / SQLAlchemy")
|
||||
(td (~tw :tokens "p-2 text-xs") "hashlib")
|
||||
(td (~tw :tokens "p-2 text-xs") "Current, prototyping"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Browser")
|
||||
(td (~tw :tokens "p-2 text-xs") "WebSocket / WebRTC")
|
||||
(td (~tw :tokens "p-2 text-xs") "IndexedDB / Cache API")
|
||||
(td (~tw :tokens "p-2 text-xs") "SubtleCrypto")
|
||||
(td (~tw :tokens "p-2 text-xs") "Client nodes"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "WASM")
|
||||
(td (~tw :tokens "p-2 text-xs") "imported from host")
|
||||
(td (~tw :tokens "p-2 text-xs") "imported from host")
|
||||
(td (~tw :tokens "p-2 text-xs") "compiled in")
|
||||
(td (~tw :tokens "p-2 text-xs") "Universal, portable"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Embedded (Rust)")
|
||||
(td (~tw :tokens "p-2 text-xs") "UART / SPI / I2C")
|
||||
(td (~tw :tokens "p-2 text-xs") "flash / EEPROM")
|
||||
(td (~tw :tokens "p-2 text-xs") "hardware SHA")
|
||||
(td (~tw :tokens "p-2 text-xs") "IoT, sensors"))))
|
||||
|
||||
(p "An SX app published to sx-pub runs on any of these hosts unchanged. "
|
||||
"The CIDs are the same. The evaluation is deterministic. "
|
||||
@@ -201,7 +201,7 @@
|
||||
"on the five primitives. Replaces Python framework dependencies.")
|
||||
|
||||
(p "This is the big step. Each protocol library is substantial:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "net/http.sx") " — request/response parsing, chunked encoding, keep-alive (~800 LOC)")
|
||||
(li (code "net/tls.sx") " — TLS 1.3 handshake, certificate verification (~1200 LOC)")
|
||||
(li (code "db/postgres.sx") " — wire protocol, query serialization, result parsing (~600 LOC)")
|
||||
@@ -213,7 +213,7 @@
|
||||
(~docs/subsection :title "Step 4: Host Binary"
|
||||
(p "Minimal native binary: SX evaluator + 5+3 IO primitives. Nothing else.")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "OCaml: CEK machine + Unix IO (~2000 LOC, single binary)")
|
||||
(li "Rust: compiled evaluator + tokio IO (~3000 LOC, single binary)")
|
||||
(li "Both: no Python, no Quart, no SQLAlchemy, no pip, no virtualenv"))
|
||||
@@ -236,34 +236,34 @@
|
||||
(p "The five primitives are not web-specific. They're IO-specific. "
|
||||
"Any device that can read bytes, write bytes, and store bytes can host SX.")
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Host") (th :class "text-left p-2" "Channels") (th :class "text-left p-2" "Storage") (th :class "text-left p-2" "Use Case")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Host") (th (~tw :tokens "text-left p-2") "Channels") (th (~tw :tokens "text-left p-2") "Storage") (th (~tw :tokens "text-left p-2") "Use Case")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "CLI")
|
||||
(td :class "p-2 text-xs" "stdin/stdout/stderr, files, pipes")
|
||||
(td :class "p-2 text-xs" "filesystem")
|
||||
(td :class "p-2 text-xs" "Build tools, scripts, automation"))
|
||||
(tr (td :class "p-2 font-medium" "Desktop (Tauri)")
|
||||
(td :class "p-2 text-xs" "IPC to native windows, file dialogs")
|
||||
(td :class "p-2 text-xs" "app data dir")
|
||||
(td :class "p-2 text-xs" "Native apps with SX UI"))
|
||||
(tr (td :class "p-2 font-medium" "Mobile")
|
||||
(td :class "p-2 text-xs" "network + sensor APIs")
|
||||
(td :class "p-2 text-xs" "SQLite / app storage")
|
||||
(td :class "p-2 text-xs" "Apps, games, tools"))
|
||||
(tr (td :class "p-2 font-medium" "Serverless")
|
||||
(td :class "p-2 text-xs" "event payload in, response out")
|
||||
(td :class "p-2 text-xs" "KV store / S3")
|
||||
(td :class "p-2 text-xs" "Lambda, Cloudflare Workers"))
|
||||
(tr (td :class "p-2 font-medium" "Game engine")
|
||||
(td :class "p-2 text-xs" "GPU commands, audio buffers")
|
||||
(td :class "p-2 text-xs" "asset cache")
|
||||
(td :class "p-2 text-xs" "SX game logic + native rendering"))
|
||||
(tr (td :class "p-2 font-medium" "Embedded")
|
||||
(td :class "p-2 text-xs" "UART, SPI, I2C, GPIO")
|
||||
(td :class "p-2 text-xs" "flash / EEPROM")
|
||||
(td :class "p-2 text-xs" "IoT, sensors, controllers"))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "CLI")
|
||||
(td (~tw :tokens "p-2 text-xs") "stdin/stdout/stderr, files, pipes")
|
||||
(td (~tw :tokens "p-2 text-xs") "filesystem")
|
||||
(td (~tw :tokens "p-2 text-xs") "Build tools, scripts, automation"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Desktop (Tauri)")
|
||||
(td (~tw :tokens "p-2 text-xs") "IPC to native windows, file dialogs")
|
||||
(td (~tw :tokens "p-2 text-xs") "app data dir")
|
||||
(td (~tw :tokens "p-2 text-xs") "Native apps with SX UI"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Mobile")
|
||||
(td (~tw :tokens "p-2 text-xs") "network + sensor APIs")
|
||||
(td (~tw :tokens "p-2 text-xs") "SQLite / app storage")
|
||||
(td (~tw :tokens "p-2 text-xs") "Apps, games, tools"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Serverless")
|
||||
(td (~tw :tokens "p-2 text-xs") "event payload in, response out")
|
||||
(td (~tw :tokens "p-2 text-xs") "KV store / S3")
|
||||
(td (~tw :tokens "p-2 text-xs") "Lambda, Cloudflare Workers"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Game engine")
|
||||
(td (~tw :tokens "p-2 text-xs") "GPU commands, audio buffers")
|
||||
(td (~tw :tokens "p-2 text-xs") "asset cache")
|
||||
(td (~tw :tokens "p-2 text-xs") "SX game logic + native rendering"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Embedded")
|
||||
(td (~tw :tokens "p-2 text-xs") "UART, SPI, I2C, GPIO")
|
||||
(td (~tw :tokens "p-2 text-xs") "flash / EEPROM")
|
||||
(td (~tw :tokens "p-2 text-xs") "IoT, sensors, controllers"))))
|
||||
|
||||
(p "Same five primitives. Same SX spec. Same content-addressed components from sx-pub. "
|
||||
"A component that works in the browser works on a server works on a Raspberry Pi — "
|
||||
@@ -276,13 +276,13 @@
|
||||
|
||||
(~docs/section :title "Relationship to Existing Plans" :id "relationships"
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Plan") (th :class "text-left p-2" "Relationship")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Plan") (th (~tw :tokens "text-left p-2") "Relationship")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "sx-web") (td :class "p-2" "sx-host defines what a node IS. sx-web defines how nodes CONNECT."))
|
||||
(tr (td :class "p-2 font-medium" "Mother Language") (td :class "p-2" "The evaluator that runs on each host. sx-host defines the interface it evaluates against."))
|
||||
(tr (td :class "p-2 font-medium" "Rust/WASM Host") (td :class "p-2" "One implementation of the five primitives. Compiles to native or WASM."))
|
||||
(tr (td :class "p-2 font-medium" "Isolated Evaluator") (td :class "p-2" "Core/platform split maps directly: core = evaluator, platform = five primitives."))
|
||||
(tr (td :class "p-2 font-medium" "sx-pub") (td :class "p-2" "Where the SX protocol libraries and app definitions live. Hosts fetch from sx-pub to bootstrap."))
|
||||
(tr (td :class "p-2 font-medium" "Runtime Slicing") (td :class "p-2" "Slicing determines which SX libraries a host needs. Minimal host = evaluator + primitives. Full host = all protocol libraries.")))))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "sx-web") (td (~tw :tokens "p-2") "sx-host defines what a node IS. sx-web defines how nodes CONNECT."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Mother Language") (td (~tw :tokens "p-2") "The evaluator that runs on each host. sx-host defines the interface it evaluates against."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Rust/WASM Host") (td (~tw :tokens "p-2") "One implementation of the five primitives. Compiles to native or WASM."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Isolated Evaluator") (td (~tw :tokens "p-2") "Core/platform split maps directly: core = evaluator, platform = five primitives."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "sx-pub") (td (~tw :tokens "p-2") "Where the SX protocol libraries and app definitions live. Hosts fetch from sx-pub to bootstrap."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Runtime Slicing") (td (~tw :tokens "p-2") "Slicing determines which SX libraries a host needs. Minimal host = evaluator + primitives. Full host = all protocol libraries.")))))))
|
||||
|
||||
@@ -13,43 +13,43 @@
|
||||
(~docs/section :title "The Problem With the Current Web" :id "problem"
|
||||
(p "The modern web stack has accumulated layers of incompatible syntax to express "
|
||||
"what are fundamentally the same things:")
|
||||
(table :class "w-full text-sm border-collapse mb-4"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-4")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Concern")
|
||||
(th :class "text-left py-2 pr-4" "Current Syntax")
|
||||
(th :class "text-left py-2" "Example")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Concern")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Current Syntax")
|
||||
(th (~tw :tokens "text-left py-2") "Example")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Resource path")
|
||||
(td :class "py-2 pr-4" "URL segments")
|
||||
(td :class "py-2 font-mono text-xs" "/users/123/posts"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Query parameters")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "?key=value")
|
||||
(td :class "py-2 font-mono text-xs" "?filter=published&sort=date"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "API queries")
|
||||
(td :class "py-2 pr-4" "GraphQL / REST")
|
||||
(td :class "py-2 font-mono text-xs" "{ posts(filter: \"published\") { title } }"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Network verb")
|
||||
(td :class "py-2 pr-4" "HTTP method")
|
||||
(td :class "py-2 font-mono text-xs" "GET, POST, PUT"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Real-time")
|
||||
(td :class "py-2 pr-4" "WebSocket URL")
|
||||
(td :class "py-2 font-mono text-xs" "wss://site.com/live"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Resource path")
|
||||
(td (~tw :tokens "py-2 pr-4") "URL segments")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "/users/123/posts"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Query parameters")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "?key=value")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "?filter=published&sort=date"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "API queries")
|
||||
(td (~tw :tokens "py-2 pr-4") "GraphQL / REST")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "{ posts(filter: \"published\") { title } }"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Network verb")
|
||||
(td (~tw :tokens "py-2 pr-4") "HTTP method")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "GET, POST, PUT"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Real-time")
|
||||
(td (~tw :tokens "py-2 pr-4") "WebSocket URL")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "wss://site.com/live"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4" "Rendering")
|
||||
(td :class "py-2 pr-4" "HTML + CSS + JS")
|
||||
(td :class "py-2" "Three separate languages"))))
|
||||
(td (~tw :tokens "py-2 pr-4") "Rendering")
|
||||
(td (~tw :tokens "py-2 pr-4") "HTML + CSS + JS")
|
||||
(td (~tw :tokens "py-2") "Three separate languages"))))
|
||||
(p "Each layer invented its own syntax. None of them compose. None of them are executable. "
|
||||
"None of them are data."))
|
||||
|
||||
(~docs/section :title "The SX Approach" :id "approach"
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-6 mb-2" "URLs as S-Expressions")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-6 mb-2") "URLs as S-Expressions")
|
||||
(p "A conventional URL:")
|
||||
(~docs/code :src (highlight "https://site.com/blog/my-post?filter=published&sort=date" "text"))
|
||||
(p "As an SX expression:")
|
||||
@@ -60,13 +60,13 @@
|
||||
(li "Path and parameters collapse into " (strong "one unified nested structure"))
|
||||
(li "No " (code "?") ", no " (code "&") ", no " (code "/") " — just lists"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-6 mb-2" "Dots, Not Spaces")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-6 mb-2") "Dots, Not Spaces")
|
||||
(p "Lisp conventionally uses spaces as separators. In URLs, spaces become " (code "%20")
|
||||
". SX uses dots instead, which are URL-safe and semantically meaningful — a dot between "
|
||||
"two atoms is a " (strong "cons pair") ", the fundamental unit of Lisp structure.")
|
||||
(~docs/code :src (highlight ";; Clean, URL-safe, valid Lisp\n(blog.(filter.published).(sort.date.desc))" "lisp"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-6 mb-2" "Verbs Are Just Atoms")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-6 mb-2") "Verbs Are Just Atoms")
|
||||
(p "HTTP methods are not special syntax — they are simply the first element of the expression:")
|
||||
(~docs/code :src (highlight "(get.site.com.(post.my-first-post)) ; read\n(post.site.com.(submit-post.(title.hello))) ; write\n(ws.site.com.(live-feed)) ; websocket / subscribe" "lisp"))
|
||||
(p "No special protocol prefixes. No " (code "https://") " vs " (code "wss://")
|
||||
@@ -79,7 +79,7 @@
|
||||
(li "Responses are dead data — JSON that must be separately rendered"))
|
||||
(p (strong "Graph-SX") " addresses both.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-6 mb-2" "Queries Are URLs")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-6 mb-2") "Queries Are URLs")
|
||||
(p "Because SX expressions are URLs, every query is a GET request:")
|
||||
(~docs/code :src (highlight ";; This is a URL and a query simultaneously\n(get.site.com.(blog.(filter.(tag.lisp)).(limit.10)))" "lisp"))
|
||||
(ul
|
||||
@@ -88,14 +88,14 @@
|
||||
(li "No POST body required for reads")
|
||||
(li "Browser back button works correctly"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-6 mb-2" "Responses Include Rendering")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-6 mb-2") "Responses Include Rendering")
|
||||
(p "GraphQL returns data. Graph-SX returns " (strong "hypermedia")
|
||||
" — data and its presentation in the same expression:")
|
||||
(~docs/code :src (highlight ";; GraphQL response (dead data)\n{\"title\": \"My Post\", \"body\": \"Hello world\"}\n\n;; Graph-SX response (live hypermedia)\n(article\n (h1 \"My Post\")\n (p \"Hello world\")\n (a (href (get.site.com.(post.next-post))) \"Next\"))" "lisp"))
|
||||
(p "The server returns what the resource " (strong "is") " and how to "
|
||||
(strong "present") " it in one unified structure. There is no separate rendering layer.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-6 mb-2" "Queries Are Transformations")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-6 mb-2") "Queries Are Transformations")
|
||||
(p "Because SX is a full programming language, the query and the transformation "
|
||||
"are the same expression:")
|
||||
(~docs/code :src (highlight ";; Fetch, filter, and transform in one expression\n(map (lambda (p) (title p))\n (filter published?\n (posts (after \"2025\"))))" "lisp"))
|
||||
@@ -127,59 +127,59 @@
|
||||
(p "The site is its own documentation. The source is always one expression away."))
|
||||
|
||||
(~docs/section :title "Comparison" :id "comparison"
|
||||
(table :class "w-full text-sm border-collapse mb-4"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-4")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Feature")
|
||||
(th :class "text-left py-2 pr-4" "REST")
|
||||
(th :class "text-left py-2 pr-4" "GraphQL")
|
||||
(th :class "text-left py-2" "Graph-SX")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Feature")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "REST")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "GraphQL")
|
||||
(th (~tw :tokens "text-left py-2") "Graph-SX")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Reads are GETs")
|
||||
(td :class "py-2 pr-4 text-green-700" "Yes")
|
||||
(td :class "py-2 pr-4 text-red-700" "No (POST)")
|
||||
(td :class "py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "CDN cacheable")
|
||||
(td :class "py-2 pr-4 text-green-700" "Yes")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Nested queries")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 pr-4 text-green-700" "Yes")
|
||||
(td :class "py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Response includes rendering")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Query is a transformation")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Composable across domains")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "One syntax for everything")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 text-green-700" "Yes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Bookmarkable deep links")
|
||||
(td :class "py-2 pr-4 text-green-700" "Yes")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 text-green-700" "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Reads are GETs")
|
||||
(td (~tw :tokens "py-2 pr-4 text-green-700") "Yes")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No (POST)")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "CDN cacheable")
|
||||
(td (~tw :tokens "py-2 pr-4 text-green-700") "Yes")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Nested queries")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-green-700") "Yes")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Response includes rendering")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Query is a transformation")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Composable across domains")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "One syntax for everything")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Bookmarkable deep links")
|
||||
(td (~tw :tokens "py-2 pr-4 text-green-700") "Yes")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4" "Self-hosting / introspectable")
|
||||
(td :class "py-2 pr-4 text-red-700" "No")
|
||||
(td :class "py-2 pr-4 text-stone-500" "Partial")
|
||||
(td :class "py-2 text-green-700" "Yes")))))
|
||||
(td (~tw :tokens "py-2 pr-4") "Self-hosting / introspectable")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-stone-500") "Partial")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes")))))
|
||||
|
||||
(~docs/section :title "Future Direction" :id "future"
|
||||
(p "The logical conclusion of SX is a " (strong "new internet protocol")
|
||||
@@ -187,11 +187,11 @@
|
||||
"and the rendering layer are all unified under one evaluable expression format.")
|
||||
(~docs/code :src (highlight ";; The entire network request — protocol, domain, verb, query, all one expression\n(get.sx.dev.(blog.(filter.(tag.lisp)).(limit.10)))" "lisp"))
|
||||
(p "HTTP becomes one possible implementation of a more general principle:")
|
||||
(blockquote :class "border-l-4 border-violet-300 pl-4 italic text-stone-600 my-4"
|
||||
(blockquote (~tw :tokens "border-l-4 border-violet-300 pl-4 italic text-stone-600 my-4")
|
||||
(p (strong "Evaluate this expression. Return an expression."))))
|
||||
|
||||
(~docs/section :title "Reference Implementation" :id "reference"
|
||||
(p "SX is implemented in SX. The reference implementation is self-hosting and available at:")
|
||||
(~docs/code :src (highlight "(get.sx.dev.(source.evaluator))" "lisp"))
|
||||
(p :class "text-sm text-stone-500 mt-4 italic"
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-4 italic")
|
||||
"This proposal was written in conversation with Claude (Anthropic). The ideas are the author's own."))))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
(~docs/section :title "Why" :id "why"
|
||||
(p "Every proxy config language reinvents the same features badly:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "Nginx: custom DSL with if-is-evil, no real conditionals, include for composition")
|
||||
(li "Caddy: Caddyfile is nice but no functions, no macros, no types")
|
||||
(li "Traefik: labels on Docker containers — stringly-typed, no validation")
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
(~docs/section :title "Dynamic Reconfiguration" :id "dynamic"
|
||||
(p "The proxy evaluates SX — so config can be dynamic:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Hot reload") " — change the SX, proxy re-evaluates. No restart.")
|
||||
(li (strong "API-driven") " — sx-post new routes. The proxy is an SX app with endpoints.")
|
||||
(li (strong "Swarm-aware") " — subscribe to swarm-status changes, "
|
||||
@@ -170,7 +170,7 @@
|
||||
"the same SX service definitions. No YAML-to-Caddyfile-to-Dockerfile translation."))
|
||||
|
||||
(~docs/section :title "Implementation Path" :id "implementation"
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
|
||||
(li (strong "Phase 1: Config compiler") " — SX route definitions compiled to Caddy JSON API. "
|
||||
"Use Caddy as the runtime, SX as the config language.")
|
||||
(li (strong "Phase 2: Middleware macros") " — defmacro for common middleware patterns. "
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
(~docs/section :title "What It Is" :id "what"
|
||||
(p "A federated content publishing protocol built entirely in SX. No JSON. Content pinned to IPFS, referenced by CID. Blockchain-anchored provenance.")
|
||||
(p "One actor — " (code "sx") " — publishes SX specs, platform libraries, components, and documentation as content-addressed packages. Other sx-pub servers can follow, mirror, and refer to content by CID. Human-readable paths map to immutable IPFS addresses.")
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "The key insight")
|
||||
(p :class "text-violet-800" "IPFS is the database. SX is the wire format. defhandlers are the endpoints. The wire format is the programming language is the component system is the package manager.")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The key insight")
|
||||
(p (~tw :tokens "text-violet-800") "IPFS is the database. SX is the wire format. defhandlers are the endpoints. The wire format is the programming language is the component system is the package manager.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Why ActivityPub Failed — and Why LISP Fixes It
|
||||
@@ -51,9 +51,9 @@
|
||||
(p "JSON-LD @context was supposed to provide semantic interoperability. In practice it's a constant source of bugs. Implementations disagree on which contexts to support, which extensions to recognise, how to resolve conflicts. The W3C ActivityStreams context document is a single point of failure that the entire fediverse depends on.")
|
||||
(p "SX needs no context resolution. " (code "(Publish :actor \"...\" :object (SxDocument :cid \"...\"))") " is self-describing. The s-expression " (em "is") " the meaning. Symbols are symbols. Keywords are keywords. No indirection, no remote JSON documents, no semantic web machinery."))
|
||||
|
||||
(div :class "rounded border border-rose-200 bg-rose-50 p-4 mt-6"
|
||||
(p :class "text-rose-900 font-medium" "The fundamental difference")
|
||||
(p :class "text-rose-800" "ActivityPub federates " (em "descriptions") " of content. Consumers must independently figure out how to present them. sx-pub federates " (em "programs") " — self-contained, evaluable, deterministic, content-addressed. The gap between \"data\" and \"application\" disappears because in LISP, they were never separate to begin with.")))
|
||||
(div (~tw :tokens "rounded border border-rose-200 bg-rose-50 p-4 mt-6")
|
||||
(p (~tw :tokens "text-rose-900 font-medium") "The fundamental difference")
|
||||
(p (~tw :tokens "text-rose-800") "ActivityPub federates " (em "descriptions") " of content. Consumers must independently figure out how to present them. sx-pub federates " (em "programs") " — self-contained, evaluable, deterministic, content-addressed. The gap between \"data\" and \"application\" disappears because in LISP, they were never separate to begin with.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Wire Format
|
||||
@@ -89,37 +89,37 @@
|
||||
(~docs/section :title "Endpoints" :id "endpoints"
|
||||
(p "All endpoints are " (code "defhandler") " definitions in " (code ".sx") " files. Python provides only IO primitives behind the boundary.")
|
||||
|
||||
(table :class "w-full text-sm"
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200 text-left"
|
||||
(th :class "py-2 pr-4" "Method")
|
||||
(th :class "py-2 pr-4" "Route")
|
||||
(th :class "py-2" "Purpose")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/actor") (td :class "py-2" "Actor profile"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/outbox") (td :class "py-2" "Published activity feed"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "POST") (td :class "py-2 pr-4 font-mono text-xs" "/pub/inbox") (td :class "py-2" "Receive activities from remote servers"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/⟨collection⟩") (td :class "py-2" "List items in a collection"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/⟨collection⟩/⟨slug⟩") (td :class "py-2" "Document by path — resolves CID, fetches from IPFS"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/cid/⟨cid⟩") (td :class "py-2" "Direct CID fetch (immutable, cache forever)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "POST") (td :class "py-2 pr-4 font-mono text-xs" "/pub/publish") (td :class "py-2" "Pin to IPFS + create Publish activity + announce"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "POST") (td :class "py-2 pr-4 font-mono text-xs" "/pub/follow") (td :class "py-2" "Follow a remote sx-pub server"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/followers") (td :class "py-2" "Who follows us"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/following") (td :class "py-2" "Who we follow"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/.well-known/webfinger") (td :class "py-2" "Discovery"))
|
||||
(tr (~tw :tokens "border-b border-stone-200 text-left")
|
||||
(th (~tw :tokens "py-2 pr-4") "Method")
|
||||
(th (~tw :tokens "py-2 pr-4") "Route")
|
||||
(th (~tw :tokens "py-2") "Purpose")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/actor") (td (~tw :tokens "py-2") "Actor profile"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/outbox") (td (~tw :tokens "py-2") "Published activity feed"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "POST") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/inbox") (td (~tw :tokens "py-2") "Receive activities from remote servers"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/⟨collection⟩") (td (~tw :tokens "py-2") "List items in a collection"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/⟨collection⟩/⟨slug⟩") (td (~tw :tokens "py-2") "Document by path — resolves CID, fetches from IPFS"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/cid/⟨cid⟩") (td (~tw :tokens "py-2") "Direct CID fetch (immutable, cache forever)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "POST") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/publish") (td (~tw :tokens "py-2") "Pin to IPFS + create Publish activity + announce"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "POST") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/follow") (td (~tw :tokens "py-2") "Follow a remote sx-pub server"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/followers") (td (~tw :tokens "py-2") "Who follows us"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/following") (td (~tw :tokens "py-2") "Who we follow"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/.well-known/webfinger") (td (~tw :tokens "py-2") "Discovery"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/anchor/⟨tree-cid⟩") (td :class "py-2" "Merkle tree verification")))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/anchor/⟨tree-cid⟩") (td (~tw :tokens "py-2") "Merkle tree verification")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Storage
|
||||
@@ -128,28 +128,28 @@
|
||||
(~docs/section :title "Storage" :id "storage"
|
||||
(p "IPFS is the canonical content store. PostgreSQL is the local index.")
|
||||
|
||||
(table :class "w-full text-sm"
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200 text-left"
|
||||
(th :class "py-2 pr-4" "What")
|
||||
(th :class "py-2" "Where")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Content (SX source)") (td :class "py-2" "IPFS — pinned, referenced by CID"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Path → CID index") (td :class "py-2" "PostgreSQL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Outbox activities") (td :class "py-2" "PostgreSQL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Followers / following") (td :class "py-2" "PostgreSQL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Anchor records") (td :class "py-2" "PostgreSQL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Remote mirrored content") (td :class "py-2" "PostgreSQL + IPFS (pinned locally)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Content cache") (td :class "py-2" "Redis"))
|
||||
(tr (~tw :tokens "border-b border-stone-200 text-left")
|
||||
(th (~tw :tokens "py-2 pr-4") "What")
|
||||
(th (~tw :tokens "py-2") "Where")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Content (SX source)") (td (~tw :tokens "py-2") "IPFS — pinned, referenced by CID"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Path → CID index") (td (~tw :tokens "py-2") "PostgreSQL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Outbox activities") (td (~tw :tokens "py-2") "PostgreSQL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Followers / following") (td (~tw :tokens "py-2") "PostgreSQL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Anchor records") (td (~tw :tokens "py-2") "PostgreSQL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Remote mirrored content") (td (~tw :tokens "py-2") "PostgreSQL + IPFS (pinned locally)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Content cache") (td (~tw :tokens "py-2") "Redis"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4" "Actor keypair") (td :class "py-2" "PostgreSQL")))))
|
||||
(td (~tw :tokens "py-2 pr-4") "Actor keypair") (td (~tw :tokens "py-2") "PostgreSQL")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; SX / Python Split
|
||||
@@ -158,10 +158,10 @@
|
||||
(~docs/section :title "SX / Python Split" :id "split"
|
||||
(p "SX handles all composition, logic, and rendering. Python provides IO primitives behind the boundary.")
|
||||
|
||||
(div :class "grid grid-cols-2 gap-6"
|
||||
(div (~tw :tokens "grid grid-cols-2 gap-6")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Pure SX (defhandler + defcomp)")
|
||||
(ul :class "text-sm text-stone-600 space-y-1 list-disc pl-5"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Pure SX (defhandler + defcomp)")
|
||||
(ul (~tw :tokens "text-sm text-stone-600 space-y-1 list-disc pl-5")
|
||||
(li "All HTTP routing and request dispatch")
|
||||
(li "Content negotiation (Accept header)")
|
||||
(li "Response construction (SX actor docs, collections)")
|
||||
@@ -169,8 +169,8 @@
|
||||
(li "Activity serialization")
|
||||
(li "Nav data for /pub/ sections")))
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Python Helpers (IO boundary)")
|
||||
(ul :class "text-sm text-stone-600 space-y-1 list-disc pl-5"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Python Helpers (IO boundary)")
|
||||
(ul (~tw :tokens "text-sm text-stone-600 space-y-1 list-disc pl-5")
|
||||
(li (code "pub-ipfs-add") " / " (code "pub-ipfs-get") " / " (code "pub-ipfs-pin") " — IPFS ops")
|
||||
(li (code "pub-db-query") " / " (code "pub-db-insert") " — database")
|
||||
(li (code "pub-sign") " / " (code "pub-verify-signature") " — HTTP signatures")
|
||||
@@ -185,8 +185,8 @@
|
||||
(~docs/section :title "Flows" :id "flows"
|
||||
|
||||
(~docs/subsection :title "Publish"
|
||||
(p :class "text-sm text-stone-600 mb-2" "Publishing is " (strong "anchor-then-announce") " — content is provenance-sealed before anyone sees it. This prevents a malicious follower from seeing content, anchoring it first, and claiming provenance.")
|
||||
(ol :class "text-sm text-stone-600 space-y-2 list-decimal pl-5"
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "Publishing is " (strong "anchor-then-announce") " — content is provenance-sealed before anyone sees it. This prevents a malicious follower from seeing content, anchoring it first, and claiming provenance.")
|
||||
(ol (~tw :tokens "text-sm text-stone-600 space-y-2 list-decimal pl-5")
|
||||
(li (code "POST /pub/publish") " with collection, slug, SX content")
|
||||
(li "Pin to IPFS → get CID (content is now addressable but not announced)")
|
||||
(li "Store path→CID in DB (status: " (code "anchoring") ")")
|
||||
@@ -194,12 +194,12 @@
|
||||
(li "On confirmation: update status to " (code "anchored") ", record proof")
|
||||
(li "Construct " (code "(Publish ...)") " with anchor proof embedded")
|
||||
(li "Deliver to follower inboxes — " (strong "only now") " is it public"))
|
||||
(p :class "text-sm text-stone-500 mt-2 italic" "This means publishing isn't instant — there's a delay while the anchor confirms. That's a feature: it forces a deliberate pace and makes provenance airtight.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-2 italic") "This means publishing isn't instant — there's a delay while the anchor confirms. That's a feature: it forces a deliberate pace and makes provenance airtight.")
|
||||
(~docs/code :src "(Publish\n :actor \"https://pub.sx-web.org/pub/actor\"\n :published \"2026-03-24T12:00:00Z\"\n :object (SxDocument\n :name \"evaluator\"\n :cid \"bafy...eval123\"\n :anchor (Anchor\n :tree-cid \"bafy...merkle\"\n :ots-cid \"bafy...proof\"\n :btc-txid \"abc123...\"\n :btc-block 890123)))")
|
||||
(p :class "text-sm text-stone-600" "Receivers verify: the CID was anchored in that Bitcoin block " (em "before") " the Publish was sent. No way to backdate."))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Receivers verify: the CID was anchored in that Bitcoin block " (em "before") " the Publish was sent. No way to backdate."))
|
||||
|
||||
(~docs/subsection :title "Follow"
|
||||
(ol :class "text-sm text-stone-600 space-y-2 list-decimal pl-5"
|
||||
(ol (~tw :tokens "text-sm text-stone-600 space-y-2 list-decimal pl-5")
|
||||
(li (code "POST /pub/follow") " with remote actor URL")
|
||||
(li "Fetch remote actor document (" (code "text/sx") ")")
|
||||
(li "Send " (code "(Follow ...)") " to their inbox, signed with our key")
|
||||
@@ -208,7 +208,7 @@
|
||||
(li "They start delivering " (code "(Publish ...)") " activities to our inbox")))
|
||||
|
||||
(~docs/subsection :title "Receive (inbox)"
|
||||
(ol :class "text-sm text-stone-600 space-y-2 list-decimal pl-5"
|
||||
(ol (~tw :tokens "text-sm text-stone-600 space-y-2 list-decimal pl-5")
|
||||
(li "Remote server POSTs " (code "(Publish ...)") " to our inbox")
|
||||
(li "Verify HTTP signature against their actor's public key")
|
||||
(li "Pin the referenced CID to our local IPFS node")
|
||||
@@ -216,15 +216,15 @@
|
||||
(li "Now we can resolve that CID locally — " (code "/pub/cid/bafy...") " just works")))
|
||||
|
||||
(~docs/subsection :title "Anchoring"
|
||||
(p :class "text-sm text-stone-600 mb-2" "Anchoring is integrated into the publish flow — not a separate background job. Content waits for provenance before going public.")
|
||||
(ol :class "text-sm text-stone-600 space-y-2 list-decimal pl-5"
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "Anchoring is integrated into the publish flow — not a separate background job. Content waits for provenance before going public.")
|
||||
(ol (~tw :tokens "text-sm text-stone-600 space-y-2 list-decimal pl-5")
|
||||
(li "Batch pending CIDs into a Merkle tree (multiple publishes can share one tree)")
|
||||
(li "Pin Merkle tree to IPFS")
|
||||
(li "Submit Merkle root to OpenTimestamps calendar servers")
|
||||
(li "Poll for Bitcoin confirmation (typically ~1 hour)")
|
||||
(li "On confirmation: store proof, transition content to " (code "anchored") " status")
|
||||
(li "Trigger delivery of Publish activities for all items in the batch"))
|
||||
(p :class "text-sm text-stone-500 mt-2 italic" "Batching amortises the anchor cost — one Bitcoin attestation can seal hundreds of CIDs via the Merkle tree. Each CID gets an inclusion proof linking it to the anchored root.")))
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-2 italic") "Batching amortises the anchor cost — one Bitcoin attestation can seal hundreds of CIDs via the Merkle tree. Each CID gets an inclusion proof linking it to the anchored root.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; CID ↔ Path
|
||||
@@ -242,19 +242,19 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Implementation Phases" :id "phases"
|
||||
(div :class "space-y-4"
|
||||
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4"
|
||||
(h4 :class "font-semibold text-emerald-800" "Phase 1: Foundation ✓")
|
||||
(p :class "text-emerald-700 text-sm" "DB schema, async IPFS client, actor endpoint, webfinger, " (code "/pub/actor") " returns SX actor document."))
|
||||
(div :class "rounded border border-sky-200 bg-sky-50 p-4"
|
||||
(h4 :class "font-semibold text-sky-800" "Phase 2: Publishing ✓")
|
||||
(p :class "text-sky-700 text-sm" "Pin to IPFS, path→CID index, collection browsing. Publish the actual SX spec files as the first content."))
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4"
|
||||
(h4 :class "font-semibold text-violet-800" "Phase 3: Federation ✓")
|
||||
(p :class "text-violet-700 text-sm" "Inbox/outbox, follow/accept, HTTP signature verification, activity delivery, content mirroring."))
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
||||
(h4 :class "font-semibold text-amber-800" "Phase 4: Anchoring ✓")
|
||||
(p :class "text-amber-700 text-sm" "Merkle trees, OpenTimestamps, Bitcoin proof, provenance verification."))))
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-emerald-800") "Phase 1: Foundation ✓")
|
||||
(p (~tw :tokens "text-emerald-700 text-sm") "DB schema, async IPFS client, actor endpoint, webfinger, " (code "/pub/actor") " returns SX actor document."))
|
||||
(div (~tw :tokens "rounded border border-sky-200 bg-sky-50 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-sky-800") "Phase 2: Publishing ✓")
|
||||
(p (~tw :tokens "text-sky-700 text-sm") "Pin to IPFS, path→CID index, collection browsing. Publish the actual SX spec files as the first content."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-violet-800") "Phase 3: Federation ✓")
|
||||
(p (~tw :tokens "text-violet-700 text-sm") "Inbox/outbox, follow/accept, HTTP signature verification, activity delivery, content mirroring."))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-amber-800") "Phase 4: Anchoring ✓")
|
||||
(p (~tw :tokens "text-amber-700 text-sm") "Merkle trees, OpenTimestamps, Bitcoin proof, provenance verification."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Live Dashboard
|
||||
@@ -266,45 +266,45 @@
|
||||
;; --- Status ---
|
||||
(~docs/subsection :title "Server Status"
|
||||
(let ((status (helper "pub-status-data")))
|
||||
(div :class "grid grid-cols-2 sm:grid-cols-4 gap-3"
|
||||
(div :class "rounded border border-stone-200 p-3 text-center"
|
||||
(p :class "text-xs text-stone-400 uppercase" "DB")
|
||||
(p :class "font-semibold text-sm" (get status "db")))
|
||||
(div :class "rounded border border-stone-200 p-3 text-center"
|
||||
(p :class "text-xs text-stone-400 uppercase" "IPFS")
|
||||
(p :class "font-semibold text-sm" (get status "ipfs")))
|
||||
(div :class "rounded border border-stone-200 p-3 text-center"
|
||||
(p :class "text-xs text-stone-400 uppercase" "Actor")
|
||||
(p :class "font-semibold text-sm" (get status "actor")))
|
||||
(div :class "rounded border border-stone-200 p-3 text-center"
|
||||
(p :class "text-xs text-stone-400 uppercase" "Domain")
|
||||
(p :class "font-semibold text-sm" (or (get status "domain") "—"))))))
|
||||
(div (~tw :tokens "grid grid-cols-2 sm:grid-cols-4 gap-3")
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 text-center")
|
||||
(p (~tw :tokens "text-xs text-stone-400 uppercase") "DB")
|
||||
(p (~tw :tokens "font-semibold text-sm") (get status "db")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 text-center")
|
||||
(p (~tw :tokens "text-xs text-stone-400 uppercase") "IPFS")
|
||||
(p (~tw :tokens "font-semibold text-sm") (get status "ipfs")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 text-center")
|
||||
(p (~tw :tokens "text-xs text-stone-400 uppercase") "Actor")
|
||||
(p (~tw :tokens "font-semibold text-sm") (get status "actor")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 text-center")
|
||||
(p (~tw :tokens "text-xs text-stone-400 uppercase") "Domain")
|
||||
(p (~tw :tokens "font-semibold text-sm") (or (get status "domain") "—"))))))
|
||||
|
||||
;; --- Actor ---
|
||||
(~docs/subsection :title "Actor Identity"
|
||||
(let ((actor (helper "pub-actor-data")))
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4 space-y-2"
|
||||
(div :class "flex items-center gap-3"
|
||||
(div :class "w-10 h-10 rounded-full bg-violet-100 flex items-center justify-center text-violet-700 font-bold" "sx")
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 space-y-2")
|
||||
(div (~tw :tokens "flex items-center gap-3")
|
||||
(div (~tw :tokens "w-10 h-10 rounded-full bg-violet-100 flex items-center justify-center text-violet-700 font-bold") "sx")
|
||||
(div
|
||||
(p :class "font-semibold" (get actor "display-name"))
|
||||
(p :class "text-sm text-stone-500" (str "@" (get actor "preferred-username") "@" (get actor "domain")))))
|
||||
(p :class "text-sm text-stone-600" (get actor "summary"))
|
||||
(details :class "text-xs"
|
||||
(summary :class "text-stone-400 cursor-pointer" "Public key")
|
||||
(pre :class "mt-2 bg-stone-100 rounded p-2 text-xs overflow-x-auto" (get actor "public-key-pem"))))))
|
||||
(p (~tw :tokens "font-semibold") (get actor "display-name"))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (str "@" (get actor "preferred-username") "@" (get actor "domain")))))
|
||||
(p (~tw :tokens "text-sm text-stone-600") (get actor "summary"))
|
||||
(details (~tw :tokens "text-xs")
|
||||
(summary (~tw :tokens "text-stone-400 cursor-pointer") "Public key")
|
||||
(pre (~tw :tokens "mt-2 bg-stone-100 rounded p-2 text-xs overflow-x-auto") (get actor "public-key-pem"))))))
|
||||
|
||||
;; --- Collections ---
|
||||
(~docs/subsection :title "Collections"
|
||||
(let ((collections (helper "pub-collections-data")))
|
||||
(div :class "grid gap-3"
|
||||
(div (~tw :tokens "grid gap-3")
|
||||
(map (fn (c)
|
||||
(div :class "rounded border border-stone-200 p-4 hover:border-violet-300 transition-colors"
|
||||
(div :class "flex items-center justify-between"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4 hover:border-violet-300 transition-colors")
|
||||
(div (~tw :tokens "flex items-center justify-between")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-800" (get c "name"))
|
||||
(p :class "text-sm text-stone-500" (get c "description")))
|
||||
(span :class "text-xs font-mono text-violet-600 bg-violet-50 px-2 py-1 rounded"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-800") (get c "name"))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (get c "description")))
|
||||
(span (~tw :tokens "text-xs font-mono text-violet-600 bg-violet-50 px-2 py-1 rounded")
|
||||
(str "/pub/" (get c "slug"))))))
|
||||
collections))))
|
||||
|
||||
@@ -312,35 +312,35 @@
|
||||
(~docs/subsection :title "Published Documents"
|
||||
(let ((specs (helper "pub-collection-items" "core-specs")))
|
||||
(when (not (get specs "error"))
|
||||
(div :class "space-y-2"
|
||||
(h4 :class "text-sm font-semibold text-stone-500 uppercase tracking-wide"
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(h4 (~tw :tokens "text-sm font-semibold text-stone-500 uppercase tracking-wide")
|
||||
(get specs "name"))
|
||||
(map (fn (d)
|
||||
(when (!= (get d "slug") "")
|
||||
(div :class "rounded border border-stone-200 p-3 flex items-center justify-between"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 flex items-center justify-between")
|
||||
(div
|
||||
(p :class "font-medium text-stone-800" (get d "title"))
|
||||
(p :class "text-xs text-stone-400" (get d "summary")))
|
||||
(div :class "text-right"
|
||||
(p :class "text-xs font-mono text-emerald-600 truncate max-w-48" (get d "cid"))
|
||||
(p :class "text-xs text-stone-400" (str (get d "size") " bytes"))))))
|
||||
(p (~tw :tokens "font-medium text-stone-800") (get d "title"))
|
||||
(p (~tw :tokens "text-xs text-stone-400") (get d "summary")))
|
||||
(div (~tw :tokens "text-right")
|
||||
(p (~tw :tokens "text-xs font-mono text-emerald-600 truncate max-w-48") (get d "cid"))
|
||||
(p (~tw :tokens "text-xs text-stone-400") (str (get d "size") " bytes"))))))
|
||||
(get specs "items"))))))
|
||||
|
||||
;; --- Outbox ---
|
||||
(~docs/subsection :title "Recent Activity"
|
||||
(let ((outbox (helper "pub-outbox-data" "")))
|
||||
(if (= (get outbox "total") 0)
|
||||
(p :class "text-sm text-stone-400 italic" "No activities yet.")
|
||||
(div :class "space-y-2"
|
||||
(p :class "text-xs text-stone-400" (str (get outbox "total") " total activities"))
|
||||
(p (~tw :tokens "text-sm text-stone-400 italic") "No activities yet.")
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(p (~tw :tokens "text-xs text-stone-400") (str (get outbox "total") " total activities"))
|
||||
(map (fn (a)
|
||||
(when (!= (get a "type") "")
|
||||
(div :class "rounded border border-stone-200 p-3 flex items-center gap-3"
|
||||
(span :class "text-xs font-semibold text-white bg-violet-500 px-2 py-0.5 rounded"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 flex items-center gap-3")
|
||||
(span (~tw :tokens "text-xs font-semibold text-white bg-violet-500 px-2 py-0.5 rounded")
|
||||
(get a "type"))
|
||||
(span :class "text-xs text-stone-500" (get a "published"))
|
||||
(span (~tw :tokens "text-xs text-stone-500") (get a "published"))
|
||||
(when (!= (get a "cid") "")
|
||||
(span :class "text-xs font-mono text-emerald-600 truncate max-w-48"
|
||||
(span (~tw :tokens "text-xs font-mono text-emerald-600 truncate max-w-48")
|
||||
(get a "cid"))))))
|
||||
(get outbox "items"))))))
|
||||
|
||||
@@ -348,22 +348,22 @@
|
||||
(~docs/subsection :title "Followers"
|
||||
(let ((followers (helper "pub-followers-data")))
|
||||
(if (empty? followers)
|
||||
(p :class "text-sm text-stone-400 italic" "No followers yet.")
|
||||
(div :class "space-y-2"
|
||||
(p (~tw :tokens "text-sm text-stone-400 italic") "No followers yet.")
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(map (fn (f)
|
||||
(when (!= (get f "acct") "")
|
||||
(div :class "rounded border border-stone-200 p-3 flex items-center gap-2"
|
||||
(div :class "w-8 h-8 rounded-full bg-sky-100 flex items-center justify-center text-sky-700 text-xs font-bold" "F")
|
||||
(p :class "text-sm font-mono text-stone-600 truncate" (get f "acct")))))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 flex items-center gap-2")
|
||||
(div (~tw :tokens "w-8 h-8 rounded-full bg-sky-100 flex items-center justify-center text-sky-700 text-xs font-bold") "F")
|
||||
(p (~tw :tokens "text-sm font-mono text-stone-600 truncate") (get f "acct")))))
|
||||
followers))))
|
||||
|
||||
;; --- API Endpoints ---
|
||||
(~docs/subsection :title "Try the API"
|
||||
(p :class "text-sm text-stone-600 mb-2" "All endpoints return " (code "text/sx") ". Try them directly:")
|
||||
(div :class "grid grid-cols-2 sm:grid-cols-3 gap-2"
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "All endpoints return " (code "text/sx") ". Try them directly:")
|
||||
(div (~tw :tokens "grid grid-cols-2 sm:grid-cols-3 gap-2")
|
||||
(map (fn (endpoint)
|
||||
(a :href (get endpoint "href")
|
||||
:class "block rounded border border-stone-200 p-2 text-center hover:border-violet-300 hover:bg-violet-50 transition-colors text-xs font-mono text-stone-600"
|
||||
(~tw :tokens "block rounded border border-stone-200 p-2 text-center hover:border-violet-300 hover:bg-violet-50 transition-colors text-xs font-mono text-stone-600")
|
||||
(get endpoint "label")))
|
||||
(list
|
||||
{"label" "GET /pub/actor" "href" "/pub/actor"}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"they're static data with ad-hoc templating bolted on. "
|
||||
"Variable substitution (${VAR}), extension fields (x-), YAML anchors (&/*) — "
|
||||
"all workarounds for not having a real language.")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "No functions — can't abstract repeated patterns")
|
||||
(li "No macros — can't generate config at definition time")
|
||||
(li "No conditionals — can't branch on environment")
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
(~docs/section :title "Health and Monitoring" :id "monitoring"
|
||||
(p "Service health as live SX components:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Dashboard") " — defcomp rendering swarm-status, auto-refreshing via sx-get polling")
|
||||
(li (strong "Log viewer") " — swarm-logs streamed via SSE, rendered as SX")
|
||||
(li (strong "Alerts") " — SX predicates on service state, notifications via sx-activity")
|
||||
@@ -155,7 +155,7 @@
|
||||
"No Grafana, no separate monitoring stack. Same language, same renderer, same platform."))
|
||||
|
||||
(~docs/section :title "Implementation Path" :id "implementation"
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
|
||||
(li (strong "Phase 1: Stack definition") " — SX data structures for services, networks, volumes. "
|
||||
"Compiler to docker-compose.yml / docker stack deploy format.")
|
||||
(li (strong "Phase 2: Environment macros") " — defmacro for env-for, dev-volumes, prod-volumes. "
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
";; Developers happily write this every day:\nhttps://api.site.com/v2/users/123/posts?filter=published&sort=date&order=desc&limit=10&offset=20\n\n;; And they would complain about this?\nhttps://site.com/(users.(posts.123.(filter.published.sort.date.limit.10)))\n\n;; The second is shorter, structured, unambiguous, and composable."
|
||||
"lisp"))
|
||||
(p "The real question: who is reading these URLs?")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "End users") " barely look at URLs anymore — they live in address bars people ignore")
|
||||
(li (strong "Developers") " will love it once they get it — it is powerful and consistent")
|
||||
(li (strong "Crawlers and bots") " do not care at all"))
|
||||
@@ -86,22 +86,22 @@
|
||||
"But this violates HTTP semantics: POST implies side effects, "
|
||||
"GET requests are cacheable, bookmarkable, shareable.")
|
||||
(p "SX URLs naturally align with HTTP because the query " (em "is") " the URL:")
|
||||
(div :class "overflow-x-auto mt-4"
|
||||
(table :class "w-full text-sm text-left"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "w-full text-sm text-left")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Method")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Semantics")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Example")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "GET")
|
||||
(td :class "py-2 px-3" "Pure evaluation — cacheable, bookmarkable")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "GET /(language.(doc.intro))"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "POST")
|
||||
(td :class "py-2 px-3" "Side effects — mutations, submissions")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "POST /(submit-post)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Method")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Semantics")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Example")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "GET")
|
||||
(td (~tw :tokens "py-2 px-3") "Pure evaluation — cacheable, bookmarkable")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "GET /(language.(doc.intro))"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "POST")
|
||||
(td (~tw :tokens "py-2 px-3") "Side effects — mutations, submissions")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "POST /(submit-post)")))))
|
||||
(p "CDN caching of Lisp hypermedia just works. "
|
||||
"This is what REST always wanted but GraphQL abandoned. "
|
||||
"SX re-aligns with HTTP while being more powerful than both."))
|
||||
@@ -109,51 +109,51 @@
|
||||
(~docs/section :title "GraphSX — This Is a Query Language" :id "graphsx"
|
||||
(p "The SX URL scheme is not just a routing convention — it is the emergence of "
|
||||
(strong "GraphSX") ": GraphQL but Lisp. The structural parallel is exact:")
|
||||
(div :class "overflow-x-auto mt-4"
|
||||
(table :class "w-full text-sm text-left"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "w-full text-sm text-left")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Concept")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "GraphQL")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "GraphSX")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Single endpoint")
|
||||
(td :class "py-2 px-3" (code "/graphql"))
|
||||
(td :class "py-2 px-3" "Catch-all " (code "/<path:expr>")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Query structure")
|
||||
(td :class "py-2 px-3" "Nested fields " (code "{ language { doc { ... } } }"))
|
||||
(td :class "py-2 px-3" "Nested s-expressions " (code "(language.(doc....))")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Resolvers")
|
||||
(td :class "py-2 px-3" "Per-field functions")
|
||||
(td :class "py-2 px-3" "Page/section functions + " (code "~components")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Fragments")
|
||||
(td :class "py-2 px-3" "Named reusable selections")
|
||||
(td :class "py-2 px-3" "Components (" (code "defcomp") ")"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Schema")
|
||||
(td :class "py-2 px-3" "Type definitions")
|
||||
(td :class "py-2 px-3" "Page function registry + component env"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Scoping")
|
||||
(td :class "py-2 px-3" "Flat — " (code "?filter=x") " applies to... what?")
|
||||
(td :class "py-2 px-3" "Structural — parens ARE scope boundaries"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Transport")
|
||||
(td :class "py-2 px-3" "POST JSON (violates HTTP GET semantics)")
|
||||
(td :class "py-2 px-3" "GET " (code "/sx/(expr)") " — cacheable, bookmarkable"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Response")
|
||||
(td :class "py-2 px-3" "JSON data (needs separate rendering)")
|
||||
(td :class "py-2 px-3" "Content " (em "and") " data — response is already meaningful")))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Concept")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "GraphQL")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "GraphSX")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Single endpoint")
|
||||
(td (~tw :tokens "py-2 px-3") (code "/graphql"))
|
||||
(td (~tw :tokens "py-2 px-3") "Catch-all " (code "/<path:expr>")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Query structure")
|
||||
(td (~tw :tokens "py-2 px-3") "Nested fields " (code "{ language { doc { ... } } }"))
|
||||
(td (~tw :tokens "py-2 px-3") "Nested s-expressions " (code "(language.(doc....))")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Resolvers")
|
||||
(td (~tw :tokens "py-2 px-3") "Per-field functions")
|
||||
(td (~tw :tokens "py-2 px-3") "Page/section functions + " (code "~components")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Fragments")
|
||||
(td (~tw :tokens "py-2 px-3") "Named reusable selections")
|
||||
(td (~tw :tokens "py-2 px-3") "Components (" (code "defcomp") ")"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Schema")
|
||||
(td (~tw :tokens "py-2 px-3") "Type definitions")
|
||||
(td (~tw :tokens "py-2 px-3") "Page function registry + component env"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Scoping")
|
||||
(td (~tw :tokens "py-2 px-3") "Flat — " (code "?filter=x") " applies to... what?")
|
||||
(td (~tw :tokens "py-2 px-3") "Structural — parens ARE scope boundaries"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Transport")
|
||||
(td (~tw :tokens "py-2 px-3") "POST JSON (violates HTTP GET semantics)")
|
||||
(td (~tw :tokens "py-2 px-3") "GET " (code "/sx/(expr)") " — cacheable, bookmarkable"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Response")
|
||||
(td (~tw :tokens "py-2 px-3") "JSON data (needs separate rendering)")
|
||||
(td (~tw :tokens "py-2 px-3") "Content " (em "and") " data — response is already meaningful")))))
|
||||
(p "The killer difference: in GraphQL, query and rendering are separate concerns — "
|
||||
"you fetch JSON, then a frontend renders it. In GraphSX, "
|
||||
(strong "the query language and the rendering language are the same thing") ". "
|
||||
(code "(language.(doc.introduction))") " is simultaneously:")
|
||||
(ol :class "space-y-1 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-1 text-stone-600 list-decimal pl-5")
|
||||
(li "A " (strong "query") " — resolve the docs/introduction content within the language section")
|
||||
(li "A " (strong "render instruction") " — render it inside a language section wrapper with nav context")
|
||||
(li "A " (strong "URL") " — addressable, bookmarkable, shareable, cacheable"))
|
||||
@@ -175,42 +175,42 @@
|
||||
|
||||
(~docs/section :title "URL Special Forms" :id "special-forms"
|
||||
(p "URL-level functions that transform how content is resolved or displayed:")
|
||||
(div :class "overflow-x-auto mt-4"
|
||||
(table :class "w-full text-sm text-left"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "w-full text-sm text-left")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Form")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Example")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Effect")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-mono text-violet-700" "source")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "/sx/(source.(~essays/sx-sucks/essay-sx-sucks))")
|
||||
(td :class "py-2 px-3" "Show defcomp source instead of rendering"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-mono text-violet-700" "inspect")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "/sx/(inspect.(language.(doc.primitives)))")
|
||||
(td :class "py-2 px-3" "Deps, CSS classes, render plan, IO requirements"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-mono text-violet-700" "diff")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "/sx/(diff.(spec.signals).(spec.eval))")
|
||||
(td :class "py-2 px-3" "Side-by-side two pages"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-mono text-violet-700" "search")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "/sx/(search.\"define\".:in.(spec.signals))")
|
||||
(td :class "py-2 px-3" "Grep within a page or spec"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-mono text-violet-700" "raw")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "/sx/(raw.(~some-component))")
|
||||
(td :class "py-2 px-3" "Skip ~layouts/doc nav wrapping"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-mono text-violet-700" "eval")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "/sx/(eval.(map.double.(list.1.2.3)))")
|
||||
(td :class "py-2 px-3" "Arbitrary evaluation — the URL bar is a REPL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-mono text-violet-700" "json")
|
||||
(td :class "py-2 px-3 font-mono text-sm" "/sx/(json.(language.(doc.primitives)))")
|
||||
(td :class "py-2 px-3" "Return data as JSON — pure query mode"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Form")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Example")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Effect")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "source")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(source.(~essays/sx-sucks/essay-sx-sucks))")
|
||||
(td (~tw :tokens "py-2 px-3") "Show defcomp source instead of rendering"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "inspect")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(inspect.(language.(doc.primitives)))")
|
||||
(td (~tw :tokens "py-2 px-3") "Deps, CSS classes, render plan, IO requirements"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "diff")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(diff.(spec.signals).(spec.eval))")
|
||||
(td (~tw :tokens "py-2 px-3") "Side-by-side two pages"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "search")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(search.\"define\".:in.(spec.signals))")
|
||||
(td (~tw :tokens "py-2 px-3") "Grep within a page or spec"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "raw")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(raw.(~some-component))")
|
||||
(td (~tw :tokens "py-2 px-3") "Skip ~layouts/doc nav wrapping"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "eval")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(eval.(map.double.(list.1.2.3)))")
|
||||
(td (~tw :tokens "py-2 px-3") "Arbitrary evaluation — the URL bar is a REPL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "json")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(json.(language.(doc.primitives)))")
|
||||
(td (~tw :tokens "py-2 px-3") "Return data as JSON — pure query mode"))))))
|
||||
|
||||
(~docs/section :title "Evaluation Model" :id "eval"
|
||||
(p "The URL path (after stripping " (code "/") " and replacing dots with spaces) "
|
||||
@@ -240,7 +240,7 @@
|
||||
"@app.get(\"/\")\nasync def sx_home():\n return await eval_sx_url(\"/\")\n\n@app.get(\"/<path:expr>\")\nasync def sx_eval_route(expr):\n return await eval_sx_url(f\"/{expr}\")"
|
||||
"python"))
|
||||
(p (code "eval_sx_url") " in seven steps:")
|
||||
(ol :class "space-y-1 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-1 text-stone-600 list-decimal pl-5")
|
||||
(li "URL-decode the path")
|
||||
(li "Replace dots with spaces")
|
||||
(li "Parse as SX expression")
|
||||
@@ -257,43 +257,43 @@
|
||||
"lisp")))
|
||||
|
||||
(~docs/section :title "Implementation Phases" :id "phases"
|
||||
(div :class "space-y-4"
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4"
|
||||
(p :class "font-semibold text-violet-800 mb-2" "Phase 1: Page Functions + Catch-All Route")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-violet-800 mb-2") "Phase 1: Page Functions + Catch-All Route")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Create " (code "page-functions.sx") " — ~20 section + ~15 page functions")
|
||||
(li "Create " (code "sx_router.py") " — URL parser (dot→space), soft eval, streaming detection")
|
||||
(li "Replace " (code "auto_mount_pages") " with catch-all")
|
||||
(li "Direct " (code "~component") " URL support")))
|
||||
(div :class "rounded border border-blue-200 bg-blue-50 p-4"
|
||||
(p :class "font-semibold text-blue-800 mb-2" "Phase 2: Navigation Data")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-blue-200 bg-blue-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-blue-800 mb-2") "Phase 2: Navigation Data")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Rewrite ~200 " (code ":href") " values to dot-separated SX URLs")
|
||||
(li "Rewrite " (code "resolve-nav-path") " for tree-descent matching")))
|
||||
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4"
|
||||
(p :class "font-semibold text-emerald-800 mb-2" "Phase 3: Backward Compatibility")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-emerald-800 mb-2") "Phase 3: Backward Compatibility")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "301 redirects from all old paths to new SX URLs")
|
||||
(li "Algorithmic pattern matching — ~25 regex rules")))
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
||||
(p :class "font-semibold text-amber-800 mb-2" "Phase 4: URL Special Forms")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-amber-800 mb-2") "Phase 4: URL Special Forms")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "source") ", " (code "inspect") ", " (code "diff") ", " (code "raw") ", " (code "eval") ", " (code "json"))
|
||||
(li "Components as query resolvers (" (code "~get") ", " (code "~page") ")")))
|
||||
(div :class "rounded border border-rose-200 bg-rose-50 p-4"
|
||||
(p :class "font-semibold text-rose-800 mb-2" "Phase 5: Client-Side Routing")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-rose-200 bg-rose-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-rose-800 mb-2") "Phase 5: Client-Side Routing")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Update " (code "_build_pages_sx()") " for function-based page registry")
|
||||
(li "Client-side dot→space→parse→eval pipeline")
|
||||
(li "Rebootstrap " (code "sx-ref.js") " and " (code "sx_ref.py"))))
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "font-semibold text-stone-800 mb-2" "Phase 6: Cleanup")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-stone-800 mb-2") "Phase 6: Cleanup")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Delete " (code "docs.sx") " (all 46 defpages)")
|
||||
(li "Grep content files for stale old-style hrefs")))))
|
||||
|
||||
(~docs/section :title "What Stays the Same" :id "unchanged"
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Defhandler API paths") " — registered before the catch-all, match first")
|
||||
(li (strong "Python demo routes") " — registered via blueprint before the catch-all")
|
||||
(li (strong "All other services") " — blog, market, etc. keep defpage routing")
|
||||
|
||||
@@ -17,38 +17,38 @@
|
||||
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(p "The platform composes existing SX subsystems into a unified workflow:")
|
||||
(div :class "overflow-x-auto mt-4"
|
||||
(table :class "w-full text-sm text-left"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "w-full text-sm text-left")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Layer")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "System")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Role")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Author")
|
||||
(td :class "py-2 px-3" "Embedded editor + Claude Code")
|
||||
(td :class "py-2 px-3" "Write SX in the browser with AI assistance"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Stage")
|
||||
(td :class "py-2 px-3" "Content-addressed components")
|
||||
(td :class "py-2 px-3" "CID-identified artifacts, preview before publish"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Test")
|
||||
(td :class "py-2 px-3" "sx-ci")
|
||||
(td :class "py-2 px-3" "Run test suites against staged changes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Publish")
|
||||
(td :class "py-2 px-3" "sx-activity")
|
||||
(td :class "py-2 px-3" "Federated distribution via ActivityPub"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Store")
|
||||
(td :class "py-2 px-3" "IPFS")
|
||||
(td :class "py-2 px-3" "Content-addressed storage, permanent availability"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Verify")
|
||||
(td :class "py-2 px-3" "Environment images")
|
||||
(td :class "py-2 px-3" "Spec CID → image CID → endpoint provenance"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Layer")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "System")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Role")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Author")
|
||||
(td (~tw :tokens "py-2 px-3") "Embedded editor + Claude Code")
|
||||
(td (~tw :tokens "py-2 px-3") "Write SX in the browser with AI assistance"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Stage")
|
||||
(td (~tw :tokens "py-2 px-3") "Content-addressed components")
|
||||
(td (~tw :tokens "py-2 px-3") "CID-identified artifacts, preview before publish"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Test")
|
||||
(td (~tw :tokens "py-2 px-3") "sx-ci")
|
||||
(td (~tw :tokens "py-2 px-3") "Run test suites against staged changes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Publish")
|
||||
(td (~tw :tokens "py-2 px-3") "sx-activity")
|
||||
(td (~tw :tokens "py-2 px-3") "Federated distribution via ActivityPub"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Store")
|
||||
(td (~tw :tokens "py-2 px-3") "IPFS")
|
||||
(td (~tw :tokens "py-2 px-3") "Content-addressed storage, permanent availability"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Verify")
|
||||
(td (~tw :tokens "py-2 px-3") "Environment images")
|
||||
(td (~tw :tokens "py-2 px-3") "Spec CID → image CID → endpoint provenance"))))))
|
||||
|
||||
(~docs/section :title "Embedded Claude Code" :id "claude-code"
|
||||
(p "Claude Code sessions run inside the browser as reactive islands. "
|
||||
@@ -56,7 +56,7 @@
|
||||
"write components, run tests, and propose changes. All within the user's security context.")
|
||||
(p "The session produces SX diffs — not text patches, but structural changes to the component tree. "
|
||||
"These diffs are first-class SX values that can be inspected, composed, reverted, and replayed.")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "Read any component definition, spec file, or plan")
|
||||
(li "Write new components (essays, examples, specs)")
|
||||
(li "Modify existing components with structural diffs")
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
(~docs/section :title "Workflow" :id "workflow"
|
||||
(p "A typical session — adding a new essay:")
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
|
||||
(li (strong "Author: ") "Open Claude Code session on sx-web.org. "
|
||||
"Describe the essay topic. Claude writes the defcomp in SX.")
|
||||
(li (strong "Preview: ") "The component renders live in the browser. "
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
(~docs/section :title "Content Types" :id "content-types"
|
||||
(p "Anything that can be a defcomp can be authored on the platform:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Essays") " — opinion pieces, rationales, explorations")
|
||||
(li (strong "Examples") " — interactive demos with live code")
|
||||
(li (strong "Specs") " — new spec modules authored and tested in-browser")
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
(~docs/section :title "Prerequisites" :id "prerequisites"
|
||||
(p "Systems that must be complete before the platform can work:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Reactive islands (L2+)") " — for the editor and preview panes")
|
||||
(li (strong "Content-addressed components") " — CID computation and IPFS storage")
|
||||
(li (strong "sx-activity") " — federated publish/subscribe")
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/sx-web/plan-sx-web-content ()
|
||||
(~docs/page :title "sx-web: Federated Component Web"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"A two-tier peer network where server nodes pin to IPFS and browser tabs "
|
||||
"participate via WebTransport. Components, tests, and content are all "
|
||||
"content-addressed SX expressions verified by CID regardless of source. "
|
||||
@@ -19,16 +19,16 @@
|
||||
|
||||
(p "Two tiers, one logical network. Trust anchored on CIDs, not sources.")
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "") (th :class "text-left p-2" "Server Node") (th :class "text-left p-2" "Browser Node")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "") (th (~tw :tokens "text-left p-2") "Server Node") (th (~tw :tokens "text-left p-2") "Browser Node")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "Runtime") (td :class "p-2" "OCaml + Python") (td :class "p-2" "JavaScript (sx-browser.js)"))
|
||||
(tr (td :class "p-2 font-medium" "Storage") (td :class "p-2" "IPFS daemon + PostgreSQL") (td :class "p-2" "IndexedDB + Cache API"))
|
||||
(tr (td :class "p-2 font-medium" "IPFS") (td :class "p-2" "Full DHT peer, pins content") (td :class "p-2" "Verifies CIDs, caches locally"))
|
||||
(tr (td :class "p-2 font-medium" "Transport") (td :class "p-2" "WebSocket + HTTP federation") (td :class "p-2" "WebSocket to home + WebRTC to peers"))
|
||||
(tr (td :class "p-2 font-medium" "Availability") (td :class "p-2" "Long-lived, always on") (td :class "p-2" "Ephemeral, online when open"))
|
||||
(tr (td :class "p-2 font-medium" "Trust") (td :class "p-2" "CID verification + blockchain anchor") (td :class "p-2" "CID verification (same guarantee)"))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Runtime") (td (~tw :tokens "p-2") "OCaml + Python") (td (~tw :tokens "p-2") "JavaScript (sx-browser.js)"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Storage") (td (~tw :tokens "p-2") "IPFS daemon + PostgreSQL") (td (~tw :tokens "p-2") "IndexedDB + Cache API"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "IPFS") (td (~tw :tokens "p-2") "Full DHT peer, pins content") (td (~tw :tokens "p-2") "Verifies CIDs, caches locally"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Transport") (td (~tw :tokens "p-2") "WebSocket + HTTP federation") (td (~tw :tokens "p-2") "WebSocket to home + WebRTC to peers"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Availability") (td (~tw :tokens "p-2") "Long-lived, always on") (td (~tw :tokens "p-2") "Ephemeral, online when open"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Trust") (td (~tw :tokens "p-2") "CID verification + blockchain anchor") (td (~tw :tokens "p-2") "CID verification (same guarantee)"))))
|
||||
|
||||
(p "A component fetched from IPFS, from a server, from another browser tab, "
|
||||
"or from local IndexedDB cache is verified the same way: hash it, check the CID. "
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
(~docs/section :title "Phase 1: Browser Node Foundation" :id "phase-1"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Make the browser a first-class participant, not just a viewer.")
|
||||
|
||||
(~docs/subsection :title "1a. Content Store (IndexedDB)"
|
||||
@@ -52,7 +52,7 @@
|
||||
"lisp"))
|
||||
|
||||
(p "Two IndexedDB object stores:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "blobs") " — CID → SX source bytes (content-addressed, immutable)")
|
||||
(li (strong "pins") " — CID → {pinned, last-accessed, size, source-node} (eviction metadata)"))
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"Everything else (verification, pinning, eviction) is pure SX."))
|
||||
|
||||
(~docs/subsection :title "1d. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/content-store.sx") " — browser content store (~120 LOC)")
|
||||
(li (code "web/lib/sw-routes.sx") " — Service Worker CID routing (~80 LOC)")
|
||||
(li "Platform primitive: " (code "content-hash") " in JS host (~15 LOC)")
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
(~docs/section :title "Phase 2: Peer Network" :id "phase-2"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Three transports, one SX protocol. Browser↔server via WebSocket, "
|
||||
"browser↔browser via WebRTC data channels, server↔server via federation. "
|
||||
"The protocol layer doesn't know or care which pipe carries it.")
|
||||
@@ -110,13 +110,13 @@
|
||||
|
||||
(~docs/subsection :title "2b. Three Transports"
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Link") (th :class "text-left p-2" "Transport") (th :class "text-left p-2" "Why")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Link") (th (~tw :tokens "text-left p-2") "Transport") (th (~tw :tokens "text-left p-2") "Why")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "Browser ↔ Home node") (td :class "p-2" "WebSocket") (td :class "p-2" "Reliable, always-on, works today, no infrastructure changes"))
|
||||
(tr (td :class "p-2 font-medium" "Browser ↔ Browser") (td :class "p-2" "WebRTC data channel") (td :class "p-2" "True peer-to-peer, NAT traversal via ICE/STUN, UDP + TCP modes"))
|
||||
(tr (td :class "p-2 font-medium" "Server ↔ Server") (td :class "p-2" "HTTP federation") (td :class "p-2" "Existing sx-pub protocol, signed activities"))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Browser ↔ Home node") (td (~tw :tokens "p-2") "WebSocket") (td (~tw :tokens "p-2") "Reliable, always-on, works today, no infrastructure changes"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Browser ↔ Browser") (td (~tw :tokens "p-2") "WebRTC data channel") (td (~tw :tokens "p-2") "True peer-to-peer, NAT traversal via ICE/STUN, UDP + TCP modes"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Server ↔ Server") (td (~tw :tokens "p-2") "HTTP federation") (td (~tw :tokens "p-2") "Existing sx-pub protocol, signed activities"))))
|
||||
|
||||
(p "The SX protocol layer is transport-agnostic. " (code "peer-send") " and " (code "peer-listen") " "
|
||||
"work identically whether the peer is connected via WebSocket, WebRTC, or HTTP. "
|
||||
@@ -144,7 +144,7 @@
|
||||
"lisp"))
|
||||
|
||||
(p "Peer-to-peer means:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Game state at LAN-like latency — no server round-trip")
|
||||
(li "Content exchange between nearby browsers without hitting IPFS")
|
||||
(li "Collaborative editing with sub-frame update propagation")
|
||||
@@ -179,7 +179,7 @@
|
||||
"The networking is invisible to the reactive layer."))
|
||||
|
||||
(~docs/subsection :title "2f. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/sx-sync.sx") " — protocol definition incl. shared signals (~300 LOC)")
|
||||
(li (code "web/lib/peer.sx") " — transport-agnostic peer abstraction (~200 LOC)")
|
||||
(li (code "web/lib/shared-signal.sx") " — shared signal primitive (~120 LOC)")
|
||||
@@ -193,7 +193,7 @@
|
||||
|
||||
(~docs/section :title "Phase 3: In-Browser Authoring" :id "phase-3"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Edit, preview, test, and publish from a browser tab. No local dev environment.")
|
||||
|
||||
(~docs/subsection :title "3a. Live Editor"
|
||||
@@ -230,7 +230,7 @@
|
||||
"Clean split: browsers create, servers distribute."))
|
||||
|
||||
(~docs/subsection :title "3d. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/editor.sx") " — live editor island (~300 LOC)")
|
||||
(li (code "web/lib/test-runner.sx") " — browser test runner (~150 LOC)")
|
||||
(li (code "web/lib/publish.sx") " — publish flow (~100 LOC)")
|
||||
@@ -242,7 +242,7 @@
|
||||
|
||||
(~docs/section :title "Phase 4: Component Discovery & Resolution" :id "phase-4"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Find, resolve, and depend on components across the federation.")
|
||||
|
||||
(~docs/subsection :title "4a. Dependency Resolution"
|
||||
@@ -267,7 +267,7 @@
|
||||
(p "The component browser is itself an SX component published to sx-pub."))
|
||||
|
||||
(~docs/subsection :title "4c. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/resolver.sx") " — dependency resolution chain (~200 LOC)")
|
||||
(li (code "sx/sx/tools/component-browser.sx") " — federated browser (~400 LOC)")
|
||||
(li (code "sx/sx/tools/search.sx") " — search interface (~150 LOC)")
|
||||
@@ -279,7 +279,7 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Node Bootstrap" :id "phase-5"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Standing up a new node: one command, minutes to first render.")
|
||||
|
||||
(~docs/subsection :title "5a. Server Node Bootstrap"
|
||||
@@ -301,7 +301,7 @@
|
||||
"The barrier to entry is typing an address."))
|
||||
|
||||
(~docs/subsection :title "5c. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Docker image: " (code "sx-node") " with OCaml host, IPFS, Caddy, PostgreSQL")
|
||||
(li "Bootstrap script: fetch specs, verify CIDs, initialize DB")
|
||||
(li "Seed peer protocol: advertise available specs + popular components")
|
||||
@@ -313,7 +313,7 @@
|
||||
|
||||
(~docs/section :title "Phase 6: AI Composition Layer" :id "phase-6"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"The federated graph is simultaneously training data, retrieval context, and composition target.")
|
||||
|
||||
(~docs/subsection :title "6a. Component Retrieval"
|
||||
@@ -333,14 +333,14 @@
|
||||
"more tests to validate against, more usage patterns to learn from.")
|
||||
|
||||
(p "The network effect works for both humans and AI:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Every component published is more material for AI composition")
|
||||
(li "Every test attached is more validation for AI output")
|
||||
(li "Every fork is a preference signal for AI ranking")
|
||||
(li "Every pin is a popularity signal for AI retrieval")))
|
||||
|
||||
(~docs/subsection :title "6c. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/ai-compose.sx") " — composition interface (~200 LOC)")
|
||||
(li "Embedding index over component metadata + signatures")
|
||||
(li "Retrieval API: search by intent, filter by quality signals")
|
||||
@@ -366,19 +366,19 @@
|
||||
|
||||
(~docs/section :title "What Already Exists (sx-pub Phases 1–4)" :id "existing"
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Capability") (th :class "text-left p-2" "Status") (th :class "text-left p-2" "Location")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Capability") (th (~tw :tokens "text-left p-2") "Status") (th (~tw :tokens "text-left p-2") "Location")))
|
||||
(tbody
|
||||
(tr (td :class "p-2" "IPFS async client") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/ipfs_client.py"))
|
||||
(tr (td :class "p-2" "Merkle tree + proofs") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/anchoring.py"))
|
||||
(tr (td :class "p-2" "OpenTimestamps anchoring") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/anchoring.py"))
|
||||
(tr (td :class "p-2" "ActivityPub federation") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/infrastructure/activitypub.py"))
|
||||
(tr (td :class "p-2" "sx-pub HTTP handlers") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "sx/sx/handlers/pub-api.sx"))
|
||||
(tr (td :class "p-2" "Publishing + CID resolution") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "sx/sxc/pages/pub_helpers.py"))
|
||||
(tr (td :class "p-2" "SX wire format (aser)") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "web/adapter-sx.sx"))
|
||||
(tr (td :class "p-2" "Content store (in-memory)") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "lib/content.sx"))
|
||||
(tr (td :class "p-2" "Component localStorage cache") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "web/boot.sx"))
|
||||
(tr (td :class "p-2" "Docker dev environment") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "docker-compose.dev-pub.yml"))))
|
||||
(tr (td (~tw :tokens "p-2") "IPFS async client") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "shared/utils/ipfs_client.py"))
|
||||
(tr (td (~tw :tokens "p-2") "Merkle tree + proofs") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "shared/utils/anchoring.py"))
|
||||
(tr (td (~tw :tokens "p-2") "OpenTimestamps anchoring") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "shared/utils/anchoring.py"))
|
||||
(tr (td (~tw :tokens "p-2") "ActivityPub federation") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "shared/infrastructure/activitypub.py"))
|
||||
(tr (td (~tw :tokens "p-2") "sx-pub HTTP handlers") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "sx/sx/handlers/pub-api.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Publishing + CID resolution") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "sx/sxc/pages/pub_helpers.py"))
|
||||
(tr (td (~tw :tokens "p-2") "SX wire format (aser)") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "web/adapter-sx.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Content store (in-memory)") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "lib/content.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Component localStorage cache") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "web/boot.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Docker dev environment") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "docker-compose.dev-pub.yml"))))
|
||||
|
||||
(p "The server-side infrastructure is complete. The plan above extends it to browsers."))))
|
||||
|
||||
@@ -4,26 +4,26 @@
|
||||
|
||||
;; Helper: render a Phase 1 result row
|
||||
(defcomp ~plans/theorem-prover/prove-phase1-row (&key (name :as string) (status :as string))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "py-1.5 px-3 font-mono text-xs text-stone-700" name)
|
||||
(td :class "py-1.5 px-3 text-xs"
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "py-1.5 px-3 font-mono text-xs text-stone-700") name)
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs")
|
||||
(if (= status "sat")
|
||||
(span :class "text-emerald-600 font-medium" "sat")
|
||||
(span :class "text-red-600 font-medium" status)))))
|
||||
(span (~tw :tokens "text-emerald-600 font-medium") "sat")
|
||||
(span (~tw :tokens "text-red-600 font-medium") status)))))
|
||||
|
||||
;; Helper: render a Phase 2 result row
|
||||
(defcomp ~plans/theorem-prover/prove-phase2-row (&key (name :as string) (status :as string) (tested :as number) (skipped :as number) (counterexample :as string?))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "py-1.5 px-3 font-mono text-xs text-stone-700" name)
|
||||
(td :class "py-1.5 px-3 text-xs"
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "py-1.5 px-3 font-mono text-xs text-stone-700") name)
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs")
|
||||
(if (= status "verified")
|
||||
(span :class "text-emerald-600 font-medium" "verified")
|
||||
(span :class "text-red-600 font-medium" "falsified")))
|
||||
(td :class "py-1.5 px-3 text-xs text-stone-500 text-right tabular-nums"
|
||||
(span (~tw :tokens "text-emerald-600 font-medium") "verified")
|
||||
(span (~tw :tokens "text-red-600 font-medium") "falsified")))
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs text-stone-500 text-right tabular-nums")
|
||||
(str tested))
|
||||
(td :class "py-1.5 px-3 text-xs text-stone-400 text-right tabular-nums"
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs text-stone-400 text-right tabular-nums")
|
||||
(str skipped))
|
||||
(td :class "py-1.5 px-3 text-xs font-mono text-red-600"
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs font-mono text-red-600")
|
||||
(or counterexample ""))))
|
||||
|
||||
|
||||
@@ -32,38 +32,38 @@
|
||||
|
||||
;; --- Intro ---
|
||||
(~docs/section :title "SX proves itself" :id "intro"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(code "prove.sx") " is a constraint solver and property prover written in SX. It takes the SX specification (" (code "primitives.sx") "), translates it to formal logic via " (code "z3.sx") ", and proves properties about the result. Every step in the pipeline is an s-expression program operating on other s-expressions.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Two proof modes. " (strong "Phase 1") " verifies that every primitive definition is satisfiable " (em "by construction") " — the definition is the model. " (strong "Phase 2") " goes further: it states algebraic properties (commutativity, associativity, transitivity, bounds) and verifies them by " (em "bounded model checking") " — exhaustive evaluation over integer domains, searching for counterexamples.")
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-sm text-violet-800 font-semibold mb-2" "Everything is SX")
|
||||
(p :class "text-sm text-violet-700"
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-sm text-violet-800 font-semibold mb-2") "Everything is SX")
|
||||
(p (~tw :tokens "text-sm text-violet-700")
|
||||
"No external solver. No Python proof logic. " (code "prove.sx") " is 400+ lines of s-expressions that parse SMT-LIB, evaluate expressions, generate test domains, compute cartesian products, search for counterexamples, and produce verification conditions. The same code would work client-side via the bootstrapped JavaScript evaluator.")))
|
||||
|
||||
;; --- Phase 1 Results ---
|
||||
(~docs/section :title "Phase 1: Definitional satisfiability" :id "phase1"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Every " (code "define-primitive") " with a " (code ":body") " produces a " (code "forall") " assertion in SMT-LIB. For example, " (code "(define-primitive \"inc\" :params (n) :body (+ n 1))") " becomes:")
|
||||
(~docs/code :src (highlight "; inc\n(declare-fun inc (Int) Int)\n(assert (forall (((n Int)))\n (= (inc n) (+ n 1))))\n(check-sat)" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is satisfiable by construction: define " (code "inc(n) = n + 1") " and the assertion holds. " (code "prove.sx") " verifies this mechanically for every primitive — it parses the SMT-LIB, extracts the definition, builds a model, and evaluates it with test values.")
|
||||
|
||||
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4 my-4"
|
||||
(p :class "text-sm font-semibold"
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-sm font-semibold")
|
||||
(if phase1-all-sat
|
||||
(span :class "text-emerald-800" (str phase1-sat "/" phase1-total " primitives: all sat"))
|
||||
(span :class "text-red-800" (str phase1-sat "/" phase1-total " sat (some failed)"))))
|
||||
(p :class "text-xs text-emerald-700 mt-1"
|
||||
(span (~tw :tokens "text-emerald-800") (str phase1-sat "/" phase1-total " primitives: all sat"))
|
||||
(span (~tw :tokens "text-red-800") (str phase1-sat "/" phase1-total " sat (some failed)"))))
|
||||
(p (~tw :tokens "text-xs text-emerald-700 mt-1")
|
||||
"Computed live in " (str phase1-ms) "ms"))
|
||||
|
||||
(~docs/subsection :title "Results"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 max-h-64 overflow-y-auto"
|
||||
(table :class "w-full text-left"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 max-h-64 overflow-y-auto")
|
||||
(table (~tw :tokens "w-full text-left")
|
||||
(thead
|
||||
(tr :class "bg-stone-100 sticky top-0"
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Primitive")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Status")))
|
||||
(tr (~tw :tokens "bg-stone-100 sticky top-0")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Primitive")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Status")))
|
||||
(tbody
|
||||
(map (fn (r)
|
||||
(~plans/theorem-prover/prove-phase1-row
|
||||
@@ -73,29 +73,29 @@
|
||||
|
||||
;; --- Phase 2 Results ---
|
||||
(~docs/section :title "Phase 2: Algebraic properties" :id "phase2"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Phase 1 proves internal consistency. Phase 2 proves " (em "external properties") " — mathematical laws that should hold across all inputs. Each property is defined as a test function evaluated over a bounded integer domain.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"For arity 1, the domain is [-50, 50] (101 values). For arity 2, [-20, 20] (1,681 pairs). For arity 3, [-8, 8] (4,913 triples). Properties with preconditions (like " (code "clamp-in-range") " requiring " (code "(<= lo hi)") ") skip value combinations that don't satisfy the precondition.")
|
||||
|
||||
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4 my-4"
|
||||
(p :class "text-sm font-semibold"
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-sm font-semibold")
|
||||
(if phase2-all-verified
|
||||
(span :class "text-emerald-800" (str phase2-verified "/" phase2-total " properties: all verified"))
|
||||
(span :class "text-red-800" (str phase2-verified "/" phase2-total " verified (" phase2-falsified " falsified)"))))
|
||||
(p :class "text-xs text-emerald-700 mt-1"
|
||||
(span (~tw :tokens "text-emerald-800") (str phase2-verified "/" phase2-total " properties: all verified"))
|
||||
(span (~tw :tokens "text-red-800") (str phase2-verified "/" phase2-total " verified (" phase2-falsified " falsified)"))))
|
||||
(p (~tw :tokens "text-xs text-emerald-700 mt-1")
|
||||
(str phase2-total-tested " constraint evaluations in " phase2-ms "ms")))
|
||||
|
||||
(~docs/subsection :title "Results"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 max-h-96 overflow-y-auto"
|
||||
(table :class "w-full text-left"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 max-h-96 overflow-y-auto")
|
||||
(table (~tw :tokens "w-full text-left")
|
||||
(thead
|
||||
(tr :class "bg-stone-100 sticky top-0"
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Property")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Status")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium text-right" "Tested")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium text-right" "Skipped")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Counterexample")))
|
||||
(tr (~tw :tokens "bg-stone-100 sticky top-0")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Property")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Status")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium text-right") "Tested")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium text-right") "Skipped")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Counterexample")))
|
||||
(tbody
|
||||
(map (fn (r)
|
||||
(~plans/theorem-prover/prove-phase2-row
|
||||
@@ -108,13 +108,13 @@
|
||||
|
||||
;; --- What the properties prove ---
|
||||
(~docs/section :title "What the properties prove" :id "properties"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"34 properties across seven categories. Each encodes a mathematical law that the SX primitives must obey.")
|
||||
|
||||
(div :class "space-y-4 mt-4"
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "Arithmetic")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "space-y-4 mt-4")
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "Arithmetic")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(+ a b) = (+ b a)") " commutativity. "
|
||||
(code "(+ (+ a b) c) = (+ a (+ b c))") " associativity. "
|
||||
(code "(+ a 0) = a") " identity. "
|
||||
@@ -122,40 +122,40 @@
|
||||
"Same for " (code "*") " — commutative, associative, identity, zero annihilation. "
|
||||
(code "(- a a) = 0") " subtraction inverse. Nine properties, all verified."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "inc / dec")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "inc / dec")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(inc n) = (+ n 1)") " and " (code "(dec n) = (- n 1)") " — definitional relationships. "
|
||||
(code "(dec (inc n)) = n") " and " (code "(inc (dec n)) = n") " — mutual inverses. "
|
||||
"These confirm that " (code "inc") " and " (code "dec") " are exactly " (code "+1") " and " (code "-1") ", not off-by-one."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "abs")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "abs")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(abs n) >= 0") " non-negativity. "
|
||||
(code "(abs (abs n)) = (abs n)") " idempotence. "
|
||||
(code "(abs n) = (abs (- 0 n))") " symmetry. "
|
||||
"Three properties that together characterize absolute value."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "Predicates")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "Predicates")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(odd? n) = (not (even? n))") " — " (code "odd?") " and " (code "even?") " are complementary. "
|
||||
(code "(even? n) = (= (mod n 2) 0)") " — " (code "even?") " matches modular arithmetic. "
|
||||
(code "(zero? n) = (= n 0)") " — " (code "zero?") " is exactly equality with zero. "
|
||||
(code "(not (not p)) = p") " — double negation elimination."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "min / max / clamp")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "min / max / clamp")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "min") " and " (code "max") " are commutative and satisfy their bounds: "
|
||||
(code "(min a b) <= a") " and " (code "(max a b) >= a") ". "
|
||||
(code "(min a b) + (max a b) = a + b") " — the min-max sum identity. "
|
||||
(code "clamp") " with precondition " (code "lo <= hi") ": result is always in " (code "[lo, hi]") ", equals " (code "x") " when already in range, and is idempotent."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "Comparison & inequality")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "Comparison & inequality")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(< a b) = (> b a)") " — flipped arguments. "
|
||||
(code "(<= a b) = (not (> a b))") " and " (code "(>= a b) = (not (< a b))") " — duality. "
|
||||
"Trichotomy: exactly one of " (code "<") ", " (code "=") ", " (code ">") " holds. "
|
||||
@@ -164,81 +164,81 @@
|
||||
|
||||
;; --- SMT-LIB output ---
|
||||
(~docs/section :title "SMT-LIB verification conditions" :id "smtlib"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Each property also generates SMT-LIB for unbounded verification by an external solver. The strategy: assert the " (em "negation") " of the universal property. If Z3 returns " (code "unsat") ", the property holds for " (em "all") " integers — not just the bounded domain.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(code "prove.sx") " reuses " (code "z3-expr") " from " (code "z3.sx") " to translate the property AST to SMT-LIB. Properties with preconditions use " (code "=>") " (implication). The same SX expression is both the bounded test and the formal verification condition.")
|
||||
(~docs/code :src (highlight smtlib-sample "lisp")))
|
||||
|
||||
;; --- What it tells us ---
|
||||
(~docs/section :title "What this tells us about SX" :id "implications"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Three things, at increasing depth.")
|
||||
|
||||
(~docs/subsection :title "1. The spec is internally consistent"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Phase 1 proves every " (code "define-primitive") " with a " (code ":body") " is satisfiable. The definition doesn't contradict itself. This is necessary but weak — it's true by construction. The value is mechanical verification: no typo, no copy-paste error, no accidental negation in any of the 91 definitions."))
|
||||
|
||||
(~docs/subsection :title "2. The primitives obey algebraic laws"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Phase 2 proves real mathematical properties hold across bounded domains. These aren't tautologies — they're constraints that " (em "could") " fail. "
|
||||
(code "(+ a b) = (+ b a)") " could fail if " (code "+") " had a subtle bug. "
|
||||
(code "clamp-in-range") " could fail if " (code "max") "/" (code "min") " were swapped. "
|
||||
"The " (str phase2-total-tested) " evaluations found zero counterexamples.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Bounded model checking is not a mathematical proof — it verifies over a finite domain. The SMT-LIB output bridges the gap: feed it to Z3 for a universal proof over all integers."))
|
||||
|
||||
(~docs/subsection :title "3. SX can reason about itself"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The deep result. The SX evaluator executes " (code "z3.sx") ", which reads SX spec files and emits formal logic. Then the SX evaluator executes " (code "prove.sx") ", which parses that logic and proves properties about it. The specification, the translator, and the prover are all written in the same language, operating on the same data structures.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The pipeline: " (code "SX spec") " → " (code "SX translator") " → " (code "formal logic") " → " (code "SX prover") " → " (code "proof") ". At every step, SX is both the subject and the tool. The system is verifying its own foundations using its own machinery.")
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4 mt-3"
|
||||
(p :class "text-sm text-amber-800 font-semibold mb-2" "The strange loop")
|
||||
(p :class "text-sm text-amber-700"
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4 mt-3")
|
||||
(p (~tw :tokens "text-sm text-amber-800 font-semibold mb-2") "The strange loop")
|
||||
(p (~tw :tokens "text-sm text-amber-700")
|
||||
"The SX spec defines primitives. " (code "z3.sx") " (written in SX, using those primitives) translates the spec to formal logic. " (code "prove.sx") " (written in SX, using those same primitives) proves properties about the logic. The primitives being verified are the same primitives doing the verifying. This is not circular — it's a fixed point. If the primitives were wrong, the proofs would fail."))))
|
||||
|
||||
;; --- The pipeline ---
|
||||
(~docs/section :title "The full pipeline" :id "pipeline"
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead (tr :class "text-left text-stone-500"
|
||||
(th :class "pb-2 pr-4" "Step")
|
||||
(th :class "pb-2 pr-4" "Input")
|
||||
(th :class "pb-2 pr-4" "Tool")
|
||||
(th :class "pb-2" "Output")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (tr (~tw :tokens "text-left text-stone-500")
|
||||
(th (~tw :tokens "pb-2 pr-4") "Step")
|
||||
(th (~tw :tokens "pb-2 pr-4") "Input")
|
||||
(th (~tw :tokens "pb-2 pr-4") "Tool")
|
||||
(th (~tw :tokens "pb-2") "Output")))
|
||||
(tbody
|
||||
(tr :class "text-stone-700 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "1")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "primitives.sx")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "z3.sx")
|
||||
(td :class "py-2" "SMT-LIB (formal logic)"))
|
||||
(tr :class "text-stone-700 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "2a")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "SMT-LIB")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "prove.sx (Phase 1)")
|
||||
(td :class "py-2" "sat (definitional consistency)"))
|
||||
(tr :class "text-stone-700 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "2b")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "property defs")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "prove.sx (Phase 2)")
|
||||
(td :class "py-2" "verified (algebraic laws)"))
|
||||
(tr :class "text-stone-700 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "3")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "properties")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "prove.sx → z3-expr")
|
||||
(td :class "py-2" "SMT-LIB for Z3 (unbounded)"))
|
||||
(tr :class "text-stone-400 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "4")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "SMT-LIB")
|
||||
(td :class "py-2 pr-4 font-mono text-xs italic" "Z3 (external)")
|
||||
(td :class "py-2 italic" "unsat (universal proof)")))))
|
||||
(p :class "text-stone-600 mt-4"
|
||||
(tr (~tw :tokens "text-stone-700 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "1")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "primitives.sx")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "z3.sx")
|
||||
(td (~tw :tokens "py-2") "SMT-LIB (formal logic)"))
|
||||
(tr (~tw :tokens "text-stone-700 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "2a")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "SMT-LIB")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "prove.sx (Phase 1)")
|
||||
(td (~tw :tokens "py-2") "sat (definitional consistency)"))
|
||||
(tr (~tw :tokens "text-stone-700 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "2b")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "property defs")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "prove.sx (Phase 2)")
|
||||
(td (~tw :tokens "py-2") "verified (algebraic laws)"))
|
||||
(tr (~tw :tokens "text-stone-700 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "3")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "properties")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "prove.sx → z3-expr")
|
||||
(td (~tw :tokens "py-2") "SMT-LIB for Z3 (unbounded)"))
|
||||
(tr (~tw :tokens "text-stone-400 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "4")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "SMT-LIB")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs italic") "Z3 (external)")
|
||||
(td (~tw :tokens "py-2 italic") "unsat (universal proof)")))))
|
||||
(p (~tw :tokens "text-stone-600 mt-4")
|
||||
"Steps 1-3 run on this page, live, in the SX evaluator. Step 4 requires an external Z3 installation — the SMT-LIB output above is ready to feed to it."))
|
||||
|
||||
;; --- Source ---
|
||||
(~docs/section :title "The source: prove.sx" :id "source"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The entire constraint solver is a single SX file. Key sections: "
|
||||
(code "smt-eval") " evaluates SMT-LIB expressions. "
|
||||
(code "prove-tuples") " generates cartesian products for bounded checking. "
|
||||
@@ -248,6 +248,6 @@
|
||||
(~docs/code :src (highlight prove-source "lisp")))
|
||||
|
||||
(~docs/section :title "The translator: z3.sx" :id "z3-source"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The translator that " (code "prove.sx") " depends on. SX expressions that walk other SX expressions and emit SMT-LIB strings. Both files together: ~760 lines of s-expressions, no host language logic.")
|
||||
(~docs/code :src (highlight z3-source "lisp")))))
|
||||
|
||||
@@ -8,48 +8,48 @@
|
||||
(~docs/section :title "The Opportunity" :id "opportunity"
|
||||
(p "SX already has types. Every primitive in " (code "primitives.sx") " declares " (code ":returns \"number\"") " or " (code ":returns \"boolean\"") ". Every IO primitive in " (code "boundary.sx") " declares " (code ":returns \"dict?\"") " or " (code ":returns \"any\"") ". Component params are named. The information exists — nobody checks it.")
|
||||
(p "A gradual type system makes this information useful. Annotations are optional. Unannotated code works exactly as before. Annotated code gets checked at registration time — zero runtime cost, errors before any request is served. The checker is a spec module (" (code "types.sx") "), bootstrapped to every host.")
|
||||
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/sx/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") " already verifies primitive properties by exhaustive search. Types serve a different purpose: they catch " (strong "composition errors") " — wrong argument passed to a component, mismatched return type piped into another function, missing keyword arg. The kind of bug you find by reading the stack trace and slapping your forehead."))
|
||||
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/sx/(etc.(plan.theorem-prover))" (~tw :tokens "text-violet-700 underline") "prove.sx") " already verifies primitive properties by exhaustive search. Types serve a different purpose: they catch " (strong "composition errors") " — wrong argument passed to a component, mismatched return type piped into another function, missing keyword arg. The kind of bug you find by reading the stack trace and slapping your forehead."))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; What already exists
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "What Already Exists" :id "existing"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Feature")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Where")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Types today")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Feature")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Where")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Types today")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitive return types")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "primitives.sx :returns")
|
||||
(td :class "px-3 py-2 text-stone-600" "\"number\", \"string\", \"boolean\", \"list\", \"dict\", \"any\""))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "IO primitive return types")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "boundary.sx :returns")
|
||||
(td :class "px-3 py-2 text-stone-600" "Same + \"dict?\", \"string?\", \"element\" — nullable types already appear"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitive param names")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "primitives.sx :params")
|
||||
(td :class "px-3 py-2 text-stone-600" "Named but untyped: " (code "(a b)") ", " (code "(&rest args)")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component param names")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "eval.sx parse-comp-params")
|
||||
(td :class "px-3 py-2 text-stone-600" (code "&key") " params, " (code "&rest") " children — named, untyped"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Runtime type predicates")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "primitives.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" (code "number?") ", " (code "string?") ", " (code "list?") ", " (code "dict?") ", " (code "nil?") ", " (code "symbol?") " — all exist"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Purity classification")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "deps.sx + boundary.py")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure vs IO — a binary type at the component level"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Property verification")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "prove.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Algebraic properties (commutativity, transitivity) verified by bounded model checking")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitive return types")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "primitives.sx :returns")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "\"number\", \"string\", \"boolean\", \"list\", \"dict\", \"any\""))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IO primitive return types")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "boundary.sx :returns")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Same + \"dict?\", \"string?\", \"element\" — nullable types already appear"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitive param names")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "primitives.sx :params")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Named but untyped: " (code "(a b)") ", " (code "(&rest args)")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component param names")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "eval.sx parse-comp-params")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") (code "&key") " params, " (code "&rest") " children — named, untyped"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Runtime type predicates")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "primitives.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") (code "number?") ", " (code "string?") ", " (code "list?") ", " (code "dict?") ", " (code "nil?") ", " (code "symbol?") " — all exist"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Purity classification")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "deps.sx + boundary.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure vs IO — a binary type at the component level"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Property verification")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "prove.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Algebraic properties (commutativity, transitivity) verified by bounded model checking")))))
|
||||
|
||||
(p "The foundation is solid. Primitives already have return types. Params already have names. Boundary enforcement already does structural analysis. Types extend this — they don't replace it."))
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
(p "The checker runs at registration time — after " (code "compute_all_deps") ", before serving. It walks every component's body AST and verifies that call sites match declared signatures.")
|
||||
|
||||
(~docs/subsection :title "What it checks"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Primitive calls:") " " (code "(+ \"hello\" 3)") " — " (code "+") " expects numbers, got a string. Error.")
|
||||
(li (strong "Component calls:") " " (code "(~plans/typed-sx/product-card :title 42)") " — " (code ":title") " declared as " (code "string") ", got " (code "number") ". Error.")
|
||||
(li (strong "Missing required params:") " " (code "(~plans/typed-sx/product-card :price 29.99)") " — " (code ":title") " not provided, no default. Error.")
|
||||
@@ -113,16 +113,16 @@
|
||||
(li (strong "Thread-first type flow:") " " (code "(-> items (filter active?) (map name) (join \", \"))") " — checks each step's output matches the next step's input.")))
|
||||
|
||||
(~docs/subsection :title "What it does NOT check"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Runtime values.") " " (code "(if condition 42 \"hello\")") " — the type is " (code "(or number string)") ". The checker doesn't know which branch executes.")
|
||||
(li (strong "Dict key presence (yet).") " " (code "(get user \"name\")") " — the checker knows " (code "get") " returns " (code "any") " but doesn't track which keys a dict has. Phase 6 (" (code "deftype") " records) will enable this.")
|
||||
(li (strong "Termination.") " That's " (a :href "/sx/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") "'s domain.")
|
||||
(li (strong "Termination.") " That's " (a :href "/sx/(etc.(plan.theorem-prover))" (~tw :tokens "text-violet-700 underline") "prove.sx") "'s domain.")
|
||||
(li (strong "Full algebraic effects.") " The effect system (Phase 7) checks static effect annotations — it does not provide algebraic effect handlers, effect polymorphism, or continuation-based effect dispatch. That door remains open for the future.")))
|
||||
|
||||
|
||||
(~docs/subsection :title "Inference"
|
||||
(p "Most types are inferred, not annotated. The checker knows:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Literal types: " (code "42") " → " (code "number") ", " (code "\"hi\"") " → " (code "string") ", " (code "true") " → " (code "boolean") ", " (code "nil") " → " (code "nil"))
|
||||
(li "Primitive return types: " (code "(+ a b)") " → " (code "number") ", " (code "(str x)") " → " (code "string") ", " (code "(empty? x)") " → " (code "boolean"))
|
||||
(li "Let bindings: " (code "(let ((x 42)) ...)") " → " (code "x : number"))
|
||||
@@ -138,7 +138,7 @@
|
||||
(~docs/section :title "Gradual Semantics" :id "gradual"
|
||||
(p "The type " (code "any") " is the escape hatch. It's compatible with everything — passes every check, accepts every value. Unannotated params are " (code "any") ". The return type of " (code "get") " is " (code "any") ". This means:")
|
||||
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-2"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Fully untyped code:") " All params are " (code "any") ", all returns are " (code "any") ". The checker has nothing to verify. No errors, no warnings. Exactly the same as today.")
|
||||
(li (strong "Partially typed:") " Some components annotate params, others don't. The checker verifies annotated call sites and skips untyped ones. You get value proportional to effort.")
|
||||
(li (strong "Fully typed:") " Every component, every lambda, every let binding. The checker catches every composition error. Maximum value, maximum annotation cost."))
|
||||
@@ -157,25 +157,25 @@
|
||||
(~docs/code :src (highlight ";; Example error output:\n;;\n;; TYPE ERROR in ~checkout-summary (checkout.sx:34)\n;;\n;; (str \"Total: \" (compute-total items))\n;; ^^^^^^^^^^^^^^^^^\n;; Argument 2 of `str` expects: string\n;; Got: number (from compute-total :returns number)\n;;\n;; Fix: (str \"Total: \" (str (compute-total items)))\n;;\n;;\n;; TYPE ERROR in ~reactive-islands/event-bridge/product-page (products.sx:12)\n;;\n;; (~plans/typed-sx/product-card :title product-name :price \"29.99\")\n;; ^^^^^^\n;; Keyword :price of ~plans/typed-sx/product-card expects: number\n;; Got: string (literal \"29.99\")\n;;\n;; Fix: (~plans/typed-sx/product-card :title product-name :price 29.99)" "lisp"))
|
||||
|
||||
(p "Severity levels:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Level")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "When")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Behavior")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Level")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "When")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Behavior")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-red-700" "Error")
|
||||
(td :class "px-3 py-2 text-stone-700" "Definite type mismatch: " (code "number") " where " (code "string") " expected")
|
||||
(td :class "px-3 py-2 text-stone-600" "Strict mode: startup crash. Permissive: logged warning."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-amber-700" "Warning")
|
||||
(td :class "px-3 py-2 text-stone-700" "Possible mismatch: " (code "any") " where " (code "number") " expected, unknown kwarg")
|
||||
(td :class "px-3 py-2 text-stone-600" "Logged, never crashes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-500" "Info")
|
||||
(td :class "px-3 py-2 text-stone-700" "Annotation suggestion: frequently-called untyped component")
|
||||
(td :class "px-3 py-2 text-stone-600" "Dev mode only")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-red-700") "Error")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Definite type mismatch: " (code "number") " where " (code "string") " expected")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Strict mode: startup crash. Permissive: logged warning."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-amber-700") "Warning")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Possible mismatch: " (code "any") " where " (code "number") " expected, unknown kwarg")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Logged, never crashes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-500") "Info")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Annotation suggestion: frequently-called untyped component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Dev mode only")))))
|
||||
|
||||
(p (code "SX_TYPE_STRICT=1") " (env var, like " (code "SX_BOUNDARY_STRICT") ") makes type errors fatal at startup. Absent = permissive. Same pattern as boundary enforcement."))
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
(~docs/code :src (highlight ";; Before: runtime error if user is nil\n(defcomp ~plans/typed-sx/greeting (&key (user : dict?))\n (h1 (str \"Hello, \" (get user \"name\"))))\n ;; ^^^ TYPE WARNING: user is dict?, get needs non-nil first arg\n\n;; After: checker enforces nil handling\n(defcomp ~plans/typed-sx/greeting (&key (user : dict?))\n (if user\n (h1 (str \"Hello, \" (get user \"name\")))\n ;; In this branch, checker narrows user to `dict` (not nil)\n (h1 \"Hello, guest\")))\n ;; No warning — nil case handled" "lisp"))
|
||||
|
||||
(p "Narrowing rules:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "(if x then else)") " — in " (code "then") ", " (code "x") " is narrowed to exclude " (code "nil") " and " (code "false"))
|
||||
(li (code "(when x body)") " — in " (code "body") ", " (code "x") " is narrowed")
|
||||
(li (code "(nil? x)") " in an if test — " (code "then") " branch: " (code "x") " is " (code "nil") ", " (code "else") " branch: " (code "x") " is non-nil")
|
||||
@@ -206,7 +206,7 @@
|
||||
(~docs/code :src (highlight ";; Definition\n(defcomp ~plans/typed-sx/product-card (&key (title : string)\n (price : number)\n (image-url : string?)\n &rest children)\n ...)\n\n;; Call site checks:\n(~plans/typed-sx/product-card :title \"Widget\" :price 29.99) ;; OK\n(~plans/typed-sx/product-card :title \"Widget\") ;; ERROR: :price required\n(~plans/typed-sx/product-card :title 42 :price 29.99) ;; ERROR: :title expects string\n(~plans/typed-sx/product-card :title \"Widget\" :price 29.99\n (p \"Description\") (p \"Details\")) ;; OK: children\n(~plans/typed-sx/product-card :titel \"Widget\" :price 29.99) ;; WARNING: :titel unknown\n ;; (did you mean :title?)" "lisp"))
|
||||
|
||||
(p "The checker walks every component call in every component body. For each call:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Look up the callee component in env")
|
||||
(li "Match provided keyword args against declared params")
|
||||
(li "Check each arg's inferred type against the param's declared type")
|
||||
@@ -231,35 +231,35 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Types vs Proofs" :id "types-vs-proofs"
|
||||
(p (a :href "/sx/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:")
|
||||
(p (a :href "/sx/(etc.(plan.theorem-prover))" (~tw :tokens "text-violet-700 underline") "prove.sx") " and types.sx are complementary, not competing:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "types.sx")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "prove.sx")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "types.sx")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "prove.sx")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Checks")
|
||||
(td :class "px-3 py-2 text-stone-700" "Composition: does A's output fit B's input?")
|
||||
(td :class "px-3 py-2 text-stone-700" "Properties: is + commutative? Is sort stable?"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Scope")
|
||||
(td :class "px-3 py-2 text-stone-700" "All component bodies, every call site")
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitives only (declared in primitives.sx)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Method")
|
||||
(td :class "px-3 py-2 text-stone-700" "Type inference + checking (fast, O(n) AST walk)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Bounded model checking (exhaustive, slower)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "When")
|
||||
(td :class "px-3 py-2 text-stone-700" "Registration time (every startup)")
|
||||
(td :class "px-3 py-2 text-stone-700" "CI / on-demand (not every startup)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Catches")
|
||||
(td :class "px-3 py-2 text-stone-700" "Wrong arg type, missing param, nil misuse")
|
||||
(td :class "px-3 py-2 text-stone-700" "Algebraic law violations, edge case failures")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Checks")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Composition: does A's output fit B's input?")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Properties: is + commutative? Is sort stable?"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Scope")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "All component bodies, every call site")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitives only (declared in primitives.sx)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Method")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Type inference + checking (fast, O(n) AST walk)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bounded model checking (exhaustive, slower)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "When")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Registration time (every startup)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CI / on-demand (not every startup)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Catches")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Wrong arg type, missing param, nil misuse")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Algebraic law violations, edge case failures")))))
|
||||
|
||||
(p "Types answer: " (em "\"does this code fit together?\"") " Proofs answer: " (em "\"does this code do the right thing?\"") " Both are spec modules, both bootstrapped, both run without external tools."))
|
||||
|
||||
@@ -297,32 +297,32 @@
|
||||
(~docs/code :src (highlight ";; Declare named effects\n(defeffect io) ;; Database, HTTP, file system\n(defeffect dom) ;; Browser DOM manipulation\n(defeffect async) ;; Asynchronous operations\n(defeffect state) ;; Mutable state (set!, dict-set!, append!)\n\n;; Functions declare their effects in brackets\n(define fetch-user : (-> number user) [io async]\n (fn (id) (query \"SELECT * FROM users WHERE id = $1\" id)))\n\n(define toggle-class : (-> element string nil) [dom]\n (fn (el cls) (set-attr! el :class cls)))\n\n;; Pure by default — no annotation means no effects\n(define add-prices : (-> (list-of number) number)\n (fn (prices) (reduce + 0 prices)))" "lisp")))
|
||||
|
||||
(~docs/subsection :title "What it checks"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Pure functions can't call effectful ones.") " A function with no effect annotation calling " (code "fetch-user") " (which has " (code "[io async]") ") is an error. The IO leaked into pure code.")
|
||||
(li (strong "Components declare their effect ceiling.") " A " (code "[pure]") " component can only call pure functions. A " (code "[io]") " component can call IO but not DOM. This is the render-mode safety guarantee.")
|
||||
(li (strong "Render modes enforce effect sets.") " " (code "render-to-html") " (server) allows " (code "[io]") " but not " (code "[dom]") ". " (code "render-to-dom") " (browser) allows " (code "[dom]") " but not " (code "[io]") ". " (code "aser") " (wire format) allows " (code "[io]") " for evaluation but serializes the result.")
|
||||
(li (strong "Islands are the effect boundary.") " Server effects (" (code "io") ") can't cross into client island code. Client effects (" (code "dom") ") can't leak into server rendering. Currently this is convention — effects make it a proof.")))
|
||||
|
||||
(~docs/subsection :title "Three effect sets match three render modes"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Render mode")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Allowed effects")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Forbidden")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Render mode")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Allowed effects")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Forbidden")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "render-to-html")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "pure") ", " (code "io") ", " (code "async") ", " (code "state"))
|
||||
(td :class "px-3 py-2 text-red-600" (code "dom")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "render-to-dom")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "pure") ", " (code "dom") ", " (code "state"))
|
||||
(td :class "px-3 py-2 text-red-600" (code "io") ", " (code "async")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "aser (wire)")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "pure") ", " (code "io") ", " (code "async") ", " (code "state"))
|
||||
(td :class "px-3 py-2 text-red-600" (code "dom"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "render-to-html")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "pure") ", " (code "io") ", " (code "async") ", " (code "state"))
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") (code "dom")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "render-to-dom")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "pure") ", " (code "dom") ", " (code "state"))
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") (code "io") ", " (code "async")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "aser (wire)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "pure") ", " (code "io") ", " (code "async") ", " (code "state"))
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") (code "dom"))))))
|
||||
|
||||
(p "This is exactly the information " (code "deps.sx") " already computes — which components have IO refs. Effects promote it from a runtime classification to a static type-level property. Pure components get an ironclad guarantee: memoize, cache, SSR anywhere, serialize for client — provably safe."))
|
||||
|
||||
@@ -338,22 +338,22 @@
|
||||
|
||||
(~docs/subsection :title "Relationship to deps.sx and boundary.sx"
|
||||
(p "Effects don't replace the existing systems — they formalize them:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "boundary.sx") " declares which primitives are IO. Effects declare which " (em "functions") " use IO.")
|
||||
(li (code "deps.sx") " computes transitive IO refs at registration time. Effects check them at type-check time — earlier, with better error messages.")
|
||||
(li "The boundary is still the source of truth for " (em "what is IO") ". Effects are the enforcement mechanism for " (em "who can use it") "."))
|
||||
(p "Long term, " (code "deps.sx") "'s IO classification can be derived from effect annotations. In the short term, both coexist — effects are checked, deps are computed, both must agree."))
|
||||
|
||||
(~docs/subsection :title "What it does NOT do"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "No algebraic effect handlers.") " You can't intercept and resume effects. This would require delimited continuations in every bootstrapper target — massive complexity for marginal UI benefit.")
|
||||
(li (strong "No effect polymorphism.") " You can't write a function generic over effects (" (code "forall e. (-> a [e] b)") "). This needs higher-kinded effect types — the same complexity as type classes.")
|
||||
(li (strong "No effect inference.") " Effects are declared, not inferred. Inference would require whole-program analysis and produce confusing error messages.")
|
||||
(li (strong "No runtime cost.") " Effects are erased after checking. The bootstrapped code has zero overhead. This is purely a static guarantee."))
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "The door stays open.")
|
||||
(p :class "text-violet-800" "Static effect checking is the pragmatic middle — 80% of the benefit of a full effect system, 20% of the complexity. If SX ever needs algebraic handlers (e.g. for suspense, or cooperative scheduling beyond what " (code "shift") "/" (code "reset") " provides), the annotation syntax is already in place. Add handlers later without changing any existing code."))))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The door stays open.")
|
||||
(p (~tw :tokens "text-violet-800") "Static effect checking is the pragmatic middle — 80% of the benefit of a full effect system, 20% of the complexity. If SX ever needs algebraic handlers (e.g. for suspense, or cooperative scheduling beyond what " (code "shift") "/" (code "reset") " provides), the annotation syntax is already in place. Add handlers later without changing any existing code."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Implementation
|
||||
@@ -363,7 +363,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 1: Type Registry (done)"
|
||||
(p "Build the type registry from existing declarations.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Parse " (code ":returns") " from " (code "primitives.sx") " and " (code "boundary.sx") " into a type map: " (code "primitive-name → return-type"))
|
||||
(li "Parse " (code ":params") " declarations into param type maps (currently untyped — default to " (code "any") ")")
|
||||
(li "Compute component signatures from " (code "parse-comp-params") " + any type annotations")
|
||||
@@ -371,7 +371,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 2: Type Inference Engine (done)"
|
||||
(p "Walk AST, infer types bottom-up.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Literals → concrete types")
|
||||
(li "Primitive calls → look up return type in registry")
|
||||
(li "Component calls → " (code "element"))
|
||||
@@ -382,7 +382,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 3: Type Checker (done)"
|
||||
(p "Compare inferred types at call sites against declared types.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Subtype check: " (code "number") " <: " (code "any") ", " (code "string") " <: " (code "string?") ", " (code "nil") " <: " (code "string?"))
|
||||
(li "Error on definite mismatch: " (code "number") " vs " (code "string"))
|
||||
(li "Warn on possible mismatch: " (code "any") " vs " (code "number") " (might work, might not)")
|
||||
@@ -390,7 +390,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 4: Annotation Parsing (done)"
|
||||
(p "Extend " (code "parse-comp-params") " and " (code "sf-defcomp") " to recognize type annotations.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "(name : type)") " in param lists → extract type, store in component metadata")
|
||||
(li (code ":returns type") " in lambda/fn bodies → store as declared return type")
|
||||
(li "Backward compatible: unannotated params remain " (code "any"))))
|
||||
@@ -402,7 +402,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 6: User-Defined Types (deftype)"
|
||||
(p "Extend the type system with named user-defined types.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "deftype") " special form — parsed by evaluator, stored in type registry")
|
||||
(li "Type aliases: " (code "(deftype price number)") " → transparent substitution")
|
||||
(li "Union types: " (code "(deftype renderable (union string number nil))") " → checked via " (code "subtype?"))
|
||||
@@ -414,7 +414,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 7: Static Effect System"
|
||||
(p "Add effect annotations and static checking. No handlers, no runtime cost.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "defeffect") " declaration form — registers named effects: " (code "io") ", " (code "dom") ", " (code "async") ", " (code "state"))
|
||||
(li "Effect annotation syntax: " (code "[io async]") " after function type or on " (code "defcomp"))
|
||||
(li "Extend " (code "check-body-walk") " — verify called functions' effects are subset of caller's declared effects")
|
||||
@@ -432,48 +432,48 @@
|
||||
(~docs/section :title "Spec Module" :id "spec-module"
|
||||
(p (code "types.sx") " — the type checker, written in SX, bootstrapped to every host.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Function")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Function")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "infer-type")
|
||||
(td :class "px-3 py-2 text-stone-700" "Infer the type of an AST node in a given type environment"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-call")
|
||||
(td :class "px-3 py-2 text-stone-700" "Check a function/component call against declared signature"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-component")
|
||||
(td :class "px-3 py-2 text-stone-700" "Type-check an entire component body"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-all")
|
||||
(td :class "px-3 py-2 text-stone-700" "Check all registered components, return error/warning list"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "subtype?")
|
||||
(td :class "px-3 py-2 text-stone-700" "Is type A a subtype of type B?"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "narrow-type")
|
||||
(td :class "px-3 py-2 text-stone-700" "Narrow a type based on a predicate test (nil?, string?, etc)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "type-union")
|
||||
(td :class "px-3 py-2 text-stone-700" "Compute the union of two types"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "parse-type-annotation")
|
||||
(td :class "px-3 py-2 text-stone-700" "Parse a type expression from annotation syntax"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "build-type-registry")
|
||||
(td :class "px-3 py-2 text-stone-700" "Build type map from primitives.sx + boundary.sx declarations"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-primitive-call")
|
||||
(td :class "px-3 py-2 text-stone-700" "Check a primitive call against declared param types (positional + rest)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "resolve-type")
|
||||
(td :class "px-3 py-2 text-stone-700" "Resolve a named type through the deftype registry (Phase 6)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-effects")
|
||||
(td :class "px-3 py-2 text-stone-700" "Verify effect annotations: callee effects ⊆ caller effects (Phase 7)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "infer-type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Infer the type of an AST node in a given type environment"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-call")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Check a function/component call against declared signature"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Type-check an entire component body"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-all")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Check all registered components, return error/warning list"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "subtype?")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Is type A a subtype of type B?"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "narrow-type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Narrow a type based on a predicate test (nil?, string?, etc)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "type-union")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Compute the union of two types"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "parse-type-annotation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Parse a type expression from annotation syntax"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "build-type-registry")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Build type map from primitives.sx + boundary.sx declarations"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-primitive-call")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Check a primitive call against declared param types (positional + rest)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "resolve-type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Resolve a named type through the deftype registry (Phase 6)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-effects")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Verify effect annotations: callee effects ⊆ caller effects (Phase 7)")))))
|
||||
|
||||
(p "The checker is a growing spec module — currently ~650 lines of SX. It's an AST walk with a type environment, structurally similar to " (code "deps.sx") " (which walks ASTs to find IO refs) and " (code "prove.sx") " (which walks ASTs to generate verification conditions). Same pattern, different question."))
|
||||
|
||||
@@ -482,15 +482,15 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Relationships" :id "relationships"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (a :href "/sx/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
|
||||
(li (a :href "/sx/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.")
|
||||
(li (a :href "/sx/(etc.(plan.environment-images))" :class "text-violet-700 underline" "Environment Images") " — the type registry serializes into the image. Type checking happens once at image build time, not on every startup.")
|
||||
(li (a :href "/sx/(etc.(plan.runtime-slicing))" :class "text-violet-700 underline" "Runtime Slicing") " — types.sx is a registration-time module, not a runtime module. It doesn't ship to the client. Zero impact on bundle size."))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (a :href "/sx/(etc.(plan.theorem-prover))" (~tw :tokens "text-violet-700 underline") "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
|
||||
(li (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "text-violet-700 underline") "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.")
|
||||
(li (a :href "/sx/(etc.(plan.environment-images))" (~tw :tokens "text-violet-700 underline") "Environment Images") " — the type registry serializes into the image. Type checking happens once at image build time, not on every startup.")
|
||||
(li (a :href "/sx/(etc.(plan.runtime-slicing))" (~tw :tokens "text-violet-700 underline") "Runtime Slicing") " — types.sx is a registration-time module, not a runtime module. It doesn't ship to the client. Zero impact on bundle size."))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "primitives.sx (return + param types), boundary.sx (IO return types + IO classification), eval.sx (defcomp/deftype/defeffect parsing), deps.sx (IO detection — effects formalize this). " (strong "New: ") (code "types.sx") " spec module, type annotations in " (code "parse-comp-params") ", " (code "deftype") " declaration form, " (code "defeffect") " declaration form."))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Depends on: ") "primitives.sx (return + param types), boundary.sx (IO return types + IO classification), eval.sx (defcomp/deftype/defeffect parsing), deps.sx (IO detection — effects formalize this). " (strong "New: ") (code "types.sx") " spec module, type annotations in " (code "parse-comp-params") ", " (code "deftype") " declaration form, " (code "defeffect") " declaration form."))
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "Why not a Haskell host?")
|
||||
(p :class "text-violet-800" "A Haskell SX host would type-check the " (em "host") " code (the evaluator, renderer, parser). But it can't type-check " (em "SX programs") " — those are dynamically typed values passing through " (code "SxVal") ". " (code "types.sx") " checks SX programs directly, on every host. One spec, all hosts benefit. The type system lives where it matters — in the language, not in any particular implementation of it."))))))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "Why not a Haskell host?")
|
||||
(p (~tw :tokens "text-violet-800") "A Haskell SX host would type-check the " (em "host") " code (the evaluator, renderer, parser). But it can't type-check " (em "SX programs") " — those are dynamically typed values passing through " (code "SxVal") ". " (code "types.sx") " checks SX programs directly, on every host. One spec, all hosts benefit. The type system lives where it matters — in the language, not in any particular implementation of it."))))))
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Why" :id "why"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "Wire size") " — bytecode is far more compact than source text. No redundant whitespace, no comments, no repeated symbol names. A component bundle that's 40KB of SX source might be 8KB of bytecode.")
|
||||
(li (strong "No parse overhead") " — the browser currently parses every SX source string (tokenize → AST → eval). Bytecode skips parsing entirely.")
|
||||
(li (strong "Eval performance") " — a Rust VM with a tight dispatch loop is significantly faster than tree-walking in JavaScript. Matters for compute-heavy islands, large list rendering, complex CSSX calculations.")
|
||||
@@ -29,14 +29,14 @@
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(p "Three new layers, all specced in " (code ".sx") " and bootstrapped:")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "1. Bytecode format — bytecode.sx")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "1. Bytecode format — bytecode.sx")
|
||||
(p "A spec for the bytecode instruction set. Stack-based VM (simpler than register-based, natural fit for s-expressions). Instructions:")
|
||||
(~docs/code :src (highlight ";; Core instructions\nPUSH_CONST idx ;; push constant from pool\nPUSH_NIL ;; push nil\nPUSH_TRUE / PUSH_FALSE\nLOOKUP idx ;; look up symbol by index\nSET idx ;; define/set symbol\nCALL n ;; call top-of-stack with n args\nTAIL_CALL n ;; tail call (TCO)\nRETURN\nJUMP offset ;; unconditional jump\nJUMP_IF_FALSE offset ;; conditional jump\nMAKE_LAMBDA idx n_params ;; create closure\nMAKE_LIST n ;; collect n stack values into list\nMAKE_DICT n ;; collect 2n stack values into dict\nPOP ;; discard top\nDUP ;; duplicate top" "lisp"))
|
||||
(p "Bytecode modules contain: a " (strong "constant pool") " (strings, numbers, symbols), a " (strong "code section") " (instruction bytes), and a " (strong "metadata section") " (source maps, component/island declarations for the host to register).")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "2. Compiler — compile.sx")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "2. Compiler — compile.sx")
|
||||
(p "An SX-to-bytecode compiler, " (strong "written in SX") ". Takes parsed AST, emits bytecode modules. Handles:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Macro expansion") " — all macros expanded at compile time. The VM never sees macros.")
|
||||
(li (strong "Constant folding") " — pure expressions with known values computed at compile time.")
|
||||
(li (strong "Closure analysis") " — determines free variables for each lambda, emits efficient capture instructions.")
|
||||
@@ -44,9 +44,9 @@
|
||||
(li (strong "Component metadata") " — defcomp/defisland declarations are extracted and stored in the module metadata, so the host can register them without evaluating the body."))
|
||||
(p "Bootstrapped to Python (server-side compilation) and Rust (if self-compilation is needed).")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "3. VM — bootstrap_rs.py → Rust/WASM")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "3. VM — bootstrap_rs.py → Rust/WASM")
|
||||
(p "A Rust implementation of the SX platform interface. The bootstrapper (" (code "bootstrap_rs.py") ") translates the spec to Rust source, which compiles to both:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Native binary") " — for server-side evaluation (replaces Python evaluators entirely)")
|
||||
(li (strong "WASM module") " — for browser-side evaluation (replaces sx-browser.js)")))
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
(~docs/section :title "DOM Interop" :id "dom-interop"
|
||||
(p "The main engineering challenge. Every DOM operation crosses the WASM↔JS boundary. Two strategies:")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Strategy A: Direct calls")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Strategy A: Direct calls")
|
||||
(p "Each DOM operation (" (code "createElement") ", " (code "setAttribute") ", " (code "appendChild") ") is a separate WASM→JS call. Simple, works, but ~50ns overhead per call. For a page with 1,000 DOM operations, that's ~50μs — negligible.")
|
||||
(~docs/code :src (highlight "// JS side — imported by WASM\nfunction domCreateElement(tag_ptr, tag_len) {\n const tag = readString(tag_ptr, tag_len);\n return storeHandle(document.createElement(tag));\n}\n\n// Rust side\nextern \"C\" { fn dom_create_element(tag: *const u8, len: u32) -> u32; }" "javascript"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Strategy B: Command buffer")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Strategy B: Command buffer")
|
||||
(p "Batch DOM operations in WASM memory as a command buffer. Flush to JS in one call. JS walks the buffer and applies all operations. Fewer boundary crossings, but more complex.")
|
||||
(~docs/code :src (highlight ";; Command buffer format (in shared WASM memory)\n;; [CREATE_ELEMENT, tag_idx, handle_out]\n;; [SET_ATTR, handle, key_idx, val_idx]\n;; [APPEND_CHILD, parent_handle, child_handle]\n;; [SET_TEXT, handle, text_idx]\n;; Then: (flush-dom-commands)" "lisp"))
|
||||
(p "Strategy A is simpler and sufficient for SX workloads. Strategy B is an optimisation if profiling shows the boundary crossing matters. " (strong "Start with A, measure, switch to B only if needed.")))
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
(~docs/section :title "String Handling" :id "strings"
|
||||
(p "WASM has no native string type. Strings must cross the boundary via shared " (code "ArrayBuffer") " memory. Options:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Copy on crossing") " — encode to UTF-8 in WASM linear memory, JS reads via " (code "TextDecoder") ". Simple, safe, ~1μs per string.")
|
||||
(li (strong "String interning") " — the constant pool already interns all string literals. Assign each a numeric ID. DOM operations reference strings by ID. JS maintains a parallel string table. Strings never cross the boundary — only IDs do.")
|
||||
(li (strong "Hybrid") " — intern constants (attribute names, tag names, class names), copy dynamic strings (computed CSS values, interpolated text)."))
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
(~docs/section :title "Memory & Closures" :id "memory"
|
||||
(p "SX values that the VM must manage:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Closures") " — lambda captures free variables. Rust: " (code "Rc<Closure>") " with captured env as " (code "Vec<Value>") ".")
|
||||
(li (strong "Signals") " — reference-counted mutable cells with subscriber lists. Subscribers hold weak references to computed nodes to avoid cycles.")
|
||||
(li (strong "Lists/Dicts") " — immutable by convention (SX doesn't mutate collections). Arena-allocate per evaluation, free the arena when done.")
|
||||
@@ -97,33 +97,33 @@
|
||||
|
||||
(~docs/section :title "What Gets Compiled" :id "compilation"
|
||||
(p "Not everything needs bytecode. The compilation boundary follows the existing server/client split:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Content")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Format")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Content")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Format")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component definitions")
|
||||
(td :class "px-3 py-2 text-stone-700" "Bytecode")
|
||||
(td :class "px-3 py-2 text-stone-600" "Evaluated on every page load, benefits from fast dispatch"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Client library (@client files)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Bytecode")
|
||||
(td :class "px-3 py-2 text-stone-600" "CSSX functions, colour computation — pure code that runs client-side"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Page content (SX wire responses)")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX source or bytecode")
|
||||
(td :class "px-3 py-2 text-stone-600" "Wire responses are small, parse overhead minimal. Bytecode optional."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Macros")
|
||||
(td :class "px-3 py-2 text-stone-700" "Expanded at compile time")
|
||||
(td :class "px-3 py-2 text-stone-600" "VM never sees macros — they're pure compile-time constructs"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component definitions")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Evaluated on every page load, benefits from fast dispatch"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client library (@client files)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "CSSX functions, colour computation — pure code that runs client-side"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Page content (SX wire responses)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX source or bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Wire responses are small, parse overhead minimal. Bytecode optional."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Macros")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Expanded at compile time")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "VM never sees macros — they're pure compile-time constructs"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Server-affinity components")
|
||||
(td :class "px-3 py-2 text-stone-700" "Not compiled")
|
||||
(td :class "px-3 py-2 text-stone-600" "Expanded server-side, never sent to client"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Server-affinity components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Not compiled")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Expanded server-side, never sent to client"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Bytecode vs direct WASM compilation
|
||||
@@ -131,37 +131,37 @@
|
||||
|
||||
(~docs/section :title "Bytecode VM vs Direct WASM Compilation" :id "vm-vs-direct"
|
||||
(p "Two paths to WASM. The choice matters:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Bytecode VM in WASM")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Compile SX → WASM directly")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Bytecode VM in WASM")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Compile SX → WASM directly")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Complexity")
|
||||
(td :class "px-3 py-2 text-stone-700" "Standard VM design — proven pattern")
|
||||
(td :class "px-3 py-2 text-stone-700" "Full compiler backend (SSA, register alloc, WASM codegen)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Dynamic loading")
|
||||
(td :class "px-3 py-2 text-stone-700" "Trivial — load bytecode module, eval")
|
||||
(td :class "px-3 py-2 text-stone-700" "Hard — must instantiate new WASM module per chunk"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "eval / REPL")
|
||||
(td :class "px-3 py-2 text-stone-700" "Works — compile + eval at runtime")
|
||||
(td :class "px-3 py-2 text-stone-700" "Impossible without bundling a compiler"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Performance")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fast — WASM dispatch loop, no JS overhead")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fastest — native WASM speed, no dispatch"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Module size")
|
||||
(td :class "px-3 py-2 text-stone-700" "One VM module (~100KB) + bytecode per page")
|
||||
(td :class "px-3 py-2 text-stone-700" "Per-page WASM modules, each self-contained"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Complexity")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Standard VM design — proven pattern")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Full compiler backend (SSA, register alloc, WASM codegen)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Dynamic loading")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Trivial — load bytecode module, eval")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Hard — must instantiate new WASM module per chunk"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "eval / REPL")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Works — compile + eval at runtime")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Impossible without bundling a compiler"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Performance")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fast — WASM dispatch loop, no JS overhead")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fastest — native WASM speed, no dispatch"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Module size")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "One VM module (~100KB) + bytecode per page")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Per-page WASM modules, each self-contained"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Debugging")
|
||||
(td :class "px-3 py-2 text-stone-700" "Source maps over bytecode")
|
||||
(td :class "px-3 py-2 text-stone-700" "DWARF debug info in WASM")))))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Debugging")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Source maps over bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DWARF debug info in WASM")))))
|
||||
(p (strong "Bytecode VM is the right choice.") " SX needs dynamic loading (HTMX responses inject new components), runtime eval (islands, reactive updates), and incremental compilation (page-by-page). Direct WASM compilation is better for static, ahead-of-time scenarios — not for a live hypermedia system."))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -172,7 +172,7 @@
|
||||
(p "The key insight: this is " (strong "not a replacement") " for the JS evaluator. It's " (strong "another compilation target from the same spec") ". The existing bootstrapper pipeline already proves this pattern:")
|
||||
(~docs/code :src (highlight "eval.sx ──→ bootstrap_js.py ──→ sx-ref.js (browser, JS eval)\n ──→ bootstrap_py.py ──→ sx_ref.py (server, Python eval)\n ──→ bootstrap_rs.py ──→ sx-vm.wasm (browser, WASM eval) ← new" "text"))
|
||||
(p "All three outputs have identical semantics because they're compiled from the same source. The choice of which to use is a " (strong "deployment decision") ", not an architectural one:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "JS-only") " — current default. Works everywhere. Zero WASM dependency. Ship sx-browser.js + SX source text.")
|
||||
(li (strong "WASM-only") " — maximum performance. Ship sx-vm.wasm + bytecode. Requires WASM support (99%+ of browsers).")
|
||||
(li (strong "Progressive") " — try WASM, fall back to JS. Ship both. The server sends bytecode in a " (code "<script type=\"text/sx-bytecode\">") " tag and source in " (code "<script type=\"text/sx\">") ". The boot script picks whichever runtime loaded.")
|
||||
@@ -185,48 +185,48 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Implementation Phases" :id "phases"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Deliverable")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Deliverable")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "1")
|
||||
(td :class "px-3 py-2 text-stone-700" "Spec the bytecode format in bytecode.sx. Instruction set, constant pool layout, module structure, encoding.")
|
||||
(td :class "px-3 py-2 text-stone-600" "bytecode.sx — format spec + serializer/deserializer"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "2")
|
||||
(td :class "px-3 py-2 text-stone-700" "Write the compiler in SX. AST → bytecode. Macro expansion, constant folding, tail call detection, closure analysis.")
|
||||
(td :class "px-3 py-2 text-stone-600" "compile.sx — bootstrapped to Python for server-side compilation"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "3")
|
||||
(td :class "px-3 py-2 text-stone-700" "Write bootstrap_rs.py — Rust bootstrapper. Translates spec to Rust source implementing the platform interface.")
|
||||
(td :class "px-3 py-2 text-stone-600" "bootstrap_rs.py + generated Rust crate"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "4")
|
||||
(td :class "px-3 py-2 text-stone-700" "Implement bytecode VM in Rust. Dispatch loop, value representation, closure/env model, GC strategy.")
|
||||
(td :class "px-3 py-2 text-stone-600" "sx-vm crate — native binary + WASM target"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "5")
|
||||
(td :class "px-3 py-2 text-stone-700" "JS bindings for DOM. WASM imports for createElement, setAttribute, appendChild, event listeners. Handle table.")
|
||||
(td :class "px-3 py-2 text-stone-600" "sx-vm-dom.js — JS glue layer (~2KB)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "6")
|
||||
(td :class "px-3 py-2 text-stone-700" "Server-side bytecode compilation pipeline. Component registration emits bytecode alongside source. Bytecode hash for caching.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Bytecode in data-components script tags, fallback to source"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "1")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Spec the bytecode format in bytecode.sx. Instruction set, constant pool layout, module structure, encoding.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bytecode.sx — format spec + serializer/deserializer"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "2")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Write the compiler in SX. AST → bytecode. Macro expansion, constant folding, tail call detection, closure analysis.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "compile.sx — bootstrapped to Python for server-side compilation"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "3")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Write bootstrap_rs.py — Rust bootstrapper. Translates spec to Rust source implementing the platform interface.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bootstrap_rs.py + generated Rust crate"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "4")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Implement bytecode VM in Rust. Dispatch loop, value representation, closure/env model, GC strategy.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "sx-vm crate — native binary + WASM target"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "5")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS bindings for DOM. WASM imports for createElement, setAttribute, appendChild, event listeners. Handle table.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "sx-vm-dom.js — JS glue layer (~2KB)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "6")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Server-side bytecode compilation pipeline. Component registration emits bytecode alongside source. Bytecode hash for caching.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Bytecode in data-components script tags, fallback to source"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "7")
|
||||
(td :class "px-3 py-2 text-stone-700" "Shadow-compare: run JS evaluator and WASM VM in parallel, assert identical DOM output on every page render.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Confidence to switch over"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "7")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Shadow-compare: run JS evaluator and WASM VM in parallel, assert identical DOM output on every page render.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Confidence to switch over"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Interaction with existing plans
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Interaction with Other Plans" :id "interactions"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "Async Eval Convergence") " — must complete first. The spec must be the single evaluator before we add another target. Otherwise we'd be bootstrapping a fork.")
|
||||
(li (strong "Runtime Slicing") " — the WASM module can be tiered just like the JS runtime. L0 hypermedia needs no VM at all (pure HTML). L1 DOM ops needs a minimal VM. L2 islands needs signals. The WASM module should be tree-shakeable.")
|
||||
(li (strong "Content-Addressed Components") " — bytecode modules are ideal for content addressing. Deterministic compilation means the same SX source always produces the same bytecode → same CID. Fetch bytecode by CID from IPFS.")
|
||||
@@ -238,7 +238,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "The spec remains the single source of truth.") " The bytecode format, compiler, and VM semantics are all specced in .sx. The Rust VM is just another host, like Python and JavaScript.")
|
||||
(li (strong "Bytecode is an optimisation, not a requirement.") " SX source text remains a valid wire format. The system degrades gracefully — if WASM isn't available, fall back to the JS evaluator. Progressive enhancement.")
|
||||
(li (strong "The VM is dumb, the compiler is smart.") " Macro expansion, constant folding, tail call detection — all done at compile time. The VM is a simple dispatch loop. This keeps the WASM module small and the compilation fast.")
|
||||
@@ -251,7 +251,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After completion:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "SX compiles to four targets: JavaScript, Python, Rust (native), Rust (WASM)")
|
||||
(li "Client wire format is ~5x smaller (bytecode vs source text)")
|
||||
(li "No parsing overhead on the client — bytecode loads directly into the VM")
|
||||
|
||||
@@ -3,55 +3,55 @@
|
||||
(defcomp ~protocols/wire-format-content ()
|
||||
(~docs/page :title "Wire Format"
|
||||
(~docs/section :title "The text/sx content type" :id "content-type"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx responses use content type text/sx. The body is s-expression source code. The client parses and evaluates it, then renders the result into the DOM.")
|
||||
(~docs/code :src (highlight "HTTP/1.1 200 OK\nContent-Type: text/sx\nSX-Css-Hash: a1b2c3d4\n\n(div :class \"p-4\"\n (~card :title \"Hello\"))" "bash")))
|
||||
(~docs/section :title "Request lifecycle" :id "lifecycle"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"1. User interacts with an element that has sx-get/sx-post/etc.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"2. sx.js fires sx:beforeRequest, then sends the HTTP request with SX-Request: true header.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"3. Server builds s-expression tree, scans CSS classes, prepends missing component definitions.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"4. Client receives text/sx response, parses it, evaluates it, renders to DOM.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"5. sx.js fires sx:afterSwap and sx:afterSettle.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"6. Any sx-swap-oob elements are swapped into their targets elsewhere in the DOM."))
|
||||
(~docs/section :title "Component definitions" :id "components"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"On full page loads, component definitions are in <script type=\"text/sx\" data-components>. On subsequent sx requests, missing definitions are prepended to the response body. The client caches definitions in localStorage keyed by a content hash."))))
|
||||
|
||||
(defcomp ~protocols/fragments-content ()
|
||||
(~docs/page :title "Cross-Service Fragments"
|
||||
(~docs/section :title "Fragment protocol" :id "protocol"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Rose Ash runs as independent microservices. Each service can expose HTML or sx fragments that other services compose into their pages. Fragment endpoints return text/sx or text/html.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The frag resolver is an I/O primitive in the render tree:")
|
||||
(~docs/code :src (highlight "(frag \"blog\" \"link-card\" :slug \"hello\")" "lisp")))
|
||||
(~docs/section :title "SxExpr wrapping" :id "wrapping"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"When a fragment returns text/sx, the response is wrapped in an SxExpr and embedded directly in the render tree. When it returns text/html, it's wrapped in a ~rich-text component that inserts the HTML via raw!. This allows transparent composition across service boundaries."))
|
||||
(~docs/section :title "fetch_fragments()" :id "fetch"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The Python helper fetch_fragments() fetches multiple fragments in parallel via asyncio.gather(). Fragments are cached in Redis with short TTLs. Each fragment request is HMAC-signed for authentication."))))
|
||||
|
||||
(defcomp ~protocols/resolver-io-content ()
|
||||
(~docs/page :title "Resolver I/O"
|
||||
(~docs/section :title "Async I/O primitives" :id "primitives"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The sx resolver identifies I/O nodes in the render tree, groups them, executes them in parallel via asyncio.gather(), and substitutes results back in.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"I/O primitives:")
|
||||
(ul :class "space-y-2 text-stone-600"
|
||||
(li (span :class "font-mono text-violet-700" "frag") " — fetch a cross-service fragment")
|
||||
(li (span :class "font-mono text-violet-700" "query") " — read data from another service")
|
||||
(li (span :class "font-mono text-violet-700" "action") " — execute a write on another service")
|
||||
(li (span :class "font-mono text-violet-700" "current-user") " — resolve the current authenticated user")))
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (span (~tw :tokens "font-mono text-violet-700") "frag") " — fetch a cross-service fragment")
|
||||
(li (span (~tw :tokens "font-mono text-violet-700") "query") " — read data from another service")
|
||||
(li (span (~tw :tokens "font-mono text-violet-700") "action") " — execute a write on another service")
|
||||
(li (span (~tw :tokens "font-mono text-violet-700") "current-user") " — resolve the current authenticated user")))
|
||||
(~docs/section :title "Execution model" :id "execution"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The render tree is walked to find I/O nodes. All nodes at the same depth are gathered and executed in parallel. Results replace the I/O nodes in the tree. The walk continues until no more I/O nodes are found. This typically completes in 1-2 passes."))))
|
||||
|
||||
(defcomp ~protocols/internal-services-content ()
|
||||
@@ -59,14 +59,14 @@
|
||||
(~docs/note
|
||||
(p "Honest note: the internal service protocol is JSON, not sx. Sx is the composition layer on top. The protocols below are the plumbing underneath."))
|
||||
(~docs/section :title "HMAC-signed HTTP" :id "hmac"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Services communicate via HMAC-signed HTTP requests with short timeouts:")
|
||||
(ul :class "space-y-2 text-stone-600 font-mono text-sm"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 font-mono text-sm")
|
||||
(li "GET /internal/data/{query} — read data (3s timeout)")
|
||||
(li "POST /internal/actions/{action} — execute write (5s timeout)")
|
||||
(li "POST /internal/inbox — ActivityPub-shaped event delivery")))
|
||||
(~docs/section :title "fetch_data / call_action" :id "fetch"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Python helpers fetch_data() and call_action() handle HMAC signing, serialization, and error handling. They resolve service URLs from environment variables (INTERNAL_URL_BLOG, etc) and fall back to public URLs in development."))))
|
||||
|
||||
(defcomp ~protocols/activitypub-content ()
|
||||
@@ -74,10 +74,10 @@
|
||||
(~docs/note
|
||||
(p "Honest note: ActivityPub wire format is JSON-LD, not sx. This documents how AP integrates with the sx rendering layer."))
|
||||
(~docs/section :title "AP activities" :id "activities"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Rose Ash services communicate cross-domain writes via ActivityPub-shaped activities. Each service has a virtual actor. Activities are JSON-LD objects sent to /internal/inbox endpoints. RSA signatures authenticate the sender."))
|
||||
(~docs/section :title "Event bus" :id "bus"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The event bus dispatches activities to registered handlers. Handlers are async functions that process the activity and may trigger side effects. The bus runs as a background processor in each service."))))
|
||||
|
||||
(defcomp ~protocols/future-content ()
|
||||
@@ -85,14 +85,14 @@
|
||||
(~docs/note
|
||||
(p "This page is speculative. Nothing here is implemented. It documents ideas that may or may not happen."))
|
||||
(~docs/section :title "Custom protocol schemes" :id "schemes"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"sx:// and sxs:// as custom URI schemes for content addressing or deep linking. An sx:// URI could resolve to an sx expression from a federated registry. This is technically feasible but practically unnecessary for a single-site deployment."))
|
||||
(~docs/section :title "Sx as AP serialization" :id "ap-sx"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"ActivityPub objects could be serialized as s-expressions instead of JSON-LD. S-expressions are more compact and easier to parse. The practical barrier: the entire AP ecosystem expects JSON-LD."))
|
||||
(~docs/section :title "Sx-native federation" :id "federation"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Federated services could exchange sx fragments directly — render a remote user's profile card by fetching its sx source from their server. This requires trust and standardization that doesn't exist yet."))
|
||||
(~docs/section :title "Realistic assessment" :id "realistic"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The most likely near-term improvement is sx:// deep linking for client-side component resolution. Everything else requires ecosystem adoption that one project can't drive alone."))))
|
||||
|
||||
@@ -5,27 +5,27 @@
|
||||
(render-count (signal 0)))
|
||||
;; Increment render-count each time the parent re-renders
|
||||
(swap! render-count inc)
|
||||
(div :class "rounded border border-violet-200 bg-violet-50/30 p-4 space-y-4"
|
||||
(div :class "space-y-2"
|
||||
(div :class "text-xs text-stone-500" (str "Parent render count: " (deref render-count)))
|
||||
(div :class "flex items-center gap-3"
|
||||
(span :class "text-sm text-stone-600" "Parent signal:")
|
||||
(button :class "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300"
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50/30 p-4 space-y-4")
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(div (~tw :tokens "text-xs text-stone-500") (str "Parent render count: " (deref render-count)))
|
||||
(div (~tw :tokens "flex items-center gap-3")
|
||||
(span (~tw :tokens "text-sm text-stone-600") "Parent signal:")
|
||||
(button (~tw :tokens "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300")
|
||||
:on-click (fn (e) (swap! parent-count inc))
|
||||
"+")
|
||||
(span :class "font-mono text-lg font-bold text-stone-700" (deref parent-count))))
|
||||
(span (~tw :tokens "font-mono text-lg font-bold text-stone-700") (deref parent-count))))
|
||||
|
||||
;; The cyst — its state survives parent re-renders
|
||||
(cyst :key "demo-cyst"
|
||||
(let ((child-count (signal 0)))
|
||||
(div :class "rounded border border-emerald-200 bg-emerald-50/30 p-3 space-y-2"
|
||||
(div :class "text-xs text-emerald-600 font-semibold" "Inside cyst (isolated)")
|
||||
(div :class "flex items-center gap-3"
|
||||
(span :class "text-sm text-stone-600" "Cyst signal:")
|
||||
(button :class "px-2 py-1 rounded bg-emerald-200 text-emerald-700 text-sm hover:bg-emerald-300"
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50/30 p-3 space-y-2")
|
||||
(div (~tw :tokens "text-xs text-emerald-600 font-semibold") "Inside cyst (isolated)")
|
||||
(div (~tw :tokens "flex items-center gap-3")
|
||||
(span (~tw :tokens "text-sm text-stone-600") "Cyst signal:")
|
||||
(button (~tw :tokens "px-2 py-1 rounded bg-emerald-200 text-emerald-700 text-sm hover:bg-emerald-300")
|
||||
:on-click (fn (e) (swap! child-count inc))
|
||||
"+")
|
||||
(span :class "font-mono text-lg font-bold text-emerald-700" (deref child-count)))))))))
|
||||
(span (~tw :tokens "font-mono text-lg font-bold text-emerald-700") (deref child-count)))))))))
|
||||
|
||||
(defcomp ~reactive-islands/demo/example-cyst ()
|
||||
(~docs/page :title "Cyst"
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
(defisland ~reactive-islands/index/demo-reactive-expressions ()
|
||||
(let ((celsius (signal 20))
|
||||
(name (signal "World")))
|
||||
(div :class "rounded border border-violet-200 bg-violet-50/30 p-4 space-y-4"
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex items-center gap-3"
|
||||
(span :class "text-sm text-stone-600" "Celsius:")
|
||||
(button :class "px-2 py-1 rounded bg-stone-200 text-sm hover:bg-stone-300"
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50/30 p-4 space-y-4")
|
||||
(div (~tw :tokens "space-y-3")
|
||||
(div (~tw :tokens "flex items-center gap-3")
|
||||
(span (~tw :tokens "text-sm text-stone-600") "Celsius:")
|
||||
(button (~tw :tokens "px-2 py-1 rounded bg-stone-200 text-sm hover:bg-stone-300")
|
||||
:on-click (fn (e) (swap! celsius (fn (c) (- c 5)))) "−5")
|
||||
(span :class "font-mono text-lg font-bold text-violet-700" (deref celsius))
|
||||
(button :class "px-2 py-1 rounded bg-stone-200 text-sm hover:bg-stone-300"
|
||||
(span (~tw :tokens "font-mono text-lg font-bold text-violet-700") (deref celsius))
|
||||
(button (~tw :tokens "px-2 py-1 rounded bg-stone-200 text-sm hover:bg-stone-300")
|
||||
:on-click (fn (e) (swap! celsius (fn (c) (+ c 5)))) "+5"))
|
||||
|
||||
;; All of these are reactive — they update when celsius changes
|
||||
(div :class "space-y-1 text-sm"
|
||||
(div (span :class "text-stone-500 w-32 inline-block" "Bare deref: ") (span :class "font-mono text-violet-700" (deref celsius)))
|
||||
(div (span :class "text-stone-500 w-32 inline-block" "str + deref: ") (span :class "font-mono text-violet-700" (str (deref celsius) "°C")))
|
||||
(div (span :class "text-stone-500 w-32 inline-block" "Math + deref: ") (span :class "font-mono text-violet-700" (+ (* (deref celsius) 1.8) 32)))
|
||||
(div (span :class "text-stone-500 w-32 inline-block" "Full conversion: ") (span :class "font-mono text-violet-700" (str (deref celsius) "°C = " (+ (* (deref celsius) 1.8) 32) "°F")))
|
||||
(div (span :class "text-stone-500 w-32 inline-block" "Conditional: ") (span :class "font-mono text-violet-700" (if (> (deref celsius) 30) "Hot!" (if (< (deref celsius) 0) "Freezing!" "Moderate")))))))))
|
||||
(div (~tw :tokens "space-y-1 text-sm")
|
||||
(div (span (~tw :tokens "text-stone-500 w-32 inline-block") "Bare deref: ") (span (~tw :tokens "font-mono text-violet-700") (deref celsius)))
|
||||
(div (span (~tw :tokens "text-stone-500 w-32 inline-block") "str + deref: ") (span (~tw :tokens "font-mono text-violet-700") (str (deref celsius) "°C")))
|
||||
(div (span (~tw :tokens "text-stone-500 w-32 inline-block") "Math + deref: ") (span (~tw :tokens "font-mono text-violet-700") (+ (* (deref celsius) 1.8) 32)))
|
||||
(div (span (~tw :tokens "text-stone-500 w-32 inline-block") "Full conversion: ") (span (~tw :tokens "font-mono text-violet-700") (str (deref celsius) "°C = " (+ (* (deref celsius) 1.8) 32) "°F")))
|
||||
(div (span (~tw :tokens "text-stone-500 w-32 inline-block") "Conditional: ") (span (~tw :tokens "font-mono text-violet-700") (if (> (deref celsius) 30) "Hot!" (if (< (deref celsius) 0) "Freezing!" "Moderate")))))))))
|
||||
|
||||
(defcomp ~reactive-islands/demo/example-reactive-expressions ()
|
||||
(~docs/page :title "Reactive Expressions"
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
:title "Examples"
|
||||
:id "examples"
|
||||
(ol
|
||||
:class "space-y-1"
|
||||
(~tw :tokens "space-y-1")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
@@ -45,7 +45,7 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-violet-600 hover:underline"
|
||||
(~tw :tokens "text-violet-600 hover:underline")
|
||||
(get item "label"))))
|
||||
reactive-examples-nav-items)))))
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
(code "(deref fahrenheit)")
|
||||
" in the span creates a reactive text node that updates when celsius changes.")
|
||||
(div
|
||||
:class "mt-6"
|
||||
(~tw :tokens "mt-6")
|
||||
(~reactive-islands/test-runner-placeholder)
|
||||
(script
|
||||
:type "text/sx-test"
|
||||
@@ -483,7 +483,7 @@
|
||||
";; Signal basics (6 tests)\n(assert-true (signal? (signal 42)))\n(assert-equal 42 (deref (signal 42)))\n(assert-equal 5 (deref 5)) ;; non-signal passthrough\n\n;; reset! changes value\n(let ((s (signal 0)))\n (reset! s 10)\n (assert-equal 10 (deref s)))\n\n;; reset! does NOT notify when value unchanged (identical? check)\n\n;; Computed (3 tests)\n(let ((a (signal 3)) (b (signal 4))\n (sum (computed (fn () (+ (deref a) (deref b))))))\n (assert-equal 7 (deref sum))\n (reset! a 10)\n (assert-equal 14 (deref sum)))\n\n;; Effects (4 tests) — immediate run, re-run on change, dispose, cleanup\n;; Batch (1 test) — defers notifications, deduplicates subscribers\n;; defisland (3 tests) — creates island, callable, accepts children"
|
||||
"lisp"))
|
||||
(p
|
||||
:class "mt-2 text-sm text-stone-500"
|
||||
(~tw :tokens "mt-2 text-sm text-stone-500")
|
||||
"Run: "
|
||||
(code "python3 shared/sx/tests/run.py signals"))))
|
||||
|
||||
@@ -495,138 +495,138 @@
|
||||
(p
|
||||
"Every React feature has an SX equivalent — most are simpler because signals are fine-grained.")
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "React")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SX")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Demo")))
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "React")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "SX")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Demo")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "useState")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "useState")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"(signal value)")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#1"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#1"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "useMemo")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "useMemo")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"(computed (fn () ...))")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#1, #2"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#1, #2"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "useEffect")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "useEffect")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"(effect (fn () ...))")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#3"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#3"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "useRef")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "useRef")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"(dict \"current\" nil) + :ref")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#9"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#9"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "useCallback")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "useCallback")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"(fn (...) ...) — no dep arrays")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "N/A"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "N/A"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "className / style")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "className / style")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
":class (str ...) :style (str ...)")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#10"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#10"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Controlled inputs")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Controlled inputs")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
":bind signal")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#6"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#6"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "key prop")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "key prop")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
":key value")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#5"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#5"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "createPortal")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "createPortal")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"(portal \"#target\" ...)")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#7"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#7"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "ErrorBoundary")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "ErrorBoundary")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"(error-boundary fallback ...)")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#8"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#8"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Suspense + use()")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Suspense + use()")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"(resource fn) + cond/deref")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#11"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#11"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "startTransition")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "startTransition")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"schedule-idle + batch")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#12"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#12"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Context / Redux")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Context / Redux")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-violet-700")
|
||||
"def-store / use-store")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "#13"))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "#13"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Virtual DOM / diffing")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Virtual DOM / diffing")
|
||||
(td
|
||||
:class "px-3 py-2 text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 text-xs text-stone-500")
|
||||
"N/A — fine-grained signals update exact DOM nodes")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" ""))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") ""))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JSX / build step")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JSX / build step")
|
||||
(td
|
||||
:class "px-3 py-2 text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 text-xs text-stone-500")
|
||||
"N/A — s-expressions are the syntax")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" ""))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") ""))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Server Components")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Server Components")
|
||||
(td
|
||||
:class "px-3 py-2 text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 text-xs text-stone-500")
|
||||
"N/A — aser mode already expands server-side")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" ""))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") ""))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Concurrent rendering")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Concurrent rendering")
|
||||
(td
|
||||
:class "px-3 py-2 text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 text-xs text-stone-500")
|
||||
"N/A — fine-grained updates are inherently incremental")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" ""))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") ""))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Hooks rules")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Hooks rules")
|
||||
(td
|
||||
:class "px-3 py-2 text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 text-xs text-stone-500")
|
||||
"N/A — signals are values, no ordering rules")
|
||||
(td :class "px-3 py-2 text-xs text-stone-500" "")))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-xs text-stone-500") "")))))))
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
:id "how"
|
||||
(p "Three components:")
|
||||
(ol
|
||||
:class "space-y-2 text-stone-600 list-decimal list-inside"
|
||||
(~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
|
||||
(li
|
||||
(strong "Server emits: ")
|
||||
"Server-rendered elements carry "
|
||||
@@ -59,7 +59,7 @@
|
||||
(p
|
||||
"Signals live in JavaScript memory (closures), not in the DOM. When htmx swaps content inside an island:")
|
||||
(ul
|
||||
:class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li
|
||||
(strong "Swap inside island: ")
|
||||
"Signals survive. The lake content is replaced but the island's signal closures are untouched. Effects re-bind to new DOM nodes if needed.")
|
||||
@@ -78,7 +78,7 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-violet-700 underline"
|
||||
(~tw :tokens "text-violet-700 underline")
|
||||
"named stores")
|
||||
" come in — they persist at page level, surviving island destruction.")))
|
||||
(~docs/section
|
||||
@@ -94,35 +94,35 @@
|
||||
"lisp"))
|
||||
(p "Platform interface required:")
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200 mt-2"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200 mt-2")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Function")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Function")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
|
||||
"(dom-listen el name handler)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"Attach event listener, return remove function"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
|
||||
"(dom-dispatch el name detail)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"Dispatch CustomEvent with detail, bubbles: true"))
|
||||
(tr
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
|
||||
"(event-detail e)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"Extract .detail from CustomEvent"))))))))
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:id "architecture"
|
||||
(p "Two orthogonal bars control how an SX page works:")
|
||||
(ul
|
||||
:class "space-y-1 text-stone-600 list-disc pl-5"
|
||||
(~tw :tokens "space-y-1 text-stone-600 list-disc pl-5")
|
||||
(li
|
||||
(strong "Render boundary")
|
||||
" — where rendering happens (server HTML vs client DOM)")
|
||||
@@ -16,36 +16,36 @@
|
||||
(strong "State flow")
|
||||
" — how state flows (server state vs client signals)"))
|
||||
(div
|
||||
:class "overflow-x-auto mt-4 mb-4"
|
||||
(~tw :tokens "overflow-x-auto mt-4 mb-4")
|
||||
(table
|
||||
:class "w-full text-sm text-left"
|
||||
(~tw :tokens "w-full text-sm text-left")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200"
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "")
|
||||
(~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "")
|
||||
(th
|
||||
:class "py-2 px-3 font-semibold text-stone-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
||||
"Server State")
|
||||
(th
|
||||
:class "py-2 px-3 font-semibold text-stone-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
||||
"Client State")))
|
||||
(tbody
|
||||
:class "text-stone-600"
|
||||
(~tw :tokens "text-stone-600")
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "py-2 px-3 font-semibold text-stone-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
||||
"Server Rendering")
|
||||
(td :class "py-2 px-3" "Pure hypermedia (htmx)")
|
||||
(td :class "py-2 px-3" "SSR + hydrated islands"))
|
||||
(td (~tw :tokens "py-2 px-3") "Pure hypermedia (htmx)")
|
||||
(td (~tw :tokens "py-2 px-3") "SSR + hydrated islands"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "py-2 px-3 font-semibold text-stone-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
||||
"Client Rendering")
|
||||
(td :class "py-2 px-3" "SX wire format (current)")
|
||||
(td (~tw :tokens "py-2 px-3") "SX wire format (current)")
|
||||
(td
|
||||
:class "py-2 px-3 font-semibold text-violet-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-violet-700")
|
||||
"Reactive islands (this)")))))
|
||||
(p
|
||||
"Most content stays pure hypermedia. Interactive regions opt into reactivity. The author controls where each component sits on both bars."))
|
||||
@@ -53,14 +53,14 @@
|
||||
:title "Four Levels"
|
||||
:id "levels"
|
||||
(div
|
||||
:class "space-y-4"
|
||||
(~tw :tokens "space-y-4")
|
||||
(div
|
||||
:class "rounded border border-stone-200 p-4"
|
||||
(~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div
|
||||
:class "font-semibold text-stone-800"
|
||||
(~tw :tokens "font-semibold text-stone-800")
|
||||
"Level 0: Pure Hypermedia")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"The default. "
|
||||
(code "sx-get")
|
||||
", "
|
||||
@@ -69,12 +69,12 @@
|
||||
(code "sx-swap")
|
||||
". Server renders everything. No client state. 90% of a typical application."))
|
||||
(div
|
||||
:class "rounded border border-stone-200 p-4"
|
||||
(~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div
|
||||
:class "font-semibold text-stone-800"
|
||||
(~tw :tokens "font-semibold text-stone-800")
|
||||
"Level 1: Local DOM Operations")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Imperative escapes: "
|
||||
(code "toggle!")
|
||||
", "
|
||||
@@ -83,23 +83,23 @@
|
||||
(code "on-event")
|
||||
". Micro-interactions too small for a server round-trip."))
|
||||
(div
|
||||
:class "rounded border border-violet-300 bg-violet-50 p-4"
|
||||
(~tw :tokens "rounded border border-violet-300 bg-violet-50 p-4")
|
||||
(div
|
||||
:class "font-semibold text-violet-900"
|
||||
(~tw :tokens "font-semibold text-violet-900")
|
||||
"Level 2: Reactive Islands")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
(code "defisland")
|
||||
" components with local signals. Fine-grained DOM updates "
|
||||
(em "without")
|
||||
" virtual DOM, diffing, or component re-renders. A signal change updates only the DOM nodes that read it."))
|
||||
(div
|
||||
:class "rounded border border-stone-200 p-4"
|
||||
(~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div
|
||||
:class "font-semibold text-stone-800"
|
||||
(~tw :tokens "font-semibold text-stone-800")
|
||||
"Level 3: Connected Islands")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Islands that share state via signal props or named stores ("
|
||||
(code "def-store")
|
||||
" / "
|
||||
@@ -118,7 +118,7 @@
|
||||
:title "Island Lifecycle"
|
||||
:id "lifecycle"
|
||||
(ol
|
||||
:class "space-y-2 text-stone-600 list-decimal list-inside"
|
||||
(~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
|
||||
(li
|
||||
(strong "Definition: ")
|
||||
(code "defisland")
|
||||
@@ -157,40 +157,40 @@
|
||||
(code "sx-post")
|
||||
" like normal hypermedia. This works because signals live in closures, not the DOM.")
|
||||
(div
|
||||
:class "space-y-2 mt-3"
|
||||
(~tw :tokens "space-y-2 mt-3")
|
||||
(div
|
||||
:class "rounded border border-green-200 bg-green-50 p-3"
|
||||
(~tw :tokens "rounded border border-green-200 bg-green-50 p-3")
|
||||
(div
|
||||
:class "font-semibold text-green-800 text-sm"
|
||||
(~tw :tokens "font-semibold text-green-800 text-sm")
|
||||
"Swap inside island")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Lake content replaced. Signals survive. Effects rebind to new DOM."))
|
||||
(div
|
||||
:class "rounded border border-green-200 bg-green-50 p-3"
|
||||
(~tw :tokens "rounded border border-green-200 bg-green-50 p-3")
|
||||
(div
|
||||
:class "font-semibold text-green-800 text-sm"
|
||||
(~tw :tokens "font-semibold text-green-800 text-sm")
|
||||
"Swap outside island")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Different part of page updated. Island completely unaffected."))
|
||||
(div
|
||||
:class "rounded border border-amber-200 bg-amber-50 p-3"
|
||||
(~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3")
|
||||
(div
|
||||
:class "font-semibold text-amber-800 text-sm"
|
||||
(~tw :tokens "font-semibold text-amber-800 text-sm")
|
||||
"Swap replaces island")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Island disposed. Local signals lost. Named stores persist — new island reconnects via "
|
||||
(code "use-store")
|
||||
"."))
|
||||
(div
|
||||
:class "rounded border border-stone-200 p-3"
|
||||
(~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(div
|
||||
:class "font-semibold text-stone-800 text-sm"
|
||||
(~tw :tokens "font-semibold text-stone-800 text-sm")
|
||||
"Full page navigation")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Everything cleared. "
|
||||
(code "clear-stores")
|
||||
" wipes the registry."))))
|
||||
@@ -395,7 +395,7 @@
|
||||
:title "Design Principles"
|
||||
:id "principles"
|
||||
(ol
|
||||
:class "space-y-2 text-stone-600 list-decimal list-inside"
|
||||
(~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
|
||||
(li
|
||||
(strong "Islands are opt-in.")
|
||||
" "
|
||||
@@ -424,168 +424,168 @@
|
||||
:title "Implementation Status"
|
||||
:id "status"
|
||||
(p
|
||||
:class "text-stone-600 mb-3"
|
||||
(~tw :tokens "text-stone-600 mb-3")
|
||||
"All signal logic lives in "
|
||||
(code ".sx")
|
||||
" spec files and is bootstrapped to JavaScript and Python. No SX-specific logic in host languages.")
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Files")))
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Files")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Signal runtime spec")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Signal runtime spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"signals.sx (291 lines)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "defisland special form")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "defisland special form")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"eval.sx, special-forms.sx, render.sx"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"DOM adapter (reactive rendering)")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"adapter-dom.sx (+140 lines)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "HTML adapter (SSR)")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "HTML adapter (SSR)")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"adapter-html.sx (+65 lines)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JS bootstrapper")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS bootstrapper")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"bootstrap_js.py, sx-ref.js (4769 lines)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Python bootstrapper")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Python bootstrapper")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"bootstrap_py.py, sx_ref.py"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Test suite")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "17/17")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Test suite")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "17/17")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"test-signals.sx"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Named stores (L3)")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Spec'd")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Named stores (L3)")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"signals.sx: def-store, use-store, clear-stores"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Event bridge")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Spec'd")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Event bridge")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"signals.sx: emit-event, on-event, bridge-event"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Client hydration")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Spec'd")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client hydration")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"boot.sx: sx-hydrate-islands, hydrate-island, dispose-island"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Event bindings")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Spec'd")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Event bindings")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"adapter-dom.sx: :on-click → domListen"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "data-sx-emit processing")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Spec'd")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "data-sx-emit processing")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"orchestration.sx: process-emit-elements"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Island disposal")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Island disposal")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"boot.sx, orchestration.sx: dispose-islands-in pre-swap"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Reactive list")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Reactive list")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"adapter-dom.sx: map + deref auto-upgrades"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Input binding")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Input binding")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"adapter-dom.sx: :bind signal, bind-input"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Keyed reconciliation")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Keyed reconciliation")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"adapter-dom.sx: :key attr, extract-key"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Portals")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Portals")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"adapter-dom.sx: portal render-dom form"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Error boundaries")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Error boundaries")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"adapter-dom.sx: error-boundary render-dom form"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Resource (async signal)")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Resource (async signal)")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"signals.sx: resource, promise-then"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Suspense pattern")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Suspense pattern")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"resource + cond/deref (no special form)"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Transition pattern")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Transition pattern")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-xs text-stone-500"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
||||
"schedule-idle + batch (no special form)"))))))))
|
||||
|
||||
(defisland
|
||||
@@ -595,24 +595,24 @@
|
||||
((count (signal (or initial 0)))
|
||||
(doubled (computed (fn () (* 2 (deref count))))))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(div
|
||||
:class "flex items-center gap-4"
|
||||
(~tw :tokens "flex items-center gap-4")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:on-click (fn (e) (swap! count dec))
|
||||
"−")
|
||||
(span
|
||||
:class "text-2xl font-bold text-violet-900 w-12 text-center"
|
||||
(~tw :tokens "text-2xl font-bold text-violet-900 w-12 text-center")
|
||||
(deref count))
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:on-click (fn (e) (swap! count inc))
|
||||
"+"))
|
||||
(p
|
||||
:class "text-sm text-stone-500 mt-2"
|
||||
(~tw :tokens "text-sm text-stone-500 mt-2")
|
||||
"doubled: "
|
||||
(span :class "font-mono text-violet-700" (deref doubled))))))
|
||||
(span (~tw :tokens "font-mono text-violet-700") (deref doubled))))))
|
||||
|
||||
(defisland
|
||||
~reactive-islands/index/demo-temperature
|
||||
@@ -621,28 +621,28 @@
|
||||
((celsius (signal 20))
|
||||
(fahrenheit (computed (fn () (+ (* (deref celsius) 1.8) 32)))))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(div
|
||||
:class "flex items-center gap-2"
|
||||
(~tw :tokens "flex items-center gap-2")
|
||||
(button
|
||||
:class "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300"
|
||||
(~tw :tokens "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300")
|
||||
:on-click (fn (e) (swap! celsius (fn (c) (- c 5))))
|
||||
"−5")
|
||||
(span
|
||||
:class "font-mono text-lg font-bold text-violet-900 w-16 text-center"
|
||||
(~tw :tokens "font-mono text-lg font-bold text-violet-900 w-16 text-center")
|
||||
(deref celsius))
|
||||
(button
|
||||
:class "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300"
|
||||
(~tw :tokens "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300")
|
||||
:on-click (fn (e) (swap! celsius (fn (c) (+ c 5))))
|
||||
"+5")
|
||||
(span :class "text-stone-500" "°C"))
|
||||
(span :class "text-stone-400" "=")
|
||||
(span (~tw :tokens "text-stone-500") "°C"))
|
||||
(span (~tw :tokens "text-stone-400") "=")
|
||||
(span
|
||||
:class "font-mono text-lg font-bold text-violet-900"
|
||||
(~tw :tokens "font-mono text-lg font-bold text-violet-900")
|
||||
(deref fahrenheit))
|
||||
(span :class "text-stone-500" "°F")))))
|
||||
(span (~tw :tokens "text-stone-500") "°F")))))
|
||||
|
||||
(defisland
|
||||
~reactive-islands/index/demo-imperative
|
||||
@@ -654,25 +654,25 @@
|
||||
(effect
|
||||
(fn () (dom-set-text-content text-node (str (deref count)))))))
|
||||
(div
|
||||
:class "rounded border border-stone-200 bg-stone-50 p-4 my-4"
|
||||
(~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-4")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mb-2"
|
||||
(~tw :tokens "text-sm text-stone-600 mb-2")
|
||||
"Imperative style — explicit "
|
||||
(code "effect")
|
||||
" + "
|
||||
(code "create-text-node")
|
||||
":")
|
||||
(div
|
||||
:class "flex items-center gap-4"
|
||||
(~tw :tokens "flex items-center gap-4")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-stone-600 text-white text-sm font-medium hover:bg-stone-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-stone-600 text-white text-sm font-medium hover:bg-stone-700")
|
||||
:on-click (fn (e) (swap! count dec))
|
||||
"−")
|
||||
(span
|
||||
:class "text-2xl font-bold text-stone-900 w-12 text-center"
|
||||
(~tw :tokens "text-2xl font-bold text-stone-900 w-12 text-center")
|
||||
text-node)
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-stone-600 text-white text-sm font-medium hover:bg-stone-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-stone-600 text-white text-sm font-medium hover:bg-stone-700")
|
||||
:on-click (fn (e) (swap! count inc))
|
||||
"+")))))
|
||||
|
||||
@@ -710,18 +710,18 @@
|
||||
btn-text
|
||||
(if (deref running) "Stop" "Start"))))))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(div
|
||||
:class "flex items-center gap-4"
|
||||
(~tw :tokens "flex items-center gap-4")
|
||||
(span
|
||||
:class "font-mono text-2xl font-bold text-violet-900 w-24 text-center"
|
||||
(~tw :tokens "font-mono text-2xl font-bold text-violet-900 w-24 text-center")
|
||||
time-text)
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:on-click (fn (e) (swap! running not))
|
||||
btn-text)
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
|
||||
:on-click (fn (e) (do (reset! running false) (reset! elapsed 0)))
|
||||
"Reset")))))
|
||||
|
||||
@@ -758,28 +758,28 @@
|
||||
(old)
|
||||
(filter (fn (item) (not (= (get item "id") id))) old))))))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(div
|
||||
:class "flex items-center gap-3 mb-3"
|
||||
(~tw :tokens "flex items-center gap-3 mb-3")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:on-click add-item
|
||||
"Add Item")
|
||||
(span
|
||||
:class "text-sm text-stone-500"
|
||||
(~tw :tokens "text-sm text-stone-500")
|
||||
(deref (computed (fn () (len (deref items)))))
|
||||
" items"))
|
||||
(ul
|
||||
:class "space-y-1"
|
||||
(~tw :tokens "space-y-1")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
(li
|
||||
:key (str (get item "id"))
|
||||
:class "flex items-center justify-between bg-white rounded px-3 py-2 text-sm"
|
||||
(~tw :tokens "flex items-center justify-between bg-white rounded px-3 py-2 text-sm")
|
||||
(span (get item "text"))
|
||||
(button
|
||||
:class "text-stone-400 hover:text-red-500 text-xs"
|
||||
(~tw :tokens "text-stone-400 hover:text-red-500 text-xs")
|
||||
:on-click (fn (e) (remove-item (get item "id")))
|
||||
"✕")))
|
||||
(deref items))))))
|
||||
@@ -790,33 +790,33 @@
|
||||
(let
|
||||
((name (signal "")) (agreed (signal false)))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3")
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(input
|
||||
:type "text"
|
||||
:bind name
|
||||
:placeholder "Type your name..."
|
||||
:class "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-48")
|
||||
(~tw :tokens "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-48"))
|
||||
(span
|
||||
:class "text-sm text-stone-600"
|
||||
(~tw :tokens "text-sm text-stone-600")
|
||||
"Hello, "
|
||||
(strong (deref name))
|
||||
"!"))
|
||||
(div
|
||||
:class "flex items-center gap-2"
|
||||
(~tw :tokens "flex items-center gap-2")
|
||||
(input
|
||||
:type "checkbox"
|
||||
:bind agreed
|
||||
:id "agree-cb"
|
||||
:class "rounded border-stone-300")
|
||||
(~tw :tokens "rounded border-stone-300"))
|
||||
(label
|
||||
:for "agree-cb"
|
||||
:class "text-sm text-stone-600"
|
||||
(~tw :tokens "text-sm text-stone-600")
|
||||
"I agree to the terms"))
|
||||
(when
|
||||
(deref agreed)
|
||||
(p :class "text-sm text-green-700" "Thanks for agreeing!")))))
|
||||
(p (~tw :tokens "text-sm text-green-700") "Thanks for agreeing!")))))
|
||||
|
||||
(defisland
|
||||
~reactive-islands/index/demo-portal
|
||||
@@ -824,9 +824,9 @@
|
||||
(let
|
||||
((open? (signal false)))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:on-click (fn (e) (swap! open? not))
|
||||
(if (deref open?) "Close Modal" "Open Modal"))
|
||||
(portal
|
||||
@@ -834,21 +834,21 @@
|
||||
(when
|
||||
(deref open?)
|
||||
(div
|
||||
:class "fixed inset-0 bg-black/50 flex items-center justify-center z-50"
|
||||
(~tw :tokens "fixed inset-0 bg-black/50 flex items-center justify-center z-50")
|
||||
:on-click (fn (e) (reset! open? false))
|
||||
(div
|
||||
:class "bg-white rounded-lg p-6 max-w-md shadow-xl"
|
||||
(~tw :tokens "bg-white rounded-lg p-6 max-w-md shadow-xl")
|
||||
:on-click (fn (e) (stop-propagation e))
|
||||
(h2
|
||||
:class "text-lg font-bold text-stone-800 mb-2"
|
||||
(~tw :tokens "text-lg font-bold text-stone-800 mb-2")
|
||||
"Portal Modal")
|
||||
(p
|
||||
:class "text-stone-600 text-sm mb-4"
|
||||
(~tw :tokens "text-stone-600 text-sm mb-4")
|
||||
"This content is rendered into "
|
||||
(code "#portal-root")
|
||||
" — outside the island's DOM subtree. It escapes overflow:hidden, z-index stacking, and layout constraints.")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:on-click (fn (e) (reset! open? false))
|
||||
"Close"))))))))
|
||||
|
||||
@@ -858,34 +858,34 @@
|
||||
(let
|
||||
((throw? (signal false)))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(div
|
||||
:class "flex items-center gap-3 mb-3"
|
||||
(~tw :tokens "flex items-center gap-3 mb-3")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-red-600 text-white text-sm font-medium hover:bg-red-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-red-600 text-white text-sm font-medium hover:bg-red-700")
|
||||
:on-click (fn (e) (reset! throw? true))
|
||||
"Trigger Error")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
|
||||
:on-click (fn (e) (reset! throw? false))
|
||||
"Reset"))
|
||||
(error-boundary
|
||||
(fn
|
||||
(err retry-fn)
|
||||
(div
|
||||
:class "p-3 bg-red-50 border border-red-200 rounded"
|
||||
(~tw :tokens "p-3 bg-red-50 border border-red-200 rounded")
|
||||
(p
|
||||
:class "text-red-700 font-medium text-sm"
|
||||
(~tw :tokens "text-red-700 font-medium text-sm")
|
||||
"Caught: "
|
||||
(error-message err))
|
||||
(button
|
||||
:class "mt-2 px-3 py-1 rounded bg-red-600 text-white text-sm hover:bg-red-700"
|
||||
(~tw :tokens "mt-2 px-3 py-1 rounded bg-red-600 text-white text-sm hover:bg-red-700")
|
||||
:on-click (fn (e) (do (reset! throw? false) (invoke retry-fn)))
|
||||
"Retry")))
|
||||
(do
|
||||
(when (deref throw?) (error "Intentional explosion!"))
|
||||
(p
|
||||
:class "text-sm text-green-700"
|
||||
(~tw :tokens "text-sm text-green-700")
|
||||
"Everything is fine. Click \"Trigger Error\" to throw."))))))
|
||||
|
||||
(defisland
|
||||
@@ -894,20 +894,20 @@
|
||||
(let
|
||||
((my-ref (dict "current" nil)) (msg (signal "")))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3")
|
||||
(input
|
||||
:ref my-ref
|
||||
:type "text"
|
||||
:placeholder "I can be focused programmatically"
|
||||
:class "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-64")
|
||||
(~tw :tokens "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-64"))
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:on-click (fn (e) (dom-focus (get my-ref "current")))
|
||||
"Focus Input")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
|
||||
:on-click (fn
|
||||
(e)
|
||||
(let
|
||||
@@ -923,7 +923,7 @@
|
||||
"Read Input"))
|
||||
(when
|
||||
(not (= (deref msg) ""))
|
||||
(p :class "text-sm text-stone-600 font-mono" (deref msg))))))
|
||||
(p (~tw :tokens "text-sm text-stone-600 font-mono") (deref msg))))))
|
||||
|
||||
(defisland
|
||||
~reactive-islands/index/demo-dynamic-class
|
||||
@@ -931,19 +931,19 @@
|
||||
(let
|
||||
((danger (signal false)) (size (signal 16)))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3")
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:on-click (fn (e) (swap! danger not))
|
||||
(if (deref danger) "Safe mode" "Danger mode"))
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
|
||||
:on-click (fn (e) (swap! size (fn (s) (+ s 2))))
|
||||
"Bigger")
|
||||
(button
|
||||
:class "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400"
|
||||
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
|
||||
:on-click (fn (e) (swap! size (fn (s) (max 10 (- s 2)))))
|
||||
"Smaller"))
|
||||
(div
|
||||
@@ -962,28 +962,28 @@
|
||||
(let
|
||||
((data (resource (fn () (promise-delayed 1500 (dict "name" "Ada Lovelace" "role" "First Programmer" "year" 1843))))))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(cond
|
||||
(get (deref data) "loading")
|
||||
(div
|
||||
:class "flex items-center gap-2 text-stone-500"
|
||||
(~tw :tokens "flex items-center gap-2 text-stone-500")
|
||||
(span
|
||||
:class "inline-block w-4 h-4 border-2 border-stone-300 border-t-violet-600 rounded-full animate-spin")
|
||||
(span :class "text-sm" "Loading..."))
|
||||
(~tw :tokens "inline-block w-4 h-4 border-2 border-stone-300 border-t-violet-600 rounded-full animate-spin"))
|
||||
(span (~tw :tokens "text-sm") "Loading..."))
|
||||
(get (deref data) "error")
|
||||
(div
|
||||
:class "p-3 bg-red-50 border border-red-200 rounded"
|
||||
(~tw :tokens "p-3 bg-red-50 border border-red-200 rounded")
|
||||
(p
|
||||
:class "text-red-700 text-sm"
|
||||
(~tw :tokens "text-red-700 text-sm")
|
||||
"Error: "
|
||||
(get (deref data) "error")))
|
||||
:else (let
|
||||
((d (get (deref data) "data")))
|
||||
(div
|
||||
:class "space-y-1"
|
||||
(p :class "font-bold text-stone-800" (get d "name"))
|
||||
(~tw :tokens "space-y-1")
|
||||
(p (~tw :tokens "font-bold text-stone-800") (get d "name"))
|
||||
(p
|
||||
:class "text-sm text-stone-600"
|
||||
(~tw :tokens "text-sm text-stone-600")
|
||||
(get d "role")
|
||||
" ("
|
||||
(get d "year")
|
||||
@@ -1022,25 +1022,25 @@
|
||||
(let
|
||||
((_eff (effect (fn () (let ((q (lower (deref query)))) (if (= q "") (do (reset! pending false) (reset! filtered all-items)) (do (reset! pending true) (schedule-idle (fn () (batch (fn () (reset! filtered (filter (fn (item) (contains? (lower item) q)) all-items)) (reset! pending false))))))))))))
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3")
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(input
|
||||
:type "text"
|
||||
:bind query
|
||||
:placeholder "Filter features..."
|
||||
:class "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-48")
|
||||
(~tw :tokens "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-48"))
|
||||
(when
|
||||
(deref pending)
|
||||
(span :class "text-xs text-stone-400" "Filtering...")))
|
||||
(span (~tw :tokens "text-xs text-stone-400") "Filtering...")))
|
||||
(ul
|
||||
:class "space-y-1"
|
||||
(~tw :tokens "space-y-1")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
(li
|
||||
:key item
|
||||
:class "text-sm text-stone-700 bg-white rounded px-3 py-1.5"
|
||||
(~tw :tokens "text-sm text-stone-700 bg-white rounded px-3 py-1.5")
|
||||
item))
|
||||
(deref filtered)))))))
|
||||
|
||||
@@ -1050,25 +1050,25 @@
|
||||
(let
|
||||
((store (def-store "demo-theme" (fn () (dict "color" (signal "violet") "dark" (signal false))))))
|
||||
(div
|
||||
:class "rounded border border-stone-200 bg-stone-50 p-4 my-2"
|
||||
(~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-2")
|
||||
(p
|
||||
:class "text-xs font-semibold text-stone-500 mb-2"
|
||||
(~tw :tokens "text-xs font-semibold text-stone-500 mb-2")
|
||||
"Island A — Store Writer")
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(select
|
||||
:bind (get store "color")
|
||||
:class "px-2 py-1 rounded border border-stone-300 text-sm"
|
||||
(~tw :tokens "px-2 py-1 rounded border border-stone-300 text-sm")
|
||||
(option :value "violet" "Violet")
|
||||
(option :value "blue" "Blue")
|
||||
(option :value "green" "Green")
|
||||
(option :value "red" "Red"))
|
||||
(label
|
||||
:class "flex items-center gap-1 text-sm text-stone-600"
|
||||
(~tw :tokens "flex items-center gap-1 text-sm text-stone-600")
|
||||
(input
|
||||
:type "checkbox"
|
||||
:bind (get store "dark")
|
||||
:class "rounded border-stone-300")
|
||||
(~tw :tokens "rounded border-stone-300"))
|
||||
"Dark mode")))))
|
||||
|
||||
(defisland
|
||||
@@ -1077,9 +1077,9 @@
|
||||
(let
|
||||
((store (use-store "demo-theme")))
|
||||
(div
|
||||
:class "rounded border border-stone-200 bg-stone-50 p-4 my-2"
|
||||
(~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-2")
|
||||
(p
|
||||
:class "text-xs font-semibold text-stone-500 mb-2"
|
||||
(~tw :tokens "text-xs font-semibold text-stone-500 mb-2")
|
||||
"Island B — Store Reader")
|
||||
(div
|
||||
:class (str
|
||||
@@ -1121,36 +1121,36 @@
|
||||
cb)))))))
|
||||
(div
|
||||
(p
|
||||
:class "text-xs font-semibold text-stone-500 mb-2"
|
||||
(~tw :tokens "text-xs font-semibold text-stone-500 mb-2")
|
||||
"Event Bridge Demo")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mb-2"
|
||||
(~tw :tokens "text-sm text-stone-600 mb-2")
|
||||
"The buttons below simulate server-rendered content dispatching events into the island.")
|
||||
(div
|
||||
:class "flex gap-2 mb-3"
|
||||
(~tw :tokens "flex gap-2 mb-3")
|
||||
(button
|
||||
:data-sx-emit "inbox:message"
|
||||
:data-sx-emit-detail "{\"text\":\"Hello from the lake!\"}"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
(~tw :tokens "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700")
|
||||
"Send \"Hello\"")
|
||||
(button
|
||||
:data-sx-emit "inbox:message"
|
||||
:data-sx-emit-detail "{\"text\":\"Another message\"}"
|
||||
:class "px-3 py-1.5 bg-blue-600 text-white rounded text-sm hover:bg-blue-700"
|
||||
(~tw :tokens "px-3 py-1.5 bg-blue-600 text-white rounded text-sm hover:bg-blue-700")
|
||||
"Send \"Another\"")
|
||||
(button
|
||||
:on-click (fn (e) (reset! messages (list)))
|
||||
:class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
|
||||
(~tw :tokens "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300")
|
||||
"Clear"))
|
||||
(div
|
||||
:class "min-h-12 p-3 rounded bg-stone-100 border border-stone-200"
|
||||
(~tw :tokens "min-h-12 p-3 rounded bg-stone-100 border border-stone-200")
|
||||
(if
|
||||
(empty? (deref messages))
|
||||
(p :class "text-stone-400 text-sm" "No messages yet.")
|
||||
(p (~tw :tokens "text-stone-400 text-sm") "No messages yet.")
|
||||
(ul
|
||||
:class "space-y-1"
|
||||
(~tw :tokens "space-y-1")
|
||||
(map
|
||||
(fn
|
||||
(msg)
|
||||
(li :class "text-sm text-stone-700" (str "→ " msg)))
|
||||
(li (~tw :tokens "text-sm text-stone-700") (str "→ " msg)))
|
||||
(deref messages))))))))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(~docs/page
|
||||
:title "Marshes"
|
||||
(p
|
||||
:class "text-stone-500 text-sm italic mb-8"
|
||||
(~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Islands are dry land. Lakes are open water. Marshes are the saturated ground between — where you can't tell whether you're standing on reactivity or wading through hypermedia.")
|
||||
(~docs/section
|
||||
:title "The boundary dissolves"
|
||||
@@ -20,7 +20,7 @@
|
||||
(em "interpenetration")
|
||||
":")
|
||||
(ul
|
||||
:class "list-disc pl-5 space-y-2 text-stone-600"
|
||||
(~tw :tokens "list-disc pl-5 space-y-2 text-stone-600")
|
||||
(li
|
||||
"A server response that writes to a signal — the lake feeds the island.")
|
||||
(li
|
||||
@@ -44,7 +44,7 @@
|
||||
"The server response carries data that should update a signal. The lake doesn't just display content — it "
|
||||
(em "feeds")
|
||||
" the island's reactive graph.")
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Mechanism: data-sx-signal")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Mechanism: data-sx-signal")
|
||||
(p
|
||||
"A server-rendered element carries a "
|
||||
(code "data-sx-signal")
|
||||
@@ -53,7 +53,7 @@
|
||||
:src (highlight
|
||||
";; Server response includes:\n(div :data-sx-signal \"cart-count:7\"\n (span \"7 items\"))\n\n;; The morph sees data-sx-signal, parses it:\n;; store name = \"cart-count\"\n;; value = 7\n;; Then: (reset! (use-store \"cart-count\") 7)\n;;\n;; Any island anywhere on the page that reads cart-count\n;; updates immediately — fine-grained, no re-render."
|
||||
"lisp"))
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Mechanism: sx-on-settle")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Mechanism: sx-on-settle")
|
||||
(p
|
||||
"An "
|
||||
(code "sx-on-settle")
|
||||
@@ -63,7 +63,7 @@
|
||||
";; A search form that updates a signal after results arrive:\n(form :sx-post \"/search\" :sx-target \"#results\"\n :sx-on-settle (reset! (use-store \"result-count\") result-count)\n (input :name \"q\" :placeholder \"Search...\"))"
|
||||
"lisp"))
|
||||
(h4
|
||||
:class "font-semibold mt-4 mb-2"
|
||||
(~tw :tokens "font-semibold mt-4 mb-2")
|
||||
"Mechanism: event bridge (already exists)")
|
||||
(p
|
||||
"The event bridge ("
|
||||
@@ -72,12 +72,12 @@
|
||||
(code "data-sx-signal")
|
||||
" is a declarative shorthand for the common case of \"server says update this value.\"")
|
||||
(div
|
||||
:class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p
|
||||
:class "text-violet-900 font-medium"
|
||||
(~tw :tokens "text-violet-900 font-medium")
|
||||
"Why not just use the event bridge for everything?")
|
||||
(p
|
||||
:class "text-violet-800 text-sm"
|
||||
(~tw :tokens "text-violet-800 text-sm")
|
||||
"You can. "
|
||||
(code "data-sx-emit")
|
||||
" dispatches a custom event; an effect in an island listens and calls "
|
||||
@@ -91,7 +91,7 @@
|
||||
"Lake morphing lets the server update "
|
||||
(em "content")
|
||||
" inside an island. Marsh morphing goes further: the server can send new SX that the island evaluates reactively.")
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Mechanism: marsh tag")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Mechanism: marsh tag")
|
||||
(p
|
||||
"A "
|
||||
(code "marsh")
|
||||
@@ -108,55 +108,55 @@
|
||||
" options bind to the existing "
|
||||
(code "variant")
|
||||
" signal. The reactive graph reconnects seamlessly.")
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Lake vs. Marsh")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Lake vs. Marsh")
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Lake")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Marsh")))
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Lake")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Marsh")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Content")
|
||||
(td :class "px-3 py-2 text-stone-600" "Static HTML")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX expressions"))
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Content")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Static HTML")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX expressions"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 font-medium text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 font-medium text-stone-700")
|
||||
"Morph action")
|
||||
(td :class "px-3 py-2 text-stone-600" "Replace DOM children")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Replace DOM children")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Parse SX, evaluate in island scope, replace DOM"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 font-medium text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 font-medium text-stone-700")
|
||||
"Signal access")
|
||||
(td :class "px-3 py-2 text-stone-600" "None (static)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None (static)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Full (binds to island signals)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Use case")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Use case")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Display text, labels, metadata")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Forms, controls, interactive fragments"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Overhead")
|
||||
(td :class "px-3 py-2 text-stone-600" "Minimal (DOM swap)")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Overhead")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Minimal (DOM swap)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Parse + eval (small — SX parser is fast)"))))))
|
||||
(~docs/subsection
|
||||
:title "Pattern 3: Reactive state directs and transforms hypermedia"
|
||||
@@ -165,7 +165,7 @@
|
||||
(strong "how to interpret it")
|
||||
".")
|
||||
(h4
|
||||
:class "font-semibold mt-4 mb-2"
|
||||
(~tw :tokens "font-semibold mt-4 mb-2")
|
||||
"3a: Signal-bound hypermedia attributes")
|
||||
(p
|
||||
"Hypermedia trigger attributes ("
|
||||
@@ -185,7 +185,7 @@
|
||||
"The "
|
||||
(code "sx-get")
|
||||
" URL isn't a static string — it's a computed signal. When the mode changes, the next search hits a different endpoint. The hypermedia trigger system reads the signal's current value at trigger time.")
|
||||
(h4 :class "font-semibold mt-4 mb-2" "3b: Reactive swap transforms")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "3b: Reactive swap transforms")
|
||||
(p
|
||||
"A "
|
||||
(code "marsh-transform")
|
||||
@@ -202,13 +202,13 @@
|
||||
" function — a reactive closure over the "
|
||||
(code "view")
|
||||
" signal — reshapes it into a grid, compact list, or default list. Change the view signal → existing content is re-transformed without a server round-trip. Fetch new results → they arrive pre-sorted, then the transform applies the current view.")
|
||||
(h4 :class "font-semibold mt-4 mb-2" "3c: Reactive interpretation")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "3c: Reactive interpretation")
|
||||
(p
|
||||
"The most intimate marsh pattern. Reactive state modifies "
|
||||
(em "how the hypermedia system itself behaves")
|
||||
" — not just what's fetched or how it's displayed, but the rules of interpretation.")
|
||||
(ul
|
||||
:class "list-disc pl-5 space-y-2 text-stone-600"
|
||||
(~tw :tokens "list-disc pl-5 space-y-2 text-stone-600")
|
||||
(li
|
||||
(strong "Reactive swap strategy: ")
|
||||
"A signal controls whether the swap is "
|
||||
@@ -252,33 +252,33 @@
|
||||
"Five new constructs, all specced in "
|
||||
(code ".sx")
|
||||
" files, bootstrapped to every host.")
|
||||
(h4 :class "font-semibold mt-4 mb-2" "1. marsh tag")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "1. marsh tag")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
";; In adapter-dom.sx / adapter-html.sx / adapter-sx.sx:\n;;\n;; (marsh :id \"controls\" :transform transform-fn children...)\n;;\n;; Server: renders as <div data-sx-marsh=\"controls\">children HTML</div>\n;; Client: wraps children in reactive evaluation scope\n;; Morph: re-parses incoming SX, evaluates in island scope, replaces DOM\n;;\n;; The :transform is optional. If present, it's called on the parsed SX\n;; before evaluation. The transform has full signal access."
|
||||
"lisp"))
|
||||
(h4
|
||||
:class "font-semibold mt-4 mb-2"
|
||||
(~tw :tokens "font-semibold mt-4 mb-2")
|
||||
"2. data-sx-signal (morph integration)")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
";; In engine.sx, morph-children:\n;;\n;; When processing a new element with data-sx-signal=\"name:value\":\n;; 1. Parse the attribute: store-name, signal-value\n;; 2. Look up (use-store store-name) — finds or creates the signal\n;; 3. (reset! signal parsed-value)\n;; 4. Remove the data-sx-signal attribute from DOM (consumed)\n;;\n;; Values are JSON-parsed: \"7\" → 7, '\"hello\"' → \"hello\",\n;; 'true' → true, '{...}' → dict"
|
||||
"lisp"))
|
||||
(h4
|
||||
:class "font-semibold mt-4 mb-2"
|
||||
(~tw :tokens "font-semibold mt-4 mb-2")
|
||||
"3. Signal-bound hypermedia attributes")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
";; In orchestration.sx, resolve-trigger-attrs:\n;;\n;; Before issuing a fetch, read sx-get/sx-post/sx-target/sx-swap.\n;; If the value is a signal or computed, deref it at trigger time.\n;;\n;; (define resolve-trigger-url\n;; (fn (el attr)\n;; (let ((val (dom-get-attr el attr)))\n;; (if (signal? val) (deref val) val))))\n;;\n;; This means the URL is evaluated lazily — it reflects the current\n;; signal state at the moment the user acts, not when the DOM was built."
|
||||
"lisp"))
|
||||
(h4
|
||||
:class "font-semibold mt-4 mb-2"
|
||||
(~tw :tokens "font-semibold mt-4 mb-2")
|
||||
"4. marsh-transform (swap pipeline)")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
";; In orchestration.sx, process-swap:\n;;\n;; After receiving server HTML and before inserting into target:\n;; 1. Find the target element\n;; 2. If target has data-sx-marsh, find its transform function\n;; 3. Parse server content as SX\n;; 4. Call transform(sx-content) — transform is a reactive closure\n;; 5. Evaluate the transformed SX in the island's signal scope\n;; 6. Replace the marsh's DOM children\n;;\n;; The transform runs inside the island's tracking context,\n;; so computed/effect dependencies are captured automatically.\n;; When a signal the transform reads changes, the marsh re-transforms."
|
||||
"lisp"))
|
||||
(h4 :class "font-semibold mt-4 mb-2" "5. sx-on-settle (post-swap hook)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "5. sx-on-settle (post-swap hook)")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
";; In orchestration.sx, after swap completes:\n;;\n;; (define process-settle-hooks\n;; (fn (trigger-el)\n;; (let ((hook (dom-get-attr trigger-el \"sx-on-settle\")))\n;; (when hook\n;; (eval-expr (parse hook) (island-env trigger-el))))))\n;;\n;; The expression is evaluated in the nearest island's environment,\n;; giving it access to signals, stores, and island-local functions."
|
||||
@@ -289,50 +289,50 @@
|
||||
(p
|
||||
"The morph algorithm already handles three zones: static DOM (full reconciliation), islands (preserve reactive nodes), and lakes (update static content within islands). Marshes add a fourth:")
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Zone")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Marker")
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Zone")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Marker")
|
||||
(th
|
||||
:class "px-3 py-2 font-medium text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 font-medium text-stone-600")
|
||||
"Morph behaviour")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Static DOM")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "(none)")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Static DOM")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "(none)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Full morph — attrs, children, text"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Island")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Island")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-sm text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600")
|
||||
"data-sx-island")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Enter, find lakes/marshes, update them, skip everything else"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Lake")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Lake")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-sm text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600")
|
||||
"data-sx-lake")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Replace static HTML children"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Marsh")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Marsh")
|
||||
(td
|
||||
:class "px-3 py-2 font-mono text-sm text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600")
|
||||
"data-sx-marsh")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Parse new content as SX, apply transform, evaluate in island scope, replace DOM")))))
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
@@ -426,80 +426,80 @@
|
||||
:id "implementation"
|
||||
(p "Spec-first, bootstrap-second, like everything else.")
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Spec files")
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Spec files")
|
||||
(th
|
||||
:class "px-3 py-2 font-medium text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 font-medium text-stone-600")
|
||||
"What it enables")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-medium" "1. marsh tag")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700 font-medium") "1. marsh tag")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"adapter-dom.sx, adapter-html.sx, adapter-sx.sx")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Server-morphable zones with reactive re-evaluation inside islands"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700 font-medium"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700 font-medium")
|
||||
"2. data-sx-signal")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"engine.sx (morph-island-children)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Server responses write to named store signals"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700 font-medium"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700 font-medium")
|
||||
"3. Signal-bound triggers")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"orchestration.sx (resolve-trigger-url)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"sx-get/sx-post URLs computed from signals"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700 font-medium"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700 font-medium")
|
||||
"4. marsh-transform")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"engine.sx, signals.sx (marsh scopes)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Reactive closures reshape server content before insertion"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700 font-medium"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700 font-medium")
|
||||
"5. sx-on-settle")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"orchestration.sx (process-settle-hooks)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Post-swap SX evaluation with island scope access"))
|
||||
(tr
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700 font-medium"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700 font-medium")
|
||||
"6. Reactive interpretation")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"orchestration.sx (swap pipeline)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
(~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Signal-driven swap strategy, URL rewriting, content transforms")))))
|
||||
(p
|
||||
"Each phase is independently deployable. Phase 1-2 are the foundation. Phase 3-5 enable the marsh patterns. Phase 6 is the deep end — reactive interpretation of the hypermedia system itself."))
|
||||
@@ -507,7 +507,7 @@
|
||||
:title "Design principles"
|
||||
:id "principles"
|
||||
(ol
|
||||
:class "space-y-3 text-stone-600 list-decimal list-inside"
|
||||
(~tw :tokens "space-y-3 text-stone-600 list-decimal list-inside")
|
||||
(li
|
||||
(strong "Marshes are opt-in per zone.")
|
||||
" "
|
||||
@@ -555,7 +555,7 @@
|
||||
(strong "Live interactive islands")
|
||||
" — click the buttons, inspect the DOM.")
|
||||
(ol
|
||||
:class "space-y-1"
|
||||
(~tw :tokens "space-y-1")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
@@ -567,7 +567,7 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-violet-600 hover:underline"
|
||||
(~tw :tokens "text-violet-600 hover:underline")
|
||||
(get item "label"))))
|
||||
marshes-examples-nav-items)))))
|
||||
|
||||
@@ -612,24 +612,24 @@
|
||||
(code "\"demo-price\"")
|
||||
". Island A creates the store and has control buttons. Island B reads it. Signal changes propagate instantly across island boundaries.")
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(~tw :tokens "space-y-3")
|
||||
(~reactive-islands/marshes/demo-marsh-store-writer)
|
||||
(~reactive-islands/marshes/demo-marsh-store-reader))
|
||||
(p
|
||||
:class "mt-3 text-sm text-stone-500"
|
||||
(~tw :tokens "mt-3 text-sm text-stone-500")
|
||||
"The \"Flash Sale\" buttons call "
|
||||
(code "(reset! price 14.99)")
|
||||
" — exactly what "
|
||||
(code "data-sx-signal=\"demo-price:14.99\"")
|
||||
" does during morph.")
|
||||
(div
|
||||
:class "mt-4 rounded border border-stone-200 bg-stone-50 p-3"
|
||||
(~tw :tokens "mt-4 rounded border border-stone-200 bg-stone-50 p-3")
|
||||
(p
|
||||
:class "text-sm font-medium text-stone-700 mb-2"
|
||||
(~tw :tokens "text-sm font-medium text-stone-700 mb-2")
|
||||
"Server endpoint (ready for morph integration):")
|
||||
(div :id "marsh-flash-target" :class "min-h-[2rem]")
|
||||
(div :id "marsh-flash-target" (~tw :tokens "min-h-[2rem]"))
|
||||
(button
|
||||
:class "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:sx-get "/sx/(geography.(reactive.(api.flash-sale)))"
|
||||
:sx-target "#marsh-flash-target"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -737,61 +737,61 @@
|
||||
(computed (fn () (- (* 19.99 (deref qty)) (deref total)))))
|
||||
(on-sale? (computed (fn () (< (deref price) 19.99)))))
|
||||
(div
|
||||
:class "rounded-lg border border-stone-200 bg-white p-4 my-4 space-y-4"
|
||||
(~tw :tokens "rounded-lg border border-stone-200 bg-white p-4 my-4 space-y-4")
|
||||
(div
|
||||
:class "flex items-center justify-between"
|
||||
(~tw :tokens "flex items-center justify-between")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-800" "Artisan Widget")
|
||||
(p :class "text-sm text-stone-500" "Hand-crafted by algorithms"))
|
||||
(h4 (~tw :tokens "font-semibold text-stone-800") "Artisan Widget")
|
||||
(p (~tw :tokens "text-sm text-stone-500") "Hand-crafted by algorithms"))
|
||||
(div
|
||||
:class "text-right"
|
||||
(~tw :tokens "text-right")
|
||||
(div
|
||||
:class "flex items-center gap-2"
|
||||
(~tw :tokens "flex items-center gap-2")
|
||||
(when
|
||||
(deref on-sale?)
|
||||
(span
|
||||
:class "px-2 py-0.5 rounded-full bg-rose-100 text-rose-700 text-xs font-bold uppercase"
|
||||
(~tw :tokens "px-2 py-0.5 rounded-full bg-rose-100 text-rose-700 text-xs font-bold uppercase")
|
||||
"Sale"))
|
||||
(span
|
||||
:class "text-2xl font-bold text-stone-800"
|
||||
(~tw :tokens "text-2xl font-bold text-stone-800")
|
||||
"$"
|
||||
(deref price)))))
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(span :class "text-sm text-stone-600" "Qty:")
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(span (~tw :tokens "text-sm text-stone-600") "Qty:")
|
||||
(button
|
||||
:class "w-7 h-7 rounded bg-stone-200 text-stone-700 text-sm font-medium hover:bg-stone-300"
|
||||
(~tw :tokens "w-7 h-7 rounded bg-stone-200 text-stone-700 text-sm font-medium hover:bg-stone-300")
|
||||
:on-click (fn (e) (swap! qty (fn (q) (max 1 (- q 1)))))
|
||||
"−")
|
||||
(span
|
||||
:class "font-mono text-lg font-bold text-violet-900 w-8 text-center"
|
||||
(~tw :tokens "font-mono text-lg font-bold text-violet-900 w-8 text-center")
|
||||
(deref qty))
|
||||
(button
|
||||
:class "w-7 h-7 rounded bg-stone-200 text-stone-700 text-sm font-medium hover:bg-stone-300"
|
||||
(~tw :tokens "w-7 h-7 rounded bg-stone-200 text-stone-700 text-sm font-medium hover:bg-stone-300")
|
||||
:on-click (fn (e) (swap! qty inc))
|
||||
"+")
|
||||
(span :class "ml-4 text-sm text-stone-600" "Total:")
|
||||
(span :class "text-lg font-bold text-emerald-700" "$" (deref total))
|
||||
(span (~tw :tokens "ml-4 text-sm text-stone-600") "Total:")
|
||||
(span (~tw :tokens "text-lg font-bold text-emerald-700") "$" (deref total))
|
||||
(when
|
||||
(> (deref savings) 0)
|
||||
(span
|
||||
:class "text-sm text-rose-600 font-medium ml-2"
|
||||
(~tw :tokens "text-sm text-rose-600 font-medium ml-2")
|
||||
"Save $"
|
||||
(deref savings))))
|
||||
(div
|
||||
:class "border-t border-stone-200 pt-3"
|
||||
(~tw :tokens "border-t border-stone-200 pt-3")
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(button
|
||||
:class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:sx-get "/sx/(geography.(reactive.(api.flash-sale)))"
|
||||
:sx-target "#marsh-server-msg"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch Price from Server")
|
||||
(span
|
||||
:class "text-xs text-stone-400"
|
||||
(~tw :tokens "text-xs text-stone-400")
|
||||
"Hits a real endpoint. Response updates the signal."))
|
||||
(div :id "marsh-server-msg" :class "mt-2 min-h-[2rem]")))))
|
||||
(div :id "marsh-server-msg" (~tw :tokens "mt-2 min-h-[2rem]"))))))
|
||||
|
||||
(defisland
|
||||
~reactive-islands/marshes/demo-marsh-store-writer
|
||||
@@ -799,38 +799,38 @@
|
||||
(let
|
||||
((price (def-store "demo-price" (fn () (signal 19.99)))))
|
||||
(div
|
||||
:class "rounded-lg border border-amber-200 bg-amber-50 p-4"
|
||||
(~tw :tokens "rounded-lg border border-amber-200 bg-amber-50 p-4")
|
||||
(div
|
||||
:class "flex items-center justify-between mb-3"
|
||||
(~tw :tokens "flex items-center justify-between mb-3")
|
||||
(div
|
||||
:class "flex items-center gap-2"
|
||||
(~tw :tokens "flex items-center gap-2")
|
||||
(span
|
||||
:class "text-sm font-semibold text-amber-800"
|
||||
(~tw :tokens "text-sm font-semibold text-amber-800")
|
||||
"Island A: Price Control")
|
||||
(span
|
||||
:class "text-xs bg-amber-200 text-amber-700 px-1.5 py-0.5 rounded font-mono"
|
||||
(~tw :tokens "text-xs bg-amber-200 text-amber-700 px-1.5 py-0.5 rounded font-mono")
|
||||
"def-store"))
|
||||
(span :class "text-2xl font-bold text-stone-800" "$" (deref price)))
|
||||
(span (~tw :tokens "text-2xl font-bold text-stone-800") "$" (deref price)))
|
||||
(div
|
||||
:class "flex flex-wrap gap-2"
|
||||
(~tw :tokens "flex flex-wrap gap-2")
|
||||
(button
|
||||
:class "px-3 py-1.5 rounded bg-rose-500 text-white text-sm font-medium hover:bg-rose-600"
|
||||
(~tw :tokens "px-3 py-1.5 rounded bg-rose-500 text-white text-sm font-medium hover:bg-rose-600")
|
||||
:on-click (fn (e) (reset! price 14.99))
|
||||
"⚡$14.99")
|
||||
(button
|
||||
:class "px-3 py-1.5 rounded bg-rose-500 text-white text-sm font-medium hover:bg-rose-600"
|
||||
(~tw :tokens "px-3 py-1.5 rounded bg-rose-500 text-white text-sm font-medium hover:bg-rose-600")
|
||||
:on-click (fn (e) (reset! price 9.99))
|
||||
"⚡$9.99")
|
||||
(button
|
||||
:class "px-3 py-1.5 rounded bg-rose-500 text-white text-sm font-medium hover:bg-rose-600"
|
||||
(~tw :tokens "px-3 py-1.5 rounded bg-rose-500 text-white text-sm font-medium hover:bg-rose-600")
|
||||
:on-click (fn (e) (reset! price 29.99))
|
||||
"⚡$29.99")
|
||||
(button
|
||||
:class "px-3 py-1.5 rounded bg-stone-300 text-stone-700 text-sm font-medium hover:bg-stone-400"
|
||||
(~tw :tokens "px-3 py-1.5 rounded bg-stone-300 text-stone-700 text-sm font-medium hover:bg-stone-400")
|
||||
:on-click (fn (e) (reset! price 19.99))
|
||||
"Reset $19.99"))
|
||||
(p
|
||||
:class "text-xs text-amber-600 mt-2"
|
||||
(~tw :tokens "text-xs text-amber-600 mt-2")
|
||||
"Each button calls "
|
||||
(code "(reset! price ...)")
|
||||
" — simulating "
|
||||
@@ -843,23 +843,23 @@
|
||||
(let
|
||||
((price (def-store "demo-price" (fn () (signal 19.99)))))
|
||||
(div
|
||||
:class "rounded-lg border border-emerald-200 bg-emerald-50 p-4"
|
||||
(~tw :tokens "rounded-lg border border-emerald-200 bg-emerald-50 p-4")
|
||||
(div
|
||||
:class "flex items-center justify-between"
|
||||
(~tw :tokens "flex items-center justify-between")
|
||||
(div
|
||||
:class "flex items-center gap-2"
|
||||
(~tw :tokens "flex items-center gap-2")
|
||||
(span
|
||||
:class "text-sm font-semibold text-emerald-800"
|
||||
(~tw :tokens "text-sm font-semibold text-emerald-800")
|
||||
"Island B: Product Display")
|
||||
(span
|
||||
:class "text-xs bg-emerald-200 text-emerald-700 px-1.5 py-0.5 rounded font-mono"
|
||||
(~tw :tokens "text-xs bg-emerald-200 text-emerald-700 px-1.5 py-0.5 rounded font-mono")
|
||||
"use-store"))
|
||||
(div
|
||||
:class "flex items-center gap-2"
|
||||
(span :class "text-stone-600" "Current price:")
|
||||
(span :class "text-2xl font-bold text-stone-800" "$" (deref price))))
|
||||
(~tw :tokens "flex items-center gap-2")
|
||||
(span (~tw :tokens "text-stone-600") "Current price:")
|
||||
(span (~tw :tokens "text-2xl font-bold text-stone-800") "$" (deref price))))
|
||||
(p
|
||||
:class "text-xs text-emerald-600 mt-2"
|
||||
(~tw :tokens "text-xs text-emerald-600 mt-2")
|
||||
"Separate island, reads the same store. Signal changes propagate instantly across island boundaries."))))
|
||||
|
||||
(defisland
|
||||
@@ -868,36 +868,36 @@
|
||||
(let
|
||||
((count (def-store "settle-count" (fn () (signal 0)))))
|
||||
(div
|
||||
:class "rounded-lg border border-stone-200 bg-white p-4 my-4 space-y-3"
|
||||
(~tw :tokens "rounded-lg border border-stone-200 bg-white p-4 my-4 space-y-3")
|
||||
(div
|
||||
:class "flex items-center justify-between"
|
||||
(~tw :tokens "flex items-center justify-between")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-800" "Settle Hook Demo")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-800") "Settle Hook Demo")
|
||||
(p
|
||||
:class "text-sm text-stone-500"
|
||||
(~tw :tokens "text-sm text-stone-500")
|
||||
"Server content + client-side counter"))
|
||||
(div
|
||||
:class "text-right"
|
||||
(span :class "text-sm text-stone-600" "Fetched: ")
|
||||
(span :class "text-2xl font-bold text-violet-700" (deref count))
|
||||
(span :class "text-sm text-stone-600" " times")))
|
||||
(~tw :tokens "text-right")
|
||||
(span (~tw :tokens "text-sm text-stone-600") "Fetched: ")
|
||||
(span (~tw :tokens "text-2xl font-bold text-violet-700") (deref count))
|
||||
(span (~tw :tokens "text-sm text-stone-600") " times")))
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(button
|
||||
:class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:sx-get "/sx/(geography.(reactive.(api.settle-data)))"
|
||||
:sx-target "#settle-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-on-settle "(swap! (use-store \"settle-count\") inc)"
|
||||
"Fetch Item")
|
||||
(button
|
||||
:class "px-3 py-1.5 rounded bg-stone-200 text-stone-700 text-sm font-medium hover:bg-stone-300"
|
||||
(~tw :tokens "px-3 py-1.5 rounded bg-stone-200 text-stone-700 text-sm font-medium hover:bg-stone-300")
|
||||
:on-click (fn (e) (reset! count 0))
|
||||
"Reset Counter"))
|
||||
(div
|
||||
:id "settle-result"
|
||||
:class "min-h-[2rem] rounded bg-stone-50 p-2"
|
||||
(p :class "text-sm text-stone-400 italic" "Nothing fetched yet.")))))
|
||||
(~tw :tokens "min-h-[2rem] rounded bg-stone-50 p-2")
|
||||
(p (~tw :tokens "text-sm text-stone-400 italic") "Nothing fetched yet.")))))
|
||||
|
||||
(defisland
|
||||
~reactive-islands/marshes/demo-marsh-signal-url
|
||||
@@ -905,10 +905,10 @@
|
||||
(let
|
||||
((mode (signal "products")) (query (signal "")))
|
||||
(div
|
||||
:class "rounded-lg border border-stone-200 bg-white p-4 my-4 space-y-3"
|
||||
(h4 :class "font-semibold text-stone-800" "Signal-Bound URL Demo")
|
||||
(~tw :tokens "rounded-lg border border-stone-200 bg-white p-4 my-4 space-y-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-800") "Signal-Bound URL Demo")
|
||||
(div
|
||||
:class "flex gap-2"
|
||||
(~tw :tokens "flex gap-2")
|
||||
(button
|
||||
:class (computed
|
||||
(fn
|
||||
@@ -946,14 +946,14 @@
|
||||
:on-click (fn (e) (reset! mode "posts"))
|
||||
"Posts"))
|
||||
(div
|
||||
:class "flex gap-2"
|
||||
(~tw :tokens "flex gap-2")
|
||||
(input
|
||||
:type "text"
|
||||
:bind query
|
||||
:placeholder "Search..."
|
||||
:class "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400")
|
||||
(~tw :tokens "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400"))
|
||||
(button
|
||||
:class "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
|
||||
(~tw :tokens "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
|
||||
:sx-get (computed
|
||||
(fn
|
||||
()
|
||||
@@ -967,7 +967,7 @@
|
||||
:sx-swap "innerHTML"
|
||||
"Search"))
|
||||
(p
|
||||
:class "text-xs text-stone-400 font-mono"
|
||||
(~tw :tokens "text-xs text-stone-400 font-mono")
|
||||
"URL: "
|
||||
(computed
|
||||
(fn
|
||||
@@ -980,9 +980,9 @@
|
||||
(deref query)))))
|
||||
(div
|
||||
:id "signal-results"
|
||||
:class "min-h-[3rem] rounded bg-stone-50 p-2"
|
||||
(~tw :tokens "min-h-[3rem] rounded bg-stone-50 p-2")
|
||||
(p
|
||||
:class "text-sm text-stone-400 italic"
|
||||
(~tw :tokens "text-sm text-stone-400 italic")
|
||||
"Select a category and search.")))))
|
||||
|
||||
(defisland
|
||||
@@ -992,10 +992,10 @@
|
||||
((view (signal "list"))
|
||||
(items (def-store "catalog-items" (fn () (signal (list))))))
|
||||
(div
|
||||
:class "rounded-lg border border-stone-200 bg-white p-4 my-4 space-y-3"
|
||||
(h4 :class "font-semibold text-stone-800" "Reactive View Transform")
|
||||
(~tw :tokens "rounded-lg border border-stone-200 bg-white p-4 my-4 space-y-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-800") "Reactive View Transform")
|
||||
(div
|
||||
:class "flex gap-2"
|
||||
(~tw :tokens "flex gap-2")
|
||||
(button
|
||||
:class (computed
|
||||
(fn
|
||||
@@ -1033,64 +1033,64 @@
|
||||
:on-click (fn (e) (reset! view "compact"))
|
||||
"Compact"))
|
||||
(div
|
||||
:class "flex items-center gap-3"
|
||||
(~tw :tokens "flex items-center gap-3")
|
||||
(button
|
||||
:class "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700"
|
||||
(~tw :tokens "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700")
|
||||
:sx-get "/sx/(geography.(reactive.(api.catalog)))"
|
||||
:sx-target "#catalog-msg"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch Catalog")
|
||||
(span
|
||||
:class "text-xs text-stone-400"
|
||||
(~tw :tokens "text-xs text-stone-400")
|
||||
"Server sends data + writes to signal"))
|
||||
(div :id "catalog-msg" :class "min-h-[1.5rem]")
|
||||
(div :id "catalog-msg" (~tw :tokens "min-h-[1.5rem]"))
|
||||
(div
|
||||
:class "min-h-[4rem]"
|
||||
(~tw :tokens "min-h-[4rem]")
|
||||
(when
|
||||
(> (len (deref items)) 0)
|
||||
(cond
|
||||
(= (deref view) "list")
|
||||
(div
|
||||
:class "space-y-2"
|
||||
(~tw :tokens "space-y-2")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
(div
|
||||
:class "flex items-center justify-between p-2 rounded bg-stone-50 border border-stone-100"
|
||||
(~tw :tokens "flex items-center justify-between p-2 rounded bg-stone-50 border border-stone-100")
|
||||
(div
|
||||
(span
|
||||
:class "text-sm font-medium text-stone-800"
|
||||
(~tw :tokens "text-sm font-medium text-stone-800")
|
||||
(get item "name"))
|
||||
(span
|
||||
:class "text-xs text-stone-500 block"
|
||||
(~tw :tokens "text-xs text-stone-500 block")
|
||||
(get item "desc")))
|
||||
(span
|
||||
:class "text-sm font-bold text-emerald-700"
|
||||
(~tw :tokens "text-sm font-bold text-emerald-700")
|
||||
"$"
|
||||
(get item "price"))))
|
||||
(deref items)))
|
||||
(= (deref view) "grid")
|
||||
(div
|
||||
:class "grid grid-cols-2 gap-2"
|
||||
(~tw :tokens "grid grid-cols-2 gap-2")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
(div
|
||||
:class "p-3 rounded bg-violet-50 border border-violet-200 text-center"
|
||||
(~tw :tokens "p-3 rounded bg-violet-50 border border-violet-200 text-center")
|
||||
(p
|
||||
:class "text-sm font-semibold text-stone-800"
|
||||
(~tw :tokens "text-sm font-semibold text-stone-800")
|
||||
(get item "name"))
|
||||
(p
|
||||
:class "text-lg font-bold text-emerald-700"
|
||||
(~tw :tokens "text-lg font-bold text-emerald-700")
|
||||
"$"
|
||||
(get item "price"))))
|
||||
(deref items)))
|
||||
:else (div
|
||||
:class "flex flex-wrap gap-1"
|
||||
(~tw :tokens "flex flex-wrap gap-1")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
(span
|
||||
:class "px-2 py-0.5 rounded bg-stone-100 text-xs text-stone-700"
|
||||
(~tw :tokens "px-2 py-0.5 rounded bg-stone-100 text-xs text-stone-700")
|
||||
(get item "name")))
|
||||
(deref items)))))))))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
(~docs/section :title "The Problem" :id "problem"
|
||||
(p "Islands are isolated by default. Signal props work when islands are adjacent, but not when they are:")
|
||||
(ul :class "space-y-1 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-1 text-stone-600 list-disc pl-5")
|
||||
(li "Distant in the DOM tree (header badge + drawer island)")
|
||||
(li "Defined in different " (code ".sx") " files")
|
||||
(li "Destroyed and recreated by htmx swaps"))
|
||||
@@ -19,7 +19,7 @@
|
||||
(p (code "def-store") " is " (strong "idempotent") " — calling it again with the same name returns the existing store. This means multiple components can call " (code "def-store") " defensively without double-creating."))
|
||||
|
||||
(~docs/section :title "Lifecycle" :id "lifecycle"
|
||||
(ol :class "space-y-2 text-stone-600 list-decimal list-inside"
|
||||
(ol (~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
|
||||
(li (strong "Page load: ") (code "def-store") " creates the store in a global registry. Signals are initialized.")
|
||||
(li (strong "Island hydration: ") "Each island calls " (code "use-store") " to get the shared signal dict. Derefs create subscriptions.")
|
||||
(li (strong "Island swap: ") "An island is destroyed by htmx swap. Its effects are cleaned up. But the store " (em "persists") " — it's in the page-level registry, not the island scope.")
|
||||
@@ -36,21 +36,21 @@
|
||||
(~docs/section :title "Spec" :id "spec"
|
||||
(p "Named stores are spec'd in " (code "signals.sx") " (section 12). Three functions:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Function")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Function")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "(def-store name init-fn)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Create or return existing named store. init-fn returns a dict of signals/computeds."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "(use-store name)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Get existing store by name. Errors if store doesn't exist."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "(def-store name init-fn)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Create or return existing named store. init-fn returns a dict of signals/computeds."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "(use-store name)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Get existing store by name. Errors if store doesn't exist."))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "(clear-stores)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Wipe all stores. Called on full page navigation."))))))))
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "(clear-stores)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Wipe all stores. Called on full page navigation."))))))))
|
||||
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
@@ -9,44 +9,44 @@
|
||||
(p "Phase 1 delivered the core reactive primitive: signals, effects, computed values, islands, disposal, stores, event bridges, and reactive DOM rendering. These are sufficient for any isolated interactive widget.")
|
||||
(p "Phase 2 fills the gaps that appear when you try to build " (em "real application UI") " with islands — forms, modals, dynamic styling, efficient lists, error handling, and async loading. Each feature is independently valuable and independently shippable. None requires changes to the signal runtime.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mt-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Feature")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "React equiv.")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Priority")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Spec file")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mt-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Feature")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "React equiv.")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Priority")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Spec file")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Input binding")
|
||||
(td :class "px-3 py-2 text-stone-500 text-xs" "controlled inputs")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-500" "adapter-dom.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Keyed reconciliation")
|
||||
(td :class "px-3 py-2 text-stone-500 text-xs" "key prop")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-500" "adapter-dom.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Portals")
|
||||
(td :class "px-3 py-2 text-stone-500 text-xs" "createPortal")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-500" "adapter-dom.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Error boundaries")
|
||||
(td :class "px-3 py-2 text-stone-500 text-xs" "componentDidCatch")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-500" "adapter-dom.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Suspense")
|
||||
(td :class "px-3 py-2 text-stone-500 text-xs" "Suspense + lazy")
|
||||
(td :class "px-3 py-2 text-stone-500 font-medium" "N/A")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-500" "covered by existing primitives"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Input binding")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-xs") "controlled inputs")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") "adapter-dom.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Keyed reconciliation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-xs") "key prop")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") "adapter-dom.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Portals")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-xs") "createPortal")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") "adapter-dom.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Error boundaries")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-xs") "componentDidCatch")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") "adapter-dom.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Suspense")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-xs") "Suspense + lazy")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 font-medium") "N/A")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") "covered by existing primitives"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Transitions")
|
||||
(td :class "px-3 py-2 text-stone-500 text-xs" "startTransition")
|
||||
(td :class "px-3 py-2 text-stone-500 font-medium" "N/A")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-500" "covered by existing primitives"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Transitions")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 text-xs") "startTransition")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 font-medium") "N/A")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") "covered by existing primitives"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; P0 — must have
|
||||
@@ -61,7 +61,7 @@
|
||||
(~docs/code :src (highlight ";; Bind a signal to an input\n(defisland ~reactive-islands/phase2/login-form ()\n (let ((email (signal \"\"))\n (password (signal \"\")))\n (form :on-submit (fn (e)\n (dom-prevent-default e)\n (fetch-json \"POST\" \"/api/login\"\n (dict \"email\" (deref email)\n \"password\" (deref password))))\n (input :type \"email\" :bind email\n :placeholder \"Email\")\n (input :type \"password\" :bind password\n :placeholder \"Password\")\n (button :type \"submit\" \"Log in\"))))" "lisp"))
|
||||
|
||||
(p "The " (code ":bind") " attribute is handled in " (code "adapter-dom.sx") "'s element rendering. For a signal " (code "s") ":")
|
||||
(ol :class "space-y-1 text-stone-600 list-decimal list-inside text-sm"
|
||||
(ol (~tw :tokens "space-y-1 text-stone-600 list-decimal list-inside text-sm")
|
||||
(li "Set the element's " (code "value") " to " (code "(deref s)") " initially")
|
||||
(li "Create an effect: when " (code "s") " changes externally, update " (code "el.value"))
|
||||
(li "Add an " (code "input") " event listener: on user input, call " (code "(reset! s el.value)"))
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
(~docs/subsection :title "Derived patterns"
|
||||
(p "Input binding composes with everything already built:")
|
||||
(ul :class "space-y-1 text-stone-600 list-disc pl-5 text-sm"
|
||||
(ul (~tw :tokens "space-y-1 text-stone-600 list-disc pl-5 text-sm")
|
||||
(li (strong "Validation: ") (code "(computed (fn () (>= (len (deref email)) 3)))") " — derived from the bound signal")
|
||||
(li (strong "Debounced search: ") "Effect with " (code "set-timeout") " cleanup, reading the bound signal")
|
||||
(li (strong "Form submission: ") (code "(deref email)") " in the submit handler gives the current value")
|
||||
@@ -90,7 +90,7 @@
|
||||
(~docs/code :src (highlight ";; Keyed list — items matched by :key, reused across updates\n(defisland ~reactive-islands/phase2/todo-list ()\n (let ((items (signal (list\n (dict \"id\" 1 \"text\" \"Buy milk\")\n (dict \"id\" 2 \"text\" \"Write spec\")\n (dict \"id\" 3 \"text\" \"Ship it\")))))\n (ul\n (map (fn (item)\n (li :key (get item \"id\")\n (span (get item \"text\"))\n (button :on-click (fn (e) ...)\n \"Remove\")))\n (deref items)))))" "lisp"))
|
||||
|
||||
(p "The reconciliation algorithm:")
|
||||
(ol :class "space-y-1 text-stone-600 list-decimal list-inside text-sm"
|
||||
(ol (~tw :tokens "space-y-1 text-stone-600 list-decimal list-inside text-sm")
|
||||
(li "Extract key from each rendered child (from " (code ":key") " attr or item identity)")
|
||||
(li "Build a map of " (code "old-key → DOM node") " from previous render")
|
||||
(li "Walk new items: if key exists in old map, " (strong "reuse") " the DOM node (move to correct position). If not, render fresh.")
|
||||
@@ -113,7 +113,7 @@
|
||||
(~docs/code :src (highlight ";; portal — render children into a target element\n(defisland ~reactive-islands/phase2/modal-trigger ()\n (let ((open? (signal false)))\n (div\n (button :on-click (fn (e) (swap! open? not))\n \"Open Modal\")\n\n ;; Portal: children rendered into #modal-root,\n ;; not into this island's DOM\n (portal \"#modal-root\"\n (when (deref open?)\n (div :class \"fixed inset-0 bg-black/50 flex items-center justify-center\"\n (div :class \"bg-white rounded-lg p-6 max-w-md\"\n (h2 \"Modal Title\")\n (p \"This is rendered outside the island's DOM subtree.\")\n (button :on-click (fn (e) (reset! open? false))\n \"Close\"))))))))" "lisp"))
|
||||
|
||||
(p "Implementation in " (code "adapter-dom.sx") ":")
|
||||
(ol :class "space-y-1 text-stone-600 list-decimal list-inside text-sm"
|
||||
(ol (~tw :tokens "space-y-1 text-stone-600 list-decimal list-inside text-sm")
|
||||
(li (code "portal") " is a new render-dom form (add to " (code "RENDER_DOM_FORMS") " and " (code "dispatch-render-form") ")")
|
||||
(li "First arg is a CSS selector string for the target container")
|
||||
(li "Remaining args are children, rendered normally via " (code "render-to-dom"))
|
||||
@@ -135,7 +135,7 @@
|
||||
(~docs/code :src (highlight ";; error-boundary — catch errors in island subtrees\n(defisland ~reactive-islands/phase2/resilient-widget ()\n (error-boundary\n ;; Fallback: shown when children throw\n (fn (err)\n (div :class \"p-4 bg-red-50 border border-red-200 rounded\"\n (p :class \"text-red-700 font-medium\" \"Something went wrong\")\n (p :class \"text-red-500 text-sm\" (error-message err))))\n ;; Children: the happy path\n (do\n (~risky-component)\n (~another-component))))" "lisp"))
|
||||
|
||||
(p "Implementation:")
|
||||
(ol :class "space-y-1 text-stone-600 list-decimal list-inside text-sm"
|
||||
(ol (~tw :tokens "space-y-1 text-stone-600 list-decimal list-inside text-sm")
|
||||
(li (code "error-boundary") " is a new render-dom form")
|
||||
(li "First arg: fallback function " (code "(fn (error) ...)") " that returns DOM")
|
||||
(li "Remaining args: children rendered inside a try/catch")
|
||||
@@ -170,17 +170,17 @@
|
||||
|
||||
(~docs/section :title "Implementation Order" :id "order"
|
||||
(p "Each feature is independent. Suggested order based on dependency and value:")
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal list-inside"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal list-inside")
|
||||
(li (strong "Input binding") " (P0) — unlocks forms. Smallest change, biggest impact. One new function in adapter-dom.sx, two platform primitives (" (code "dom-set-prop") ", " (code "dom-get-prop") "). Add to demo page immediately.")
|
||||
(li (strong "Keyed reconciliation") " (P0) — unlocks efficient dynamic lists. Replace reactive-list's effect body. Add " (code ":key") " extraction. No new primitives needed.")
|
||||
(li (strong "Portals") " (P1) — one new render-dom form. Needs disposal integration. Unlocks modals, tooltips, toasts.")
|
||||
(li (strong "Error boundaries") " (P2) — one new render-dom form with try/catch. Independent of everything else."))
|
||||
|
||||
(p :class "mt-4 text-stone-600" "Every feature follows the same pattern: spec in " (code ".sx") " → bootstrap to JS/Python → add platform primitives → add demo island. No feature requires changes to the signal runtime, the evaluator, or the rendering pipeline. They are all additive."))
|
||||
(p (~tw :tokens "mt-4 text-stone-600") "Every feature follows the same pattern: spec in " (code ".sx") " → bootstrap to JS/Python → add platform primitives → add demo island. No feature requires changes to the signal runtime, the evaluator, or the rendering pipeline. They are all additive."))
|
||||
|
||||
(~docs/section :title "What we are NOT building" :id "not-building"
|
||||
(p "Some React features are deliberately excluded:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Virtual DOM / diffing") " — SX uses fine-grained signals. There is no component re-render to diff against. The " (code "reactive-text") ", " (code "reactive-attr") ", " (code "reactive-fragment") ", and " (code "reactive-list") " primitives update the exact DOM nodes that changed.")
|
||||
(li (strong "JSX / template compilation") " — SX is interpreted at runtime. No build step. The s-expression syntax " (em "is") " the component tree — there is nothing to compile.")
|
||||
(li (strong "Server components (React-style)") " — SX already has a richer version. The " (code "aser") " mode evaluates server-side logic and serializes the result as SX wire format. Components can be expanded on the server or deferred to the client. This is more flexible than React's server/client component split.")
|
||||
|
||||
@@ -17,36 +17,36 @@
|
||||
(p
|
||||
"These two bars are independent. You can have server-rendered HTML with client state (SSR + hydrated React). You can have client-rendered components with server state (current SX). The combination creates four quadrants:")
|
||||
(div
|
||||
:class "overflow-x-auto mt-4 mb-4"
|
||||
(~tw :tokens "overflow-x-auto mt-4 mb-4")
|
||||
(table
|
||||
:class "w-full text-sm text-left"
|
||||
(~tw :tokens "w-full text-sm text-left")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200"
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "")
|
||||
(~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "")
|
||||
(th
|
||||
:class "py-2 px-3 font-semibold text-stone-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
||||
"Server State")
|
||||
(th
|
||||
:class "py-2 px-3 font-semibold text-stone-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
||||
"Client State")))
|
||||
(tbody
|
||||
:class "text-stone-600"
|
||||
(~tw :tokens "text-stone-600")
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "py-2 px-3 font-semibold text-stone-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
||||
"Server Rendering")
|
||||
(td :class "py-2 px-3" "Pure hypermedia (htmx)")
|
||||
(td :class "py-2 px-3" "SSR + hydrated islands (Next.js)"))
|
||||
(td (~tw :tokens "py-2 px-3") "Pure hypermedia (htmx)")
|
||||
(td (~tw :tokens "py-2 px-3") "SSR + hydrated islands (Next.js)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "py-2 px-3 font-semibold text-stone-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
||||
"Client Rendering")
|
||||
(td :class "py-2 px-3" "SX wire format (current)")
|
||||
(td (~tw :tokens "py-2 px-3") "SX wire format (current)")
|
||||
(td
|
||||
:class "py-2 px-3 font-semibold text-violet-700"
|
||||
(~tw :tokens "py-2 px-3 font-semibold text-violet-700")
|
||||
"Reactive islands (this plan)")))))
|
||||
(p
|
||||
"Today SX occupies the bottom-left quadrant — client-rendered components with server state. This plan adds the bottom-right: "
|
||||
@@ -110,36 +110,36 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "text-violet-700 underline"
|
||||
(~tw :tokens "text-violet-700 underline")
|
||||
"event bridge")
|
||||
".")
|
||||
(~docs/subsection
|
||||
:title "Navigation scenarios"
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(~tw :tokens "space-y-3")
|
||||
(div
|
||||
:class "rounded border border-green-200 bg-green-50 p-3"
|
||||
(div :class "font-semibold text-green-800" "Swap inside island")
|
||||
(~tw :tokens "rounded border border-green-200 bg-green-50 p-3")
|
||||
(div (~tw :tokens "font-semibold text-green-800") "Swap inside island")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Lake content replaced. Signals survive. Effects can rebind to new DOM. User state intact."))
|
||||
(div
|
||||
:class "rounded border border-green-200 bg-green-50 p-3"
|
||||
(div :class "font-semibold text-green-800" "Swap outside island")
|
||||
(~tw :tokens "rounded border border-green-200 bg-green-50 p-3")
|
||||
(div (~tw :tokens "font-semibold text-green-800") "Swap outside island")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Different part of page updated. Island completely unaffected. User state intact."))
|
||||
(div
|
||||
:class "rounded border border-amber-200 bg-amber-50 p-3"
|
||||
(div :class "font-semibold text-amber-800" "Swap replaces island")
|
||||
(~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3")
|
||||
(div (~tw :tokens "font-semibold text-amber-800") "Swap replaces island")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Island disposed. Local signals lost. Named stores persist — new island reconnects via use-store."))
|
||||
(div
|
||||
:class "rounded border border-stone-200 p-3"
|
||||
(div :class "font-semibold text-stone-800" "Full page navigation")
|
||||
(~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(div (~tw :tokens "font-semibold text-stone-800") "Full page navigation")
|
||||
(p
|
||||
:class "text-sm text-stone-600 mt-1"
|
||||
(~tw :tokens "text-sm text-stone-600 mt-1")
|
||||
"Everything cleared. clean slate. clear-stores wipes the registry.")))))
|
||||
(~docs/section
|
||||
:title "Reactive DOM Rendering"
|
||||
@@ -183,120 +183,120 @@
|
||||
:title "Status"
|
||||
:id "status"
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200"
|
||||
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(~tw :tokens "w-full text-left text-sm")
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Task")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
|
||||
(~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Task")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Signal runtime")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Signal runtime")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"signals.sx: signal, deref, reset!, swap!, computed, effect, batch"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Named stores (L3)")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Named stores (L3)")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"signals.sx: def-store, use-store, clear-stores"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Event bridge")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Event bridge")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"signals.sx: emit-event, on-event, bridge-event"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Event bindings")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Event bindings")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"adapter-dom.sx: :on-click (fn ...) → domListen"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "data-sx-emit")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "data-sx-emit")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"orchestration.sx: auto-dispatch custom events from server content"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Client hydration")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client hydration")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"boot.sx: hydrate-island, dispose-island, post-swap wiring"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Bootstrapping")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bootstrapping")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"All functions transpiled to JS and Python, platform primitives implemented"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Island disposal")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Island disposal")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"boot.sx, orchestration.sx: effects/computeds auto-register disposers, pre-swap cleanup"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Reactive list")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Reactive list")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"adapter-dom.sx: map + deref auto-upgrades to reactive-list"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"Input binding + keyed lists")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"adapter-dom.sx: :bind signal, :key attr"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Portals")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Portals")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"adapter-dom.sx: portal render-dom form"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Error boundaries")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Error boundaries")
|
||||
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"adapter-dom.sx: error-boundary render-dom form"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Suspense")
|
||||
(td :class "px-3 py-2 text-stone-500 font-medium" "N/A")
|
||||
(~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Suspense")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 font-medium") "N/A")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"covered by existing primitives"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Transitions")
|
||||
(td :class "px-3 py-2 text-stone-500 font-medium" "N/A")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Transitions")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-500 font-medium") "N/A")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
(~tw :tokens "px-3 py-2 text-stone-700")
|
||||
"covered by existing primitives"))))))
|
||||
(~docs/section
|
||||
:title "Design Principles"
|
||||
:id "principles"
|
||||
(ol
|
||||
:class "space-y-3 text-stone-600 list-decimal list-inside"
|
||||
(~tw :tokens "space-y-3 text-stone-600 list-decimal list-inside")
|
||||
(li
|
||||
(strong "Islands are opt-in.")
|
||||
" "
|
||||
@@ -324,10 +324,10 @@
|
||||
(strong "No build step.")
|
||||
" Reactive bindings are created at runtime during DOM rendering. No JSX compilation, no Babel transforms, no Vite plugins."))
|
||||
(p
|
||||
:class "mt-4"
|
||||
(~tw :tokens "mt-4")
|
||||
"The recommendation from the "
|
||||
(a
|
||||
:href "/sx/(etc.(essay.client-reactivity))"
|
||||
:class "text-violet-700 underline"
|
||||
(~tw :tokens "text-violet-700 underline")
|
||||
"Client Reactivity")
|
||||
" essay was: \"Tier 4 probably never.\" This plan is what happens when the answer changes. The design avoids every footgun that essay warns about — no useState cascading to useEffect cascading to Context cascading to a state management library. Signals are one primitive. Islands are one boundary. The rest is composition."))))
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
(defcomp ~reactive-islands/test-runner-placeholder () (div :class "rounded border border-stone-200 bg-stone-50 p-4" :data-sx-island "reactive-islands/test-runner" (p :class "text-stone-400 text-sm italic" "Loading tests...")))
|
||||
(defcomp ~reactive-islands/test-runner-placeholder () (div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4") :data-sx-island "reactive-islands/test-runner" (p (~tw :tokens "text-stone-400 text-sm italic") "Loading tests...")))
|
||||
|
||||
(defisland ~reactive-islands/test-runner () (let ((results (signal nil)) (running (signal false))) (letrec ((run-tests (fn () (reset! running true) (let ((script-el (dom-query "script[data-for]")) (test-results (list))) (when script-el (let ((test-src (host-get script-el "textContent")) (parsed (let ((raw (host-get script-el "textContent")) (decoded (host-call raw "replaceAll" """ "\""))) (sx-parse (host-call decoded "replaceAll" "&" "&"))))) (for-each (fn (expr) (when (and (list? expr) (not (empty? expr)) (= (type-of (first expr)) "symbol")) (let ((head (symbol-name (first expr)))) (cond (= head "defsuite") (for-each (fn (child) (when (and (list? child) (not (empty? child)) (= (type-of (first child)) "symbol") (= (symbol-name (first child)) "deftest")) (append! test-results (try-test (nth child 1) (last child))))) (slice expr 2)) (= head "deftest") (append! test-results (try-test (nth expr 1) (last expr))))))) parsed))) (reset! results test-results) (reset! running false)))) (try-test (fn (name body) (let ((result (cek-try (fn () (eval-expr body (global-env)) true) (fn (err) err)))) (if (= result true) {:pass true :error nil :name name} {:pass false :error (str result) :name name}))))) (run-tests) (div :class "rounded border border-stone-200 bg-stone-50 p-4" (div :class "flex items-center justify-between mb-3" (h4 :class "text-sm font-semibold text-stone-700" "Tests") (button :class "px-2 py-1 text-xs rounded bg-stone-200 hover:bg-stone-300 transition" :on-click (fn (e) (run-tests)) "Re-run")) (if (deref running) (p :class "text-stone-400 text-sm italic" "Running...") (if (nil? (deref results)) (p :class "text-stone-400 text-sm italic" "No test source found") (let ((r (deref results)) (pass-count (len (filter (fn (t) (get t "pass")) r))) (fail-count (len (filter (fn (t) (not (get t "pass"))) r)))) (div :class "space-y-2" (div :class "text-sm font-mono" (span :class (if (= fail-count 0) "text-emerald-600 font-semibold" "text-red-600 font-semibold") (str pass-count "/" (len r) " passed"))) (map (fn (t) (div :class "flex items-start gap-2 text-xs font-mono py-0.5" (span :class (if (get t "pass") "text-emerald-500" "text-red-500") (if (get t "pass") "✓" "✗")) (span :class "text-stone-600" (get t "name")) (when (get t "error") (span :class "text-red-400 ml-2" (get t "error"))))) r)))))))))
|
||||
(defisland ~reactive-islands/test-runner () (let ((results (signal nil)) (running (signal false))) (letrec ((run-tests (fn () (reset! running true) (let ((script-el (dom-query "script[data-for]")) (test-results (list))) (when script-el (let ((test-src (host-get script-el "textContent")) (parsed (let ((raw (host-get script-el "textContent")) (decoded (host-call raw "replaceAll" """ "\""))) (sx-parse (host-call decoded "replaceAll" "&" "&"))))) (for-each (fn (expr) (when (and (list? expr) (not (empty? expr)) (= (type-of (first expr)) "symbol")) (let ((head (symbol-name (first expr)))) (cond (= head "defsuite") (for-each (fn (child) (when (and (list? child) (not (empty? child)) (= (type-of (first child)) "symbol") (= (symbol-name (first child)) "deftest")) (append! test-results (try-test (nth child 1) (last child))))) (slice expr 2)) (= head "deftest") (append! test-results (try-test (nth expr 1) (last expr))))))) parsed))) (reset! results test-results) (reset! running false)))) (try-test (fn (name body) (let ((result (cek-try (fn () (eval-expr body (global-env)) true) (fn (err) err)))) (if (= result true) {:pass true :error nil :name name} {:pass false :error (str result) :name name}))))) (run-tests) (div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4") (div (~tw :tokens "flex items-center justify-between mb-3") (h4 (~tw :tokens "text-sm font-semibold text-stone-700") "Tests") (button (~tw :tokens "px-2 py-1 text-xs rounded bg-stone-200 hover:bg-stone-300 transition") :on-click (fn (e) (run-tests)) "Re-run")) (if (deref running) (p (~tw :tokens "text-stone-400 text-sm italic") "Running...") (if (nil? (deref results)) (p (~tw :tokens "text-stone-400 text-sm italic") "No test source found") (let ((r (deref results)) (pass-count (len (filter (fn (t) (get t "pass")) r))) (fail-count (len (filter (fn (t) (not (get t "pass"))) r)))) (div (~tw :tokens "space-y-2") (div (~tw :tokens "text-sm font-mono") (span :class (if (= fail-count 0) "text-emerald-600 font-semibold" "text-red-600 font-semibold") (str pass-count "/" (len r) " passed"))) (map (fn (t) (div (~tw :tokens "flex items-start gap-2 text-xs font-mono py-0.5") (span :class (if (get t "pass") "text-emerald-500" "text-red-500") (if (get t "pass") "✓" "✗")) (span (~tw :tokens "text-stone-600") (get t "name")) (when (get t "error") (span (~tw :tokens "text-red-400 ml-2") (get t "error"))))) r)))))))))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user