Capabilities: rename from Semantics, add server/client fetch demos

- Removed duplicate one-liner sx-nav-tree (leftover from before refactor)
- Renamed "Semantics" → "Capabilities" in nav tree — direct geography
  entry, no subsection
- Added "Where code evaluates" section with three demos:
  - Server-side fetch (SSR with io capability)
  - Client-side fetch (island with reactive signals)
  - Same component both ways (isomorphic evaluation)
- Shows how capabilities abstract over location — same code runs
  server or client depending on the evaluation context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 16:26:05 +00:00
parent 77a80e0640
commit 07d1603b2c
2 changed files with 173 additions and 39 deletions

View File

@@ -1,52 +1,186 @@
(defcomp ~geography/capabilities-content ()
(~docs/page :title "Capabilities"
(p :class "text-stone-500 text-sm italic mb-8"
(defcomp
~geography/capabilities-content
()
(~docs/page
:title "Capabilities"
(p
:class "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" :id "model"
(p "SX expressions evaluate in contexts that provide named capabilities. A capability is a permission to perform a class of side effect: " (code "io") " for network/filesystem, " (code "mutation") " for mutable state, " (code "dom") " for browser DOM, " (code "render") " for rendering operations.")
(p "The key insight: capabilities are abstract. " (code "io") " doesn't mean 'server' — it means 'this context can perform input/output.' A server, an edge worker, and a browser tab can all provide " (code "io") ". The language doesn't care where the code runs, only what it's allowed to do.")
(~docs/code :src (str "(with-capabilities (list \"pure\" \"mutation\")\n (fn ()\n (has-capability? \"io\") ;; false\n (has-capability? \"pure\") ;; true\n (require-capability! \"io\") ;; ERROR: Capability 'io' not available\n ))")))
(~docs/section :title "Capability primitives" :id "primitives"
(table :class "min-w-full text-sm mb-6"
(~docs/section
:title "The model"
:id "model"
(p
"SX expressions evaluate in contexts that provide named capabilities. A capability is a permission to perform a class of side effect: "
(code "io")
" for network/filesystem, "
(code "mutation")
" for mutable state, "
(code "dom")
" for browser DOM, "
(code "render")
" for rendering operations.")
(p
"The key insight: capabilities are abstract. "
(code "io")
" doesn't mean 'server' — it means 'this context can perform input/output.' A server, an edge worker, and a browser tab can all provide "
(code "io")
". The language doesn't care where the code runs, only what it's allowed to do.")
(~docs/code
:src (str
"(with-capabilities (list \"pure\" \"mutation\")\n (fn ()\n (has-capability? \"io\") ;; false\n (has-capability? \"pure\") ;; true\n (require-capability! \"io\") ;; ERROR: Capability 'io' not available\n ))")))
(~docs/section
:title "Capability primitives"
:id "primitives"
(table
:class "min-w-full text-sm mb-6"
(thead
(tr
(th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Primitive")
(th
:class "text-left pr-4 pb-2 font-semibold text-stone-700"
"Primitive")
(th :class "text-left pb-2 font-semibold text-stone-700" "Purpose")))
(tbody :class "text-stone-600"
(tbody
:class "text-stone-600"
(tr
(td :class "pr-4 py-1 font-mono text-xs" "with-capabilities")
(td :class "py-1" "Restrict capabilities for a body. Takes a list of capability names and a thunk."))
(td
:class "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 :class "py-1" "Check if a capability is available. Returns true in unrestricted contexts."))
(td
:class "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 :class "py-1" "Assert a capability. Error with a clear message if not available."))
(td
:class "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 :class "py-1" "Return the current capability set, or nil if unrestricted.")))))
(~docs/section :title "Effect annotations" :id "effects"
(td
:class "py-1"
"Return the current capability set, or nil if unrestricted.")))))
(~docs/section
:title "Effect annotations"
:id "effects"
(p "Every function can declare its effect requirements:")
(~docs/code :src (str "(define fetch-user :effects (io)\n (fn (id) (http-get (str \"/users/\" id))))\n\n(define format-name :effects ()\n (fn (user) (str (get user \"first\") \" \" (get user \"last\"))))"))
(p (code "format-name") " is pure — it can run anywhere. " (code "fetch-user") " requires " (code "io") " — it can only run in a context that provides that capability. The " (em "where") " is decided by the host, not the language."))
(~docs/section :title "Standard capabilities" :id "standard"
(table :class "min-w-full text-sm mb-6"
(~docs/code
:src (str
"(define fetch-user :effects (io)\n (fn (id) (http-get (str \"/users/\" id))))\n\n(define format-name :effects ()\n (fn (user) (str (get user \"first\") \" \" (get user \"last\"))))"))
(p
(code "format-name")
" is pure — it can run anywhere. "
(code "fetch-user")
" requires "
(code "io")
" — it can only run in a context that provides that capability. The "
(em "where")
" is decided by the host, not the language."))
(~docs/section
:title "Standard capabilities"
:id "standard"
(table
:class "min-w-full text-sm mb-6"
(thead
(tr
(th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Capability")
(th :class "text-left pb-2 font-semibold text-stone-700" "What it permits")))
(tbody :class "text-stone-600"
(tr (td :class "pr-4 py-1 font-mono text-xs" "pure") (td :class "py-1" "No side effects. Deterministic. Cacheable. Runnable anywhere."))
(tr (td :class "pr-4 py-1 font-mono text-xs" "mutation") (td :class "py-1" "Mutable state: set!, append!, dict-set!, signal mutation."))
(tr (td :class "pr-4 py-1 font-mono text-xs" "io") (td :class "py-1" "External I/O: network, filesystem, timers, promises."))
(tr (td :class "pr-4 py-1 font-mono text-xs" "dom") (td :class "py-1" "Browser DOM operations: create elements, set attributes, event listeners."))
(tr (td :class "pr-4 py-1 font-mono text-xs" "render") (td :class "py-1" "Rendering operations: component expansion, HTML generation, DOM patching.")))))
(~docs/section :title "Why not phases?" :id "why-not-phases"
(p "Many frameworks hard-code evaluation phases: 'server' vs 'client', 'build time' vs 'runtime'. This bakes in assumptions about deployment topology.")
(p "SX might run on a server, in a browser, at an edge node, in a WebAssembly sandbox, on another peer's machine, or in a context that doesn't exist yet. Capabilities are the right abstraction because they describe " (em "what") " an expression needs, not " (em "where") " it runs.")
(p "A 'server' is just a context that provides " (code "(pure mutation io)") ". A 'browser' provides " (code "(pure mutation io dom render)") ". An edge worker might provide " (code "(pure io)") " but not " (code "mutation") ". The evaluator doesn't need to know — it just checks capabilities."))))
(th
:class "text-left pr-4 pb-2 font-semibold text-stone-700"
"Capability")
(th
:class "text-left pb-2 font-semibold text-stone-700"
"What it permits")))
(tbody
:class "text-stone-600"
(tr
(td :class "pr-4 py-1 font-mono text-xs" "pure")
(td
:class "py-1"
"No side effects. Deterministic. Cacheable. Runnable anywhere."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "mutation")
(td
:class "py-1"
"Mutable state: set!, append!, dict-set!, signal mutation."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "io")
(td
:class "py-1"
"External I/O: network, filesystem, timers, promises."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "dom")
(td
:class "py-1"
"Browser DOM operations: create elements, set attributes, event listeners."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "render")
(td
:class "py-1"
"Rendering operations: component expansion, HTML generation, DOM patching.")))))
(~docs/section
:title "Why not phases?"
:id "why-not-phases"
(p
"Many frameworks hard-code evaluation phases: 'server' vs 'client', 'build time' vs 'runtime'. This bakes in assumptions about deployment topology.")
(p
"SX might run on a server, in a browser, at an edge node, in a WebAssembly sandbox, on another peer's machine, or in a context that doesn't exist yet. Capabilities are the right abstraction because they describe "
(em "what")
" an expression needs, not "
(em "where")
" it runs.")
(p
"A 'server' is just a context that provides "
(code "(pure mutation io)")
". A 'browser' provides "
(code "(pure mutation io dom render)")
". An edge worker might provide "
(code "(pure io)")
" but not "
(code "mutation")
". The evaluator doesn't need to know — it just checks capabilities."))
(~docs/section
:title "Where code evaluates"
:id "where"
(p
"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")
(p
"A component that fetches data during SSR. The server has "
(code "io")
" capability, so the fetch runs at render time. The browser receives pre-rendered HTML — no fetch, no loading state, no flash.")
(~docs/code
:src (highlight
"(defcomp ~user-card (&key user-id)\n :effects (io)\n (let ((data (fetch-json (str \"/api/users/\" user-id))))\n (div :class \"rounded border p-4\"\n (h3 (get data \"name\"))\n (p :class \"text-sm text-stone-500\" (get data \"email\")))))"
"lisp"))
(p
"This renders to static HTML on the server. The client never sees the fetch. The "
(code ":effects (io)")
" 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")
(p
"An island that fetches after hydration. The browser has "
(code "io")
" too, but the fetch happens reactively — the signal drives the UI update.")
(~docs/code
:src (highlight
"(defisland ~user-profile (&key user-id)\n (let ((data (signal nil))\n (loading (signal true)))\n ;; Fetch on mount — runs in browser\n (effect (fn ()\n (fetch-json (str \"/api/users/\" user-id)\n (fn (result)\n (reset! data result)\n (reset! loading false)))))\n ;; Reactive UI\n (div :class \"rounded border p-4\"\n (if (deref loading)\n (p \"Loading...\")\n (do\n (h3 (get (deref data) \"name\"))\n (p :class \"text-sm text-stone-500\"\n (get (deref data) \"email\")))))))"
"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")
(p
"With isomorphic evaluation, a single component can be SSR'd on first load (server provides "
(code "io")
"), then hydrated in the browser where the island takes over with reactive signals. The capability set changes but the component definition doesn't.")
(~docs/code
:src (highlight
";; Server: full capabilities, renders HTML\n;; Browser: same component, hydrates as island\n;; The evaluator checks capabilities, not location\n\n(defcomp ~data-card (&key url)\n :effects (io)\n (let ((result (fetch-json url)))\n (div :class \"p-4 rounded bg-white shadow\"\n (h3 (get result \"title\"))\n (p (get result \"body\")))))"
"lisp"))
(p
"On the server, "
(code "fetch-json")
" runs synchronously. In the browser, the same call goes through the async adapter. The component doesn't know or care. Capabilities are the abstraction boundary — not 'server' and 'client'."))))

View File

@@ -76,7 +76,8 @@
(dict
:href "/sx/(geography.(cek))"
:label "CEK Machine"
:children cek-nav-items)))
:children cek-nav-items)
(dict :href "/sx/(geography.(capabilities))" :label "Capabilities")))
(dict
:href "/sx/(language)"
:label "Language"
@@ -154,7 +155,6 @@
(= (get child "href") path)
(has-descendant-href? child path)))
children)))))
(define sx-nav-tree {:href "/sx/" :children (list {:href "/sx/(geography)" :children (list {:href "/sx/(geography.(reactive))" :children reactive-islands-nav-items :label "Reactive Islands"} {:href "/sx/(geography.(hypermedia))" :children (list {:href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items :label "Reference"} {:href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items :label "Examples"}) :label "Hypermedia Lakes"} {:href "/sx/(geography.(scopes))" :summary "The unified primitive beneath provide, collect!, spreads, and islands. Named scope with downward value, upward accumulation, and a dedup flag." :label "Scopes"} {:href "/sx/(geography.(provide))" :summary "Sugar for scope-with-value. Render-time dynamic scope — the substrate beneath spreads, CSSX, and script collection." :label "Provide / Emit!"} {:href "/sx/(geography.(spreads))" :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, built on scopes." :label "Spreads"} {:href "/sx/(geography.(marshes))" :children marshes-examples-nav-items :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted." :label "Marshes"} {:href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items :label "Isomorphism"} {:href "/sx/(geography.(cek))" :children cek-nav-items :label "CEK Machine"} {:href "/sx/(geography.(capabilities))" :children semantics-nav-items :label "Semantics"}) :label "Geography"} {:href "/sx/(language)" :children (list {:href "/sx/(language.(doc))" :children docs-nav-items :label "Docs"} {:href "/sx/(language.(spec))" :children specs-nav-items :label "Specs"} {:href "/sx/(language.(spec.(explore.evaluator)))" :label "Spec Explorer"} {:href "/sx/(language.(bootstrapper))" :children bootstrappers-nav-items :label "Bootstrappers"} {:href "/sx/(language.(test))" :children testing-nav-items :label "Testing"}) :label "Language"} {:href "/sx/(applications)" :children (list {:href "/sx/(applications.(sx-urls))" :label "SX URLs"} {:href "/sx/(applications.(cssx))" :children cssx-nav-items :label "CSSX"} {:href "/sx/(applications.(protocol))" :children protocols-nav-items :label "Protocols"} {:href "/sx/(applications.(sx-pub))" :label "sx-pub"} {:href "/sx/(applications.(reactive-runtime))" :children reactive-runtime-nav-items :label "Reactive Runtime"}) :label "Applications"} {:href "/sx/(tools)" :children tools-nav-items :label "Tools"} {:href "/sx/(etc)" :children (list {:href "/sx/(etc.(essay))" :children essays-nav-items :label "Essays"} {:href "/sx/(etc.(philosophy))" :children philosophy-nav-items :label "Philosophy"} {:href "/sx/(etc.(plan))" :children plans-nav-items :label "Plans"}) :label "Etc"}) :label "sx"})
(define
find-nav-match