Files
rose-ash/sx/sx/plans.sx
giles cf5e767510 Phase 3: Client-side routing with SX page registry + routing analyzer demo
Add client-side route matching so pure pages (no IO deps) can render
instantly without a server roundtrip. Page metadata serialized as SX
dict literals (not JSON) in <script type="text/sx-pages"> blocks.

- New router.sx spec: route pattern parsing and matching (6 pure functions)
- boot.sx: process page registry using SX parser at startup
- orchestration.sx: intercept boost links for client routing with
  try-first/fallback — client attempts local eval, falls back to server
- helpers.py: _build_pages_sx() serializes defpage metadata as SX
- Routing analyzer demo page showing per-page client/server classification
- 32 tests for Phase 2 IO detection (scan_io_refs, transitive_io_refs,
  compute_all_io_refs, component_pure?) + fallback/ref parity
- 37 tests for Phase 3 router functions + page registry serialization
- Fix bootstrap_py.py _emit_let cell variable initialization bug
- Fix missing primitive aliases (split, length, merge) in bootstrap_py.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:47:56 +00:00

1024 lines
84 KiB
Plaintext

;; Plans section — architecture roadmaps and implementation plans
;; ---------------------------------------------------------------------------
;; Plans index page
;; ---------------------------------------------------------------------------
(defcomp ~plans-index-content ()
(~doc-page :title "Plans"
(div :class "space-y-4"
(p :class "text-lg text-stone-600 mb-4"
"Architecture roadmaps and implementation plans for SX.")
(div :class "space-y-3"
(map (fn (item)
(a :href (get item "href")
:sx-get (get item "href") :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(div :class "font-semibold text-stone-800" (get item "label"))
(when (get item "summary")
(p :class "text-sm text-stone-500 mt-1" (get item "summary")))))
plans-nav-items)))))
;; ---------------------------------------------------------------------------
;; Reader Macros
;; ---------------------------------------------------------------------------
(defcomp ~plan-reader-macros-content ()
(~doc-page :title "Reader Macros"
(~doc-section :title "Context" :id "context"
(p "SX has three hardcoded reader transformations: " (code "`") " → " (code "(quasiquote ...)") ", " (code ",") " → " (code "(unquote ...)") ", " (code ",@") " → " (code "(splice-unquote ...)") ". These are baked into the parser with no extensibility. The " (code "~") " prefix for components and " (code "&") " for param modifiers are just symbol characters, handled at eval time.")
(p "Reader macros add parse-time transformations triggered by a dispatch character. Motivating use case: a " (code "~md") " component that uses heredoc syntax for markdown source instead of string literals. More broadly: datum comments, raw strings, and custom literal syntax."))
(~doc-section :title "Design" :id "design"
(~doc-subsection :title "Dispatch Character: #"
(p "Lisp tradition. " (code "#") " is NOT in " (code "ident-start") " or " (code "ident-char") " — completely free. Pattern:")
(~doc-code :code (highlight "#;expr → (read and discard expr, return next)\n#|...| → raw string literal\n#'expr → (quote expr)" "lisp")))
(~doc-subsection :title "#; — Datum comment"
(p "Scheme/Racket standard. Reads and discards the next expression. Preserves balanced parens.")
(~doc-code :code (highlight "(list 1 #;(this is commented out) 2 3) → (list 1 2 3)" "lisp")))
(~doc-subsection :title "#|...| — Raw string"
(p "No escape processing. Everything between " (code "#|") " and " (code "|") " is literal. Enables inline markdown, regex patterns, code examples.")
(~doc-code :code (highlight "(~md #|## Title\n\nSome **bold** text with \"quotes\" and \\backslashes.|)" "lisp")))
(~doc-subsection :title "#' — Quote shorthand"
(p "Currently no single-char quote (" (code "`") " is quasiquote).")
(~doc-code :code (highlight "#'my-function → (quote my-function)" "lisp")))
(~doc-subsection :title "No user-defined reader macros (yet)"
(p "Would require multi-pass parsing or boot-phase registration. The three built-ins cover practical needs. Extensible dispatch can come later without breaking anything.")))
;; -----------------------------------------------------------------------
;; Implementation
;; -----------------------------------------------------------------------
(~doc-section :title "Implementation" :id "implementation"
(~doc-subsection :title "1. Spec: parser.sx"
(p "Add " (code "#") " dispatch to " (code "read-expr") " (after the " (code ",") "/" (code ",@") " case, before number). Add " (code "read-raw-string") " helper function.")
(~doc-code :code (highlight ";; Reader macro dispatch\n(= ch \"#\")\n (do (set! pos (inc pos))\n (if (>= pos len-src)\n (error \"Unexpected end of input after #\")\n (let ((dispatch-ch (nth source pos)))\n (cond\n ;; #; — datum comment: read and discard next expr\n (= dispatch-ch \";\")\n (do (set! pos (inc pos))\n (read-expr) ;; read and discard\n (read-expr)) ;; return the NEXT expr\n\n ;; #| — raw string\n (= dispatch-ch \"|\")\n (do (set! pos (inc pos))\n (read-raw-string))\n\n ;; #' — quote shorthand\n (= dispatch-ch \"'\")\n (do (set! pos (inc pos))\n (list (make-symbol \"quote\") (read-expr)))\n\n :else\n (error (str \"Unknown reader macro: #\" dispatch-ch))))))" "lisp"))
(p "The " (code "read-raw-string") " helper:")
(~doc-code :code (highlight "(define read-raw-string\n (fn ()\n (let ((buf \"\"))\n (define raw-loop\n (fn ()\n (if (>= pos len-src)\n (error \"Unterminated raw string\")\n (let ((ch (nth source pos)))\n (if (= ch \"|\")\n (do (set! pos (inc pos)) nil) ;; done\n (do (set! buf (str buf ch))\n (set! pos (inc pos))\n (raw-loop)))))))\n (raw-loop)\n buf)))" "lisp")))
(~doc-subsection :title "2. Python: parser.py"
(p "Add " (code "#") " dispatch to " (code "_parse_expr()") " (after " (code ",") "/" (code ",@") " handling ~line 252). Add " (code "_read_raw_string()") " method to Tokenizer.")
(~doc-code :code (highlight "# In _parse_expr(), after the comma/splice-unquote block:\nif raw == \"#\":\n tok._advance(1) # consume the #\n if tok.pos >= len(tok.text):\n raise ParseError(\"Unexpected end of input after #\",\n tok.pos, tok.line, tok.col)\n dispatch = tok.text[tok.pos]\n if dispatch == \";\":\n tok._advance(1)\n _parse_expr(tok) # read and discard\n return _parse_expr(tok) # return next\n if dispatch == \"|\":\n tok._advance(1)\n return tok._read_raw_string()\n if dispatch == \"'\":\n tok._advance(1)\n return [Symbol(\"quote\"), _parse_expr(tok)]\n raise ParseError(f\"Unknown reader macro: #{dispatch}\",\n tok.pos, tok.line, tok.col)" "python"))
(p "The " (code "_read_raw_string()") " method on Tokenizer:")
(~doc-code :code (highlight "def _read_raw_string(self) -> str:\n buf = []\n while self.pos < len(self.text):\n ch = self.text[self.pos]\n if ch == \"|\":\n self._advance(1)\n return \"\".join(buf)\n buf.append(ch)\n self._advance(1)\n raise ParseError(\"Unterminated raw string\",\n self.pos, self.line, self.col)" "python")))
(~doc-subsection :title "3. JS: auto-transpiled"
(p "JS parser comes from bootstrap of parser.sx — spec change handles it automatically."))
(~doc-subsection :title "4. Rebootstrap both targets"
(p "Run " (code "bootstrap_js.py") " and " (code "bootstrap_py.py") " to regenerate " (code "sx-ref.js") " and " (code "sx_ref.py") " from the updated parser.sx spec."))
(~doc-subsection :title "5. Grammar update"
(p "Add reader macro syntax to the grammar comment at the top of parser.sx:")
(~doc-code :code (highlight ";; reader → '#;' expr (datum comment)\n;; | '#|' raw-chars '|' (raw string)\n;; | \"#'\" expr (quote shorthand)" "lisp"))))
;; -----------------------------------------------------------------------
;; Files
;; -----------------------------------------------------------------------
(~doc-section :title "Files" :id "files"
(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" "File")
(th :class "px-3 py-2 font-medium text-stone-600" "Change")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/parser.sx")
(td :class "px-3 py-2 text-stone-700" "# dispatch in read-expr, read-raw-string helper, grammar comment"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/parser.py")
(td :class "px-3 py-2 text-stone-700" "# dispatch in _parse_expr(), _read_raw_string()"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/sx_ref.py")
(td :class "px-3 py-2 text-stone-700" "Rebootstrap"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/static/scripts/sx-ref.js")
(td :class "px-3 py-2 text-stone-700" "Rebootstrap"))))))
;; -----------------------------------------------------------------------
;; Verification
;; -----------------------------------------------------------------------
(~doc-section :title "Verification" :id "verification"
(~doc-subsection :title "Parse tests"
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
(li "#;(ignored) 42 → 42")
(li "(list 1 #;2 3) → (list 1 3)")
(li "#|hello \"world\" \\n| → string: hello \"world\" \\n (literal, no escaping)")
(li "#|multi\\nline| → string with actual newline")
(li "#'foo → (quote foo)")
(li "# at EOF → error")
(li "#x unknown → error")))
(~doc-subsection :title "Regression"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "All existing parser tests pass after changes")
(li "Rebootstrapped JS and Python pass their test suites")
(li "JS parity: SX.parse('#|hello|') returns [\"hello\"]"))))))
;; ---------------------------------------------------------------------------
;; SX-Activity: Federated SX over ActivityPub
;; ---------------------------------------------------------------------------
(defcomp ~plan-sx-activity-content ()
(~doc-page :title "SX-Activity"
(~doc-section :title "Context" :id "context"
(p "The web is six incompatible formats duct-taped together: HTML for structure, CSS for style, JavaScript for behavior, JSON for data, server languages for backend logic, build tools for compilation. Moving anything between layers requires serialization, template languages, API contracts, and glue code. Federation (ActivityPub) adds a seventh — JSON-LD — which is inert data that every consumer must interpret from scratch and wrap in their own UI.")
(p "SX is already one evaluable format that does all six. A component definition is simultaneously structure, style (CSSX atoms), behavior (event handlers), data (the AST " (em "is") " data), server-renderable (Python evaluator), and client-renderable (JS evaluator). The pieces already exist: content-addressed DAG execution (artdag), IPFS storage with CIDs, OpenTimestamps Bitcoin anchoring, boundary-enforced sandboxing.")
(p "SX-Activity wires these together into a new web. Everything — content, UI components, markdown parsers, syntax highlighters, validation logic, media, processing pipelines — is the same executable format, stored on a content-addressed network, running within each participant's own security context. " (strong "The wire format is the programming language is the component system is the package manager.")))
(~doc-section :title "Current State" :id "current-state"
(ul :class "space-y-2 text-stone-700 list-disc pl-5"
(li (strong "ActivityPub: ") "Full implementation — virtual per-app actors, HTTP signatures, webfinger, inbox/outbox, followers/following, delivery with idempotent logging.")
(li (strong "Activity bus: ") "Unified event bus with NOTIFY/LISTEN wakeup, at-least-once delivery, handler registry keyed by (activity_type, object_type).")
(li (strong "Content addressing: ") "artdag nodes use SHA3-256 hashing. Cache layer tracks IPFS CIDs. IPFSPin model tracks pinned content across domains.")
(li (strong "Bitcoin anchoring: ") "APAnchor model — Merkle tree of activities, OpenTimestamps proof CID, Bitcoin txid. Infrastructure exists but isn't wired to all activity types.")
(li (strong "SX wire format: ") "Server serializes to SX source via _aser, client parses and renders. Component caching via localStorage + content hashes.")
(li (strong "Boundary enforcement: ") "SX_BOUNDARY_STRICT=1 validates all primitives at registration. Pure components can't do IO — safe to load from untrusted sources.")))
;; -----------------------------------------------------------------------
;; Phase 1: SX Wire Format for Activities
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 1: SX Wire Format for Activities" :id "phase-1"
(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" "Activities expressed as s-expressions instead of JSON-LD. Same semantics as ActivityStreams, but compact, parseable, and directly evaluable. Dual-format support for backward compatibility with existing AP servers."))
(~doc-subsection :title "The Problem"
(p "JSON-LD activities are verbose and require context resolution:")
(~doc-code :code (highlight "{\"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Create\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"Note\",\n \"content\": \"<p>Hello world</p>\",\n \"attributedTo\": \"https://example.com/users/alice\"\n }}" "json"))
(p "Every consumer parses JSON, resolves @context, extracts fields, then builds their own UI around the raw data. The content is HTML embedded in a JSON string — two formats nested, neither evaluable."))
(~doc-subsection :title "SX Activity Format"
(p "The same activity as SX:")
(~doc-code :code (highlight "(Create\n :actor \"https://example.com/users/alice\"\n :published \"2026-03-06T12:00:00Z\"\n :object (Note\n :attributed-to \"https://example.com/users/alice\"\n :content (p \"Hello world\")\n :media-type \"text/sx\"))" "lisp"))
(p "The content isn't a string containing markup — it " (em "is") " markup. The receiving server can evaluate it directly. The Note's content is a renderable SX expression."))
(~doc-subsection :title "Approach"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Activity vocabulary in SX")
(p "Map ActivityStreams types to SX symbols. Activities are lists with a type head and keyword properties:")
(~doc-code :code (highlight ";; Core activity types\n(Create :actor ... :object ...)\n(Update :actor ... :object ...)\n(Delete :actor ... :object ...)\n(Follow :actor ... :object ...)\n(Like :actor ... :object ...)\n(Announce :actor ... :object ...)\n\n;; Object types\n(Note :content ... :attributed-to ...)\n(Article :name ... :content ... :summary ...)\n(Image :url ... :media-type ... :cid ...)\n(Collection :total-items ... :items ...)" "lisp")))
(div
(h4 :class "font-semibold text-stone-700" "2. Content negotiation")
(p "Inbox accepts both formats. " (code "Accept: text/sx") " gets SX, " (code "Accept: application/activity+json") " gets JSON-LD. Outbox serves both. SX-native servers negotiate SX; legacy Mastodon/Pleroma servers get JSON-LD as today."))
(div
(h4 :class "font-semibold text-stone-700" "3. Bidirectional translation")
(p "Lossless mapping between JSON-LD and SX activity formats. Translate at the boundary — internal processing always uses SX. The existing " (code "APActivity") " model gains an " (code "sx_source") " column storing the canonical SX representation."))
(div
(h4 :class "font-semibold text-stone-700" "4. HTTP Signatures over SX")
(p "Same RSA signature mechanism. Digest header computed over the SX body. Existing keypair infrastructure unchanged."))))
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Round-trip: SX → JSON-LD → SX produces identical output")
(li "Legacy AP servers receive valid JSON-LD (Mastodon can display the post)")
(li "SX-native servers receive evaluable SX (client can render directly)")
(li "HTTP signatures verify over SX bodies"))))
;; -----------------------------------------------------------------------
;; Phase 2: Content-Addressed Components on IPFS
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 2: Content-Addressed Components on IPFS" :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" "Component definitions stored on IPFS, referenced by CID. Any server can publish components. Any browser can fetch them. No central registry — content addressing IS the registry."))
(~doc-subsection :title "The Insight"
(p "SX components are pure functions — they take data and return markup. They can't do IO (boundary enforcement guarantees this). That means they're " (strong "safe to load from any source") ". And if they're content-addressed, the CID " (em "is") " the identity — you don't need to trust the source, you just verify the hash.")
(p "Currently, component definitions travel with each page via " (code "<script type=\"text/sx\" data-components>") ". Each server bundles its own. With IPFS, components become shared infrastructure — define once, use everywhere."))
(~doc-subsection :title "Approach"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Component CID computation")
(p "Each " (code "defcomp") " definition gets a content address:")
(~doc-code :code (highlight ";; Component source\n(defcomp ~card (&key title &rest children)\n (div :class \"border rounded p-4\"\n (h2 title) children))\n\n;; CID = SHA3-256 of canonical serialized form\n;; → bafy...abc123\n;; Stored: ipfs://bafy...abc123 → component source" "lisp"))
(p "Canonical form: normalize whitespace, sort keyword args alphabetically, strip comments. Same component always produces same CID regardless of formatting."))
(div
(h4 :class "font-semibold text-stone-700" "2. Component references in activities")
(p "Activities declare which components they need by CID:")
(~doc-code :code (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :requires (list\n \"bafy...card\" ;; ~card component\n \"bafy...avatar\") ;; ~avatar component\n :object (Note\n :content (~card :title \"Hello\"\n (~avatar :src \"ipfs://bafy...photo\")\n (p \"This renders with the card component.\"))))" "lisp"))
(p "The receiving browser fetches missing components from IPFS, verifies CIDs, registers them, then renders the content."))
(div
(h4 :class "font-semibold text-stone-700" "3. IPFS component resolution")
(p "Client-side resolution pipeline:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Check localStorage cache (keyed by CID — cache-forever semantics)")
(li "Check local IPFS node if running (ipfs cat)")
(li "Fetch from IPFS gateway (configurable, default: dweb.link)")
(li "Verify SHA3-256 matches CID")
(li "Parse, register in component env, render")))
(div
(h4 :class "font-semibold text-stone-700" "4. Component publication")
(p "Server-side: on component registration, compute CID and pin to IPFS. Track in " (code "IPFSPin") " model (already exists). Publish component availability via AP outbox:")
(~doc-code :code (highlight "(Create\n :actor \"https://rose-ash.com/apps/market\"\n :object (SxComponent\n :name \"~product-card\"\n :cid \"bafy...productcard\"\n :version \"1.0.0\"\n :deps (list \"bafy...card\" \"bafy...price-tag\")))" "lisp")))))
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Component pinned to IPFS → fetchable via gateway → CID verifies")
(li "Browser renders federated post using IPFS-fetched components")
(li "Modified component → different CID → old content still renders with old version")
(li "Boundary enforcement: IPFS-loaded component cannot call IO primitives"))))
;; -----------------------------------------------------------------------
;; Phase 3: Federated Media & Content Store
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 3: Federated Media & Content Store" :id "phase-3"
(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" "All media (images, video, audio, DAG outputs) stored content-addressed on IPFS. Activities reference media by CID. No hotlinking, no broken links, no dependence on the origin server staying online."))
(~doc-subsection :title "Current Mechanism"
(p "artdag already content-addresses all DAG outputs with SHA3-256 and tracks IPFS CIDs in " (code "IPFSPin") ". But media in the web platform (blog images, product photos, event banners) is stored as regular files on the origin server. Federated posts include " (code "url") " fields pointing to the origin — if the server goes down, the media is gone."))
(~doc-subsection :title "Approach"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Media CID pipeline")
(p "On upload: hash content → pin to IPFS → store CID in database. Activities reference media by CID alongside URL fallback:")
(~doc-code :code (highlight "(Image\n :cid \"bafy...photo123\"\n :url \"https://rose-ash.com/media/photo.jpg\" ;; fallback\n :media-type \"image/jpeg\"\n :width 1200 :height 800)" "lisp")))
(div
(h4 :class "font-semibold text-stone-700" "2. DAG output federation")
(p "artdag processing results (rendered video, processed images) already have CIDs. Federate them as activities:")
(~doc-code :code (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :object (Artwork\n :name \"Sunset Remix\"\n :cid \"bafy...artwork\"\n :dag-cid \"bafy...dag\" ;; full DAG for reproduction\n :media-type \"video/mp4\"\n :sources (list\n (Image :cid \"bafy...src1\" :attribution \"...\")\n (Image :cid \"bafy...src2\" :attribution \"...\"))))" "lisp"))
(p "The " (code ":dag-cid") " lets anyone re-execute the processing pipeline. The artwork is both a result and a reproducible recipe."))
(div
(h4 :class "font-semibold text-stone-700" "3. Shared SX content store")
(p "Not just components and media — full page content can be content-addressed. An Article's body is SX, pinned to IPFS:")
(~doc-code :code (highlight "(Article\n :name \"Why S-Expressions\"\n :content-cid \"bafy...article-body\" ;; SX source on IPFS\n :requires (list \"bafy...doc-page\" \"bafy...code-block\")\n :summary \"Why SX uses s-expressions instead of HTML.\")" "lisp"))
(p "The content outlives the server. Anyone with the CID can fetch, parse, and render the article with its original components."))
(div
(h4 :class "font-semibold text-stone-700" "4. Progressive resolution")
(p "Client resolves content progressively:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Inline content renders immediately")
(li "CID-referenced content shows placeholder → fetches from IPFS → renders")
(li "Large media uses IPFS streaming (chunked CIDs)")
(li "Integrates with Phase 5 of isomorphic plan (streaming/suspense)")))))
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Origin server offline → content still resolvable via IPFS gateway")
(li "DAG CID → re-executing DAG produces identical output")
(li "Media CID verifies → tampered content rejected"))))
;; -----------------------------------------------------------------------
;; Phase 4: Component Registry & Discovery
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 4: Component Registry & Discovery" :id "phase-4"
(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" "Federated component discovery. Servers publish component collections. Other servers follow component feeds. Like npm, but federated, content-addressed, and the packages are safe to run (pure functions, no IO)."))
(~doc-subsection :title "Approach"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Component collections as AP actors")
(p "Each server exposes a component registry actor:")
(~doc-code :code (highlight "(Service\n :id \"https://rose-ash.com/sx-registry\"\n :type \"SxComponentRegistry\"\n :name \"Rose Ash Components\"\n :outbox \"https://rose-ash.com/sx-registry/outbox\"\n :followers \"https://rose-ash.com/sx-registry/followers\")" "lisp"))
(p "Follow the registry to receive component updates. The outbox is a chronological feed of Create/Update/Delete activities for components."))
(div
(h4 :class "font-semibold text-stone-700" "2. Component metadata")
(~doc-code :code (highlight "(SxComponent\n :name \"~data-table\"\n :cid \"bafy...datatable\"\n :version \"2.1.0\"\n :deps (list \"bafy...sortable\" \"bafy...paginator\")\n :params (list\n (dict :name \"rows\" :type \"list\" :required true)\n (dict :name \"columns\" :type \"list\" :required true)\n (dict :name \"sortable\" :type \"boolean\" :default false))\n :css-atoms (list :border :rounded :p-4 :text-sm)\n :preview-cid \"bafy...screenshot\"\n :license \"MIT\")" "lisp"))
(p "Dependencies are transitive CID references. CSS atoms declare which CSSX rules the component needs. Preview CID is a screenshot for registry browsing."))
(div
(h4 :class "font-semibold text-stone-700" "3. Discovery protocol")
(p "Webfinger-style lookup for components by name:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Local registry: check own component env first")
(li "Followed registries: check cached feeds from followed registries")
(li "Global search: query known registries by component name")
(li "CID resolution: if you have a CID, skip discovery — fetch directly from IPFS")))
(div
(h4 :class "font-semibold text-stone-700" "4. Version resolution")
(p "Components are immutable (CID = identity). \"Updating\" a component publishes a new CID. Activities reference specific CIDs, so old content always renders correctly. The registry tracks version history:")
(~doc-code :code (highlight "(Update\n :actor \"https://rose-ash.com/sx-registry\"\n :object (SxComponent\n :name \"~card\"\n :cid \"bafy...card-v2\" ;; new version\n :replaces \"bafy...card-v1\" ;; previous version\n :changelog \"Added subtitle slot\"))" "lisp")))))
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Follow registry → receive component Create activities → components available locally")
(li "Render post using component from foreign registry → works")
(li "Old post referencing old CID → still renders correctly with old version"))))
;; -----------------------------------------------------------------------
;; Phase 5: Bitcoin-Anchored Provenance
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 5: Bitcoin-Anchored Provenance" :id "phase-5"
(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" "Cryptographic proof that content existed at a specific time, authored by a specific actor. Leverages the existing APAnchor/OpenTimestamps infrastructure. Unforgeable, independently verifiable, survives server shutdown."))
(~doc-subsection :title "Current Mechanism"
(p "The " (code "APAnchor") " model already batches activities into Merkle trees, stores the tree on IPFS, creates an OpenTimestamps proof, and records the Bitcoin txid. This runs but isn't surfaced to users or integrated with the full activity lifecycle."))
(~doc-subsection :title "Approach"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Automatic anchoring pipeline")
(p "Every SX activity gets queued for anchoring. Batch processor runs periodically:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Collect pending activities (content CIDs + actor signatures)")
(li "Build Merkle tree of activity hashes")
(li "Pin Merkle tree to IPFS → tree CID")
(li "Submit tree root to OpenTimestamps calendar servers")
(li "When Bitcoin confirmation arrives: store txid, update activities with anchor reference")))
(div
(h4 :class "font-semibold text-stone-700" "2. Provenance chain in activities")
(~doc-code :code (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :object (Note :content (p \"Hello\") :cid \"bafy...note\")\n :provenance (Anchor\n :tree-cid \"bafy...merkle-tree\"\n :leaf-index 42\n :ots-cid \"bafy...ots-proof\"\n :btc-txid \"abc123...def\"\n :btc-block 890123\n :anchored-at \"2026-03-06T12:00:00Z\"))" "lisp"))
(p "Any party can verify: fetch the OTS proof from IPFS, check the Merkle path from the activity's CID to the tree root, confirm the tree root is committed in the Bitcoin block."))
(div
(h4 :class "font-semibold text-stone-700" "3. Component provenance")
(p "Components published to the registry also get anchored. This proves authorship and publication time:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "\"This component was published by rose-ash.com at block 890123\"")
(li "Prevents backdating — can't claim you published a component before you actually did")
(li "License disputes resolvable by checking anchor timestamps")))
(div
(h4 :class "font-semibold text-stone-700" "4. Verification UI")
(p "Client-side provenance badge on federated content:")
(~doc-code :code (highlight "(defcomp ~provenance-badge (&key anchor)\n (when anchor\n (details :class \"inline text-xs text-stone-400\"\n (summary \"✓ Anchored\")\n (dl :class \"mt-1 space-y-1\"\n (dt \"Bitcoin block\") (dd (get anchor \"btc-block\"))\n (dt \"Timestamp\") (dd (get anchor \"anchored-at\"))\n (dt \"Proof\") (dd (a :href (str \"ipfs://\" (get anchor \"ots-cid\"))\n \"OTS proof\"))))))" "lisp")))))
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Activity anchored → OTS proof fetchable from IPFS → Merkle path validates → txid confirms in Bitcoin")
(li "Tampered activity → Merkle proof fails → provenance badge shows ✗")
(li "Server goes offline → provenance still verifiable (all proofs on IPFS + Bitcoin)"))))
;; -----------------------------------------------------------------------
;; Phase 6: The Evaluable Web
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 6: The Evaluable Web" :id "phase-6"
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
(p :class "text-violet-900 font-medium" "What this really is")
(p :class "text-violet-800" "Not ActivityPub-with-SX. A new web. One where everything — content, components, parsers, renderers, server logic, client logic — is the same executable format, shared on a content-addressed network, running within each participant's own security context."))
(~doc-subsection :title "The insight"
(p "The web has six layers that don't talk to each other: HTML (structure), CSS (style), JavaScript (behavior), JSON (data interchange), server frameworks (backend logic), and build tools (compilation). Each has its own syntax, its own semantics, its own ecosystem. Moving data between them requires serialization, deserialization, template languages, API contracts, type coercion, and an endless parade of glue code.")
(p "SX collapses all six into one evaluable format. A component definition is simultaneously structure, style (CSSX atoms), behavior (event handlers), data (the AST is data), server-renderable (Python evaluator), and client-renderable (JS evaluator). There is no boundary between \"data\" and \"program\" — s-expressions are both.")
(p "Once that's true, " (strong "everything becomes shareable.") " Not just UI components — markdown parsers, syntax highlighters, date formatters, validation logic, layout algorithms, color systems, animation curves. Any pure function over data. All content-addressed, all on IPFS, all executable within your own security context."))
(~doc-subsection :title "What travels on the network"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "Content")
(p "Blog posts, product listings, event descriptions, social media posts. Not HTML strings embedded in JSON — live SX expressions that evaluate to rendered UI. The content " (em "is") " the application."))
(div
(h4 :class "font-semibold text-stone-700" "Components")
(p "UI building blocks: cards, tables, forms, navigation, media players. Published to IPFS, referenced by CID. A commerce site publishes " (code "~product-card") ". A blogging platform publishes " (code "~article-layout") ". A social network publishes " (code "~thread-view") ". Anyone can compose them. They're pure functions — safe to load from anywhere."))
(div
(h4 :class "font-semibold text-stone-700" "Parsers and transforms")
(p "A markdown parser is just an SX function: takes a string, returns an SX tree. Publish it to IPFS. Now anyone can use your markdown dialect. Same for: syntax highlighters, BBCode parsers, wiki markup, LaTeX subsets, CSV-to-table converters, JSON-to-SX adapters. " (strong "The parser ecosystem becomes shared infrastructure."))
(~doc-code :code (highlight ";; A markdown parser, published to IPFS\n;; CID: bafy...md-parser\n(define parse-markdown\n (fn (source)\n ;; tokenize → build AST → return SX tree\n ;; (parse-markdown \"# Hello\\n**bold**\")\n ;; → (h1 \"Hello\") (p (strong \"bold\"))\n ...))\n\n;; Anyone can use it in their components\n(defcomp ~blog-post (&key markdown-source)\n (div :class \"prose\"\n (parse-markdown markdown-source)))" "lisp")))
(div
(h4 :class "font-semibold text-stone-700" "Server-side and client-side logic")
(p "The same SX code runs on either side. A validation function published to IPFS runs server-side in Python for form processing and client-side in JavaScript for instant feedback. A price calculator runs server-side for order totals and client-side for live previews. " (em "The server/client split is a deployment decision, not a language boundary.")))
(div
(h4 :class "font-semibold text-stone-700" "Media and processing pipelines")
(p "Images, video, audio — all content-addressed on IPFS. But also the " (em "processing pipelines") " that created them. artdag DAGs are SX. Publish a DAG CID alongside the output CID and anyone can verify the provenance, re-render at different resolution, or fork the pipeline for their own work."))))
(~doc-subsection :title "The security model"
(p "This only works because of boundary enforcement. Every piece of SX fetched from the network runs within the receiver's security context:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "Pure functions can't do IO. ") "A component from IPFS can produce markup — it cannot read your cookies, make network requests, access localStorage, or call any IO primitive. The boundary spec (boundary.sx) is enforced at registration time. This isn't a policy — it's structural. The evaluator literally doesn't have IO primitives available when running untrusted code.")
(li (strong "IO requires explicit grant. ") "Only locally-registered IO primitives (query, frag, current-user) have access to server resources. Fetched components never see them. The host decides what capabilities to grant.")
(li (strong "Step limits cap computation. ") "Untrusted code runs with configurable eval step limits. No infinite loops, no resource exhaustion. Exceeding the limit halts evaluation and returns an error node.")
(li (strong "Content addressing prevents tampering. ") "You asked for CID X, you got CID X, the hash proves it. No MITM, no CDN poisoning, no supply chain attacks on the content itself.")
(li (strong "Provenance proves authorship. ") "Bitcoin-anchored timestamps prove who published what and when. Not \"trust me\" — independently verifiable against the Bitcoin blockchain."))
(p "This is the opposite of the npm model. npm packages run with full access to your system — a malicious package can exfiltrate secrets, install backdoors, modify the filesystem. SX components are structurally sandboxed. The worst a malicious component can do is render a " (code "(div \"haha got you\")") "."))
(~doc-subsection :title "What this replaces"
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(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" "Current web")
(th :class "px-3 py-2 font-medium text-stone-600" "SX web")
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "npm / package registries")
(td :class "px-3 py-2 text-stone-700" "IPFS + component CIDs")
(td :class "px-3 py-2 text-stone-600" "Content-addressed, no central authority, structurally safe"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "CDNs for JS/CSS")
(td :class "px-3 py-2 text-stone-700" "IPFS gateways")
(td :class "px-3 py-2 text-stone-600" "Permanent, decentralized, self-verifying"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "REST/GraphQL APIs")
(td :class "px-3 py-2 text-stone-700" "SX activities over AP")
(td :class "px-3 py-2 text-stone-600" "Responses are evaluable, not just data"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "HTML + CSS + JS")
(td :class "px-3 py-2 text-stone-700" "SX (one format)")
(td :class "px-3 py-2 text-stone-600" "No impedance mismatch, same evaluator everywhere"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Build tools (webpack, vite)")
(td :class "px-3 py-2 text-stone-700" "Nothing")
(td :class "px-3 py-2 text-stone-600" "SX evaluates directly, no compilation step"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Template languages")
(td :class "px-3 py-2 text-stone-700" "Nothing")
(td :class "px-3 py-2 text-stone-600" "SX is the template and the language"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "JSON-LD federation")
(td :class "px-3 py-2 text-stone-700" "SX federation")
(td :class "px-3 py-2 text-stone-600" "Wire format is executable, content renders itself"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Trust-based package security")
(td :class "px-3 py-2 text-stone-700" "Structural sandboxing")
(td :class "px-3 py-2 text-stone-600" "Pure functions can't have side effects — not by policy, by construction"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Servers + hosting + DNS + TLS")
(td :class "px-3 py-2 text-stone-700" "IPFS CID")
(td :class "px-3 py-2 text-stone-600" "Entire applications are content-addressed, no infrastructure needed"))))))
(~doc-subsection :title "Serverless applications on IPFS"
(p "The logical conclusion: " (strong "entire web applications hosted on IPFS with no server at all."))
(p "An SX application is a tree of content-addressed artifacts: a root page definition, component dependencies, media, stylesheets, parsers, transforms. Pin the root CID to IPFS and the application is live. No server, no DNS, no hosting provider, no deployment pipeline. Someone gives you a CID, you paste it into an SX-aware browser, and the application runs.")
(~doc-code :code (highlight ";; An entire blog — one CID\n;; ipfs://bafy...my-blog\n(defpage blog-home\n :path \"/\"\n :requires (list\n \"bafy...article-layout\" ;; layout component\n \"bafy...md-parser\" ;; markdown parser\n \"bafy...syntax-highlight\" ;; code highlighting\n \"bafy...nav-component\") ;; navigation\n :content\n (~article-layout\n :title \"My Blog\"\n :nav (~nav-component\n :items (list\n (dict :label \"Post 1\" :cid \"bafy...post-1\")\n (dict :label \"Post 2\" :cid \"bafy...post-2\")))\n :body (~markdown-page\n :source-cid \"bafy...homepage-md\")))" "lisp"))
(p "What this looks like in practice:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "Personal sites: ") "A portfolio or blog is a handful of SX files + media. Pin to IPFS. Share the CID. No hosting costs, no domain renewal, no SSL certificates. The site is permanent.")
(li (strong "Documentation: ") "Pin your docs. They can't go offline, can't be censored, can't be altered after publication (provenance proves it). Anyone can mirror them by pinning the same CID.")
(li (strong "Collaborative applications: ") "Multiple authors contribute pages and components. Each publishes their CIDs. A root manifest composes them. Update the manifest CID to add content — old CIDs remain valid forever.")
(li (strong "Offline-first: ") "An IPFS-hosted app works the same whether you're online or have the content cached locally. The browser's SX evaluator + local IPFS node = complete offline platform.")
(li (strong "Zero-cost deployment: ") "\"Deploying\" means computing a hash. No CI/CD, no Docker, no cloud provider. Pin locally, pin to a remote IPFS node, or let others pin if they want to help host."))
(p "For applications that " (em "do") " need a server — user accounts, payments, real-time collaboration, database queries — the server provides IO primitives via the existing boundary system. The SX application fetches data from the server's IO endpoints, but the application itself (all the rendering, routing, component logic) lives on IPFS. The server is a " (em "data service") ", not an application host.")
(p "This inverts the current model. Today: server hosts the application, client is a thin renderer. SX web: IPFS hosts the application, server is an optional IO provider. " (strong "The application is the content. The content is the application. Both are just s-expressions.")))
(~doc-subsection :title "The end state"
(p "A browser with an SX evaluator and an IPFS gateway is a complete web platform. Given a CID — for a page, a post, an application — it can:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Fetch the content from IPFS")
(li "Resolve component dependencies (also from IPFS)")
(li "Resolve media (also from IPFS)")
(li "Evaluate the content (pure computation, sandboxed)")
(li "Render to DOM")
(li "Verify provenance (Bitcoin anchor)")
(li "Cache everything forever (content-addressed = immutable)"))
(p "No server needed. No DNS. No TLS certificates. No hosting provider. No build step. No framework. Just content-addressed s-expressions evaluating in a sandbox.")
(p "The server becomes optional infrastructure for " (em "IO") " — database queries, authentication, payment processing, real-time events. Everything else lives on the content-addressed network. The web stops being a collection of servers you visit and becomes a " (strong "shared evaluable space") " you participate in.")))
;; -----------------------------------------------------------------------
;; Cross-Cutting Concerns
;; -----------------------------------------------------------------------
(~doc-section :title "Cross-Cutting Concerns" :id "cross-cutting"
(~doc-subsection :title "Security"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "Boundary enforcement is the foundation. ") "IPFS-fetched components are parsed and registered like any other component. SX_BOUNDARY_STRICT ensures they can't call IO primitives. A malicious component can produce ugly markup but can't exfiltrate data or make network requests.")
(li (strong "CID verification: ") "Content fetched from IPFS is hashed and compared to the expected CID before use. Tampered content is rejected.")
(li (strong "Signature chain: ") "Actor signatures (RSA/HTTP Signatures) prove authorship. Bitcoin anchors prove timing. Together they establish non-repudiable provenance.")
(li (strong "Resource limits: ") "Evaluation of untrusted components runs with step limits (max eval steps, max recursion depth). Infinite loops are caught and terminated.")))
(~doc-subsection :title "Backward Compatibility"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Content negotiation ensures legacy AP servers always receive valid JSON-LD")
(li "SX-Activity is strictly opt-in — servers that don't understand it get standard AP")
(li "Existing internal activity bus unchanged — SX format is for federation, not internal events")
(li "URL fallbacks on all media references — CID is preferred, URL is fallback")))
(~doc-subsection :title "Performance"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Component CIDs cached in localStorage forever (content-addressed = immutable)")
(li "IPFS gateway responses cached with long TTL (content can't change)")
(li "Local IPFS node (if present) eliminates gateway latency")
(li "Provenance verification is lazy — badge shows unverified until user clicks to verify")))
(~doc-subsection :title "Integration with Isomorphic Architecture"
(p "SX-Activity builds on the isomorphic architecture plan:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Phase 1 (component distribution) → IPFS replaces per-server bundles")
(li "Phase 2 (IO detection) → pure components safe for IPFS publication")
(li "Phase 3 (client routing) → client can resolve federated content without server")
(li "Phase 5 (streaming/suspense) → progressive IPFS resolution uses same infrastructure"))))
;; -----------------------------------------------------------------------
;; Critical Files
;; -----------------------------------------------------------------------
(~doc-section :title "Critical Files" :id "critical-files"
(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" "File")
(th :class "px-3 py-2 font-medium text-stone-600" "Role")
(th :class "px-3 py-2 font-medium text-stone-600" "Phases")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/activitypub.py")
(td :class "px-3 py-2 text-stone-700" "AP blueprint — add SX content negotiation")
(td :class "px-3 py-2 text-stone-600" "1"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/events/bus.py")
(td :class "px-3 py-2 text-stone-700" "Activity bus — add sx_source column, SX serialization")
(td :class "px-3 py-2 text-stone-600" "1"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/activity.py")
(td :class "px-3 py-2 text-stone-700" "SX ↔ JSON-LD bidirectional translation (new)")
(td :class "px-3 py-2 text-stone-600" "1"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ipfs.py")
(td :class "px-3 py-2 text-stone-700" "Component CID computation, IPFS pinning (new)")
(td :class "px-3 py-2 text-stone-600" "2, 3"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/ipfs-resolve.sx")
(td :class "px-3 py-2 text-stone-700" "Client-side IPFS resolution spec (new)")
(td :class "px-3 py-2 text-stone-600" "2, 3"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/models/federation.py")
(td :class "px-3 py-2 text-stone-700" "IPFSPin, APAnchor models — extend for components")
(td :class "px-3 py-2 text-stone-600" "2, 3, 5"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/registry.py")
(td :class "px-3 py-2 text-stone-700" "Component registry actor, discovery protocol (new)")
(td :class "px-3 py-2 text-stone-600" "4"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/anchor.py")
(td :class "px-3 py-2 text-stone-700" "Anchoring pipeline — wire to activity lifecycle (new)")
(td :class "px-3 py-2 text-stone-600" "5"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boot.sx")
(td :class "px-3 py-2 text-stone-700" "Client boot — IPFS component loading")
(td :class "px-3 py-2 text-stone-600" "2, 6"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/events/handlers/ap_delivery_handler.py")
(td :class "px-3 py-2 text-stone-700" "Federation delivery — SX format support")
(td :class "px-3 py-2 text-stone-600" "1"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "artdag/core/artdag/cache.py")
(td :class "px-3 py-2 text-stone-700" "Content addressing — shared with component CIDs")
(td :class "px-3 py-2 text-stone-600" "2, 3"))))))))
;; ---------------------------------------------------------------------------
;; Isomorphic Architecture Roadmap
;; ---------------------------------------------------------------------------
(defcomp ~plan-isomorphic-content ()
(~doc-page :title "Isomorphic Architecture Roadmap"
(~doc-section :title "Context" :id "context"
(p "SX has a working server-client pipeline: server evaluates pages with IO (DB, fragments), serializes as SX wire format, client parses and renders to DOM. The language and primitives are already isomorphic " (em "— same spec, same semantics, both sides.") " What's missing is the " (strong "plumbing") " that makes the boundary between server and client a sliding window rather than a fixed wall.")
(p "The key insight: " (strong "s-expressions can partially unfold on the server after IO, then finish unfolding on the client.") " The system knows which components have data fetches (via IO detection in " (a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx") "), resolves those server-side, and sends the rest as pure SX for client rendering. The boundary slides automatically based on what each component actually needs."))
(~doc-section :title "Current State" :id "current-state"
(ul :class "space-y-2 text-stone-700 list-disc pl-5"
(li (strong "Primitive parity: ") "100%. ~80 pure primitives, same names/semantics, JS and Python.")
(li (strong "eval/parse/render: ") "Complete both sides. sx-ref.js has eval, parse, render-to-html, render-to-dom, aser.")
(li (strong "Engine: ") "engine.sx (morph, swaps, triggers, history), orchestration.sx (fetch, events), boot.sx (hydration) — all transpiled.")
(li (strong "Wire format: ") "Server _aser → SX source → client parses → renders to DOM. Boundary is clean.")
(li (strong "Component caching: ") "Hash-based localStorage for component definitions and style dictionaries.")
(li (strong "CSS on-demand: ") "CSSX resolves keywords to CSS rules, injects only used rules.")
(li (strong "Boundary enforcement: ") "boundary.sx + SX_BOUNDARY_STRICT=1 validates all primitives/IO/helpers at registration.")
(li (strong "Dependency analysis: ") "deps.sx computes per-page component bundles — only definitions a page actually uses are sent.")
(li (strong "IO detection: ") "deps.sx classifies every component as pure or IO-dependent. Server expands IO components, serializes pure ones for client.")
(li (strong "Client-side routing: ") "router.sx matches URL patterns. Pure pages render instantly without server roundtrips. Pages with :data fall through to server transparently.")))
;; -----------------------------------------------------------------------
;; Phase 1
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 1: Component Distribution & Dependency Analysis" :id "phase-1"
(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"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Per-page component bundles instead of sending every definition to every page. Smaller payloads, faster boot, better cache hit rates."))
(~doc-subsection :title "The Problem"
(p "The page boot payload serializes every component definition in the environment. A page that uses 5 components still receives all 50+. No mechanism determines which components a page actually needs — the boundary between \"loaded\" and \"used\" is invisible."))
(~doc-subsection :title "Implementation"
(p "The dependency analysis algorithm is defined in "
(a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx")
" — a spec module bootstrapped to every host. Each host loads it via " (code "--spec-modules deps") " and provides 6 platform functions. The spec is the single source of truth; hosts are interchangeable.")
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Transitive closure (deps.sx)")
(p "9 functions that walk the component graph. The core:")
(~doc-code :code (highlight "(define (transitive-deps name env)\n (let ((key (if (starts-with? name \"~\") name\n (concat \"~\" name)))\n (seen (set-create)))\n (transitive-deps-walk key env seen)\n (set-remove seen key)))" "lisp"))
(p (code "scan-refs") " walks a component body AST collecting " (code "~") " symbols. "
(code "transitive-deps") " follows references recursively through the env. "
(code "compute-all-deps") " batch-computes and caches deps for every component. "
"Circular references terminate safely via a seen-set."))
(div
(h4 :class "font-semibold text-stone-700" "2. Page scanning")
(~doc-code :code (highlight "(define (components-needed page-source env)\n (let ((direct (scan-components-from-source page-source))\n (all-needed (set-create)))\n (for-each (fn (name) ...\n (set-add! all-needed name)\n (set-union! all-needed (component-deps comp)))\n direct)\n all-needed))" "lisp"))
(p (code "scan-components-from-source") " finds " (code "(~name") " patterns in serialized SX via regex. " (code "components-needed") " combines scanning with the cached transitive closure to produce the minimal component set for a page."))
(div
(h4 :class "font-semibold text-stone-700" "3. Per-page CSS scoping")
(p (code "page-css-classes") " unions the CSS classes from only the components in the page bundle. Pages that don't use a component never pay for its styles."))
(div
(h4 :class "font-semibold text-stone-700" "4. Platform interface")
(p "The spec declares 6 functions each host implements natively — the only host-specific code:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (code "component-deps") " / " (code "component-set-deps!") " — read/write the cached deps set on a component object")
(li (code "component-css-classes") " — read pre-scanned CSS class names from a component")
(li (code "env-components") " — enumerate all component entries in an environment")
(li (code "regex-find-all") " / " (code "scan-css-classes") " — host-native regex and CSS scanning")))))
(~doc-subsection :title "Spec module"
(p "deps.sx is loaded as a " (strong "spec module") " — an optional extension to the core spec. The bootstrapper flag " (code "--spec-modules deps") " includes it in the generated output alongside the core evaluator, parser, and renderer. Phase 2 IO detection was added to the same module — same bootstrapping mechanism, no architecture changes needed.")
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
(li "shared/sx/ref/deps.sx — canonical spec (14 functions, 8 platform declarations)")
(li "Bootstrapped to all host targets via --spec-modules deps")))
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "15 dedicated tests: scan, transitive closure, circular deps, compute-all, components-needed")
(li "Bootstrapped output verified on both host targets")
(li "Full test suite passes with zero regressions")
(li (a :href "/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app"))))
;; -----------------------------------------------------------------------
;; Phase 2
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 2: Smart Server/Client Boundary — IO Detection" :id "phase-2"
(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 "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. 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. 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. 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. 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 ...) 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
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 3: Client-Side Routing (SPA Mode)" :id "phase-3"
(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/router" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
(a :href "/isomorphism/routing-analyzer" :class "text-green-700 underline text-sm font-medium" "Live routing analyzer"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "After initial page load, pure pages render instantly without server roundtrips. Client matches routes locally, evaluates content expressions with cached components, and only falls back to server for pages with :data dependencies."))
(~doc-subsection :title "Architecture"
(p "Three-layer approach: spec defines pure route matching, page registry bridges server metadata to client, orchestration intercepts navigation for try-first/fallback.")
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Route matching spec (router.sx)")
(p "New spec module with pure functions for Flask-style route pattern matching:")
(~doc-code :code (highlight "(define split-path-segments ;; \"/docs/hello\" → (\"docs\" \"hello\")\n(define parse-route-pattern ;; \"/docs/<slug>\" → segment descriptors\n(define match-route-segments ;; segments + pattern → params dict or nil\n(define find-matching-route ;; path + route table → first match" "lisp"))
(p "No platform interface needed — uses only pure string and list primitives. Bootstrapped to both hosts via " (code "--spec-modules deps,router") "."))
(div
(h4 :class "font-semibold text-stone-700" "2. Page registry")
(p "Server serializes defpage metadata as SX dict literals inside " (code "<script type=\"text/sx-pages\">") ". Each entry carries name, path pattern, auth level, has-data flag, serialized content expression, and closure values.")
(~doc-code :code (highlight "{:name \"docs-page\" :path \"/docs/<slug>\"\n :auth \"public\" :has-data false\n :content \"(case slug ...)\" :closure {}}" "lisp"))
(p "boot.sx processes these at startup using the SX parser — the same " (code "parse") " function from parser.sx — building route entries with parsed patterns into the " (code "_page-routes") " table. No JSON dependency."))
(div
(h4 :class "font-semibold text-stone-700" "3. Client-side interception (orchestration.sx)")
(p (code "bind-client-route-link") " replaces " (code "bind-boost-link") " in boost processing. On click:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Extract pathname from href")
(li "Call " (code "find-matching-route") " against " (code "_page-routes"))
(li "If match found AND no :data: evaluate content expression locally with component env + URL params")
(li "If evaluation succeeds: swap into #main-panel, pushState, log " (code "\"sx:route client /path\""))
(li "If anything fails (no match, has data, eval error): transparent fallback to server fetch"))
(p (code "handle-popstate") " also tries client routing before server fetch on back/forward."))))
(~doc-subsection :title "What becomes client-routable"
(p "Pages WITHOUT " (code ":data") " that have pure content expressions — most of this docs app:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (code "/") ", " (code "/docs/") ", " (code "/docs/<slug>") " (most slugs), " (code "/protocols/") ", " (code "/protocols/<slug>"))
(li (code "/examples/") ", " (code "/examples/<slug>") ", " (code "/essays/") ", " (code "/essays/<slug>"))
(li (code "/plans/") ", " (code "/plans/<slug>") ", " (code "/isomorphism/") ", " (code "/bootstrappers/")))
(p "Pages that fall through to server:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (code "/docs/primitives") " and " (code "/docs/special-forms") " (call " (code "primitives-data") " / " (code "special-forms-data") " helpers)")
(li (code "/reference/<slug>") " (has " (code ":data (reference-data slug)") ")")
(li (code "/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")")
(li (code "/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")")))
(~doc-subsection :title "Try-first/fallback design"
(p "Client routing uses a try-first approach: attempt local evaluation in a try/catch, fall back to server fetch on any failure. This avoids needing perfect static analysis of content expressions — if a content expression calls a page helper the client doesn't have, the eval throws, and the server handles it transparently.")
(p "Console messages provide visibility: " (code "sx:route client /essays/why-sexps") " vs " (code "sx:route server /specs/eval") "."))
(~doc-subsection :title "Files"
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
(li "shared/sx/ref/router.sx — route pattern matching spec")
(li "shared/sx/ref/boot.sx — process page registry scripts")
(li "shared/sx/ref/orchestration.sx — client route interception")
(li "shared/sx/ref/bootstrap_js.py — router spec module + platform functions")
(li "shared/sx/ref/bootstrap_py.py — router spec module (parity)")
(li "shared/sx/helpers.py — page registry SX serialization")))
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Pure page navigation: zero server requests, console shows \"sx:route client\"")
(li "IO/data page fallback: falls through to server fetch transparently")
(li "Browser back/forward works with client-routed pages")
(li "Disabling page registry → identical behavior to before")
(li "Bootstrap parity: sx_ref.py and sx-ref.js both contain router functions"))))
;; -----------------------------------------------------------------------
;; Phase 4
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 4: Client Async & IO Bridge" :id "phase-4"
(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" "Client evaluates IO primitives by mapping them to server REST calls. Same SX code, different transport. (query \"market\" \"products\" :ids \"1,2,3\") on server → DB; on client → fetch(\"/internal/data/products?ids=1,2,3\")."))
(~doc-subsection :title "Approach"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Async client evaluator")
(p "Two possible mechanisms:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "Promise-based: ") "evalExpr returns value or Promise; rendering awaits")
(li (strong "Continuation-based: ") "use existing shift/reset to suspend on IO, resume when data arrives (architecturally cleaner, leverages existing spec)")))
(div
(h4 :class "font-semibold text-stone-700" "2. IO primitive bridge")
(p "Register async IO primitives in client PRIMITIVES:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
(li "query → fetch to /internal/data/")
(li "service → fetch to target service internal endpoint")
(li "frag → fetch fragment HTML")
(li "current-user → cached from initial page load")))
(div
(h4 :class "font-semibold text-stone-700" "3. Client data cache")
(p "Keyed by (service, query, params-hash), configurable TTL, server can invalidate via SX-Invalidate header."))
(div
(h4 :class "font-semibold text-stone-700" "4. Optimistic updates")
(p "Extend existing apply-optimistic/revert-optimistic in engine.sx from DOM-level to data-level."))))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 2 (IO affinity), Phase 3 (routing for when to trigger IO)."))
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Client (query ...) returns identical data to server-side")
(li "Data cache prevents redundant fetches")
(li "Same component source → identical output on either side"))))
;; -----------------------------------------------------------------------
;; Phase 5
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 5: Streaming & Suspense" :id "phase-5"
(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" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately, fills in suspended parts. Like React Suspense but built on delimited continuations."))
(~doc-subsection :title "Approach"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Continuation-based suspension")
(p "When _aser encounters IO during slot evaluation, emit a placeholder with a suspension ID, schedule async resolution:")
(~doc-code :code (highlight "(~suspense :id \"placeholder-123\"\n :fallback (div \"Loading...\"))" "lisp")))
(div
(h4 :class "font-semibold text-stone-700" "2. Chunked transfer")
(p "Quart async generator responses:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "First chunk: HTML shell + synchronous content + placeholders")
(li "Subsequent chunks: <script> tags replacing placeholders with resolved content")))
(div
(h4 :class "font-semibold text-stone-700" "3. Client suspension rendering")
(p "~suspense component renders fallback, listens for resolution via inline script or SSE (existing SSE infrastructure in orchestration.sx)."))
(div
(h4 :class "font-semibold text-stone-700" "4. Priority-based IO")
(p "Above-fold content resolves first. All IO starts concurrently (asyncio.create_task), results flushed in priority order."))))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 4 (client async for filling suspended subtrees), Phase 2 (IO analysis for priority).")))
;; -----------------------------------------------------------------------
;; Phase 6
;; -----------------------------------------------------------------------
(~doc-section :title "Phase 6: Full Isomorphism" :id "phase-6"
(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" "Same SX code runs on either side. Runtime chooses optimal split. Offline-first with cached data + client eval."))
(~doc-subsection :title "Approach"
(div :class "space-y-4"
(div
(h4 :class "font-semibold text-stone-700" "1. Runtime boundary optimizer")
(p "Given component tree + IO dependency graph, decide per-component: server-expand, client-render, or stream. Planning step cached at registration, recomputed on component change."))
(div
(h4 :class "font-semibold text-stone-700" "2. Affinity annotations")
(~doc-code :code (highlight "(defcomp ~product-grid (&key products)\n :affinity :client ;; interactive, prefer client\n ...)\n\n(defcomp ~auth-menu (&key user)\n :affinity :server ;; auth-sensitive, always server\n ...)" "lisp"))
(p "Default: auto (runtime decides from IO analysis)."))
(div
(h4 :class "font-semibold text-stone-700" "3. Offline data layer")
(p "Service Worker intercepts /internal/data/ requests, serves from IndexedDB when offline, syncs when back online."))
(div
(h4 :class "font-semibold text-stone-700" "4. Isomorphic testing")
(p "Evaluate same component on Python and JS, compare output. Extends existing test_sx_ref.py cross-evaluator comparison."))
(div
(h4 :class "font-semibold text-stone-700" "5. Universal page descriptor")
(p "defpage is portable: server executes via execute_page(), client executes via route match → fetch data → eval content → render DOM. Same descriptor, different execution environment."))))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "All previous phases.")))
;; -----------------------------------------------------------------------
;; Cross-Cutting Concerns
;; -----------------------------------------------------------------------
(~doc-section :title "Cross-Cutting Concerns" :id "cross-cutting"
(~doc-subsection :title "Error Reporting"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Phase 1: \"Unknown component\" includes which page expected it and what bundle was sent")
(li "Phase 2: Server logs which components expanded server-side vs sent to client")
(li "Phase 3: Client route failures include unmatched path and available routes")
(li "Phase 4: Client IO errors include query name, params, server response")
(li "Source location tracking in parser → propagate through eval → include in error messages")))
(~doc-subsection :title "Backward Compatibility"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Pages without annotations behave as today")
(li "SX-Request / SX-Components / SX-Css header protocol continues")
(li "Existing .sx files require no changes")
(li "_expand_components continues as override")
(li "Each phase is opt-in: disable → identical to previous behavior")))
(~doc-subsection :title "Spec Integrity"
(p "All new behavior specified in .sx files under shared/sx/ref/ before implementation. Bootstrappers transpile from spec. This ensures JS and Python stay in sync.")))
;; -----------------------------------------------------------------------
;; Critical Files
;; -----------------------------------------------------------------------
(~doc-section :title "Critical Files" :id "critical-files"
(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" "File")
(th :class "px-3 py-2 font-medium text-stone-600" "Role")
(th :class "px-3 py-2 font-medium text-stone-600" "Phases")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/async_eval.py")
(td :class "px-3 py-2 text-stone-700" "Core evaluator, _aser, server/client boundary")
(td :class "px-3 py-2 text-stone-600" "2, 5"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
(td :class "px-3 py-2 text-stone-700" "sx_page(), sx_response(), output pipeline")
(td :class "px-3 py-2 text-stone-600" "1, 3"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/jinja_bridge.py")
(td :class "px-3 py-2 text-stone-700" "_COMPONENT_ENV, component registry")
(td :class "px-3 py-2 text-stone-600" "1, 2"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/pages.py")
(td :class "px-3 py-2 text-stone-700" "defpage, execute_page(), page lifecycle")
(td :class "px-3 py-2 text-stone-600" "2, 3"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boot.sx")
(td :class "px-3 py-2 text-stone-700" "Client boot, component caching")
(td :class "px-3 py-2 text-stone-600" "1, 3, 4"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/orchestration.sx")
(td :class "px-3 py-2 text-stone-700" "Client fetch/swap/morph")
(td :class "px-3 py-2 text-stone-600" "3, 4"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/eval.sx")
(td :class "px-3 py-2 text-stone-700" "Evaluator spec")
(td :class "px-3 py-2 text-stone-600" "4"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/engine.sx")
(td :class "px-3 py-2 text-stone-700" "Morph, swaps, triggers")
(td :class "px-3 py-2 text-stone-600" "3"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/deps.py")
(td :class "px-3 py-2 text-stone-700" "Dependency analysis (new)")
(td :class "px-3 py-2 text-stone-600" "1, 2"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/router.sx")
(td :class "px-3 py-2 text-stone-700" "Client-side routing (new)")
(td :class "px-3 py-2 text-stone-600" "3"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/io-bridge.sx")
(td :class "px-3 py-2 text-stone-700" "Client IO primitives (new)")
(td :class "px-3 py-2 text-stone-600" "4"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/suspense.sx")
(td :class "px-3 py-2 text-stone-700" "Streaming/suspension (new)")
(td :class "px-3 py-2 text-stone-600" "5"))))))))