GraphSX URL routing: s-expression URLs for sx-docs
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m50s
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:
@@ -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)."))))))
|
||||
|
||||
@@ -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).")))))
|
||||
|
||||
@@ -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")))))
|
||||
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -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.")))))
|
||||
|
||||
@@ -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.")))
|
||||
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
@@ -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.")))))
|
||||
|
||||
@@ -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."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
@@ -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 (7a–7f) 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
197
sx/sx/plans/sx-protocol.sx
Normal 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
302
sx/sx/plans/sx-urls.sx
Normal 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")))))
|
||||
@@ -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."))
|
||||
|
||||
Reference in New Issue
Block a user