Files
rose-ash/sx/sx/plans/scoped-effects.sx
giles 6f96452f70 Fix empty code blocks: rename ~docs/code param, fix batched IO dispatch
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>
2026-03-25 18:08:40 +00:00

450 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
;; 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.")))))