Phase 2: IO detection & selective expansion in deps.sx
Extend the spec with IO scanning functions (scan-io-refs, transitive-io-refs, compute-all-io-refs, component-pure?) that detect IO primitive references in component ASTs. Components are classified as pure (no IO deps, safe for client rendering) or IO-dependent (must expand server-side). The partial evaluator (_aser) now uses per-component IO metadata instead of the global _expand_components toggle: IO-dependent components expand server- side, pure components serialize for client. Layout slot context still expands all components for backwards compat. Spec: 5 new functions + 2 platform interface additions in deps.sx Host: io_refs field + is_pure property on Component, compute_all_io_refs() Bootstrap: both sx_ref.py and sx-ref.js updated with IO functions Bundle analyzer: shows pure/IO classification per page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -683,43 +683,61 @@
|
||||
;; Phase 2
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 2: Smart Server/Client Boundary" :id "phase-2"
|
||||
(~doc-section :title "Phase 2: Smart Server/Client Boundary — IO Detection" :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" "Formalized partial evaluation model. Server evaluates IO, serializes pure subtrees. The system automatically knows \"this component needs server data\" vs \"this component is pure and can render anywhere.\""))
|
||||
(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 "/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
|
||||
(a :href "/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."))
|
||||
|
||||
(~doc-subsection :title "Current Mechanism"
|
||||
(p "_aser in async_eval.py already does partial evaluation — IO primitives are awaited and substituted, HTML tags and component calls serialize as SX. The _expand_components context var controls expansion. But this is a global toggle, not per-component."))
|
||||
|
||||
(~doc-subsection :title "Approach"
|
||||
(~doc-subsection :title "IO Detection in the Spec"
|
||||
(p "Five new functions in "
|
||||
(a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx")
|
||||
" extend the Phase 1 walker to detect IO primitive references:")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Automatic IO detection")
|
||||
(p "Extend Phase 1 AST walker to check for references to IO_PRIMITIVES names (frag, query, service, current-user, etc.).")
|
||||
(~doc-code :code (highlight "def has_io_deps(name: str, env: dict) -> bool:\n \"\"\"True if component transitively references any IO primitive.\"\"\"\n ..." "python")))
|
||||
(h4 :class "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).")
|
||||
(~doc-code :code (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. Component metadata")
|
||||
(~doc-code :code (highlight "ComponentMeta:\n deps: set[str] # transitive component deps (Phase 1)\n io_refs: set[str] # IO primitive names referenced\n is_pure: bool # True if io_refs empty (transitively)" "python")))
|
||||
(h4 :class "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.")
|
||||
(~doc-code :code (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. Selective expansion")
|
||||
(p "Refine _aser: instead of checking a global _expand_components flag, check the component's is_pure metadata:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "IO-dependent → expand server-side (IO must resolve)")
|
||||
(li "Pure → serialize for client (let client render)")
|
||||
(li "Explicit override: :server true on defcomp forces server expansion")))
|
||||
(h4 :class "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. Data manifest for pages")
|
||||
(p "PageDef produces a declaration of what IO the page needs, enabling Phase 3 (client can prefetch data) and Phase 5 (streaming)."))))
|
||||
(h4 :class "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."))))
|
||||
|
||||
(~doc-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"
|
||||
(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)"))
|
||||
(p "A component calling " (code "(highlight ...)") " or " (code "(query ...)") " is IO-dependent. A component with only HTML tags and string ops is pure."))
|
||||
|
||||
(~doc-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"
|
||||
(li "(component-io-refs c) → cached IO ref list")
|
||||
(li "(component-set-io-refs! c refs) → cache IO refs on component")))
|
||||
|
||||
(~doc-subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "Components calling (query ...) classified IO-dependent; pure components classified pure")
|
||||
(li "Existing pages produce identical output (regression)"))))
|
||||
(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 "/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 3
|
||||
|
||||
Reference in New Issue
Block a user