Two bugs caused code blocks to render empty across the site: 1. ~docs/code component had parameter named `code` which collided with the HTML <code> tag name. Renamed to `src` and updated all 57 callers. Added font-mono class for explicit monospace. 2. Batched IO dispatch in ocaml_bridge.py only skipped one leading number (batch ID) but the format has two (epoch + ID): (io-request EPOCH ID "name" args...). Changed to skip all leading numbers so the string name is correctly found. This fixes highlight and other batchable helpers returning empty results. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
450 lines
28 KiB
Plaintext
450 lines
28 KiB
Plaintext
;; Scoped Effects — The Deepest Primitive
|
||
;; Algebraic effects as the unified foundation for spreads, islands, lakes, and context.
|
||
|
||
(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"
|
||
"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. "
|
||
"This plan traces that pattern to its foundation in algebraic effects "
|
||
"and proposes " (code "scope") " as the single primitive underneath everything.")
|
||
|
||
;; =====================================================================
|
||
;; I. The Observation
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "The observation" :id "observation"
|
||
|
||
(p "SX has accumulated several mechanisms that all do variations of the same thing:")
|
||
|
||
(table :class "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")))
|
||
(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
|
||
(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"))))
|
||
|
||
(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.")
|
||
|
||
(p "But look at the table. Every row is: " (em "a named region, "
|
||
"a direction of data flow, a moment when effects are realised, "
|
||
"and a boundary that determines what crosses") ". They are all the same shape."))
|
||
|
||
|
||
;; =====================================================================
|
||
;; II. Three propagation modes
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Three propagation modes" :id "propagation"
|
||
|
||
(p "The \"when\" column has exactly three values. These are the three "
|
||
"propagation modes of the render tree:")
|
||
|
||
(~docs/subsection :title "Render propagation"
|
||
(p "Effects are realised " (strong "during the render pass") ". "
|
||
"Synchronous, one-shot, deterministic. The renderer walks the tree, "
|
||
"encounters effects, handles them immediately.")
|
||
(p "Instances: " (code "provide/context/emit!") ", " (code "spread") ", "
|
||
(code "collect!/collected") ".")
|
||
(p "Characteristic: by the time rendering finishes, all render-time effects "
|
||
"are fully resolved. The output is static HTML with everything baked in."))
|
||
|
||
(~docs/subsection :title "Reactive propagation"
|
||
(p "Effects are realised " (strong "when signals change") ". "
|
||
"Fine-grained, incremental, potentially infinite. A signal notifies "
|
||
"its subscribers, each subscriber updates its piece of the DOM.")
|
||
(p "Instances: " (code "signal/effect/computed") ", " (code "reactive-spread") ", "
|
||
(code "defisland") ", " (code "def-store/use-store") ".")
|
||
(p "Characteristic: the initial render produces a snapshot. "
|
||
"Subsequent changes propagate through the signal graph without re-rendering. "
|
||
"Each signal write touches only the DOM nodes that actually depend on it."))
|
||
|
||
(~docs/subsection :title "Morph propagation"
|
||
(p "Effects are realised " (strong "when the server sends new HTML") ". "
|
||
"Network-bound, server-initiated, structural. The morph algorithm "
|
||
"compares old and new DOM trees and surgically updates.")
|
||
(p "Instances: " (code "lake") ", full-page morph, " (code "sx-get/sx-post") " responses.")
|
||
(p "Characteristic: the server is the source of truth for content. "
|
||
"The client has reactive state that must be " (em "protected") " during morph. "
|
||
"Lakes mark the boundaries: morph enters islands, finds lake elements by ID, "
|
||
"updates their children, leaves reactive attrs untouched.")))
|
||
|
||
|
||
;; =====================================================================
|
||
;; III. The scope primitive
|
||
;; =====================================================================
|
||
|
||
(~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"
|
||
(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"))
|
||
|
||
(~docs/code :src (highlight "(scope \"name\"\n :value expr ;; downward (readable by descendants)\n :propagation :render ;; :render | :reactive | :morph\n body...)" "lisp"))
|
||
|
||
(p "Every existing mechanism is a scope with a specific configuration:")
|
||
|
||
(table :class "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")))
|
||
(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
|
||
(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)"))))
|
||
|
||
(~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"))
|
||
(p "Three scopes, three propagation modes, nested naturally. "
|
||
"The reactive scope manages signal lifecycle. "
|
||
"The render scope provides downward context. "
|
||
"The morph scope marks where server content flows in. "
|
||
"Each scope handles its own effects without knowing about the others.")))
|
||
|
||
|
||
;; =====================================================================
|
||
;; IV. The 2×3 matrix
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "The 2×3 matrix" :id "matrix"
|
||
|
||
(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"
|
||
(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")))
|
||
(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
|
||
(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"))))
|
||
|
||
(p "Some cells are already implemented. Some are new. "
|
||
"The point is that " (code "scope") " fills them all from " (strong "one primitive") ". "
|
||
"You don't need to invent a new mechanism for each cell — you configure the scope.")
|
||
|
||
(~docs/subsection :title "The new cell: reactive context"
|
||
(p "The (Down ↓, Reactive) cell — " (strong "reactive context") " — "
|
||
"is the most interesting gap. It's React's Context + signals, but without "
|
||
"the re-render avalanche that makes React Context slow.")
|
||
(~docs/code :src (highlight ";; Reactive context: value is a signal, propagation is reactive\n(scope \"theme\" :value (signal {:primary \"violet\"}) :propagation :reactive\n ;; Any descendant reads with context + deref\n ;; Only the specific DOM node that uses the value updates\n (h1 :style (str \"color:\" (get (deref (context \"theme\")) :primary))\n \"This h1 updates when the theme signal changes\")\n ;; Deep nesting doesn't matter — it's O(1) per subscriber\n (~deeply-nested-component-tree))" "lisp"))
|
||
(p "React re-renders " (em "every") " component that reads a changed context. "
|
||
"SX's reactive context updates " (em "only") " the DOM nodes that " (code "deref") " the signal. "
|
||
"Same API, fundamentally different performance characteristics.")))
|
||
|
||
|
||
;; =====================================================================
|
||
;; V. Algebraic effects
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Algebraic effects: the deepest layer" :id "algebraic-effects"
|
||
|
||
(p "The scope primitive has a name in programming language theory: "
|
||
(strong "algebraic effects with handlers") ".")
|
||
|
||
(~docs/subsection :title "The correspondence"
|
||
(table :class "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")))
|
||
(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
|
||
(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"))))
|
||
|
||
(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 "
|
||
"a different value. This is exactly what reactive propagation does: when a signal "
|
||
"changes, the effect handler (the island scope) re-invokes the subscribed "
|
||
"continuations (the effect callbacks) with the new value."))
|
||
|
||
(~docs/subsection :title "Why this matters"
|
||
(p "Algebraic effects are " (strong "the most general control flow abstraction known") ". "
|
||
"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"
|
||
(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, "
|
||
"then re-handle it every time the value changes. Like a generator that yields "
|
||
"whenever a signal fires.")
|
||
(li (strong "Morph") " = " (em "external resumption") " handler — the server decides "
|
||
"when to resume, sending new content over the network. Like an async/await "
|
||
"where the promise is resolved by a different machine.")))
|
||
|
||
(~docs/subsection :title "The effect hierarchy"
|
||
(p "Effects form a hierarchy. Lower-level effects are handled by higher-level scopes:")
|
||
(~docs/code :src (highlight ";; Effect hierarchy (innermost handled first)\n;;\n;; spread → element scope handles (merge attrs)\n;; emit! → nearest provide handles (accumulate)\n;; signal write → reactive scope handles (notify subscribers)\n;; morph → morph scope handles (diff + patch)\n;;\n;; If no handler is found, the effect bubbles up.\n;; Unhandled effects at the root are errors (like uncaught exceptions).\n;;\n;; This is why (emit! \"cssx\" rule) without a provider\n;; should error — there's no handler for the effect." "lisp"))
|
||
(p "The current " (code "collect!") " is a global accumulator — effectively an "
|
||
"implicit top-level handler. Under the scope model, it would be an explicit "
|
||
(code "provide") " in the layout that handles " (code "\"cssx\"") " effects.")))
|
||
|
||
|
||
;; =====================================================================
|
||
;; VI. What scope enables
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "What scope enables" :id "enables"
|
||
|
||
(~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"
|
||
(li (strong ":animation") " — effects realised on requestAnimationFrame, "
|
||
"batched per frame, interruptible by higher-priority updates")
|
||
(li (strong ":idle") " — effects deferred to requestIdleCallback, "
|
||
"used for non-urgent background updates (the transition pattern)")
|
||
(li (strong ":stream") " — effects realised as server-sent events arrive, "
|
||
"for live data (scores, prices, notifications)")
|
||
(li (strong ":worker") " — effects computed in a Web Worker, "
|
||
"results propagated back to the main thread")))
|
||
|
||
(~docs/subsection :title "2. Effect composition"
|
||
(p "Because scopes nest, effects compose naturally:")
|
||
(~docs/code :src (highlight ";; Animation scope inside a reactive scope\n(scope \"island\" :propagation :reactive\n (let ((items (signal large-list)))\n (scope \"smooth\" :propagation :animation\n ;; Updates to items are batched per frame\n ;; The reactive scope tracks deps\n ;; The animation scope throttles DOM writes\n (for-each (fn (item)\n (div (get item \"name\"))) (deref items)))))" "lisp"))
|
||
(p "The reactive scope notifies when " (code "items") " changes. "
|
||
"The animation scope batches the resulting DOM writes to the next frame. "
|
||
"Neither scope knows about the other. They compose by nesting."))
|
||
|
||
(~docs/subsection :title "3. Server/client as effect boundary"
|
||
(p "The deepest consequence: the server/client boundary becomes " (em "just another "
|
||
"scope boundary") ". What crosses it is determined by the propagation mode:")
|
||
(~docs/code :src (highlight ";; The server renders this:\n(scope \"page\" :propagation :render\n (scope \"header\" :propagation :reactive\n ;; Client takes over — reactive scope\n (island-body...))\n (scope \"content\" :propagation :morph\n ;; Server controls — morphed on navigation\n (page-content...))\n (scope \"footer\" :propagation :render\n ;; Static — rendered once, never changes\n (footer...)))" "lisp"))
|
||
(p "The renderer walks the tree. When it hits a reactive scope, it serialises "
|
||
"state and emits a hydration marker. When it hits a morph scope, it emits "
|
||
"a lake marker. When it hits a render scope, it just renders. "
|
||
"The three scope types naturally produce the islands-and-lakes architecture — "
|
||
"not as a special case, but as the " (em "only") " case."))
|
||
|
||
(~docs/subsection :title "4. Cross-scope communication"
|
||
(p "Named stores (" (code "def-store/use-store") ") are scopes that transcend "
|
||
"the render tree. Two islands sharing a signal is two reactive scopes "
|
||
"referencing the same named scope:")
|
||
(~docs/code :src (highlight ";; Two islands, one shared scope\n(scope \"cart\" :value (signal []) :propagation :reactive :global true\n ;; Any island can read/write the cart\n ;; The scope transcends the render tree\n ;; Signal propagation handles cross-island updates)" "lisp"))
|
||
(p "The " (code ":global") " flag lifts the scope out of the tree hierarchy "
|
||
"into a named registry. " (code "def-store") " is syntax sugar for this.")))
|
||
|
||
|
||
;; =====================================================================
|
||
;; VII. Implementation path
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "Implementation path" :id "implementation"
|
||
|
||
(p "The path from current SX to the scope primitive follows the existing plan "
|
||
"and adds two phases:")
|
||
|
||
(~docs/subsection :title "Phase 1: provide/context/emit! ✓"
|
||
(p (strong "Complete. ") "Render-time dynamic scope. Four primitives: "
|
||
(code "provide") " (special form), " (code "context") ", " (code "emit!") ", "
|
||
(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")
|
||
" and "
|
||
(a :href "/sx/(geography.(spreads))" :class "text-violet-600 hover:underline" "spreads article")
|
||
"."))
|
||
|
||
(~docs/subsection :title "Phase 2: scope as the common form ✓"
|
||
(p (strong "Complete. ") (code "scope") " is now the general form. "
|
||
(code "provide") " is sugar for " (code "(scope name :value v body...)") ". "
|
||
(code "collect!") " creates a lazy root scope with deduplication. "
|
||
"All adapters use " (code "scope-push!/scope-pop!") " directly.")
|
||
(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")
|
||
"."))
|
||
|
||
(~docs/subsection :title "Phase 3: effect handlers (future)"
|
||
(p "Make propagation modes extensible. A " (code ":propagation") " value is a "
|
||
"handler function that determines when and how effects are realised:")
|
||
(~docs/code :src (highlight ";; Custom propagation mode\n(define :debounced\n (fn (emit-fn delay)\n ;; Returns a handler that debounces effect realisation\n (let ((timer nil))\n (fn (effect)\n (when timer (clear-timeout timer))\n (set! timer (set-timeout\n (fn () (emit-fn effect)) delay))))))\n\n;; Use it\n(scope \"search\" :propagation (:debounced 300)\n ;; Effects in this scope are debounced by 300ms\n (input :on-input (fn (e)\n (emit! \"search\" (get-value e)))))" "lisp"))
|
||
(p (strong "Delivers: ") "user-defined propagation modes (animation, debounce, throttle, "
|
||
"worker, stream), effect composition by nesting, "
|
||
"the full algebraic effects model.")))
|
||
|
||
|
||
;; =====================================================================
|
||
;; VIII. What this means for the spec
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "What this means for the spec" :id "spec"
|
||
|
||
(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"
|
||
(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")))
|
||
(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
|
||
(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"))))
|
||
|
||
(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 "
|
||
"re-runs effects when the value changes, not whether the value " (em "can") " change."))
|
||
|
||
|
||
;; =====================================================================
|
||
;; IX. The deepest thing
|
||
;; =====================================================================
|
||
|
||
(~docs/section :title "The deepest thing" :id "deepest"
|
||
|
||
(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"
|
||
"Evaluate this expression in a scope. Handle effects that cross scope boundaries."))
|
||
|
||
(p "Rendering is evaluating expressions in a scope where " (code "emit!") " effects "
|
||
"are handled by accumulating HTML. Reactivity is re-evaluating expressions "
|
||
"when signals fire. Morphing is receiving new expressions from the server "
|
||
"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"
|
||
(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)"))
|
||
|
||
(p (code "scope") " is the primitive that makes all three questions explicit "
|
||
"and composable. It's the last primitive SX needs.")
|
||
|
||
(~docs/note
|
||
(p (strong "Status: ") "Phase 1 (" (code "provide/context/emit!") ") and "
|
||
"Phase 2 (" (code "scope") " unification) are complete. "
|
||
"574 tests pass. All four adapters use " (code "scope-push!/scope-pop!") ". "
|
||
(code "collect!") " is backed by lazy scopes with dedup. "
|
||
"Phase 3 (extensible handlers) is the research frontier — "
|
||
"it may turn out that three modes are sufficient, or it may turn out that "
|
||
"user-defined modes unlock something unexpected.")))))
|