GraphSX URL routing: s-expression URLs for sx-docs
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m50s

Replace path-based URLs with nested s-expression URLs across the sx app.
URLs like /language/docs/introduction become /(language.(doc.introduction)),
making the URL simultaneously a query, render instruction, and address.

- Add sx_router.py: catch-all route evaluator with dot→space conversion,
  auto-quoting slugs, two-phase eval, streaming detection, 301 redirects
- Add page-functions.sx: section + page functions for URL dispatch
- Rewrite nav-data.sx: ~200 hrefs to SX expression format, tree-descent
  nav matching via has-descendant-href? (replaces prefix heuristics)
- Convert ~120 old-style hrefs across 26 .sx content files
- Add SX Protocol proposal (etc/plans/sx-protocol)
- Wire catch-all route in app.py with before_request redirect handler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 09:51:04 +00:00
parent 0cc2f178a9
commit da1ca6009a
32 changed files with 1763 additions and 348 deletions

View File

@@ -280,7 +280,7 @@
(~doc-subsection :title "CID References in Page Registry"
(p "The page registry (shipped to the client as " (code "<script type=\"text/sx-pages\">") ") currently lists deps by name. Extend to include CIDs:")
(~doc-code :code (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :deps ({:name \"~essay-foo\" :cid \"bafy...essay\"}\n {:name \"~doc-code\" :cid \"bafy...doccode\"})\n :content \"(case slug ...)\" :closure {}}" "lisp"))
(p "The " (a :href "/etc/plans/predictive-prefetch" :class "text-violet-700 underline" "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint."))
(p "The " (a :href "/(etc.(plan.predictive-prefetch))" :class "text-violet-700 underline" "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint."))
(~doc-subsection :title "SX Response Component Headers"
(p "Currently, " (code "SX-Components") " header lists loaded component names. Extend to support CIDs:")
@@ -407,9 +407,9 @@
(~doc-section :title "Relationships" :id "relationships"
(p "This plan is the foundation for several other plans and roadmaps:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/etc/plans/sx-activity" :class "text-violet-700 underline" "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.")
(li (a :href "/etc/plans/predictive-prefetch" :class "text-violet-700 underline" "Predictive prefetching") " gains CID-based resolution — the " (code "/sx/components") " endpoint and IPFS gateway become alternative resolution paths in the prefetch cascade.")
(li (a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "Isomorphic architecture") " Phase 1 (component distribution) is enhanced — CIDs make per-page bundles verifiable and cross-server shareable.")
(li (a :href "/(etc.(plan.sx-activity))" :class "text-violet-700 underline" "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.")
(li (a :href "/(etc.(plan.predictive-prefetch))" :class "text-violet-700 underline" "Predictive prefetching") " gains CID-based resolution — the " (code "/sx/components") " endpoint and IPFS gateway become alternative resolution paths in the prefetch cascade.")
(li (a :href "/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "Isomorphic architecture") " Phase 1 (component distribution) is enhanced — CIDs make per-page bundles verifiable and cross-server shareable.")
(li "The SX-Activity vision of " (strong "serverless applications on IPFS") " depends entirely on this plan. Without content-addressed components, applications can't be pinned to IPFS as self-contained artifacts."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "deps.sx (complete), boundary enforcement (complete), IPFS infrastructure (exists in artdag, needs wiring to web platform)."))))))

View File

@@ -278,11 +278,11 @@
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Canonical serialization")
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started"))
(td :class "px-3 py-2" (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 1"))
(td :class "px-3 py-2" (a :href "/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 1"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Component CIDs")
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started"))
(td :class "px-3 py-2" (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 2"))
(td :class "px-3 py-2" (a :href "/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 2"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Purity verification")
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete"))
@@ -301,4 +301,4 @@
(td :class "px-3 py-2 text-stone-600" "artdag L1/L2, IPFSPin model")))))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Builds on: ") (a :href "/etc/plans/content-addressed-components" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/etc/plans/self-hosting-bootstrapper" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/etc/plans/sx-activity" :class "underline" "SX-Activity") " (serverless applications on IPFS).")))))
(p :class "text-amber-800 text-sm" (strong "Builds on: ") (a :href "/(etc.(plan.content-addressed-components))" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/(etc.(plan.self-hosting-bootstrapper))" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/(etc.(plan.sx-activity))" :class "underline" "SX-Activity") " (serverless applications on IPFS).")))))

View File

@@ -7,7 +7,7 @@
(~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 "/language/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."))
(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 "/(language.(spec.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"
@@ -32,8 +32,8 @@
(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 "/language/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/geography/isomorphism/bundle-analyzer" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer"))
(a :href "/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/(geography.(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."))
@@ -43,7 +43,7 @@
(~doc-subsection :title "Implementation"
(p "The dependency analysis algorithm is defined in "
(a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
(a :href "/(language.(spec.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"
@@ -85,7 +85,7 @@
(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 "/geography/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app"))))
(li (a :href "/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app"))))
;; -----------------------------------------------------------------------
;; Phase 2
@@ -96,14 +96,14 @@
(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 "/language/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/geography/isomorphism/bundle-analyzer" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer with IO"))
(a :href "/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/(geography.(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 "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
(a :href "/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
" extend the Phase 1 walker to detect IO primitive references:")
(div :class "space-y-4"
@@ -145,7 +145,7 @@
(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 "/geography/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification"))))
(li (a :href "/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification"))))
;; -----------------------------------------------------------------------
;; Phase 3
@@ -156,8 +156,8 @@
(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 "/language/specs/router" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
(a :href "/geography/isomorphism/routing-analyzer" :class "text-green-700 underline text-sm font-medium" "Live routing analyzer"))
(a :href "/(language.(spec.router))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
(a :href "/(geography.(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."))
@@ -200,7 +200,7 @@
(li (code "/reference/<slug>") " (has " (code ":data (reference-data slug)") ")")
(li (code "/language/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")")
(li (code "/geography/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")")
(li (code "/geography/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "Phase 4 demo") ")")))
(li (code "/geography/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "Phase 4 demo") ")")))
(~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.")
@@ -232,7 +232,7 @@
(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 "/geography/isomorphism/data-test" :class "text-green-700 underline text-sm font-medium" "Live data test page"))
(a :href "/(geography.(isomorphism.data-test))" :class "text-green-700 underline text-sm font-medium" "Live data test page"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Client fetches server-evaluated data and renders :data pages locally. Data cached with TTL to avoid redundant fetches on back/forward navigation. All IO stays server-side — no continuations needed."))
@@ -258,7 +258,7 @@
(li "Cache miss: " (code "sx:route client+data /path") " — fetches from server, caches, renders")
(li "Cache hit: " (code "sx:route client+cache /path") " — instant render from cached data")
(li "After TTL: stale entry evicted, fresh fetch on next visit"))
(p "Try it: navigate to the " (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch)."))))
(p "Try it: navigate to the " (a :href "/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch)."))))
(~doc-subsection :title "Files"
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
@@ -273,7 +273,7 @@
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "30 unit tests: serialize roundtrip, kebab-case, deps, full pipeline simulation, cache TTL")
(li "Console: " (code "sx:route client+data") " on first visit, " (code "sx:route client+cache") " on return within 30s")
(li (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "Live data test page") " exercises the full pipeline with server time + pipeline steps")
(li (a :href "/(geography.(isomorphism.data-test))" :class "text-violet-700 underline" "Live data test page") " exercises the full pipeline with server time + pipeline steps")
(li "append! and dict-set! registered as proper primitives in spec + both hosts"))))
;; -----------------------------------------------------------------------
@@ -331,7 +331,7 @@
(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 "/geography/isomorphism/streaming" :class "text-green-700 underline text-sm font-medium" "Live streaming demo"))
(a :href "/(geography.(isomorphism.streaming))" :class "text-green-700 underline text-sm font-medium" "Live streaming demo"))
(p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately with loading skeletons, fills in suspended parts as data arrives."))
@@ -392,9 +392,9 @@
(li "sx/sxc/pages/helpers.py — streaming-demo-data page helper")))
(~doc-subsection :title "Demonstration"
(p "The " (a :href "/geography/isomorphism/streaming" :class "text-violet-700 underline" "streaming demo page") " exercises the full pipeline:")
(p "The " (a :href "/(geography.(isomorphism.streaming))" :class "text-violet-700 underline" "streaming demo page") " exercises the full pipeline:")
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
(li "Navigate to " (a :href "/geography/isomorphism/streaming" :class "text-violet-700 underline" "/geography/isomorphism/streaming"))
(li "Navigate to " (a :href "/(geography.(isomorphism.streaming))" :class "text-violet-700 underline" "/(geography.(isomorphism.streaming))"))
(li "The page skeleton appears " (strong "instantly") " — animated loading skeletons fill the content area")
(li "After ~1.5 seconds, the real content replaces the skeletons (streamed from server)")
(li "Open the Network tab — observe " (code "Transfer-Encoding: chunked") " on the document response")
@@ -481,7 +481,7 @@
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "5 new spec tests (page-render-plan suite)")
(li "Render plans visible on " (a :href "/geography/isomorphism/affinity" "affinity demo page"))
(li "Render plans visible on " (a :href "/(geography.(isomorphism.affinity))" "affinity demo page"))
(li "Client page registry includes :render-plan for each page"))))
(~doc-subsection :title "7c. Cache Invalidation & Optimistic Data Updates"
@@ -518,7 +518,7 @@
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Live demo at " (a :href "/geography/isomorphism/optimistic" :class "text-violet-600 hover:underline" "/geography/isomorphism/optimistic"))
(li "Live demo at " (a :href "/(geography.(isomorphism.optimistic))" :class "text-violet-600 hover:underline" "/(geography.(isomorphism.optimistic))"))
(li "Console log: " (code "sx:optimistic confirmed") " / " (code "sx:optimistic reverted")))))
(~doc-subsection :title "7d. Offline Data Layer"
@@ -553,7 +553,7 @@
(~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Live demo at " (a :href "/geography/isomorphism/offline" :class "text-violet-600 hover:underline" "/geography/isomorphism/offline"))
(li "Live demo at " (a :href "/(geography.(isomorphism.offline))" :class "text-violet-600 hover:underline" "/(geography.(isomorphism.offline))"))
(li "Test with DevTools Network → Offline mode")
(li "Console log: " (code "sx:offline queued") ", " (code "sx:offline syncing") ", " (code "sx:offline synced")))))

