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")
|
||||
|
||||
Reference in New Issue
Block a user