View File

@@ -479,7 +479,7 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
(code "createElement") ", " (code "appendChild") ", " (code "textContent")
". This gives SX a compilation target competitive with Svelte's "
"approach: components compile away, the framework disappears.")
(p "Combined with the " (a :href "/etc/plans/content-addressed-components" "content-addressed components")
(p "Combined with the " (a :href "/(etc.(plan.content-addressed-components))" "content-addressed components")
" plan, a page's compiled JS could be stored on IPFS by its content hash. "
"The server returns a CID. The browser fetches and executes pre-compiled JavaScript. "
"No parser, no evaluator, no network round-trip for component definitions."))

View File

@@ -248,7 +248,7 @@
(~doc-section :title "Relationship to Isomorphic Roadmap" :id "relationship"
(p "This plan sits between Phase 3 (client-side routing) and Phase 4 (client async & IO bridge) of the "
(a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "isomorphic architecture roadmap")
(a :href "/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "isomorphic architecture roadmap")
". It extends Phase 3 by making more navigations go client-side without needing any IO bridge — purely by ensuring component definitions are available before they're needed.")
(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 3 (client-side routing with deps checking). No dependency on Phase 4.")))))

View File

@@ -28,7 +28,7 @@
(~doc-code :code (highlight "#'my-function → (quote my-function)" "lisp")))
(~doc-subsection :title "Extensible dispatch: #name"
(p "User-defined reader macros via " (code "#name expr") ". The parser reads an identifier after " (code "#") ", looks up a handler in the reader macro registry, and calls it with the next parsed expression. See the " (a :href "/etc/plans/reader-macro-demo" :class "text-violet-600 hover:underline" "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB.")))
(p "User-defined reader macros via " (code "#name expr") ". The parser reads an identifier after " (code "#") ", looks up a handler in the reader macro registry, and calls it with the next parsed expression. See the " (a :href "/(etc.(plan.reader-macro-demo))" :class "text-violet-600 hover:underline" "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB.")))
;; -----------------------------------------------------------------------

View File

@@ -40,7 +40,7 @@
;; -----------------------------------------------------------------------
(~doc-section :title "Tiers" :id "tiers"
(p "Four tiers, matching the " (a :href "/geography/reactive/plan" :class "text-violet-700 underline" "reactive islands") " levels:")
(p "Four tiers, matching the " (a :href "/(geography.(reactive.plan))" :class "text-violet-700 underline" "reactive islands") " levels:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
@@ -82,7 +82,7 @@
;; -----------------------------------------------------------------------
(~doc-section :title "The Slicer is SX" :id "slicer-is-sx"
(p "Per the " (a :href "/etc/plans/self-hosting-bootstrapper" :class "text-violet-700 underline" "self-hosting principle") ", the slicer is not a build tool — it's a spec module. " (code "slice.sx") " analyzes the spec's own dependency graph and determines which defines belong to which tier.")
(p "Per the " (a :href "/(etc.(plan.self-hosting-bootstrapper))" :class "text-violet-700 underline" "self-hosting principle") ", the slicer is not a build tool — it's a spec module. " (code "slice.sx") " analyzes the spec's own dependency graph and determines which defines belong to which tier.")
(p (code "js.sx") " (the self-hosting SX-to-JavaScript translator) already compiles the full spec. Slicing is a filter: " (code "js.sx") " translates only the defines that " (code "slice.sx") " selects for a given tier.")
(~doc-code :code (highlight ";; slice.sx — determine which defines each tier needs\n;;\n;; Input: the full list of defines from all spec files\n;; Output: a filtered list for the requested tier\n\n(define tier-deps\n ;; Which spec modules each tier requires\n {:L0 (list \"engine\" \"boot-partial\")\n :L1 (list \"engine\" \"boot-partial\" \"dom-partial\")\n :L2 (list \"engine\" \"boot-partial\" \"dom-partial\"\n \"signals\" \"dom-island\")\n :L3 (list \"eval\" \"render\" \"parser\"\n \"engine\" \"orchestration\" \"boot\"\n \"dom\" \"signals\" \"router\")})\n\n(define slice-defines\n (fn (tier all-defines)\n ;; 1. Get the module list for this tier\n ;; 2. Walk each define's dependency references\n ;; 3. Include a define only if ALL its deps are\n ;; satisfiable within the tier's module set\n ;; 4. Return the filtered define list\n (let ((modules (get tier-deps tier)))\n (filter\n (fn (d) (tier-satisfies? modules (define-deps d)))\n all-defines))))" "lisp"))
@@ -161,7 +161,7 @@
(~doc-subsection :title "Cache Behavior"
(p "Each tier file is content-hashed (like the current " (code "sx_js_hash") " mechanism). Cache-forever semantics. A user who visits any L0 page caches the L0 runtime permanently. If they later visit an L2 page, only the ~10KB delta downloads.")
(p "Combined with " (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "environment images") ": the image CID includes the tier. An L0 image is smaller than an L3 image — it contains fewer primitives, no parser state, no evaluator. The standalone HTML bundle for an L0 page is tiny.")))
(p "Combined with " (a :href "/(etc.(plan.environment-images))" :class "text-violet-700 underline" "environment images") ": the image CID includes the tier. An L0 image is smaller than an L3 image — it contains fewer primitives, no parser state, no evaluator. The standalone HTML bundle for an L0 page is tiny.")))
;; -----------------------------------------------------------------------
;; Automatic tier detection
@@ -273,10 +273,10 @@
(~doc-section :title "Relationships" :id "relationships"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.")
(li (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.")
(li (a :href "/geography/reactive/plan" :class "text-violet-700 underline" "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.")
(li (a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it."))
(li (a :href "/(etc.(plan.environment-images))" :class "text-violet-700 underline" "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.")
(li (a :href "/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.")
(li (a :href "/(geography.(reactive.plan))" :class "text-violet-700 underline" "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.")
(li (a :href "/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") (code "js.sx") " (complete), " (code "deps.sx") " (complete), " (code "bootstrap_js.py") " adapter selection (exists). " (strong "New: ") (code "slice.sx") " spec module.")))))

View File

@@ -17,7 +17,7 @@
(p :class "text-green-700 text-sm"
(code "py.sx") " is implemented and verified. G0 == G1: 128/128 defines match, "
"1490 lines, 88,955 bytes — byte-for-byte identical. "
(a :href "/language/bootstrappers/self-hosting" :class "underline text-green-600 font-medium"
(a :href "/(language.(bootstrapper.self-hosting))" :class "underline text-green-600 font-medium"
"See live verification."))))
;; -----------------------------------------------------------------------

View File

@@ -37,37 +37,37 @@
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 1: Dependency Analysis"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 1: Dependency Analysis"))
(p :class "text-sm text-stone-600" "Per-page component bundles via deps.sx. Transitive closure, scan-refs, components-needed, page-css-classes. 15 tests, bootstrapped to both hosts."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 2: IO Detection"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 2: IO Detection"))
(p :class "text-sm text-stone-600" "Automatic IO classification. scan-io-refs, transitive-io-refs, compute-all-io-refs. Server expands IO components, serializes pure ones for client."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing"))
(p :class "text-sm text-stone-600" "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
(p :class "text-sm text-stone-600" "Server evaluates :data expressions, serializes as SX wire format. Client fetches pre-evaluated data, caches with 30s TTL, renders :content locally. 30 unit tests."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 5: Client IO Proxy"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-green-800 underline" "Isomorphic Phase 5: Client IO Proxy"))
(p :class "text-sm text-stone-600" "IO primitives (highlight, asset-url, etc.) proxied to server via registerIoDeps(). Async DOM renderer handles promises through the render tree. Components with IO deps render client-side via server round-trips — no continuations needed."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/language/testing/" :class "font-semibold text-green-800 underline" "Modular Test Architecture"))
(a :href "/(language.(test))" :class "font-semibold text-green-800 underline" "Modular Test Architecture"))
(p :class "text-sm text-stone-600" "Per-module test specs (eval, parser, router, render) with 161 tests. Three runners: Python, Node.js, browser. 5 platform functions, everything else pure SX."))))
;; -----------------------------------------------------------------------
@@ -81,7 +81,7 @@
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial")
(a :href "/etc/plans/fragment-protocol" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
(a :href "/(etc.(plan.fragment-protocol))" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
(p :class "text-sm text-stone-600" "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers."))))
;; -----------------------------------------------------------------------
@@ -95,49 +95,49 @@
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-700 text-white uppercase" "Done")
(a :href "/etc/plans/reader-macros" :class "font-semibold text-stone-800 underline" "Reader Macros"))
(a :href "/(etc.(plan.reader-macros))" :class "font-semibold text-stone-800 underline" "Reader Macros"))
(p :class "text-sm text-stone-600" "# dispatch in parser.sx spec, Python parser.py, hand-written sx.js. Three built-ins (#;, #|...|, #') plus extensible #name dispatch. #z3 demo translates define-primitive to SMT-LIB.")
(p :class "text-sm text-stone-500 mt-1" "48 parser tests (SX + Python), all passing. Rebootstrapped to JS and Python."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/etc/plans/sx-activity" :class "font-semibold text-stone-800 underline" "SX-Activity"))
(a :href "/(etc.(plan.sx-activity))" :class "font-semibold text-stone-800 underline" "SX-Activity"))
(p :class "text-sm text-stone-600" "Federated SX over ActivityPub — 6 phases from SX wire format for activities to the evaluable web on IPFS. Existing AP infrastructure provides the foundation but no SX-specific federation code exists.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: shared/sx/activity.py (SX<->JSON-LD), shared/sx/ipfs.py, shared/sx/ref/ipfs-resolve.sx, shared/sx/registry.py, shared/sx/anchor.py."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/etc/plans/glue-decoupling" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue"))
(a :href "/(etc.(plan.glue-decoupling))" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue"))
(p :class "text-sm text-stone-600" "Eliminate all cross-app model imports by routing through a glue service layer. No glue/ directory exists. Apps are currently decoupled via HTTP interfaces and DTOs instead.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: glue/services/ for pages, page_config, calendars, marketplaces, cart_items, products, post_associations. 25+ cross-app imports to eliminate."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/etc/plans/social-sharing" :class "font-semibold text-stone-800 underline" "Social Network Sharing"))
(a :href "/(etc.(plan.social-sharing))" :class "font-semibold text-stone-800 underline" "Social Network Sharing"))
(p :class "text-sm text-stone-600" "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon via the account service. No models, blueprints, or platform clients created.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: SocialConnection model, social_crypto.py, platform OAuth clients (6), account/bp/social/ blueprint, share button fragment."))
(div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Streaming & Suspense"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Streaming & Suspense"))
(p :class "text-sm text-stone-600" "Server streams partially-evaluated SX as IO resolves. ~suspense component renders fallbacks, inline resolution scripts fill in content. Concurrent IO via asyncio, chunked transfer encoding.")
(p :class "text-sm text-stone-500 mt-1" "Demo: " (a :href "/geography/isomorphism/streaming" "/geography/isomorphism/streaming")))
(p :class "text-sm text-stone-500 mt-1" "Demo: " (a :href "/(geography.(isomorphism.streaming))" "/(geography.(isomorphism.streaming))")))
(div :class "rounded border border-green-300 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/geography/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 7: Full Isomorphism"))
(a :href "/(geography.(isomorphism))" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 7: Full Isomorphism"))
(p :class "text-sm text-stone-600" "Affinity annotations, render plans, optimistic data updates, offline mutation queue, isomorphic testing harness, universal page descriptor.")
(p :class "text-sm text-stone-500 mt-1" "All 6 sub-phases (7a7f) complete."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/etc/plans/spec-explorer" :class "font-semibold text-stone-800 underline" "Spec Explorer — The Fifth Ring"))
(a :href "/(etc.(plan.spec-explorer))" :class "font-semibold text-stone-800 underline" "Spec Explorer — The Fifth Ring"))
(p :class "text-sm text-stone-600" "SX exploring itself. Per-function cards showing all five rings: SX source (nucleus), Python/JS/Z3 translations (bootstrapper), platform dependencies (bridge), tests and proofs (runtime), and usage examples (application). The documentation is the thing documenting itself.")
(p :class "text-sm text-stone-500 mt-1" "Prerequisite complete: 180+ functions annotated with :effects across all 14 spec files. Three increments: core + translations, bridge + runtime, examples + polish."))))))

197
sx/sx/plans/sx-protocol.sx Normal file
View File

@@ -0,0 +1,197 @@
;; SX Protocol — A Proposal
;; S-expressions as a universal protocol for networked hypermedia.
(defcomp ~plan-sx-protocol-content ()
(~doc-page :title "SX Protocol — A Proposal"
(~doc-section :title "Abstract" :id "abstract"
(p "SX is a Lisp dialect and a proposed universal protocol for networked hypermedia. "
"It replaces URLs, HTTP verbs, query strings, API query languages, and rendering layers "
"with a single unified concept: " (strong "the s-expression") ".")
(p "Everything is an expression. Everything is evaluable. Everything is composable."))
(~doc-section :title "The Problem With the Current Web" :id "problem"
(p "The modern web stack has accumulated layers of incompatible syntax to express "
"what are fundamentally the same things:")
(table :class "w-full text-sm border-collapse mb-4"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "Concern")
(th :class "text-left py-2 pr-4" "Current Syntax")
(th :class "text-left py-2" "Example")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Resource path")
(td :class "py-2 pr-4" "URL segments")
(td :class "py-2 font-mono text-xs" "/users/123/posts"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Query parameters")
(td :class "py-2 pr-4 font-mono text-xs" "?key=value")
(td :class "py-2 font-mono text-xs" "?filter=published&sort=date"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "API queries")
(td :class "py-2 pr-4" "GraphQL / REST")
(td :class "py-2 font-mono text-xs" "{ posts(filter: \"published\") { title } }"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Network verb")
(td :class "py-2 pr-4" "HTTP method")
(td :class "py-2 font-mono text-xs" "GET, POST, PUT"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Real-time")
(td :class "py-2 pr-4" "WebSocket URL")
(td :class "py-2 font-mono text-xs" "wss://site.com/live"))
(tr
(td :class "py-2 pr-4" "Rendering")
(td :class "py-2 pr-4" "HTML + CSS + JS")
(td :class "py-2" "Three separate languages"))))
(p "Each layer invented its own syntax. None of them compose. None of them are executable. "
"None of them are data."))
(~doc-section :title "The SX Approach" :id "approach"
(h3 :class "text-lg font-semibold mt-6 mb-2" "URLs as S-Expressions")
(p "A conventional URL:")
(~doc-code :code (highlight "https://site.com/blog/my-post?filter=published&sort=date" "text"))
(p "As an SX expression:")
(~doc-code :code (highlight "(get.site.com.(blog.(my-post.(filter.published.sort.date))))" "lisp"))
(ul
(li "The protocol/verb is the first atom: " (code "get"))
(li "The domain follows: " (code "site.com"))
(li "Path and parameters collapse into " (strong "one unified nested structure"))
(li "No " (code "?") ", no " (code "&") ", no " (code "/") " — just lists"))
(h3 :class "text-lg font-semibold mt-6 mb-2" "Dots, Not Spaces")
(p "Lisp conventionally uses spaces as separators. In URLs, spaces become " (code "%20")
". SX uses dots instead, which are URL-safe and semantically meaningful — a dot between "
"two atoms is a " (strong "cons pair") ", the fundamental unit of Lisp structure.")
(~doc-code :code (highlight ";; Clean, URL-safe, valid Lisp\n(blog.(filter.published).(sort.date.desc))" "lisp"))
(h3 :class "text-lg font-semibold mt-6 mb-2" "Verbs Are Just Atoms")
(p "HTTP methods are not special syntax — they are simply the first element of the expression:")
(~doc-code :code (highlight "(get.site.com.(post.my-first-post)) ; read\n(post.site.com.(submit-post.(title.hello))) ; write\n(ws.site.com.(live-feed)) ; websocket / subscribe" "lisp"))
(p "No special protocol prefixes. No " (code "https://") " vs " (code "wss://")
". The verb is data, like everything else."))
(~doc-section :title "Graph-SX: Hypermedia Queries" :id "graph-sx"
(p "GraphQL was a major advance over REST, but it made two compromises:")
(ol
(li "Queries are sent as POST bodies, sacrificing cacheability and shareability")
(li "Responses are dead data — JSON that must be separately rendered"))
(p (strong "Graph-SX") " addresses both.")
(h3 :class "text-lg font-semibold mt-6 mb-2" "Queries Are URLs")
(p "Because SX expressions are URLs, every query is a GET request:")
(~doc-code :code (highlight ";; This is a URL and a query simultaneously\n(get.site.com.(blog.(filter.(tag.lisp)).(limit.10)))" "lisp"))
(ul
(li "Fully cacheable by CDNs")
(li "Bookmarkable and shareable")
(li "No POST body required for reads")
(li "Browser back button works correctly"))
(h3 :class "text-lg font-semibold mt-6 mb-2" "Responses Include Rendering")
(p "GraphQL returns data. Graph-SX returns " (strong "hypermedia")
" — data and its presentation in the same expression:")
(~doc-code :code (highlight ";; GraphQL response (dead data)\n{\"title\": \"My Post\", \"body\": \"Hello world\"}\n\n;; Graph-SX response (live hypermedia)\n(article\n (h1 \"My Post\")\n (p \"Hello world\")\n (a (href (get.site.com.(post.next-post))) \"Next\"))" "lisp"))
(p "The server returns what the resource " (strong "is") " and how to "
(strong "present") " it in one unified structure. There is no separate rendering layer.")
(h3 :class "text-lg font-semibold mt-6 mb-2" "Queries Are Transformations")
(p "Because SX is a full programming language, the query and the transformation "
"are the same expression:")
(~doc-code :code (highlight ";; Fetch, filter, and transform in one expression\n(map (lambda (p) (title p))\n (filter published?\n (posts (after \"2025\"))))" "lisp"))
(p "No separate processing step. No client-side data manipulation layer."))
(~doc-section :title "Components" :id "components"
(p "SX supports server-side composable components via the " (code "~") " prefix convention:")
(~doc-code :code (highlight "(~get.everything-under-the-sun)" "lisp"))
(p "A " (code "~component") " is a named server-side function that:")
(ol
(li "Receives the expression as arguments")
(li "Makes onward queries as needed")
(li "Processes and composes results")
(li "Returns hypermedia"))
(p "Components compose naturally:")
(~doc-code :code (highlight "(~page.home\n (~hero.banner)\n (~get.latest-posts.(limit.5))\n (~get.featured.(filter.pinned)))" "lisp"))
(p "This is equivalent to React Server Components — but without a framework, "
"without a build step, and without leaving Lisp."))
(~doc-section :title "Cross-Domain Composition" :id "cross-domain"
(p "Because domain and verb are just atoms, cross-domain queries are structurally "
"identical to local ones:")
(~doc-code :code (highlight ";; Local\n(post.my-first-post)\n\n;; Remote — identical structure, qualified\n(get.site.com.(post.my-first-post))\n\n;; Composed across domains\n(~render\n (get.site.com.(post.my-first-post))\n (get.cdn.com.(image.hero)))" "lisp"))
(p "Network calls are function calls. Remote resources are just namespaced expressions."))
(~doc-section :title "Self-Describing and Introspectable" :id "introspectable"
(p "Because the site is implemented in SX and served as SX, every page is introspectable:")
(~doc-code :code (highlight "(get.sx.dev.(about)) ; the about page\n(get.sx.dev.(source.(about))) ; the SX source for the about page\n(get.sx.dev.(eval.(source.about))) ; re-evaluate it live" "lisp"))
(p "The site is its own documentation. The source is always one expression away."))
(~doc-section :title "Comparison" :id "comparison"
(table :class "w-full text-sm border-collapse mb-4"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "Feature")
(th :class "text-left py-2 pr-4" "REST")
(th :class "text-left py-2 pr-4" "GraphQL")
(th :class "text-left py-2" "Graph-SX")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Reads are GETs")
(td :class "py-2 pr-4 text-green-700" "Yes")
(td :class "py-2 pr-4 text-red-700" "No (POST)")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "CDN cacheable")
(td :class "py-2 pr-4 text-green-700" "Yes")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Nested queries")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-green-700" "Yes")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Response includes rendering")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Query is a transformation")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Composable across domains")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "One syntax for everything")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4" "Bookmarkable deep links")
(td :class "py-2 pr-4 text-green-700" "Yes")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 text-green-700" "Yes"))
(tr
(td :class "py-2 pr-4" "Self-hosting / introspectable")
(td :class "py-2 pr-4 text-red-700" "No")
(td :class "py-2 pr-4 text-stone-500" "Partial")
(td :class "py-2 text-green-700" "Yes")))))
(~doc-section :title "Future Direction" :id "future"
(p "The logical conclusion of SX is a " (strong "new internet protocol")
" in which the URL, the HTTP verb, the query language, the response format, "
"and the rendering layer are all unified under one evaluable expression format.")
(~doc-code :code (highlight ";; The entire network request — protocol, domain, verb, query, all one expression\n(get.sx.dev.(blog.(filter.(tag.lisp)).(limit.10)))" "lisp"))
(p "HTTP becomes one possible implementation of a more general principle:")
(blockquote :class "border-l-4 border-violet-300 pl-4 italic text-stone-600 my-4"
(p (strong "Evaluate this expression. Return an expression."))))
(~doc-section :title "Reference Implementation" :id "reference"
(p "SX is implemented in SX. The reference implementation is self-hosting and available at:")
(~doc-code :code (highlight "(get.sx.dev.(source.evaluator))" "lisp"))
(p :class "text-sm text-stone-500 mt-4 italic"
"This proposal was written in conversation with Claude (Anthropic). The ideas are the author's own."))))

302
sx/sx/plans/sx-urls.sx Normal file
View File

@@ -0,0 +1,302 @@
;; SX Expression URLs — GraphSX
;; Plan: replace path-based routing with s-expression URLs where the URL
;; IS the query, the render instruction, and the address — all at once.
(defcomp ~plan-sx-urls-content ()
(~doc-page :title "SX Expression URLs"
(~doc-section :title "Vision" :id "vision"
(p "URLs become s-expressions. The entire routing layer collapses into eval. "
"Every page is a function, every URL is a function call, and the nav tree hierarchy "
"is encoded directly in the nesting of the expression.")
(p "Current URLs like " (code "/language/docs/introduction") " become "
(code "/(language.(doc.introduction))") ". Dots replace spaces as the URL-friendly "
"separator — they are unreserved in RFC 3986, never percent-encoded, and visually clean. "
"The parser treats dot as whitespace: " (code "s/./ /") " before parsing as SX.")
(~doc-code :code (highlight
";; Current → SX URLs (dots = spaces)\n/language/specs/signals → /(language.(spec.signals))\n/language/specs/explore/signals → /(language.(spec.(explore.signals)))\n/language/docs/introduction → /(language.(doc.introduction))\n/etc/plans/spec-explorer → /(etc.(plan.spec-explorer))\n\n;; Direct component access — any defcomp is addressable\n/(~essay-sx-sucks)\n/(~plan-sx-urls-content)\n/(~bundle-analyzer-content)"
"lisp")))
(~doc-section :title "Scoping — The 30-Year Ambiguity, Fixed" :id "scoping"
(p "REST URLs have an inherent ambiguity: does a filter/parameter apply to "
"the last segment, or the whole path? Consider:")
(~doc-code :code (highlight
";; REST — ambiguous:\n/users/123/posts?filter=published\n;; Is the filter scoped to posts? Or to the user? Or the whole query?\n;; Nobody knows. Conventions vary. Documentation required.\n\n;; SX URLs — explicit scoping via nesting:\n/(hello.(sailor.(filter.hhh))) ;; filter scoped to sailor\n/(hello.sailor.(filter.hhh)) ;; filter scoped to hello\n\n;; These mean different things, both expressible.\n;; Parens make scope visible. No ambiguity. No documentation needed."
"lisp"))
(p "This is not a minor syntactic preference. REST has never been able to express "
"the difference between a parameter scoped to a sub-resource and one scoped to the "
"parent. Query strings are flat — " (code "?filter=x") " applies to... what, exactly? "
"The last path segment? The whole URL? It depends on the API.")
(p "S-expression nesting makes scope " (em "structural") ". "
"The paren boundaries are the scope boundaries. "
"What took REST 30 years of convention documents to approximate, "
"SX URLs express in the syntax itself."))
(~doc-section :title "Dots as URL-Safe Whitespace" :id "dots"
(p "Spaces in URLs are ugly — they become " (code "%20") " in copy-paste, curl, logs, and proxies. "
"Dots are unreserved in RFC 3986, never encoded, and read naturally as \"drill down.\"")
(p "The rule is simple: " (strong "dot = space, nothing more") ". "
"Parens carry all the structural meaning. Dots are syntactic sugar for URLs only:")
(~doc-code :code (highlight
";; These are identical after dot→space transform:\n/(language.(doc.introduction)) → (language (doc introduction))\n/(geography.(hypermedia.(reference.attributes)))\n → (geography (hypermedia (reference attributes)))\n\n;; Parens are still required for nesting:\n/(language.doc.introduction) → (language doc introduction)\n;; = language(\"doc\", \"introduction\") — WRONG\n\n;; Correct nesting:\n/(language.(doc.introduction)) → (language (doc introduction))\n;; = language(doc(\"introduction\")) — RIGHT"
"lisp"))
(p "The server's URL handler does one thing before parsing: "
(code "url_expr = raw_path[1:].replace('.', ' ')") ". Then standard SX parsing takes over."))
(~doc-section :title "The Lisp Tax" :id "parens"
(p "People will hate the parentheses. But consider what developers already accept:")
(~doc-code :code (highlight
";; Developers happily write this every day:\nhttps://api.site.com/v2/users/123/posts?filter=published&sort=date&order=desc&limit=10&offset=20\n\n;; And they would complain about this?\nhttps://site.com/(users.(posts.123.(filter.published.sort.date.limit.10)))\n\n;; The second is shorter, structured, unambiguous, and composable."
"lisp"))
(p "The real question: who is reading these URLs?")
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "End users") " barely look at URLs anymore — they live in address bars people ignore")
(li (strong "Developers") " will love it once they get it — it is powerful and consistent")
(li (strong "Crawlers and bots") " do not care at all"))
(p "And this site is " (em "about SX, implemented in SX") ". "
"The audience is exactly the people who will love the parens. "
"Every URL on the site is a live example of SX in action. "
"Visiting a page is evaluating an expression."))
(~doc-section :title "The Site Is a REPL" :id "repl"
(p "The address bar becomes the input line of a REPL. The page is the output.")
(~doc-code :code (highlight
"/(about) ;; renders the about page\n/(source.(about)) ;; returns the SX source for the about page\n/(eval.(source.(about))) ;; re-evaluates it live\n\n;; The killer demo:\n/(eval.(map.double.(list.1.2.3))) ;; actually returns (2 4 6)\n\n;; The website IS a REPL. The address bar IS the input."
"lisp"))
(p "You do not need to explain what SX is. You show someone a URL and they "
"immediately understand the entire language and philosophy. "
"The whole site becomes a self-hosting proof of concept — "
"that is not just elegant, that is the pitch."))
(~doc-section :title "Components as Query Resolvers" :id "resolvers"
(p "The " (code "~") " sigil means \"find and execute this component.\" "
"Components can make onward queries, process results, and return composed content — "
"like server-side includes but Lispy and composable.")
(~doc-code :code (highlight
";; ~get is a component that fetches, processes, and returns\n/(~get.everything-under-the-sun)\n\n;; The flow:\n;; 1. Server finds ~get component in env\n;; 2. ~get makes onward queries\n;; 3. Processes and transforms results\n;; 4. Returns composed hypermedia\n\n;; Because it's all SX, you nest and compose:\n/(~page.home\n .(~hero.banner)\n .(~get.latest-posts.(limit.5))\n .(~get.featured.(filter.pinned)))\n\n;; Each ~component is independently:\n;; - cacheable (by its expression)\n;; - reusable (same component, different args)\n;; - testable (evaluate in isolation)"
"lisp"))
(p "This is what React Server Components are trying to do — server-side data resolution "
"composed with client-side rendering. Except React needs a framework, a bundler, "
"a serialization protocol, and \"use server\" pragmas. "
"SX gets it from a sigil and an evaluator."))
(~doc-section :title "HTTP Semantics — REST Re-Aligned" :id "http"
(p "GraphQL uses POST for queries even though they are pure reads — "
"because queries can be long and the body feels more natural for structured data. "
"But this violates HTTP semantics: POST implies side effects, "
"GET requests are cacheable, bookmarkable, shareable.")
(p "SX URLs naturally align with HTTP because the query " (em "is") " the URL:")
(div :class "overflow-x-auto mt-4"
(table :class "w-full text-sm text-left"
(thead
(tr :class "border-b border-stone-200"
(th :class "py-2 px-3 font-semibold text-stone-700" "Method")
(th :class "py-2 px-3 font-semibold text-stone-700" "Semantics")
(th :class "py-2 px-3 font-semibold text-stone-700" "Example")))
(tbody :class "text-stone-600"
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "GET")
(td :class "py-2 px-3" "Pure evaluation — cacheable, bookmarkable")
(td :class "py-2 px-3 font-mono text-sm" "GET /(language.(doc.intro))"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "POST")
(td :class "py-2 px-3" "Side effects — mutations, submissions")
(td :class "py-2 px-3 font-mono text-sm" "POST /(submit-post)")))))
(p "CDN caching of Lisp hypermedia just works. "
"This is what REST always wanted but GraphQL abandoned. "
"SX re-aligns with HTTP while being more powerful than both."))
(~doc-section :title "GraphSX — This Is a Query Language" :id "graphsx"
(p "The SX URL scheme is not just a routing convention — it is the emergence of "
(strong "GraphSX") ": GraphQL but Lisp. The structural parallel is exact:")
(div :class "overflow-x-auto mt-4"
(table :class "w-full text-sm text-left"
(thead
(tr :class "border-b border-stone-200"
(th :class "py-2 px-3 font-semibold text-stone-700" "Concept")
(th :class "py-2 px-3 font-semibold text-stone-700" "GraphQL")
(th :class "py-2 px-3 font-semibold text-stone-700" "GraphSX")))
(tbody :class "text-stone-600"
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Single endpoint")
(td :class "py-2 px-3" (code "/graphql"))
(td :class "py-2 px-3" "Catch-all " (code "/<path:expr>")))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Query structure")
(td :class "py-2 px-3" "Nested fields " (code "{ language { doc { ... } } }"))
(td :class "py-2 px-3" "Nested s-expressions " (code "(language.(doc....))")))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Resolvers")
(td :class "py-2 px-3" "Per-field functions")
(td :class "py-2 px-3" "Page/section functions + " (code "~components")))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Fragments")
(td :class "py-2 px-3" "Named reusable selections")
(td :class "py-2 px-3" "Components (" (code "defcomp") ")"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Schema")
(td :class "py-2 px-3" "Type definitions")
(td :class "py-2 px-3" "Page function registry + component env"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Scoping")
(td :class "py-2 px-3" "Flat — " (code "?filter=x") " applies to... what?")
(td :class "py-2 px-3" "Structural — parens ARE scope boundaries"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Transport")
(td :class "py-2 px-3" "POST JSON (violates HTTP GET semantics)")
(td :class "py-2 px-3" "GET " (code "/(expr)") " — cacheable, bookmarkable"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-semibold" "Response")
(td :class "py-2 px-3" "JSON data (needs separate rendering)")
(td :class "py-2 px-3" "Content " (em "and") " data — response is already meaningful")))))
(p "The killer difference: in GraphQL, query and rendering are separate concerns — "
"you fetch JSON, then a frontend renders it. In GraphSX, "
(strong "the query language and the rendering language are the same thing") ". "
(code "(language.(doc.introduction))") " is simultaneously:")
(ol :class "space-y-1 text-stone-600 list-decimal pl-5"
(li "A " (strong "query") " — resolve the docs/introduction content within the language section")
(li "A " (strong "render instruction") " — render it inside a language section wrapper with nav context")
(li "A " (strong "URL") " — addressable, bookmarkable, shareable, cacheable"))
(p "This is what Lisp does. The uniform syntax means there is no boundary between "
"routing DSL, query language, template language, and component system. "
"They are all s-expressions evaluated in the same environment. "
"GraphQL had to invent a special syntax for queries because JSON is data, not code. "
"S-expressions are both."))
(~doc-section :title "Direct Component URLs" :id "components"
(p "Any " (code "defcomp") " is directly addressable via its " (code "~name") ". "
"The URL evaluator sees " (code "~essay-sx-sucks") ", looks it up in the component env, "
"evaluates it, wraps in " (code "~sx-doc") ", and returns.")
(~doc-code :code (highlight
";; Page functions are convenience wrappers:\n/(etc.(essay.sx-sucks)) ;; dispatches via case statement\n\n;; But you can bypass them entirely:\n/(~essay-sx-sucks) ;; direct component — no routing needed\n\n;; Every defcomp is instantly URL-accessible:\n/(~plan-sx-urls-content) ;; this very page\n/(~bundle-analyzer-content) ;; tools\n/(~docs-evaluator-content) ;; docs"
"lisp"))
(p "New components are instantly URL-accessible without routing wiring. "
"Debugging is trivial — render any component in isolation."))
(~doc-section :title "URL Special Forms" :id "special-forms"
(p "URL-level functions that transform how content is resolved or displayed:")
(div :class "overflow-x-auto mt-4"
(table :class "w-full text-sm text-left"
(thead
(tr :class "border-b border-stone-200"
(th :class "py-2 px-3 font-semibold text-stone-700" "Form")
(th :class "py-2 px-3 font-semibold text-stone-700" "Example")
(th :class "py-2 px-3 font-semibold text-stone-700" "Effect")))
(tbody :class "text-stone-600"
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "source")
(td :class "py-2 px-3 font-mono text-sm" "/(source.(~essay-sx-sucks))")
(td :class "py-2 px-3" "Show defcomp source instead of rendering"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "inspect")
(td :class "py-2 px-3 font-mono text-sm" "/(inspect.(language.(doc.primitives)))")
(td :class "py-2 px-3" "Deps, CSS classes, render plan, IO requirements"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "diff")
(td :class "py-2 px-3 font-mono text-sm" "/(diff.(spec.signals).(spec.eval))")
(td :class "py-2 px-3" "Side-by-side two pages"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "search")
(td :class "py-2 px-3 font-mono text-sm" "/(search.\"define\".:in.(spec.signals))")
(td :class "py-2 px-3" "Grep within a page or spec"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "raw")
(td :class "py-2 px-3 font-mono text-sm" "/(raw.(~some-component))")
(td :class "py-2 px-3" "Skip ~sx-doc nav wrapping"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "eval")
(td :class "py-2 px-3 font-mono text-sm" "/(eval.(map.double.(list.1.2.3)))")
(td :class "py-2 px-3" "Arbitrary evaluation — the URL bar is a REPL"))
(tr :class "border-b border-stone-100"
(td :class "py-2 px-3 font-mono text-violet-700" "json")
(td :class "py-2 px-3 font-mono text-sm" "/(json.(language.(doc.primitives)))")
(td :class "py-2 px-3" "Return data as JSON — pure query mode"))))))
(~doc-section :title "Evaluation Model" :id "eval"
(p "The URL path (after stripping " (code "/") " and replacing dots with spaces) "
"is parsed as SX and evaluated with a " (strong "soft eval") ": "
"known function names are called; unknown symbols self-evaluate to their name as a string; "
"components (" (code "~name") ") are looked up in the component env.")
(~doc-code :code (highlight
"/(language.(doc.introduction))\n\n;; After dot→space: (language (doc introduction))\n;; 1. Eval `introduction` → not a known function → \"introduction\"\n;; 2. Eval (doc \"introduction\") → call doc(\"introduction\") → page content\n;; 3. Eval (language content) → call language(content) → passes through\n;; 4. Router wraps result in (~sx-doc :path \"(language (doc introduction))\" ...)\n\n/(~essay-sx-sucks)\n;; 1. Eval ~essay-sx-sucks → component lookup → evaluate → content\n;; 2. Router wraps in ~sx-doc"
"lisp"))
(~doc-subsection :title "Section Functions"
(p "Structural functions that encode hierarchy and pass through content:")
(~doc-code :code (highlight
"(define language\n (fn (&rest args)\n (if (empty? args) (language-index-content) (first args))))\n\n(define geography\n (fn (&rest args)\n (if (empty? args) (geography-index-content) (first args))))\n\n;; Sub-sections also pass through\n(define hypermedia\n (fn (&rest args)\n (if (empty? args) (hypermedia-index-content) (first args))))"
"lisp")))
(~doc-subsection :title "Page Functions"
(p "Leaf functions that dispatch to content components. "
"Data-dependent pages call helpers directly — the async evaluator handles IO:")
(~doc-code :code (highlight
"(define doc\n (fn (&rest args)\n (let ((slug (first-or-nil args)))\n (if (nil? slug)\n (~docs-introduction-content)\n (case slug\n \"introduction\" (~docs-introduction-content)\n \"getting-started\" (~docs-getting-started-content)\n ...)))))\n\n(define bootstrapper\n (fn (&rest args)\n (let ((slug (first-or-nil args))\n (data (when slug (bootstrapper-data slug))))\n (if (nil? slug)\n (~bootstrappers-index-content)\n (if (get data \"bootstrapper-not-found\")\n (~spec-not-found :slug slug)\n (case slug\n \"python\" (~bootstrapper-py-content ...)\n ...))))))"
"lisp"))))
(~doc-section :title "The Catch-All Route" :id "route"
(p "The entire routing layer becomes one handler:")
(~doc-code :code (highlight
"@app.get(\"/\")\nasync def sx_home():\n return await eval_sx_url(\"/\")\n\n@app.get(\"/<path:expr>\")\nasync def sx_eval_route(expr):\n return await eval_sx_url(f\"/{expr}\")"
"python"))
(p (code "eval_sx_url") " in seven steps:")
(ol :class "space-y-1 text-stone-600 list-decimal pl-5"
(li "URL-decode the path")
(li "Replace dots with spaces")
(li "Parse as SX expression")
(li "Auto-quote unknown symbols (slugs become strings)")
(li "Evaluate with components + helpers + page/section functions in env")
(li "Wrap result in " (code "~sx-doc") " with the URL expression as " (code ":path"))
(li "Return HTML or SX wire format depending on HTMX request"))
(p "Defhandler API endpoints and Python demo routes are registered " (em "before") " the catch-all, "
"so they match first."))
(~doc-section :title "Composability" :id "composability"
(~doc-code :code (highlight
";; Direct component access\n/(~essay-sx-sucks)\n/(~spec-explorer-content)\n\n;; URL special forms\n/(source.(~essay-sx-sucks)) ;; view defcomp source\n/(inspect.(language.(doc.primitives))) ;; deps, render plan\n/(diff.(language.(spec.signals)).(language.(spec.eval))) ;; side by side\n/(eval.(map.double.(list.1.2.3))) ;; REPL in the URL bar\n\n;; Components as query resolvers\n/(~page.home\n .(~hero.banner)\n .(~get.latest-posts.(limit.5))\n .(~get.featured.(filter.pinned)))\n\n;; Scoping is explicit\n/(users.(posts.123.(filter.published))) ;; filter scoped to posts\n/(users.posts.123.(filter.published)) ;; filter scoped to users\n\n;; Cross-service (future)\n/(market.(product.42.:fields.(name.price)))\n/(subscribe.(etc.(plan.status)))"
"lisp")))
(~doc-section :title "Implementation Phases" :id "phases"
(div :class "space-y-4"
(div :class "rounded border border-violet-200 bg-violet-50 p-4"
(p :class "font-semibold text-violet-800 mb-2" "Phase 1: Page Functions + Catch-All Route")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Create " (code "page-functions.sx") " — ~20 section + ~15 page functions")
(li "Create " (code "sx_router.py") " — URL parser (dot→space), soft eval, streaming detection")
(li "Replace " (code "auto_mount_pages") " with catch-all")
(li "Direct " (code "~component") " URL support")))
(div :class "rounded border border-blue-200 bg-blue-50 p-4"
(p :class "font-semibold text-blue-800 mb-2" "Phase 2: Navigation Data")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Rewrite ~200 " (code ":href") " values to dot-separated SX URLs")
(li "Rewrite " (code "resolve-nav-path") " for tree-descent matching")))
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4"
(p :class "font-semibold text-emerald-800 mb-2" "Phase 3: Backward Compatibility")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "301 redirects from all old paths to new SX URLs")
(li "Algorithmic pattern matching — ~25 regex rules")))
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
(p :class "font-semibold text-amber-800 mb-2" "Phase 4: URL Special Forms")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (code "source") ", " (code "inspect") ", " (code "diff") ", " (code "raw") ", " (code "eval") ", " (code "json"))
(li "Components as query resolvers (" (code "~get") ", " (code "~page") ")")))
(div :class "rounded border border-rose-200 bg-rose-50 p-4"
(p :class "font-semibold text-rose-800 mb-2" "Phase 5: Client-Side Routing")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Update " (code "_build_pages_sx()") " for function-based page registry")
(li "Client-side dot→space→parse→eval pipeline")
(li "Rebootstrap " (code "sx-ref.js") " and " (code "sx_ref.py"))))
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
(p :class "font-semibold text-stone-800 mb-2" "Phase 6: Cleanup")
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Delete " (code "docs.sx") " (all 46 defpages)")
(li "Grep content files for stale old-style hrefs")))))
(~doc-section :title "What Stays the Same" :id "unchanged"
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "Defhandler API paths") " — registered before the catch-all, match first")
(li (strong "Python demo routes") " — registered via blueprint before the catch-all")
(li (strong "All other services") " — blog, market, etc. keep defpage routing")
(li (strong "Component definitions") " — all " (code ".sx") " files unchanged")
(li (strong "Page helpers") " — called from page functions instead of defpage " (code ":data"))
(li (strong "Layout, CSS, shell, rendering pipeline") " — no changes")))))

View File

@@ -8,7 +8,7 @@
(~doc-section :title "The Opportunity" :id "opportunity"
(p "SX already has types. Every primitive in " (code "primitives.sx") " declares " (code ":returns \"number\"") " or " (code ":returns \"boolean\"") ". Every IO primitive in " (code "boundary.sx") " declares " (code ":returns \"dict?\"") " or " (code ":returns \"any\"") ". Component params are named. The information exists — nobody checks it.")
(p "A gradual type system makes this information useful. Annotations are optional. Unannotated code works exactly as before. Annotated code gets checked at registration time — zero runtime cost, errors before any request is served. The checker is a spec module (" (code "types.sx") "), bootstrapped to every host.")
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") " already verifies primitive properties by exhaustive search. Types serve a different purpose: they catch " (strong "composition errors") " — wrong argument passed to a component, mismatched return type piped into another function, missing keyword arg. The kind of bug you find by reading the stack trace and slapping your forehead."))
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") " already verifies primitive properties by exhaustive search. Types serve a different purpose: they catch " (strong "composition errors") " — wrong argument passed to a component, mismatched return type piped into another function, missing keyword arg. The kind of bug you find by reading the stack trace and slapping your forehead."))
;; -----------------------------------------------------------------------
;; What already exists
@@ -116,7 +116,7 @@
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "Runtime values.") " " (code "(if condition 42 \"hello\")") " — the type is " (code "(or number string)") ". The checker doesn't know which branch executes.")
(li (strong "Dict key presence (yet).") " " (code "(get user \"name\")") " — the checker knows " (code "get") " returns " (code "any") " but doesn't track which keys a dict has. Phase 6 (" (code "deftype") " records) will enable this.")
(li (strong "Termination.") " That's " (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") "'s domain.")
(li (strong "Termination.") " That's " (a :href "/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") "'s domain.")
(li (strong "Full algebraic effects.") " The effect system (Phase 7) checks static effect annotations — it does not provide algebraic effect handlers, effect polymorphism, or continuation-based effect dispatch. That door remains open for the future.")))
@@ -231,7 +231,7 @@
;; -----------------------------------------------------------------------
(~doc-section :title "Types vs Proofs" :id "types-vs-proofs"
(p (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:")
(p (a :href "/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
@@ -483,10 +483,10 @@
(~doc-section :title "Relationships" :id "relationships"
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
(li (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.")
(li (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "Environment Images") " — the type registry serializes into the image. Type checking happens once at image build time, not on every startup.")
(li (a :href "/etc/plans/runtime-slicing" :class "text-violet-700 underline" "Runtime Slicing") " — types.sx is a registration-time module, not a runtime module. It doesn't ship to the client. Zero impact on bundle size."))
(li (a :href "/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
(li (a :href "/(etc.(plan.content-addressed-components))" :class "text-violet-700 underline" "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.")
(li (a :href "/(etc.(plan.environment-images))" :class "text-violet-700 underline" "Environment Images") " — the type registry serializes into the image. Type checking happens once at image build time, not on every startup.")
(li (a :href "/(etc.(plan.runtime-slicing))" :class "text-violet-700 underline" "Runtime Slicing") " — types.sx is a registration-time module, not a runtime module. It doesn't ship to the client. Zero impact on bundle size."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "primitives.sx (return + param types), boundary.sx (IO return types + IO classification), eval.sx (defcomp/deftype/defeffect parsing), deps.sx (IO detection — effects formalize this). " (strong "New: ") (code "types.sx") " spec module, type annotations in " (code "parse-comp-params") ", " (code "deftype") " declaration form, " (code "defeffect") " declaration form."))