sx-tools: WASM kernel updates, TW/CSSX rework, content refresh, new debugging tools
Build tooling: updated OCaml bootstrapper, compile-modules, bundle.sh, sx-build-all. WASM browser: rebuilt sx_browser.bc.js/wasm, sx-platform-2.js, .sxbc bytecode files. CSSX/Tailwind: reworked cssx.sx templates and tw-layout, added tw-type support. Content: refreshed essays, plans, geography, reactive islands, docs, demos, handlers. New tools: bisect_sxbc.sh, test-spa.js, render-trace.sx, morph playwright spec. Tests: added test-match.sx, test-examples.sx, updated test-tw.sx and web tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/art-dag-sx/plan-art-dag-sx-content ()
|
||||
(~docs/page :title "Art DAG on SX"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"An SX endpoint is a portal into an execution environment. The URL is the entry point; the boundary declaration determines what world you enter.")
|
||||
|
||||
;; =====================================================================
|
||||
@@ -44,9 +44,9 @@
|
||||
(~docs/section :title "Content-addressed everything" :id "content-addressed"
|
||||
(p "Recipes are CIDs. Effects are CIDs. Intermediate frames are content-addressed. The execution DAG is a content-addressed graph " (em "- ") "every step can be verified, cached, or replayed.")
|
||||
(p "A composite of three layers with a specific blend mode always produces the same CID. Caching is automatic: if the CID exists locally, skip the computation. This is the Art DAG's existing model " (em "- ") "SHA3-256 hashes identify everything. SX makes it explicit: the recipe source itself is content-addressed. Two users who write the same recipe get the same CID. They're running the same program.")
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4 my-4"
|
||||
(p :class "text-stone-700 font-medium mb-2" "Content addressing as memoization")
|
||||
(p :class "text-stone-600 text-sm" "Every function call with content-addressed inputs has a content-addressed output. " (code "(gpu-exec :op \"composite\" :layers (list CID-a CID-b) :blend \"multiply\")") " always produces the same result CID. The runtime can check: does this output CID exist? If yes, skip the computation. The entire execution DAG becomes a cache key. Rerunning a recipe that's already been computed is instantaneous " (em "- ") "every intermediate result already exists."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-stone-700 font-medium mb-2") "Content addressing as memoization")
|
||||
(p (~tw :tokens "text-stone-600 text-sm") "Every function call with content-addressed inputs has a content-addressed output. " (code "(gpu-exec :op \"composite\" :layers (list CID-a CID-b) :blend \"multiply\")") " always produces the same result CID. The runtime can check: does this output CID exist? If yes, skip the computation. The entire execution DAG becomes a cache key. Rerunning a recipe that's already been computed is instantaneous " (em "- ") "every intermediate result already exists."))
|
||||
(p "The execution trace is also content-addressed. You can inspect exactly what happened: which CIDs were resolved, which GPU operations ran, which feeds were opened, what the final output was. The trace is the recipe's proof of work. It's immutable, verifiable, and shareable."))
|
||||
|
||||
;; =====================================================================
|
||||
@@ -85,43 +85,43 @@
|
||||
|
||||
(~docs/section :title "The primitive sets" :id "primitive-sets"
|
||||
(p "Each execution environment provides its own set of primitives. The language is the same everywhere. The capabilities differ.")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Environment")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Primitives")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Runs on")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Environment")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Primitives")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Runs on")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Browser")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "render-to-dom, signal, deref, connect-stream")
|
||||
(td :class "px-3 py-2 text-stone-600" "Client"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "App server")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "query-db, render-to-html, fetch-fragment")
|
||||
(td :class "px-3 py-2 text-stone-600" "Quart service"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "L1 Worker")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "gpu-exec, resolve-cid, encode-stream, cache-put")
|
||||
(td :class "px-3 py-2 text-stone-600" "Celery + GPU"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "L2 Registry")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "discover-recipe, publish-recipe, federate")
|
||||
(td :class "px-3 py-2 text-stone-600" "FastAPI"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Live Ingest")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "open-feed, capture-frame, transcode")
|
||||
(td :class "px-3 py-2 text-stone-600" "WebRTC gateway"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Browser")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "render-to-dom, signal, deref, connect-stream")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Client"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "App server")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "query-db, render-to-html, fetch-fragment")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Quart service"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "L1 Worker")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "gpu-exec, resolve-cid, encode-stream, cache-put")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Celery + GPU"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "L2 Registry")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "discover-recipe, publish-recipe, federate")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "FastAPI"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Live Ingest")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "open-feed, capture-frame, transcode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "WebRTC gateway"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "IPFS Node")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "pin-cid, resolve-cid, dag-put, dag-get")
|
||||
(td :class "px-3 py-2 text-stone-600" "Kubo")))))
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "IPFS Node")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "pin-cid, resolve-cid, dag-put, dag-get")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Kubo")))))
|
||||
|
||||
(p "A pure SX program (no IO primitives) runs on all six. A program that calls " (code "gpu-exec") " runs on L1 workers. A program that calls " (code "render-to-dom") " runs in the browser. The boundary declaration is the type signature of the environment. It tells you where the program can execute.")
|
||||
(p "Adding a new environment means declaring a new primitive set. A hypothetical audio-processing environment would provide " (code "mix-tracks") ", " (code "apply-effect") ", " (code "encode-audio") ". A program that uses those primitives runs wherever that environment is hosted. The language doesn't change. The evaluator doesn't change. Only the available primitives change.")
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(p :class "text-violet-900 font-medium" "One language, many worlds")
|
||||
(p :class "text-violet-800 text-sm" "The art-dag integration isn't a new feature bolted onto SX. It's a demonstration of what SX already is: a language where the execution environment is parameterized by its primitive set. The browser, the app server, the GPU worker, and the IPFS node all run the same evaluator. They differ only in what primitives they provide. The art-dag is just another world you can enter through an endpoint.")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "One language, many worlds")
|
||||
(p (~tw :tokens "text-violet-800 text-sm") "The art-dag integration isn't a new feature bolted onto SX. It's a demonstration of what SX already is: a language where the execution environment is parameterized by its primitive set. The browser, the app server, the GPU worker, and the IPFS node all run the same evaluator. They differ only in what primitives they provide. The art-dag is just another world you can enter through an endpoint.")))
|
||||
|
||||
))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
(~docs/section :title "The Problem" :id "problem"
|
||||
(p "There are currently " (strong "three") " lambda call implementations that must be kept in sync:")
|
||||
(ol :class "list-decimal list-inside space-y-2 mt-2"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 mt-2")
|
||||
(li (code "shared/sx/ref/eval.sx") " — the canonical spec, bootstrapped to " (code "sx-ref.js") " and " (code "sx_ref.py"))
|
||||
(li (code "shared/sx/evaluator.py") " — hand-written synchronous Python evaluator")
|
||||
(li (code "shared/sx/async_eval.py") " — hand-written asynchronous Python evaluator (the production server path)"))
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
(~docs/section :title "Why async_eval.py Exists" :id "why"
|
||||
(p "The async evaluator exists because SX page rendering needs to:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Await IO primitives") " — page helpers like " (code "highlight") ", " (code "reference-data") ", " (code "component-source") " call Python async functions (DB queries, HTTP fetches). The spec evaluator is synchronous.")
|
||||
(li (strong "Expand server-affinity components") " — " (code ":affinity :server") " components must be fully expanded server-side before serialising to SX wire format. This requires async eval of the component body.")
|
||||
(li (strong "Handle the aser rendering mode") " — the " (code "_aser") " function evaluates control flow server-side but serialises HTML tags and component calls as SX source for the client. This hybrid eval/serialize mode isn't in the spec."))
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
(~docs/section :title "Target Architecture" :id "architecture"
|
||||
(p "The goal is to " (strong "eliminate hand-written evaluator code entirely") ". All evaluation semantics come from the spec via bootstrapping. The host provides only:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Platform primitives") " — type constructors, env operations, DOM/HTML primitives")
|
||||
(li (strong "Async IO bridge") " — a thin wrapper that makes the bootstrapped evaluator await-compatible")
|
||||
(li (strong "Rendering modes") " — aser/render-to-html dispatch, already mostly specced in " (code "render.sx")))
|
||||
@@ -41,20 +41,20 @@
|
||||
(~docs/section :title "Approach: Async Adapter Layer" :id "approach"
|
||||
(p "Rather than making the spec itself async (which would pollute it with Python-specific concerns), introduce a thin adapter layer between the bootstrapped evaluator and the IO boundary:")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 1 — Async call hook")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 1 — Async call hook")
|
||||
(p "The bootstrapped evaluator calls primitives via " (code "apply(fn, args)") ". In the Python host, " (code "apply") " is a platform primitive. Replace it with an async-aware version:")
|
||||
(~docs/code :src (highlight "(define apply-fn\n (fn (f args)\n ;; Platform provides: if f returns a coroutine, await it\n (apply-maybe-async f args)))" "lisp"))
|
||||
(p "The bootstrapper emits " (code "apply_maybe_async") " as a Python " (code "async def") " that checks if the result is a coroutine and awaits it if so. Pure functions return immediately. IO primitives return coroutines that get awaited. " (strong "Zero overhead for pure calls") " — just an " (code "isinstance") " check.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 2 — Async trampoline")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 2 — Async trampoline")
|
||||
(p "The spec's trampoline loop resolves thunks synchronously. The Python bootstrapper emits an " (code "async def trampoline") " variant that can await thunks whose bodies contain IO calls. The trampoline structure is identical — only the " (code "await") " keyword is added.")
|
||||
(~docs/code :src (highlight "# Bootstrapper emits this for Python async target\nasync def trampoline(val):\n while isinstance(val, Thunk):\n val = await eval_expr(val.expr, val.env)\n return val" "python"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 3 — Aser as spec module")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 3 — Aser as spec module")
|
||||
(p "The " (code "_aser") " rendering mode (evaluate control flow, serialize HTML/components as SX source) should be specced as a module in " (code "render.sx") " alongside " (code "render-to-html") " and " (code "render-to-dom") ". It's currently hand-written Python because it predates the spec, but its logic is pure SX: walk the AST, eval certain forms, serialize others.")
|
||||
(p "Once aser is specced, the bootstrapper emits it with the same async adapter — IO calls within aser bodies get awaited transparently.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 4 — Delete hand-written evaluators")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 4 — Delete hand-written evaluators")
|
||||
(p "With the async adapter + specced aser, " (code "evaluator.py") " and " (code "async_eval.py") " become dead code. Delete them. All evaluation flows through the bootstrapped " (code "sx_ref.py") " with async adapter."))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
(~docs/section :title "Bootstrapper Changes" :id "bootstrapper"
|
||||
(p "The Python bootstrapper (" (code "bootstrap_py.py") ") gains a new emit mode: " (code "--async") ". This emits:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (code "async def eval_expr") " instead of " (code "def eval_expr"))
|
||||
(li (code "async def trampoline") " with " (code "await") " on thunk eval")
|
||||
(li (code "apply_maybe_async") " that awaits coroutine results")
|
||||
@@ -75,40 +75,40 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Migration Path" :id "migration"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Step")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Risk")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Step")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Risk")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "1")
|
||||
(td :class "px-3 py-2 text-stone-700" "Add async emit mode to bootstrap_py.py. Generate async_sx_ref.py alongside sx_ref.py.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Low — new file, nothing changes yet"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "2")
|
||||
(td :class "px-3 py-2 text-stone-700" "Run async_sx_ref.py in parallel with async_eval.py, compare outputs on every page render.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Low — shadow mode, no user impact"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "3")
|
||||
(td :class "px-3 py-2 text-stone-700" "Spec aser in render.sx. Bootstrap it. Shadow-compare with hand-written _aser.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Medium — aser has edge cases around OOB, fragments"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "4")
|
||||
(td :class "px-3 py-2 text-stone-700" "Switch page rendering to async_sx_ref.py. Keep async_eval.py as fallback.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Medium — production path changes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "1")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Add async emit mode to bootstrap_py.py. Generate async_sx_ref.py alongside sx_ref.py.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Low — new file, nothing changes yet"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "2")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Run async_sx_ref.py in parallel with async_eval.py, compare outputs on every page render.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Low — shadow mode, no user impact"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "3")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Spec aser in render.sx. Bootstrap it. Shadow-compare with hand-written _aser.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Medium — aser has edge cases around OOB, fragments"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "4")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Switch page rendering to async_sx_ref.py. Keep async_eval.py as fallback.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Medium — production path changes"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "5")
|
||||
(td :class "px-3 py-2 text-stone-700" "Delete evaluator.py and async_eval.py.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Low — once shadow confirms parity"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "5")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Delete evaluator.py and async_eval.py.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Low — once shadow confirms parity"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Principles
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "The spec is the single source of truth.") " All SX evaluation semantics live in .sx files. Host code implements platform primitives, not evaluation rules.")
|
||||
(li (strong "Async is a host concern, not a language concern.") " The spec is synchronous. The Python bootstrapper emits async wrappers. The JS bootstrapper emits sync code. The spec doesn't know or care.")
|
||||
(li (strong "Shadow-compare before switching.") " Every migration step runs both paths in parallel and asserts identical output. No big-bang cutover.")
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After convergence:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "One evaluator implementation (the spec), bootstrapped to every host")
|
||||
(li "Semantic changes made once in .sx, automatically propagated")
|
||||
(li "~2,000 lines of hand-written Python evaluator code deleted")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(defcomp ~plans/cek-reactive/plan-cek-reactive-content ()
|
||||
(~docs/page :title "Deref as Shift — CEK-Based Reactive DOM Renderer"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Phase A collapsed signals to plain dicts with zero platform primitives. "
|
||||
"Phase B replaces explicit effect wrapping in the reactive DOM renderer "
|
||||
"with implicit continuation capture: when " (code "deref") " encounters a signal "
|
||||
@@ -19,7 +19,7 @@
|
||||
(~docs/section :title "The Insight" :id "insight"
|
||||
|
||||
(p "Each reactive binding is a micro-computation:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "reactive-text") ": given signal value, set text node content to " (code "(str value)"))
|
||||
(li (code "reactive-attr") ": given signal value, set attribute to " (code "(str value)")))
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"javascript")))
|
||||
|
||||
(~docs/subsection :title "1b. run_js_sx.py — Update compile_ref_to_js"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Auto-add " (code "\"frames\"") " when " (code "\"cek\"") " in spec_mod_set (mirror Python " (code "bootstrap_py.py") ")")
|
||||
(li "Auto-add " (code "\"cek\"") " + " (code "\"frames\"") " when " (code "\"dom\"") " adapter included (CEK needed for reactive rendering)")
|
||||
(li "Use " (code "SPEC_MODULE_ORDER") " for ordering instead of " (code "sorted()"))
|
||||
@@ -78,7 +78,7 @@
|
||||
"python")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Rebootstrap JS: " (code "python3 bootstrap_js.py"))
|
||||
(li "Check output contains frame constructors + CEK step functions")
|
||||
(li "Run existing CEK Python tests: " (code "python3 run_cek_tests.py") " (should still pass)"))))
|
||||
@@ -179,7 +179,7 @@
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "4e. What stays unchanged"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "reactive-list") " — keyed reconciliation is complex; keep effect-based for now")
|
||||
(li (code "reactive-spread") " — spread tracking is complex; keep effect-based")
|
||||
(li (code "effect") ", " (code "computed") " — still needed for non-rendering side effects")
|
||||
@@ -193,7 +193,7 @@
|
||||
|
||||
(~docs/subsection :title "5a. test-cek-reactive.sx"
|
||||
(p "Tests:")
|
||||
(ol :class "list-decimal pl-6 mb-4 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-6 mb-4 space-y-1")
|
||||
(li (code "deref") " non-signal passes through (no shift)")
|
||||
(li (code "deref") " signal without reactive-reset: returns value, no subscription")
|
||||
(li (code "deref") " signal with reactive-reset: shifts, registers subscriber, update-fn called on change")
|
||||
@@ -212,7 +212,7 @@
|
||||
|
||||
(~docs/section :title "Step 6: Browser Demo" :id "step-6"
|
||||
(p "Demo showing:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Counter island with implicit reactivity (no explicit effects)")
|
||||
(li (code "(deref counter)") " in text position auto-updates")
|
||||
(li (code "(str \"count-\" (deref class-sig))") " in attr position auto-updates")
|
||||
@@ -226,7 +226,7 @@
|
||||
|
||||
(~docs/code :src (highlight "(str (deref first-name) \" \" (deref last-name))" "lisp"))
|
||||
|
||||
(ol :class "list-decimal pl-6 mb-6 space-y-3"
|
||||
(ol (~tw :tokens "list-decimal pl-6 mb-6 space-y-3")
|
||||
(li (strong "Initial render:") " First " (code "deref") " hits signal → shifts, captures "
|
||||
(code "(str [HOLE] \" \" (deref last-name))") ". Subscriber registered for "
|
||||
(code "first-name") ". Returns current value. Second " (code "deref")
|
||||
@@ -247,7 +247,7 @@
|
||||
|
||||
(~docs/section :title "Commit Strategy" :id "commits"
|
||||
|
||||
(ol :class "list-decimal pl-6 mb-4 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-6 mb-4 space-y-1")
|
||||
(li (strong "Commit 1:") " Bootstrap CEK to JS (Step 1) — mechanical, independent")
|
||||
(li (strong "Commit 2:") " ReactiveResetFrame + DerefFrame (Step 2) — new frame types")
|
||||
(li (strong "Commit 3:") " Deref-as-shift + adapter integration + tests (Steps 3-5) — the core change")
|
||||
@@ -259,33 +259,33 @@
|
||||
|
||||
(~docs/section :title "Files" :id "files"
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "File")
|
||||
(th :class "text-left pb-2 font-semibold" "Change")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "File")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Change")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/platform_js.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/platform_js.py")
|
||||
(td "SPEC_MODULES entries, PLATFORM_CEK_JS, CEK_FIXUPS_JS, SPEC_MODULE_ORDER"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/run_js_sx.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/run_js_sx.py")
|
||||
(td "compile_ref_to_js: has_cek, auto-inclusion, ordering, platform code"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/js.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/js.sx")
|
||||
(td "RENAMES for CEK predicate functions"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/bootstrap_py.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/bootstrap_py.py")
|
||||
(td "RENAMES for CEK predicates"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/frames.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/frames.sx")
|
||||
(td "ReactiveResetFrame, DerefFrame, has-reactive-reset-frame?, kont-capture-to-reactive-reset"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/cek.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/cek.sx")
|
||||
(td "step-sf-deref, reactive-shift-deref, deref in dispatch, ReactiveResetFrame in step-continue"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/adapter-dom.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/adapter-dom.sx")
|
||||
(td "*use-cek-reactive* flag, cek-reactive-attr, cek-reactive-text, conditional dispatch"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/test-cek-reactive.sx")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/test-cek-reactive.sx")
|
||||
(td (strong "New:") " continuation-based reactivity tests"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/run_cek_reactive_tests.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/run_cek_reactive_tests.py")
|
||||
(td (strong "New:") " Python test runner"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/sx/ref/sx_ref.py")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/sx/ref/sx_ref.py")
|
||||
(td "Rebootstrap (generated)"))
|
||||
(tr (td :class "pr-4 py-1 font-mono text-xs" "shared/static/scripts/sx-browser.js")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono text-xs") "shared/static/scripts/sx-browser.js")
|
||||
(td "Rebootstrap (generated)"))))))
|
||||
|
||||
;; =====================================================================
|
||||
@@ -294,7 +294,7 @@
|
||||
|
||||
(~docs/section :title "Risks" :id "risks"
|
||||
|
||||
(ol :class "list-decimal pl-6 mb-4 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-6 mb-4 space-y-2")
|
||||
(li (strong "Performance:") " CEK allocates a dict per step. Mitigated: opt-in flag, tree-walk remains default.")
|
||||
(li (strong "Multi-deref stale subscribers:") " Mitigated: sub-scope disposal before re-invocation.")
|
||||
(li (strong "Interaction with user shift/reset:") " " (code "kont-capture-to-reactive-reset")
|
||||
|
||||
@@ -18,45 +18,45 @@
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(p "What already exists and what's missing.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Capability")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Where")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Capability")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Where")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Deterministic serialization")
|
||||
(td :class "px-3 py-2 text-stone-700" "Partial — " (code "serialize(body, pretty=True)") " from AST, but no canonical normalization")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "parser.py:296-427"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component identity")
|
||||
(td :class "px-3 py-2 text-stone-700" "By name (" (code "~card") ") — names are mutable, server-local")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "types.py:157-180"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Bundle hashing")
|
||||
(td :class "px-3 py-2 text-stone-700" "SHA256 of all defs concatenated — per-bundle, not per-component")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "jinja_bridge.py:60-86"))
|
||||
(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") " — " (code "is_pure") " via transitive IO ref analysis")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "deps.sx, boundary.py"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Dependency graph")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete") " — " (code "Component.deps") " transitive closure")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "deps.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS infrastructure")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Exists") " — IPFSPin model, async upload tasks, CID tracking")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "models/federation.py, artdag/l1/tasks/"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Client component caching")
|
||||
(td :class "px-3 py-2 text-stone-700" "Hash-based localStorage — but keyed by bundle hash, not individual CID")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "boot.sx, helpers.py"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Content-addressed components")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not yet") " — no per-component CID, no IPFS resolution")
|
||||
(td :class "px-3 py-2 text-stone-600" "—"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Deterministic serialization")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Partial — " (code "serialize(body, pretty=True)") " from AST, but no canonical normalization")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "parser.py:296-427"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component identity")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "By name (" (code "~card") ") — names are mutable, server-local")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "types.py:157-180"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bundle hashing")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SHA256 of all defs concatenated — per-bundle, not per-component")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "jinja_bridge.py:60-86"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Purity verification")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete") " — " (code "is_pure") " via transitive IO ref analysis")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "deps.sx, boundary.py"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Dependency graph")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete") " — " (code "Component.deps") " transitive closure")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "deps.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS infrastructure")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Exists") " — IPFSPin model, async upload tasks, CID tracking")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "models/federation.py, artdag/l1/tasks/"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client component caching")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Hash-based localStorage — but keyed by bundle hash, not individual CID")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "boot.sx, helpers.py"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Content-addressed components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-red-700 font-medium") "Not yet") " — no per-component CID, no IPFS resolution")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "—"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Canonical Serialization
|
||||
@@ -64,16 +64,16 @@
|
||||
|
||||
(~docs/section :title "Phase 1: Canonical Serialization" :id "canonical-serialization"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "The foundation")
|
||||
(p :class "text-violet-800" "Same component must always produce the same bytes, regardless of original formatting, whitespace, or comment placement. Without this, content addressing is meaningless."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The foundation")
|
||||
(p (~tw :tokens "text-violet-800") "Same component must always produce the same bytes, regardless of original formatting, whitespace, or comment placement. Without this, content addressing is meaningless."))
|
||||
|
||||
(~docs/subsection :title "The Problem"
|
||||
(p "Currently " (code "serialize(body, pretty=True)") " produces readable SX source from the parsed AST. But serialization isn't fully canonical — it depends on the internal representation order, and there's no normalization pass. Two semantically identical components formatted differently would produce different hashes.")
|
||||
(p "We need a " (strong "canonical form") " that strips all variance:"))
|
||||
|
||||
(~docs/subsection :title "Canonical Form Rules"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Strip comments.") " Comments are parsing artifacts, not part of the AST. The serializer already ignores them (it works from the parsed tree), but any future comment-preserving parser must not affect canonical output.")
|
||||
(li (strong "Normalize whitespace.") " Single space between tokens, newline before each top-level form in a body. No trailing whitespace. No blank lines.")
|
||||
(li (strong "Sort keyword arguments alphabetically.") " In component calls: " (code "(~card :class \"x\" :title \"y\")") " not " (code "(~card :title \"y\" :class \"x\")") ". In dict literals: " (code "{:a 1 :b 2}") " not " (code "{:b 2 :a 1}") ".")
|
||||
@@ -93,13 +93,13 @@
|
||||
|
||||
(~docs/section :title "Phase 2: CID Computation" :id "cid-computation"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Every component gets a stable, unique content identifier. Same source → same CID, always. Different source → different CID, always."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Every component gets a stable, unique content identifier. Same source → same CID, always. Different source → different CID, always."))
|
||||
|
||||
(~docs/subsection :title "CID Format"
|
||||
(p "Use " (a :href "https://github.com/multiformats/cid" :class "text-violet-700 underline" "CIDv1") " with:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(p "Use " (a :href "https://github.com/multiformats/cid" (~tw :tokens "text-violet-700 underline") "CIDv1") " with:")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Hash function:") " SHA3-256 (already used by artdag for content addressing)")
|
||||
(li (strong "Codec:") " raw (the content is the canonical SX source bytes, not a DAG-PB wrapper)")
|
||||
(li (strong "Base encoding:") " base32lower for URL-safe representation (" (code "bafy...") " prefix)"))
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
(~docs/subsection :title "CID Stability"
|
||||
(p "A component's CID changes when and only when its " (strong "semantics") " change:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Reformatting the " (code ".sx") " source file → same AST → same canonical form → " (strong "same CID"))
|
||||
(li "Adding a comment → stripped by parser → same AST → " (strong "same CID"))
|
||||
(li "Changing a class name in the body → different AST → " (strong "different CID"))
|
||||
@@ -125,14 +125,14 @@
|
||||
|
||||
(~docs/section :title "Phase 3: Component Manifest" :id "manifest"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Metadata that travels with a CID — what a component needs, what it provides, whether it's safe to run. Enough information to resolve, validate, and render without fetching the source first."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Metadata that travels with a CID — what a component needs, what it provides, whether it's safe to run. Enough information to resolve, validate, and render without fetching the source first."))
|
||||
|
||||
(~docs/subsection :title "Manifest Structure"
|
||||
(~docs/code :src (highlight ";; Component manifest — published alongside the source\n(SxComponent\n :name \"~product-card\"\n :cid \"bafy...productcard\"\n :source-bytes 847\n :params (:title :price :image-url)\n :has-children true\n :pure true\n :deps (\n {:name \"~card\" :cid \"bafy...card\"}\n {:name \"~price-tag\" :cid \"bafy...pricetag\"}\n {:name \"~lazy-image\" :cid \"bafy...lazyimg\"})\n :css-atoms (:border :rounded :p-4 :text-sm :font-bold\n :text-green-700 :line-through :text-stone-400)\n :author \"https://rose-ash.com/apps/market\"\n :published \"2026-03-06T14:30:00Z\")" "lisp"))
|
||||
(p "Key fields:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code ":cid") " — content address of the canonical serialized source")
|
||||
(li (code ":deps") " — dependency CIDs, not just names. A consumer can recursively resolve the entire tree by CID without name ambiguity")
|
||||
(li (code ":pure") " — pre-computed purity flag. The consumer " (em "re-verifies") " this after fetching (never trust the manifest alone), but it enables fast rejection of IO-dependent components before downloading")
|
||||
@@ -150,13 +150,13 @@
|
||||
|
||||
(~docs/section :title "Phase 4: IPFS Storage & Resolution" :id "ipfs"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Components live on IPFS. Any browser can fetch them by CID. No origin server needed. No CDN. No DNS. The content network IS the distribution network."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Components live on IPFS. Any browser can fetch them by CID. No origin server needed. No CDN. No DNS. The content network IS the distribution network."))
|
||||
|
||||
(~docs/subsection :title "Server-Side: Publication"
|
||||
(p "On component registration (startup or hot-reload), the server:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Computes canonical form and CID")
|
||||
(li "Checks " (code "IPFSPin") " — if CID already pinned, skip (content can't have changed)")
|
||||
(li "Pins canonical source to IPFS (async Celery task, same pattern as artdag)")
|
||||
@@ -171,39 +171,39 @@
|
||||
(p "The cache-forever semantics are the key insight: because CIDs are content-addressed, a cached component " (strong "can never be stale") ". If the source changes, it gets a new CID. Old CIDs remain valid forever. There is no cache invalidation problem."))
|
||||
|
||||
(~docs/subsection :title "Resolution Cascade"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Lookup")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Latency")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "When")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Lookup")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Latency")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "When")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "1. Component env")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "(env-has? env cid)")
|
||||
(td :class "px-3 py-2 text-stone-600" "0ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "Already loaded this session"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "2. localStorage")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "localStorage[\"sx-cid:\" + cid]")
|
||||
(td :class "px-3 py-2 text-stone-600" "<1ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "Previously fetched, persists across sessions"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "3. Origin server")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "GET /sx/components?cid=bafy...")
|
||||
(td :class "px-3 py-2 text-stone-600" "~20ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "Same-origin component, not yet cached"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "4. IPFS gateway")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "GET https://gateway/ipfs/{cid}")
|
||||
(td :class "px-3 py-2 text-stone-600" "~200ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "Foreign component, federated content"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "5. Local IPFS node")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "ipfs cat {cid}")
|
||||
(td :class "px-3 py-2 text-stone-600" "~5ms")
|
||||
(td :class "px-3 py-2 text-stone-600" "User runs own IPFS node (power users)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "1. Component env")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "(env-has? env cid)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "0ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Already loaded this session"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "2. localStorage")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "localStorage[\"sx-cid:\" + cid]")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "<1ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Previously fetched, persists across sessions"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "3. Origin server")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "GET /sx/components?cid=bafy...")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~20ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Same-origin component, not yet cached"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "4. IPFS gateway")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "GET https://gateway/ipfs/{cid}")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~200ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Foreign component, federated content"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "5. Local IPFS node")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "ipfs cat {cid}")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~5ms")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "User runs own IPFS node (power users)")))))
|
||||
(p "Layer 5 is optional — checked between 2 and 3 if " (code "window.ipfs") " or a local gateway is detected. For most users, layers 1-4 cover all cases.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -212,9 +212,9 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Security Model" :id "security"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "The hard part")
|
||||
(p :class "text-violet-800" "Loading code from the network is the web's original sin. Content-addressed components are safe because of three structural guarantees — not policies, not trust, not sandboxes that can be escaped."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The hard part")
|
||||
(p (~tw :tokens "text-violet-800") "Loading code from the network is the web's original sin. Content-addressed components are safe because of three structural guarantees — not policies, not trust, not sandboxes that can be escaped."))
|
||||
|
||||
(~docs/subsection :title "Guarantee 1: Purity is Structural"
|
||||
(p "SX boundary enforcement isn't a runtime sandbox — it's a registration-time structural check. When a component is loaded from IPFS and parsed, " (code "compute_all_io_refs()") " walks its entire AST and transitive dependencies. If " (em "any") " node references an IO primitive, the component is classified as IO-dependent and " (strong "rejected for untrusted registration."))
|
||||
@@ -227,40 +227,40 @@
|
||||
|
||||
(~docs/subsection :title "Guarantee 3: Evaluation Limits"
|
||||
(p "Pure doesn't mean terminating. A component could contain an infinite loop or exponential recursion. SX evaluators enforce step limits:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Max eval steps:") " configurable per context. Untrusted components get a lower limit than local ones.")
|
||||
(li (strong "Max recursion depth:") " prevents stack exhaustion.")
|
||||
(li (strong "Max output size:") " prevents a component from producing gigabytes of DOM nodes."))
|
||||
(p "Exceeding any limit halts evaluation and returns an error node. The worst case is wasted CPU — never data exfiltration, never unauthorized IO."))
|
||||
|
||||
(~docs/subsection :title "Trust Tiers"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Tier")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Source")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Allowed")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Eval limits")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Tier")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Source")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Allowed")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Eval limits")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Local")
|
||||
(td :class "px-3 py-2 text-stone-700" "Server's own " (code ".sx") " files")
|
||||
(td :class "px-3 py-2 text-stone-700" "Pure + IO primitives + page helpers")
|
||||
(td :class "px-3 py-2 text-stone-600" "None (trusted)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Followed")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components from followed AP actors")
|
||||
(td :class "px-3 py-2 text-stone-700" "Pure only (IO rejected)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Standard limits"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Federated")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components from any IPFS source")
|
||||
(td :class "px-3 py-2 text-stone-700" "Pure only (IO rejected)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Strict limits"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Local")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Server's own " (code ".sx") " files")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pure + IO primitives + page helpers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None (trusted)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Followed")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components from followed AP actors")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pure only (IO rejected)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Standard limits"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Federated")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components from any IPFS source")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pure only (IO rejected)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Strict limits"))))))
|
||||
|
||||
(~docs/subsection :title "What Can Go Wrong"
|
||||
(p "Honest accounting of the attack surface:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Visual spoofing:") " A malicious component could render UI that looks like a login form. Mitigation: untrusted components render inside a visually distinct container with origin attribution.")
|
||||
(li (strong "CSS abuse:") " A component's CSS atoms could interfere with page layout. Mitigation: scoped CSS — untrusted components' classes are namespaced.")
|
||||
(li (strong "Resource exhaustion:") " A component could be expensive to evaluate. Mitigation: step limits, timeout, lazy rendering for off-screen components.")
|
||||
@@ -273,14 +273,14 @@
|
||||
|
||||
(~docs/section :title "Phase 6: Wire Format & Prefetch Integration" :id "wire-format"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Pages and SX responses reference components by CID. The prefetch system resolves them from the most efficient source. Components become location-independent."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Pages and SX responses reference components by CID. The prefetch system resolves them from the most efficient source. Components become location-independent."))
|
||||
|
||||
(~docs/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:")
|
||||
(~docs/code :src (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 "/sx/(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."))
|
||||
(p "The " (a :href "/sx/(etc.(plan.predictive-prefetch))" (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "SX Response Component Headers"
|
||||
(p "Currently, " (code "SX-Components") " header lists loaded component names. Extend to support CIDs:")
|
||||
@@ -298,9 +298,9 @@
|
||||
|
||||
(~docs/section :title "Phase 7: Sharing & Discovery" :id "sharing"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Servers publish component collections via AP. Other servers follow them. Like npm, but federated, content-addressed, and structurally safe."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Servers publish component collections via AP. Other servers follow them. Like npm, but federated, content-addressed, and structurally safe."))
|
||||
|
||||
(~docs/subsection :title "Component Registry as AP Actor"
|
||||
(p "Each server exposes a component registry actor:")
|
||||
@@ -315,7 +315,7 @@
|
||||
|
||||
(~docs/subsection :title "Name Resolution"
|
||||
(p "Names are human-friendly aliases for CIDs. The same name on different servers can refer to different components (different CIDs). Conflict resolution is simple:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Local wins:") " If the server defines " (code "~card") ", that definition takes precedence over any federated " (code "~card") ".")
|
||||
(li (strong "CID pinning:") " When referencing a federated component, pin the CID. " (code "(:name \"~card\" :cid \"bafy...abc\")") " — the name is informational, the CID is authoritative.")
|
||||
(li (strong "No global namespace:") " There is no \"npm\" that owns " (code "~card") ". Names are scoped to the server that defines them. CIDs are global."))))
|
||||
@@ -327,78 +327,78 @@
|
||||
(~docs/section :title "Spec Modules" :id "spec-modules"
|
||||
(p "Per the SX host architecture principle, all content-addressing logic is specced in " (code ".sx") " files and bootstrapped:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Spec module")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Functions")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Platform obligations")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Spec module")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Functions")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Platform obligations")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "canonical.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "canonical-serialize") ", " (code "canonical-number") ", " (code "escape-canonical"))
|
||||
(td :class "px-3 py-2 text-stone-600" "None — pure string operations"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "cid.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "component-cid") ", " (code "verify-cid") ", " (code "cid-to-string") ", " (code "parse-cid"))
|
||||
(td :class "px-3 py-2 text-stone-600" (code "sha3-256") ", " (code "encode-base32") ", " (code "encode-utf8")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "resolve.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "resolve-component-by-cid") ", " (code "resolve-deps-recursive") ", " (code "register-untrusted-component"))
|
||||
(td :class "px-3 py-2 text-stone-600" (code "local-storage-get/set") ", " (code "fetch-cid") ", " (code "register-component-source"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "canonical.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "canonical-serialize") ", " (code "canonical-number") ", " (code "escape-canonical"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None — pure string operations"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "cid.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "component-cid") ", " (code "verify-cid") ", " (code "cid-to-string") ", " (code "parse-cid"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") (code "sha3-256") ", " (code "encode-base32") ", " (code "encode-utf8")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "resolve.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "resolve-component-by-cid") ", " (code "resolve-deps-recursive") ", " (code "register-untrusted-component"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") (code "local-storage-get/set") ", " (code "fetch-cid") ", " (code "register-component-source"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Critical files
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Critical Files" :id "critical-files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Role")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/canonical.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Canonical serialization spec (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/cid.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "CID computation and verification spec (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/types.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Add " (code "cid") " and " (code "dep_cids") " to Component")
|
||||
(td :class "px-3 py-2 text-stone-600" "2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/jinja_bridge.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Add " (code "compute_all_cids()") " to registration lifecycle")
|
||||
(td :class "px-3 py-2 text-stone-600" "2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/models/federation.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFSPin records for component CIDs")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/resolve.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client-side CID resolution cascade (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "CIDs in page registry, " (code "/sx/components?cid=") " endpoint")
|
||||
(td :class "px-3 py-2 text-stone-600" "6"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/orchestration.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "CID-aware prefetch in resolution cascade")
|
||||
(td :class "px-3 py-2 text-stone-600" "6"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/activitypub.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Component registry actor, Webfinger extension")
|
||||
(td :class "px-3 py-2 text-stone-600" "7"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/boundary.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Trust tier enforcement for untrusted components")
|
||||
(td :class "px-3 py-2 text-stone-600" "5"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/canonical.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Canonical serialization spec (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/cid.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CID computation and verification spec (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/types.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Add " (code "cid") " and " (code "dep_cids") " to Component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/jinja_bridge.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Add " (code "compute_all_cids()") " to registration lifecycle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/models/federation.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFSPin records for component CIDs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/resolve.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client-side CID resolution cascade (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/helpers.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CIDs in page registry, " (code "/sx/components?cid=") " endpoint")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "6"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/orchestration.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CID-aware prefetch in resolution cascade")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "6"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/activitypub.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component registry actor, Webfinger extension")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "7"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/boundary.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Trust tier enforcement for untrusted components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "5"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Relationship
|
||||
@@ -406,13 +406,13 @@
|
||||
|
||||
(~docs/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 "/sx/(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 "/sx/(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 "/sx/(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.")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (a :href "/sx/(etc.(plan.sx-activity))" (~tw :tokens "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 "/sx/(etc.(plan.predictive-prefetch))" (~tw :tokens "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 "/sx/(etc.(plan.isomorphic-architecture))" (~tw :tokens "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)."))))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Depends on: ") "deps.sx (complete), boundary enforcement (complete), IPFS infrastructure (exists in artdag, needs wiring to web platform)."))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Predictive Component Prefetching
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
(p "Every served SX endpoint should point back to its spec. The spec CIDs identify the exact evaluator, renderer, parser, and primitives that produced the output. This makes every endpoint " (strong "fully executable") " — anyone with the CIDs can independently reproduce the result.")
|
||||
(p "But evaluating spec files from source on every cold start is wasteful. The specs are pure — same source always produces the same evaluated environment. So we can serialize the " (em "evaluated") " environment as a content-addressed image: all defcomps, defmacros, bound symbols, resolved closures frozen into a single artifact. The image CID is a function of its contents. Load the image, skip evaluation, get the same result.")
|
||||
(p "The chain becomes:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Served page") " → CID of the spec that defines its semantics")
|
||||
(li (strong "Spec CID") " → the evaluator, renderer, parser, primitives that any conforming host can execute")
|
||||
(li (strong "Image CID") " → the pre-evaluated environment, a cache of (2) that any conforming host can deserialize"))
|
||||
(p "The spec is the truth. The image is a verified cache. The bootstrapper that compiled the spec for a particular host is an implementation detail — irrelevant to the content address.")
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "Prior art")
|
||||
(p :class "text-violet-800" (a :href "https://github.com/KinaKnowledge/juno-lang" :class "underline" "Juno") " — a self-hosted Lisp-to-JS compiler — implements image persistence: serialize a running environment, restore it later, even bundle it as a standalone HTML document. Their Seedling IDE saves/restores entire development sessions as images. SX can do this more rigorously because our images are content-addressed (Juno's are not) and our components are boundary-enforced pure (Juno's are not).")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "Prior art")
|
||||
(p (~tw :tokens "text-violet-800") (a :href "https://github.com/KinaKnowledge/juno-lang" (~tw :tokens "underline") "Juno") " — a self-hosted Lisp-to-JS compiler — implements image persistence: serialize a running environment, restore it later, even bundle it as a standalone HTML document. Their Seedling IDE saves/restores entire development sessions as images. SX can do this more rigorously because our images are content-addressed (Juno's are not) and our components are boundary-enforced pure (Juno's are not).")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; What gets serialized
|
||||
@@ -25,36 +25,36 @@
|
||||
(~docs/section :title "What Gets Serialized" :id "what"
|
||||
(p "An environment image is a snapshot of everything produced by evaluating the spec files. Not the source — the result.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Category")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Contents")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Source")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Category")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Contents")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Source")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Components")
|
||||
(td :class "px-3 py-2 text-stone-700" "All " (code "defcomp") " definitions — name, params, body AST, closure bindings, CID, deps, css_classes")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "Service .sx files + shared/sx/templates/"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Macros")
|
||||
(td :class "px-3 py-2 text-stone-700" "All " (code "defmacro") " definitions — name, params, body AST, closure")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "Spec files"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Bindings")
|
||||
(td :class "px-3 py-2 text-stone-700" "Top-level " (code "define") " values — constants, lookup tables, configuration")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "Spec files + service .sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitives")
|
||||
(td :class "px-3 py-2 text-stone-700" "Registry of pure primitive names (not implementations — those are host-specific)")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "primitives.sx, boundary.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Spec provenance")
|
||||
(td :class "px-3 py-2 text-stone-700" "CIDs of the spec files that produced this image")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "eval.sx, render.sx, parser.sx, ...")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "All " (code "defcomp") " definitions — name, params, body AST, closure bindings, CID, deps, css_classes")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "Service .sx files + shared/sx/templates/"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Macros")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "All " (code "defmacro") " definitions — name, params, body AST, closure")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "Spec files"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bindings")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Top-level " (code "define") " values — constants, lookup tables, configuration")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "Spec files + service .sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitives")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Registry of pure primitive names (not implementations — those are host-specific)")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "primitives.sx, boundary.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Spec provenance")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CIDs of the spec files that produced this image")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "eval.sx, render.sx, parser.sx, ...")))))
|
||||
|
||||
(p "Notably " (strong "absent") " from the image:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "IO primitive implementations") " — these are host-specific. The image records their " (em "names") " (for boundary enforcement) but not their code.")
|
||||
(li (strong "Page helpers") " — same reason. " (code "fetch-data") ", " (code "app-url") " etc. are registered by the host app at startup.")
|
||||
(li (strong "Runtime state") " — no request context, no DB connections, no session data. The image is a pure function's result, not a running process snapshot.")))
|
||||
@@ -69,7 +69,7 @@
|
||||
(~docs/code :src (highlight "(sx-image\n :version 1\n :spec-cids {:eval \"bafy...eval\"\n :render \"bafy...render\"\n :parser \"bafy...parser\"\n :primitives \"bafy...prims\"\n :boundary \"bafy...boundary\"\n :signals \"bafy...signals\"}\n\n :components (\n (defcomp ~plans/environment-images/card (&key title subtitle &rest children)\n (div :class \"card\" (h2 title) (when subtitle (p subtitle)) children))\n (defcomp ~plans/environment-images/nav (&key items current)\n (nav :class \"nav\" (map (fn (item) ...) items)))\n ;; ... all registered components\n )\n\n :macros (\n (defmacro when (test &rest body)\n (list 'if test (cons 'begin body) nil))\n ;; ... all macros\n )\n\n :bindings (\n (define void-elements (list \"area\" \"base\" \"br\" \"col\" ...))\n (define boolean-attrs (list \"checked\" \"disabled\" ...))\n ;; ... all top-level defines\n )\n\n :primitive-names (\"str\" \"+\" \"-\" \"*\" \"/\" \"=\" \"<\" \">\" ...)\n :io-names (\"fetch-data\" \"call-action\" \"app-url\" ...))" "lisp"))
|
||||
|
||||
(p "The " (code ":spec-cids") " field is the key. It links this image back to the exact spec that produced it. Anyone can verify the image by:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Fetch the spec files by CID")
|
||||
(li "Evaluate them with a conforming evaluator")
|
||||
(li "Serialize the resulting environment")
|
||||
@@ -98,25 +98,25 @@
|
||||
(~docs/code :src (highlight "HTTP/1.1 200 OK\nContent-Type: text/html\nSX-Spec: bafy...eval,bafy...render,bafy...parser,bafy...prims\nSX-Image: bafy...image\nSX-Page-Components: ~plans/environment-images/card:bafy...card,~plans/environment-images/nav:bafy...nav" "http"))
|
||||
|
||||
(p "Three levels of verification:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Level")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What you verify")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Trust assumption")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Level")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What you verify")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Trust assumption")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Component")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fetch " (code "~plans/environment-images/card") " by CID, verify hash")
|
||||
(td :class "px-3 py-2 text-stone-600" "Trust the evaluator"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Image")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fetch image by CID, deserialize, re-render page")
|
||||
(td :class "px-3 py-2 text-stone-600" "Trust the image producer"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Spec")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fetch specs by CID, re-evaluate, compare image CID")
|
||||
(td :class "px-3 py-2 text-stone-600" "Trust only the hash function")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fetch " (code "~plans/environment-images/card") " by CID, verify hash")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Trust the evaluator"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Image")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fetch image by CID, deserialize, re-render page")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Trust the image producer"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fetch specs by CID, re-evaluate, compare image CID")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Trust only the hash function")))))
|
||||
|
||||
(p "Level 3 is the nuclear option — full independent verification from source. It's expensive but proves the entire chain. Most consumers will operate at level 1 (component verification) or level 2 (image verification)."))
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
(p "The practical motivation: evaluating all spec files + service components on every server restart is slow. An image eliminates this.")
|
||||
|
||||
(~docs/subsection :title "Server Startup"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li "Check if a cached image exists for the current spec CIDs")
|
||||
(li "If yes: deserialize the image (fast — parsing a single file, no evaluation)")
|
||||
(li "If no: evaluate spec files from source, build image, cache it")
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
(~docs/subsection :title "Client Boot"
|
||||
(p "The client already caches component definitions in localStorage keyed by bundle hash. Images extend this: cache the entire evaluated environment, not just individual components.")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Page ships " (code "SX-Image") " header with image CID")
|
||||
(li "Client checks localStorage for " (code "sx-image:{cid}"))
|
||||
(li "If hit: deserialize and boot (no component-by-component parsing)")
|
||||
@@ -157,9 +157,9 @@
|
||||
|
||||
(p "This document is its own CID. Pin it to IPFS and it's a permanent, executable, verifiable application. No origin server, no CDN, no DNS. The content network is the deployment target.")
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4 mt-4"
|
||||
(p :class "text-amber-900 font-medium" "Juno comparison")
|
||||
(p :class "text-amber-800" "Juno's Seedling IDE already does this — export a running environment as a standalone HTML file. But their images are opaque JavaScript blobs serialized by the runtime. SX images are " (strong "s-expressions") " — parseable, inspectable, content-addressable. You can diff two SX images and see exactly what changed. You can extract a single component from an image by CID. You can merge images from different sources by composing their component lists. The format IS the tooling.")))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-amber-900 font-medium") "Juno comparison")
|
||||
(p (~tw :tokens "text-amber-800") "Juno's Seedling IDE already does this — export a running environment as a standalone HTML file. But their images are opaque JavaScript blobs serialized by the runtime. SX images are " (strong "s-expressions") " — parseable, inspectable, content-addressable. You can diff two SX images and see exactly what changed. You can extract a single component from an image by CID. You can merge images from different sources by composing their component lists. The format IS the tooling.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Namespace scoping
|
||||
@@ -172,29 +172,29 @@
|
||||
|
||||
(p "Resolution: " (code "market/~plans/environment-images/product-card") " → look in market image first, then fall through to the shared image (via " (code ":extends") "). Each service produces its own image, layered on top of the shared base.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Image")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Contents")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Extends")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Image")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Contents")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Extends")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "bafy...shared")
|
||||
(td :class "px-3 py-2 text-stone-700" "~plans/environment-images/card, ~plans/environment-images/nav, ~nav-data/section-nav, ~docs/page, ~docs/code — shared components from " (code "shared/sx/templates/"))
|
||||
(td :class "px-3 py-2 text-stone-600" "None (root)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "bafy...blog")
|
||||
(td :class "px-3 py-2 text-stone-700" "~shared:cards/post-card, ~post-body, ~tag-list — blog-specific from " (code "blog/sx/"))
|
||||
(td :class "px-3 py-2 text-stone-600" "bafy...shared"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "bafy...market")
|
||||
(td :class "px-3 py-2 text-stone-700" "~plans/environment-images/product-card, ~plans/environment-images/price-tag, ~shared:fragments/cart-mini — market-specific from " (code "market/sx/"))
|
||||
(td :class "px-3 py-2 text-stone-600" "bafy...shared"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "bafy...sx-docs")
|
||||
(td :class "px-3 py-2 text-stone-700" "~docs/section, ~examples/source, plans, essays — sx docs from " (code "sx/sx/"))
|
||||
(td :class "px-3 py-2 text-stone-600" "bafy...shared")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "bafy...shared")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~plans/environment-images/card, ~plans/environment-images/nav, ~nav-data/section-nav, ~docs/page, ~docs/code — shared components from " (code "shared/sx/templates/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None (root)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "bafy...blog")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~shared:cards/post-card, ~post-body, ~tag-list — blog-specific from " (code "blog/sx/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bafy...shared"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "bafy...market")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~plans/environment-images/product-card, ~plans/environment-images/price-tag, ~shared:fragments/cart-mini — market-specific from " (code "market/sx/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bafy...shared"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "bafy...sx-docs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~docs/section, ~examples/source, plans, essays — sx docs from " (code "sx/sx/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bafy...shared")))))
|
||||
|
||||
(p "The " (code ":extends") " field is a CID, not a name. Image composition is content-addressed: changing the shared image produces a new shared CID, which invalidates all service images that extend it. Exactly the right cascading behavior."))
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 1: Image Serialization"
|
||||
(p "Spec module " (code "image.sx") " — serialize and deserialize evaluated environments.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "serialize-environment") " — walk the env, extract components/macros/bindings, produce " (code "(sx-image ...)") " form")
|
||||
(li (code "deserialize-image") " — parse image, reconstitute components/macros/bindings into env")
|
||||
(li (code "image-cid") " — canonical-serialize the image form, hash → CID")
|
||||
@@ -225,14 +225,14 @@
|
||||
|
||||
(~docs/subsection :title "Phase 2: Spec Provenance"
|
||||
(p "Compute CIDs for all spec files at startup. Attach to environment metadata.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Hash each spec file's canonical source at load time")
|
||||
(li "Store in env metadata as " (code ":spec-cids") " dict")
|
||||
(li "Include in image serialization")))
|
||||
|
||||
(~docs/subsection :title "Phase 3: Server-Side Caching"
|
||||
(p "Cache images on disk keyed by spec CIDs. Skip evaluation on warm restart.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "On startup: compute spec CIDs → derive expected image CID → check cache")
|
||||
(li "Cache hit: deserialize (parse only, no eval)")
|
||||
(li "Cache miss: evaluate specs, serialize image, write cache")
|
||||
@@ -240,7 +240,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 4: Client Images"
|
||||
(p "Ship image CID in response headers. Client caches full env in localStorage.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "SX-Image") " response header with image CID")
|
||||
(li "Client boot checks localStorage for cached image")
|
||||
(li "Cache hit: deserialize, skip per-component fetch/parse")
|
||||
@@ -248,7 +248,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 5: Standalone Export"
|
||||
(p "Generate self-contained HTML with inlined image. Pin to IPFS.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Inline " (code "(sx-image ...)") " as " (code "<script type=\"text/sx-image\">"))
|
||||
(li "Inline page definitions as " (code "<script type=\"text/sx-pages\">"))
|
||||
(li "Include sx-ref.js (or link to its CID)")
|
||||
@@ -256,7 +256,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 6: Namespaced Images"
|
||||
(p "Per-service images with " (code ":extends") " for layered composition.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Shared image: components from " (code "shared/sx/templates/"))
|
||||
(li "Service images: extend shared, add service-specific components")
|
||||
(li "Resolution: service image → shared image → primitives")
|
||||
@@ -268,37 +268,37 @@
|
||||
|
||||
(~docs/section :title "Dependencies" :id "dependencies"
|
||||
(p "What must exist before this plan can execute:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Dependency")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Plan")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Dependency")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Plan")))
|
||||
(tbody
|
||||
(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 "/sx/(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 "/sx/(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"))
|
||||
(td :class "px-3 py-2 text-stone-600" "deps.sx + boundary.py"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Self-hosting spec")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete"))
|
||||
(td :class "px-3 py-2 text-stone-600" "eval.sx, render.sx, parser.sx, ..."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Self-hosting bootstrappers")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete"))
|
||||
(td :class "px-3 py-2 text-stone-600" "py.sx, js.sx — G0 == G1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS infrastructure")
|
||||
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Exists"))
|
||||
(td :class "px-3 py-2 text-stone-600" "artdag L1/L2, IPFSPin model")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Canonical serialization")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-red-700 font-medium") "Not started"))
|
||||
(td (~tw :tokens "px-3 py-2") (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "text-violet-700 underline") "Content-Addressed Components") " Phase 1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component CIDs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-red-700 font-medium") "Not started"))
|
||||
(td (~tw :tokens "px-3 py-2") (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "text-violet-700 underline") "Content-Addressed Components") " Phase 2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Purity verification")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "deps.sx + boundary.py"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Self-hosting spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "eval.sx, render.sx, parser.sx, ..."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Self-hosting bootstrappers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Complete"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "py.sx, js.sx — G0 == G1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS infrastructure")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (span (~tw :tokens "text-green-700 font-medium") "Exists"))
|
||||
(td (~tw :tokens "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 "/sx/(etc.(plan.content-addressed-components))" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/sx/(etc.(plan.sx-activity))" :class "underline" "SX-Activity") " (serverless applications on IPFS).")))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Builds on: ") (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "underline") "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" (~tw :tokens "underline") "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/sx/(etc.(plan.sx-activity))" (~tw :tokens "underline") "SX-Activity") " (serverless applications on IPFS).")))))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(defcomp ~plans/foundations/plan-foundations-content ()
|
||||
(~docs/page :title "Foundations \u2014 The Computational Floor"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Scoped effects unify everything SX does. But they are not the bottom. "
|
||||
"Beneath them is a hierarchy of increasingly fundamental primitives, "
|
||||
"terminating at three irreducible things: an expression, an environment, and a continuation. "
|
||||
@@ -15,7 +15,7 @@
|
||||
;; The hierarchy
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "The Hierarchy")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "The Hierarchy")
|
||||
|
||||
(p "Every layer is definable in terms of the one below. "
|
||||
"No layer can be decomposed without the layer beneath it.")
|
||||
@@ -35,62 +35,62 @@
|
||||
;; What we built (status)
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "What We Built")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "What We Built")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Layer")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Spec files")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "What it provides")
|
||||
(th :class "text-left pb-2 font-semibold" "Tests")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Layer")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Spec files")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "What it provides")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Tests")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "0 \u2014 CEK")
|
||||
(td :class "pr-4 font-mono text-xs" "cek.sx, frames.sx")
|
||||
(td :class "pr-4" "Explicit step function, 20+ frame types, cek-call dispatch, CEK-native HO forms")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "0 \u2014 CEK")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "cek.sx, frames.sx")
|
||||
(td (~tw :tokens "pr-4") "Explicit step function, 20+ frame types, cek-call dispatch, CEK-native HO forms")
|
||||
(td "43 CEK + 26 reactive"))
|
||||
(tr (td :class "pr-4 py-1" "1 \u2014 Continuations")
|
||||
(td :class "pr-4 font-mono text-xs" "continuations.sx, callcc.sx")
|
||||
(td :class "pr-4" "shift/reset (delimited), call/cc (full), ReactiveResetFrame + DerefFrame")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "1 \u2014 Continuations")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "continuations.sx, callcc.sx")
|
||||
(td (~tw :tokens "pr-4") "shift/reset (delimited), call/cc (full), ReactiveResetFrame + DerefFrame")
|
||||
(td "Continuation tests"))
|
||||
(tr (td :class "pr-4 py-1" "2 \u2014 Effect signatures")
|
||||
(td :class "pr-4 font-mono text-xs" "boundary.sx, eval.sx")
|
||||
(td :class "pr-4" ":effects annotations on define, boundary enforcement at startup")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "2 \u2014 Effect signatures")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "boundary.sx, eval.sx")
|
||||
(td (~tw :tokens "pr-4") ":effects annotations on define, boundary enforcement at startup")
|
||||
(td "Boundary validation"))
|
||||
(tr (td :class "pr-4 py-1" "3 \u2014 Scoped effects")
|
||||
(td :class "pr-4 font-mono text-xs" "eval.sx, adapters")
|
||||
(td :class "pr-4" "scope/provide/context/emit!/emitted, scope-push!/scope-pop!")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "3 \u2014 Scoped effects")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "eval.sx, adapters")
|
||||
(td (~tw :tokens "pr-4") "scope/provide/context/emit!/emitted, scope-push!/scope-pop!")
|
||||
(td "Scope integration"))
|
||||
(tr (td :class "pr-4 py-1" "4 \u2014 Patterns")
|
||||
(td :class "pr-4 font-mono text-xs" "signals.sx, adapter-dom.sx, engine.sx")
|
||||
(td :class "pr-4" "signal/deref/computed/effect/batch, island/lake, spread/collect")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4 \u2014 Patterns")
|
||||
(td (~tw :tokens "pr-4 font-mono text-xs") "signals.sx, adapter-dom.sx, engine.sx")
|
||||
(td (~tw :tokens "pr-4") "signal/deref/computed/effect/batch, island/lake, spread/collect")
|
||||
(td "20 signal + 26 CEK reactive")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Layer 0: The CEK machine
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Layer 0: The CEK Machine")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Layer 0: The CEK Machine")
|
||||
|
||||
(p "The CEK machine (Felleisen & Friedman, 1986) is a small-step evaluator with three registers:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Register")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Name")
|
||||
(th :class "text-left pb-2 font-semibold" "Meaning")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Register")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Name")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Meaning")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-mono" "C") (td :class "pr-4" "Control") (td "The expression being evaluated"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "E") (td :class "pr-4" "Environment") (td "The bindings in scope \u2014 names to values"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "K") (td :class "pr-4" "Kontinuation") (td "What to do with the result")))))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "C") (td (~tw :tokens "pr-4") "Control") (td "The expression being evaluated"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "E") (td (~tw :tokens "pr-4") "Environment") (td "The bindings in scope \u2014 names to values"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "K") (td (~tw :tokens "pr-4") "Kontinuation") (td "What to do with the result")))))
|
||||
|
||||
(p "Every step of evaluation is a transition: take the current (C, E, K), "
|
||||
"produce a new (C\u2032, E\u2032, K\u2032). That's it. That's all computation is.")
|
||||
|
||||
(p "Can you remove any register?")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Remove C") " \u2014 nothing to evaluate. No computation.")
|
||||
(li (strong "Remove E") " \u2014 names are meaningless. Lose abstraction. "
|
||||
"Every value must be literal, every function must be closed. No variables, no closures, no reuse.")
|
||||
@@ -99,13 +99,13 @@
|
||||
|
||||
(p "Three things, all necessary, none decomposable further.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "CEK in SX")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "CEK in SX")
|
||||
|
||||
(p "The CEK machine is the default evaluator on both client (JS) and server (Python). "
|
||||
"Every " (code "eval-expr") " call goes through " (code "cek-run") ". "
|
||||
"The spec lives in two files:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "frames.sx") " \u2014 20+ frame types (IfFrame, ArgFrame, MapFrame, ReactiveResetFrame, ...)")
|
||||
(li (code "cek.sx") " \u2014 step function, run loop, special form handlers, HO form handlers, cek-call"))
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
"It replaces the old " (code "invoke") " shim \u2014 SX lambdas go through " (code "cek-run") ", "
|
||||
"native callables through " (code "apply") ". One calling convention, bootstrapped identically to every host.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "CEK-native higher-order forms")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "CEK-native higher-order forms")
|
||||
|
||||
(p "All higher-order forms step element-by-element through the CEK machine:")
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
;; Layer 1: Delimited continuations
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Layer 1: Delimited Continuations")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Layer 1: Delimited Continuations")
|
||||
|
||||
(p "Delimited continuations (Felleisen 1988, Danvy & Filinski 1990) "
|
||||
"expose the K register as a first-class value:")
|
||||
@@ -149,7 +149,7 @@
|
||||
(code "shift") " says: \"give me everything between here and that boundary as a callable function.\" "
|
||||
"The captured continuation " (em "is") " a slice of the K register.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "Deref as shift")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "Deref as shift")
|
||||
|
||||
(p "The reactive payoff. " (code "deref") " inside a " (code "reactive-reset") " boundary "
|
||||
"is shift/reset applied to signals:")
|
||||
@@ -167,7 +167,7 @@
|
||||
(p "No explicit " (code "effect()") " wrapping needed. "
|
||||
"The continuation capture IS the subscription mechanism.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "The Filinski embedding")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "The Filinski embedding")
|
||||
|
||||
(p "Filinski (1994) proved that " (code "shift/reset") " can encode "
|
||||
(em "any") " monadic effect. State, exceptions, nondeterminism, I/O, "
|
||||
@@ -179,11 +179,11 @@
|
||||
;; The floor proof
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Why Layer 0 Is the Floor")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Why Layer 0 Is the Floor")
|
||||
|
||||
(p "Two independent arguments:")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "1. Church\u2013Turing")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "1. Church\u2013Turing")
|
||||
|
||||
(p "The Church\u2013Turing thesis: any effectively computable function can be computed by "
|
||||
"a Turing machine (equivalently, by the lambda calculus, equivalently, by the CEK machine). "
|
||||
@@ -193,11 +193,11 @@
|
||||
(p "This means CEK captures " (em "all") " computation. Adding more primitives doesn't let you "
|
||||
"compute anything new. It only changes what's " (em "convenient") " to express.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "2. Irreducibility of C, E, K")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "2. Irreducibility of C, E, K")
|
||||
|
||||
(p "The three registers are independently necessary:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "C without E") " = combinatory logic. Turing-complete but inhumane. "
|
||||
"No named bindings \u2014 everything via S, K, I combinators.")
|
||||
(li (strong "C without K") " = single expression evaluation. "
|
||||
@@ -213,47 +213,47 @@
|
||||
;; Digging deeper: what's next
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Progress")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Progress")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Step")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "What")
|
||||
(th :class "text-left pb-2 font-semibold" "Status")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Step")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "What")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Status")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "1")
|
||||
(td :class "pr-4" "Serializable CEK state")
|
||||
(td :class "text-emerald-600 font-semibold" "\u2714 Done \u2014 freeze-scope, freeze-signal, freeze-to-sx, thaw-from-sx"))
|
||||
(tr (td :class "pr-4 py-1" "2")
|
||||
(td :class "pr-4" "CEK stepping debugger")
|
||||
(td :class "text-emerald-600 font-semibold" "\u2714 Done \u2014 live island with reactive code view, DOM preview"))
|
||||
(tr (td :class "pr-4 py-1" "3")
|
||||
(td :class "pr-4" "Content-addressed computation")
|
||||
(td :class "text-emerald-600 font-semibold" "\u2714 Done \u2014 content-hash, freeze-to-cid, thaw-from-cid"))
|
||||
(tr (td :class "pr-4 py-1" "3.5")
|
||||
(td :class "pr-4" "Data representations")
|
||||
(td :class "text-stone-400" "Planned — byte buffers + typed structs"))
|
||||
(tr (td :class "pr-4 py-1" "3.7")
|
||||
(td :class "pr-4" "Verified components")
|
||||
(td :class "text-stone-400" "Planned — content-addressed UI trust"))
|
||||
(tr (td :class "pr-4 py-1" "4")
|
||||
(td :class "pr-4" "Concurrent CEK")
|
||||
(td :class "text-amber-600 font-semibold" "Spec complete — implementation next"))
|
||||
(tr (td :class "pr-4 py-1" "5")
|
||||
(td :class "pr-4" "Linear effects")
|
||||
(td :class "text-stone-400" "Future")))))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "1")
|
||||
(td (~tw :tokens "pr-4") "Serializable CEK state")
|
||||
(td (~tw :tokens "text-emerald-600 font-semibold") "\u2714 Done \u2014 freeze-scope, freeze-signal, freeze-to-sx, thaw-from-sx"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "2")
|
||||
(td (~tw :tokens "pr-4") "CEK stepping debugger")
|
||||
(td (~tw :tokens "text-emerald-600 font-semibold") "\u2714 Done \u2014 live island with reactive code view, DOM preview"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "3")
|
||||
(td (~tw :tokens "pr-4") "Content-addressed computation")
|
||||
(td (~tw :tokens "text-emerald-600 font-semibold") "\u2714 Done \u2014 content-hash, freeze-to-cid, thaw-from-cid"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "3.5")
|
||||
(td (~tw :tokens "pr-4") "Data representations")
|
||||
(td (~tw :tokens "text-stone-400") "Planned — byte buffers + typed structs"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "3.7")
|
||||
(td (~tw :tokens "pr-4") "Verified components")
|
||||
(td (~tw :tokens "text-stone-400") "Planned — content-addressed UI trust"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4")
|
||||
(td (~tw :tokens "pr-4") "Concurrent CEK")
|
||||
(td (~tw :tokens "text-amber-600 font-semibold") "Spec complete — implementation next"))
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "5")
|
||||
(td (~tw :tokens "pr-4") "Linear effects")
|
||||
(td (~tw :tokens "text-stone-400") "Future")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Step 3.5: Data Representations
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Step 3.5: Data Representations")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Step 3.5: Data Representations")
|
||||
|
||||
(p "Two primitives that sit below concurrency but above the raw CEK machine. "
|
||||
"Both are about how values are represented — enabling work that's currently host-only.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.5a Byte Buffers")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.5a Byte Buffers")
|
||||
|
||||
(p "Fixed-size mutable byte arrays. A small primitive surface that unlocks "
|
||||
"binary protocol parsing, image headers, wire formats, and efficient CID computation:")
|
||||
@@ -274,7 +274,7 @@
|
||||
|
||||
(p "Primitives:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "make-buffer") " — allocate N zero-filled bytes")
|
||||
(li (code "buffer-read-u8/u16/u32") ", " (code "buffer-read-i8/i16/i32") " — typed reads at offset")
|
||||
(li (code "buffer-write-u8!/u16!/u32!") " — typed writes at offset (big-endian default)")
|
||||
@@ -285,25 +285,25 @@
|
||||
|
||||
(p "Host mapping:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Primitive")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "JavaScript")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Python")
|
||||
(th :class "text-left pb-2 font-semibold" "OCaml")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Primitive")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "JavaScript")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Python")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "OCaml")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-mono" "make-buffer")
|
||||
(td :class "pr-4" "new ArrayBuffer(n)")
|
||||
(td :class "pr-4" "bytearray(n)")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "make-buffer")
|
||||
(td (~tw :tokens "pr-4") "new ArrayBuffer(n)")
|
||||
(td (~tw :tokens "pr-4") "bytearray(n)")
|
||||
(td "Bytes.create n"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "buffer-read-u32")
|
||||
(td :class "pr-4" "DataView.getUint32")
|
||||
(td :class "pr-4" "struct.unpack_from")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "buffer-read-u32")
|
||||
(td (~tw :tokens "pr-4") "DataView.getUint32")
|
||||
(td (~tw :tokens "pr-4") "struct.unpack_from")
|
||||
(td "Bytes.get_int32_be"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "buffer-slice")
|
||||
(td :class "pr-4" "new Uint8Array(buf, off, len)")
|
||||
(td :class "pr-4" "memoryview(buf)[off:off+len]")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "buffer-slice")
|
||||
(td (~tw :tokens "pr-4") "new Uint8Array(buf, off, len)")
|
||||
(td (~tw :tokens "pr-4") "memoryview(buf)[off:off+len]")
|
||||
(td "Bytes.sub buf off len")))))
|
||||
|
||||
(p "Byte buffers connect directly to content addressing (Step 3). "
|
||||
@@ -311,7 +311,7 @@
|
||||
"With buffers, the hash input can be a canonical binary representation — "
|
||||
"deterministic, compact, and consistent across hosts.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.5b Typed Structs")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.5b Typed Structs")
|
||||
|
||||
(p "Named product types with fixed fields. On interpreted hosts, structs are syntactic sugar over dicts. "
|
||||
"On compiled hosts (OCaml, Rust), they compile to unboxed records — no dict overhead, "
|
||||
@@ -332,7 +332,7 @@
|
||||
|
||||
(p "What " (code "defstruct") " generates:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "make-point") " — constructor (arity-checked)")
|
||||
(li (code "point?") " — type predicate")
|
||||
(li (code "point-x") ", " (code "point-y") " — field accessors")
|
||||
@@ -340,21 +340,21 @@
|
||||
|
||||
(p "The performance difference matters for compiled hosts:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Operation")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Dict (current)")
|
||||
(th :class "text-left pb-2 font-semibold" "Struct (compiled)")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Operation")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Dict (current)")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Struct (compiled)")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "Field access")
|
||||
(td :class "pr-4" "Hash lookup — O(1) amortized, cache-unfriendly")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Field access")
|
||||
(td (~tw :tokens "pr-4") "Hash lookup — O(1) amortized, cache-unfriendly")
|
||||
(td "Offset load — O(1) actual, single instruction"))
|
||||
(tr (td :class "pr-4 py-1" "Construction")
|
||||
(td :class "pr-4" "Allocate hash table + insert N entries")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Construction")
|
||||
(td (~tw :tokens "pr-4") "Allocate hash table + insert N entries")
|
||||
(td "Allocate N words, write sequentially"))
|
||||
(tr (td :class "pr-4 py-1" "Pattern match")
|
||||
(td :class "pr-4" "N string comparisons")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "Pattern match")
|
||||
(td (~tw :tokens "pr-4") "N string comparisons")
|
||||
(td "Tag check + field projection")))))
|
||||
|
||||
(p "Typed structs connect to the gradual type system in " (code "types.sx") ". "
|
||||
@@ -365,7 +365,7 @@
|
||||
;; Step 3.7: Verified Components
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Step 3.7: Verified Components")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Step 3.7: Verified Components")
|
||||
|
||||
(p "Content-addressed components become a trust mechanism. "
|
||||
"HTTPS tells you the connection is authentic. "
|
||||
@@ -373,13 +373,13 @@
|
||||
"that the payment form in your browser is the exact component that was audited, "
|
||||
"not a tampered copy injected by XSS, a rogue extension, or a compromised CDN.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "Why SX can do this")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "Why SX can do this")
|
||||
|
||||
(p "Most frameworks can't verify UI at the component level because there's no stable identity. "
|
||||
"A React component is compiled, bundled, minified, tree-shaken — "
|
||||
"the thing in the browser bears no relationship to the source. In SX:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Components are source") " — the " (code ".sx") " definition IS the component. No compilation step that could diverge.")
|
||||
(li (strong "Components are pure functions") " — same inputs, same output. Deterministic.")
|
||||
(li (strong "Content addressing is built in") " — " (code "freeze-to-cid") " gives every component a CID (Step 3).")
|
||||
@@ -390,7 +390,7 @@
|
||||
"There is no gap between \"what was audited\" and \"what runs.\" "
|
||||
"That gap is where every UI supply chain attack lives.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.7a Transitive closure CID")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.7a Transitive closure CID")
|
||||
|
||||
(p "A component's CID must cover its entire dependency tree. "
|
||||
"If " (code "~bank/payment-form") " calls " (code "~bank/amount-input") " calls "
|
||||
@@ -407,7 +407,7 @@
|
||||
";; A one-character change in ~ui/text-field\n"
|
||||
";; produces a completely different deep CID."))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.7b Canonical serialization")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.7b Canonical serialization")
|
||||
|
||||
(p "For CIDs to match across hosts, the serialized form must be identical. "
|
||||
"Canonical SX: no comments, no redundant whitespace, deterministic key ordering in dicts, "
|
||||
@@ -421,7 +421,7 @@
|
||||
";; Dict key ordering is sorted:\n"
|
||||
"(canonical-sx '{:b 2 :a 1}) ;; => \"{:a 1 :b 2}\""))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.7c Browser verification")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.7c Browser verification")
|
||||
|
||||
(p "The client-side verification flow:")
|
||||
|
||||
@@ -445,7 +445,7 @@
|
||||
(p "Visual indicator — like the HTTPS lock icon, but for individual UI components. "
|
||||
"The browser knows which components have verified CIDs and can surface this to the user.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "3.7d Manifest and discovery")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "3.7d Manifest and discovery")
|
||||
|
||||
(p "Publishers declare expected CIDs via a well-known manifest:")
|
||||
|
||||
@@ -470,46 +470,46 @@
|
||||
|
||||
(p "Alternative discovery mechanisms:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "DNS TXT") " — " (code "_sx-verify.bank.com TXT \"payment-form=bafyrei...\""))
|
||||
(li (strong "Certificate transparency") " — append-only log of component CIDs, publicly auditable")
|
||||
(li (strong "IPFS") " — the CID is the address; fetching from IPFS is self-verifying")
|
||||
(li (strong "Signed manifest") " — publisher signs the manifest with their TLS key; browser verifies signature"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "How this differs from SRI")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "How this differs from SRI")
|
||||
|
||||
(p "Subresource Integrity (SRI) already does hash verification for " (code "<script>") " tags. "
|
||||
"But SRI has three gaps that verified components close:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "SRI")
|
||||
(th :class "text-left pb-2 font-semibold" "SX Verified Components")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "SRI")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "SX Verified Components")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-semibold" "Granularity")
|
||||
(td :class "pr-4" "Whole files (a JS bundle)")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-semibold") "Granularity")
|
||||
(td (~tw :tokens "pr-4") "Whole files (a JS bundle)")
|
||||
(td "Individual components"))
|
||||
(tr (td :class "pr-4 py-1 font-semibold" "Who sets the hash?")
|
||||
(td :class "pr-4" "The server — if compromised, serves matching hashes")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-semibold") "Who sets the hash?")
|
||||
(td (~tw :tokens "pr-4") "The server — if compromised, serves matching hashes")
|
||||
(td "Independent manifest — client verifies against external source"))
|
||||
(tr (td :class "pr-4 py-1 font-semibold" "What's verified?")
|
||||
(td :class "pr-4" "The file bytes — says nothing about runtime behaviour")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-semibold") "What's verified?")
|
||||
(td (~tw :tokens "pr-4") "The file bytes — says nothing about runtime behaviour")
|
||||
(td "The definition — and since components are pure functions, definition = behaviour")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Step 4: Concurrent CEK — deep spec
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Step 4: Concurrent CEK")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Step 4: Concurrent CEK")
|
||||
|
||||
(p "Multiple CEK machines running concurrently. The machines are independent \u2014 "
|
||||
"each has its own C, E, K registers. Communication is via typed channels. "
|
||||
"The host provides the concurrency mechanism (Web Workers, forkIO, tokio::spawn). "
|
||||
"The spec defines the coordination primitives.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.1 Spawn")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.1 Spawn")
|
||||
|
||||
(p "The fundamental primitive. Create a new CEK machine from a thunk, "
|
||||
"run it concurrently, return a signal that resolves with the result:")
|
||||
@@ -549,7 +549,7 @@
|
||||
"
|
||||
" (fn (val) (reset! result-sig val))))))"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.2 Channels")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.2 Channels")
|
||||
|
||||
(p "Typed communication pipes between concurrent computations. "
|
||||
"Send is non-blocking. Receive suspends via " (code "shift") " until data arrives:")
|
||||
@@ -588,13 +588,13 @@
|
||||
|
||||
(p "Channel semantics:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Unbuffered") " \u2014 send blocks until a receiver is ready (rendezvous)")
|
||||
(li (strong "Buffered") " \u2014 send succeeds immediately, values queue (current default)")
|
||||
(li (strong "Broadcast") " \u2014 every receiver gets every value (pub/sub)")
|
||||
(li (strong "Select") " \u2014 wait on multiple channels, take the first that has data"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.3 Fork / Join")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.3 Fork / Join")
|
||||
|
||||
(p "Spawn multiple computations, collect all results. "
|
||||
"The spec primitive, not a library pattern:")
|
||||
@@ -621,12 +621,12 @@
|
||||
(p "Fork/join composes with the reactive system. Each result signal drives "
|
||||
"a reactive DOM node. Results appear as they complete, not all-or-nothing.")
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.4 Scheduler")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.4 Scheduler")
|
||||
|
||||
(p "The scheduler decides which CEK machine steps next. "
|
||||
"Different strategies for different use cases:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Round-robin") " \u2014 fair, each machine gets equal time")
|
||||
(li (strong "Priority") " \u2014 UI-facing computations step first")
|
||||
(li (strong "Work-stealing") " \u2014 idle workers steal from busy ones")
|
||||
@@ -664,11 +664,11 @@
|
||||
"
|
||||
" [:b :c] [:d]}})"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.5 Content-addressed concurrency")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.5 Content-addressed concurrency")
|
||||
|
||||
(p "Concurrent computation + content addressing = verifiable parallel execution:")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Each spawned computation\u2019s initial state has a CID")
|
||||
(li "Each result has a CID")
|
||||
(li "The mapping input-CID \u2192 output-CID is a proof of computation")
|
||||
@@ -699,90 +699,90 @@
|
||||
"
|
||||
" result)))"))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.6 Host mapping")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.6 Host mapping")
|
||||
|
||||
(p "Each host implements concurrency differently. The spec is the same:")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Primitive")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "JavaScript")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Python")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "OCaml")
|
||||
(th :class "text-left pb-2 font-semibold" "Rust/WASM")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Primitive")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "JavaScript")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Python")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "OCaml")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Rust/WASM")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1 font-mono" "spawn")
|
||||
(td :class "pr-4" "Web Worker")
|
||||
(td :class "pr-4" "asyncio.create_task")
|
||||
(td :class "pr-4" "Eio.Fiber.fork")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "spawn")
|
||||
(td (~tw :tokens "pr-4") "Web Worker")
|
||||
(td (~tw :tokens "pr-4") "asyncio.create_task")
|
||||
(td (~tw :tokens "pr-4") "Eio.Fiber.fork")
|
||||
(td "tokio::spawn"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "channel")
|
||||
(td :class "pr-4" "MessageChannel")
|
||||
(td :class "pr-4" "asyncio.Queue")
|
||||
(td :class "pr-4" "Eio.Stream.t")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "channel")
|
||||
(td (~tw :tokens "pr-4") "MessageChannel")
|
||||
(td (~tw :tokens "pr-4") "asyncio.Queue")
|
||||
(td (~tw :tokens "pr-4") "Eio.Stream.t")
|
||||
(td "mpsc::channel"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "yield!")
|
||||
(td :class "pr-4" "setTimeout(0)")
|
||||
(td :class "pr-4" "await asyncio.sleep(0)")
|
||||
(td :class "pr-4" "Eio.Fiber.yield")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "yield!")
|
||||
(td (~tw :tokens "pr-4") "setTimeout(0)")
|
||||
(td (~tw :tokens "pr-4") "await asyncio.sleep(0)")
|
||||
(td (~tw :tokens "pr-4") "Eio.Fiber.yield")
|
||||
(td "tokio::task::yield_now"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "freeze/thaw")
|
||||
(td :class "pr-4" "postMessage + JSON")
|
||||
(td :class "pr-4" "pickle / SX text")
|
||||
(td :class "pr-4" "Marshal + SX text")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "freeze/thaw")
|
||||
(td (~tw :tokens "pr-4") "postMessage + JSON")
|
||||
(td (~tw :tokens "pr-4") "pickle / SX text")
|
||||
(td (~tw :tokens "pr-4") "Marshal + SX text")
|
||||
(td "serde + SX text"))
|
||||
(tr (td :class "pr-4 py-1 font-mono" "select")
|
||||
(td :class "pr-4" "Promise.race")
|
||||
(td :class "pr-4" "asyncio.wait FIRST_COMPLETED")
|
||||
(td :class "pr-4" "Eio.Fiber.any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1 font-mono") "select")
|
||||
(td (~tw :tokens "pr-4") "Promise.race")
|
||||
(td (~tw :tokens "pr-4") "asyncio.wait FIRST_COMPLETED")
|
||||
(td (~tw :tokens "pr-4") "Eio.Fiber.any")
|
||||
(td "tokio::select!")))))
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-8 mb-3" "4.7 Roadmap")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-8 mb-3") "4.7 Roadmap")
|
||||
|
||||
(div :class "overflow-x-auto mb-6"
|
||||
(table :class "min-w-full text-sm"
|
||||
(div (~tw :tokens "overflow-x-auto mb-6")
|
||||
(table (~tw :tokens "min-w-full text-sm")
|
||||
(thead (tr
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Phase")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "What")
|
||||
(th :class "text-left pr-4 pb-2 font-semibold" "Host")
|
||||
(th :class "text-left pb-2 font-semibold" "Validates")))
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Phase")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "What")
|
||||
(th (~tw :tokens "text-left pr-4 pb-2 font-semibold") "Host")
|
||||
(th (~tw :tokens "text-left pb-2 font-semibold") "Validates")))
|
||||
(tbody
|
||||
(tr (td :class "pr-4 py-1" "4a")
|
||||
(td :class "pr-4" "spawn + Web Worker")
|
||||
(td :class "pr-4" "JavaScript")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4a")
|
||||
(td (~tw :tokens "pr-4") "spawn + Web Worker")
|
||||
(td (~tw :tokens "pr-4") "JavaScript")
|
||||
(td "Freeze/thaw across execution contexts"))
|
||||
(tr (td :class "pr-4 py-1" "4b")
|
||||
(td :class "pr-4" "Channels (buffered)")
|
||||
(td :class "pr-4" "JavaScript")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4b")
|
||||
(td (~tw :tokens "pr-4") "Channels (buffered)")
|
||||
(td (~tw :tokens "pr-4") "JavaScript")
|
||||
(td "Cross-worker communication"))
|
||||
(tr (td :class "pr-4 py-1" "4c")
|
||||
(td :class "pr-4" "Fork/join + DAG scheduler")
|
||||
(td :class "pr-4" "JavaScript")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4c")
|
||||
(td (~tw :tokens "pr-4") "Fork/join + DAG scheduler")
|
||||
(td (~tw :tokens "pr-4") "JavaScript")
|
||||
(td "Art DAG integration path"))
|
||||
(tr (td :class "pr-4 py-1" "4d")
|
||||
(td :class "pr-4" "OCaml bootstrapper → native compilation")
|
||||
(td :class "pr-4" "OCaml")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4d")
|
||||
(td (~tw :tokens "pr-4") "OCaml bootstrapper → native compilation")
|
||||
(td (~tw :tokens "pr-4") "OCaml")
|
||||
(td "Native performance, direct CEK-to-ML mapping"))
|
||||
(tr (td :class "pr-4 py-1" "4e")
|
||||
(td :class "pr-4" "Rust/WASM bootstrapper")
|
||||
(td :class "pr-4" "Rust")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4e")
|
||||
(td (~tw :tokens "pr-4") "Rust/WASM bootstrapper")
|
||||
(td (~tw :tokens "pr-4") "Rust")
|
||||
(td "Browser performance, WASM target"))
|
||||
(tr (td :class "pr-4 py-1" "4f")
|
||||
(td :class "pr-4" "Content-addressed spawn")
|
||||
(td :class "pr-4" "Any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4f")
|
||||
(td (~tw :tokens "pr-4") "Content-addressed spawn")
|
||||
(td (~tw :tokens "pr-4") "Any")
|
||||
(td "Memoization, distribution, verification"))
|
||||
(tr (td :class "pr-4 py-1" "4g")
|
||||
(td :class "pr-4" "IPFS-backed content store")
|
||||
(td :class "pr-4" "Any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4g")
|
||||
(td (~tw :tokens "pr-4") "IPFS-backed content store")
|
||||
(td (~tw :tokens "pr-4") "Any")
|
||||
(td "Global content addressing, peer-to-peer"))
|
||||
(tr (td :class "pr-4 py-1" "4h")
|
||||
(td :class "pr-4" "Select + broadcast channels")
|
||||
(td :class "pr-4" "Any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4h")
|
||||
(td (~tw :tokens "pr-4") "Select + broadcast channels")
|
||||
(td (~tw :tokens "pr-4") "Any")
|
||||
(td "Complex coordination patterns"))
|
||||
(tr (td :class "pr-4 py-1" "4i")
|
||||
(td :class "pr-4" "Linear channels")
|
||||
(td :class "pr-4" "Any")
|
||||
(tr (td (~tw :tokens "pr-4 py-1") "4i")
|
||||
(td (~tw :tokens "pr-4") "Linear channels")
|
||||
(td (~tw :tokens "pr-4") "Any")
|
||||
(td "Resource safety, exactly-once delivery")))))
|
||||
|
||||
(p "Each phase is independently valuable. Phase 4a (Web Worker spawn) is the immediate next build. "
|
||||
@@ -794,13 +794,13 @@
|
||||
;; Step 5: Linear effects (future)
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(h2 :class "text-xl font-bold mt-12 mb-4" "Step 5: Linear Effects")
|
||||
(h2 (~tw :tokens "text-xl font-bold mt-12 mb-4") "Step 5: Linear Effects")
|
||||
|
||||
(p "Constrain the continuation: a handler " (em "must") " resume exactly once. "
|
||||
"No dropping (resource leak), no duplicating (nondeterminism). "
|
||||
"This is the linearity axis from the three-axis model.")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "Affine channels") " \u2014 send at most once. Prevents duplicate messages.")
|
||||
(li (strong "Linear scopes") " \u2014 a freeze scope that must be thawed. Prevents orphaned state.")
|
||||
(li (strong "Session types") " \u2014 a protocol that must complete. Prevents half-open connections.")
|
||||
@@ -814,5 +814,5 @@
|
||||
"content addressing \u2014 must be solid first. Linearity is a discipline on top, "
|
||||
"not a prerequisite beneath.")
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mt-12"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mt-12")
|
||||
"The floor is thought itself. We can't go deeper, because there's no one left to dig.")))
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
(p "The fix: transfer component definitions alongside fragments. Services tell the provider what they already have; the provider sends only what's missing."))
|
||||
|
||||
(~docs/section :title "What exists" :id "exists"
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Fragment GET infrastructure works (" (code "shared/infrastructure/fragments.py") ")")
|
||||
(li (code "X-Fragment-Request") " header protocol for internal service calls")
|
||||
(li "Content type negotiation for text/html and text/sx responses")
|
||||
(li "Fragment caching and composition in page rendering"))))
|
||||
|
||||
(~docs/section :title "What remains" :id "remains"
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "POST sexp protocol: ") "Switch from GET to POST with structured sexp body containing " (code ":components") " list of what consumer already has")
|
||||
(li (strong "Structured response: ") (code "(fragment-response :defs (...) :content (...))") " — provider sends only missing component defs")
|
||||
(li (strong (code "fragment_response()") " builder: ") "New function in helpers.py that diffs provider's component env against consumer's list")
|
||||
@@ -27,24 +27,24 @@
|
||||
(li (strong "Shared blueprint factory: ") (code "create_fragment_blueprint(handlers)") " to deduplicate the identical fragment endpoint pattern across 8 services"))))
|
||||
|
||||
(~docs/section :title "Files to modify" :id "files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Change")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Change")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/fragments.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "POST sexp body, parse response, register defs"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "fragment_response() builder"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/fragment_endpoint.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "NEW — shared blueprint factory"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "*/bp/fragments/routes.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "All 8 services: use create_fragment_blueprint"))))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/fragments.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "POST sexp body, parse response, register defs"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/helpers.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "fragment_response() builder"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/fragment_endpoint.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "NEW — shared blueprint factory"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "*/bp/fragments/routes.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "All 8 services: use create_fragment_blueprint"))))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Glue Decoupling
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/generative-sx/plan-generative-sx-content ()
|
||||
(~docs/page :title "Generative SX"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"In the browser, SX is a program that modifies itself in response to external stimuli. Outside the browser, it becomes a program that writes itself as it runs.")
|
||||
|
||||
;; =====================================================================
|
||||
@@ -15,7 +15,7 @@
|
||||
(~docs/section :title "The observation" :id "observation"
|
||||
(p "The marshes work made something visible. A server response arrives carrying " (code "(reset! (use-store \"price\") 14.99)") " inside a " (code "data-init") " script. The SX evaluator parses this string, evaluates it in its own environment, and mutates its own signal graph. The program accepted new source at runtime and changed itself.")
|
||||
(p "This isn't metaprogramming. Macros expand at compile time — they transform source before evaluation. This is different: the program is " (em "already running") " when it receives new code, evaluates it, and continues with an extended state. The DOM is just the boundary. The signal graph is just the state. The mechanism is general:")
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-600"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-600")
|
||||
(li "A running SX evaluator with an environment")
|
||||
(li "New SX source arrives (from any external source)")
|
||||
(li "The evaluator parses it and evaluates it " (em "in its own environment"))
|
||||
@@ -58,34 +58,34 @@
|
||||
|
||||
(p "The program evaluates source. If the result is more source, it evaluates that too. Each iteration can extend the environment — add new functions, new macros, new primitives. The environment grows. The program becomes capable of things it couldn't do at the start.")
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(p :class "text-violet-900 font-medium" "This is not eval-in-a-loop")
|
||||
(p :class "text-violet-800 text-sm" "A REPL evaluates user input in a persistent environment. That's interactive, not generative. The generative pattern is different: the program itself decides what to evaluate next. No user in the loop. The output of one evaluation becomes the input to the next. The program writes itself."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "This is not eval-in-a-loop")
|
||||
(p (~tw :tokens "text-violet-800 text-sm") "A REPL evaluates user input in a persistent environment. That's interactive, not generative. The generative pattern is different: the program itself decides what to evaluate next. No user in the loop. The output of one evaluation becomes the input to the next. The program writes itself."))
|
||||
|
||||
(~docs/subsection :title "Three modes"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Mode")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Input")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Output")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Growth")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Mode")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Input")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Output")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Growth")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Analytic")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX program + data")
|
||||
(td :class "px-3 py-2 text-stone-600" "Analysis results")
|
||||
(td :class "px-3 py-2 text-stone-600" "None — pure function"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Synthetic")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX program")
|
||||
(td :class "px-3 py-2 text-stone-600" "New SX source")
|
||||
(td :class "px-3 py-2 text-stone-600" "Code generation — new defs emitted"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Analytic")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX program + data")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Analysis results")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "None — pure function"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Synthetic")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX program")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "New SX source")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Code generation — new defs emitted"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-medium text-stone-700" "Generative")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX seed")
|
||||
(td :class "px-3 py-2 text-stone-600" "Running program")
|
||||
(td :class "px-3 py-2 text-stone-600" "Self-extending — output evaluated as input")))))))
|
||||
(td (~tw :tokens "px-3 py-2 font-medium text-stone-700") "Generative")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX seed")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Running program")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Self-extending — output evaluated as input")))))))
|
||||
|
||||
;; =====================================================================
|
||||
;; IV. Concrete manifestations
|
||||
@@ -132,9 +132,9 @@
|
||||
|
||||
(p "The seed is a quine that doesn't just reproduce itself — it " (em "extends") " itself. Each call to " (code "next-source") " returns new SX that the seed evaluates in its own environment. The environment grows. The seed's capabilities grow. But the seed itself never changes — it's the fixed point of the generative process.")
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4 my-4"
|
||||
(p :class "text-stone-700 font-medium mb-2" "The minimal seed is the spec")
|
||||
(p :class "text-stone-600 text-sm" (code "eval.sx") " + " (code "parser.sx") " + " (code "primitives.sx") " = a complete SX evaluator defined in SX. This is already the seed. The bootstrappers compile it to JavaScript and Python. A generative runtime compiles it to " (em "itself") " — the seed evaluates the seed and obtains a running evaluator that can evaluate anything, including more of itself.")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-stone-700 font-medium mb-2") "The minimal seed is the spec")
|
||||
(p (~tw :tokens "text-stone-600 text-sm") (code "eval.sx") " + " (code "parser.sx") " + " (code "primitives.sx") " = a complete SX evaluator defined in SX. This is already the seed. The bootstrappers compile it to JavaScript and Python. A generative runtime compiles it to " (em "itself") " — the seed evaluates the seed and obtains a running evaluator that can evaluate anything, including more of itself.")))
|
||||
|
||||
;; =====================================================================
|
||||
;; VI. Growth constraints
|
||||
@@ -170,7 +170,7 @@
|
||||
|
||||
(~docs/subsection :title "Runtime eval with first-class environments"
|
||||
(p (code "eval-in") " requires evaluating arbitrary expressions in arbitrary environments at runtime. The host must support:")
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-1 text-stone-600")
|
||||
(li "Creating new environments (" (code "env-extend") ")")
|
||||
(li "Adding bindings to existing environments (" (code "env-set!") ")")
|
||||
(li "Inspecting environment contents (" (code "env-snapshot") ")")
|
||||
@@ -211,10 +211,10 @@
|
||||
(p "The generative primitives (" (code "read-file") ", " (code "write-file") ", " (code "list-files") ") are the " (em "only") " way generated code touches the outside world. The host must be able to intercept, log, and deny all IO. There is no escape hatch through FFI or native calls.")
|
||||
(p "This is what makes generative programs auditable. If the host allows generated code to call raw " (code "fs.writeFileSync") " or " (code "os.system") ", the boundary is meaningless. The host must virtualize all IO through the declared primitives. Generated code that tries to escape the sandbox hits the boundary, not the OS."))
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 my-4"
|
||||
(p :class "text-violet-900 font-medium" "The acid test")
|
||||
(p :class "text-violet-800 text-sm" "SX already has properties 1–7 in the spec. They fall out of the language design. Properties 8–10 (deterministic evaluation, serializable state, IO isolation) are constraints on the " (em "host implementation") ", not the language. A host that violates 1–7 can't run the spec correctly. A host that violates 8–10 can run it but can't be trusted in a generative context.")
|
||||
(p :class "text-violet-800 text-sm mt-2" "Phase 1 — the self-compiling spec — tests properties 1–7. If a host can compile the spec from the spec, it necessarily has them. The remaining three are operational guarantees verified by the seed network (Phase 5), where multiple hosts must agree on the same generative output.")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The acid test")
|
||||
(p (~tw :tokens "text-violet-800 text-sm") "SX already has properties 1–7 in the spec. They fall out of the language design. Properties 8–10 (deterministic evaluation, serializable state, IO isolation) are constraints on the " (em "host implementation") ", not the language. A host that violates 1–7 can't run the spec correctly. A host that violates 8–10 can run it but can't be trusted in a generative context.")
|
||||
(p (~tw :tokens "text-violet-800 text-sm mt-2") "Phase 1 — the self-compiling spec — tests properties 1–7. If a host can compile the spec from the spec, it necessarily has them. The remaining three are operational guarantees verified by the seed network (Phase 5), where multiple hosts must agree on the same generative output.")))
|
||||
|
||||
;; =====================================================================
|
||||
;; VIII. Environment migration
|
||||
@@ -255,42 +255,42 @@
|
||||
|
||||
(~docs/subsection :title "Phase 0: Generative primitives"
|
||||
(p "Add the minimal set of primitives needed for a generative loop. These are IO primitives — they cross the boundary.")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Primitive")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Signature")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Primitive")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Signature")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "read-file")
|
||||
(td :class "px-3 py-2 text-stone-600" "(path) → string")
|
||||
(td :class "px-3 py-2 text-stone-600" "Read file contents as string"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "write-file")
|
||||
(td :class "px-3 py-2 text-stone-600" "(path content) → nil")
|
||||
(td :class "px-3 py-2 text-stone-600" "Write string to file"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "list-files")
|
||||
(td :class "px-3 py-2 text-stone-600" "(path pattern) → list")
|
||||
(td :class "px-3 py-2 text-stone-600" "Glob-match files in directory"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "eval-in")
|
||||
(td :class "px-3 py-2 text-stone-600" "(source env) → any")
|
||||
(td :class "px-3 py-2 text-stone-600" "Parse and evaluate SX source in given environment"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "env-snapshot")
|
||||
(td :class "px-3 py-2 text-stone-600" "(env) → dict")
|
||||
(td :class "px-3 py-2 text-stone-600" "Serialize environment to inspectable dict"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "read-file")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(path) → string")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Read file contents as string"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "write-file")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(path content) → nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Write string to file"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "list-files")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(path pattern) → list")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Glob-match files in directory"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "eval-in")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(source env) → any")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Parse and evaluate SX source in given environment"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "env-snapshot")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(env) → dict")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Serialize environment to inspectable dict"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "content-hash")
|
||||
(td :class "px-3 py-2 text-stone-600" "(source) → string")
|
||||
(td :class "px-3 py-2 text-stone-600" "SHA3-256 hash of source string")))))
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "content-hash")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "(source) → string")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SHA3-256 hash of source string")))))
|
||||
(p "These are the building blocks. The generative loop composes them. The primitives themselves are minimal — no networking, no databases, no UI. Just: read, write, evaluate, inspect, hash."))
|
||||
|
||||
(~docs/subsection :title "Phase 1: Self-compiling spec"
|
||||
(p "Rewrite " (code "bootstrap_js.py") " as " (code "bootstrap.sx") ". The bootstrapper becomes an SX program that reads the spec files and emits target code.")
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-600"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-600")
|
||||
(li "Write " (code "codegen-js.sx") " — JavaScript code generation adapter (emit JS from SX AST)")
|
||||
(li "Write " (code "codegen-py.sx") " — Python code generation adapter")
|
||||
(li "Write " (code "bootstrap.sx") " — the generative loop that loads a spec, loads an adapter, and emits")
|
||||
@@ -320,7 +320,7 @@
|
||||
|
||||
(~docs/section :title "The strange loop" :id "strange-loop"
|
||||
(p "Hofstadter's strange loop: a hierarchy of levels where the top level reaches back down and affects the bottom level. In a generative SX program:")
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-600")
|
||||
(li "The bottom level is the evaluator — it evaluates expressions")
|
||||
(li "The middle level is the program — expressions that produce values")
|
||||
(li "The top level is the generator — values that are new expressions")
|
||||
@@ -330,8 +330,8 @@
|
||||
|
||||
(p "The browser version constrains this to DOM mutations. The server version constrains it to request handling. The unconstrained version — a bare seed with " (code "next-source") " — is a strange loop in its purest form: an evaluator that evaluates what it generates, generating what it evaluates.")
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4 mt-6"
|
||||
(p :class "text-stone-700 font-medium mb-2" "The practical consequence")
|
||||
(p :class "text-stone-600 text-sm" "An SX development environment where the tools are written in SX, running inside SX, modifying SX. The editor understands the code because it " (em "is") " the code. The debugger inspects the environment because it " (em "shares") " the environment. The compiler reads the spec because the spec is in the same format as everything else. There is no impedance mismatch between any layer because there is only one layer.")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 mt-6")
|
||||
(p (~tw :tokens "text-stone-700 font-medium mb-2") "The practical consequence")
|
||||
(p (~tw :tokens "text-stone-600 text-sm") "An SX development environment where the tools are written in SX, running inside SX, modifying SX. The editor understands the code because it " (em "is") " the code. The debugger inspects the environment because it " (em "shares") " the environment. The compiler reads the spec because the spec is in the same format as everything else. There is no impedance mismatch between any layer because there is only one layer.")))
|
||||
|
||||
))
|
||||
|
||||
@@ -11,38 +11,38 @@
|
||||
|
||||
(~docs/section :title "Current state" :id "current"
|
||||
(p "Apps are partially decoupled via HTTP interfaces (fetch_data, call_action, send_internal_activity) and DTOs. The Cart microservice split (relations, likes, orders) is complete. But direct model imports persist in:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Cart") " — 9 files importing from market, events, blog")
|
||||
(li (strong "Blog") " — 8 files importing from cart, events, market")
|
||||
(li (strong "Events") " — 5 files importing from blog, market, cart")
|
||||
(li (strong "Market") " — 1 file importing from blog")))
|
||||
|
||||
(~docs/section :title "What remains" :id "remains"
|
||||
(div :class "space-y-3"
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "1. glue/services/pages.py")
|
||||
(p :class "text-sm text-stone-600" "Dict-based Post access for non-blog apps: get_page_by_slug, get_page_by_id, get_pages_by_ids, page_exists, search_posts."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "2. glue/services/page_config.py")
|
||||
(p :class "text-sm text-stone-600" "PageConfig CRUD: get_page_config, get_or_create_page_config, get_page_configs_by_ids."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "3. glue/services/calendars.py")
|
||||
(p :class "text-sm text-stone-600" "Calendar queries + entry associations (from blog): get_calendars_for_page, toggle_entry_association, get_associated_entries."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "4. glue/services/marketplaces.py")
|
||||
(p :class "text-sm text-stone-600" "MarketPlace CRUD (from blog+events): get_marketplaces_for_page, create_marketplace, soft_delete_marketplace."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "5. glue/services/cart_items.py")
|
||||
(p :class "text-sm text-stone-600" "CartItem/CalendarEntry queries for cart: get_cart_items, find_or_create_cart_item, clear_cart_for_order."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "6. glue/services/products.py")
|
||||
(p :class "text-sm text-stone-600" "Minimal Product access for cart orders: get_product."))
|
||||
(div :class "rounded border border-stone-200 p-3"
|
||||
(h4 :class "font-semibold text-stone-700" "7. Model registration + cleanup")
|
||||
(p :class "text-sm text-stone-600" "register_models() in glue/setup.py, update all app.py files, delete moved service files."))))
|
||||
(div (~tw :tokens "space-y-3")
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. glue/services/pages.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Dict-based Post access for non-blog apps: get_page_by_slug, get_page_by_id, get_pages_by_ids, page_exists, search_posts."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. glue/services/page_config.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "PageConfig CRUD: get_page_config, get_or_create_page_config, get_page_configs_by_ids."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. glue/services/calendars.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Calendar queries + entry associations (from blog): get_calendars_for_page, toggle_entry_association, get_associated_entries."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. glue/services/marketplaces.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "MarketPlace CRUD (from blog+events): get_marketplaces_for_page, create_marketplace, soft_delete_marketplace."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "5. glue/services/cart_items.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "CartItem/CalendarEntry queries for cart: get_cart_items, find_or_create_cart_item, clear_cart_for_order."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "6. glue/services/products.py")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Minimal Product access for cart orders: get_product."))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "7. Model registration + cleanup")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "register_models() in glue/setup.py, update all app.py files, delete moved service files."))))
|
||||
|
||||
(~docs/section :title "Docker consideration" :id "docker"
|
||||
(p :class "text-stone-600" "For glue services to work in Docker (single app per container), model files from other apps must be importable. Recommended: try/except at import time — glue services that can't import a model raise ImportError at call time, which only happens if called from the wrong app."))))
|
||||
(p (~tw :tokens "text-stone-600") "For glue services to work in Docker (single app per container), model files from other apps must be importable. Recommended: try/except at import time — glue services that can't import a model raise ImportError at call time, which only happens if called from the wrong app."))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Social Sharing
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
(~docs/page
|
||||
:title "Plans"
|
||||
(div
|
||||
:class "space-y-4"
|
||||
(~tw :tokens "space-y-4")
|
||||
(p
|
||||
:class "text-lg text-stone-600 mb-4"
|
||||
(~tw :tokens "text-lg text-stone-600 mb-4")
|
||||
"Architecture roadmaps and implementation plans for SX.")
|
||||
(div
|
||||
:class "space-y-3"
|
||||
(~tw :tokens "space-y-3")
|
||||
(map
|
||||
(fn
|
||||
(item)
|
||||
@@ -20,9 +20,9 @@
|
||||
:sx-select "#sx-content"
|
||||
:sx-swap "outerHTML"
|
||||
:sx-push-url "true"
|
||||
:class "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors"
|
||||
(div :class "font-semibold text-stone-800" (get item "label"))
|
||||
(~tw :tokens "block rounded border border-stone-200 p-4 hover:border-violet-300 hover:bg-violet-50 transition-colors")
|
||||
(div (~tw :tokens "font-semibold text-stone-800") (get item "label"))
|
||||
(when
|
||||
(get item "summary")
|
||||
(p :class "text-sm text-stone-500 mt-1" (get item "summary")))))
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") (get item "summary")))))
|
||||
plans-nav-items)))))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
(~docs/section :title "Context" :id "context"
|
||||
(p "The SX spec is already split into three layers:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (code "spec/") " \u2014 Core language (19 files): evaluator, parser, primitives, CEK, types, continuations. Host-independent.")
|
||||
(li (code "web/") " \u2014 Web framework (20 files): signals, adapters, engine, orchestration, boot, router, deps. Built on core spec.")
|
||||
(li (code "sx/") " \u2014 Application (sx-docs website). Built on web framework."))
|
||||
@@ -16,35 +16,35 @@
|
||||
(p "This also involves sorting out the JavaScript: eliminating hand-coded JS that duplicates specced " (code ".sx") " logic, and moving web framework " (code ".sx") " from compiled-into-evaluator to runtime-evaluated."))
|
||||
|
||||
(~docs/section :title "Existing Architecture" :id "existing"
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Three-layer spec split (DONE)")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Directory")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Files")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Content")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Three-layer spec split (DONE)")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Directory")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Files")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Content")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Core spec")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "spec/"))
|
||||
(td :class "px-3 py-2 text-stone-700" "19")
|
||||
(td :class "px-3 py-2 text-stone-600" "eval, parser, primitives, render, types, CEK, continuations, boundary-core"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Web framework")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "web/"))
|
||||
(td :class "px-3 py-2 text-stone-700" "20")
|
||||
(td :class "px-3 py-2 text-stone-600" "adapters, signals, engine, orchestration, boot, router, deps, forms, boundary-web"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Core spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "spec/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "19")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "eval, parser, primitives, render, types, CEK, continuations, boundary-core"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Web framework")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "web/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "20")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "adapters, signals, engine, orchestration, boot, router, deps, forms, boundary-web"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Application")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "sx/"))
|
||||
(td :class "px-3 py-2 text-stone-700" "\u2014")
|
||||
(td :class "px-3 py-2 text-stone-600" "sx-docs website, page components, content")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Application")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "sx/"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "\u2014")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "sx-docs website, page components, content")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Rust/WASM evaluator (DONE)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Rust/WASM evaluator (DONE)")
|
||||
(p (code "sx-rust/") " has a working parser + evaluator + render-to-html in WASM: 9,823 lines generated Rust, 75 real primitives, 154 stubs, 92 tests passing. Currently pure computation \u2014 no DOM interaction.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What needs to change")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What needs to change")
|
||||
(p "Currently " (strong "everything") " (core + web framework) gets compiled into one monolithic " (code "sx-browser.js") ". The web framework " (code ".sx") " files (signals, engine, orchestration, boot, etc.) are baked into the evaluator output by the bootstrapper. They should instead be " (strong "evaluated at runtime") " by the core evaluator, like any other " (code ".sx") " code.")
|
||||
(p "The JavaScript platform primitives (DOM, fetch, timers, storage) are also inlined into the bootstrapped output. They need to be extracted into a standalone " (code "sx-platform.js") " module that both JS and WASM evaluators share."))
|
||||
|
||||
@@ -56,89 +56,89 @@
|
||||
(~docs/section :title "Bootstrapped vs Runtime-Evaluated" :id "bootstrap-vs-runtime"
|
||||
(p "The key question: what MUST be compiled into the evaluator vs what can be loaded as " (code ".sx") " at runtime?")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Must be bootstrapped (core spec)")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Dir")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Must be bootstrapped (core spec)")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Dir")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "eval.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "spec/")
|
||||
(td :class "px-3 py-2 text-stone-600" "IS the language \u2014 can't evaluate without it"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "parser.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "spec/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Can't read .sx source without a parser"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "primitives.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "spec/")
|
||||
(td :class "px-3 py-2 text-stone-600" "80+ built-in pure functions \u2014 must be native"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "render.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "spec/")
|
||||
(td :class "px-3 py-2 text-stone-600" "HTML_TAGS registry, parse-element-args"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "adapter-html.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "render-to-html \u2014 co-recursive with eval-expr"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "adapter-sx.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "aser (wire format) \u2014 co-recursive with eval-expr"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "eval.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "spec/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "IS the language \u2014 can't evaluate without it"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "parser.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "spec/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Can't read .sx source without a parser"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "primitives.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "spec/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "80+ built-in pure functions \u2014 must be native"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "render.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "spec/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "HTML_TAGS registry, parse-element-args"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "adapter-html.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "render-to-html \u2014 co-recursive with eval-expr"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "adapter-sx.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "aser (wire format) \u2014 co-recursive with eval-expr"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" (code "adapter-dom.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "render-to-dom \u2014 co-recursive with eval-expr")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "adapter-dom.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "render-to-dom \u2014 co-recursive with eval-expr")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Runtime-evaluated (web framework)")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Dir")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why it can be runtime")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Runtime-evaluated (web framework)")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Dir")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why it can be runtime")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "signals.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure computation \u2014 dicts with markers, no new types"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "engine.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure logic \u2014 trigger parsing, swap specs, morph"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "orchestration.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Event binding + fetch \u2014 calls platform primitives"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "boot.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Page lifecycle \u2014 calls platform primitives"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "router.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "URL pattern matching \u2014 pure computation"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "deps.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Component dependency analysis \u2014 pure AST walking"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "page-helpers.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Data transformation helpers"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "signals.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure computation \u2014 dicts with markers, no new types"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "engine.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure logic \u2014 trigger parsing, swap specs, morph"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "orchestration.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Event binding + fetch \u2014 calls platform primitives"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "boot.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Page lifecycle \u2014 calls platform primitives"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "router.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "URL pattern matching \u2014 pure computation"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "deps.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Component dependency analysis \u2014 pure AST walking"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "page-helpers.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Data transformation helpers"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" (code "forms.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "web/")
|
||||
(td :class "px-3 py-2 text-stone-600" "Server-only definition forms")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "forms.sx"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "web/")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Server-only definition forms")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "adapter-dom.sx reactive split")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "adapter-dom.sx reactive split")
|
||||
(p (code "adapter-dom.sx") " contains reactive-aware code (" (code "reactive-text") ", " (code "reactive-attr") ", " (code "render-dom-island") ", " (code "render-dom-lake") ") interleaved with core DOM rendering. These call " (code "signal?") " and " (code "deref") " from " (code "signals.sx") " via environment lookup \u2014 no compile-time dependency. Option: split reactive DOM functions into " (code "adapter-dom-reactive.sx") " (web/ layer), keeping base " (code "adapter-dom.sx") " purely about elements/text/fragments/components.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Hand-coded JS to clean up")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Hand-coded JS to clean up")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (code "CONTINUATIONS_JS") " in " (code "platform_js.py") " \u2014 hand-coded shift/reset. Should use specced " (code "continuations.sx") " or be eliminated if continuations are application-level.")
|
||||
(li (code "ASYNC_IO_JS") " in " (code "platform_js.py") " \u2014 hand-coded async rendering dispatch. Already replaced by " (code "adapter-async.sx") " for Python. JS version should also be bootstrapped or eliminated.")
|
||||
(li "Various wrapper functions in " (code "PLATFORM_BOOT_JS") " that duplicate logic from " (code "boot.sx") ".")))
|
||||
@@ -151,47 +151,47 @@
|
||||
(~docs/section :title "Phase 1: Extract sx-platform.js" :id "phase-1"
|
||||
(p (strong "Goal:") " All real-world-touching JavaScript lives in one standalone module. The evaluator never directly accesses " (code "document") ", " (code "window") ", " (code "fetch") ", " (code "localStorage") ", " (code "history") ", etc.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Architecture")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Architecture")
|
||||
(~docs/code :src (highlight " \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 sx-platform.js \u2502 \u2190 DOM, fetch, timers, storage\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 sx-evaluator.js \u2502 \u2502 sx-wasm-shim.js \u2502\n \u2502 (isolated JS) \u2502 \u2502 (WASM instance \u2502\n \u2502 \u2502 \u2502 + handle table) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518" "text"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What moves into sx-platform.js")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What moves into sx-platform.js")
|
||||
(p "Extracted from " (code "platform_js.py") " string constants:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Category")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Source")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "~Functions")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Category")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Source")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "~Functions")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "DOM primitives")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_DOM_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~50 (createElement, setAttribute, appendChild...)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Engine platform")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_ENGINE_PURE_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~6 (locationHref, pushState, nowMs...)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Orchestration platform")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_ORCHESTRATION_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~80 (fetch, abort, timers, SSE, scroll, media...)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Boot platform")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_BOOT_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~20 (mount target, localStorage, cookies, logging...)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DOM primitives")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_DOM_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~50 (createElement, setAttribute, appendChild...)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Engine platform")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_ENGINE_PURE_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~6 (locationHref, pushState, nowMs...)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Orchestration platform")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_ORCHESTRATION_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~80 (fetch, abort, timers, SSE, scroll, media...)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Boot platform")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_BOOT_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~20 (mount target, localStorage, cookies, logging...)"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Parser helpers")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "PLATFORM_PARSER_JS"))
|
||||
(td :class "px-3 py-2 text-stone-600" "~4 (isIdentStart, parseNumber...)")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Parser helpers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "PLATFORM_PARSER_JS"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~4 (isIdentStart, parseNumber...)")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Isolation rule")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Isolation rule")
|
||||
(p "After extraction, searching " (code "sx-evaluator.js") " for " (code "document") ", " (code "window") ", " (code "fetch") ", " (code "localStorage") ", " (code "history") ", " (code "setTimeout") ", " (code "console") " should find " (strong "zero") " direct references.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The callSxFunction bridge")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The callSxFunction bridge")
|
||||
(p "Platform code (event listeners, timers) needs to invoke SX lambdas. The evaluator provides a single " (code "callSxFunction(fn, args) \u2192 result") " bridge to the platform at registration time. This is the ONE evaluator-to-platform callback.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Implementation")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Implementation")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Modify " (code "platform_js.py") " to emit platform functions as a separate output section")
|
||||
(li "Create " (code "sx-platform.js") " as an IIFE that sets " (code "globalThis.SxPlatform = {...}"))
|
||||
(li "The evaluator IIFE reads " (code "globalThis.SxPlatform") " at init, registers each function as a PRIMITIVE")
|
||||
@@ -206,19 +206,19 @@
|
||||
(~docs/section :title "Phase 2: Isolate the JS Evaluator" :id "phase-2"
|
||||
(p (strong "Goal:") " " (code "sx-evaluator.js") " contains ONLY core spec + render adapters. Web framework " (code ".sx") " is evaluated at runtime.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Core-only build")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Core-only build")
|
||||
(p "The bootstrapper already supports selecting which modules to compile. A core-only build:")
|
||||
(~docs/code :src (highlight "# In run_js_sx.py \u2014 core-only build\ncompile_ref_to_js(\n adapters=[\"parser\", \"html\", \"sx\", \"dom\"], # core spec + adapters\n modules=None, # no signals, engine, orchestration, boot\n extensions=None, # no continuations\n spec_modules=None # no deps, router, cek, frames, page-helpers\n)" "python"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Web framework loading")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Web framework loading")
|
||||
(p "Web framework " (code ".sx") " files ship as " (code "<script type=\"text/sx-lib\">") " blocks. The platform boot shim evaluates them before component scripts:")
|
||||
(~docs/code :src (highlight "<script src=\"/static/scripts/sx-platform.js\"></script>\n<script src=\"/static/scripts/sx-evaluator.js\"></script>\n<script type=\"text/sx-lib\">\n ;; concatenated web/ framework: signals, deps, router,\n ;; engine, orchestration, boot\n</script>\n<script type=\"text/sx\" data-components>\n ;; page component definitions\n</script>" "html"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Boot chicken-and-egg")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Boot chicken-and-egg")
|
||||
(p (code "boot.sx") " orchestrates the boot sequence but is itself web framework code. Solution: thin native boot shim (~30 lines) in " (code "sx-platform.js") ":")
|
||||
(~docs/code :src (highlight "SxPlatform.boot = function(evaluator) {\n // 1. Evaluate web framework .sx libraries\n var libs = document.querySelectorAll('script[type=\"text/sx-lib\"]');\n for (var i = 0; i < libs.length; i++) {\n evaluator.evalSource(libs[i].textContent);\n }\n // 2. Call boot-init (defined in boot.sx)\n evaluator.callFunction('boot-init');\n};" "javascript"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Performance")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Performance")
|
||||
(p "Parsing + evaluating ~5,000 lines of web framework " (code ".sx") " at startup takes ~10\u201350ms. After " (code "define") ", functions are Lambda objects dispatched identically to compiled functions. " (strong "Zero ongoing performance difference.")))
|
||||
|
||||
|
||||
@@ -229,22 +229,22 @@
|
||||
(~docs/section :title "Phase 3: Wire Up Rust/WASM" :id "phase-3"
|
||||
(p (strong "Goal:") " Rust evaluator calls " (code "sx-platform.js") " via wasm-bindgen imports. Handle table bridges DOM references.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Handle table (JS-side)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Handle table (JS-side)")
|
||||
(~docs/code :src (highlight "// In sx-wasm-shim.js\nconst handles = [null]; // index 0 = null handle\nfunction allocHandle(obj) { handles.push(obj); return handles.length - 1; }\nfunction getHandle(id) { return handles[id]; }\nfunction freeHandle(id) { handles[id] = null; }" "javascript"))
|
||||
(p "DOM nodes are JS objects. The handle table maps " (code "u32") " IDs to JS objects. Rust stores " (code "Value::Handle(u32)") " and passes the " (code "u32") " to imported JS functions.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Value::Handle in Rust")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Value::Handle in Rust")
|
||||
(~docs/code :src (highlight "// In platform.rs\npub enum Value {\n // ... existing variants ...\n Handle(u32), // opaque reference to JS-side object\n}" "rust"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "WASM imports from platform")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "WASM imports from platform")
|
||||
(~docs/code :src (highlight "#[wasm_bindgen(module = \"/sx-platform-wasm.js\")]\nextern \"C\" {\n fn platform_create_element(tag: &str) -> u32;\n fn platform_create_text_node(text: &str) -> u32;\n fn platform_set_attr(handle: u32, name: &str, value: &str);\n fn platform_append_child(parent: u32, child: u32);\n fn platform_add_event_listener(handle: u32, event: &str, callback_id: u32);\n // ... ~50 DOM primitives\n}" "rust"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Callback table for events")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Callback table for events")
|
||||
(p "When Rust creates an event handler (a Lambda), it stores it in a callback table and gets a " (code "u32") " ID. JS " (code "addEventListener") " wraps it: when the event fires, JS calls into WASM with the callback ID. Rust looks up the Lambda and evaluates it.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "sx-wasm-shim.js")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "sx-wasm-shim.js")
|
||||
(p "Thin glue (~100 lines):")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li "Instantiate WASM module")
|
||||
(li "Wire handle table")
|
||||
(li "Delegate all platform calls to " (code "sx-platform.js"))
|
||||
@@ -258,8 +258,8 @@
|
||||
(~docs/section :title "Phase 4: Web Framework Loading" :id "phase-4"
|
||||
(p (strong "Goal:") " Both JS and WASM evaluators load the same web framework " (code ".sx") " files at runtime.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Boot sequence (identical for both evaluators)")
|
||||
(ol :class "list-decimal list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Boot sequence (identical for both evaluators)")
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 mt-2")
|
||||
(li "Load " (code "sx-platform.js") " + evaluator (" (code "sx-evaluator.js") " or " (code "sx-wasm-shim.js") ")")
|
||||
(li "Platform registers primitives with evaluator")
|
||||
(li "Platform boot shim evaluates " (code "<script type=\"text/sx-lib\">") " blocks")
|
||||
@@ -267,7 +267,7 @@
|
||||
(li (code "boot-init") " called \u2014 processes component scripts, hydrates, initializes engine")
|
||||
(li "Page is interactive"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Library dependency order")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Library dependency order")
|
||||
(~docs/code :src (highlight "signals.sx \u2190 no SX deps (uses only core primitives)\ndeps.sx \u2190 no SX deps\nframes.sx \u2190 no SX deps\nrouter.sx \u2190 no SX deps\npage-helpers.sx \u2190 no SX deps\nengine.sx \u2190 uses render.sx (core), adapter-dom.sx (core)\norchestration.sx \u2190 depends on engine.sx\nboot.sx \u2190 depends on orchestration.sx" "text")))
|
||||
|
||||
|
||||
@@ -276,17 +276,17 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Phase 5: Verification + Rollout" :id "phase-5"
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Shadow comparison")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Shadow comparison")
|
||||
(p "Run both JS and WASM evaluators on same input, compare outputs:")
|
||||
(ol :class "list-decimal list-inside space-y-1 mt-2"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-1 mt-2")
|
||||
(li (strong "Parse") " \u2014 same AST (already testable with current WASM exports)")
|
||||
(li (strong "Eval") " \u2014 same values (already testable)")
|
||||
(li (strong "Render") " \u2014 same DOM structure (requires Phase 3)"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Feature flag")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Feature flag")
|
||||
(p "Server sets " (code "data-sx-runtime=\"wasm\"") " or " (code "\"js\"") " on root element. Boot shim loads appropriate evaluator. Progressive enhancement: try WASM, fall back to JS.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Test matrix")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Test matrix")
|
||||
(p "All " (code "test-*.sx") " files from both " (code "spec/") " and " (code "web/") " run on BOTH evaluators."))
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "Core is what defines the language.") " Parser, evaluator, primitives, render modes. If you can't run SX without it, it's core (" (code "spec/") "). If you can write it " (em "in") " SX, it's web framework (" (code "web/") ").")
|
||||
(li (strong "Web framework runs ON the evaluator.") " Signals, engine, orchestration, boot, router, deps \u2014 these are SX programs. They're evaluated at runtime, not compiled into the evaluator.")
|
||||
(li (strong "Isolation is the boundary.") " The evaluator can't touch the real world. Platform primitives are the only bridge. JS and WASM evaluators have identical isolation.")
|
||||
@@ -309,7 +309,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Interaction with Other Plans" :id "interactions"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong (a :href "/sx/(etc.(plan.rust-wasm-host))" "Rust/WASM Host")) " \u2014 this plan supersedes it. That plan didn't distinguish core from web framework or propose evaluator isolation. The shared platform layer is the key architectural difference.")
|
||||
(li (strong (a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM")) " \u2014 complementary. This plan bootstraps the tree-walking evaluator. A future bytecode VM compiles SX to bytecodes. The platform layer and handle table are shared.")
|
||||
(li (strong (a :href "/sx/(etc.(plan.runtime-slicing))" "Runtime Slicing")) " \u2014 simplified. With web framework as runtime-evaluated " (code ".sx") ", slicing becomes: ship core + only the framework files you need. L0 pages skip signals/engine entirely.")
|
||||
@@ -323,7 +323,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After completion:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Core evaluator is ~3,200 lines of bootstrapped spec (" (code "spec/") " + adapters from " (code "web/") ").")
|
||||
(li "JS evaluator is isolated \u2014 can't touch the real world, same sandbox as WASM.")
|
||||
(li "Shared " (code "sx-platform.js") " provides all browser primitives to both evaluators.")
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
(~docs/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 "/sx/(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."))
|
||||
(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 "/sx/(language.(spec.deps))" (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(ul :class "space-y-2 text-stone-700 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-700 list-disc pl-5")
|
||||
(li (strong "Primitive parity: ") "100%. ~80 pure primitives, same names/semantics, JS and Python.")
|
||||
(li (strong "eval/parse/render: ") "Complete both sides. sx-ref.js has eval, parse, render-to-html, render-to-dom, aser.")
|
||||
(li (strong "Engine: ") "engine.sx (morph, swaps, triggers, history), orchestration.sx (fetch, events), boot.sx (hydration) — all transpiled.")
|
||||
@@ -29,13 +29,13 @@
|
||||
|
||||
(~docs/section :title "Phase 1: Component Distribution & Dependency Analysis" :id "phase-1"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
|
||||
(a :href "/sx/(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."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-green-700 underline text-sm font-medium") "View canonical spec: deps.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.bundle-analyzer))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live bundle analyzer"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Per-page component bundles instead of sending every definition to every page. Smaller payloads, faster boot, better cache hit rates."))
|
||||
|
||||
(~docs/subsection :title "The Problem"
|
||||
(p "The page boot payload serializes every component definition in the environment. A page that uses 5 components still receives all 50+. No mechanism determines which components a page actually needs — the boundary between \"loaded\" and \"used\" is invisible."))
|
||||
@@ -43,12 +43,12 @@
|
||||
(~docs/subsection :title "Implementation"
|
||||
|
||||
(p "The dependency analysis algorithm is defined in "
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-violet-700 underline") "deps.sx")
|
||||
" — a spec module bootstrapped to every host. Each host loads it via " (code "--spec-modules deps") " and provides 6 platform functions. The spec is the single source of truth; hosts are interchangeable.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Transitive closure (deps.sx)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Transitive closure (deps.sx)")
|
||||
(p "9 functions that walk the component graph. The core:")
|
||||
(~docs/code :src (highlight "(define (transitive-deps name env)\n (let ((key (if (starts-with? name \"~\") name\n (concat \"~\" name)))\n (seen (set-create)))\n (transitive-deps-walk key env seen)\n (set-remove seen key)))" "lisp"))
|
||||
(p (code "scan-refs") " walks a component body AST collecting " (code "~") " symbols. "
|
||||
@@ -57,18 +57,18 @@
|
||||
"Circular references terminate safely via a seen-set."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Page scanning")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Page scanning")
|
||||
(~docs/code :src (highlight "(define (components-needed page-source env)\n (let ((direct (scan-components-from-source page-source))\n (all-needed (set-create)))\n (for-each (fn (name) ...\n (set-add! all-needed name)\n (set-union! all-needed (component-deps comp)))\n direct)\n all-needed))" "lisp"))
|
||||
(p (code "scan-components-from-source") " finds " (code "(~plans/content-addressed-components/name") " patterns in serialized SX via regex. " (code "components-needed") " combines scanning with the cached transitive closure to produce the minimal component set for a page."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Per-page CSS scoping")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Per-page CSS scoping")
|
||||
(p (code "page-css-classes") " unions the CSS classes from only the components in the page bundle. Pages that don't use a component never pay for its styles."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Platform interface")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Platform interface")
|
||||
(p "The spec declares 6 functions each host implements natively — the only host-specific code:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "component-deps") " / " (code "component-set-deps!") " — read/write the cached deps set on a component object")
|
||||
(li (code "component-css-classes") " — read pre-scanned CSS class names from a component")
|
||||
(li (code "env-components") " — enumerate all component entries in an environment")
|
||||
@@ -76,16 +76,16 @@
|
||||
|
||||
(~docs/subsection :title "Spec module"
|
||||
(p "deps.sx is loaded as a " (strong "spec module") " — an optional extension to the core spec. The bootstrapper flag " (code "--spec-modules deps") " includes it in the generated output alongside the core evaluator, parser, and renderer. Phase 2 IO detection was added to the same module — same bootstrapping mechanism, no architecture changes needed.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/deps.sx — canonical spec (14 functions, 8 platform declarations)")
|
||||
(li "Bootstrapped to all host targets via --spec-modules deps")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "15 dedicated tests: scan, transitive closure, circular deps, compute-all, components-needed")
|
||||
(li "Bootstrapped output verified on both host targets")
|
||||
(li "Full test suite passes with zero regressions")
|
||||
(li (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app"))))
|
||||
(li (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" (~tw :tokens "text-violet-700 underline") "Live bundle analyzer") " shows real per-page savings on this app"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 2
|
||||
@@ -93,41 +93,41 @@
|
||||
|
||||
(~docs/section :title "Phase 2: Smart Server/Client Boundary — IO Detection" :id "phase-2"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
|
||||
(a :href "/sx/(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."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-green-700 underline text-sm font-medium") "View canonical spec: deps.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.bundle-analyzer))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live bundle analyzer with IO"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "IO Detection in the Spec"
|
||||
(p "Five new functions in "
|
||||
(a :href "/sx/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
|
||||
(a :href "/sx/(language.(spec.deps))" (~tw :tokens "text-violet-700 underline") "deps.sx")
|
||||
" extend the Phase 1 walker to detect IO primitive references:")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. IO scanning")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. IO scanning")
|
||||
(p (code "scan-io-refs") " walks an AST node, collecting symbol names that match an IO name set. The IO set is provided by the host from boundary declarations (all three tiers: core IO, deployment IO, page helpers).")
|
||||
(~docs/code :src (highlight "(define scan-io-refs\n (fn (node io-names)\n (let ((refs (list)))\n (scan-io-refs-walk node io-names refs)\n refs)))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Transitive IO closure")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Transitive IO closure")
|
||||
(p (code "transitive-io-refs") " follows component deps recursively, unioning IO refs from all reachable components and macros. Cycle-safe via seen-set.")
|
||||
(~docs/code :src (highlight "(define transitive-io-refs\n (fn (name env io-names)\n ;; Walk deps, scan each body for IO refs,\n ;; union all refs transitively.\n ...))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Batch computation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Batch computation")
|
||||
(p (code "compute-all-io-refs") " iterates the env, computes transitive IO refs for each component, and caches the result via " (code "component-set-io-refs!") ". Called after " (code "compute-all-deps") " at component registration time."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Component metadata")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Component metadata")
|
||||
(p "Each component now carries " (code "io_refs") " (transitive IO primitive names) alongside " (code "deps") " and " (code "css_classes") ". The derived " (code "is_pure") " property is true when " (code "io_refs") " is empty — the component can render anywhere without server data."))))
|
||||
|
||||
(~docs/subsection :title "Selective Expansion"
|
||||
(p "The partial evaluator " (code "_aser") " now uses per-component IO metadata instead of a global toggle:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "IO-dependent") " → expand server-side (IO must resolve)")
|
||||
(li (strong "Pure") " → serialize for client (let client render)")
|
||||
(li (strong "Layout slot context") " → all components still expand (backwards compat via " (code "_expand_components") " context var)"))
|
||||
@@ -135,17 +135,17 @@
|
||||
|
||||
(~docs/subsection :title "Platform interface additions"
|
||||
(p "Two new platform functions each host implements:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "(component-io-refs c) → cached IO ref list")
|
||||
(li "(component-set-io-refs! c refs) → cache IO refs on component")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Components calling (query ...) or (highlight ...) classified IO-dependent")
|
||||
(li "Pure components (HTML-only) classified pure with empty io_refs")
|
||||
(li "Transitive IO detection: component calling ~other where ~other calls (current-user) → IO-dependent")
|
||||
(li "Bootstrapped to both hosts (sx_ref.py + sx-ref.js)")
|
||||
(li (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification"))))
|
||||
(li (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" (~tw :tokens "text-violet-700 underline") "Live bundle analyzer") " shows per-page IO classification"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 3
|
||||
@@ -153,34 +153,34 @@
|
||||
|
||||
(~docs/section :title "Phase 3: Client-Side Routing (SPA Mode)" :id "phase-3"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(div :class "flex items-center gap-2 mb-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
|
||||
(a :href "/sx/(language.(spec.router))" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
|
||||
(a :href "/sx/(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."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(language.(spec.router))" (~tw :tokens "text-green-700 underline text-sm font-medium") "View canonical spec: router.sx")
|
||||
(a :href "/sx/(geography.(isomorphism.routing-analyzer))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live routing analyzer"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "Architecture"
|
||||
(p "Three-layer approach: spec defines pure route matching, page registry bridges server metadata to client, orchestration intercepts navigation for try-first/fallback.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Route matching spec (router.sx)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Route matching spec (router.sx)")
|
||||
(p "New spec module with pure functions for Flask-style route pattern matching:")
|
||||
(~docs/code :src (highlight "(define split-path-segments ;; \"/language/docs/hello\" → (\"docs\" \"hello\")\n(define parse-route-pattern ;; \"/language/docs/<slug>\" → segment descriptors\n(define match-route-segments ;; segments + pattern → params dict or nil\n(define find-matching-route ;; path + route table → first match" "lisp"))
|
||||
(p "No platform interface needed — uses only pure string and list primitives. Bootstrapped to both hosts via " (code "--spec-modules deps,router") "."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Page registry")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Page registry")
|
||||
(p "Server serializes defpage metadata as SX dict literals inside " (code "<script type=\"text/sx-pages\">") ". Each entry carries name, path pattern, auth level, has-data flag, serialized content expression, and closure values.")
|
||||
(~docs/code :src (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :content \"(case slug ...)\" :closure {}}" "lisp"))
|
||||
(p "boot.sx processes these at startup using the SX parser — the same " (code "parse") " function from parser.sx — building route entries with parsed patterns into the " (code "_page-routes") " table. No JSON dependency."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Client-side interception (orchestration.sx)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Client-side interception (orchestration.sx)")
|
||||
(p (code "bind-client-route-link") " replaces " (code "bind-boost-link") " in boost processing. On click:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Extract pathname from href")
|
||||
(li "Call " (code "find-matching-route") " against " (code "_page-routes"))
|
||||
(li "If match found AND no :data: evaluate content expression locally with component env + URL params")
|
||||
@@ -190,24 +190,24 @@
|
||||
|
||||
(~docs/subsection :title "What becomes client-routable"
|
||||
(p "All pages with content expressions — most of this docs app. Pure pages render instantly; :data pages fetch data then render client-side (Phase 4):")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "/") ", " (code "/language/docs/") ", " (code "/language/docs/<slug>") " (most slugs), " (code "/applications/protocols/") ", " (code "/applications/protocols/<slug>"))
|
||||
(li (code "/examples/") ", " (code "/examples/<slug>") ", " (code "/etc/essays/") ", " (code "/etc/essays/<slug>"))
|
||||
(li (code "/etc/plans/") ", " (code "/etc/plans/<slug>") ", " (code "/geography/isomorphism/") ", " (code "/language/bootstrappers/")))
|
||||
(p "Pages that fall through to server:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "/language/docs/primitives") " and " (code "/language/docs/special-forms") " (call " (code "primitives-data") " / " (code "special-forms-data") " helpers)")
|
||||
(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 "/sx/(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 "/sx/(geography.(isomorphism.data-test))" (~tw :tokens "text-violet-700 underline") "Phase 4 demo") ")")))
|
||||
|
||||
(~docs/subsection :title "Try-first/fallback design"
|
||||
(p "Client routing uses a try-first approach: attempt local evaluation in a try/catch, fall back to server fetch on any failure. This avoids needing perfect static analysis of content expressions — if a content expression calls a page helper the client doesn't have, the eval throws, and the server handles it transparently.")
|
||||
(p "Console messages provide visibility: " (code "sx:route client /essays/why-sexps") " vs " (code "sx:route server /specs/eval") "."))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/router.sx — route pattern matching spec")
|
||||
(li "shared/sx/ref/boot.sx — process page registry scripts")
|
||||
(li "shared/sx/ref/orchestration.sx — client route interception")
|
||||
@@ -216,7 +216,7 @@
|
||||
(li "shared/sx/helpers.py — page registry SX serialization")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Pure page navigation: zero server requests, console shows \"sx:route client\"")
|
||||
(li "IO/data page fallback: falls through to server fetch transparently")
|
||||
(li "Browser back/forward works with client-routed pages")
|
||||
@@ -229,39 +229,39 @@
|
||||
|
||||
(~docs/section :title "Phase 4: Client Async & IO Bridge" :id "phase-4"
|
||||
|
||||
(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 "/sx/(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."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism.data-test))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live data test page"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "Architecture"
|
||||
(p "Separates IO from rendering. Server evaluates :data expression (async, with DB/service access), serializes result as SX wire format. Client fetches pre-evaluated data, parses it, merges into env, renders pure :content client-side.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Abstract resolve-page-data")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Abstract resolve-page-data")
|
||||
(p "Spec-level primitive in orchestration.sx. The spec says \"I need data for this page\" — platform provides transport:")
|
||||
(~docs/code :src (highlight "(resolve-page-data page-name params\n (fn (data)\n ;; data is a dict — merge into env and render\n (let ((env (merge closure params data))\n (rendered (try-eval-content content-src env)))\n (swap-rendered-content target rendered pathname))))" "lisp"))
|
||||
(p "Browser platform: HTTP fetch to " (code "/sx/data/<page-name>") ". Future platforms could use IPC, cache, WebSocket, etc."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Server data endpoint")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Server data endpoint")
|
||||
(p (code "evaluate_page_data()") " evaluates the :data expression, kebab-cases dict keys (Python " (code "total_count") " → SX " (code "total-count") "), serializes as SX wire format.")
|
||||
(p "Response content type: " (code "text/sx; charset=utf-8") ". Per-page auth enforcement via " (code "_check_page_auth()") "."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Client data cache")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Client data cache")
|
||||
(p "In-memory cache in orchestration.sx, keyed by " (code "page-name:param=value") ". 30-second TTL prevents redundant fetches on back/forward navigation:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(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 "/sx/(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 "/sx/(geography.(isomorphism.data-test))" (~tw :tokens "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)."))))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/orchestration.sx — resolve-page-data spec, data cache")
|
||||
(li "shared/sx/ref/bootstrap_js.py — platform resolvePageData (HTTP fetch)")
|
||||
(li "shared/sx/pages.py — evaluate_page_data(), auto_mount_page_data()")
|
||||
@@ -270,10 +270,10 @@
|
||||
(li "shared/sx/tests/test_page_data.py — 30 unit tests")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "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 "/sx/(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 "/sx/(geography.(isomorphism.data-test))" (~tw :tokens "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"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -282,26 +282,26 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Client IO Proxy" :id "phase-5"
|
||||
|
||||
(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"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "Components with IO dependencies render client-side. IO primitives are proxied to the server — the client evaluator calls them like normal functions, the proxy fetches results via HTTP, the async DOM renderer awaits the promises and continues."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Components with IO dependencies render client-side. IO primitives are proxied to the server — the client evaluator calls them like normal functions, the proxy fetches results via HTTP, the async DOM renderer awaits the promises and continues."))
|
||||
|
||||
(~docs/subsection :title "How it works"
|
||||
(p "Instead of async-aware continuations (originally planned), Phase 5 was solved by combining three mechanisms that emerged from Phases 3-4:")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. IO dependency detection (from Phase 2)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. IO dependency detection (from Phase 2)")
|
||||
(p "The component dep analyzer scans AST bodies for IO primitive names (highlight, asset-url, query, frag, etc.) and computes transitive IO refs. Pages include their IO dep list in the page registry."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. IO proxy registration")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. IO proxy registration")
|
||||
(p (code "registerIoDeps(names)") " in orchestration.sx registers proxy functions for each IO primitive. When the client evaluator encounters " (code "(highlight code \"sx\")") ", the proxy sends an HTTP request to the server's IO endpoint and returns a Promise."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Async DOM renderer")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Async DOM renderer")
|
||||
(p (code "asyncRenderToDom") " walks the expression tree and handles Promises transparently. When a subexpression returns a Promise (from an IO proxy call), the renderer awaits it and continues building the DOM tree. No continuations needed — JavaScript's native Promise mechanism provides the suspension."))))
|
||||
|
||||
(~docs/subsection :title "Why continuations weren't needed"
|
||||
@@ -309,14 +309,14 @@
|
||||
(p "Delimited continuations remain valuable for Phase 6 (streaming/suspense on the " (em "server") " side, where Python doesn't have native Promise-based suspension in the evaluator). But for client-side IO, Promises + async render were sufficient."))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/orchestration.sx — registerIoDeps, IO proxy registration")
|
||||
(li "shared/sx/ref/bootstrap_js.py — asyncRenderToDom, IO proxy HTTP transport")
|
||||
(li "shared/sx/helpers.py — io_deps in page registry entries")
|
||||
(li "shared/sx/deps.py — transitive IO ref computation")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Navigate to any page with IO deps (e.g. /testing/eval) — console shows IO proxy calls")
|
||||
(li "Components using " (code "highlight") " render correctly via proxy")
|
||||
(li "Pages with " (code "asset-url") " resolve script paths via proxy")
|
||||
@@ -328,15 +328,15 @@
|
||||
|
||||
(~docs/section :title "Phase 6: Streaming & Suspense" :id "phase-6"
|
||||
|
||||
(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 "/sx/(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."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism.streaming))" (~tw :tokens "text-green-700 underline text-sm font-medium") "Live streaming demo"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(~docs/subsection :title "What was built"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "~shared:pages/suspense") " component — renders fallback content with a stable DOM ID, replaced when resolution arrives")
|
||||
(li (code "defpage :stream true") " — opts a page into streaming response mode")
|
||||
(li (code "defpage :fallback expr") " — custom loading skeleton for streaming pages")
|
||||
@@ -349,35 +349,35 @@
|
||||
|
||||
(~docs/subsection :title "Architecture"
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Suspense component")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Suspense component")
|
||||
(p "When streaming, the server renders the page with " (code "~shared:pages/suspense") " placeholders instead of awaiting IO:")
|
||||
(~docs/code :src (highlight "(~app-body\n :header-rows (~shared:pages/suspense :id \"stream-headers\"\n :fallback (div :class \"h-12 bg-stone-200 animate-pulse\"))\n :content (~shared:pages/suspense :id \"stream-content\"\n :fallback (div :class \"p-8 animate-pulse\" ...)))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Chunked transfer")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Chunked transfer")
|
||||
(p "Quart async generator response yields chunks in order:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "HTML shell + CSS + component defs + page registry + suspense page SX + scripts (immediate)")
|
||||
(li "Resolution " (code "<script>") " tags as each IO completes")
|
||||
(li "Closing " (code "</body></html>"))))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Client resolution")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Client resolution")
|
||||
(p "Each resolution chunk is an inline script:")
|
||||
(~docs/code :src (highlight "<script>\n window.__sxResolve(\"stream-content\",\n \"(~article :title \\\"Hello\\\")\")\n</script>" "html"))
|
||||
(p "The client parses the SX, renders to DOM, and replaces the suspense placeholder's children."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Concurrent IO")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Concurrent IO")
|
||||
(p "Data evaluation and header construction run in parallel. " (code "asyncio.wait(FIRST_COMPLETED)") " yields resolution chunks in whatever order IO completes — no artificial sequencing."))))
|
||||
|
||||
(~docs/subsection :title "Continuation foundation"
|
||||
(p "Delimited continuations (" (code "reset") "/" (code "shift") ") are implemented in the Python evaluator (async_eval.py lines 586-624) and available as special forms. Phase 6 uses the simpler pattern of concurrent IO + completion-order streaming, but the continuation machinery is in place for Phase 7's more sophisticated evaluation-level suspension."))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/templates/pages.sx — ~shared:pages/suspense component definition")
|
||||
(li "shared/sx/types.py — PageDef.stream, PageDef.fallback_expr fields")
|
||||
(li "shared/sx/evaluator.py — defpage :stream/:fallback parsing")
|
||||
@@ -392,16 +392,16 @@
|
||||
(li "sx/sxc/pages/helpers.py — streaming-demo-data page helper")))
|
||||
|
||||
(~docs/subsection :title "Demonstration"
|
||||
(p "The " (a :href "/sx/(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 "/sx/(geography.(isomorphism.streaming))" :class "text-violet-700 underline" "/sx/(geography.(isomorphism.streaming))"))
|
||||
(p "The " (a :href "/sx/(geography.(isomorphism.streaming))" (~tw :tokens "text-violet-700 underline") "streaming demo page") " exercises the full pipeline:")
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Navigate to " (a :href "/sx/(geography.(isomorphism.streaming))" (~tw :tokens "text-violet-700 underline") "/sx/(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")
|
||||
(li "The document response shows multiple chunks arriving over time: shell first, then resolution scripts")))
|
||||
|
||||
(~docs/subsection :title "What to verify"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Instant shell: ") "The page HTML arrives immediately — no waiting for the 1.5s data fetch")
|
||||
(li (strong "Suspense placeholders: ") "The " (code "~shared:pages/suspense") " component renders a " (code "data-suspense") " wrapper with animated fallback content")
|
||||
(li (strong "Resolution: ") "The " (code "__sxResolve()") " inline script replaces the placeholder with real rendered content")
|
||||
@@ -415,24 +415,24 @@
|
||||
|
||||
(~docs/section :title "Phase 7: Full Isomorphism" :id "phase-7"
|
||||
|
||||
(div :class "rounded border border-green-200 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"))
|
||||
(p :class "text-green-900 font-medium" "What it enables")
|
||||
(p :class "text-green-800" "Same SX code runs on either side. Runtime chooses optimal split via affinity annotations and render plans. Client data cache managed via invalidation headers and server-driven updates. Cross-host isomorphism verified by 61 automated tests."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-green-800") "Same SX code runs on either side. Runtime chooses optimal split via affinity annotations and render plans. Client data cache managed via invalidation headers and server-driven updates. Cross-host isomorphism verified by 61 automated tests."))
|
||||
|
||||
(~docs/subsection :title "7a. Affinity Annotations & Render Target"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-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"))
|
||||
(p :class "text-green-800 text-sm" "Components declare where they prefer to render. The spec combines affinity with IO analysis to produce a per-component render target decision."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Components declare where they prefer to render. The spec combines affinity with IO analysis to produce a per-component render target decision."))
|
||||
|
||||
(p "Affinity annotations let component authors express rendering preferences:")
|
||||
(~docs/code :src (highlight "(defcomp ~plans/isomorphic/product-grid (&key products)\n :affinity :client ;; interactive, prefer client rendering\n (div ...))\n\n(defcomp ~plans/isomorphic/auth-menu (&key user)\n :affinity :server ;; auth-sensitive, always server\n (div ...))\n\n(defcomp ~plans/isomorphic/card (&key title)\n ;; no annotation = :affinity :auto (default)\n ;; runtime decides from IO analysis\n (div ...))" "lisp"))
|
||||
|
||||
(p "The " (code "render-target") " function in deps.sx combines affinity with IO analysis:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code ":affinity :server") " → always " (code "\"server\"") " (auth-sensitive, secrets, heavy IO)")
|
||||
(li (code ":affinity :client") " → always " (code "\"client\"") " (interactive, IO proxied)")
|
||||
(li (code ":affinity :auto") " (default) → " (code "\"server\"") " if IO-dependent, " (code "\"client\"") " if pure"))
|
||||
@@ -440,7 +440,7 @@
|
||||
(p "The server's partial evaluator (" (code "_aser") ") uses " (code "render_target") " instead of the previous " (code "is_pure") " check. Components with " (code ":affinity :client") " are serialized for client rendering even if they call IO primitives — the IO proxy (Phase 5) handles the calls.")
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/eval.sx — defcomp annotation parsing, defcomp-kwarg helper")
|
||||
(li "shared/sx/ref/deps.sx — render-target function, platform interface")
|
||||
(li "shared/sx/types.py — Component.affinity field, render_target property")
|
||||
@@ -452,7 +452,7 @@
|
||||
(li "shared/sx/ref/test-deps.sx — 6 new render-target tests")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "269 spec tests pass (10 new: 4 eval + 6 deps)")
|
||||
(li "79 Python unit tests pass")
|
||||
(li "Bootstrapped to both hosts (sx_ref.py + sx-browser.js)")
|
||||
@@ -460,10 +460,10 @@
|
||||
|
||||
(~docs/subsection :title "7b. Runtime Boundary Optimizer"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-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"))
|
||||
(p :class "text-green-800 text-sm" "Per-page render plans computed at registration time. Each page knows exactly which components render server-side vs client-side, cached on PageDef."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Per-page render plans computed at registration time. Each page knows exactly which components render server-side vs client-side, cached on PageDef."))
|
||||
|
||||
(p "Given component tree + IO dependency graph + affinity annotations, decide per-component: server-expand, client-render, or stream. Planning step cached at registration, recomputed on component change.")
|
||||
|
||||
@@ -471,7 +471,7 @@
|
||||
(~docs/code :src (highlight "(page-render-plan page-source env io-names)\n;; Returns:\n;; {:components {~plans/content-addressed-components/name \"server\"|\"client\" ...}\n;; :server (list of server-expanded names)\n;; :client (list of client-rendered names)\n;; :io-deps (IO primitives needed by server components)}" "lisp"))
|
||||
|
||||
(~docs/subsection :title "Integration Points"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "shared/sx/ref/deps.sx") " — " (code "page-render-plan") " spec function")
|
||||
(li (code "shared/sx/deps.py") " — Python wrapper, dispatches to bootstrapped code")
|
||||
(li (code "shared/sx/pages.py") " — " (code "compute_page_render_plans()") " called at mount time, caches on PageDef")
|
||||
@@ -479,17 +479,17 @@
|
||||
(li (code "shared/sx/types.py") " — " (code "PageDef.render_plan") " field")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "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 "/sx/(geography.(isomorphism.affinity))" "affinity demo page"))
|
||||
(li "Client page registry includes :render-plan for each page"))))
|
||||
|
||||
(~docs/subsection :title "7c. Cache Invalidation & Optimistic Data Updates"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-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"))
|
||||
(p :class "text-green-800 text-sm" "Client data cache management, optimistic predicted mutations with snapshot rollback, and server-driven cache updates."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Client data cache management, optimistic predicted mutations with snapshot rollback, and server-driven cache updates."))
|
||||
|
||||
(p "The client-side page data cache (30-second TTL) now supports cache invalidation, server-driven updates, and optimistic mutations. The client predicts the result of a mutation, immediately re-renders with the predicted data, and confirms or reverts when the server responds.")
|
||||
|
||||
@@ -497,12 +497,12 @@
|
||||
(p "Component authors can declare cache invalidation on elements that trigger mutations:")
|
||||
(~docs/code :src (highlight ";; Clear specific page's cache after successful action\n(form :sx-post \"/cart/remove\"\n :sx-cache-invalidate \"cart-page\"\n ...)\n\n;; Clear ALL page caches after action\n(button :sx-post \"/admin/reset\"\n :sx-cache-invalidate \"*\")" "lisp"))
|
||||
(p "The server can also control client cache via response headers:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "SX-Cache-Invalidate: page-name") " — clear cache for a page")
|
||||
(li (code "SX-Cache-Update: page-name") " — replace cache with the response body (SX-format data)")))
|
||||
|
||||
(~docs/subsection :title "Optimistic Mutations"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "optimistic-cache-update") " — applies a mutator function to cached data, saves a snapshot for rollback")
|
||||
(li (strong "optimistic-cache-revert") " — restores the pre-mutation snapshot if the server rejects")
|
||||
(li (strong "optimistic-cache-confirm") " — discards the snapshot after server confirmation")
|
||||
@@ -510,33 +510,33 @@
|
||||
(li (strong "/sx/action/<name>") " — server endpoint for processing mutations (POST, returns SX wire format)")))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/ref/orchestration.sx — cache management + optimistic cache functions + submit-mutation spec")
|
||||
(li "shared/sx/ref/engine.sx — SX-Cache-Invalidate, SX-Cache-Update response headers")
|
||||
(li "shared/sx/pages.py — mount_action_endpoint for /sx/action/<name>")
|
||||
(li "sx/sx/optimistic-demo.sx — live demo component")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "Live demo at " (a :href "/sx/(geography.(isomorphism.optimistic))" :class "text-violet-600 hover:underline" "/sx/(geography.(isomorphism.optimistic))"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Live demo at " (a :href "/sx/(geography.(isomorphism.optimistic))" (~tw :tokens "text-violet-600 hover:underline") "/sx/(geography.(isomorphism.optimistic))"))
|
||||
(li "Console log: " (code "sx:optimistic confirmed") " / " (code "sx:optimistic reverted")))))
|
||||
|
||||
(~docs/subsection :title "7d. Offline Data Layer"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-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"))
|
||||
(p :class "text-green-800 text-sm" "Service Worker with IndexedDB caching, connectivity tracking, and offline mutation queue with replay on reconnect."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Service Worker with IndexedDB caching, connectivity tracking, and offline mutation queue with replay on reconnect."))
|
||||
|
||||
(p "A Service Worker registered at " (code "/sx-sw.js") " provides three-tier caching, plus an offline mutation queue that builds on Phase 7c's optimistic updates:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "/sx/data/* ") "— network-first with IndexedDB fallback. Page data cached on fetch, served from IndexedDB when offline.")
|
||||
(li (strong "/sx/io/* ") "— network-first with IndexedDB fallback. IO proxy responses cached the same way.")
|
||||
(li (strong "/static/* ") "— stale-while-revalidate via Cache API. Serves cached assets immediately, updates in background.")
|
||||
(li (strong "Offline mutations") " — " (code "offline-aware-mutation") " routes to " (code "submit-mutation") " when online, " (code "offline-queue-mutation") " when offline. " (code "offline-sync") " replays the queue on reconnect."))
|
||||
|
||||
(~docs/subsection :title "How It Works"
|
||||
(ol :class "list-decimal list-inside text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal list-inside text-stone-700 space-y-2")
|
||||
(li "On boot, " (code "sx-browser.js") " registers the SW at " (code "/sx-sw.js") " (root scope)")
|
||||
(li "SW intercepts fetch events and routes by URL pattern")
|
||||
(li "For data/IO: try network first, on failure serve from IndexedDB")
|
||||
@@ -545,51 +545,51 @@
|
||||
(li "Offline mutations queue locally, replay on reconnect via " (code "offline-sync"))))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/static/scripts/sx-sw.js — Service Worker (network-first + stale-while-revalidate)")
|
||||
(li "shared/sx/ref/orchestration.sx — offline queue, sync, connectivity tracking, sw-post-message")
|
||||
(li "shared/sx/pages.py — mount_service_worker() serves SW at /sx-sw.js")
|
||||
(li "sx/sx/offline-demo.sx — live demo component")))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "Live demo at " (a :href "/sx/(geography.(isomorphism.offline))" :class "text-violet-600 hover:underline" "/sx/(geography.(isomorphism.offline))"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Live demo at " (a :href "/sx/(geography.(isomorphism.offline))" (~tw :tokens "text-violet-600 hover:underline") "/sx/(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")))))
|
||||
|
||||
(~docs/subsection :title "7e. Isomorphic Testing"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-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"))
|
||||
(p :class "text-green-800 text-sm" "Cross-host test suite: same SX expressions evaluated on Python (sx_ref.py) and JS (sx-browser.js via Node.js), HTML output compared."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "Cross-host test suite: same SX expressions evaluated on Python (sx_ref.py) and JS (sx-browser.js via Node.js), HTML output compared."))
|
||||
|
||||
(p "61 isomorphic tests verify that Python and JS produce identical results:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "37 eval tests: arithmetic, comparison, strings, collections, logic, let/lambda, higher-order, dict, keywords, cond/case")
|
||||
(li "24 render tests: elements, attributes, nesting, void elements, boolean attrs, conditionals, map, components, affinity, HTML escaping"))
|
||||
|
||||
(~docs/subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "shared/sx/tests/test_isomorphic.py — cross-host test suite")
|
||||
(li "Run: " (code "python3 -m pytest shared/sx/tests/test_isomorphic.py -q")))))
|
||||
|
||||
(~docs/subsection :title "7f. Universal Page Descriptor"
|
||||
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-3 mb-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"))
|
||||
(p :class "text-green-800 text-sm" "defpage is portable: same descriptor executes on server (execute_page) and client (tryClientRoute)."))
|
||||
(div (~tw :tokens "rounded border border-green-300 bg-green-50 p-3 mb-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete"))
|
||||
(p (~tw :tokens "text-green-800 text-sm") "defpage is portable: same descriptor executes on server (execute_page) and client (tryClientRoute)."))
|
||||
|
||||
(p "The defpage descriptor is universal — the same definition works on both hosts:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Server: ") (code "execute_page()") " evaluates :data and :content slots, expands server components via " (code "_aser") ", returns SX wire format")
|
||||
(li (strong "Client: ") (code "try-client-route") " matches route, evaluates content SX, renders to DOM. Data pages fetch via " (code "/sx/data/") ", IO proxied via " (code "/sx/io/"))
|
||||
(li (strong "Render plan: ") "each page's " (code ":render-plan") " is included in the client page registry, showing which components render where")
|
||||
(li (strong "Console visibility: ") "client logs " (code "sx:route plan pagename — N server, M client") " on each navigation")))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "All previous phases.")))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Depends on: ") "All previous phases.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Cross-Cutting Concerns
|
||||
@@ -598,7 +598,7 @@
|
||||
(~docs/section :title "Cross-Cutting Concerns" :id "cross-cutting"
|
||||
|
||||
(~docs/subsection :title "Error Reporting"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Phase 1: \"Unknown component\" includes which page expected it and what bundle was sent")
|
||||
(li "Phase 2: Server logs which components expanded server-side vs sent to client")
|
||||
(li "Phase 3: Client route failures include unmatched path and available routes")
|
||||
@@ -606,7 +606,7 @@
|
||||
(li "Source location tracking in parser → propagate through eval → include in error messages")))
|
||||
|
||||
(~docs/subsection :title "Backward Compatibility"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Pages without annotations behave as today")
|
||||
(li "SX-Request / SX-Components / SX-Css header protocol continues")
|
||||
(li "Existing .sx files require no changes")
|
||||
@@ -621,61 +621,61 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Critical Files" :id "critical-files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Role")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phases")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phases")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/async_eval.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Core evaluator, _aser, server/client boundary")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "sx_page(), sx_response(), output pipeline")
|
||||
(td :class "px-3 py-2 text-stone-600" "1, 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/jinja_bridge.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "_COMPONENT_ENV, component registry")
|
||||
(td :class "px-3 py-2 text-stone-600" "1, 2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/pages.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "defpage, execute_page(), page lifecycle")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boot.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client boot, component caching")
|
||||
(td :class "px-3 py-2 text-stone-600" "1, 3, 4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/orchestration.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client fetch/swap/morph")
|
||||
(td :class "px-3 py-2 text-stone-600" "3, 4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/eval.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Evaluator spec")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/engine.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Morph, swaps, triggers")
|
||||
(td :class "px-3 py-2 text-stone-600" "3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/deps.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Dependency analysis (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "1, 2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/router.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client-side routing (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/io-bridge.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client IO primitives (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/suspense.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Streaming/suspension (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "5"))))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/async_eval.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Core evaluator, _aser, server/client boundary")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/helpers.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "sx_page(), sx_response(), output pipeline")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1, 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/jinja_bridge.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "_COMPONENT_ENV, component registry")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1, 2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/pages.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "defpage, execute_page(), page lifecycle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boot.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client boot, component caching")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1, 3, 4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/orchestration.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client fetch/swap/morph")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "3, 4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/eval.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Evaluator spec")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/engine.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Morph, swaps, triggers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/deps.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Dependency analysis (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1, 2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/router.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client-side routing (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/io-bridge.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client IO primitives (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/suspense.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Streaming/suspension (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "5"))))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; SX CI Pipeline
|
||||
|
||||
@@ -74,63 +74,63 @@
|
||||
(p "The JS bootstrapper has more moving parts than the Python one because "
|
||||
"JavaScript is the " (em "client") " host. The browser runtime includes "
|
||||
"things Python never needs:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Spec Module")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Purpose")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Python?")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Browser?")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Spec Module")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Purpose")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Python?")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Browser?")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "eval.sx")
|
||||
(td :class "px-4 py-2" "Core evaluator, special forms, TCO")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "render.sx")
|
||||
(td :class "px-4 py-2" "Tag registry, void elements, boolean attrs")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "parser.sx")
|
||||
(td :class "px-4 py-2" "Tokenizer, parser, serializer")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "adapter-html.sx")
|
||||
(td :class "px-4 py-2" "Render to HTML string")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "adapter-sx.sx")
|
||||
(td :class "px-4 py-2" "Serialize to SX wire format")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono" "adapter-dom.sx")
|
||||
(td :class "px-4 py-2" "Render to live DOM nodes")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono" "engine.sx")
|
||||
(td :class "px-4 py-2" "Fetch, swap, trigger, history")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono" "orchestration.sx")
|
||||
(td :class "px-4 py-2" "Element scanning, attribute processing")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono" "boot.sx")
|
||||
(td :class "px-4 py-2" "Script processing, mount, hydration")
|
||||
(td :class "px-4 py-2 text-center" "—") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "signals.sx")
|
||||
(td :class "px-4 py-2" "Reactive signal runtime")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "deps.sx")
|
||||
(td :class "px-4 py-2" "Component dependency analysis")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "router.sx")
|
||||
(td :class "px-4 py-2" "Client-side route matching")
|
||||
(td :class "px-4 py-2 text-center" "Yes") (td :class "px-4 py-2 text-center" "Yes")))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "eval.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Core evaluator, special forms, TCO")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "render.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Tag registry, void elements, boolean attrs")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "parser.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Tokenizer, parser, serializer")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "adapter-html.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Render to HTML string")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "adapter-sx.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Serialize to SX wire format")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "adapter-dom.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Render to live DOM nodes")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "engine.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Fetch, swap, trigger, history")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "orchestration.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Element scanning, attribute processing")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "boot.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Script processing, mount, hydration")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "—") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "signals.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Reactive signal runtime")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "deps.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Component dependency analysis")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "router.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "Client-side route matching")
|
||||
(td (~tw :tokens "px-4 py-2 text-center") "Yes") (td (~tw :tokens "px-4 py-2 text-center") "Yes")))))
|
||||
(p "Blue rows are browser-only modules. " (code "js.sx") " must handle all of them. "
|
||||
"The platform interface is also larger: DOM primitives (" (code "dom-create-element")
|
||||
", " (code "dom-append") ", " (code "dom-set-attr") ", ...), "
|
||||
@@ -147,78 +147,78 @@
|
||||
|
||||
(~docs/subsection :title "Name Mangling"
|
||||
(p "SX uses kebab-case. JavaScript uses camelCase.")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "SX")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "JavaScript")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Rule")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "SX")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "JavaScript")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Rule")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "eval-expr")
|
||||
(td :class "px-4 py-2 font-mono" "evalExpr")
|
||||
(td :class "px-4 py-2" "kebab → camelCase"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "nil?")
|
||||
(td :class "px-4 py-2 font-mono" "isNil")
|
||||
(td :class "px-4 py-2" "predicate → is-prefix"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "empty?")
|
||||
(td :class "px-4 py-2 font-mono" "isEmpty")
|
||||
(td :class "px-4 py-2" "? → is-prefix (general)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "set!")
|
||||
(td :class "px-4 py-2 font-mono" "—")
|
||||
(td :class "px-4 py-2" "assignment (no rename)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "dom-create-element")
|
||||
(td :class "px-4 py-2 font-mono" "domCreateElement")
|
||||
(td :class "px-4 py-2" "platform function rename"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "delete")
|
||||
(td :class "px-4 py-2 font-mono" "delete_")
|
||||
(td :class "px-4 py-2" "JS reserved word escape"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "eval-expr")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "evalExpr")
|
||||
(td (~tw :tokens "px-4 py-2") "kebab → camelCase"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "nil?")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "isNil")
|
||||
(td (~tw :tokens "px-4 py-2") "predicate → is-prefix"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "empty?")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "isEmpty")
|
||||
(td (~tw :tokens "px-4 py-2") "? → is-prefix (general)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "set!")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "—")
|
||||
(td (~tw :tokens "px-4 py-2") "assignment (no rename)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "dom-create-element")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "domCreateElement")
|
||||
(td (~tw :tokens "px-4 py-2") "platform function rename"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "delete")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "delete_")
|
||||
(td (~tw :tokens "px-4 py-2") "JS reserved word escape"))))))
|
||||
|
||||
(~docs/subsection :title "Special Forms → JavaScript"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "SX")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "JavaScript")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "SX")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "JavaScript")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(if c t e)")
|
||||
(td :class "px-4 py-2 font-mono" "(sxTruthy(c) ? t : e)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(when c body)")
|
||||
(td :class "px-4 py-2 font-mono" "(sxTruthy(c) ? body : NIL)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(let ((a 1)) body)")
|
||||
(td :class "px-4 py-2 font-mono" "(function(a) { return body; })(1)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(fn (x) body)")
|
||||
(td :class "px-4 py-2 font-mono" "function(x) { return body; }"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(define name val)")
|
||||
(td :class "px-4 py-2 font-mono" "var name = val;"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(and a b c)")
|
||||
(td :class "px-4 py-2 font-mono" "(sxTruthy(a) ? (sxTruthy(b) ? c : b) : a)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(case x \"a\" 1 ...)")
|
||||
(td :class "px-4 py-2 font-mono" "sxCase(x, [[\"a\", () => 1], ...])"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(str a b c)")
|
||||
(td :class "px-4 py-2 font-mono" "sxStr(a, b, c)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "&rest args")
|
||||
(td :class "px-4 py-2 font-mono" "...args (rest params)"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(if c t e)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(sxTruthy(c) ? t : e)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(when c body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(sxTruthy(c) ? body : NIL)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(let ((a 1)) body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(function(a) { return body; })(1)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(fn (x) body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "function(x) { return body; }"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(define name val)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "var name = val;"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(and a b c)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(sxTruthy(a) ? (sxTruthy(b) ? c : b) : a)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(case x \"a\" 1 ...)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "sxCase(x, [[\"a\", () => 1], ...])"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(str a b c)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "sxStr(a, b, c)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "&rest args")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "...args (rest params)"))))))
|
||||
|
||||
(~docs/subsection :title "JavaScript Advantages"
|
||||
(p "JavaScript is easier to target than Python in two key ways:")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-700")
|
||||
(li (strong "No mutation problem. ")
|
||||
"JavaScript closures capture by reference, not by value. "
|
||||
(code "set!") " from a nested function Just Works — no cell variable "
|
||||
@@ -237,7 +237,7 @@
|
||||
"definitions — it fetches data, resolves conditionals, expands components, "
|
||||
"and produces a complete DOM description as an SX tree. Currently this tree "
|
||||
"is either:")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li "Rendered to HTML server-side (" (code "render-to-html") ")")
|
||||
(li "Serialized as SX wire format for the client to render (" (code "aser") ")"))
|
||||
(p "A third option: " (strong "compile it to JavaScript") ". "
|
||||
@@ -279,7 +279,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
(~docs/subsection :title "Why Not Just Use HTML?"
|
||||
(p "HTML already does this — " (code "innerHTML") " parses and builds DOM. "
|
||||
"Why compile to JS instead?")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-700")
|
||||
(li (strong "Event handlers. ")
|
||||
"HTML can't express " (code ":on-click") " or " (code ":sx-get")
|
||||
" — those need JavaScript. The compiled JS can wire up event "
|
||||
@@ -304,7 +304,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
(~docs/subsection :title "Hybrid Mode"
|
||||
(p "Not every page is fully static. Some parts are server-rendered, "
|
||||
"some are interactive. " (code "js.sx") " handles this with a hybrid approach:")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-700")
|
||||
(li (strong "Static subtrees") " → compiled to DOM construction code (no runtime)")
|
||||
(li (strong "Reactive islands") " → compiled with signal creation + subscriptions "
|
||||
"(needs signal runtime, ~2KB)")
|
||||
@@ -322,45 +322,45 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/section :title "The Bootstrap Chain" :id "chain"
|
||||
(p "With both " (code "py.sx") " and " (code "js.sx") ", the full picture:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Translator")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Written in")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Outputs")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Replaces")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Translator")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Written in")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Outputs")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Replaces")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "z3.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "SMT-LIB")
|
||||
(td :class "px-4 py-2 text-stone-400 italic" "(none — new capability)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "prove.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "Constraint proofs")
|
||||
(td :class "px-4 py-2 text-stone-400 italic" "(none — new capability)"))
|
||||
(tr :class "border-t border-stone-100 bg-violet-50"
|
||||
(td :class "px-4 py-2 font-mono text-violet-700" "py.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "Python")
|
||||
(td :class "px-4 py-2 font-mono" "bootstrap_py.py"))
|
||||
(tr :class "border-t border-stone-100 bg-blue-50"
|
||||
(td :class "px-4 py-2 font-mono text-blue-700" "js.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "JavaScript")
|
||||
(td :class "px-4 py-2 font-mono" "bootstrap_js.py"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono text-stone-400" "go.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "Go")
|
||||
(td :class "px-4 py-2 text-stone-400 italic" "(future host)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono text-stone-400" "rs.sx")
|
||||
(td :class "px-4 py-2" "SX")
|
||||
(td :class "px-4 py-2" "Rust")
|
||||
(td :class "px-4 py-2 text-stone-400 italic" "(future host)")))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "z3.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "SMT-LIB")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400 italic") "(none — new capability)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "prove.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "Constraint proofs")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400 italic") "(none — new capability)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-violet-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-violet-700") "py.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "Python")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "bootstrap_py.py"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-blue-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-blue-700") "js.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "JavaScript")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "bootstrap_js.py"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-stone-400") "go.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "Go")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400 italic") "(future host)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-stone-400") "rs.sx")
|
||||
(td (~tw :tokens "px-4 py-2") "SX")
|
||||
(td (~tw :tokens "px-4 py-2") "Rust")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400 italic") "(future host)")))))
|
||||
(p "Every translator is an SX program. The only Python left is the platform "
|
||||
"interface (types, DOM primitives, runtime support functions) and the thin "
|
||||
"runner script that loads " (code "py.sx") " or " (code "js.sx")
|
||||
@@ -374,7 +374,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/subsection :title "Phase 1: Expression Translator"
|
||||
(p "Core SX-to-JavaScript expression translation.")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "js-mangle") " — SX name → JavaScript identifier (RENAMES + kebab→camelCase)")
|
||||
(li (code "js-literal") " — atoms: numbers, strings, booleans, nil, symbols, keywords")
|
||||
(li (code "js-expr") " — recursive expression translator")
|
||||
@@ -388,7 +388,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/subsection :title "Phase 2: Statement Translator"
|
||||
(p "Top-level and function body statement emission.")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "js-statement") " — emit as JavaScript statement")
|
||||
(li (code "define") " → " (code "var name = expr;"))
|
||||
(li (code "set!") " → direct assignment (closures capture by reference)")
|
||||
@@ -398,7 +398,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/subsection :title "Phase 3: Spec Bootstrapper"
|
||||
(p "Process spec files identically to " (code "bootstrap_js.py") ".")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "js-extract-defines") " — parse .sx source, collect top-level defines")
|
||||
(li (code "js-translate-file") " — translate a list of define expressions")
|
||||
(li "Adapter selection: parser, html, sx, dom, engine, orchestration, boot")
|
||||
@@ -407,7 +407,7 @@ _0.appendChild(_2);" "javascript")))
|
||||
|
||||
(~docs/subsection :title "Phase 4: Component Compiler"
|
||||
(p "Ahead-of-time compilation of evaluated SX trees to JavaScript.")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "js-compile-element") " — emit " (code "createElement") " + attribute setting")
|
||||
(li (code "js-compile-text") " — emit " (code "textContent") " or " (code "createTextNode"))
|
||||
(li (code "js-compile-component") " — inline-expand or emit component call")
|
||||
@@ -431,42 +431,42 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Comparison with py.sx" :id "comparison"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Concern")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" (code "py.sx"))
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" (code "js.sx"))))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Concern")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") (code "py.sx"))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") (code "js.sx"))))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Naming convention")
|
||||
(td :class "px-4 py-2" "snake_case")
|
||||
(td :class "px-4 py-2" "camelCase"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Closures & mutation")
|
||||
(td :class "px-4 py-2" "Cell variable hack")
|
||||
(td :class "px-4 py-2" "Direct (reference capture)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Spec modules")
|
||||
(td :class "px-4 py-2" "eval, render, html, sx, deps, signals")
|
||||
(td :class "px-4 py-2" "All 12 modules"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Platform interface")
|
||||
(td :class "px-4 py-2" "~300 lines")
|
||||
(td :class "px-4 py-2" "~1500 lines (DOM, browser APIs)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "RENAMES table")
|
||||
(td :class "px-4 py-2" "~200 entries")
|
||||
(td :class "px-4 py-2" "~350 entries"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Component compilation")
|
||||
(td :class "px-4 py-2 text-stone-400" "N/A")
|
||||
(td :class "px-4 py-2" "Ahead-of-time DOM compiler"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2" "Estimated size")
|
||||
(td :class "px-4 py-2" "~800-1000 lines")
|
||||
(td :class "px-4 py-2" "~1200-1500 lines"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Naming convention")
|
||||
(td (~tw :tokens "px-4 py-2") "snake_case")
|
||||
(td (~tw :tokens "px-4 py-2") "camelCase"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Closures & mutation")
|
||||
(td (~tw :tokens "px-4 py-2") "Cell variable hack")
|
||||
(td (~tw :tokens "px-4 py-2") "Direct (reference capture)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Spec modules")
|
||||
(td (~tw :tokens "px-4 py-2") "eval, render, html, sx, deps, signals")
|
||||
(td (~tw :tokens "px-4 py-2") "All 12 modules"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Platform interface")
|
||||
(td (~tw :tokens "px-4 py-2") "~300 lines")
|
||||
(td (~tw :tokens "px-4 py-2") "~1500 lines (DOM, browser APIs)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "RENAMES table")
|
||||
(td (~tw :tokens "px-4 py-2") "~200 entries")
|
||||
(td (~tw :tokens "px-4 py-2") "~350 entries"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Component compilation")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-400") "N/A")
|
||||
(td (~tw :tokens "px-4 py-2") "Ahead-of-time DOM compiler"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2") "Estimated size")
|
||||
(td (~tw :tokens "px-4 py-2") "~800-1000 lines")
|
||||
(td (~tw :tokens "px-4 py-2") "~1200-1500 lines"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Implications
|
||||
@@ -486,7 +486,7 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
|
||||
|
||||
(~docs/subsection :title "Progressive Enhancement Layers"
|
||||
(p "The component compiler naturally supports progressive enhancement:")
|
||||
(ol :class "list-decimal pl-6 space-y-1 text-stone-700"
|
||||
(ol (~tw :tokens "list-decimal pl-6 space-y-1 text-stone-700")
|
||||
(li (strong "HTML") " — server renders to HTML string. No JS needed. Works everywhere.")
|
||||
(li (strong "Compiled JS") " — server compiles to DOM construction code. "
|
||||
"Event handlers work. No SX runtime. Kilobytes, not megabytes.")
|
||||
@@ -500,7 +500,7 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
|
||||
|
||||
(~docs/subsection :title "The Bootstrap Completion"
|
||||
(p "With " (code "py.sx") " and " (code "js.sx") " both written in SX:")
|
||||
(ul :class "list-disc pl-6 space-y-2 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-2 text-stone-700")
|
||||
(li "The " (em "spec") " defines SX semantics (" (code "eval.sx") ", " (code "render.sx") ", ...)")
|
||||
(li "The " (em "translators") " convert the spec to host languages (" (code "py.sx") ", " (code "js.sx") ")")
|
||||
(li "The " (em "prover") " verifies the spec's properties (" (code "z3.sx") ", " (code "prove.sx") ")")
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
(~docs/subsection :title "Transport Hierarchy"
|
||||
(p "Three tiers, progressively more capable:")
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li (strong "Chunked streaming") " (done) — single HTTP response, each suspense resolves once. "
|
||||
"Best for: initial page load with slow IO.")
|
||||
(li (strong "SSE") " — persistent one-way connection, server pushes resolve events. "
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
(~docs/subsection :title "Shared Resolution Mechanism"
|
||||
(p "All three transports use the same client-side resolution:")
|
||||
(ul :class "list-disc list-inside space-y-1 text-stone-600 text-sm"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 text-stone-600 text-sm")
|
||||
(li (code "Sx.resolveSuspense(id, sxSource)") " — already exists, parses SX and renders to DOM")
|
||||
(li "SSE: " (code "EventSource") " → " (code "onmessage") " → " (code "resolveSuspense()"))
|
||||
(li "WS: " (code "WebSocket") " → " (code "onmessage") " → " (code "resolveSuspense()"))
|
||||
@@ -54,7 +54,7 @@
|
||||
(~docs/section :title "Implementation" :id "implementation"
|
||||
|
||||
(~docs/subsection :title "Phase 1: SSE Infrastructure"
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li "Add " (code "~live") " component to " (code "shared/sx/templates/") " — renders child suspense placeholders, "
|
||||
"emits " (code "data-sx-live") " attribute with SSE endpoint URL")
|
||||
(li "Add " (code "sx-live.js") " client module — on boot, finds " (code "[data-sx-live]") " elements, "
|
||||
@@ -63,48 +63,48 @@
|
||||
(li "Add " (code "sse_stream()") " Quart helper — returns async generator Response with correct headers")))
|
||||
|
||||
(~docs/subsection :title "Phase 2: Defpage Integration"
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li "New " (code ":live") " defpage slot — declares SSE endpoint + suspense bindings")
|
||||
(li "Auto-mount SSE endpoint alongside the page route")
|
||||
(li "Component defs sent as first SSE event on connection open")
|
||||
(li "Automatic reconnection with exponential backoff")))
|
||||
|
||||
(~docs/subsection :title "Phase 3: WebSocket"
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li "Add " (code "~ws") " component — bidirectional channel with send/receive")
|
||||
(li "Add " (code "sx-ws.js") " client module — WebSocket management, message routing")
|
||||
(li "Server-side: Quart WebSocket handlers that receive and broadcast SX events")
|
||||
(li "Client-side: " (code "sx-send") " primitive for sending SX expressions to server")))
|
||||
|
||||
(~docs/subsection :title "Phase 4: Spec & Boundary"
|
||||
(ol :class "list-decimal list-inside space-y-2 text-stone-700 text-sm"
|
||||
(ol (~tw :tokens "list-decimal list-inside space-y-2 text-stone-700 text-sm")
|
||||
(li "Spec " (code "~live") " and " (code "~ws") " in " (code "render.sx") " (how they render in each mode)")
|
||||
(li "Add SSE/WS IO primitives to " (code "boundary.sx"))
|
||||
(li "Bootstrap SSE/WS connection management into " (code "sx-ref.js"))
|
||||
(li "Spec-level tests for resolve, reconnection, and message routing"))))
|
||||
|
||||
(~docs/section :title "Files" :id "files"
|
||||
(table :class "w-full text-left border-collapse"
|
||||
(table (~tw :tokens "w-full text-left border-collapse")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/live.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "~live component definition"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/static/scripts/sx-live.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "SSE client — EventSource → resolveSuspense"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/sse.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "SSE helpers — event formatting, stream response"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/static/scripts/sx-ws.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "WebSocket client — bidirectional SX channel"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/render.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Spec: ~live and ~ws rendering in all modes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boundary.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "SSE/WS IO primitive declarations")))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/live.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~live component definition"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/static/scripts/sx-live.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SSE client — EventSource → resolveSuspense"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/sse.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SSE helpers — event formatting, stream response"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/static/scripts/sx-ws.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "WebSocket client — bidirectional SX channel"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/render.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Spec: ~live and ~ws rendering in all modes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boundary.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SSE/WS IO primitive declarations")))))))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/mother-language/plan-mother-language-content ()
|
||||
(~docs/page :title "Mother Language"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"The ideal language for evaluating the SX core spec is SX itself. "
|
||||
"The path: OCaml as the initial substrate (closest existing language to what CEK is), "
|
||||
"Koka as an alternative (compile-time linearity), ultimately a self-hosting SX compiler "
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
(~docs/section :title "The Argument" :id "argument"
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What the evaluator actually does")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What the evaluator actually does")
|
||||
(p "The CEK machine is a " (code "state \u2192 state") " loop over sum types. "
|
||||
"Each step pattern-matches on the Control register, consults the Environment, "
|
||||
"and transforms the Kontinuation. Every SX expression, every component render, "
|
||||
@@ -27,10 +27,10 @@
|
||||
"with minimal allocation. That means: algebraic types, pattern matching, "
|
||||
"persistent data structures, and a native effect system.")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Why multiple hosts is the wrong goal")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Why multiple hosts is the wrong goal")
|
||||
(p "The current architecture bootstraps the spec to Python, JavaScript, and Rust. "
|
||||
"Each host has impedance mismatches:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (strong "Python") " \u2014 slow. Tree-walk overhead is 100\u20131000x vs native. "
|
||||
"The async adapter is complex because Python's async model is cooperative, not effect-based.")
|
||||
(li (strong "JavaScript") " \u2014 no sum types, prototype-based dispatch, GC pauses unpredictable. "
|
||||
@@ -40,7 +40,7 @@
|
||||
(p "Each host makes the evaluator work, but none make it " (em "natural") ". "
|
||||
"The translation is structure-" (em "creating") ", not structure-" (em "preserving") ".")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "The Mother Language is SX")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "The Mother Language is SX")
|
||||
(p "The spec defines the semantics. The CEK machine is the most explicit form of those semantics. "
|
||||
"The ideal \"language\" is one that maps 1:1 onto CEK transitions and compiles them to "
|
||||
"optimal machine code. That language is SX itself \u2014 compiled, not interpreted.")
|
||||
@@ -58,49 +58,49 @@
|
||||
(p "OCaml is the closest existing language to what the CEK machine is. "
|
||||
"The translation from " (code "cek.sx") " to OCaml is nearly mechanical.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Natural mapping")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SX concept")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "OCaml primitive")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Notes")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Natural mapping")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "SX concept")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "OCaml primitive")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Notes")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Value (Nil | Num | Str | List | ...)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Algebraic variant type")
|
||||
(td :class "px-3 py-2 text-stone-600" "Direct mapping, no boxing"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Frame (IfFrame | ArgFrame | MapFrame | ...)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Algebraic variant type")
|
||||
(td :class "px-3 py-2 text-stone-600" "20+ variants, pattern match dispatch"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Environment (persistent map)")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "Map.S"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Built-in balanced tree, structural sharing"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Continuation (list of frames)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Immutable list")
|
||||
(td :class "px-3 py-2 text-stone-600" "cons/match, O(1) push/pop"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "cek-step (pattern match on C)")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "match") " expression")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compiles to jump table"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "shift/reset")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "perform") " / " (code "continue"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Native in OCaml 5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Concurrent CEK (fibers)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Domains + effect handlers")
|
||||
(td :class "px-3 py-2 text-stone-600" "One fiber per CEK machine"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Value (Nil | Num | Str | List | ...)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Algebraic variant type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Direct mapping, no boxing"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Frame (IfFrame | ArgFrame | MapFrame | ...)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Algebraic variant type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "20+ variants, pattern match dispatch"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Environment (persistent map)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "Map.S"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Built-in balanced tree, structural sharing"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Continuation (list of frames)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Immutable list")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "cons/match, O(1) push/pop"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "cek-step (pattern match on C)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "match") " expression")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compiles to jump table"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "shift/reset")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "perform") " / " (code "continue"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Native in OCaml 5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Concurrent CEK (fibers)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Domains + effect handlers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "One fiber per CEK machine"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Linear continuations")
|
||||
(td :class "px-3 py-2 text-stone-700" "One-shot continuations (default)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Runtime-enforced, not compile-time")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Linear continuations")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "One-shot continuations (default)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Runtime-enforced, not compile-time")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Compilation targets")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Compilation targets")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (strong "Native") " \u2014 OCaml's native compiler produces fast binaries, small footprint. "
|
||||
"Embed in Python via C ABI (ctypes/cffi). Embed in Node via N-API.")
|
||||
(li (strong "WASM") " \u2014 " (code "wasm_of_ocaml") " is mature (used by Facebook's Flow/Reason). "
|
||||
@@ -108,7 +108,7 @@
|
||||
(li (strong "JavaScript") " \u2014 " (code "js_of_ocaml") " for legacy browser targets. "
|
||||
"Falls back to JS when WASM isn't available."))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What OCaml replaces")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What OCaml replaces")
|
||||
(p "The Haskell and Rust evaluator implementations become unnecessary. "
|
||||
"OCaml covers both server (native) and client (WASM) from one codebase. "
|
||||
"The sx-haskell and sx-rust work proved the spec is host-independent \u2014 "
|
||||
@@ -128,8 +128,8 @@
|
||||
(p "Koka (Daan Leijen, MSR) addresses OCaml's one weakness: "
|
||||
(strong "compile-time linearity") ".")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Where Koka wins")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Where Koka wins")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Perceus reference counting") " \u2014 the compiler tracks which values are used linearly. "
|
||||
"Linear values are mutated in-place (zero allocation). "
|
||||
"Non-linear values use reference counting (no GC at all).")
|
||||
@@ -140,8 +140,8 @@
|
||||
"The type system prevents invoking a linear continuation twice. "
|
||||
"No runtime check needed."))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Where Koka is weaker")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Where Koka is weaker")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Maturity") " \u2014 research language. Smaller ecosystem, fewer FFI bindings, "
|
||||
"less battle-tested than OCaml.")
|
||||
(li (strong "WASM backend") " \u2014 compiles to C \u2192 WASM (via Emscripten). "
|
||||
@@ -162,38 +162,38 @@
|
||||
|
||||
(p "The end state: SX compiles itself. No intermediate language, no general-purpose host.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Phase 1: OCaml bootstrapper")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Phase 1: OCaml bootstrapper")
|
||||
(p "Write " (code "bootstrap_ml.py") " \u2014 reads " (code "cek.sx") " + " (code "frames.sx")
|
||||
" + " (code "primitives.sx") " + " (code "eval.sx") ", emits OCaml source. "
|
||||
"Same pattern as the existing Rust/Python/JS bootstrappers.")
|
||||
(p "The OCaml output is a standalone module:")
|
||||
(~docs/code :src (highlight "type value =\n | Nil | Bool of bool | Num of float | Str of string\n | Sym of string | Kw of string\n | List of value list | Dict of (value * value) list\n | Lambda of params * value list * env\n | Component of string * params * value list * env\n | Handle of int (* opaque FFI reference *)\n\ntype frame =\n | IfFrame of value list * value list * env\n | ArgFrame of value list * value list * env\n | MapFrame of value * value list * value list * env\n | ReactiveResetFrame of value\n | DerefFrame of value\n (* ... 20+ frame types from frames.sx *)\n\ntype kont = frame list\ntype state = value * env * kont\n\nlet step ((ctrl, env, kont) : state) : state =\n match ctrl with\n | Lit v -> continue_val v kont\n | Var name -> continue_val (Env.find name env) kont\n | App (f, args) -> (f, env, ArgFrame(args, [], env) :: kont)\n | ..." "ocaml"))
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Phase 2: Native + WASM builds")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Phase 2: Native + WASM builds")
|
||||
(p "Compile the OCaml output to:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (code "sx_core.so") " / " (code "sx_core.dylib") " \u2014 native shared library, C ABI")
|
||||
(li (code "sx_core.wasm") " \u2014 via " (code "wasm_of_ocaml") " for browser")
|
||||
(li (code "sx_core.js") " \u2014 via " (code "js_of_ocaml") " as JS fallback"))
|
||||
(p "Python web framework calls " (code "sx_core.so") " via cffi. "
|
||||
"Browser loads " (code "sx_core.wasm") " via " (code "sx-platform.js") ".")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Phase 3: SX evaluates web framework")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Phase 3: SX evaluates web framework")
|
||||
(p "The compiled core evaluator loads web framework " (code ".sx") " at runtime "
|
||||
"(signals, engine, orchestration, boot). Same as the "
|
||||
(a :href "/sx/(etc.(plan.isolated-evaluator))" "Isolated Evaluator") " plan, "
|
||||
"but the evaluator is compiled OCaml/WASM instead of bootstrapped JS.")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Phase 4: SX linearity checking")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Phase 4: SX linearity checking")
|
||||
(p "Extend " (code "types.sx") " with quantity annotations:")
|
||||
(~docs/code :src (highlight ";; Quantity annotations on types\n(define-type (Signal a) :quantity :affine) ;; use at most once per scope\n(define-type (Channel a) :quantity :linear) ;; must be consumed exactly once\n\n;; Effect declarations with linearity\n(define-io-primitive \"send-message\"\n :params (channel message)\n :quantity :linear\n :effects [io]\n :doc \"Must be handled exactly once.\")\n\n;; The type checker (specced in .sx, compiled to OCaml) validates\n;; linearity at component registration time. Runtime enforcement\n;; by OCaml's one-shot continuations is the safety net." "lisp"))
|
||||
(p "The type checker runs at spec-validation time. The compiled evaluator "
|
||||
"executes already-verified code. SX's type system provides the linearity "
|
||||
"guarantees, not the host language.")
|
||||
|
||||
(h4 :class "font-semibold mt-6 mb-2" "Phase 5: Self-hosting compiler")
|
||||
(h4 (~tw :tokens "font-semibold mt-6 mb-2") "Phase 5: Self-hosting compiler")
|
||||
(p "Write the compiler itself in SX:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li "Spec the CEK-to-native code generation in " (code ".sx") " files")
|
||||
(li "The Phase 2 OCaml evaluator compiles the compiler spec")
|
||||
(li "The compiled compiler can then compile itself")
|
||||
@@ -215,46 +215,46 @@
|
||||
(p "OCaml 5's concurrency model maps directly onto the "
|
||||
(a :href "/sx/(etc.(plan.foundations))" "Foundations") " plan's concurrent CEK spec.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Mapping")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SX primitive")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "OCaml 5")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Characteristic")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Mapping")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "SX primitive")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "OCaml 5")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Characteristic")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "spawn"))
|
||||
(td :class "px-3 py-2 text-stone-700" "Fiber via " (code "perform Spawn"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Lightweight, scheduled by effect handler"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "channel"))
|
||||
(td :class "px-3 py-2 text-stone-700" (code "Eio.Stream"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Typed, bounded, backpressure"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "yield!"))
|
||||
(td :class "px-3 py-2 text-stone-700" (code "perform Yield"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Cooperative, zero-cost"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "select"))
|
||||
(td :class "px-3 py-2 text-stone-700" (code "Eio.Fiber.any"))
|
||||
(td :class "px-3 py-2 text-stone-600" "First-to-complete"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "fork-join"))
|
||||
(td :class "px-3 py-2 text-stone-700" (code "Eio.Fiber.all"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Structured concurrency"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "spawn"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fiber via " (code "perform Spawn"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Lightweight, scheduled by effect handler"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "channel"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "Eio.Stream"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Typed, bounded, backpressure"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "yield!"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "perform Yield"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Cooperative, zero-cost"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "select"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "Eio.Fiber.any"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "First-to-complete"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "fork-join"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "Eio.Fiber.all"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Structured concurrency"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "DAG scheduler")
|
||||
(td :class "px-3 py-2 text-stone-700" "Domains + fiber pool")
|
||||
(td :class "px-3 py-2 text-stone-600" "True parallelism across cores")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DAG scheduler")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Domains + fiber pool")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "True parallelism across cores")))))
|
||||
|
||||
(p "Each concurrent CEK machine is a fiber. The scheduler is an effect handler. "
|
||||
"This isn't simulating concurrency \u2014 it's using native concurrency whose mechanism " (em "is") " effects.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The Art DAG connection")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The Art DAG connection")
|
||||
(p "Art DAG's 3-phase execution (analyze \u2192 plan \u2192 execute) maps onto "
|
||||
"concurrent CEK + OCaml domains:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li "Analyze: single CEK machine walks the DAG graph (one fiber)")
|
||||
(li "Plan: resolve dependencies, topological sort (pure computation)")
|
||||
(li "Execute: spawn one fiber per independent node, fan out to domains (true parallelism)")
|
||||
@@ -269,9 +269,9 @@
|
||||
|
||||
(p "The linearity axis from foundations. Two enforcement layers:")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Layer 1: SX type system (primary)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Layer 1: SX type system (primary)")
|
||||
(p "Quantity annotations in " (code "types.sx") " checked at spec-validation time:")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li (code ":linear") " (1) \u2014 must be used exactly once")
|
||||
(li (code ":affine") " (\u22641) \u2014 may be used at most once (can drop)")
|
||||
(li (code ":unrestricted") " (\u03c9) \u2014 may be used any number of times"))
|
||||
@@ -279,16 +279,16 @@
|
||||
"A channel is consumed. A resource handle is closed. "
|
||||
"The type checker proves this before the evaluator ever runs.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Layer 2: Host runtime (safety net)")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Layer 2: Host runtime (safety net)")
|
||||
(p "OCaml 5's one-shot continuations enforce linearity at runtime. "
|
||||
"A continuation can only be " (code "continue") "'d once \u2014 second invocation raises an exception. "
|
||||
"This catches any bugs in the type checker itself.")
|
||||
(p "If Koka replaces OCaml: compile-time enforcement replaces runtime enforcement. "
|
||||
"The safety net becomes a proof. Same semantics, stronger guarantees.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Decision point")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Decision point")
|
||||
(p "When Step 5 (Linear Effects) of the foundations plan is reached:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "If SX's type checker can enforce linearity reliably \u2192 "
|
||||
(strong "stay on OCaml") ". Runtime one-shot is sufficient.")
|
||||
(li "If linearity bugs keep slipping through \u2192 "
|
||||
@@ -309,7 +309,7 @@
|
||||
"SX is not an interpreted scripting language with a nice spec. "
|
||||
"It's a compiled language whose compiler also runs in the browser.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "JIT in the browser")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "JIT in the browser")
|
||||
(p "The server sends SX (component definitions, page content). "
|
||||
"The client receives it and " (strong "compiles to WASM and executes") ". "
|
||||
"Not interprets. Not dispatches bytecodes. Compiles.")
|
||||
@@ -322,7 +322,7 @@
|
||||
"And content-addressing means compiled artifacts are cacheable by CID \u2014 "
|
||||
"compile once, store forever.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The compilation tiers")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The compilation tiers")
|
||||
|
||||
(~docs/code :src (highlight "Tier 0: .sx source \u2192 tree-walking CEK (correct, slow \u2014 current)\nTier 1: .sx source \u2192 bytecodes \u2192 dispatch loop (correct, fast)\nTier 2: .sx source \u2192 WASM functions \u2192 execute (correct, fastest)\nTier 3: .sx source \u2192 native machine code (ahead-of-time, maximum)" "text"))
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"Tier 3 is AOT \u2014 the entire app precompiled. "
|
||||
"All tiers use the same spec, same platform layer, same platform primitives.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Server-side precompilation")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Server-side precompilation")
|
||||
(p "The server can compile too. Instead of sending SX source for the client to JIT, "
|
||||
"send precompiled WASM:")
|
||||
|
||||
@@ -342,13 +342,13 @@
|
||||
(p "Option B skips parsing and compilation entirely. The client instantiates "
|
||||
"the WASM module and calls it. The server did all the work.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed compilation cache")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Content-addressed compilation cache")
|
||||
(p "Every " (code ".sx") " expression has a CID. Every compiled artifact has a CID. "
|
||||
"The mapping is deterministic \u2014 the compiler is a pure function:")
|
||||
|
||||
(~docs/code :src (highlight "source CID \u2192 compiled WASM CID\nbafyrei... \u2192 bafyrei...\n\nThis mapping is cacheable everywhere:\n\u2022 Browser cache \u2014 first visitor compiles, second visitor gets cached WASM\n\u2022 CDN \u2014 compiled artifacts served at the edge\n\u2022 IPFS \u2014 content-addressed by definition, globally deduplicated\n\u2022 Local disk \u2014 offline apps work from cached compiled components" "text"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Entire apps as machine code")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Entire apps as machine code")
|
||||
(p "The entire application can be ahead-of-time compiled to a WASM binary. "
|
||||
"Component definitions, page layouts, event handlers, signal computations \u2014 "
|
||||
"all compiled to native WASM functions. The \"app\" is a " (code ".wasm") " file. "
|
||||
@@ -357,7 +357,7 @@
|
||||
"user-generated SX, REPL input, " (code "eval") "'d strings. "
|
||||
"And even those get JIT'd on first use and cached by CID.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The architecture")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The architecture")
|
||||
|
||||
(~docs/code :src (highlight "sx-platform.js \u2190 DOM, fetch, timers (the real world)\n \u2191 calls\nsx-compiler.wasm \u2190 the SX compiler (itself compiled to WASM)\n \u2191 compiles\n.sx source \u2190 received from server / cache / inline\n \u2193 emits\nnative WASM functions \u2190 cached by CID, instantiated on demand\n \u2193 executes\nactual DOM mutations via platform primitives" "text"))
|
||||
|
||||
@@ -377,48 +377,48 @@
|
||||
"WASM + the platform layer means compiled SX code has "
|
||||
(strong "zero ambient capabilities") " \u2014 every capability is explicitly granted.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Five defence layers")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Five defence layers")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Enforced by")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What it prevents")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Enforced by")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What it prevents")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "1. WASM sandbox")
|
||||
(td :class "px-3 py-2 text-stone-700" "Browser")
|
||||
(td :class "px-3 py-2 text-stone-600" "Memory isolation, no system calls, no DOM access except via explicit imports. Validated before execution."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "2. Platform capabilities")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "sx-platform.js"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Compiled code can only call functions you register. No fetch? Can't fetch. No localStorage? Can't read storage. The platform is a capability system."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "3. Content-addressed verification")
|
||||
(td :class "px-3 py-2 text-stone-700" "CID determinism")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compiler is deterministic: same source \u2192 same CID. Client can re-compile and verify. Tampered WASM produces wrong CID \u2192 reject."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "4. Per-component attenuation")
|
||||
(td :class "px-3 py-2 text-stone-700" "Platform scoping")
|
||||
(td :class "px-3 py-2 text-stone-600" "Different components get different capability subsets. User-generated content gets a locked-down platform \u2014 can render DOM but can't fetch or listen to events."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "1. WASM sandbox")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Browser")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Memory isolation, no system calls, no DOM access except via explicit imports. Validated before execution."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "2. Platform capabilities")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "sx-platform.js"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compiled code can only call functions you register. No fetch? Can't fetch. No localStorage? Can't read storage. The platform is a capability system."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "3. Content-addressed verification")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CID determinism")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compiler is deterministic: same source \u2192 same CID. Client can re-compile and verify. Tampered WASM produces wrong CID \u2192 reject."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "4. Per-component attenuation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Platform scoping")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Different components get different capability subsets. User-generated content gets a locked-down platform \u2014 can render DOM but can't fetch or listen to events."))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "5. Source-first fallback")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client compiler")
|
||||
(td :class "px-3 py-2 text-stone-600" "Don't trust precompiled WASM? Compile from source locally. The client has the compiler. Precompilation is an optimisation, not a trust requirement.")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "5. Source-first fallback")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client compiler")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Don't trust precompiled WASM? Compile from source locally. The client has the compiler. Precompilation is an optimisation, not a trust requirement.")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed tamper detection")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Content-addressed tamper detection")
|
||||
(p "The server sends both SX source and precompiled WASM CID. The client can verify:")
|
||||
|
||||
(~docs/code :src (highlight ";; Server sends:\nContent-Type: application/wasm\nX-Sx-Source-Cid: bafyrei..source\nX-Sx-Compiled-Cid: bafyrei..compiled\n\n;; Client verifies (optional, configurable):\n1. Hash the WASM binary \u2192 matches X-Sx-Compiled-Cid?\n2. Compile source locally \u2192 produces same compiled CID?\n3. Check manifest of pinned CIDs \u2192 CID is expected?\n\n;; Any mismatch = tampered = reject" "text"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Capability attenuation per component")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Capability attenuation per component")
|
||||
(p "The platform scopes capabilities per evaluator instance. "
|
||||
"App shell gets full access. Third-party or user-generated content gets the minimum:")
|
||||
|
||||
(~docs/code :src (highlight "// Full capabilities for the app shell\nplatform.registerAll(appShellCompiler);\n\n// Restricted for user-generated content\nplatform.registerSubset(userContentCompiler, {\n allow: [\"dom-create-element\", \"dom-set-attr\", \"dom-append\",\n \"dom-create-text-node\", \"dom-set-text\"],\n deny: [\"fetch\", \"localStorage\", \"dom-listen\",\n \"dom-set-inner-html\", \"eval\"]\n});\n\n// The restricted compiler's WASM module literally doesn't\n// have imports for the denied functions. Not just blocked\n// at runtime \u2014 absent from the binary." "javascript"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Component manifests")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Component manifests")
|
||||
(p "The app ships with a manifest of expected CIDs for its core components. "
|
||||
"Like subresource integrity (SRI) but for compiled code:")
|
||||
|
||||
@@ -442,7 +442,7 @@
|
||||
"and to DOM on the client. Compiled WASM doesn't change this. "
|
||||
"It makes the client side faster without affecting what crawlers see.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The rendering pipeline")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The rendering pipeline")
|
||||
|
||||
(~docs/code :src (highlight "Crawler visits:\n GET /page\n \u2192 Server compiles SX (native OCaml)\n \u2192 render-to-html (adapter-html.sx)\n \u2192 Full static HTML with semantic markup\n \u2192 Google indexes it\n\nUser first visit:\n GET /page\n \u2192 Server renders HTML (same as crawler)\n \u2192 Browser displays immediately (no JS needed)\n \u2192 Client loads sx-compiler.wasm + sx-platform.js\n \u2192 Hydrates: attaches event handlers, activates islands\n \u2192 Page is interactive\n\nUser navigates (SPA):\n sx-get /next-page\n \u2192 Server sends SX wire format (aser)\n \u2192 Client compiles + renders via WASM\n \u2192 Morph engine patches the DOM" "text"))
|
||||
|
||||
@@ -453,15 +453,15 @@
|
||||
"makes hydration and SPA navigation faster, but the initial HTML "
|
||||
"is always server-rendered.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What crawlers see")
|
||||
(ul :class "list-disc list-inside space-y-1 mt-2"
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What crawlers see")
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-1 mt-2")
|
||||
(li "Fully rendered HTML \u2014 no \"loading...\" skeleton, no JS-dependent content")
|
||||
(li "Semantic markup \u2014 " (code "<h1>") ", " (code "<nav>") ", " (code "<article>") ", " (code "<a href>")
|
||||
" \u2014 all from the SX component tree")
|
||||
(li "Meta tags, canonical URLs, structured data \u2014 rendered server-side by " (code "shell.sx"))
|
||||
(li "No WASM, no JS required \u2014 the HTML is the page, complete on first byte"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed prerendering")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Content-addressed prerendering")
|
||||
(p "The server can prerender every page to static HTML, hash it, "
|
||||
"and cache it at the CDN edge:")
|
||||
|
||||
@@ -472,23 +472,23 @@
|
||||
"the server-rendered HTML are cached by CID. "
|
||||
"The entire delivery pipeline is content-addressed.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Progressive enhancement")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Progressive enhancement")
|
||||
(p "The page works at every level:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Client capability")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Experience")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Client capability")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Experience")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "No JS (crawler, reader mode)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Full HTML. Links work. Forms submit. Content is complete."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JS, no WASM")
|
||||
(td :class "px-3 py-2 text-stone-600" "Falls back to js_of_ocaml evaluator or interpreted JS. SPA navigation, islands, signals all work."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "No JS (crawler, reader mode)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Full HTML. Links work. Forms submit. Content is complete."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS, no WASM")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Falls back to js_of_ocaml evaluator or interpreted JS. SPA navigation, islands, signals all work."))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "JS + WASM")
|
||||
(td :class "px-3 py-2 text-stone-600" "Full compiled pipeline. JIT compilation, cached WASM functions, near-native rendering speed."))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS + WASM")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Full compiled pipeline. JIT compilation, cached WASM functions, near-native rendering speed."))))))
|
||||
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -502,33 +502,33 @@
|
||||
"or \u2014 when the client already has the components cached \u2014 "
|
||||
"just the data as gzipped bytecode.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Three response tiers")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Three response tiers")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Client cache state")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Server sends")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Client does")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Client cache state")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Server sends")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Client does")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Nothing cached")
|
||||
(td :class "px-3 py-2 text-stone-700" "HTML (fallback)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Parse HTML, morph DOM"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Compiler cached")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX source")
|
||||
(td :class "px-3 py-2 text-stone-600" "Compile, execute, morph DOM"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Components cached")
|
||||
(td :class "px-3 py-2 text-stone-700" "Bytecode (data only)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Call cached WASM function with args"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Nothing cached")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "HTML (fallback)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Parse HTML, morph DOM"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Compiler cached")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX source")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Compile, execute, morph DOM"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components cached")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bytecode (data only)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Call cached WASM function with args"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Components cached + gzip")
|
||||
(td :class "px-3 py-2 text-stone-700" "Gzipped bytecode")
|
||||
(td :class "px-3 py-2 text-stone-600" "Decompress, call cached function")))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components cached + gzip")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Gzipped bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Decompress, call cached function")))))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Why bytecode is tiny")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Why bytecode is tiny")
|
||||
(p "The bytecode doesn't repeat what the client already knows. "
|
||||
"Component CID references are 4-byte pointers to already-cached compiled functions. "
|
||||
"Tag names, class strings, attribute keys \u2014 all baked into the compiled component. "
|
||||
@@ -545,7 +545,7 @@
|
||||
"The only thing on the wire is the data that differs between instances. "
|
||||
"Gzip crushes the repetitive framing to almost nothing.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Content negotiation")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Content negotiation")
|
||||
(p "The client advertises what it has via request headers:")
|
||||
|
||||
(~docs/code :src (highlight ";; Client tells server what's cached\nAccept: application/sx-bytecode, text/sx, text/html\nX-Sx-Cached-Components: bafyrei..card, bafyrei..header, bafyrei..nav\n\n;; Server picks the smallest response:\n;; - Client has ~card cached? Send bytecode (data only)\n;; - Client has compiler but not ~card? Send SX source\n;; - Client has nothing? Send HTML" "text"))
|
||||
@@ -555,7 +555,7 @@
|
||||
"Subsequent navigations are gzipped bytecode \u2014 "
|
||||
"just the data, a few hundred bytes, instant render via cached compiled components.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The server becomes a data API")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The server becomes a data API")
|
||||
(p "At the bytecode tier, the server is no longer rendering views. "
|
||||
"It's serving data \u2014 structured arguments for compiled client-side functions. "
|
||||
"The rendering logic is cached on the client as compiled WASM. "
|
||||
@@ -569,36 +569,36 @@
|
||||
|
||||
(~docs/section :title "Impact on Existing Plans" :id "impact"
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Plan")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Impact")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Plan")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Impact")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.rust-wasm-host))" "Rust/WASM Host"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Superseded. OCaml/WASM replaces Rust/WASM. The handle table and platform layer design carry over."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.isolated-evaluator))" "Isolated Evaluator"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Architecture preserved. sx-platform.js and evaluator isolation apply to the OCaml evaluator too. The JS evaluator becomes a fallback."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.foundations))" "Foundations"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Accelerated. OCaml 5 has native effects/continuations/fibers. Steps 4 (Concurrent CEK) and 5 (Linear Effects) map directly."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Subsumed as Tier 1. Bytecodes become an intermediate step on the path to native WASM compilation. The dispatch loop is Tier 1; JIT to WASM functions is Tier 2; AOT is Tier 3."))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700")
|
||||
(a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" "Self-Hosting Bootstrapper"))
|
||||
(td :class "px-3 py-2 text-stone-600"
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600")
|
||||
"Converges. js.sx and py.sx are self-hosting emitters. The self-hosting SX compiler (Phase 5 here) is the logical endpoint."))))))
|
||||
|
||||
|
||||
@@ -607,7 +607,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "SX is the language.") " Not OCaml, not Rust, not Haskell. "
|
||||
"Those are substrates. SX defines the semantics, SX checks the types, "
|
||||
"SX will ultimately compile itself.")
|
||||
@@ -632,7 +632,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After completion:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "SX core compiled from spec via OCaml \u2014 native + WASM from one codebase.")
|
||||
(li "Python web framework calls native " (code "sx_core.so") " for rendering \u2014 "
|
||||
"100\u20131000x faster than interpreted Python.")
|
||||
@@ -646,5 +646,5 @@
|
||||
(li "Self-hosting compiler: SX compiles itself to machine code. OCaml scaffolding removed.")
|
||||
(li "The only interpreter in the system is the CPU."))
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mt-12"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mt-12")
|
||||
"The Mother Language was always SX. We just needed to find the right scaffolding to stand it up."))))
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
(~docs/code :src (highlight ";; Home (nothing selected)\n;;\n;; [ sx ]\n;;\n;; Docs CSSX Reference Protocols Examples\n;; Essays Philosophy Specs Bootstrappers\n;; Testing Isomorphism Plans Reactive Islands\n\n\n;; Section selected (e.g. Plans)\n;;\n;; [ sx ]\n;;\n;; < Plans >\n;;\n;; Status Reader Macros Theorem Prover\n;; Self-Hosting JS Bootstrapper SX-Activity\n;; Predictive Prefetching Content-Addressed\n;; Environment Images Runtime Slicing Typed SX\n;; Fragment Protocol ...\n\n\n;; Page selected (e.g. Typed SX under Plans)\n;;\n;; [ sx ]\n;;\n;; < Plans >\n;;\n;; < Typed SX >\n;;\n;; [ page content here ]" "lisp")))
|
||||
|
||||
(~docs/subsection :title "Rules"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Logo at top, centered.") " Always visible. Click = home. The only fixed element.")
|
||||
(li (strong "Level 1: section list.") " Shown on home page as a wrapped, centered list of links. This is the full menu — no hiding, no hamburger.")
|
||||
(li (strong "When a section is selected:") " Section name replaces the list. Left arrow and right arrow for sibling navigation (previous/next section). The section's children appear as a new list below.")
|
||||
@@ -39,29 +39,29 @@
|
||||
(~docs/section :title "Visual Language" :id "visual"
|
||||
(~docs/subsection :title "Levels"
|
||||
(p "Each level has decreasing visual weight:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Level")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Selected state")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "List state")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Level")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Selected state")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "List state")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Logo")
|
||||
(td :class "px-3 py-2 text-stone-700" "Large, violet, always visible")
|
||||
(td :class "px-3 py-2 text-stone-600" "—"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Section")
|
||||
(td :class "px-3 py-2 text-stone-700" "Medium text, violet-700, arrows")
|
||||
(td :class "px-3 py-2 text-stone-600" "Medium text, stone-600, wrapped inline"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Subsection")
|
||||
(td :class "px-3 py-2 text-stone-700" "Smaller text, violet-600, arrows")
|
||||
(td :class "px-3 py-2 text-stone-600" "Small text, stone-500, wrapped inline"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Level 3+")
|
||||
(td :class "px-3 py-2 text-stone-700" "Same as subsection")
|
||||
(td :class "px-3 py-2 text-stone-600" "Same as subsection"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Logo")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Large, violet, always visible")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "—"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Section")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Medium text, violet-700, arrows")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Medium text, stone-600, wrapped inline"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Subsection")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Smaller text, violet-600, arrows")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Small text, stone-500, wrapped inline"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Level 3+")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Same as subsection")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Same as subsection"))))))
|
||||
|
||||
(~docs/subsection :title "Arrows"
|
||||
(p "Left and right arrows are inline with the selected item name. They navigate to the previous/next sibling in the current list. Keyboard accessible: left/right arrow keys when the row is focused.")
|
||||
@@ -128,49 +128,49 @@
|
||||
|
||||
(~docs/section :title "What Goes Away" :id "removal"
|
||||
(p "Significant deletion:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Component")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Component")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~shared:layout/menu-row-sx")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/layout.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Horizontal bar with colour levels — replaced by breadcrumb rows"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~sx-header-row")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/layouts.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Top menu bar — replaced by logo + breadcrumb"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~sx-sub-row")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/layouts.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Sub-section bar — replaced by second breadcrumb row"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~sx-main-nav")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/layouts.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Horizontal nav list — replaced by ~plans/nav-redesign/nav-list"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~nav-data/section-nav")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/nav-data.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Sub-nav builder — replaced by ~plans/nav-redesign/nav-list"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~shared:layout/nav-link")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/layout.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Complex link with aria-selected + submenu wrapper — replaced by plain a tags"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "~shared:layout/mobile-menu-section")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/layout.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Separate mobile menu — new nav is inherently responsive"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "6 layout variants")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx/sx/layouts.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "full/oob/mobile × home/section — replaced by one layout with ~plans/nav-redesign/nav"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" ".nav-group CSS")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/templates/shell.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Hover submenu CSS — no submenus to hover")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~shared:layout/menu-row-sx")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/layout.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Horizontal bar with colour levels — replaced by breadcrumb rows"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~sx-header-row")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/layouts.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Top menu bar — replaced by logo + breadcrumb"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~sx-sub-row")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/layouts.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Sub-section bar — replaced by second breadcrumb row"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~sx-main-nav")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/layouts.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Horizontal nav list — replaced by ~plans/nav-redesign/nav-list"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~nav-data/section-nav")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/nav-data.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Sub-nav builder — replaced by ~plans/nav-redesign/nav-list"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~shared:layout/nav-link")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/layout.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Complex link with aria-selected + submenu wrapper — replaced by plain a tags"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "~shared:layout/mobile-menu-section")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/layout.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Separate mobile menu — new nav is inherently responsive"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "6 layout variants")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx/sx/layouts.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "full/oob/mobile × home/section — replaced by one layout with ~plans/nav-redesign/nav"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") ".nav-group CSS")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/templates/shell.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Hover submenu CSS — no submenus to hover")))))
|
||||
|
||||
(p "The layout variants collapse from 6 (full/oob/mobile × home/section) to 2 (full/oob). No mobile variant needed — the nav is one column, it works everywhere."))
|
||||
|
||||
@@ -194,12 +194,12 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Scope" :id "scope"
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4 mb-4"
|
||||
(p :class "text-amber-900 font-medium" "SX docs only — for now")
|
||||
(p :class "text-amber-800" "This redesign applies to the SX docs app (" (code "sx/") "). The other services (blog, market, events, etc.) keep their current navigation. If the pattern proves out, it can migrate to shared infrastructure and replace the root menu system too."))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-amber-900 font-medium") "SX docs only — for now")
|
||||
(p (~tw :tokens "text-amber-800") "This redesign applies to the SX docs app (" (code "sx/") "). The other services (blog, market, events, etc.) keep their current navigation. If the pattern proves out, it can migrate to shared infrastructure and replace the root menu system too."))
|
||||
|
||||
(p "What changes:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "sx/sx/nav-data.sx") " — add " (code "sx-nav-tree") " (wraps existing lists, no content change)")
|
||||
(li (code "sx/sx/layouts.sx") " — rewrite: delete 12 components, add 5 (logo, breadcrumb, list, nav, 2 layouts)")
|
||||
(li (code "sx/sxc/pages/docs.sx") " — simplify every defpage's " (code ":layout") " declaration")
|
||||
@@ -207,7 +207,7 @@
|
||||
(li (code "shared/sx/templates/layout.sx") " — no changes needed (shared components untouched, only SX-docs-specific ones change)"))
|
||||
|
||||
(p "What doesn't change:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Page content — all " (code "~plan-*-content") ", " (code "~doc-*-content") ", etc. are untouched")
|
||||
(li "Nav data — all " (code "*-nav-items") " lists are unchanged, just composed into a tree")
|
||||
(li "Routing — all defpage paths stay the same")
|
||||
@@ -220,25 +220,25 @@
|
||||
(~docs/section :title "Implementation" :id "implementation"
|
||||
|
||||
(~docs/subsection :title "Phase 1: Nav tree + resolution"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Add " (code "sx-nav-tree") " to " (code "nav-data.sx") " — compose existing " (code "*-nav-items") " lists into a tree")
|
||||
(li "Write " (code "resolve-nav-path") " — pure function, tree walk, returns trail + children")
|
||||
(li "Test: given a path, produces the correct breadcrumb trail and child list")))
|
||||
|
||||
(~docs/subsection :title "Phase 2: New components"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Write " (code "~plans/nav-redesign/logo") ", " (code "~plans/nav-redesign/nav-breadcrumb") ", " (code "~plans/nav-redesign/nav-list") ", " (code "~plans/nav-redesign/nav"))
|
||||
(li "Write " (code "~plans/nav-redesign/docs-layout-full") " and " (code "~plans/nav-redesign/docs-layout-oob"))
|
||||
(li "Register new layout in " (code "layouts.py"))
|
||||
(li "Test with one defpage first — verify morph transitions work")))
|
||||
|
||||
(~docs/subsection :title "Phase 3: Migrate all defpages"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Update every defpage in " (code "docs.sx") " to use " (code ":layout (:sx-docs :path ...)"))
|
||||
(li "This is mechanical — replace the 5-param layout block with 1-param")))
|
||||
|
||||
(~docs/subsection :title "Phase 4: Delete old components"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Delete " (code "~sx-main-nav") ", " (code "~sx-header-row") ", " (code "~sx-sub-row") ", " (code "~nav-data/section-nav"))
|
||||
(li "Delete all 12 SX layout variants from " (code "layouts.sx"))
|
||||
(li "Delete old layout registrations from " (code "layouts.py"))
|
||||
|
||||
@@ -11,37 +11,37 @@
|
||||
(p "But this goes beyond just hover-to-prefetch. The full spectrum includes: bundling linked routes' components with the initial page load, batch-prefetching after idle, predicting mouse trajectory toward links, and even splitting the component/data fetch so that " (code ":data") " pages can prefetch their components and only fetch data on click. Each strategy trades bandwidth for latency, and pages should be able to declare which tradeoff they want."))
|
||||
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What exists")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Where")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What exists")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Where")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Page registry")
|
||||
(td :class "px-3 py-2 text-stone-700" "Each page carries " (code ":deps (\"~card\" \"~essay-foo\" ...)"))
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "helpers.py → <script type=\"text/sx-pages\">"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Dep check")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "has-all-deps?") " gates client routing")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "orchestration.sx:546-559"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component bundle")
|
||||
(td :class "px-3 py-2 text-stone-700" "Per-page inline " (code "<script type=\"text/sx\" data-components>"))
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "helpers.py:715, jinja_bridge.py"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Incremental defs")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "components_for_request()") " sends only missing defs in SX responses")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "helpers.py:459-509"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Preload cache")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "sx-preload") " prefetches full responses on hover/mousedown")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "orchestration.sx:686-708"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Route matching")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "find-matching-route") " matches pathname to page entry")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "router.sx"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Page registry")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Each page carries " (code ":deps (\"~card\" \"~essay-foo\" ...)"))
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "helpers.py → <script type=\"text/sx-pages\">"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Dep check")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "has-all-deps?") " gates client routing")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "orchestration.sx:546-559"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component bundle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Per-page inline " (code "<script type=\"text/sx\" data-components>"))
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "helpers.py:715, jinja_bridge.py"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Incremental defs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "components_for_request()") " sends only missing defs in SX responses")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "helpers.py:459-509"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Preload cache")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "sx-preload") " prefetches full responses on hover/mousedown")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "orchestration.sx:686-708"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Route matching")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "find-matching-route") " matches pathname to page entry")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "router.sx"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Prefetch strategies
|
||||
@@ -50,49 +50,49 @@
|
||||
(~docs/section :title "Prefetch Strategies" :id "strategies"
|
||||
(p "Prefetching is a spectrum from conservative to aggressive. The system should support all of these, configured declaratively per link or per page via " (code "defpage") " metadata and " (code "sx-prefetch") " attributes.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Strategy")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Trigger")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What prefetches")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Latency on click")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Strategy")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Trigger")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What prefetches")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Latency on click")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Eager bundle")
|
||||
(td :class "px-3 py-2 text-stone-700" "Initial page load")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for linked routes included in " (code "<script data-components>"))
|
||||
(td :class "px-3 py-2 text-stone-600" "Zero — already in memory"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Idle timer")
|
||||
(td :class "px-3 py-2 text-stone-700" "After page settles (requestIdleCallback or setTimeout)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for visible nav links, batched in one request")
|
||||
(td :class "px-3 py-2 text-stone-600" "Zero if idle fetch completed"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Viewport")
|
||||
(td :class "px-3 py-2 text-stone-700" "Link scrolls into view (IntersectionObserver)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for that link's route")
|
||||
(td :class "px-3 py-2 text-stone-600" "Zero if user scrolled before clicking"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Mouse approach")
|
||||
(td :class "px-3 py-2 text-stone-700" "Cursor moving toward link (trajectory prediction)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for predicted target")
|
||||
(td :class "px-3 py-2 text-stone-600" "Near-zero — fetch starts ~200ms before hover"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Hover")
|
||||
(td :class "px-3 py-2 text-stone-700" "mouseover (150ms debounce)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for hovered link's route")
|
||||
(td :class "px-3 py-2 text-stone-600" "Low — typical hover-to-click is 300-500ms"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Mousedown")
|
||||
(td :class "px-3 py-2 text-stone-700" "mousedown (0ms debounce)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components for clicked link's route")
|
||||
(td :class "px-3 py-2 text-stone-600" "~80ms — mousedown-to-click gap"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "Components + data")
|
||||
(td :class "px-3 py-2 text-stone-700" "Any of the above")
|
||||
(td :class "px-3 py-2 text-stone-700" "Components " (em "and") " page data for " (code ":data") " pages")
|
||||
(td :class "px-3 py-2 text-stone-600" "Zero for components; data fetch may still be in flight")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Eager bundle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Initial page load")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for linked routes included in " (code "<script data-components>"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Zero — already in memory"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Idle timer")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "After page settles (requestIdleCallback or setTimeout)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for visible nav links, batched in one request")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Zero if idle fetch completed"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Viewport")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Link scrolls into view (IntersectionObserver)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for that link's route")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Zero if user scrolled before clicking"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Mouse approach")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Cursor moving toward link (trajectory prediction)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for predicted target")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Near-zero — fetch starts ~200ms before hover"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Hover")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "mouseover (150ms debounce)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for hovered link's route")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Low — typical hover-to-click is 300-500ms"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Mousedown")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "mousedown (0ms debounce)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components for clicked link's route")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "~80ms — mousedown-to-click gap"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "Components + data")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Any of the above")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Components " (em "and") " page data for " (code ":data") " pages")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Zero for components; data fetch may still be in flight")))))
|
||||
|
||||
(~docs/subsection :title "Eager Bundle"
|
||||
(p "The server already computes per-page component bundles. For key navigation paths — the main nav bar, section nav — the server can include " (em "linked routes' components") " in the initial bundle, not just the current page's.")
|
||||
@@ -112,7 +112,7 @@
|
||||
(~docs/subsection :title "Components + Data (Hybrid Prefetch)"
|
||||
(p "The most interesting strategy. For pages with " (code ":data") " dependencies, current behavior is full server fallback. But the page's " (em "components") " are still pure and prefetchable. If we prefetch components ahead of time, the click only needs to fetch " (em "data") " — a much smaller, faster response.")
|
||||
(p "This creates a new rendering path:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Prefetch: hover/idle/viewport triggers " (code "prefetch-components") " for the target page")
|
||||
(li "Click: client has components, but page has " (code ":data") " — fetch data from server")
|
||||
(li "Server returns " (em "only data") " (JSON or SX bindings), not the full rendered page")
|
||||
@@ -143,34 +143,34 @@
|
||||
(~docs/subsection :title "Phase 2: Client Prefetch Logic (SX spec)"
|
||||
(p "New functions in " (code "orchestration.sx") " (or a new " (code "prefetch.sx") " if scope warrants):")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. compute-missing-deps")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. compute-missing-deps")
|
||||
(p "Given a pathname, find the page, return dep names not in " (code "loaded-component-names") ". Returns nil if page not found or has data (can't client-route anyway).")
|
||||
(~docs/code :src (highlight "(define compute-missing-deps\n (fn (pathname)\n (let ((match (find-matching-route pathname _page-routes)))\n (when (and match (not (get match \"has-data\")))\n (let ((deps (or (get match \"deps\") (list)))\n (loaded (loaded-component-names)))\n (filter (fn (d) (not (contains? loaded d))) deps))))))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. prefetch-components")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. prefetch-components")
|
||||
(p "Fetch component definitions from the server for a list of names. Deduplicates in-flight requests. On success, parses and registers the returned definitions into the component env.")
|
||||
(~docs/code :src (highlight "(define _prefetch-pending (dict))\n\n(define prefetch-components\n (fn (names)\n (let ((key (join \",\" (sort names))))\n (when (not (get _prefetch-pending key))\n (set! _prefetch-pending\n (merge _prefetch-pending {key true}))\n (fetch-components-from-server names\n (fn (sx-text)\n (sx-process-component-text sx-text)\n (dict-remove! _prefetch-pending key)))))))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. prefetch-route-deps")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. prefetch-route-deps")
|
||||
(p "High-level composition: compute missing deps for a route, fetch if any.")
|
||||
(~docs/code :src (highlight "(define prefetch-route-deps\n (fn (pathname)\n (let ((missing (compute-missing-deps pathname)))\n (when (and missing (not (empty? missing)))\n (log-info (str \"sx:prefetch \"\n (len missing) \" components for \" pathname))\n (prefetch-components missing)))))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Trigger: link hover")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Trigger: link hover")
|
||||
(p "On mouseover of a boosted link, prefetch its route's missing components. Debounced 150ms to avoid fetching on quick mouse-throughs.")
|
||||
(~docs/code :src (highlight "(define bind-prefetch-on-hover\n (fn (link)\n (let ((timer nil))\n (dom-add-listener link \"mouseover\"\n (fn (e)\n (clear-timeout timer)\n (set! timer (set-timeout\n (fn () (prefetch-route-deps\n (url-pathname (dom-get-attr link \"href\"))))\n 150))))\n (dom-add-listener link \"mouseout\"\n (fn (e) (clear-timeout timer))))))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "5. Trigger: viewport intersection (opt-in)")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "5. Trigger: viewport intersection (opt-in)")
|
||||
(p "More aggressive strategy: when a link scrolls into view, prefetch its route's deps. Opt-in via " (code "sx-prefetch=\"visible\"") " attribute.")
|
||||
(~docs/code :src (highlight "(define bind-prefetch-on-visible\n (fn (link)\n (observe-intersection link\n (fn () (prefetch-route-deps\n (url-pathname (dom-get-attr link \"href\"))))\n true 0)))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "6. Integration into process-elements")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "6. Integration into process-elements")
|
||||
(p "During the existing hydration pass, for each boosted link:")
|
||||
(~docs/code :src (highlight ";; In process-elements, after binding boost behavior:\n(when (and (should-boost-link? link)\n (dom-get-attr link \"href\"))\n (bind-prefetch-on-hover link))\n\n;; Explicit viewport prefetch:\n(when (dom-has-attr? link \"sx-prefetch\")\n (bind-prefetch-on-visible link))" "lisp")))))
|
||||
|
||||
@@ -196,40 +196,40 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "File Changes" :id "file-changes"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Change")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Change")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/helpers.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "New " (code "sx_components_endpoint()") " route handler")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/factory.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Register " (code "/sx/components") " route on all SX apps")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/orchestration.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Prefetch functions: compute-missing-deps, prefetch-components, prefetch-route-deps, bind-prefetch-on-hover, bind-prefetch-on-visible")
|
||||
(td :class "px-3 py-2 text-stone-600" "2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boundary.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Declare " (code "fetch-components-from-server") ", " (code "sx-process-component-text"))
|
||||
(td :class "px-3 py-2 text-stone-600" "3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/bootstrap_js.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Emit new spec functions, boundary adapter stubs")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/helpers.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "New " (code "sx_components_endpoint()") " route handler")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/factory.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Register " (code "/sx/components") " route on all SX apps")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/orchestration.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Prefetch functions: compute-missing-deps, prefetch-components, prefetch-route-deps, bind-prefetch-on-hover, bind-prefetch-on-visible")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boundary.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Declare " (code "fetch-components-from-server") ", " (code "sx-process-component-text"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/bootstrap_js.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Emit new spec functions, boundary adapter stubs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Non-goals & rollout
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Non-Goals (This Phase)" :id "non-goals"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Analytics-driven prediction") " — no ML models or click-frequency heuristics. Trajectory prediction uses geometry, not statistics.")
|
||||
(li (strong "Cross-service prefetch") " — components are per-service. A link to a different service domain is always a server navigation.")
|
||||
(li (strong "Service worker caching") " — could layer on later, but basic fetch + in-memory registration is sufficient.")
|
||||
@@ -237,7 +237,7 @@
|
||||
|
||||
(~docs/section :title "Rollout" :id "rollout"
|
||||
(p "Incremental, each step independently valuable:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Component endpoint") " — purely additive. Refactor " (code "components_for_request()") " to accept explicit " (code "?names=") " param.")
|
||||
(li (strong "Core spec functions") " — " (code "compute-missing-deps") ", " (code "prefetch-components") ", " (code "prefetch-route-deps") " in orchestration.sx. Testable in isolation.")
|
||||
(li (strong "Hover prefetch") " — wire " (code "bind-prefetch-on-hover") " into " (code "process-elements") ". All boosted links get it automatically. Console logs show activity.")
|
||||
@@ -248,10 +248,10 @@
|
||||
|
||||
(~docs/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 "/sx/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "isomorphic architecture roadmap")
|
||||
(a :href "/sx/(etc.(plan.isomorphic-architecture))" (~tw :tokens "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.")))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "text-amber-800 text-sm") (strong "Depends on: ") "Phase 3 (client-side routing with deps checking). No dependency on Phase 4.")))))
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Plan Status Overview
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
(~docs/code :src (highlight "#'my-function → (quote my-function)" "lisp")))
|
||||
|
||||
(~docs/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 "/sx/(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.")))
|
||||
(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 "/sx/(etc.(plan.reader-macro-demo))" (~tw :tokens "text-violet-600 hover:underline") "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB.")))
|
||||
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -64,24 +64,24 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Files" :id "files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Change")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Change")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/parser.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "# dispatch in read-expr, read-raw-string helper, grammar comment"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/parser.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "# dispatch in _parse_expr(), _read_raw_string()"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/sx_ref.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Rebootstrap"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/static/scripts/sx-ref.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "Rebootstrap"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/parser.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "# dispatch in read-expr, read-raw-string helper, grammar comment"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/parser.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "# dispatch in _parse_expr(), _read_raw_string()"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/sx_ref.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Rebootstrap"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/static/scripts/sx-ref.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Rebootstrap"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Verification
|
||||
@@ -90,7 +90,7 @@
|
||||
(~docs/section :title "Verification" :id "verification"
|
||||
|
||||
(~docs/subsection :title "Parse tests"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm")
|
||||
(li "#;(ignored) 42 → 42")
|
||||
(li "(list 1 #;2 3) → (list 1 3)")
|
||||
(li "#|hello \"world\" \\n| → string: hello \"world\" \\n (literal, no escaping)")
|
||||
@@ -100,7 +100,7 @@
|
||||
(li "#x unknown → error")))
|
||||
|
||||
(~docs/subsection :title "Regression"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "All existing parser tests pass after changes")
|
||||
(li "Rebootstrapped JS and Python pass their test suites")
|
||||
(li "JS parity: SX.parse('#|hello|') returns [\"hello\"]"))))))
|
||||
|
||||
@@ -8,29 +8,29 @@
|
||||
(~docs/section :title "The Problem" :id "problem"
|
||||
(p "sx-browser.js is the full SX client runtime — evaluator, parser, renderer, engine, morph, signals, routing, orchestration, boot. Every page loads all of it.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Raw")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Gzipped")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Min+Gz")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Raw")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Gzipped")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Min+Gz")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx-browser.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "354KB")
|
||||
(td :class "px-3 py-2 text-stone-700" "75KB")
|
||||
(td :class "px-3 py-2 text-stone-700" "44KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "sx-ref.js")
|
||||
(td :class "px-3 py-2 text-stone-700" "244KB")
|
||||
(td :class "px-3 py-2 text-stone-700" "49KB")
|
||||
(td :class "px-3 py-2 text-stone-700" "29KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "React + ReactDOM")
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-700" "~5KB + ~40KB")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx-browser.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "354KB")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "75KB")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "44KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx-ref.js")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "244KB")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "49KB")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "29KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "React + ReactDOM")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~5KB + ~40KB")))))
|
||||
|
||||
(p "Most pages are L0 (pure hypermedia — server renders, client morphs). They don't need the parser, the evaluator, the full primitive set, signals, or client-side routing. They need morph + swap + trigger dispatch. That's a fraction of the runtime.")
|
||||
(p "The runtime should be sliceable: each page declares what level it operates at, and the bootstrapper emits only the code that level requires."))
|
||||
@@ -40,55 +40,55 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Tiers" :id "tiers"
|
||||
(p "Four tiers, matching the " (a :href "/sx/(geography.(reactive.(reactive-design)))" :class "text-violet-700 underline" "reactive islands") " levels:")
|
||||
(p "Four tiers, matching the " (a :href "/sx/(geography.(reactive.(reactive-design)))" (~tw :tokens "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"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Tier")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Modules")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Target (min+gz)")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Tier")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Modules")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Target (min+gz)")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L0 Hypermedia")
|
||||
(td :class "px-3 py-2 text-stone-700" "Morph, swap, trigger dispatch, history")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "engine, boot (partial)")
|
||||
(td :class "px-3 py-2 text-stone-700" "~5KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L1 DOM Ops")
|
||||
(td :class "px-3 py-2 text-stone-700" "L0 + toggle!, set-attr!, on-event, class-list ops")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "+ DOM adapter (partial)")
|
||||
(td :class "px-3 py-2 text-stone-700" "~8KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L2 Islands")
|
||||
(td :class "px-3 py-2 text-stone-700" "L1 + signals, computed, effect, defisland hydration")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "+ signals, DOM adapter (full)")
|
||||
(td :class "px-3 py-2 text-stone-700" "~15KB"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L3 Full Eval")
|
||||
(td :class "px-3 py-2 text-stone-700" "L2 + parser, evaluator, all primitives, client routing, component resolution")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "everything")
|
||||
(td :class "px-3 py-2 text-stone-700" "~44KB (current)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L0 Hypermedia")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Morph, swap, trigger dispatch, history")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "engine, boot (partial)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~5KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L1 DOM Ops")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "L0 + toggle!, set-attr!, on-event, class-list ops")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "+ DOM adapter (partial)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~8KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L2 Islands")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "L1 + signals, computed, effect, defisland hydration")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "+ signals, DOM adapter (full)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~15KB"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L3 Full Eval")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "L2 + parser, evaluator, all primitives, client routing, component resolution")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "everything")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~44KB (current)")))))
|
||||
|
||||
(p "90% of typical pages are L0. A blog post, a product listing, a checkout form — server renders, client morphs on navigation. The full evaluator only loads for pages that do client-side rendering or have reactive islands.")
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4 mt-4"
|
||||
(p :class "text-amber-900 font-medium" "Progressive loading")
|
||||
(p :class "text-amber-800" "A user navigating an L0 page downloads ~5KB. If they navigate to an L2 page (reactive island), the delta (~10KB) loads on demand. The runtime grows with need, never speculatively.")))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-amber-900 font-medium") "Progressive loading")
|
||||
(p (~tw :tokens "text-amber-800") "A user navigating an L0 page downloads ~5KB. If they navigate to an L2 page (reactive island), the delta (~10KB) loads on demand. The runtime grows with need, never speculatively.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; The slicer is SX
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "The Slicer is SX" :id "slicer-is-sx"
|
||||
(p "Per the " (a :href "/sx/(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 "Per the " (a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" (~tw :tokens "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.")
|
||||
|
||||
(~docs/code :src (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"))
|
||||
|
||||
(p "The pipeline becomes:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (code "slice.sx") " analyzes the spec and produces a define list per tier")
|
||||
(li (code "js.sx") " translates each define list to JavaScript (same translator, different input)")
|
||||
(li "The bootstrapper wraps each tier's output with its platform interface (the hand-written JS glue)")
|
||||
@@ -107,29 +107,29 @@
|
||||
|
||||
(p "From these per-define deps, we build the full dependency graph and compute transitive closures. A tier's define set is the transitive closure of its entry points:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Tier")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Entry points")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Pulls in")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Tier")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Entry points")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Pulls in")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L0")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "morph-node, process-swap, dispatch-trigger, push-url")
|
||||
(td :class "px-3 py-2 text-stone-700" "morph-attrs, morph-children, create-element, extract-swap-config — the morph/swap subgraph"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L1")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "L0 + toggle-class!, set-attr!, add-event-listener!")
|
||||
(td :class "px-3 py-2 text-stone-700" "DOM manipulation helpers — small subgraph"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L2")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "L1 + signal, deref, reset!, computed, effect, render-dom-island")
|
||||
(td :class "px-3 py-2 text-stone-700" "Signal runtime + reactive DOM adapter"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-800" "L3")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-600" "L2 + eval-expr, sx-parse, render-to-dom, resolve-component-by-cid")
|
||||
(td :class "px-3 py-2 text-stone-700" "Everything — full evaluator, parser, all ~80 primitives"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L0")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "morph-node, process-swap, dispatch-trigger, push-url")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "morph-attrs, morph-children, create-element, extract-swap-config — the morph/swap subgraph"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L1")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "L0 + toggle-class!, set-attr!, add-event-listener!")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DOM manipulation helpers — small subgraph"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L2")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "L1 + signal, deref, reset!, computed, effect, render-dom-island")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Signal runtime + reactive DOM adapter"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-800") "L3")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-600") "L2 + eval-expr, sx-parse, render-to-dom, resolve-component-by-cid")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Everything — full evaluator, parser, all ~80 primitives"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Platform interface slicing
|
||||
@@ -161,7 +161,7 @@
|
||||
|
||||
(~docs/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 "/sx/(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.")))
|
||||
(p "Combined with " (a :href "/sx/(etc.(plan.environment-images))" (~tw :tokens "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
|
||||
@@ -181,49 +181,49 @@
|
||||
(~docs/section :title "What L0 Actually Needs" :id "l0-detail"
|
||||
(p "L0 is the critical tier — it's what most pages load. Every byte matters. Let's be precise about what it contains:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Function")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Source")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Function")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Source")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "morph-node")
|
||||
(td :class "px-3 py-2 text-stone-700" "DOM diffing — update existing DOM from new HTML")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "morph-attrs")
|
||||
(td :class "px-3 py-2 text-stone-700" "Attribute diffing on a single element")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "morph-children")
|
||||
(td :class "px-3 py-2 text-stone-700" "Child node reconciliation")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "process-swap")
|
||||
(td :class "px-3 py-2 text-stone-700" "Apply sx-swap directive (innerHTML, outerHTML, etc)")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "dispatch-trigger")
|
||||
(td :class "px-3 py-2 text-stone-700" "Process sx-trigger attributes (click, submit, load, etc)")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "sx-fetch")
|
||||
(td :class "px-3 py-2 text-stone-700" "Make sx-get/sx-post requests, process response")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "orchestration.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "push-url / replace-url")
|
||||
(td :class "px-3 py-2 text-stone-700" "History management for sx-push-url")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "engine.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "boot-triggers")
|
||||
(td :class "px-3 py-2 text-stone-700" "Scan DOM for sx-* attributes, wire up event listeners")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "boot.sx"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-stone-700" "resolve-suspense")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fill in streamed suspense slots")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "boot.sx")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "morph-node")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DOM diffing — update existing DOM from new HTML")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "morph-attrs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Attribute diffing on a single element")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "morph-children")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Child node reconciliation")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "process-swap")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Apply sx-swap directive (innerHTML, outerHTML, etc)")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "dispatch-trigger")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Process sx-trigger attributes (click, submit, load, etc)")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "sx-fetch")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Make sx-get/sx-post requests, process response")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "orchestration.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "push-url / replace-url")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "History management for sx-push-url")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "engine.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "boot-triggers")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Scan DOM for sx-* attributes, wire up event listeners")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "boot.sx"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-stone-700") "resolve-suspense")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fill in streamed suspense slots")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "boot.sx")))))
|
||||
|
||||
(p "That's roughly 20-30 defines from engine.sx + orchestration.sx + boot.sx, plus their transitive deps. No parser, no evaluator, no primitives beyond what those defines call internally. The platform JS is just: fetch wrapper, DOM helpers (createElement, setAttribute, morphing), history API, and event delegation.")
|
||||
|
||||
@@ -240,30 +240,30 @@
|
||||
|
||||
(p "The " (code "--delta") " flag emits only the defines not present in the previous tier. The delta file calls " (code "Sx.extend()") " to register its additions into the already-loaded runtime.")
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "Self-hosting all the way")
|
||||
(p :class "text-violet-800" (code "slice.sx") " is spec. " (code "js.sx") " is spec. The bootstrapper script (" (code "bootstrap_js.py") ") is the thin host-specific glue that reads slice output, calls js.sx via the evaluator, and wraps with platform JS. The slicer could itself be bootstrapped to JavaScript and run in a browser build tool — but that's a future concern.")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "Self-hosting all the way")
|
||||
(p (~tw :tokens "text-violet-800") (code "slice.sx") " is spec. " (code "js.sx") " is spec. The bootstrapper script (" (code "bootstrap_js.py") ") is the thin host-specific glue that reads slice output, calls js.sx via the evaluator, and wraps with platform JS. The slicer could itself be bootstrapped to JavaScript and run in a browser build tool — but that's a future concern.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Spec modules
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Spec Modules" :id "spec-modules"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Module")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Functions")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Depends on")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Module")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Functions")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Depends on")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "slice.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "define-refs") ", " (code "define-dep-graph") ", " (code "slice-defines") ", " (code "tier-entry-points") ", " (code "page-tier") ", " (code "component-tier"))
|
||||
(td :class "px-3 py-2 text-stone-600" "deps.sx (component analysis), eval.sx (AST walking)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "js.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "js-translate-file") " — already exists, unchanged")
|
||||
(td :class "px-3 py-2 text-stone-600" "eval.sx (runs on evaluator)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "slice.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "define-refs") ", " (code "define-dep-graph") ", " (code "slice-defines") ", " (code "tier-entry-points") ", " (code "page-tier") ", " (code "component-tier"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "deps.sx (component analysis), eval.sx (AST walking)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "js.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "js-translate-file") " — already exists, unchanged")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "eval.sx (runs on evaluator)")))))
|
||||
|
||||
(p "One new spec file (" (code "slice.sx") "), one existing translator (" (code "js.sx") "), one modified host script (" (code "bootstrap_js.py") " gains " (code "--tier") " and " (code "--delta") " flags)."))
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Relationships" :id "relationships"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (a :href "/sx/(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 "/sx/(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 "/sx/(geography.(reactive.(reactive-design)))" :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 "/sx/(etc.(plan.isomorphic-architecture))" :class "text-violet-700 underline" "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it."))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (a :href "/sx/(etc.(plan.environment-images))" (~tw :tokens "text-violet-700 underline") "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.")
|
||||
(li (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "text-violet-700 underline") "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.")
|
||||
(li (a :href "/sx/(geography.(reactive.(reactive-design)))" (~tw :tokens "text-violet-700 underline") "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.")
|
||||
(li (a :href "/sx/(etc.(plan.isomorphic-architecture))" (~tw :tokens "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.")))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "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.")))))
|
||||
|
||||
@@ -10,34 +10,34 @@
|
||||
(p "The Rust bootstrapper (" (code "bootstrap_rs.py") ") reads all 20 " (code ".sx") " spec files and emits a complete Rust crate — " (strong "9,781 lines") " of Rust that compiles with zero errors. The test suite has " (strong "92 tests passing") " across parser, evaluator, primitives, and rendering.")
|
||||
(p "This is distinct from the " (a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM") " plan. That plan designs a custom bytecode format and VM. This plan bootstraps the " (strong "same tree-walking evaluator") " directly to Rust/WASM from the same " (code ".sx") " specs — no new bytecode, no new VM, just another host.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "What exists today")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Artifact")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Lines")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Status")))
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "What exists today")
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Artifact")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Lines")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "bootstrap_rs.py"))
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-600" "Rust bootstrapper, reads all 20 .sx spec files"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "sx_ref.rs"))
|
||||
(td :class "px-3 py-2 text-stone-700" "9,781")
|
||||
(td :class "px-3 py-2 text-stone-600" "Generated Rust — compiles clean, 0 errors"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "platform.rs"))
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-600" "Rust platform interface (type constructors, env ops)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" (code "test_parser.rs"))
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-600" "92 tests passing (parser, eval, primitives, render)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "bootstrap_rs.py"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Rust bootstrapper, reads all 20 .sx spec files"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "sx_ref.rs"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "9,781")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Generated Rust — compiles clean, 0 errors"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "platform.rs"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Rust platform interface (type constructors, env ops)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "test_parser.rs"))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "92 tests passing (parser, eval, primitives, render)"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitives")
|
||||
(td :class "px-3 py-2 text-stone-700" "—")
|
||||
(td :class "px-3 py-2 text-stone-600" "75 real implementations, 154 stubs"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitives")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "—")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "75 real implementations, 154 stubs"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Architecture
|
||||
@@ -46,21 +46,21 @@
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(p "The key architectural insight: " (strong "factor the browser primitives into a shared platform layer") " that both the JS evaluator and the WASM module consume.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Shared platform layer")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Shared platform layer")
|
||||
(p (code "platform_js.py") " already contains all DOM, browser, fetch, timer, and storage implementations — bootstrapped from " (code "boundary.sx") ". These are pure JavaScript functions that call browser APIs. They don't depend on the evaluator.")
|
||||
(p "Extract them into a standalone " (code "sx-platform.js") " module. Both " (code "sx-browser.js") " (the current JS evaluator) and the new " (code "sx-wasm-shim.js") " import from the same platform module:")
|
||||
(~docs/code :src (highlight " ┌─────────────────┐\n │ sx-platform.js │ ← DOM, fetch, timers, storage\n └────────┬────────┘\n │\n ┌──────────────┼──────────────┐\n │ │\n ┌─────────┴─────────┐ ┌────────┴────────┐\n │ sx-browser.js │ │ sx-wasm-shim.js │\n │ (JS tree-walker) │ │ (WASM instance │\n │ │ │ + handle table) │\n └────────────────────┘ └─────────────────┘" "text"))
|
||||
(p "One codebase for all browser primitives. Bug fixes apply to both targets. The evaluator is the only thing that changes — JS tree-walker vs Rust/WASM tree-walker.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Opaque handle table")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Opaque handle table")
|
||||
(p "Rust/WASM can't hold DOM node references directly. Instead, Rust values use " (code "Value::Handle(u32)") " — an opaque integer that indexes into a JavaScript-side handle table:")
|
||||
(~docs/code :src (highlight "// JS side (in sx-wasm-shim.js)\nconst handles = []; // handle_id → DOM node\n\nfunction allocHandle(node) {\n const id = handles.length;\n handles.push(node);\n return id;\n}\n\nfunction getHandle(id) { return handles[id]; }\nfunction freeHandle(id) { handles[id] = null; }" "javascript"))
|
||||
(~docs/code :src (highlight "// Rust side\n#[derive(Clone, Debug)]\nenum Value {\n Nil,\n Bool(bool),\n Number(f64),\n Str(String),\n Symbol(String),\n Keyword(String),\n List(Vec<Value>),\n Dict(Vec<(Value, Value)>),\n Lambda(Rc<Closure>),\n Handle(u32), // ← opaque DOM node reference\n}" "rust"))
|
||||
(p "When Rust calls a DOM primitive (e.g. " (code "createElement") "), it gets back a " (code "Handle(id)") ". When it passes that handle to " (code "appendChild") ", the JS shim looks up the real node. Rust never sees a DOM node — only integer IDs.")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "The JS shim is thin")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "The JS shim is thin")
|
||||
(p "The WASM shim's job is minimal:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Instantiate the WASM module")
|
||||
(li "Wire up the handle table")
|
||||
(li "Delegate all browser primitives to " (code "sx-platform.js"))
|
||||
@@ -73,49 +73,49 @@
|
||||
|
||||
(~docs/section :title "Current Stub Breakdown" :id "stubs"
|
||||
(p "Of the 154 stubbed primitives, most fall into a few categories that map directly to implementation phases:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Category")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Count")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Examples")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Category")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Count")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Examples")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "DOM creation & attrs")
|
||||
(td :class "px-3 py-2 text-stone-700" "~30")
|
||||
(td :class "px-3 py-2 text-stone-600" "createElement, setAttribute, appendChild")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 2"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Events & callbacks")
|
||||
(td :class "px-3 py-2 text-stone-700" "~20")
|
||||
(td :class "px-3 py-2 text-stone-600" "addEventListener, setTimeout, requestAnimationFrame")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Fetch & network")
|
||||
(td :class "px-3 py-2 text-stone-700" "~15")
|
||||
(td :class "px-3 py-2 text-stone-600" "fetch, XMLHttpRequest, SSE")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Browser APIs")
|
||||
(td :class "px-3 py-2 text-stone-700" "~25")
|
||||
(td :class "px-3 py-2 text-stone-600" "history, location, localStorage, console")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Morph engine")
|
||||
(td :class "px-3 py-2 text-stone-700" "~15")
|
||||
(td :class "px-3 py-2 text-stone-600" "morph, sync-attrs, reconcile")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Signals & reactivity")
|
||||
(td :class "px-3 py-2 text-stone-700" "~20")
|
||||
(td :class "px-3 py-2 text-stone-600" "signal, deref, effect, computed, batch")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 6"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DOM creation & attrs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~30")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "createElement, setAttribute, appendChild")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 2"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Events & callbacks")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~20")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "addEventListener, setTimeout, requestAnimationFrame")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fetch & network")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~15")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "fetch, XMLHttpRequest, SSE")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Browser APIs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~25")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "history, location, localStorage, console")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Morph engine")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~15")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "morph, sync-attrs, reconcile")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Signals & reactivity")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~20")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "signal, deref, effect, computed, batch")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 6"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Component lifecycle")
|
||||
(td :class "px-3 py-2 text-stone-700" "~29")
|
||||
(td :class "px-3 py-2 text-stone-600" "boot, hydrate, register-component")
|
||||
(td :class "px-3 py-2 text-stone-600" "Phase 4"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component lifecycle")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "~29")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "boot, hydrate, register-component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Phase 4"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 1: WASM build + parse/eval
|
||||
@@ -123,7 +123,7 @@
|
||||
|
||||
(~docs/section :title "Phase 1: WASM Build + Parse/Eval" :id "phase-1"
|
||||
(p "Get the existing Rust code compiling to WASM and running parse/eval in the browser.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Add " (code "wasm-bindgen") " and " (code "wasm-pack") " to the Rust crate")
|
||||
(li "Export " (code "#[wasm_bindgen]") " functions: " (code "parse(src) -> JsValue") " and " (code "eval(src) -> JsValue"))
|
||||
(li "All 75 real primitives work — arithmetic, string ops, list ops, dict ops, comparisons")
|
||||
@@ -137,7 +137,7 @@
|
||||
|
||||
(~docs/section :title "Phase 2: DOM Rendering via Handle Table" :id "phase-2"
|
||||
(p "Implement the shared platform layer and handle table. Rust can create and manipulate DOM nodes.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "Extract " (code "sx-platform.js") " from " (code "platform_js.py") " output — all DOM primitives as standalone functions")
|
||||
(li "Implement " (code "sx-wasm-shim.js") " — WASM instantiation + handle table + platform imports")
|
||||
(li "Wire " (code "Value::Handle(u32)") " through the Rust evaluator for DOM node references")
|
||||
@@ -151,7 +151,7 @@
|
||||
|
||||
(~docs/section :title "Phase 3: Events + Fetch + Morph" :id "phase-3"
|
||||
(p "The hardest phase — callbacks cross the WASM/JS boundary. Event handlers are Rust closures that JS must be able to invoke.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Callback table") " — mirror of the handle table but for functions. Rust registers a closure, gets back an ID. JS calls the ID when the event fires. Rust looks up and invokes the closure.")
|
||||
(li (strong "Event listeners") " — " (code "addEventListener") " stores the callback ID on the element handle. JS dispatches events to the WASM callback table.")
|
||||
(li (strong "Fetch") " — Rust initiates fetch via JS import, JS calls " (code "sx-platform.js") " " (code "fetch") ", returns result to Rust via callback.")
|
||||
@@ -164,7 +164,7 @@
|
||||
|
||||
(~docs/section :title "Phase 4: Boot + Hydration + Components" :id "phase-4"
|
||||
(p "Full page lifecycle — the WASM module replaces " (code "sx-browser.js") " for a complete page.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Boot sequence") " — WASM module loads, reads " (code "data-components") " script tags, registers component definitions, processes " (code "data-sx-page") " content")
|
||||
(li (strong "Component registration") " — " (code "defcomp") " and " (code "defisland") " evaluated in Rust, stored in the WASM-side environment")
|
||||
(li (strong "Hydration") " — server-rendered HTML matched against component tree, event handlers attached, islands activated")
|
||||
@@ -177,7 +177,7 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Routing + Streaming + SSE" :id "phase-5"
|
||||
(p "Client-side navigation and real-time updates.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Client routing") " — " (code "navigate-to") ", " (code "popstate") " handling, URL matching from " (code "defpage") " routes")
|
||||
(li (strong "History API") " — " (code "pushState") " / " (code "replaceState") " via JS imports")
|
||||
(li (strong "SSE") " — server-sent events for live updates, morph on incoming HTML/SX")
|
||||
@@ -190,7 +190,7 @@
|
||||
|
||||
(~docs/section :title "Phase 6: Signals + Reactive Islands" :id "phase-6"
|
||||
(p "The most architecturally interesting phase — closures-as-values must work across the WASM boundary.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Signal primitives") " — " (code "signal") ", " (code "deref") ", " (code "reset!") ", " (code "swap!") ", " (code "computed") ", " (code "effect") ", " (code "batch") " all implemented in Rust")
|
||||
(li (strong "Reactive DOM updates") " — signal changes trigger DOM mutations via handle table operations. No full re-render — fine-grained updates.")
|
||||
(li (strong "Island scoping") " — " (code "with-island-scope") " manages signal lifecycle. Dispose island = drop all signals, effects, and handles.")
|
||||
@@ -204,7 +204,7 @@
|
||||
|
||||
(~docs/section :title "Phase 7: Full Parity + Gradual Rollout" :id "phase-7"
|
||||
(p "Shadow-compare and feature-flagged rollout.")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Shadow compare") " — run both JS and WASM evaluators in parallel on every page render. Assert identical DOM output. Log divergences. Same principle as async eval convergence.")
|
||||
(li (strong "Feature flag") " — server sets " (code "data-sx-runtime=\"wasm\"") " or " (code "\"js\"") " on the page. Boot script loads the corresponding evaluator. Flag can be per-page, per-user, or global.")
|
||||
(li (strong "Progressive enhancement") " — try WASM first, fall back to JS if WASM instantiation fails. Ship both " (code "sx-browser.js") " and " (code "sx-wasm.wasm") ".")
|
||||
@@ -218,7 +218,7 @@
|
||||
(~docs/section :title "Shared Platform Layer" :id "shared-platform"
|
||||
(p "The architectural heart of this plan. " (code "platform_js.py") " already generates all browser primitive implementations — DOM manipulation, fetch wrappers, timer management, storage access, history API, SSE handling. Currently these are inlined into " (code "sx-browser.js") ".")
|
||||
(p "The extraction:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Step 1") " — refactor " (code "platform_js.py") " to emit a standalone " (code "sx-platform.js") " module (ES module exports)")
|
||||
(li (strong "Step 2") " — " (code "sx-browser.js") " imports from " (code "sx-platform.js") " instead of containing the implementations inline")
|
||||
(li (strong "Step 3") " — " (code "sx-wasm-shim.js") " imports from the same " (code "sx-platform.js") " and wires the functions as WASM imports"))
|
||||
@@ -230,7 +230,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Interaction with Other Plans" :id "interactions"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "WASM Bytecode VM") " — complementary, not competing. This plan bootstraps the tree-walking evaluator to Rust/WASM. The bytecode VM plan compiles SX to a custom bytecode format and runs it in a dispatch loop. Tree-walking comes first (it's working now). Bytecode VM is a future optimisation on top of the Rust host.")
|
||||
(li (strong "Runtime Slicing") " — the WASM module can be tiered. L0 pages need no WASM at all. L1 pages need a minimal WASM module (just parse + eval, no DOM). L2+ pages need the full module with DOM and signals. Tree-shake unused primitives per tier.")
|
||||
(li (strong "Content-Addressed Components") " — deterministic Rust compilation means the same " (code ".sx") " source always produces the same WASM binary. CID-addressable WASM modules. Fetch the evaluator itself by content hash.")
|
||||
@@ -241,7 +241,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "Same spec, another host.") " The Rust target is not special — it's the same architecture as Python and JavaScript. " (code "bootstrap_rs.py") " reads the same " (code ".sx") " files and emits Rust instead of Python or JS. The spec doesn't know which host runs it.")
|
||||
(li (strong "Platform primitives stay in JavaScript.") " DOM, fetch, timers, storage — these are browser APIs. Rust doesn't reimplement them. It calls them through the shared platform layer via the handle table.")
|
||||
(li (strong "Shared platform, not duplicated platform.") " The key win over a pure-WASM approach. Browser primitives exist once in " (code "sx-platform.js") ". Both evaluators use them. No divergence, no duplicate bugs.")
|
||||
@@ -254,7 +254,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After completion:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "SX bootstraps to four hosts: JavaScript, Python, Rust (native), Rust (WASM)")
|
||||
(li "Browser evaluation runs at near-native speed via WASM tree-walking")
|
||||
(li "All browser primitives shared between JS and WASM evaluators — zero duplication")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(defcomp ~plans/scoped-effects/plan-scoped-effects-content ()
|
||||
(~docs/page :title "Scoped Effects — The Deepest Primitive"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Everything SX does — spreads, signals, islands, lakes, morph, context, collect — "
|
||||
"is an instance of one pattern: a named scope with a value flowing down, "
|
||||
"contributions flowing up, and a propagation mode determining when effects are realised. "
|
||||
@@ -19,54 +19,54 @@
|
||||
|
||||
(p "SX has accumulated several mechanisms that all do variations of the same thing:")
|
||||
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Mechanism")
|
||||
(th :class "text-left py-2 pr-4" "Direction")
|
||||
(th :class "text-left py-2 pr-4" "When")
|
||||
(th :class "text-left py-2" "Boundary")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Mechanism")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Direction")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "When")
|
||||
(th (~tw :tokens "text-left py-2") "Boundary")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "spread")
|
||||
(td :class "py-2 pr-4" "child → parent")
|
||||
(td :class "py-2 pr-4" "render time")
|
||||
(td :class "py-2" "element"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "collect! / collected")
|
||||
(td :class "py-2 pr-4" "child → ancestor")
|
||||
(td :class "py-2 pr-4" "render time")
|
||||
(td :class "py-2" "render tree"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "provide / context")
|
||||
(td :class "py-2 pr-4" "ancestor → child")
|
||||
(td :class "py-2 pr-4" "render time")
|
||||
(td :class "py-2" "render subtree"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "signal / effect")
|
||||
(td :class "py-2 pr-4" "value → subscribers")
|
||||
(td :class "py-2 pr-4" "signal change")
|
||||
(td :class "py-2" "reactive scope"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "defisland")
|
||||
(td :class "py-2 pr-4" "server → client")
|
||||
(td :class "py-2 pr-4" "hydration")
|
||||
(td :class "py-2" "server/client"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "lake")
|
||||
(td :class "py-2 pr-4" "server → client slot")
|
||||
(td :class "py-2 pr-4" "navigation/morph")
|
||||
(td :class "py-2" "client/server"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "reactive-spread")
|
||||
(td :class "py-2 pr-4" "child → parent")
|
||||
(td :class "py-2 pr-4" "signal change")
|
||||
(td :class "py-2" "element"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "spread")
|
||||
(td (~tw :tokens "py-2 pr-4") "child → parent")
|
||||
(td (~tw :tokens "py-2 pr-4") "render time")
|
||||
(td (~tw :tokens "py-2") "element"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "collect! / collected")
|
||||
(td (~tw :tokens "py-2 pr-4") "child → ancestor")
|
||||
(td (~tw :tokens "py-2 pr-4") "render time")
|
||||
(td (~tw :tokens "py-2") "render tree"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "provide / context")
|
||||
(td (~tw :tokens "py-2 pr-4") "ancestor → child")
|
||||
(td (~tw :tokens "py-2 pr-4") "render time")
|
||||
(td (~tw :tokens "py-2") "render subtree"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "signal / effect")
|
||||
(td (~tw :tokens "py-2 pr-4") "value → subscribers")
|
||||
(td (~tw :tokens "py-2 pr-4") "signal change")
|
||||
(td (~tw :tokens "py-2") "reactive scope"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "defisland")
|
||||
(td (~tw :tokens "py-2 pr-4") "server → client")
|
||||
(td (~tw :tokens "py-2 pr-4") "hydration")
|
||||
(td (~tw :tokens "py-2") "server/client"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "lake")
|
||||
(td (~tw :tokens "py-2 pr-4") "server → client slot")
|
||||
(td (~tw :tokens "py-2 pr-4") "navigation/morph")
|
||||
(td (~tw :tokens "py-2") "client/server"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "reactive-spread")
|
||||
(td (~tw :tokens "py-2 pr-4") "child → parent")
|
||||
(td (~tw :tokens "py-2 pr-4") "signal change")
|
||||
(td (~tw :tokens "py-2") "element"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "def-store / use-store")
|
||||
(td :class "py-2 pr-4" "island ↔ island")
|
||||
(td :class "py-2 pr-4" "signal change")
|
||||
(td :class "py-2" "cross-island"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "def-store / use-store")
|
||||
(td (~tw :tokens "py-2 pr-4") "island ↔ island")
|
||||
(td (~tw :tokens "py-2 pr-4") "signal change")
|
||||
(td (~tw :tokens "py-2") "cross-island"))))
|
||||
|
||||
(p "Eight mechanisms. Each invented separately for a real use case. "
|
||||
"Each with its own API surface, its own implementation path, its own section in the spec.")
|
||||
@@ -122,7 +122,7 @@
|
||||
(~docs/section :title "The scope primitive" :id "scope"
|
||||
|
||||
(p "A " (code "scope") " is a named region of the render tree with three properties:")
|
||||
(ol :class "space-y-2 text-stone-600"
|
||||
(ol (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "Value") " — data flowing " (em "downward") " to descendants (readable via " (code "context") ")")
|
||||
(li (strong "Accumulator") " — data flowing " (em "upward") " from descendants (writable via " (code "emit!") ")")
|
||||
(li (strong "Propagation mode") " — " (em "when") " effects on this scope are realised"))
|
||||
@@ -131,37 +131,37 @@
|
||||
|
||||
(p "Every existing mechanism is a scope with a specific configuration:")
|
||||
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Mechanism")
|
||||
(th :class "text-left py-2 pr-4" "Scope equivalent")
|
||||
(th :class "text-left py-2" "Propagation")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Mechanism")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Scope equivalent")
|
||||
(th (~tw :tokens "text-left py-2") "Propagation")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "provide")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :value v body)")
|
||||
(td :class "py-2" ":render"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "defisland")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :propagation :reactive body)")
|
||||
(td :class "py-2" ":reactive"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "lake")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :propagation :morph body)")
|
||||
(td :class "py-2" ":morph"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "spread")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(emit! :element-attrs dict)")
|
||||
(td :class "py-2" ":render (implicit)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "collect!")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(emit! name value)")
|
||||
(td :class "py-2" ":render"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "provide")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(scope name :value v body)")
|
||||
(td (~tw :tokens "py-2") ":render"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "defisland")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(scope name :propagation :reactive body)")
|
||||
(td (~tw :tokens "py-2") ":reactive"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "lake")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(scope name :propagation :morph body)")
|
||||
(td (~tw :tokens "py-2") ":morph"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "spread")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(emit! :element-attrs dict)")
|
||||
(td (~tw :tokens "py-2") ":render (implicit)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "collect!")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(emit! name value)")
|
||||
(td (~tw :tokens "py-2") ":render"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "def-store")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :value (signal v) :propagation :reactive)")
|
||||
(td :class "py-2" ":reactive (cross-scope)"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "def-store")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "(scope name :value (signal v) :propagation :reactive)")
|
||||
(td (~tw :tokens "py-2") ":reactive (cross-scope)"))))
|
||||
|
||||
(~docs/subsection :title "Scopes compose by nesting"
|
||||
(~docs/code :src (highlight ";; An island with a theme context and a morphable lake\n(scope \"my-island\" :propagation :reactive\n (let ((colour (signal \"violet\")))\n (scope \"theme\" :value {:primary colour} :propagation :render\n (div\n (h1 :style (str \"color:\" (deref (get (context \"theme\") :primary)))\n \"Themed heading\")\n (scope \"product-details\" :propagation :morph\n ;; Server morphs this on navigation\n ;; Reactive attrs on the h1 are protected\n (~product-card :id 42))))))" "lisp"))
|
||||
@@ -181,29 +181,29 @@
|
||||
(p "Direction (up/down) × propagation mode (render/reactive/morph) gives six cells. "
|
||||
"Every mechanism in SX occupies one:")
|
||||
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "")
|
||||
(th :class "text-left py-2 pr-4" "Render-time")
|
||||
(th :class "text-left py-2 pr-4" "Reactive")
|
||||
(th :class "text-left py-2" "Morph")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Render-time")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Reactive")
|
||||
(th (~tw :tokens "text-left py-2") "Morph")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-semibold" "Down ↓")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "context")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "reactive context")
|
||||
(td :class "py-2 font-mono text-xs" "server props"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-semibold" "Up ↑")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "emit! / spread")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "reactive-spread")
|
||||
(td :class "py-2 font-mono text-xs" "lake contributions"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-semibold") "Down ↓")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "context")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "reactive context")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "server props"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-semibold") "Up ↑")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "emit! / spread")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "reactive-spread")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "lake contributions"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-semibold" "Both ↕")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "provide")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "island")
|
||||
(td :class "py-2 font-mono text-xs" "full morph"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-semibold") "Both ↕")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "provide")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "island")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "full morph"))))
|
||||
|
||||
(p "Some cells are already implemented. Some are new. "
|
||||
"The point is that " (code "scope") " fills them all from " (strong "one primitive") ". "
|
||||
@@ -229,33 +229,33 @@
|
||||
(strong "algebraic effects with handlers") ".")
|
||||
|
||||
(~docs/subsection :title "The correspondence"
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Algebraic effects")
|
||||
(th :class "text-left py-2 pr-4" "SX")
|
||||
(th :class "text-left py-2" "What it does")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Algebraic effects")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "SX")
|
||||
(th (~tw :tokens "text-left py-2") "What it does")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "perform effect")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "emit!")
|
||||
(td :class "py-2" "Raise an effect upward through the tree"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "handle effect")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "scope / provide")
|
||||
(td :class "py-2" "Catch and interpret the effect"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "ask")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "context")
|
||||
(td :class "py-2" "Read the nearest handler's value"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "handler nesting")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "scope nesting")
|
||||
(td :class "py-2" "Inner handlers shadow outer ones"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "perform effect")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "emit!")
|
||||
(td (~tw :tokens "py-2") "Raise an effect upward through the tree"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "handle effect")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "scope / provide")
|
||||
(td (~tw :tokens "py-2") "Catch and interpret the effect"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "ask")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "context")
|
||||
(td (~tw :tokens "py-2") "Read the nearest handler's value"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "handler nesting")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "scope nesting")
|
||||
(td (~tw :tokens "py-2") "Inner handlers shadow outer ones"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "resumption")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "reactive propagation")
|
||||
(td :class "py-2" "Handler can re-invoke the continuation"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "resumption")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "reactive propagation")
|
||||
(td (~tw :tokens "py-2") "Handler can re-invoke the continuation"))))
|
||||
|
||||
(p "The last row is the deep one. In algebraic effect theory, a handler can "
|
||||
(strong "resume") " the computation that performed the effect — optionally with "
|
||||
@@ -268,7 +268,7 @@
|
||||
"Exceptions, generators, async/await, coroutines, backtracking, and "
|
||||
"nondeterminism are all instances of algebraic effects with different handler strategies.")
|
||||
(p "SX's three propagation modes are three handler strategies:")
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-600")
|
||||
(li (strong "Render") " = " (em "one-shot") " handler — handle the effect immediately, "
|
||||
"don't resume. Like " (code "try/catch") " for rendering.")
|
||||
(li (strong "Reactive") " = " (em "multi-shot") " handler — handle the effect, "
|
||||
@@ -295,7 +295,7 @@
|
||||
(~docs/subsection :title "1. New propagation modes"
|
||||
(p "The three modes (render, reactive, morph) are not hardcoded — they are "
|
||||
"handler strategies. New strategies can be added without new primitives:")
|
||||
(ul :class "list-disc pl-5 space-y-2 text-stone-600"
|
||||
(ul (~tw :tokens "list-disc pl-5 space-y-2 text-stone-600")
|
||||
(li (strong ":animation") " — effects realised on requestAnimationFrame, "
|
||||
"batched per frame, interruptible by higher-priority updates")
|
||||
(li (strong ":idle") " — effects deferred to requestIdleCallback, "
|
||||
@@ -346,9 +346,9 @@
|
||||
(code "emitted") ". Platform provides " (code "scope-push!/scope-pop!") ". "
|
||||
"Spreads reimplemented on provide/emit!.")
|
||||
(p "See "
|
||||
(a :href "/sx/(geography.(provide))" :class "text-violet-600 hover:underline" "provide article")
|
||||
(a :href "/sx/(geography.(provide))" (~tw :tokens "text-violet-600 hover:underline") "provide article")
|
||||
" and "
|
||||
(a :href "/sx/(geography.(spreads))" :class "text-violet-600 hover:underline" "spreads article")
|
||||
(a :href "/sx/(geography.(spreads))" (~tw :tokens "text-violet-600 hover:underline") "spreads article")
|
||||
"."))
|
||||
|
||||
(~docs/subsection :title "Phase 2: scope as the common form ✓"
|
||||
@@ -359,7 +359,7 @@
|
||||
(p "The unified platform structure:")
|
||||
(~docs/code :src (highlight "_scope_stacks = {} ;; {name: [{value, emitted: [], dedup: bool}]}" "python"))
|
||||
(p "See "
|
||||
(a :href "/sx/(geography.(scopes))" :class "text-violet-600 hover:underline" "scopes article")
|
||||
(a :href "/sx/(geography.(scopes))" (~tw :tokens "text-violet-600 hover:underline") "scopes article")
|
||||
"."))
|
||||
|
||||
(~docs/subsection :title "Phase 3: effect handlers (future)"
|
||||
@@ -380,33 +380,33 @@
|
||||
(p "The self-hosting spec currently has separate code paths for each mechanism. "
|
||||
"Under the scope model, they converge:")
|
||||
|
||||
(table :class "w-full text-sm border-collapse mb-6"
|
||||
(table (~tw :tokens "w-full text-sm border-collapse mb-6")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-300"
|
||||
(th :class "text-left py-2 pr-4" "Spec file")
|
||||
(th :class "text-left py-2 pr-4" "Current")
|
||||
(th :class "text-left py-2" "After scope")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Spec file")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Current")
|
||||
(th (~tw :tokens "text-left py-2") "After scope")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "eval.sx")
|
||||
(td :class "py-2 pr-4" "provide + defisland as separate special forms")
|
||||
(td :class "py-2" "scope as one special form, sugar for provide/defisland/lake"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "adapter-html.sx")
|
||||
(td :class "py-2 pr-4" "provide, island, lake as separate dispatch cases")
|
||||
(td :class "py-2" "one scope dispatch, mode determines serialisation"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "adapter-dom.sx")
|
||||
(td :class "py-2 pr-4" "render-dom-island, render-dom-lake, reactive-spread")
|
||||
(td :class "py-2" "one render-dom-scope, mode determines lifecycle"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "engine.sx")
|
||||
(td :class "py-2 pr-4" "morph-island-children, sync-attrs, data-sx-reactive-attrs")
|
||||
(td :class "py-2" "morph-scope (scope boundary determines skip/update)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "eval.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "provide + defisland as separate special forms")
|
||||
(td (~tw :tokens "py-2") "scope as one special form, sugar for provide/defisland/lake"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "adapter-html.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "provide, island, lake as separate dispatch cases")
|
||||
(td (~tw :tokens "py-2") "one scope dispatch, mode determines serialisation"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "adapter-dom.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "render-dom-island, render-dom-lake, reactive-spread")
|
||||
(td (~tw :tokens "py-2") "one render-dom-scope, mode determines lifecycle"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "engine.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "morph-island-children, sync-attrs, data-sx-reactive-attrs")
|
||||
(td (~tw :tokens "py-2") "morph-scope (scope boundary determines skip/update)"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "signals.sx")
|
||||
(td :class "py-2 pr-4" "standalone signal runtime")
|
||||
(td :class "py-2" "unchanged — signals are the value layer, scopes are the structure layer"))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "signals.sx")
|
||||
(td (~tw :tokens "py-2 pr-4") "standalone signal runtime")
|
||||
(td (~tw :tokens "py-2") "unchanged — signals are the value layer, scopes are the structure layer"))))
|
||||
|
||||
(p "Signals remain orthogonal. A scope's " (code ":value") " can be a signal or a plain "
|
||||
"value — the scope doesn't care. The propagation mode determines whether the scope "
|
||||
@@ -421,8 +421,8 @@
|
||||
|
||||
(p "Go deep enough and there is one operation:")
|
||||
|
||||
(blockquote :class "border-l-4 border-violet-300 pl-4 my-6"
|
||||
(p :class "text-lg font-semibold text-stone-700"
|
||||
(blockquote (~tw :tokens "border-l-4 border-violet-300 pl-4 my-6")
|
||||
(p (~tw :tokens "text-lg font-semibold text-stone-700")
|
||||
"Evaluate this expression in a scope. Handle effects that cross scope boundaries."))
|
||||
|
||||
(p "Rendering is evaluating expressions in a scope where " (code "emit!") " effects "
|
||||
@@ -431,7 +431,7 @@
|
||||
"and re-evaluating them in existing scopes.")
|
||||
|
||||
(p "Every SX mechanism — every one — is a specific answer to three questions:")
|
||||
(ol :class "space-y-2 text-stone-600"
|
||||
(ol (~tw :tokens "space-y-2 text-stone-600")
|
||||
(li (strong "What scope") " am I in? (element / subtree / island / lake / global)")
|
||||
(li (strong "What direction") " does data flow? (down via context, up via emit)")
|
||||
(li (strong "When") " are effects realised? (render / signal change / network)"))
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
;; Status banner
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(div :class "rounded-lg bg-green-50 border border-green-200 p-4 mb-8"
|
||||
(div :class "flex items-center gap-3"
|
||||
(span :class "inline-flex items-center rounded-full bg-green-100 px-3 py-1 text-sm font-semibold text-green-800"
|
||||
(div (~tw :tokens "rounded-lg bg-green-50 border border-green-200 p-4 mb-8")
|
||||
(div (~tw :tokens "flex items-center gap-3")
|
||||
(span (~tw :tokens "inline-flex items-center rounded-full bg-green-100 px-3 py-1 text-sm font-semibold text-green-800")
|
||||
"Complete")
|
||||
(p :class "text-green-700 text-sm"
|
||||
(p (~tw :tokens "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 "/sx/(language.(bootstrapper.self-hosting))" :class "underline text-green-600 font-medium"
|
||||
(a :href "/sx/(language.(bootstrapper.self-hosting))" (~tw :tokens "underline text-green-600 font-medium")
|
||||
"See live verification."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -43,31 +43,31 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Results" :id "results"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Metric")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Value")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Metric")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Value")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "py.sx size")
|
||||
(td :class "px-4 py-2 font-mono" "1,182 lines"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "bootstrap_py.py size (replaced)")
|
||||
(td :class "px-4 py-2 font-mono" "902 lines (PyEmitter class)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "Defines translated")
|
||||
(td :class "px-4 py-2 font-mono" "128/128 exact match"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "Spec files processed")
|
||||
(td :class "px-4 py-2 font-mono" "7 (eval, forms, render, adapter-html, adapter-sx, deps, signals)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 text-stone-600" "Output size")
|
||||
(td :class "px-4 py-2 font-mono" "1,490 lines / 88,955 bytes"))
|
||||
(tr :class "border-t border-stone-100 bg-green-50"
|
||||
(td :class "px-4 py-2 font-semibold text-green-700" "G0 == G1")
|
||||
(td :class "px-4 py-2 font-semibold text-green-700" "Identical (diff is empty)"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "py.sx size")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "1,182 lines"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "bootstrap_py.py size (replaced)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "902 lines (PyEmitter class)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "Defines translated")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "128/128 exact match"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "Spec files processed")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "7 (eval, forms, render, adapter-html, adapter-sx, deps, signals)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 text-stone-600") "Output size")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "1,490 lines / 88,955 bytes"))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-green-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-semibold text-green-700") "G0 == G1")
|
||||
(td (~tw :tokens "px-4 py-2 font-semibold text-green-700") "Identical (diff is empty)"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Architecture
|
||||
@@ -75,34 +75,34 @@
|
||||
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(p "Three bootstrapper generations:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Gen")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Source")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Runs on")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Reads")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Produces")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Gen")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Source")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Runs on")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Reads")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Produces")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono text-stone-600" "G0")
|
||||
(td :class "px-4 py-2" (code "bootstrap_py.py"))
|
||||
(td :class "px-4 py-2" "Python (manual)")
|
||||
(td :class "px-4 py-2" (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td :class "px-4 py-2" (code "sx_ref.py")))
|
||||
(tr :class "border-t border-stone-100 bg-green-50"
|
||||
(td :class "px-4 py-2 font-mono text-green-700" "G1")
|
||||
(td :class "px-4 py-2" (code "py.sx"))
|
||||
(td :class "px-4 py-2" "Python evaluator + SX")
|
||||
(td :class "px-4 py-2" (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td :class "px-4 py-2 font-semibold text-green-700" (code "sx_ref.py") " (identical)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono text-stone-600" "G2")
|
||||
(td :class "px-4 py-2" (code "py.sx"))
|
||||
(td :class "px-4 py-2" (code "sx_ref.py") " from G1 + SX")
|
||||
(td :class "px-4 py-2" (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td :class "px-4 py-2" (code "sx_ref.py") " (fixed-point)")))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-stone-600") "G0")
|
||||
(td (~tw :tokens "px-4 py-2") (code "bootstrap_py.py"))
|
||||
(td (~tw :tokens "px-4 py-2") "Python (manual)")
|
||||
(td (~tw :tokens "px-4 py-2") (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td (~tw :tokens "px-4 py-2") (code "sx_ref.py")))
|
||||
(tr (~tw :tokens "border-t border-stone-100 bg-green-50")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-green-700") "G1")
|
||||
(td (~tw :tokens "px-4 py-2") (code "py.sx"))
|
||||
(td (~tw :tokens "px-4 py-2") "Python evaluator + SX")
|
||||
(td (~tw :tokens "px-4 py-2") (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td (~tw :tokens "px-4 py-2 font-semibold text-green-700") (code "sx_ref.py") " (identical)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono text-stone-600") "G2")
|
||||
(td (~tw :tokens "px-4 py-2") (code "py.sx"))
|
||||
(td (~tw :tokens "px-4 py-2") (code "sx_ref.py") " from G1 + SX")
|
||||
(td (~tw :tokens "px-4 py-2") (code "eval.sx") ", " (code "render.sx") ", ...")
|
||||
(td (~tw :tokens "px-4 py-2") (code "sx_ref.py") " (fixed-point)")))))
|
||||
|
||||
(p "G0 is the hand-written compiler. G1 replaces it with SX. "
|
||||
"G2 proves the fixed-point: " (code "py.sx") " compiled by its own output "
|
||||
@@ -118,59 +118,59 @@
|
||||
(p "SX identifiers become valid Python identifiers. "
|
||||
"The RENAMES dict (200+ entries) handles explicit mappings; "
|
||||
"general rules handle the rest:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "SX")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Python")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Rule")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "SX")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Python")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Rule")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "eval-expr")
|
||||
(td :class "px-4 py-2 font-mono" "eval_expr")
|
||||
(td :class "px-4 py-2" "kebab → snake"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "nil?")
|
||||
(td :class "px-4 py-2 font-mono" "is_nil")
|
||||
(td :class "px-4 py-2" "predicate rename"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "empty?")
|
||||
(td :class "px-4 py-2 font-mono" "empty_p")
|
||||
(td :class "px-4 py-2" "? → _p (general)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "set!")
|
||||
(td :class "px-4 py-2 font-mono" "set_b")
|
||||
(td :class "px-4 py-2" "! → _b"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "type")
|
||||
(td :class "px-4 py-2 font-mono" "type_")
|
||||
(td :class "px-4 py-2" "Python reserved word escape"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "eval-expr")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "eval_expr")
|
||||
(td (~tw :tokens "px-4 py-2") "kebab → snake"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "nil?")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "is_nil")
|
||||
(td (~tw :tokens "px-4 py-2") "predicate rename"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "empty?")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "empty_p")
|
||||
(td (~tw :tokens "px-4 py-2") "? → _p (general)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "set!")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "set_b")
|
||||
(td (~tw :tokens "px-4 py-2") "! → _b"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "type")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "type_")
|
||||
(td (~tw :tokens "px-4 py-2") "Python reserved word escape"))))))
|
||||
|
||||
(~docs/subsection :title "Special Forms"
|
||||
(p "Each SX special form maps to a Python expression pattern:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 my-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead :class "bg-stone-50"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 my-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (~tw :tokens "bg-stone-50")
|
||||
(tr
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "SX")
|
||||
(th :class "px-4 py-2 text-left font-semibold text-stone-700" "Python")))
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "SX")
|
||||
(th (~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700") "Python")))
|
||||
(tbody
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(if c t e)")
|
||||
(td :class "px-4 py-2 font-mono" "(t if sx_truthy(c) else e)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(let ((a 1)) body)")
|
||||
(td :class "px-4 py-2 font-mono" "(lambda a: body)(1)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(fn (x) body)")
|
||||
(td :class "px-4 py-2 font-mono" "lambda x: body"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(and a b c)")
|
||||
(td :class "px-4 py-2 font-mono" "(a if not sx_truthy(a) else ...)"))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "px-4 py-2 font-mono" "(case x \"a\" 1)")
|
||||
(td :class "px-4 py-2 font-mono" "_sx_case(x, [(\"a\", lambda: 1)])"))))))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(if c t e)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(t if sx_truthy(c) else e)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(let ((a 1)) body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(lambda a: body)(1)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(fn (x) body)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "lambda x: body"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(and a b c)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(a if not sx_truthy(a) else ...)"))
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "(case x \"a\" 1)")
|
||||
(td (~tw :tokens "px-4 py-2 font-mono") "_sx_case(x, [(\"a\", lambda: 1)])"))))))
|
||||
|
||||
(~docs/subsection :title "Mutation: set! and Cell Variables"
|
||||
(p "Python closures can read but not rebind outer variables. "
|
||||
@@ -201,7 +201,7 @@
|
||||
"it translates SX syntax, not SX semantics. Every SX form has a "
|
||||
"straightforward Python equivalent.")
|
||||
(p "The roadmap:")
|
||||
(ul :class "list-disc pl-6 space-y-1 text-stone-700"
|
||||
(ul (~tw :tokens "list-disc pl-6 space-y-1 text-stone-700")
|
||||
(li (code "py.sx") " → Python — " (strong "done"))
|
||||
(li (code "js.sx") " → JavaScript (replaces " (code "bootstrap_js.py") ")")
|
||||
(li (code "go.sx") " → Go (new host)")
|
||||
|
||||
@@ -12,48 +12,48 @@
|
||||
(~docs/section :title "What remains" :id "remains"
|
||||
(~docs/note "Nothing has been implemented. This is the full scope of work.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 1: Data Model + Encryption")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 1: Data Model + Encryption")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li (code "shared/models/social_connection.py") " — SocialConnection model (user_id, platform, tokens, scopes, extra_data)")
|
||||
(li (code "shared/infrastructure/social_crypto.py") " — Fernet encrypt/decrypt for tokens")
|
||||
(li "Alembic migration for social_connections table")
|
||||
(li "Environment variables for per-platform OAuth credentials")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 2: Platform OAuth Clients")
|
||||
(p :class "text-sm text-stone-600 mb-2" "All in " (code "account/services/social_platforms/") ":")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 2: Platform OAuth Clients")
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "All in " (code "account/services/social_platforms/") ":")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li (code "base.py") " — SocialPlatform ABC, OAuthResult, ShareResult")
|
||||
(li (code "meta.py") " — Facebook + Instagram + Threads (Graph API)")
|
||||
(li (code "twitter.py") " — OAuth 2.0 with PKCE")
|
||||
(li (code "linkedin.py") " — LinkedIn Posts API")
|
||||
(li (code "mastodon.py") " — Dynamic app registration per instance")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 3: Account Blueprint")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 3: Account Blueprint")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li (code "account/bp/social/routes.py") " — /social/ list, /social/connect/<platform>/, /social/callback/<platform>/, /social/share/")
|
||||
(li "Register before account blueprint (account has catch-all /<slug>/ route)")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 4: Templates")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 4: Templates")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li "Social panel — platform cards, connect/disconnect")
|
||||
(li "Share panel — content preview, account checkboxes, share button")
|
||||
(li "Share result — per-platform success/failure with links")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 5: Share Button in Content Apps")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 5: Share Button in Content Apps")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li "share-button fragment from account service")
|
||||
(li "Blog, events, market detail pages fetch and render the fragment")))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Phase 6: Token Refresh + Share History")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 text-sm"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Phase 6: Token Refresh + Share History")
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1 text-sm")
|
||||
(li "Automatic token refresh before posting")
|
||||
(li "Optional social_shares table for history and duplicate prevention")))))))
|
||||
|
||||
|
||||
@@ -9,36 +9,36 @@
|
||||
(p "SX has a peculiar architecture. At its centre sits a specification — a set of s-expression files that define the language. Not a description of the language. Not documentation about the language. The specification " (em "is") " the language. It is simultaneously a formal definition and executable code. You can read it as a document or run it as a program. It does not describe how to build an SX evaluator; it " (em "is") " an SX evaluator, expressed in the language it defines.")
|
||||
(p "This is the nucleus. Everything else radiates outward from it.")
|
||||
|
||||
(div :class "space-y-3 my-4"
|
||||
(div :class "rounded border border-violet-200 bg-violet-50/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-violet-600 text-white" "1")
|
||||
(span :class "font-semibold text-stone-800" "The Nucleus"))
|
||||
(p :class "text-sm text-stone-600" "The spec itself — " (code "shared/sx/ref/*.sx") ". Fourteen files, 180+ functions with type and effect annotations. Each function is simultaneously a formal definition and executable code."))
|
||||
(div (~tw :tokens "space-y-3 my-4")
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white") "1")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Nucleus"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "The spec itself — " (code "shared/sx/ref/*.sx") ". Fourteen files, 180+ functions with type and effect annotations. Each function is simultaneously a formal definition and executable code."))
|
||||
|
||||
(div :class "rounded border border-sky-200 bg-sky-50/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-sky-600 text-white" "2")
|
||||
(span :class "font-semibold text-stone-800" "The Bootstrapper Ring"))
|
||||
(p :class "text-sm text-stone-600" "Translators that read the spec and emit native implementations. " (code "bootstrap_py.py") " emits Python. " (code "js.sx") " emits JavaScript. " (code "z3.sx") " emits SMT-LIB verification conditions. The spec's knowledge is preserved in every translation. Nothing is added, nothing is lost."))
|
||||
(div (~tw :tokens "rounded border border-sky-200 bg-sky-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white") "2")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Bootstrapper Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Translators that read the spec and emit native implementations. " (code "bootstrap_py.py") " emits Python. " (code "js.sx") " emits JavaScript. " (code "z3.sx") " emits SMT-LIB verification conditions. The spec's knowledge is preserved in every translation. Nothing is added, nothing is lost."))
|
||||
|
||||
(div :class "rounded border border-rose-200 bg-rose-50/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-rose-500 text-white" "3")
|
||||
(span :class "font-semibold text-stone-800" "The Bridge Ring"))
|
||||
(p :class "text-sm text-stone-600" "The platform boundary — " (code "boundary.sx") " is literally the membrane between the made world (the spec) and the found world (the host environment). It declares what the host must provide so the spec can function. Each spec function's dependencies trace back to either other spec functions or platform primitives."))
|
||||
(div (~tw :tokens "rounded border border-rose-200 bg-rose-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-rose-500 text-white") "3")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Bridge Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "The platform boundary — " (code "boundary.sx") " is literally the membrane between the made world (the spec) and the found world (the host environment). It declares what the host must provide so the spec can function. Each spec function's dependencies trace back to either other spec functions or platform primitives."))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50/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-500 text-white" "4")
|
||||
(span :class "font-semibold text-stone-800" "The Runtime Ring"))
|
||||
(p :class "text-sm text-stone-600" "Bootstrapped spec + platform bridge = working system. Tests verify behaviour. " (code "prove.sx") " verifies algebraic properties by bounded model checking. " (code "z3.sx") " translates to SMT-LIB for unbounded proofs. The spec doesn't just claim to work — it proves it."))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-500 text-white") "4")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Runtime Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Bootstrapped spec + platform bridge = working system. Tests verify behaviour. " (code "prove.sx") " verifies algebraic properties by bounded model checking. " (code "z3.sx") " translates to SMT-LIB for unbounded proofs. The spec doesn't just claim to work — it proves it."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50/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" "5")
|
||||
(span :class "font-semibold text-stone-800" "The Application Ring"))
|
||||
(p :class "text-sm text-stone-600" "This website — rendering the spec's source code using the runtime the spec produced, displayed in components written in the language the spec defines, navigated by an engine the spec specifies. The documentation is the thing documenting itself.")))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50/50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white") "5")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "The Application Ring"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "This website — rendering the spec's source code using the runtime the spec produced, displayed in components written in the language the spec defines, navigated by an engine the spec specifies. The documentation is the thing documenting itself.")))
|
||||
|
||||
(p "The spec explorer makes all five rings visible for every function."))
|
||||
|
||||
@@ -49,38 +49,38 @@
|
||||
(~docs/section :title "Per-Function Cards" :id "cards"
|
||||
(p "Each function in the spec gets a card showing all five rings:")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Ring")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Panel")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Content")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Ring")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Panel")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Content")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white" "1"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Nucleus")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX source with syntax highlighting. Effect badges (pure/mutation/io/render). Typed parameter list."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white" "2"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Translations")
|
||||
(td :class "px-3 py-2 text-stone-600" "Collapsible panels showing the same function in Python, JavaScript, and Z3/SMT-LIB. Each generated by the actual bootstrappers — not hand-written."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-rose-500 text-white" "3"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Bridge")
|
||||
(td :class "px-3 py-2 text-stone-600" "Cross-references: which spec functions and platform primitives this function depends on. Platform deps marked with " (code "⬡") "."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-500 text-white" "4"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Runtime")
|
||||
(td :class "px-3 py-2 text-stone-600" "Tests matched from " (code "test-*.sx") " files. Proof status from " (code "prove.sx") " — sat/unknown/n/a. Algebraic properties that reference this function."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2"
|
||||
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white" "5"))
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Examples")
|
||||
(td :class "px-3 py-2 text-stone-600" "Usage examples extracted from comments, test assertions, and curated examples. Living documentation."))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white") "1"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Nucleus")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX source with syntax highlighting. Effect badges (pure/mutation/io/render). Typed parameter list."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white") "2"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Translations")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Collapsible panels showing the same function in Python, JavaScript, and Z3/SMT-LIB. Each generated by the actual bootstrappers — not hand-written."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-rose-500 text-white") "3"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Bridge")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Cross-references: which spec functions and platform primitives this function depends on. Platform deps marked with " (code "⬡") "."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-500 text-white") "4"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Runtime")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Tests matched from " (code "test-*.sx") " files. Proof status from " (code "prove.sx") " — sat/unknown/n/a. Algebraic properties that reference this function."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white") "5"))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Examples")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Usage examples extracted from comments, test assertions, and curated examples. Living documentation."))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Effect system
|
||||
@@ -89,11 +89,11 @@
|
||||
(~docs/section :title "Effect Annotations" :id "effects"
|
||||
(p "Every function in the spec now carries an " (code ":effects") " annotation declaring what kind of side effects it performs:")
|
||||
|
||||
(div :class "flex flex-wrap gap-3 my-4"
|
||||
(span :class "inline-flex items-center gap-1 px-3 py-1 rounded bg-green-100 text-green-700 text-sm font-medium" "pure " (code ":effects []"))
|
||||
(span :class "inline-flex items-center gap-1 px-3 py-1 rounded bg-amber-100 text-amber-700 text-sm font-medium" "mutation " (code ":effects [mutation]"))
|
||||
(span :class "inline-flex items-center gap-1 px-3 py-1 rounded bg-orange-100 text-orange-700 text-sm font-medium" "io " (code ":effects [io]"))
|
||||
(span :class "inline-flex items-center gap-1 px-3 py-1 rounded bg-sky-100 text-sky-700 text-sm font-medium" "render " (code ":effects [render]")))
|
||||
(div (~tw :tokens "flex flex-wrap gap-3 my-4")
|
||||
(span (~tw :tokens "inline-flex items-center gap-1 px-3 py-1 rounded bg-green-100 text-green-700 text-sm font-medium") "pure " (code ":effects []"))
|
||||
(span (~tw :tokens "inline-flex items-center gap-1 px-3 py-1 rounded bg-amber-100 text-amber-700 text-sm font-medium") "mutation " (code ":effects [mutation]"))
|
||||
(span (~tw :tokens "inline-flex items-center gap-1 px-3 py-1 rounded bg-orange-100 text-orange-700 text-sm font-medium") "io " (code ":effects [io]"))
|
||||
(span (~tw :tokens "inline-flex items-center gap-1 px-3 py-1 rounded bg-sky-100 text-sky-700 text-sm font-medium") "render " (code ":effects [render]")))
|
||||
|
||||
(p "The explorer shows effect badges on each function card, and the stats bar aggregates them across the whole file. Pure functions (green) are the nucleus — no side effects, fully deterministic, safe to cache, reorder, or parallelise.")
|
||||
|
||||
@@ -108,15 +108,15 @@
|
||||
|
||||
(~docs/subsection :title "Python (via bootstrap_py.py)"
|
||||
(~docs/code :src (highlight "def signal(initial_value):\n return make_signal(initial_value)" "python"))
|
||||
(p :class "text-sm text-stone-500" (code "PyEmitter._emit_define()") " — the exact same code path that generates " (code "sx_ref.py") "."))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (code "PyEmitter._emit_define()") " — the exact same code path that generates " (code "sx_ref.py") "."))
|
||||
|
||||
(~docs/subsection :title "JavaScript (via js.sx)"
|
||||
(~docs/code :src (highlight "var signal = function(initial_value) {\n return make_signal(initial_value);\n};" "javascript"))
|
||||
(p :class "text-sm text-stone-500" (code "js-emit-define") " — the self-hosting JS bootstrapper, written in SX, evaluated by the Python evaluator."))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (code "js-emit-define") " — the self-hosting JS bootstrapper, written in SX, evaluated by the Python evaluator."))
|
||||
|
||||
(~docs/subsection :title "Z3 / SMT-LIB (via z3.sx)"
|
||||
(~docs/code :src (highlight "; signal — Create a reactive signal container with an initial value.\n(declare-fun signal (Value) Value)" "lisp"))
|
||||
(p :class "text-sm text-stone-500" (code "z3-translate") " — the first self-hosted bootstrapper, translating spec declarations to verification conditions for theorem provers.")))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (code "z3-translate") " — the first self-hosted bootstrapper, translating spec declarations to verification conditions for theorem provers.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Testing and proving
|
||||
@@ -147,8 +147,8 @@
|
||||
|
||||
(~docs/subsection :title "Layer 2: SX components"
|
||||
(p (code "specs-explorer.sx") " — 12-15 " (code "defcomp") " components rendering the structured data:")
|
||||
(div :class "overflow-x-auto"
|
||||
(pre :class "text-xs bg-stone-100 rounded p-3"
|
||||
(div (~tw :tokens "overflow-x-auto")
|
||||
(pre (~tw :tokens "text-xs bg-stone-100 rounded p-3")
|
||||
(code "~specs-explorer/spec-explorer-content top-level, receives parsed data\n ~specs-explorer/spec-explorer-header filename, title, source link\n ~specs-explorer/spec-explorer-stats aggregate badges: effects, tests, proofs\n ~spec-explorer-toc section table of contents\n ~specs-explorer/spec-explorer-section one section with its defines\n ~specs-explorer/spec-explorer-define one function card (all five rings)\n ~specs-explorer/spec-effect-badge colored effect badge\n ~specs-explorer/spec-param-list typed parameter list\n ~specs-explorer/spec-ring-translations SX / Python / JS / Z3 panels\n ~specs-explorer/spec-ring-bridge cross-references + platform deps\n ~specs-explorer/spec-ring-runtime tests + proofs\n ~spec-ring-examples usage examples\n ~specs-explorer/spec-platform-interface platform primitives table"))))
|
||||
|
||||
(~docs/subsection :title "Layer 3: Routing"
|
||||
@@ -159,13 +159,13 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Incremental Delivery" :id "increments"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(div :class "rounded border border-stone-200 p-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-violet-600 text-white uppercase" "Inc 1")
|
||||
(span :class "font-semibold text-stone-800" "Core + Translations (Ring 1-2)"))
|
||||
(ul :class "list-disc pl-5 text-sm text-stone-600 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-violet-600 text-white uppercase") "Inc 1")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Core + Translations (Ring 1-2)"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-sm text-stone-600 space-y-1")
|
||||
(li (code "_spec_explorer_data()") " — sections, defines, effects, params, source extraction")
|
||||
(li "Python translation via " (code "PyEmitter._emit_define()"))
|
||||
(li "JavaScript translation via " (code "js-emit-define") " (from " (code "run_js_sx.py") ")")
|
||||
@@ -173,11 +173,11 @@
|
||||
(li "Explorer components + translation panels + routing")
|
||||
(li "Test with " (code "signals.sx"))))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-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-sky-600 text-white uppercase" "Inc 2")
|
||||
(span :class "font-semibold text-stone-800" "Bridge + Runtime (Ring 3-4)"))
|
||||
(ul :class "list-disc pl-5 text-sm text-stone-600 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-sky-600 text-white uppercase") "Inc 2")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Bridge + Runtime (Ring 3-4)"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-sm text-stone-600 space-y-1")
|
||||
(li "Cross-reference index: function→slug mapping across all spec files")
|
||||
(li "Platform dependency detection (ref not in index = platform primitive)")
|
||||
(li "Test file parsing: " (code "defsuite") "/" (code "deftest") " structure extraction")
|
||||
@@ -185,11 +185,11 @@
|
||||
(li "Proof generation via " (code "prove-translate"))
|
||||
(li "Property matching from " (code "sx-properties"))))
|
||||
|
||||
(div :class "rounded border border-stone-200 p-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" "Inc 3")
|
||||
(span :class "font-semibold text-stone-800" "Examples + Polish (Ring 5)"))
|
||||
(ul :class "list-disc pl-5 text-sm text-stone-600 space-y-1"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-2")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Inc 3")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Examples + Polish (Ring 5)"))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-sm text-stone-600 space-y-1")
|
||||
(li "Example extraction from comments + test assertions")
|
||||
(li "Curated examples for key functions")
|
||||
(li "Stats bar with aggregate counts")
|
||||
@@ -203,7 +203,7 @@
|
||||
|
||||
(~docs/section :title "The Strange Loop" :id "strange-loop"
|
||||
(p "When you view " (code "/language/specs/explore/eval") ", what happens is this:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li "The SX evaluator — bootstrapped from " (code "eval.sx") " — evaluates the page definition.")
|
||||
(li "The page calls " (code "spec-explorer-data(\"eval\")") ", which parses " (code "eval.sx") " using " (code "parse_all()") " — itself bootstrapped from " (code "parser.sx") ".")
|
||||
(li "The parsed AST is fed to " (code "PyEmitter") " and " (code "js-emit-define") " — the same bootstrappers that produced the running evaluator.")
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/status/plan-status-content ()
|
||||
(~docs/page :title "Plan Status"
|
||||
|
||||
(p :class "text-lg text-stone-600 mb-6"
|
||||
(p (~tw :tokens "text-lg text-stone-600 mb-6")
|
||||
"Audit of all plans across the SX language and Rose Ash platform. Last updated March 2026.")
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -14,61 +14,61 @@
|
||||
|
||||
(~docs/section :title "Completed" :id "completed"
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(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")
|
||||
(span :class "font-semibold text-stone-800" "Split Cart into Microservices"))
|
||||
(p :class "text-sm text-stone-600" "Cart decomposed into 4 services: relations (internal, owns ContainerRelation), likes (internal, unified generic likes), orders (public, owns Order/OrderItem + SumUp checkout), and cart (thin CartItem CRUD). All three new services deployed with dedicated databases."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Split Cart into Microservices"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Cart decomposed into 4 services: relations (internal, owns ContainerRelation), likes (internal, unified generic likes), orders (public, owns Order/OrderItem + SumUp checkout), and cart (thin CartItem CRUD). All three new services deployed with dedicated databases."))
|
||||
|
||||
(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")
|
||||
(span :class "font-semibold text-stone-800" "Ticket Purchase Through Cart"))
|
||||
(p :class "text-sm text-stone-600" "Tickets flow through the cart like products: state=pending in cart, reserved at checkout, confirmed on payment. TicketDTO, CartSummaryDTO with ticket_count/ticket_total, CalendarService protocol methods all implemented."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Ticket Purchase Through Cart"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Tickets flow through the cart like products: state=pending in cart, reserved at checkout, confirmed on payment. TicketDTO, CartSummaryDTO with ticket_count/ticket_total, CalendarService protocol methods all implemented."))
|
||||
|
||||
(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")
|
||||
(span :class "font-semibold text-stone-800" "Ticket UX Improvements"))
|
||||
(p :class "text-sm text-stone-600" "+/- quantity buttons on entry pages and cart. Tickets grouped by event in cart display. Adjust quantity route, sold/basket counts, matching product card UX pattern."))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(span (~tw :tokens "font-semibold text-stone-800") "Ticket UX Improvements"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "+/- quantity buttons on entry pages and cart. Tickets grouped by event in cart display. Adjust quantity route, sold/basket counts, matching product card UX pattern."))
|
||||
|
||||
(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 "/sx/(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 (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 1: Dependency Analysis"))
|
||||
(p (~tw :tokens "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 "/sx/(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 (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 2: IO Detection"))
|
||||
(p (~tw :tokens "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 "/sx/(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 (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 3: Client-Side Routing"))
|
||||
(p (~tw :tokens "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 "/sx/(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 (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 4: Client Async & IO Bridge"))
|
||||
(p (~tw :tokens "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 "/sx/(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 (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-green-800 underline") "Isomorphic Phase 5: Client IO Proxy"))
|
||||
(p (~tw :tokens "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 "/sx/(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."))))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(language.(test))" (~tw :tokens "font-semibold text-green-800 underline") "Modular Test Architecture"))
|
||||
(p (~tw :tokens "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."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; In Progress / Partial
|
||||
@@ -76,13 +76,13 @@
|
||||
|
||||
(~docs/section :title "In Progress" :id "in-progress"
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(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 "/sx/(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."))))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase") "Partial")
|
||||
(a :href "/sx/(etc.(plan.fragment-protocol))" (~tw :tokens "font-semibold text-amber-900 underline") "Fragment Protocol"))
|
||||
(p (~tw :tokens "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."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Not Started
|
||||
@@ -90,56 +90,56 @@
|
||||
|
||||
(~docs/section :title "Not Started" :id "not-started"
|
||||
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
|
||||
(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 "/sx/(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 (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-700 text-white uppercase") "Done")
|
||||
(a :href "/sx/(etc.(plan.reader-macros))" (~tw :tokens "font-semibold text-stone-800 underline") "Reader Macros"))
|
||||
(p (~tw :tokens "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 (~tw :tokens "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 "/sx/(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 (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase") "Not Started")
|
||||
(a :href "/sx/(etc.(plan.sx-activity))" (~tw :tokens "font-semibold text-stone-800 underline") "SX-Activity"))
|
||||
(p (~tw :tokens "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 (~tw :tokens "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 "/sx/(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 (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase") "Not Started")
|
||||
(a :href "/sx/(etc.(plan.glue-decoupling))" (~tw :tokens "font-semibold text-stone-800 underline") "Cross-App Decoupling via Glue"))
|
||||
(p (~tw :tokens "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 (~tw :tokens "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 "/sx/(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 (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase") "Not Started")
|
||||
(a :href "/sx/(etc.(plan.social-sharing))" (~tw :tokens "font-semibold text-stone-800 underline") "Social Network Sharing"))
|
||||
(p (~tw :tokens "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 (~tw :tokens "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 "/sx/(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. ~shared:pages/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 "/sx/(geography.(isomorphism.streaming))" "/sx/(geography.(isomorphism.streaming))")))
|
||||
(div (~tw :tokens "rounded border border-green-200 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-stone-800 underline") "Isomorphic Phase 6: Streaming & Suspense"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Server streams partially-evaluated SX as IO resolves. ~shared:pages/suspense component renders fallbacks, inline resolution scripts fill in content. Concurrent IO via asyncio, chunked transfer encoding.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-1") "Demo: " (a :href "/sx/(geography.(isomorphism.streaming))" "/sx/(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 "/sx/(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 (~tw :tokens "rounded border border-green-300 bg-green-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase") "Complete")
|
||||
(a :href "/sx/(geography.(isomorphism))" (~tw :tokens "font-semibold text-stone-800 underline") "Isomorphic Phase 7: Full Isomorphism"))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Affinity annotations, render plans, optimistic data updates, offline mutation queue, isomorphic testing harness, universal page descriptor.")
|
||||
(p (~tw :tokens "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 "/sx/(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."))))))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(div (~tw :tokens "flex items-center gap-2 mb-1")
|
||||
(span (~tw :tokens "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase") "Not Started")
|
||||
(a :href "/sx/(etc.(plan.spec-explorer))" (~tw :tokens "font-semibold text-stone-800 underline") "Spec Explorer — The Fifth Ring"))
|
||||
(p (~tw :tokens "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 (~tw :tokens "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."))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Fragment Protocol
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
(p "SX-Activity wires these together into a new web. Everything — content, UI components, markdown parsers, syntax highlighters, validation logic, media, processing pipelines — is the same executable format, stored on a content-addressed network, running within each participant's own security context. " (strong "The wire format is the programming language is the component system is the package manager.")))
|
||||
|
||||
(~docs/section :title "Current State" :id "current-state"
|
||||
(ul :class "space-y-2 text-stone-700 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-700 list-disc pl-5")
|
||||
(li (strong "ActivityPub: ") "Full implementation — virtual per-app actors, HTTP signatures, webfinger, inbox/outbox, followers/following, delivery with idempotent logging.")
|
||||
(li (strong "Activity bus: ") "Unified event bus with NOTIFY/LISTEN wakeup, at-least-once delivery, handler registry keyed by (activity_type, object_type).")
|
||||
(li (strong "Content addressing: ") "artdag nodes use SHA3-256 hashing. Cache layer tracks IPFS CIDs. IPFSPin model tracks pinned content across domains.")
|
||||
@@ -25,9 +25,9 @@
|
||||
|
||||
(~docs/section :title "Phase 1: SX Wire Format for Activities" :id "phase-1"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Activities expressed as s-expressions instead of JSON-LD. Same semantics as ActivityStreams, but compact, parseable, and directly evaluable. Dual-format support for backward compatibility with existing AP servers."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Activities expressed as s-expressions instead of JSON-LD. Same semantics as ActivityStreams, but compact, parseable, and directly evaluable. Dual-format support for backward compatibility with existing AP servers."))
|
||||
|
||||
(~docs/subsection :title "The Problem"
|
||||
(p "JSON-LD activities are verbose and require context resolution:")
|
||||
@@ -40,26 +40,26 @@
|
||||
(p "The content isn't a string containing markup — it " (em "is") " markup. The receiving server can evaluate it directly. The Note's content is a renderable SX expression."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Activity vocabulary in SX")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Activity vocabulary in SX")
|
||||
(p "Map ActivityStreams types to SX symbols. Activities are lists with a type head and keyword properties:")
|
||||
(~docs/code :src (highlight ";; Core activity types\n(Create :actor ... :object ...)\n(Update :actor ... :object ...)\n(Delete :actor ... :object ...)\n(Follow :actor ... :object ...)\n(Like :actor ... :object ...)\n(Announce :actor ... :object ...)\n\n;; Object types\n(Note :content ... :attributed-to ...)\n(Article :name ... :content ... :summary ...)\n(Image :url ... :media-type ... :cid ...)\n(Collection :total-items ... :items ...)" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Content negotiation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Content negotiation")
|
||||
(p "Inbox accepts both formats. " (code "Accept: text/sx") " gets SX, " (code "Accept: application/activity+json") " gets JSON-LD. Outbox serves both. SX-native servers negotiate SX; legacy Mastodon/Pleroma servers get JSON-LD as today."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Bidirectional translation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Bidirectional translation")
|
||||
(p "Lossless mapping between JSON-LD and SX activity formats. Translate at the boundary — internal processing always uses SX. The existing " (code "APActivity") " model gains an " (code "sx_source") " column storing the canonical SX representation."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. HTTP Signatures over SX")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. HTTP Signatures over SX")
|
||||
(p "Same RSA signature mechanism. Digest header computed over the SX body. Existing keypair infrastructure unchanged."))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Round-trip: SX → JSON-LD → SX produces identical output")
|
||||
(li "Legacy AP servers receive valid JSON-LD (Mastodon can display the post)")
|
||||
(li "SX-native servers receive evaluable SX (client can render directly)")
|
||||
@@ -71,32 +71,32 @@
|
||||
|
||||
(~docs/section :title "Phase 2: Content-Addressed Components on IPFS" :id "phase-2"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Component definitions stored on IPFS, referenced by CID. Any server can publish components. Any browser can fetch them. No central registry — content addressing IS the registry."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Component definitions stored on IPFS, referenced by CID. Any server can publish components. Any browser can fetch them. No central registry — content addressing IS the registry."))
|
||||
|
||||
(~docs/subsection :title "The Insight"
|
||||
(p "SX components are pure functions — they take data and return markup. They can't do IO (boundary enforcement guarantees this). That means they're " (strong "safe to load from any source") ". And if they're content-addressed, the CID " (em "is") " the identity — you don't need to trust the source, you just verify the hash.")
|
||||
(p "Currently, component definitions travel with each page via " (code "<script type=\"text/sx\" data-components>") ". Each server bundles its own. With IPFS, components become shared infrastructure — define once, use everywhere."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Component CID computation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Component CID computation")
|
||||
(p "Each " (code "defcomp") " definition gets a content address:")
|
||||
(~docs/code :src (highlight ";; Component source\n(defcomp ~plans/sx-activity/card (&key title &rest children)\n (div :class \"border rounded p-4\"\n (h2 title) children))\n\n;; CID = SHA3-256 of canonical serialized form\n;; → bafy...abc123\n;; Stored: ipfs://bafy...abc123 → component source" "lisp"))
|
||||
(p "Canonical form: normalize whitespace, sort keyword args alphabetically, strip comments. Same component always produces same CID regardless of formatting."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Component references in activities")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Component references in activities")
|
||||
(p "Activities declare which components they need by CID:")
|
||||
(~docs/code :src (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :requires (list\n \"bafy...card\" ;; ~plans/sx-activity/card component\n \"bafy...avatar\") ;; ~shared:misc/avatar component\n :object (Note\n :content (~plans/sx-activity/card :title \"Hello\"\n (~shared:misc/avatar :src \"ipfs://bafy...photo\")\n (p \"This renders with the card component.\"))))" "lisp"))
|
||||
(p "The receiving browser fetches missing components from IPFS, verifies CIDs, registers them, then renders the content."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. IPFS component resolution")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. IPFS component resolution")
|
||||
(p "Client-side resolution pipeline:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Check localStorage cache (keyed by CID — cache-forever semantics)")
|
||||
(li "Check local IPFS node if running (ipfs cat)")
|
||||
(li "Fetch from IPFS gateway (configurable, default: dweb.link)")
|
||||
@@ -104,12 +104,12 @@
|
||||
(li "Parse, register in component env, render")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Component publication")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Component publication")
|
||||
(p "Server-side: on component registration, compute CID and pin to IPFS. Track in " (code "IPFSPin") " model (already exists). Publish component availability via AP outbox:")
|
||||
(~docs/code :src (highlight "(Create\n :actor \"https://rose-ash.com/apps/market\"\n :object (SxComponent\n :name \"~product-card\"\n :cid \"bafy...productcard\"\n :version \"1.0.0\"\n :deps (list \"bafy...card\" \"bafy...price-tag\")))" "lisp")))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Component pinned to IPFS → fetchable via gateway → CID verifies")
|
||||
(li "Browser renders federated post using IPFS-fetched components")
|
||||
(li "Modified component → different CID → old content still renders with old version")
|
||||
@@ -121,43 +121,43 @@
|
||||
|
||||
(~docs/section :title "Phase 3: Federated Media & Content Store" :id "phase-3"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "All media (images, video, audio, DAG outputs) stored content-addressed on IPFS. Activities reference media by CID. No hotlinking, no broken links, no dependence on the origin server staying online."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "All media (images, video, audio, DAG outputs) stored content-addressed on IPFS. Activities reference media by CID. No hotlinking, no broken links, no dependence on the origin server staying online."))
|
||||
|
||||
(~docs/subsection :title "Current Mechanism"
|
||||
(p "artdag already content-addresses all DAG outputs with SHA3-256 and tracks IPFS CIDs in " (code "IPFSPin") ". But media in the web platform (blog images, product photos, event banners) is stored as regular files on the origin server. Federated posts include " (code "url") " fields pointing to the origin — if the server goes down, the media is gone."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Media CID pipeline")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Media CID pipeline")
|
||||
(p "On upload: hash content → pin to IPFS → store CID in database. Activities reference media by CID alongside URL fallback:")
|
||||
(~docs/code :src (highlight "(Image\n :cid \"bafy...photo123\"\n :url \"https://rose-ash.com/media/photo.jpg\" ;; fallback\n :media-type \"image/jpeg\"\n :width 1200 :height 800)" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. DAG output federation")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. DAG output federation")
|
||||
(p "artdag processing results (rendered video, processed images) already have CIDs. Federate them as activities:")
|
||||
(~docs/code :src (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :object (Artwork\n :name \"Sunset Remix\"\n :cid \"bafy...artwork\"\n :dag-cid \"bafy...dag\" ;; full DAG for reproduction\n :media-type \"video/mp4\"\n :sources (list\n (Image :cid \"bafy...src1\" :attribution \"...\")\n (Image :cid \"bafy...src2\" :attribution \"...\"))))" "lisp"))
|
||||
(p "The " (code ":dag-cid") " lets anyone re-execute the processing pipeline. The artwork is both a result and a reproducible recipe."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Shared SX content store")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Shared SX content store")
|
||||
(p "Not just components and media — full page content can be content-addressed. An Article's body is SX, pinned to IPFS:")
|
||||
(~docs/code :src (highlight "(Article\n :name \"Why S-Expressions\"\n :content-cid \"bafy...article-body\" ;; SX source on IPFS\n :requires (list \"bafy...doc-page\" \"bafy...code-block\")\n :summary \"Why SX uses s-expressions instead of HTML.\")" "lisp"))
|
||||
(p "The content outlives the server. Anyone with the CID can fetch, parse, and render the article with its original components."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Progressive resolution")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Progressive resolution")
|
||||
(p "Client resolves content progressively:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Inline content renders immediately")
|
||||
(li "CID-referenced content shows placeholder → fetches from IPFS → renders")
|
||||
(li "Large media uses IPFS streaming (chunked CIDs)")
|
||||
(li "Integrates with Phase 6 of isomorphic plan (streaming/suspense)")))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Origin server offline → content still resolvable via IPFS gateway")
|
||||
(li "DAG CID → re-executing DAG produces identical output")
|
||||
(li "Media CID verifies → tampered content rejected"))))
|
||||
@@ -168,39 +168,39 @@
|
||||
|
||||
(~docs/section :title "Phase 4: Component Registry & Discovery" :id "phase-4"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Federated component discovery. Servers publish component collections. Other servers follow component feeds. Like npm, but federated, content-addressed, and the packages are safe to run (pure functions, no IO)."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Federated component discovery. Servers publish component collections. Other servers follow component feeds. Like npm, but federated, content-addressed, and the packages are safe to run (pure functions, no IO)."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Component collections as AP actors")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Component collections as AP actors")
|
||||
(p "Each server exposes a component registry actor:")
|
||||
(~docs/code :src (highlight "(Service\n :id \"https://rose-ash.com/sx-registry\"\n :type \"SxComponentRegistry\"\n :name \"Rose Ash Components\"\n :outbox \"https://rose-ash.com/sx-registry/outbox\"\n :followers \"https://rose-ash.com/sx-registry/followers\")" "lisp"))
|
||||
(p "Follow the registry to receive component updates. The outbox is a chronological feed of Create/Update/Delete activities for components."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Component metadata")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Component metadata")
|
||||
(~docs/code :src (highlight "(SxComponent\n :name \"~data-table\"\n :cid \"bafy...datatable\"\n :version \"2.1.0\"\n :deps (list \"bafy...sortable\" \"bafy...paginator\")\n :params (list\n (dict :name \"rows\" :type \"list\" :required true)\n (dict :name \"columns\" :type \"list\" :required true)\n (dict :name \"sortable\" :type \"boolean\" :default false))\n :css-atoms (list :border :rounded :p-4 :text-sm)\n :preview-cid \"bafy...screenshot\"\n :license \"MIT\")" "lisp"))
|
||||
(p "Dependencies are transitive CID references. CSS atoms declare which CSSX rules the component needs. Preview CID is a screenshot for registry browsing."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Discovery protocol")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Discovery protocol")
|
||||
(p "Webfinger-style lookup for components by name:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Local registry: check own component env first")
|
||||
(li "Followed registries: check cached feeds from followed registries")
|
||||
(li "Global search: query known registries by component name")
|
||||
(li "CID resolution: if you have a CID, skip discovery — fetch directly from IPFS")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Version resolution")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Version resolution")
|
||||
(p "Components are immutable (CID = identity). \"Updating\" a component publishes a new CID. Activities reference specific CIDs, so old content always renders correctly. The registry tracks version history:")
|
||||
(~docs/code :src (highlight "(Update\n :actor \"https://rose-ash.com/sx-registry\"\n :object (SxComponent\n :name \"~card\"\n :cid \"bafy...card-v2\" ;; new version\n :replaces \"bafy...card-v1\" ;; previous version\n :changelog \"Added subtitle slot\"))" "lisp")))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Follow registry → receive component Create activities → components available locally")
|
||||
(li "Render post using component from foreign registry → works")
|
||||
(li "Old post referencing old CID → still renders correctly with old version"))))
|
||||
@@ -211,19 +211,19 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Bitcoin-Anchored Provenance" :id "phase-5"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Cryptographic proof that content existed at a specific time, authored by a specific actor. Leverages the existing APAnchor/OpenTimestamps infrastructure. Unforgeable, independently verifiable, survives server shutdown."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What it enables")
|
||||
(p (~tw :tokens "text-violet-800") "Cryptographic proof that content existed at a specific time, authored by a specific actor. Leverages the existing APAnchor/OpenTimestamps infrastructure. Unforgeable, independently verifiable, survives server shutdown."))
|
||||
|
||||
(~docs/subsection :title "Current Mechanism"
|
||||
(p "The " (code "APAnchor") " model already batches activities into Merkle trees, stores the tree on IPFS, creates an OpenTimestamps proof, and records the Bitcoin txid. This runs but isn't surfaced to users or integrated with the full activity lifecycle."))
|
||||
|
||||
(~docs/subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Automatic anchoring pipeline")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "1. Automatic anchoring pipeline")
|
||||
(p "Every SX activity gets queued for anchoring. Batch processor runs periodically:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Collect pending activities (content CIDs + actor signatures)")
|
||||
(li "Build Merkle tree of activity hashes")
|
||||
(li "Pin Merkle tree to IPFS → tree CID")
|
||||
@@ -231,25 +231,25 @@
|
||||
(li "When Bitcoin confirmation arrives: store txid, update activities with anchor reference")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. Provenance chain in activities")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "2. Provenance chain in activities")
|
||||
(~docs/code :src (highlight "(Create\n :actor \"https://rose-ash.com/users/alice\"\n :object (Note :content (p \"Hello\") :cid \"bafy...note\")\n :provenance (Anchor\n :tree-cid \"bafy...merkle-tree\"\n :leaf-index 42\n :ots-cid \"bafy...ots-proof\"\n :btc-txid \"abc123...def\"\n :btc-block 890123\n :anchored-at \"2026-03-06T12:00:00Z\"))" "lisp"))
|
||||
(p "Any party can verify: fetch the OTS proof from IPFS, check the Merkle path from the activity's CID to the tree root, confirm the tree root is committed in the Bitcoin block."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Component provenance")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "3. Component provenance")
|
||||
(p "Components published to the registry also get anchored. This proves authorship and publication time:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "\"This component was published by rose-ash.com at block 890123\"")
|
||||
(li "Prevents backdating — can't claim you published a component before you actually did")
|
||||
(li "License disputes resolvable by checking anchor timestamps")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Verification UI")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "4. Verification UI")
|
||||
(p "Client-side provenance badge on federated content:")
|
||||
(~docs/code :src (highlight "(defcomp ~plans/sx-activity/provenance-badge (&key anchor)\n (when anchor\n (details :class \"inline text-xs text-stone-400\"\n (summary \"✓ Anchored\")\n (dl :class \"mt-1 space-y-1\"\n (dt \"Bitcoin block\") (dd (get anchor \"btc-block\"))\n (dt \"Timestamp\") (dd (get anchor \"anchored-at\"))\n (dt \"Proof\") (dd (a :href (str \"ipfs://\" (get anchor \"ots-cid\"))\n \"OTS proof\"))))))" "lisp")))))
|
||||
|
||||
(~docs/subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Activity anchored → OTS proof fetchable from IPFS → Merkle path validates → txid confirms in Bitcoin")
|
||||
(li "Tampered activity → Merkle proof fails → provenance badge shows ✗")
|
||||
(li "Server goes offline → provenance still verifiable (all proofs on IPFS + Bitcoin)"))))
|
||||
@@ -260,9 +260,9 @@
|
||||
|
||||
(~docs/section :title "Phase 6: The Evaluable Web" :id "phase-6"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What this really is")
|
||||
(p :class "text-violet-800" "Not ActivityPub-with-SX. A new web. One where everything — content, components, parsers, renderers, server logic, client logic — is the same executable format, shared on a content-addressed network, running within each participant's own security context."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mb-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "What this really is")
|
||||
(p (~tw :tokens "text-violet-800") "Not ActivityPub-with-SX. A new web. One where everything — content, components, parsers, renderers, server logic, client logic — is the same executable format, shared on a content-addressed network, running within each participant's own security context."))
|
||||
|
||||
(~docs/subsection :title "The insight"
|
||||
(p "The web has six layers that don't talk to each other: HTML (structure), CSS (style), JavaScript (behavior), JSON (data interchange), server frameworks (backend logic), and build tools (compilation). Each has its own syntax, its own semantics, its own ecosystem. Moving data between them requires serialization, deserialization, template languages, API contracts, type coercion, and an endless parade of glue code.")
|
||||
@@ -270,31 +270,31 @@
|
||||
(p "Once that's true, " (strong "everything becomes shareable.") " Not just UI components — markdown parsers, syntax highlighters, date formatters, validation logic, layout algorithms, color systems, animation curves. Any pure function over data. All content-addressed, all on IPFS, all executable within your own security context."))
|
||||
|
||||
(~docs/subsection :title "What travels on the network"
|
||||
(div :class "space-y-4"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Content")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Content")
|
||||
(p "Blog posts, product listings, event descriptions, social media posts. Not HTML strings embedded in JSON — live SX expressions that evaluate to rendered UI. The content " (em "is") " the application."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Components")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Components")
|
||||
(p "UI building blocks: cards, tables, forms, navigation, media players. Published to IPFS, referenced by CID. A commerce site publishes " (code "~product-card") ". A blogging platform publishes " (code "~article-layout") ". A social network publishes " (code "~thread-view") ". Anyone can compose them. They're pure functions — safe to load from anywhere."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Parsers and transforms")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Parsers and transforms")
|
||||
(p "A markdown parser is just an SX function: takes a string, returns an SX tree. Publish it to IPFS. Now anyone can use your markdown dialect. Same for: syntax highlighters, BBCode parsers, wiki markup, LaTeX subsets, CSV-to-table converters, JSON-to-SX adapters. " (strong "The parser ecosystem becomes shared infrastructure."))
|
||||
(~docs/code :src (highlight ";; A markdown parser, published to IPFS\n;; CID: bafy...md-parser\n(define parse-markdown\n (fn (source)\n ;; tokenize → build AST → return SX tree\n ;; (parse-markdown \"# Hello\\n**bold**\")\n ;; → (h1 \"Hello\") (p (strong \"bold\"))\n ...))\n\n;; Anyone can use it in their components\n(defcomp ~plans/sx-activity/blog-post (&key markdown-source)\n (div :class \"prose\"\n (parse-markdown markdown-source)))" "lisp")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Server-side and client-side logic")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Server-side and client-side logic")
|
||||
(p "The same SX code runs on either side. A validation function published to IPFS runs server-side in Python for form processing and client-side in JavaScript for instant feedback. A price calculator runs server-side for order totals and client-side for live previews. " (em "The server/client split is a deployment decision, not a language boundary.")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "Media and processing pipelines")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700") "Media and processing pipelines")
|
||||
(p "Images, video, audio — all content-addressed on IPFS. But also the " (em "processing pipelines") " that created them. artdag DAGs are SX. Publish a DAG CID alongside the output CID and anyone can verify the provenance, re-render at different resolution, or fork the pipeline for their own work."))))
|
||||
|
||||
(~docs/subsection :title "The security model"
|
||||
(p "This only works because of boundary enforcement. Every piece of SX fetched from the network runs within the receiver's security context:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Pure functions can't do IO. ") "A component from IPFS can produce markup — it cannot read your cookies, make network requests, access localStorage, or call any IO primitive. The boundary spec (boundary.sx) is enforced at registration time. This isn't a policy — it's structural. The evaluator literally doesn't have IO primitives available when running untrusted code.")
|
||||
(li (strong "IO requires explicit grant. ") "Only locally-registered IO primitives (query, frag, current-user) have access to server resources. Fetched components never see them. The host decides what capabilities to grant.")
|
||||
(li (strong "Step limits cap computation. ") "Untrusted code runs with configurable eval step limits. No infinite loops, no resource exhaustion. Exceeding the limit halts evaluation and returns an error node.")
|
||||
@@ -303,56 +303,56 @@
|
||||
(p "This is the opposite of the npm model. npm packages run with full access to your system — a malicious package can exfiltrate secrets, install backdoors, modify the filesystem. SX components are structurally sandboxed. The worst a malicious component can do is render a " (code "(div \"haha got you\")") "."))
|
||||
|
||||
(~docs/subsection :title "What this replaces"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Current web")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SX web")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Current web")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "SX web")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "npm / package registries")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS + component CIDs")
|
||||
(td :class "px-3 py-2 text-stone-600" "Content-addressed, no central authority, structurally safe"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "CDNs for JS/CSS")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS gateways")
|
||||
(td :class "px-3 py-2 text-stone-600" "Permanent, decentralized, self-verifying"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "REST/GraphQL APIs")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX activities over AP")
|
||||
(td :class "px-3 py-2 text-stone-600" "Responses are evaluable, not just data"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "HTML + CSS + JS")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX (one format)")
|
||||
(td :class "px-3 py-2 text-stone-600" "No impedance mismatch, same evaluator everywhere"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Build tools (webpack, vite)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Nothing")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX evaluates directly, no compilation step"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Template languages")
|
||||
(td :class "px-3 py-2 text-stone-700" "Nothing")
|
||||
(td :class "px-3 py-2 text-stone-600" "SX is the template and the language"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "JSON-LD federation")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX federation")
|
||||
(td :class "px-3 py-2 text-stone-600" "Wire format is executable, content renders itself"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Trust-based package security")
|
||||
(td :class "px-3 py-2 text-stone-700" "Structural sandboxing")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure functions can't have side effects — not by policy, by construction"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Servers + hosting + DNS + TLS")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFS CID")
|
||||
(td :class "px-3 py-2 text-stone-600" "Entire applications are content-addressed, no infrastructure needed"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "npm / package registries")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS + component CIDs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Content-addressed, no central authority, structurally safe"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CDNs for JS/CSS")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS gateways")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Permanent, decentralized, self-verifying"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "REST/GraphQL APIs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX activities over AP")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Responses are evaluable, not just data"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "HTML + CSS + JS")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX (one format)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "No impedance mismatch, same evaluator everywhere"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Build tools (webpack, vite)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Nothing")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX evaluates directly, no compilation step"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Template languages")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Nothing")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "SX is the template and the language"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JSON-LD federation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX federation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Wire format is executable, content renders itself"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Trust-based package security")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Structural sandboxing")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure functions can't have side effects — not by policy, by construction"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Servers + hosting + DNS + TLS")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFS CID")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Entire applications are content-addressed, no infrastructure needed"))))))
|
||||
|
||||
(~docs/subsection :title "Serverless applications on IPFS"
|
||||
(p "The logical conclusion: " (strong "entire web applications hosted on IPFS with no server at all."))
|
||||
(p "An SX application is a tree of content-addressed artifacts: a root page definition, component dependencies, media, stylesheets, parsers, transforms. Pin the root CID to IPFS and the application is live. No server, no DNS, no hosting provider, no deployment pipeline. Someone gives you a CID, you paste it into an SX-aware browser, and the application runs.")
|
||||
(~docs/code :src (highlight ";; An entire blog — one CID\n;; ipfs://bafy...my-blog\n(defpage blog-home\n :path \"/\"\n :requires (list\n \"bafy...article-layout\" ;; layout component\n \"bafy...md-parser\" ;; markdown parser\n \"bafy...syntax-highlight\" ;; code highlighting\n \"bafy...nav-component\") ;; navigation\n :content\n (~article-layout\n :title \"My Blog\"\n :nav (~nav-component\n :items (list\n (dict :label \"Post 1\" :cid \"bafy...post-1\")\n (dict :label \"Post 2\" :cid \"bafy...post-2\")))\n :body (~markdown-page\n :source-cid \"bafy...homepage-md\")))" "lisp"))
|
||||
(p "What this looks like in practice:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Personal sites: ") "A portfolio or blog is a handful of SX files + media. Pin to IPFS. Share the CID. No hosting costs, no domain renewal, no SSL certificates. The site is permanent.")
|
||||
(li (strong "Documentation: ") "Pin your docs. They can't go offline, can't be censored, can't be altered after publication (provenance proves it). Anyone can mirror them by pinning the same CID.")
|
||||
(li (strong "Collaborative applications: ") "Multiple authors contribute pages and components. Each publishes their CIDs. A root manifest composes them. Update the manifest CID to add content — old CIDs remain valid forever.")
|
||||
@@ -363,7 +363,7 @@
|
||||
|
||||
(~docs/subsection :title "The end state"
|
||||
(p "A browser with an SX evaluator and an IPFS gateway is a complete web platform. Given a CID — for a page, a post, an application — it can:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Fetch the content from IPFS")
|
||||
(li "Resolve component dependencies (also from IPFS)")
|
||||
(li "Resolve media (also from IPFS)")
|
||||
@@ -381,21 +381,21 @@
|
||||
(~docs/section :title "Cross-Cutting Concerns" :id "cross-cutting"
|
||||
|
||||
(~docs/subsection :title "Security"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "Boundary enforcement is the foundation. ") "IPFS-fetched components are parsed and registered like any other component. SX_BOUNDARY_STRICT ensures they can't call IO primitives. A malicious component can produce ugly markup but can't exfiltrate data or make network requests.")
|
||||
(li (strong "CID verification: ") "Content fetched from IPFS is hashed and compared to the expected CID before use. Tampered content is rejected.")
|
||||
(li (strong "Signature chain: ") "Actor signatures (RSA/HTTP Signatures) prove authorship. Bitcoin anchors prove timing. Together they establish non-repudiable provenance.")
|
||||
(li (strong "Resource limits: ") "Evaluation of untrusted components runs with step limits (max eval steps, max recursion depth). Infinite loops are caught and terminated.")))
|
||||
|
||||
(~docs/subsection :title "Backward Compatibility"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Content negotiation ensures legacy AP servers always receive valid JSON-LD")
|
||||
(li "SX-Activity is strictly opt-in — servers that don't understand it get standard AP")
|
||||
(li "Existing internal activity bus unchanged — SX format is for federation, not internal events")
|
||||
(li "URL fallbacks on all media references — CID is preferred, URL is fallback")))
|
||||
|
||||
(~docs/subsection :title "Performance"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Component CIDs cached in localStorage forever (content-addressed = immutable)")
|
||||
(li "IPFS gateway responses cached with long TTL (content can't change)")
|
||||
(li "Local IPFS node (if present) eliminates gateway latency")
|
||||
@@ -403,7 +403,7 @@
|
||||
|
||||
(~docs/subsection :title "Integration with Isomorphic Architecture"
|
||||
(p "SX-Activity builds on the isomorphic architecture plan:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Phase 1 (component distribution) → IPFS replaces per-server bundles")
|
||||
(li "Phase 2 (IO detection) → pure components safe for IPFS publication")
|
||||
(li "Phase 3 (client routing) → client can resolve federated content without server")
|
||||
@@ -414,57 +414,57 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Critical Files" :id "critical-files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Role")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phases")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phases")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/infrastructure/activitypub.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "AP blueprint — add SX content negotiation")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/events/bus.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Activity bus — add sx_source column, SX serialization")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/activity.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX ↔ JSON-LD bidirectional translation (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ipfs.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Component CID computation, IPFS pinning (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/ipfs-resolve.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client-side IPFS resolution spec (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/models/federation.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "IPFSPin, APAnchor models — extend for components")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3, 5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/registry.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Component registry actor, discovery protocol (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "4"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/anchor.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Anchoring pipeline — wire to activity lifecycle (new)")
|
||||
(td :class "px-3 py-2 text-stone-600" "5"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boot.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Client boot — IPFS component loading")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 6"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/events/handlers/ap_delivery_handler.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Federation delivery — SX format support")
|
||||
(td :class "px-3 py-2 text-stone-600" "1"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "artdag/core/artdag/cache.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Content addressing — shared with component CIDs")
|
||||
(td :class "px-3 py-2 text-stone-600" "2, 3"))))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/infrastructure/activitypub.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "AP blueprint — add SX content negotiation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/events/bus.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Activity bus — add sx_source column, SX serialization")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/activity.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX ↔ JSON-LD bidirectional translation (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ipfs.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component CID computation, IPFS pinning (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/ipfs-resolve.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client-side IPFS resolution spec (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/models/federation.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IPFSPin, APAnchor models — extend for components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3, 5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/registry.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component registry actor, discovery protocol (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "4"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/anchor.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Anchoring pipeline — wire to activity lifecycle (new)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "5"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boot.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client boot — IPFS component loading")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 6"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/events/handlers/ap_delivery_handler.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Federation delivery — SX format support")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "1"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "artdag/core/artdag/cache.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Content addressing — shared with component CIDs")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "2, 3"))))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Content-Addressed Components
|
||||
|
||||
@@ -5,127 +5,127 @@
|
||||
(defcomp ~plans/sx-ci/plan-sx-ci-content ()
|
||||
(~docs/page :title "SX CI Pipeline"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Build, test, and deploy Rose Ash using the same language the application is written in.")
|
||||
|
||||
(~docs/section :title "Context" :id "context"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Rose Ash currently uses shell scripts for CI: " (code "deploy.sh") " auto-detects changed services via git diff, builds Docker images, pushes to the registry, and restarts Swarm services. " (code "dev.sh") " starts the dev environment and runs tests. These work, but they are opaque imperative scripts with no reuse, no composition, and no relationship to SX.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The CI pipeline is the last piece of infrastructure not expressed in s-expressions. Fixing that completes the \"one representation for everything\" claim — the same language that defines the spec, the components, the pages, the essays, and the deployment config also defines the build pipeline."))
|
||||
|
||||
(~docs/section :title "Design" :id "design"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Pipeline definitions are " (code ".sx") " files. A minimal Python CLI runner evaluates them using " (code "sx_ref.py") ". CI-specific IO primitives (shell execution, Docker, git) are boundary-declared and only available to the pipeline runner — never to web components.")
|
||||
(~docs/code :src (highlight ";; pipeline/deploy.sx\n(let ((targets (if (= (length ARGS) 0)\n (~plans/sx-ci/detect-changed :base \"HEAD~1\")\n (filter (fn (svc) (some (fn (a) (= a (get svc \"name\"))) ARGS))\n services))))\n (when (= (length targets) 0)\n (log-step \"No changes detected\")\n (exit 0))\n\n (log-step (str \"Deploying: \" (join \" \" (map (fn (s) (get s \"name\")) targets))))\n\n ;; Tests first\n (~unit-tests)\n (~sx-spec-tests)\n\n ;; Build, push, restart\n (for-each (fn (svc) (~plans/sx-ci/build-service :service svc)) targets)\n (for-each (fn (svc) (~restart-service :service svc)) targets)\n\n (log-step \"Deploy complete\"))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Pipeline steps are components. " (code "~unit-tests") ", " (code "~plans/sx-ci/build-service") ", " (code "~plans/sx-ci/detect-changed") " are " (code "defcomp") " definitions that compose by nesting — the same mechanism used for page layouts, navigation, and every other piece of the system."))
|
||||
|
||||
(~docs/section :title "CI Primitives" :id "primitives"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"New IO primitives declared in " (code "boundary.sx") ", implemented only in the CI runner context:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Primitive")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Signature")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Primitive")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Signature")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shell-run")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(command) -> dict")
|
||||
(td :class "px-3 py-2 text-stone-700" "Execute shell command, return " (code "{:exit N :stdout \"...\" :stderr \"...\"}") ""))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shell-run!")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(command) -> dict")
|
||||
(td :class "px-3 py-2 text-stone-700" "Execute shell command, throw on non-zero exit"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-build")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(&key file tag context) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Build Docker image"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-push")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(tag) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Push image to registry"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "docker-restart")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(service) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Restart Swarm service"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "git-diff-files")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(base head) -> list")
|
||||
(td :class "px-3 py-2 text-stone-700" "List changed files between commits"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "git-branch")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "() -> string")
|
||||
(td :class "px-3 py-2 text-stone-700" "Current branch name"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "log-step")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(message) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Formatted pipeline output"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "fail!")
|
||||
(td :class "px-3 py-2 font-mono text-xs text-stone-600" "(message) -> nil")
|
||||
(td :class "px-3 py-2 text-stone-700" "Abort pipeline with error")))))
|
||||
(p :class "text-stone-600"
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shell-run")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(command) -> dict")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Execute shell command, return " (code "{:exit N :stdout \"...\" :stderr \"...\"}") ""))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shell-run!")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(command) -> dict")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Execute shell command, throw on non-zero exit"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "docker-build")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(&key file tag context) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Build Docker image"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "docker-push")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(tag) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Push image to registry"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "docker-restart")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(service) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Restart Swarm service"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "git-diff-files")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(base head) -> list")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "List changed files between commits"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "git-branch")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "() -> string")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Current branch name"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "log-step")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(message) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Formatted pipeline output"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "fail!")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-600") "(message) -> nil")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Abort pipeline with error")))))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The boundary system ensures these primitives are " (em "only") " available in the CI context. Web components cannot call " (code "shell-run!") " — the evaluator will refuse to resolve the symbol, just as it refuses to resolve any other unregistered IO primitive. The sandbox is structural, not a convention."))
|
||||
|
||||
(~docs/section :title "Reusable Steps" :id "steps"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Pipeline steps are components — same " (code "defcomp") " as UI components, same " (code "&key") " params, same composition by nesting:")
|
||||
(~docs/code :src (highlight "(defcomp ~plans/sx-ci/detect-changed (&key base)\n (let ((files (git-diff-files (or base \"HEAD~1\") \"HEAD\")))\n (if (some (fn (f) (starts-with? f \"shared/\")) files)\n services\n (filter (fn (svc)\n (some (fn (f) (starts-with? f (str (get svc \"dir\") \"/\"))) files))\n services))))\n\n(defcomp ~plans/sx-ci/build-service (&key service)\n (let ((name (get service \"name\"))\n (tag (str registry \"/\" name \":latest\")))\n (log-step (str \"Building \" name))\n (docker-build :file (str (get service \"dir\") \"/Dockerfile\") :tag tag :context \".\")\n (docker-push tag)))\n\n(defcomp ~plans/sx-ci/bootstrap-check ()\n (log-step \"Checking bootstrapped files are up to date\")\n (shell-run! \"python shared/sx/ref/bootstrap_js.py\")\n (shell-run! \"python shared/sx/ref/bootstrap_py.py\")\n (let ((diff (shell-run \"git diff --name-only shared/static/scripts/sx-ref.js shared/sx/ref/sx_ref.py\")))\n (when (not (= (get diff \"stdout\") \"\"))\n (fail! \"Bootstrapped files are stale — rebootstrap and commit\"))))" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
"Compare this to GitHub Actions YAML, where \"reuse\" means composite actions with " (code "uses:") " references, input/output mappings, shell script blocks inside YAML strings, and a " (a :href "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions" :class "text-violet-600 hover:underline" "100-page syntax reference") ". SX pipeline reuse is function composition. That is all it has ever been."))
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Compare this to GitHub Actions YAML, where \"reuse\" means composite actions with " (code "uses:") " references, input/output mappings, shell script blocks inside YAML strings, and a " (a :href "https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions" (~tw :tokens "text-violet-600 hover:underline") "100-page syntax reference") ". SX pipeline reuse is function composition. That is all it has ever been."))
|
||||
|
||||
(~docs/section :title "Pipelines" :id "pipelines"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Two primary pipelines, each a single " (code ".sx") " file:")
|
||||
(div :class "space-y-4"
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "pipeline/test.sx")
|
||||
(p :class "text-sm text-stone-600" "Unit tests, SX spec tests (Python + Node), bootstrap staleness check, Tailwind CSS check. Run locally or in CI.")
|
||||
(p :class "text-sm font-mono text-violet-700 mt-1" "python -m shared.sx.ci pipeline/test.sx"))
|
||||
(div :class "rounded border border-stone-200 p-4"
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "pipeline/deploy.sx")
|
||||
(p :class "text-sm text-stone-600" "Auto-detect changed services (or accept explicit args), run tests, build Docker images, push to registry, restart Swarm services.")
|
||||
(p :class "text-sm font-mono text-violet-700 mt-1" "python -m shared.sx.ci pipeline/deploy.sx blog market"))))
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "pipeline/test.sx")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Unit tests, SX spec tests (Python + Node), bootstrap staleness check, Tailwind CSS check. Run locally or in CI.")
|
||||
(p (~tw :tokens "text-sm font-mono text-violet-700 mt-1") "python -m shared.sx.ci pipeline/test.sx"))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "pipeline/deploy.sx")
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Auto-detect changed services (or accept explicit args), run tests, build Docker images, push to registry, restart Swarm services.")
|
||||
(p (~tw :tokens "text-sm font-mono text-violet-700 mt-1") "python -m shared.sx.ci pipeline/deploy.sx blog market"))))
|
||||
|
||||
(~docs/section :title "Why this matters" :id "why"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"CI pipelines are the strongest test case for \"one representation for everything.\" GitHub Actions, GitLab CI, CircleCI — all use YAML. YAML is not a programming language. So every CI system reinvents conditionals (" (code "if:") " expressions evaluated as strings), iteration (" (code "matrix:") " strategies), composition (" (code "uses:") " references with input/output schemas), and error handling (" (code "continue-on-error:") " booleans) — all in a data format that was never designed for any of it.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The result is a domain-specific language trapped inside YAML, with worse syntax than any language designed to be one. Every CI pipeline of sufficient complexity becomes a programming task performed in a notation that actively resists programming.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"SX pipelines use real conditionals, real functions, real composition, and real error handling — because SX is a real language. The pipeline definition and the application code are the same thing. An AI that can generate SX components can generate SX pipelines. A developer who reads SX pages can read SX deploys. The representation is universal."))
|
||||
|
||||
(~docs/section :title "Files" :id "files"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "File")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ci.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "Pipeline runner CLI (~150 lines)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ci_primitives.py")
|
||||
(td :class "px-3 py-2 text-stone-700" "CI IO primitive implementations"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/services.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Service registry (data)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/steps.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Reusable step components"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/test.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Test pipeline"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "pipeline/deploy.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Deploy pipeline"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "shared/sx/ref/boundary.sx")
|
||||
(td :class "px-3 py-2 text-stone-700" "Add CI primitive declarations"))))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ci.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pipeline runner CLI (~150 lines)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ci_primitives.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CI IO primitive implementations"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pipeline/services.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Service registry (data)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pipeline/steps.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Reusable step components"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pipeline/test.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Test pipeline"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pipeline/deploy.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Deploy pipeline"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "shared/sx/ref/boundary.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Add CI primitive declarations"))))))))
|
||||
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"Markdown rendering, and a template engine that's separate from the application logic. "
|
||||
"Every layer speaks a different language.")
|
||||
(p "sx-forge collapses these layers:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "Repository browsing = SX components rendering git tree objects")
|
||||
(li "Issue tracking = SX forms with sx-post, stored as content-addressed SX documents")
|
||||
(li "Pull requests = SX diff viewer + sx-activity for review comments")
|
||||
@@ -29,42 +29,42 @@
|
||||
(li "API = SX wire format (text/sx) alongside JSON for compatibility")))
|
||||
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(div :class "overflow-x-auto mt-4"
|
||||
(table :class "w-full text-sm text-left"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "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" "Layer")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Implementation")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Notes")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Git backend")
|
||||
(td :class "py-2 px-3" "libgit2 or shell-out to git")
|
||||
(td :class "py-2 px-3" "Smart HTTP + SSH protocols. Bare repos on disk."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "UI")
|
||||
(td :class "py-2 px-3" "SX components (defcomp)")
|
||||
(td :class "py-2 px-3" "Tree browser, diff viewer, blame, commit log — all defcomps."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Issues / PRs")
|
||||
(td :class "py-2 px-3" "SX documents on IPFS")
|
||||
(td :class "py-2 px-3" "Content-addressed. Federated via sx-activity."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "CI")
|
||||
(td :class "py-2 px-3" "sx-ci pipelines")
|
||||
(td :class "py-2 px-3" "Push hook triggers pipeline. Results as SX components."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Auth")
|
||||
(td :class "py-2 px-3" "OAuth2 + SX policy macros")
|
||||
(td :class "py-2 px-3" "Permissions are macro-expanded predicates."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Config")
|
||||
(td :class "py-2 px-3" "SX s-expressions")
|
||||
(td :class "py-2 px-3" "forge.sx per-instance. repo.sx per-repo."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Federation")
|
||||
(td :class "py-2 px-3" "sx-activity (ActivityPub)")
|
||||
(td :class "py-2 px-3" "Cross-instance PRs, issues, stars, forks."))))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Layer")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Implementation")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Notes")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Git backend")
|
||||
(td (~tw :tokens "py-2 px-3") "libgit2 or shell-out to git")
|
||||
(td (~tw :tokens "py-2 px-3") "Smart HTTP + SSH protocols. Bare repos on disk."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "UI")
|
||||
(td (~tw :tokens "py-2 px-3") "SX components (defcomp)")
|
||||
(td (~tw :tokens "py-2 px-3") "Tree browser, diff viewer, blame, commit log — all defcomps."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Issues / PRs")
|
||||
(td (~tw :tokens "py-2 px-3") "SX documents on IPFS")
|
||||
(td (~tw :tokens "py-2 px-3") "Content-addressed. Federated via sx-activity."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "CI")
|
||||
(td (~tw :tokens "py-2 px-3") "sx-ci pipelines")
|
||||
(td (~tw :tokens "py-2 px-3") "Push hook triggers pipeline. Results as SX components."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Auth")
|
||||
(td (~tw :tokens "py-2 px-3") "OAuth2 + SX policy macros")
|
||||
(td (~tw :tokens "py-2 px-3") "Permissions are macro-expanded predicates."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Config")
|
||||
(td (~tw :tokens "py-2 px-3") "SX s-expressions")
|
||||
(td (~tw :tokens "py-2 px-3") "forge.sx per-instance. repo.sx per-repo."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Federation")
|
||||
(td (~tw :tokens "py-2 px-3") "sx-activity (ActivityPub)")
|
||||
(td (~tw :tokens "py-2 px-3") "Cross-instance PRs, issues, stars, forks."))))))
|
||||
|
||||
(~docs/section :title "Configuration as SX" :id "config"
|
||||
(p "Instance configuration is an SX file, not YAML or INI:")
|
||||
@@ -112,7 +112,7 @@
|
||||
:lines (get hunk \"lines\")))
|
||||
(get diff \"hunks\")))" "lisp")
|
||||
(p "Because diffs are SX data, macros can transform them:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "Syntax highlighting via the same " (code "highlight") " helper used everywhere")
|
||||
(li "Inline review comments as SX forms (sx-post to comment endpoint)")
|
||||
(li "Suggestion blocks — click to apply a proposed change")
|
||||
@@ -120,7 +120,7 @@
|
||||
|
||||
(~docs/section :title "Federated Forge" :id "federation"
|
||||
(p "sx-activity enables cross-instance collaboration:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Cross-instance PRs") " — open a PR from your fork on another instance")
|
||||
(li (strong "Federated issues") " — file an issue on a remote repo from your instance")
|
||||
(li (strong "Stars and forks") " — ActivityPub Follow/Like activities")
|
||||
@@ -143,7 +143,7 @@
|
||||
(p "Pages use these directly — no controller layer, no ORM:"))
|
||||
|
||||
(~docs/section :title "Implementation Path" :id "implementation"
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
|
||||
(li (strong "Phase 1: Read-only browser") " — git-tree, git-blob, git-log, git-diff as IO primitives. "
|
||||
"SX components for tree view, blob view, commit log, diff view.")
|
||||
(li (strong "Phase 2: Issues") " — SX forms for create/edit. Content-addressed storage. "
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/sx-host/plan-sx-host-content ()
|
||||
(~docs/page :title "sx-host: Universal Platform Primitives"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"Every SX host — browser, server, CLI, embedded — is the same five IO primitives. "
|
||||
"Everything above that floor is SX evaluating SX. HTTP is an SX library. "
|
||||
"SQL is an SX library. TLS is an SX library. The host only touches raw bytes.")
|
||||
@@ -16,15 +16,15 @@
|
||||
|
||||
(~docs/section :title "The Five Primitives" :id "primitives"
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Primitive") (th :class "text-left p-2" "Signature") (th :class "text-left p-2" "What")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Primitive") (th (~tw :tokens "text-left p-2") "Signature") (th (~tw :tokens "text-left p-2") "What")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "read") (td :class "p-2 font-mono text-xs" "(read channel) → bytes") (td :class "p-2" "Bytes in. From a socket, a file handle, stdin, a sensor."))
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "write") (td :class "p-2 font-mono text-xs" "(write channel bytes) → n") (td :class "p-2" "Bytes out. To a socket, a file handle, stdout, an actuator."))
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "store") (td :class "p-2 font-mono text-xs" "(store key bytes) → ok") (td :class "p-2" "Persist bytes by key. Filesystem, KV store, database row, IPFS block."))
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "retrieve") (td :class "p-2 font-mono text-xs" "(retrieve key) → bytes | nil") (td :class "p-2" "Get bytes by key. Same backends."))
|
||||
(tr (td :class "p-2 font-mono text-violet-700" "hash") (td :class "p-2 font-mono text-xs" "(hash bytes) → digest") (td :class "p-2" "Content-address bytes. SHA-256. The trust anchor for everything."))))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "read") (td (~tw :tokens "p-2 font-mono text-xs") "(read channel) → bytes") (td (~tw :tokens "p-2") "Bytes in. From a socket, a file handle, stdin, a sensor."))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "write") (td (~tw :tokens "p-2 font-mono text-xs") "(write channel bytes) → n") (td (~tw :tokens "p-2") "Bytes out. To a socket, a file handle, stdout, an actuator."))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "store") (td (~tw :tokens "p-2 font-mono text-xs") "(store key bytes) → ok") (td (~tw :tokens "p-2") "Persist bytes by key. Filesystem, KV store, database row, IPFS block."))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "retrieve") (td (~tw :tokens "p-2 font-mono text-xs") "(retrieve key) → bytes | nil") (td (~tw :tokens "p-2") "Get bytes by key. Same backends."))
|
||||
(tr (td (~tw :tokens "p-2 font-mono text-violet-700") "hash") (td (~tw :tokens "p-2 font-mono text-xs") "(hash bytes) → digest") (td (~tw :tokens "p-2") "Content-address bytes. SHA-256. The trust anchor for everything."))))
|
||||
|
||||
(p "These are irreducible. You cannot implement " (code "read") " in terms of the others. "
|
||||
"You cannot implement " (code "store") " without the host. You cannot implement "
|
||||
@@ -50,23 +50,23 @@
|
||||
|
||||
(p "Each protocol is an SX library that reads and writes bytes through channels.")
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "What") (th :class "text-left p-2" "Primitives Used") (th :class "text-left p-2" "SX Library")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "What") (th (~tw :tokens "text-left p-2") "Primitives Used") (th (~tw :tokens "text-left p-2") "SX Library")))
|
||||
(tbody
|
||||
(tr (td :class "p-2" "TCP connection") (td :class "p-2 font-mono text-xs" "open, read, write, close") (td :class "p-2 font-mono text-xs" "net/tcp.sx"))
|
||||
(tr (td :class "p-2" "TLS handshake") (td :class "p-2 font-mono text-xs" "read, write, hash") (td :class "p-2 font-mono text-xs" "net/tls.sx"))
|
||||
(tr (td :class "p-2" "HTTP server") (td :class "p-2 font-mono text-xs" "listen, read, write") (td :class "p-2 font-mono text-xs" "net/http.sx"))
|
||||
(tr (td :class "p-2" "HTTP client") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "net/http-client.sx"))
|
||||
(tr (td :class "p-2" "Postgres client") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "db/postgres.sx"))
|
||||
(tr (td :class "p-2" "Redis client") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "db/redis.sx"))
|
||||
(tr (td :class "p-2" "IPFS content store") (td :class "p-2 font-mono text-xs" "store, retrieve, hash") (td :class "p-2 font-mono text-xs" "net/ipfs.sx"))
|
||||
(tr (td :class "p-2" "File system") (td :class "p-2 font-mono text-xs" "store, retrieve") (td :class "p-2 font-mono text-xs" "io/fs.sx"))
|
||||
(tr (td :class "p-2" "DNS resolver") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "net/dns.sx"))
|
||||
(tr (td :class "p-2" "WebSocket") (td :class "p-2 font-mono text-xs" "read, write (over HTTP upgrade)") (td :class "p-2 font-mono text-xs" "net/websocket.sx"))
|
||||
(tr (td :class "p-2" "SMTP") (td :class "p-2 font-mono text-xs" "open, read, write") (td :class "p-2 font-mono text-xs" "net/smtp.sx"))
|
||||
(tr (td :class "p-2" "ActivityPub") (td :class "p-2 font-mono text-xs" "HTTP + hash (signatures)") (td :class "p-2 font-mono text-xs" "net/activitypub.sx"))
|
||||
(tr (td :class "p-2" "sx-sync protocol") (td :class "p-2 font-mono text-xs" "read, write (over WS/WebRTC)") (td :class "p-2 font-mono text-xs" "net/sx-sync.sx"))))
|
||||
(tr (td (~tw :tokens "p-2") "TCP connection") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write, close") (td (~tw :tokens "p-2 font-mono text-xs") "net/tcp.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "TLS handshake") (td (~tw :tokens "p-2 font-mono text-xs") "read, write, hash") (td (~tw :tokens "p-2 font-mono text-xs") "net/tls.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "HTTP server") (td (~tw :tokens "p-2 font-mono text-xs") "listen, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "net/http.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "HTTP client") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "net/http-client.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Postgres client") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "db/postgres.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Redis client") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "db/redis.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "IPFS content store") (td (~tw :tokens "p-2 font-mono text-xs") "store, retrieve, hash") (td (~tw :tokens "p-2 font-mono text-xs") "net/ipfs.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "File system") (td (~tw :tokens "p-2 font-mono text-xs") "store, retrieve") (td (~tw :tokens "p-2 font-mono text-xs") "io/fs.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "DNS resolver") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "net/dns.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "WebSocket") (td (~tw :tokens "p-2 font-mono text-xs") "read, write (over HTTP upgrade)") (td (~tw :tokens "p-2 font-mono text-xs") "net/websocket.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "SMTP") (td (~tw :tokens "p-2 font-mono text-xs") "open, read, write") (td (~tw :tokens "p-2 font-mono text-xs") "net/smtp.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "ActivityPub") (td (~tw :tokens "p-2 font-mono text-xs") "HTTP + hash (signatures)") (td (~tw :tokens "p-2 font-mono text-xs") "net/activitypub.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "sx-sync protocol") (td (~tw :tokens "p-2 font-mono text-xs") "read, write (over WS/WebRTC)") (td (~tw :tokens "p-2 font-mono text-xs") "net/sx-sync.sx"))))
|
||||
|
||||
(~docs/code :src (highlight
|
||||
";; HTTP server — pure SX on the five primitives\n(define http-server\n (fn (address handler)\n (listen address (fn (channel)\n (let ((request-bytes (read channel))\n (request (http-parse-request request-bytes))\n (response (handler request))\n (response-bytes (http-serialize-response response)))\n (write channel response-bytes)\n (close channel))))))\n\n;; Your app — SX all the way down\n(http-server \":8080\" (fn (req)\n (let ((page (route (get req :path))))\n (http-response 200 (render-to-html page)))))"
|
||||
@@ -85,45 +85,45 @@
|
||||
|
||||
(p "Each host implements the same 5+3 primitives. The SX layer is identical across all of them.")
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Host") (th :class "text-left p-2" "read/write") (th :class "text-left p-2" "store/retrieve") (th :class "text-left p-2" "hash") (th :class "text-left p-2" "Target")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Host") (th (~tw :tokens "text-left p-2") "read/write") (th (~tw :tokens "text-left p-2") "store/retrieve") (th (~tw :tokens "text-left p-2") "hash") (th (~tw :tokens "text-left p-2") "Target")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "OCaml native")
|
||||
(td :class "p-2 text-xs" "Unix.read/write")
|
||||
(td :class "p-2 text-xs" "file IO / mmap")
|
||||
(td :class "p-2 text-xs" "Digestif SHA256")
|
||||
(td :class "p-2 text-xs" "Production servers"))
|
||||
(tr (td :class "p-2 font-medium" "Rust native")
|
||||
(td :class "p-2 text-xs" "tokio async IO")
|
||||
(td :class "p-2 text-xs" "sled / filesystem")
|
||||
(td :class "p-2 text-xs" "ring SHA256")
|
||||
(td :class "p-2 text-xs" "Edge, embedded"))
|
||||
(tr (td :class "p-2 font-medium" "Node.js")
|
||||
(td :class "p-2 text-xs" "net.Socket")
|
||||
(td :class "p-2 text-xs" "fs / LevelDB")
|
||||
(td :class "p-2 text-xs" "crypto.createHash")
|
||||
(td :class "p-2 text-xs" "Quick deploy, Lambda"))
|
||||
(tr (td :class "p-2 font-medium" "Python/Quart")
|
||||
(td :class "p-2 text-xs" "asyncio streams")
|
||||
(td :class "p-2 text-xs" "aiofiles / SQLAlchemy")
|
||||
(td :class "p-2 text-xs" "hashlib")
|
||||
(td :class "p-2 text-xs" "Current, prototyping"))
|
||||
(tr (td :class "p-2 font-medium" "Browser")
|
||||
(td :class "p-2 text-xs" "WebSocket / WebRTC")
|
||||
(td :class "p-2 text-xs" "IndexedDB / Cache API")
|
||||
(td :class "p-2 text-xs" "SubtleCrypto")
|
||||
(td :class "p-2 text-xs" "Client nodes"))
|
||||
(tr (td :class "p-2 font-medium" "WASM")
|
||||
(td :class "p-2 text-xs" "imported from host")
|
||||
(td :class "p-2 text-xs" "imported from host")
|
||||
(td :class "p-2 text-xs" "compiled in")
|
||||
(td :class "p-2 text-xs" "Universal, portable"))
|
||||
(tr (td :class "p-2 font-medium" "Embedded (Rust)")
|
||||
(td :class "p-2 text-xs" "UART / SPI / I2C")
|
||||
(td :class "p-2 text-xs" "flash / EEPROM")
|
||||
(td :class "p-2 text-xs" "hardware SHA")
|
||||
(td :class "p-2 text-xs" "IoT, sensors"))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "OCaml native")
|
||||
(td (~tw :tokens "p-2 text-xs") "Unix.read/write")
|
||||
(td (~tw :tokens "p-2 text-xs") "file IO / mmap")
|
||||
(td (~tw :tokens "p-2 text-xs") "Digestif SHA256")
|
||||
(td (~tw :tokens "p-2 text-xs") "Production servers"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Rust native")
|
||||
(td (~tw :tokens "p-2 text-xs") "tokio async IO")
|
||||
(td (~tw :tokens "p-2 text-xs") "sled / filesystem")
|
||||
(td (~tw :tokens "p-2 text-xs") "ring SHA256")
|
||||
(td (~tw :tokens "p-2 text-xs") "Edge, embedded"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Node.js")
|
||||
(td (~tw :tokens "p-2 text-xs") "net.Socket")
|
||||
(td (~tw :tokens "p-2 text-xs") "fs / LevelDB")
|
||||
(td (~tw :tokens "p-2 text-xs") "crypto.createHash")
|
||||
(td (~tw :tokens "p-2 text-xs") "Quick deploy, Lambda"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Python/Quart")
|
||||
(td (~tw :tokens "p-2 text-xs") "asyncio streams")
|
||||
(td (~tw :tokens "p-2 text-xs") "aiofiles / SQLAlchemy")
|
||||
(td (~tw :tokens "p-2 text-xs") "hashlib")
|
||||
(td (~tw :tokens "p-2 text-xs") "Current, prototyping"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Browser")
|
||||
(td (~tw :tokens "p-2 text-xs") "WebSocket / WebRTC")
|
||||
(td (~tw :tokens "p-2 text-xs") "IndexedDB / Cache API")
|
||||
(td (~tw :tokens "p-2 text-xs") "SubtleCrypto")
|
||||
(td (~tw :tokens "p-2 text-xs") "Client nodes"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "WASM")
|
||||
(td (~tw :tokens "p-2 text-xs") "imported from host")
|
||||
(td (~tw :tokens "p-2 text-xs") "imported from host")
|
||||
(td (~tw :tokens "p-2 text-xs") "compiled in")
|
||||
(td (~tw :tokens "p-2 text-xs") "Universal, portable"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Embedded (Rust)")
|
||||
(td (~tw :tokens "p-2 text-xs") "UART / SPI / I2C")
|
||||
(td (~tw :tokens "p-2 text-xs") "flash / EEPROM")
|
||||
(td (~tw :tokens "p-2 text-xs") "hardware SHA")
|
||||
(td (~tw :tokens "p-2 text-xs") "IoT, sensors"))))
|
||||
|
||||
(p "An SX app published to sx-pub runs on any of these hosts unchanged. "
|
||||
"The CIDs are the same. The evaluation is deterministic. "
|
||||
@@ -201,7 +201,7 @@
|
||||
"on the five primitives. Replaces Python framework dependencies.")
|
||||
|
||||
(p "This is the big step. Each protocol library is substantial:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "net/http.sx") " — request/response parsing, chunked encoding, keep-alive (~800 LOC)")
|
||||
(li (code "net/tls.sx") " — TLS 1.3 handshake, certificate verification (~1200 LOC)")
|
||||
(li (code "db/postgres.sx") " — wire protocol, query serialization, result parsing (~600 LOC)")
|
||||
@@ -213,7 +213,7 @@
|
||||
(~docs/subsection :title "Step 4: Host Binary"
|
||||
(p "Minimal native binary: SX evaluator + 5+3 IO primitives. Nothing else.")
|
||||
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "OCaml: CEK machine + Unix IO (~2000 LOC, single binary)")
|
||||
(li "Rust: compiled evaluator + tokio IO (~3000 LOC, single binary)")
|
||||
(li "Both: no Python, no Quart, no SQLAlchemy, no pip, no virtualenv"))
|
||||
@@ -236,34 +236,34 @@
|
||||
(p "The five primitives are not web-specific. They're IO-specific. "
|
||||
"Any device that can read bytes, write bytes, and store bytes can host SX.")
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Host") (th :class "text-left p-2" "Channels") (th :class "text-left p-2" "Storage") (th :class "text-left p-2" "Use Case")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Host") (th (~tw :tokens "text-left p-2") "Channels") (th (~tw :tokens "text-left p-2") "Storage") (th (~tw :tokens "text-left p-2") "Use Case")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "CLI")
|
||||
(td :class "p-2 text-xs" "stdin/stdout/stderr, files, pipes")
|
||||
(td :class "p-2 text-xs" "filesystem")
|
||||
(td :class "p-2 text-xs" "Build tools, scripts, automation"))
|
||||
(tr (td :class "p-2 font-medium" "Desktop (Tauri)")
|
||||
(td :class "p-2 text-xs" "IPC to native windows, file dialogs")
|
||||
(td :class "p-2 text-xs" "app data dir")
|
||||
(td :class "p-2 text-xs" "Native apps with SX UI"))
|
||||
(tr (td :class "p-2 font-medium" "Mobile")
|
||||
(td :class "p-2 text-xs" "network + sensor APIs")
|
||||
(td :class "p-2 text-xs" "SQLite / app storage")
|
||||
(td :class "p-2 text-xs" "Apps, games, tools"))
|
||||
(tr (td :class "p-2 font-medium" "Serverless")
|
||||
(td :class "p-2 text-xs" "event payload in, response out")
|
||||
(td :class "p-2 text-xs" "KV store / S3")
|
||||
(td :class "p-2 text-xs" "Lambda, Cloudflare Workers"))
|
||||
(tr (td :class "p-2 font-medium" "Game engine")
|
||||
(td :class "p-2 text-xs" "GPU commands, audio buffers")
|
||||
(td :class "p-2 text-xs" "asset cache")
|
||||
(td :class "p-2 text-xs" "SX game logic + native rendering"))
|
||||
(tr (td :class "p-2 font-medium" "Embedded")
|
||||
(td :class "p-2 text-xs" "UART, SPI, I2C, GPIO")
|
||||
(td :class "p-2 text-xs" "flash / EEPROM")
|
||||
(td :class "p-2 text-xs" "IoT, sensors, controllers"))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "CLI")
|
||||
(td (~tw :tokens "p-2 text-xs") "stdin/stdout/stderr, files, pipes")
|
||||
(td (~tw :tokens "p-2 text-xs") "filesystem")
|
||||
(td (~tw :tokens "p-2 text-xs") "Build tools, scripts, automation"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Desktop (Tauri)")
|
||||
(td (~tw :tokens "p-2 text-xs") "IPC to native windows, file dialogs")
|
||||
(td (~tw :tokens "p-2 text-xs") "app data dir")
|
||||
(td (~tw :tokens "p-2 text-xs") "Native apps with SX UI"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Mobile")
|
||||
(td (~tw :tokens "p-2 text-xs") "network + sensor APIs")
|
||||
(td (~tw :tokens "p-2 text-xs") "SQLite / app storage")
|
||||
(td (~tw :tokens "p-2 text-xs") "Apps, games, tools"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Serverless")
|
||||
(td (~tw :tokens "p-2 text-xs") "event payload in, response out")
|
||||
(td (~tw :tokens "p-2 text-xs") "KV store / S3")
|
||||
(td (~tw :tokens "p-2 text-xs") "Lambda, Cloudflare Workers"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Game engine")
|
||||
(td (~tw :tokens "p-2 text-xs") "GPU commands, audio buffers")
|
||||
(td (~tw :tokens "p-2 text-xs") "asset cache")
|
||||
(td (~tw :tokens "p-2 text-xs") "SX game logic + native rendering"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Embedded")
|
||||
(td (~tw :tokens "p-2 text-xs") "UART, SPI, I2C, GPIO")
|
||||
(td (~tw :tokens "p-2 text-xs") "flash / EEPROM")
|
||||
(td (~tw :tokens "p-2 text-xs") "IoT, sensors, controllers"))))
|
||||
|
||||
(p "Same five primitives. Same SX spec. Same content-addressed components from sx-pub. "
|
||||
"A component that works in the browser works on a server works on a Raspberry Pi — "
|
||||
@@ -276,13 +276,13 @@
|
||||
|
||||
(~docs/section :title "Relationship to Existing Plans" :id "relationships"
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Plan") (th :class "text-left p-2" "Relationship")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Plan") (th (~tw :tokens "text-left p-2") "Relationship")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "sx-web") (td :class "p-2" "sx-host defines what a node IS. sx-web defines how nodes CONNECT."))
|
||||
(tr (td :class "p-2 font-medium" "Mother Language") (td :class "p-2" "The evaluator that runs on each host. sx-host defines the interface it evaluates against."))
|
||||
(tr (td :class "p-2 font-medium" "Rust/WASM Host") (td :class "p-2" "One implementation of the five primitives. Compiles to native or WASM."))
|
||||
(tr (td :class "p-2 font-medium" "Isolated Evaluator") (td :class "p-2" "Core/platform split maps directly: core = evaluator, platform = five primitives."))
|
||||
(tr (td :class "p-2 font-medium" "sx-pub") (td :class "p-2" "Where the SX protocol libraries and app definitions live. Hosts fetch from sx-pub to bootstrap."))
|
||||
(tr (td :class "p-2 font-medium" "Runtime Slicing") (td :class "p-2" "Slicing determines which SX libraries a host needs. Minimal host = evaluator + primitives. Full host = all protocol libraries.")))))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "sx-web") (td (~tw :tokens "p-2") "sx-host defines what a node IS. sx-web defines how nodes CONNECT."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Mother Language") (td (~tw :tokens "p-2") "The evaluator that runs on each host. sx-host defines the interface it evaluates against."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Rust/WASM Host") (td (~tw :tokens "p-2") "One implementation of the five primitives. Compiles to native or WASM."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Isolated Evaluator") (td (~tw :tokens "p-2") "Core/platform split maps directly: core = evaluator, platform = five primitives."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "sx-pub") (td (~tw :tokens "p-2") "Where the SX protocol libraries and app definitions live. Hosts fetch from sx-pub to bootstrap."))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Runtime Slicing") (td (~tw :tokens "p-2") "Slicing determines which SX libraries a host needs. Minimal host = evaluator + primitives. Full host = all protocol libraries.")))))))
|
||||
|
||||
@@ -13,43 +13,43 @@
|
||||
(~docs/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"
|
||||
(table (~tw :tokens "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")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Concern")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Current Syntax")
|
||||
(th (~tw :tokens "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 (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Resource path")
|
||||
(td (~tw :tokens "py-2 pr-4") "URL segments")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "/users/123/posts"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Query parameters")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "?key=value")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "?filter=published&sort=date"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "API queries")
|
||||
(td (~tw :tokens "py-2 pr-4") "GraphQL / REST")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "{ posts(filter: \"published\") { title } }"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Network verb")
|
||||
(td (~tw :tokens "py-2 pr-4") "HTTP method")
|
||||
(td (~tw :tokens "py-2 font-mono text-xs") "GET, POST, PUT"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Real-time")
|
||||
(td (~tw :tokens "py-2 pr-4") "WebSocket URL")
|
||||
(td (~tw :tokens "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"))))
|
||||
(td (~tw :tokens "py-2 pr-4") "Rendering")
|
||||
(td (~tw :tokens "py-2 pr-4") "HTML + CSS + JS")
|
||||
(td (~tw :tokens "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."))
|
||||
|
||||
(~docs/section :title "The SX Approach" :id "approach"
|
||||
|
||||
(h3 :class "text-lg font-semibold mt-6 mb-2" "URLs as S-Expressions")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-6 mb-2") "URLs as S-Expressions")
|
||||
(p "A conventional URL:")
|
||||
(~docs/code :src (highlight "https://site.com/blog/my-post?filter=published&sort=date" "text"))
|
||||
(p "As an SX expression:")
|
||||
@@ -60,13 +60,13 @@
|
||||
(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")
|
||||
(h3 (~tw :tokens "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.")
|
||||
(~docs/code :src (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")
|
||||
(h3 (~tw :tokens "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:")
|
||||
(~docs/code :src (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://")
|
||||
@@ -79,7 +79,7 @@
|
||||
(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")
|
||||
(h3 (~tw :tokens "text-lg font-semibold mt-6 mb-2") "Queries Are URLs")
|
||||
(p "Because SX expressions are URLs, every query is a GET request:")
|
||||
(~docs/code :src (highlight ";; This is a URL and a query simultaneously\n(get.site.com.(blog.(filter.(tag.lisp)).(limit.10)))" "lisp"))
|
||||
(ul
|
||||
@@ -88,14 +88,14 @@
|
||||
(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")
|
||||
(h3 (~tw :tokens "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:")
|
||||
(~docs/code :src (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")
|
||||
(h3 (~tw :tokens "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:")
|
||||
(~docs/code :src (highlight ";; Fetch, filter, and transform in one expression\n(map (lambda (p) (title p))\n (filter published?\n (posts (after \"2025\"))))" "lisp"))
|
||||
@@ -127,59 +127,59 @@
|
||||
(p "The site is its own documentation. The source is always one expression away."))
|
||||
|
||||
(~docs/section :title "Comparison" :id "comparison"
|
||||
(table :class "w-full text-sm border-collapse mb-4"
|
||||
(table (~tw :tokens "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")))
|
||||
(tr (~tw :tokens "border-b border-stone-300")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "Feature")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "REST")
|
||||
(th (~tw :tokens "text-left py-2 pr-4") "GraphQL")
|
||||
(th (~tw :tokens "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 (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Reads are GETs")
|
||||
(td (~tw :tokens "py-2 pr-4 text-green-700") "Yes")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No (POST)")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "CDN cacheable")
|
||||
(td (~tw :tokens "py-2 pr-4 text-green-700") "Yes")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Nested queries")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-green-700") "Yes")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Response includes rendering")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Query is a transformation")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Composable across domains")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "One syntax for everything")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Bookmarkable deep links")
|
||||
(td (~tw :tokens "py-2 pr-4 text-green-700") "Yes")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "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")))))
|
||||
(td (~tw :tokens "py-2 pr-4") "Self-hosting / introspectable")
|
||||
(td (~tw :tokens "py-2 pr-4 text-red-700") "No")
|
||||
(td (~tw :tokens "py-2 pr-4 text-stone-500") "Partial")
|
||||
(td (~tw :tokens "py-2 text-green-700") "Yes")))))
|
||||
|
||||
(~docs/section :title "Future Direction" :id "future"
|
||||
(p "The logical conclusion of SX is a " (strong "new internet protocol")
|
||||
@@ -187,11 +187,11 @@
|
||||
"and the rendering layer are all unified under one evaluable expression format.")
|
||||
(~docs/code :src (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"
|
||||
(blockquote (~tw :tokens "border-l-4 border-violet-300 pl-4 italic text-stone-600 my-4")
|
||||
(p (strong "Evaluate this expression. Return an expression."))))
|
||||
|
||||
(~docs/section :title "Reference Implementation" :id "reference"
|
||||
(p "SX is implemented in SX. The reference implementation is self-hosting and available at:")
|
||||
(~docs/code :src (highlight "(get.sx.dev.(source.evaluator))" "lisp"))
|
||||
(p :class "text-sm text-stone-500 mt-4 italic"
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-4 italic")
|
||||
"This proposal was written in conversation with Claude (Anthropic). The ideas are the author's own."))))
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
(~docs/section :title "Why" :id "why"
|
||||
(p "Every proxy config language reinvents the same features badly:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "Nginx: custom DSL with if-is-evil, no real conditionals, include for composition")
|
||||
(li "Caddy: Caddyfile is nice but no functions, no macros, no types")
|
||||
(li "Traefik: labels on Docker containers — stringly-typed, no validation")
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
(~docs/section :title "Dynamic Reconfiguration" :id "dynamic"
|
||||
(p "The proxy evaluates SX — so config can be dynamic:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Hot reload") " — change the SX, proxy re-evaluates. No restart.")
|
||||
(li (strong "API-driven") " — sx-post new routes. The proxy is an SX app with endpoints.")
|
||||
(li (strong "Swarm-aware") " — subscribe to swarm-status changes, "
|
||||
@@ -170,7 +170,7 @@
|
||||
"the same SX service definitions. No YAML-to-Caddyfile-to-Dockerfile translation."))
|
||||
|
||||
(~docs/section :title "Implementation Path" :id "implementation"
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
|
||||
(li (strong "Phase 1: Config compiler") " — SX route definitions compiled to Caddy JSON API. "
|
||||
"Use Caddy as the runtime, SX as the config language.")
|
||||
(li (strong "Phase 2: Middleware macros") " — defmacro for common middleware patterns. "
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
(~docs/section :title "What It Is" :id "what"
|
||||
(p "A federated content publishing protocol built entirely in SX. No JSON. Content pinned to IPFS, referenced by CID. Blockchain-anchored provenance.")
|
||||
(p "One actor — " (code "sx") " — publishes SX specs, platform libraries, components, and documentation as content-addressed packages. Other sx-pub servers can follow, mirror, and refer to content by CID. Human-readable paths map to immutable IPFS addresses.")
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "The key insight")
|
||||
(p :class "text-violet-800" "IPFS is the database. SX is the wire format. defhandlers are the endpoints. The wire format is the programming language is the component system is the package manager.")))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The key insight")
|
||||
(p (~tw :tokens "text-violet-800") "IPFS is the database. SX is the wire format. defhandlers are the endpoints. The wire format is the programming language is the component system is the package manager.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Why ActivityPub Failed — and Why LISP Fixes It
|
||||
@@ -51,9 +51,9 @@
|
||||
(p "JSON-LD @context was supposed to provide semantic interoperability. In practice it's a constant source of bugs. Implementations disagree on which contexts to support, which extensions to recognise, how to resolve conflicts. The W3C ActivityStreams context document is a single point of failure that the entire fediverse depends on.")
|
||||
(p "SX needs no context resolution. " (code "(Publish :actor \"...\" :object (SxDocument :cid \"...\"))") " is self-describing. The s-expression " (em "is") " the meaning. Symbols are symbols. Keywords are keywords. No indirection, no remote JSON documents, no semantic web machinery."))
|
||||
|
||||
(div :class "rounded border border-rose-200 bg-rose-50 p-4 mt-6"
|
||||
(p :class "text-rose-900 font-medium" "The fundamental difference")
|
||||
(p :class "text-rose-800" "ActivityPub federates " (em "descriptions") " of content. Consumers must independently figure out how to present them. sx-pub federates " (em "programs") " — self-contained, evaluable, deterministic, content-addressed. The gap between \"data\" and \"application\" disappears because in LISP, they were never separate to begin with.")))
|
||||
(div (~tw :tokens "rounded border border-rose-200 bg-rose-50 p-4 mt-6")
|
||||
(p (~tw :tokens "text-rose-900 font-medium") "The fundamental difference")
|
||||
(p (~tw :tokens "text-rose-800") "ActivityPub federates " (em "descriptions") " of content. Consumers must independently figure out how to present them. sx-pub federates " (em "programs") " — self-contained, evaluable, deterministic, content-addressed. The gap between \"data\" and \"application\" disappears because in LISP, they were never separate to begin with.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Wire Format
|
||||
@@ -89,37 +89,37 @@
|
||||
(~docs/section :title "Endpoints" :id "endpoints"
|
||||
(p "All endpoints are " (code "defhandler") " definitions in " (code ".sx") " files. Python provides only IO primitives behind the boundary.")
|
||||
|
||||
(table :class "w-full text-sm"
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200 text-left"
|
||||
(th :class "py-2 pr-4" "Method")
|
||||
(th :class "py-2 pr-4" "Route")
|
||||
(th :class "py-2" "Purpose")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/actor") (td :class "py-2" "Actor profile"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/outbox") (td :class "py-2" "Published activity feed"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "POST") (td :class "py-2 pr-4 font-mono text-xs" "/pub/inbox") (td :class "py-2" "Receive activities from remote servers"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/⟨collection⟩") (td :class "py-2" "List items in a collection"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/⟨collection⟩/⟨slug⟩") (td :class "py-2" "Document by path — resolves CID, fetches from IPFS"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/cid/⟨cid⟩") (td :class "py-2" "Direct CID fetch (immutable, cache forever)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "POST") (td :class "py-2 pr-4 font-mono text-xs" "/pub/publish") (td :class "py-2" "Pin to IPFS + create Publish activity + announce"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "POST") (td :class "py-2 pr-4 font-mono text-xs" "/pub/follow") (td :class "py-2" "Follow a remote sx-pub server"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/followers") (td :class "py-2" "Who follows us"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/following") (td :class "py-2" "Who we follow"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/.well-known/webfinger") (td :class "py-2" "Discovery"))
|
||||
(tr (~tw :tokens "border-b border-stone-200 text-left")
|
||||
(th (~tw :tokens "py-2 pr-4") "Method")
|
||||
(th (~tw :tokens "py-2 pr-4") "Route")
|
||||
(th (~tw :tokens "py-2") "Purpose")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/actor") (td (~tw :tokens "py-2") "Actor profile"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/outbox") (td (~tw :tokens "py-2") "Published activity feed"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "POST") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/inbox") (td (~tw :tokens "py-2") "Receive activities from remote servers"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/⟨collection⟩") (td (~tw :tokens "py-2") "List items in a collection"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/⟨collection⟩/⟨slug⟩") (td (~tw :tokens "py-2") "Document by path — resolves CID, fetches from IPFS"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/cid/⟨cid⟩") (td (~tw :tokens "py-2") "Direct CID fetch (immutable, cache forever)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "POST") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/publish") (td (~tw :tokens "py-2") "Pin to IPFS + create Publish activity + announce"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "POST") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/follow") (td (~tw :tokens "py-2") "Follow a remote sx-pub server"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/followers") (td (~tw :tokens "py-2") "Who follows us"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/following") (td (~tw :tokens "py-2") "Who we follow"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/.well-known/webfinger") (td (~tw :tokens "py-2") "Discovery"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "GET") (td :class "py-2 pr-4 font-mono text-xs" "/pub/anchor/⟨tree-cid⟩") (td :class "py-2" "Merkle tree verification")))))
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "GET") (td (~tw :tokens "py-2 pr-4 font-mono text-xs") "/pub/anchor/⟨tree-cid⟩") (td (~tw :tokens "py-2") "Merkle tree verification")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Storage
|
||||
@@ -128,28 +128,28 @@
|
||||
(~docs/section :title "Storage" :id "storage"
|
||||
(p "IPFS is the canonical content store. PostgreSQL is the local index.")
|
||||
|
||||
(table :class "w-full text-sm"
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200 text-left"
|
||||
(th :class "py-2 pr-4" "What")
|
||||
(th :class "py-2" "Where")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Content (SX source)") (td :class "py-2" "IPFS — pinned, referenced by CID"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Path → CID index") (td :class "py-2" "PostgreSQL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Outbox activities") (td :class "py-2" "PostgreSQL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Followers / following") (td :class "py-2" "PostgreSQL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Anchor records") (td :class "py-2" "PostgreSQL"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Remote mirrored content") (td :class "py-2" "PostgreSQL + IPFS (pinned locally)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 pr-4" "Content cache") (td :class "py-2" "Redis"))
|
||||
(tr (~tw :tokens "border-b border-stone-200 text-left")
|
||||
(th (~tw :tokens "py-2 pr-4") "What")
|
||||
(th (~tw :tokens "py-2") "Where")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Content (SX source)") (td (~tw :tokens "py-2") "IPFS — pinned, referenced by CID"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Path → CID index") (td (~tw :tokens "py-2") "PostgreSQL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Outbox activities") (td (~tw :tokens "py-2") "PostgreSQL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Followers / following") (td (~tw :tokens "py-2") "PostgreSQL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Anchor records") (td (~tw :tokens "py-2") "PostgreSQL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Remote mirrored content") (td (~tw :tokens "py-2") "PostgreSQL + IPFS (pinned locally)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 pr-4") "Content cache") (td (~tw :tokens "py-2") "Redis"))
|
||||
(tr
|
||||
(td :class "py-2 pr-4" "Actor keypair") (td :class "py-2" "PostgreSQL")))))
|
||||
(td (~tw :tokens "py-2 pr-4") "Actor keypair") (td (~tw :tokens "py-2") "PostgreSQL")))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; SX / Python Split
|
||||
@@ -158,10 +158,10 @@
|
||||
(~docs/section :title "SX / Python Split" :id "split"
|
||||
(p "SX handles all composition, logic, and rendering. Python provides IO primitives behind the boundary.")
|
||||
|
||||
(div :class "grid grid-cols-2 gap-6"
|
||||
(div (~tw :tokens "grid grid-cols-2 gap-6")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Pure SX (defhandler + defcomp)")
|
||||
(ul :class "text-sm text-stone-600 space-y-1 list-disc pl-5"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Pure SX (defhandler + defcomp)")
|
||||
(ul (~tw :tokens "text-sm text-stone-600 space-y-1 list-disc pl-5")
|
||||
(li "All HTTP routing and request dispatch")
|
||||
(li "Content negotiation (Accept header)")
|
||||
(li "Response construction (SX actor docs, collections)")
|
||||
@@ -169,8 +169,8 @@
|
||||
(li "Activity serialization")
|
||||
(li "Nav data for /pub/ sections")))
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700 mb-2" "Python Helpers (IO boundary)")
|
||||
(ul :class "text-sm text-stone-600 space-y-1 list-disc pl-5"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-700 mb-2") "Python Helpers (IO boundary)")
|
||||
(ul (~tw :tokens "text-sm text-stone-600 space-y-1 list-disc pl-5")
|
||||
(li (code "pub-ipfs-add") " / " (code "pub-ipfs-get") " / " (code "pub-ipfs-pin") " — IPFS ops")
|
||||
(li (code "pub-db-query") " / " (code "pub-db-insert") " — database")
|
||||
(li (code "pub-sign") " / " (code "pub-verify-signature") " — HTTP signatures")
|
||||
@@ -185,8 +185,8 @@
|
||||
(~docs/section :title "Flows" :id "flows"
|
||||
|
||||
(~docs/subsection :title "Publish"
|
||||
(p :class "text-sm text-stone-600 mb-2" "Publishing is " (strong "anchor-then-announce") " — content is provenance-sealed before anyone sees it. This prevents a malicious follower from seeing content, anchoring it first, and claiming provenance.")
|
||||
(ol :class "text-sm text-stone-600 space-y-2 list-decimal pl-5"
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "Publishing is " (strong "anchor-then-announce") " — content is provenance-sealed before anyone sees it. This prevents a malicious follower from seeing content, anchoring it first, and claiming provenance.")
|
||||
(ol (~tw :tokens "text-sm text-stone-600 space-y-2 list-decimal pl-5")
|
||||
(li (code "POST /pub/publish") " with collection, slug, SX content")
|
||||
(li "Pin to IPFS → get CID (content is now addressable but not announced)")
|
||||
(li "Store path→CID in DB (status: " (code "anchoring") ")")
|
||||
@@ -194,12 +194,12 @@
|
||||
(li "On confirmation: update status to " (code "anchored") ", record proof")
|
||||
(li "Construct " (code "(Publish ...)") " with anchor proof embedded")
|
||||
(li "Deliver to follower inboxes — " (strong "only now") " is it public"))
|
||||
(p :class "text-sm text-stone-500 mt-2 italic" "This means publishing isn't instant — there's a delay while the anchor confirms. That's a feature: it forces a deliberate pace and makes provenance airtight.")
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-2 italic") "This means publishing isn't instant — there's a delay while the anchor confirms. That's a feature: it forces a deliberate pace and makes provenance airtight.")
|
||||
(~docs/code :src "(Publish\n :actor \"https://pub.sx-web.org/pub/actor\"\n :published \"2026-03-24T12:00:00Z\"\n :object (SxDocument\n :name \"evaluator\"\n :cid \"bafy...eval123\"\n :anchor (Anchor\n :tree-cid \"bafy...merkle\"\n :ots-cid \"bafy...proof\"\n :btc-txid \"abc123...\"\n :btc-block 890123)))")
|
||||
(p :class "text-sm text-stone-600" "Receivers verify: the CID was anchored in that Bitcoin block " (em "before") " the Publish was sent. No way to backdate."))
|
||||
(p (~tw :tokens "text-sm text-stone-600") "Receivers verify: the CID was anchored in that Bitcoin block " (em "before") " the Publish was sent. No way to backdate."))
|
||||
|
||||
(~docs/subsection :title "Follow"
|
||||
(ol :class "text-sm text-stone-600 space-y-2 list-decimal pl-5"
|
||||
(ol (~tw :tokens "text-sm text-stone-600 space-y-2 list-decimal pl-5")
|
||||
(li (code "POST /pub/follow") " with remote actor URL")
|
||||
(li "Fetch remote actor document (" (code "text/sx") ")")
|
||||
(li "Send " (code "(Follow ...)") " to their inbox, signed with our key")
|
||||
@@ -208,7 +208,7 @@
|
||||
(li "They start delivering " (code "(Publish ...)") " activities to our inbox")))
|
||||
|
||||
(~docs/subsection :title "Receive (inbox)"
|
||||
(ol :class "text-sm text-stone-600 space-y-2 list-decimal pl-5"
|
||||
(ol (~tw :tokens "text-sm text-stone-600 space-y-2 list-decimal pl-5")
|
||||
(li "Remote server POSTs " (code "(Publish ...)") " to our inbox")
|
||||
(li "Verify HTTP signature against their actor's public key")
|
||||
(li "Pin the referenced CID to our local IPFS node")
|
||||
@@ -216,15 +216,15 @@
|
||||
(li "Now we can resolve that CID locally — " (code "/pub/cid/bafy...") " just works")))
|
||||
|
||||
(~docs/subsection :title "Anchoring"
|
||||
(p :class "text-sm text-stone-600 mb-2" "Anchoring is integrated into the publish flow — not a separate background job. Content waits for provenance before going public.")
|
||||
(ol :class "text-sm text-stone-600 space-y-2 list-decimal pl-5"
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "Anchoring is integrated into the publish flow — not a separate background job. Content waits for provenance before going public.")
|
||||
(ol (~tw :tokens "text-sm text-stone-600 space-y-2 list-decimal pl-5")
|
||||
(li "Batch pending CIDs into a Merkle tree (multiple publishes can share one tree)")
|
||||
(li "Pin Merkle tree to IPFS")
|
||||
(li "Submit Merkle root to OpenTimestamps calendar servers")
|
||||
(li "Poll for Bitcoin confirmation (typically ~1 hour)")
|
||||
(li "On confirmation: store proof, transition content to " (code "anchored") " status")
|
||||
(li "Trigger delivery of Publish activities for all items in the batch"))
|
||||
(p :class "text-sm text-stone-500 mt-2 italic" "Batching amortises the anchor cost — one Bitcoin attestation can seal hundreds of CIDs via the Merkle tree. Each CID gets an inclusion proof linking it to the anchored root.")))
|
||||
(p (~tw :tokens "text-sm text-stone-500 mt-2 italic") "Batching amortises the anchor cost — one Bitcoin attestation can seal hundreds of CIDs via the Merkle tree. Each CID gets an inclusion proof linking it to the anchored root.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; CID ↔ Path
|
||||
@@ -242,19 +242,19 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Implementation Phases" :id "phases"
|
||||
(div :class "space-y-4"
|
||||
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4"
|
||||
(h4 :class "font-semibold text-emerald-800" "Phase 1: Foundation ✓")
|
||||
(p :class "text-emerald-700 text-sm" "DB schema, async IPFS client, actor endpoint, webfinger, " (code "/pub/actor") " returns SX actor document."))
|
||||
(div :class "rounded border border-sky-200 bg-sky-50 p-4"
|
||||
(h4 :class "font-semibold text-sky-800" "Phase 2: Publishing ✓")
|
||||
(p :class "text-sky-700 text-sm" "Pin to IPFS, path→CID index, collection browsing. Publish the actual SX spec files as the first content."))
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4"
|
||||
(h4 :class "font-semibold text-violet-800" "Phase 3: Federation ✓")
|
||||
(p :class "text-violet-700 text-sm" "Inbox/outbox, follow/accept, HTTP signature verification, activity delivery, content mirroring."))
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
||||
(h4 :class "font-semibold text-amber-800" "Phase 4: Anchoring ✓")
|
||||
(p :class "text-amber-700 text-sm" "Merkle trees, OpenTimestamps, Bitcoin proof, provenance verification."))))
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-emerald-800") "Phase 1: Foundation ✓")
|
||||
(p (~tw :tokens "text-emerald-700 text-sm") "DB schema, async IPFS client, actor endpoint, webfinger, " (code "/pub/actor") " returns SX actor document."))
|
||||
(div (~tw :tokens "rounded border border-sky-200 bg-sky-50 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-sky-800") "Phase 2: Publishing ✓")
|
||||
(p (~tw :tokens "text-sky-700 text-sm") "Pin to IPFS, path→CID index, collection browsing. Publish the actual SX spec files as the first content."))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-violet-800") "Phase 3: Federation ✓")
|
||||
(p (~tw :tokens "text-violet-700 text-sm") "Inbox/outbox, follow/accept, HTTP signature verification, activity delivery, content mirroring."))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(h4 (~tw :tokens "font-semibold text-amber-800") "Phase 4: Anchoring ✓")
|
||||
(p (~tw :tokens "text-amber-700 text-sm") "Merkle trees, OpenTimestamps, Bitcoin proof, provenance verification."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Live Dashboard
|
||||
@@ -266,45 +266,45 @@
|
||||
;; --- Status ---
|
||||
(~docs/subsection :title "Server Status"
|
||||
(let ((status (helper "pub-status-data")))
|
||||
(div :class "grid grid-cols-2 sm:grid-cols-4 gap-3"
|
||||
(div :class "rounded border border-stone-200 p-3 text-center"
|
||||
(p :class "text-xs text-stone-400 uppercase" "DB")
|
||||
(p :class "font-semibold text-sm" (get status "db")))
|
||||
(div :class "rounded border border-stone-200 p-3 text-center"
|
||||
(p :class "text-xs text-stone-400 uppercase" "IPFS")
|
||||
(p :class "font-semibold text-sm" (get status "ipfs")))
|
||||
(div :class "rounded border border-stone-200 p-3 text-center"
|
||||
(p :class "text-xs text-stone-400 uppercase" "Actor")
|
||||
(p :class "font-semibold text-sm" (get status "actor")))
|
||||
(div :class "rounded border border-stone-200 p-3 text-center"
|
||||
(p :class "text-xs text-stone-400 uppercase" "Domain")
|
||||
(p :class "font-semibold text-sm" (or (get status "domain") "—"))))))
|
||||
(div (~tw :tokens "grid grid-cols-2 sm:grid-cols-4 gap-3")
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 text-center")
|
||||
(p (~tw :tokens "text-xs text-stone-400 uppercase") "DB")
|
||||
(p (~tw :tokens "font-semibold text-sm") (get status "db")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 text-center")
|
||||
(p (~tw :tokens "text-xs text-stone-400 uppercase") "IPFS")
|
||||
(p (~tw :tokens "font-semibold text-sm") (get status "ipfs")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 text-center")
|
||||
(p (~tw :tokens "text-xs text-stone-400 uppercase") "Actor")
|
||||
(p (~tw :tokens "font-semibold text-sm") (get status "actor")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 text-center")
|
||||
(p (~tw :tokens "text-xs text-stone-400 uppercase") "Domain")
|
||||
(p (~tw :tokens "font-semibold text-sm") (or (get status "domain") "—"))))))
|
||||
|
||||
;; --- Actor ---
|
||||
(~docs/subsection :title "Actor Identity"
|
||||
(let ((actor (helper "pub-actor-data")))
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4 space-y-2"
|
||||
(div :class "flex items-center gap-3"
|
||||
(div :class "w-10 h-10 rounded-full bg-violet-100 flex items-center justify-center text-violet-700 font-bold" "sx")
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 space-y-2")
|
||||
(div (~tw :tokens "flex items-center gap-3")
|
||||
(div (~tw :tokens "w-10 h-10 rounded-full bg-violet-100 flex items-center justify-center text-violet-700 font-bold") "sx")
|
||||
(div
|
||||
(p :class "font-semibold" (get actor "display-name"))
|
||||
(p :class "text-sm text-stone-500" (str "@" (get actor "preferred-username") "@" (get actor "domain")))))
|
||||
(p :class "text-sm text-stone-600" (get actor "summary"))
|
||||
(details :class "text-xs"
|
||||
(summary :class "text-stone-400 cursor-pointer" "Public key")
|
||||
(pre :class "mt-2 bg-stone-100 rounded p-2 text-xs overflow-x-auto" (get actor "public-key-pem"))))))
|
||||
(p (~tw :tokens "font-semibold") (get actor "display-name"))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (str "@" (get actor "preferred-username") "@" (get actor "domain")))))
|
||||
(p (~tw :tokens "text-sm text-stone-600") (get actor "summary"))
|
||||
(details (~tw :tokens "text-xs")
|
||||
(summary (~tw :tokens "text-stone-400 cursor-pointer") "Public key")
|
||||
(pre (~tw :tokens "mt-2 bg-stone-100 rounded p-2 text-xs overflow-x-auto") (get actor "public-key-pem"))))))
|
||||
|
||||
;; --- Collections ---
|
||||
(~docs/subsection :title "Collections"
|
||||
(let ((collections (helper "pub-collections-data")))
|
||||
(div :class "grid gap-3"
|
||||
(div (~tw :tokens "grid gap-3")
|
||||
(map (fn (c)
|
||||
(div :class "rounded border border-stone-200 p-4 hover:border-violet-300 transition-colors"
|
||||
(div :class "flex items-center justify-between"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-4 hover:border-violet-300 transition-colors")
|
||||
(div (~tw :tokens "flex items-center justify-between")
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-800" (get c "name"))
|
||||
(p :class "text-sm text-stone-500" (get c "description")))
|
||||
(span :class "text-xs font-mono text-violet-600 bg-violet-50 px-2 py-1 rounded"
|
||||
(h4 (~tw :tokens "font-semibold text-stone-800") (get c "name"))
|
||||
(p (~tw :tokens "text-sm text-stone-500") (get c "description")))
|
||||
(span (~tw :tokens "text-xs font-mono text-violet-600 bg-violet-50 px-2 py-1 rounded")
|
||||
(str "/pub/" (get c "slug"))))))
|
||||
collections))))
|
||||
|
||||
@@ -312,35 +312,35 @@
|
||||
(~docs/subsection :title "Published Documents"
|
||||
(let ((specs (helper "pub-collection-items" "core-specs")))
|
||||
(when (not (get specs "error"))
|
||||
(div :class "space-y-2"
|
||||
(h4 :class "text-sm font-semibold text-stone-500 uppercase tracking-wide"
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(h4 (~tw :tokens "text-sm font-semibold text-stone-500 uppercase tracking-wide")
|
||||
(get specs "name"))
|
||||
(map (fn (d)
|
||||
(when (!= (get d "slug") "")
|
||||
(div :class "rounded border border-stone-200 p-3 flex items-center justify-between"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 flex items-center justify-between")
|
||||
(div
|
||||
(p :class "font-medium text-stone-800" (get d "title"))
|
||||
(p :class "text-xs text-stone-400" (get d "summary")))
|
||||
(div :class "text-right"
|
||||
(p :class "text-xs font-mono text-emerald-600 truncate max-w-48" (get d "cid"))
|
||||
(p :class "text-xs text-stone-400" (str (get d "size") " bytes"))))))
|
||||
(p (~tw :tokens "font-medium text-stone-800") (get d "title"))
|
||||
(p (~tw :tokens "text-xs text-stone-400") (get d "summary")))
|
||||
(div (~tw :tokens "text-right")
|
||||
(p (~tw :tokens "text-xs font-mono text-emerald-600 truncate max-w-48") (get d "cid"))
|
||||
(p (~tw :tokens "text-xs text-stone-400") (str (get d "size") " bytes"))))))
|
||||
(get specs "items"))))))
|
||||
|
||||
;; --- Outbox ---
|
||||
(~docs/subsection :title "Recent Activity"
|
||||
(let ((outbox (helper "pub-outbox-data" "")))
|
||||
(if (= (get outbox "total") 0)
|
||||
(p :class "text-sm text-stone-400 italic" "No activities yet.")
|
||||
(div :class "space-y-2"
|
||||
(p :class "text-xs text-stone-400" (str (get outbox "total") " total activities"))
|
||||
(p (~tw :tokens "text-sm text-stone-400 italic") "No activities yet.")
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(p (~tw :tokens "text-xs text-stone-400") (str (get outbox "total") " total activities"))
|
||||
(map (fn (a)
|
||||
(when (!= (get a "type") "")
|
||||
(div :class "rounded border border-stone-200 p-3 flex items-center gap-3"
|
||||
(span :class "text-xs font-semibold text-white bg-violet-500 px-2 py-0.5 rounded"
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 flex items-center gap-3")
|
||||
(span (~tw :tokens "text-xs font-semibold text-white bg-violet-500 px-2 py-0.5 rounded")
|
||||
(get a "type"))
|
||||
(span :class "text-xs text-stone-500" (get a "published"))
|
||||
(span (~tw :tokens "text-xs text-stone-500") (get a "published"))
|
||||
(when (!= (get a "cid") "")
|
||||
(span :class "text-xs font-mono text-emerald-600 truncate max-w-48"
|
||||
(span (~tw :tokens "text-xs font-mono text-emerald-600 truncate max-w-48")
|
||||
(get a "cid"))))))
|
||||
(get outbox "items"))))))
|
||||
|
||||
@@ -348,22 +348,22 @@
|
||||
(~docs/subsection :title "Followers"
|
||||
(let ((followers (helper "pub-followers-data")))
|
||||
(if (empty? followers)
|
||||
(p :class "text-sm text-stone-400 italic" "No followers yet.")
|
||||
(div :class "space-y-2"
|
||||
(p (~tw :tokens "text-sm text-stone-400 italic") "No followers yet.")
|
||||
(div (~tw :tokens "space-y-2")
|
||||
(map (fn (f)
|
||||
(when (!= (get f "acct") "")
|
||||
(div :class "rounded border border-stone-200 p-3 flex items-center gap-2"
|
||||
(div :class "w-8 h-8 rounded-full bg-sky-100 flex items-center justify-center text-sky-700 text-xs font-bold" "F")
|
||||
(p :class "text-sm font-mono text-stone-600 truncate" (get f "acct")))))
|
||||
(div (~tw :tokens "rounded border border-stone-200 p-3 flex items-center gap-2")
|
||||
(div (~tw :tokens "w-8 h-8 rounded-full bg-sky-100 flex items-center justify-center text-sky-700 text-xs font-bold") "F")
|
||||
(p (~tw :tokens "text-sm font-mono text-stone-600 truncate") (get f "acct")))))
|
||||
followers))))
|
||||
|
||||
;; --- API Endpoints ---
|
||||
(~docs/subsection :title "Try the API"
|
||||
(p :class "text-sm text-stone-600 mb-2" "All endpoints return " (code "text/sx") ". Try them directly:")
|
||||
(div :class "grid grid-cols-2 sm:grid-cols-3 gap-2"
|
||||
(p (~tw :tokens "text-sm text-stone-600 mb-2") "All endpoints return " (code "text/sx") ". Try them directly:")
|
||||
(div (~tw :tokens "grid grid-cols-2 sm:grid-cols-3 gap-2")
|
||||
(map (fn (endpoint)
|
||||
(a :href (get endpoint "href")
|
||||
:class "block rounded border border-stone-200 p-2 text-center hover:border-violet-300 hover:bg-violet-50 transition-colors text-xs font-mono text-stone-600"
|
||||
(~tw :tokens "block rounded border border-stone-200 p-2 text-center hover:border-violet-300 hover:bg-violet-50 transition-colors text-xs font-mono text-stone-600")
|
||||
(get endpoint "label")))
|
||||
(list
|
||||
{"label" "GET /pub/actor" "href" "/pub/actor"}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"they're static data with ad-hoc templating bolted on. "
|
||||
"Variable substitution (${VAR}), extension fields (x-), YAML anchors (&/*) — "
|
||||
"all workarounds for not having a real language.")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "No functions — can't abstract repeated patterns")
|
||||
(li "No macros — can't generate config at definition time")
|
||||
(li "No conditionals — can't branch on environment")
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
(~docs/section :title "Health and Monitoring" :id "monitoring"
|
||||
(p "Service health as live SX components:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Dashboard") " — defcomp rendering swarm-status, auto-refreshing via sx-get polling")
|
||||
(li (strong "Log viewer") " — swarm-logs streamed via SSE, rendered as SX")
|
||||
(li (strong "Alerts") " — SX predicates on service state, notifications via sx-activity")
|
||||
@@ -155,7 +155,7 @@
|
||||
"No Grafana, no separate monitoring stack. Same language, same renderer, same platform."))
|
||||
|
||||
(~docs/section :title "Implementation Path" :id "implementation"
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
|
||||
(li (strong "Phase 1: Stack definition") " — SX data structures for services, networks, volumes. "
|
||||
"Compiler to docker-compose.yml / docker stack deploy format.")
|
||||
(li (strong "Phase 2: Environment macros") " — defmacro for env-for, dev-volumes, prod-volumes. "
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
";; 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"
|
||||
(ul (~tw :tokens "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"))
|
||||
@@ -86,22 +86,22 @@
|
||||
"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"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "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)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Method")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Semantics")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Example")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "GET")
|
||||
(td (~tw :tokens "py-2 px-3") "Pure evaluation — cacheable, bookmarkable")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "GET /(language.(doc.intro))"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "POST")
|
||||
(td (~tw :tokens "py-2 px-3") "Side effects — mutations, submissions")
|
||||
(td (~tw :tokens "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."))
|
||||
@@ -109,51 +109,51 @@
|
||||
(~docs/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"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "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 "/sx/(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")))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Concept")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "GraphQL")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "GraphSX")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Single endpoint")
|
||||
(td (~tw :tokens "py-2 px-3") (code "/graphql"))
|
||||
(td (~tw :tokens "py-2 px-3") "Catch-all " (code "/<path:expr>")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Query structure")
|
||||
(td (~tw :tokens "py-2 px-3") "Nested fields " (code "{ language { doc { ... } } }"))
|
||||
(td (~tw :tokens "py-2 px-3") "Nested s-expressions " (code "(language.(doc....))")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Resolvers")
|
||||
(td (~tw :tokens "py-2 px-3") "Per-field functions")
|
||||
(td (~tw :tokens "py-2 px-3") "Page/section functions + " (code "~components")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Fragments")
|
||||
(td (~tw :tokens "py-2 px-3") "Named reusable selections")
|
||||
(td (~tw :tokens "py-2 px-3") "Components (" (code "defcomp") ")"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Schema")
|
||||
(td (~tw :tokens "py-2 px-3") "Type definitions")
|
||||
(td (~tw :tokens "py-2 px-3") "Page function registry + component env"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Scoping")
|
||||
(td (~tw :tokens "py-2 px-3") "Flat — " (code "?filter=x") " applies to... what?")
|
||||
(td (~tw :tokens "py-2 px-3") "Structural — parens ARE scope boundaries"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Transport")
|
||||
(td (~tw :tokens "py-2 px-3") "POST JSON (violates HTTP GET semantics)")
|
||||
(td (~tw :tokens "py-2 px-3") "GET " (code "/sx/(expr)") " — cacheable, bookmarkable"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Response")
|
||||
(td (~tw :tokens "py-2 px-3") "JSON data (needs separate rendering)")
|
||||
(td (~tw :tokens "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"
|
||||
(ol (~tw :tokens "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"))
|
||||
@@ -175,42 +175,42 @@
|
||||
|
||||
(~docs/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"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "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" "/sx/(source.(~essays/sx-sucks/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" "/sx/(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" "/sx/(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" "/sx/(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" "/sx/(raw.(~some-component))")
|
||||
(td :class "py-2 px-3" "Skip ~layouts/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" "/sx/(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" "/sx/(json.(language.(doc.primitives)))")
|
||||
(td :class "py-2 px-3" "Return data as JSON — pure query mode"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Form")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Example")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Effect")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "source")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(source.(~essays/sx-sucks/essay-sx-sucks))")
|
||||
(td (~tw :tokens "py-2 px-3") "Show defcomp source instead of rendering"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "inspect")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(inspect.(language.(doc.primitives)))")
|
||||
(td (~tw :tokens "py-2 px-3") "Deps, CSS classes, render plan, IO requirements"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "diff")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(diff.(spec.signals).(spec.eval))")
|
||||
(td (~tw :tokens "py-2 px-3") "Side-by-side two pages"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "search")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(search.\"define\".:in.(spec.signals))")
|
||||
(td (~tw :tokens "py-2 px-3") "Grep within a page or spec"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "raw")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(raw.(~some-component))")
|
||||
(td (~tw :tokens "py-2 px-3") "Skip ~layouts/doc nav wrapping"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "eval")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(eval.(map.double.(list.1.2.3)))")
|
||||
(td (~tw :tokens "py-2 px-3") "Arbitrary evaluation — the URL bar is a REPL"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "json")
|
||||
(td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(json.(language.(doc.primitives)))")
|
||||
(td (~tw :tokens "py-2 px-3") "Return data as JSON — pure query mode"))))))
|
||||
|
||||
(~docs/section :title "Evaluation Model" :id "eval"
|
||||
(p "The URL path (after stripping " (code "/") " and replacing dots with spaces) "
|
||||
@@ -240,7 +240,7 @@
|
||||
"@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"
|
||||
(ol (~tw :tokens "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")
|
||||
@@ -257,43 +257,43 @@
|
||||
"lisp")))
|
||||
|
||||
(~docs/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"
|
||||
(div (~tw :tokens "space-y-4")
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-violet-800 mb-2") "Phase 1: Page Functions + Catch-All Route")
|
||||
(ul (~tw :tokens "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"
|
||||
(div (~tw :tokens "rounded border border-blue-200 bg-blue-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-blue-800 mb-2") "Phase 2: Navigation Data")
|
||||
(ul (~tw :tokens "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"
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-emerald-800 mb-2") "Phase 3: Backward Compatibility")
|
||||
(ul (~tw :tokens "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"
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-amber-800 mb-2") "Phase 4: URL Special Forms")
|
||||
(ul (~tw :tokens "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"
|
||||
(div (~tw :tokens "rounded border border-rose-200 bg-rose-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-rose-800 mb-2") "Phase 5: Client-Side Routing")
|
||||
(ul (~tw :tokens "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"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "font-semibold text-stone-800 mb-2") "Phase 6: Cleanup")
|
||||
(ul (~tw :tokens "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")))))
|
||||
|
||||
(~docs/section :title "What Stays the Same" :id "unchanged"
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "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")
|
||||
|
||||
@@ -17,38 +17,38 @@
|
||||
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(p "The platform composes existing SX subsystems into a unified workflow:")
|
||||
(div :class "overflow-x-auto mt-4"
|
||||
(table :class "w-full text-sm text-left"
|
||||
(div (~tw :tokens "overflow-x-auto mt-4")
|
||||
(table (~tw :tokens "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" "Layer")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "System")
|
||||
(th :class "py-2 px-3 font-semibold text-stone-700" "Role")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Author")
|
||||
(td :class "py-2 px-3" "Embedded editor + Claude Code")
|
||||
(td :class "py-2 px-3" "Write SX in the browser with AI assistance"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Stage")
|
||||
(td :class "py-2 px-3" "Content-addressed components")
|
||||
(td :class "py-2 px-3" "CID-identified artifacts, preview before publish"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Test")
|
||||
(td :class "py-2 px-3" "sx-ci")
|
||||
(td :class "py-2 px-3" "Run test suites against staged changes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Publish")
|
||||
(td :class "py-2 px-3" "sx-activity")
|
||||
(td :class "py-2 px-3" "Federated distribution via ActivityPub"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Store")
|
||||
(td :class "py-2 px-3" "IPFS")
|
||||
(td :class "py-2 px-3" "Content-addressed storage, permanent availability"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Verify")
|
||||
(td :class "py-2 px-3" "Environment images")
|
||||
(td :class "py-2 px-3" "Spec CID → image CID → endpoint provenance"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-200")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Layer")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "System")
|
||||
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Role")))
|
||||
(tbody (~tw :tokens "text-stone-600")
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Author")
|
||||
(td (~tw :tokens "py-2 px-3") "Embedded editor + Claude Code")
|
||||
(td (~tw :tokens "py-2 px-3") "Write SX in the browser with AI assistance"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Stage")
|
||||
(td (~tw :tokens "py-2 px-3") "Content-addressed components")
|
||||
(td (~tw :tokens "py-2 px-3") "CID-identified artifacts, preview before publish"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Test")
|
||||
(td (~tw :tokens "py-2 px-3") "sx-ci")
|
||||
(td (~tw :tokens "py-2 px-3") "Run test suites against staged changes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Publish")
|
||||
(td (~tw :tokens "py-2 px-3") "sx-activity")
|
||||
(td (~tw :tokens "py-2 px-3") "Federated distribution via ActivityPub"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Store")
|
||||
(td (~tw :tokens "py-2 px-3") "IPFS")
|
||||
(td (~tw :tokens "py-2 px-3") "Content-addressed storage, permanent availability"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "py-2 px-3 font-semibold") "Verify")
|
||||
(td (~tw :tokens "py-2 px-3") "Environment images")
|
||||
(td (~tw :tokens "py-2 px-3") "Spec CID → image CID → endpoint provenance"))))))
|
||||
|
||||
(~docs/section :title "Embedded Claude Code" :id "claude-code"
|
||||
(p "Claude Code sessions run inside the browser as reactive islands. "
|
||||
@@ -56,7 +56,7 @@
|
||||
"write components, run tests, and propose changes. All within the user's security context.")
|
||||
(p "The session produces SX diffs — not text patches, but structural changes to the component tree. "
|
||||
"These diffs are first-class SX values that can be inspected, composed, reverted, and replayed.")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li "Read any component definition, spec file, or plan")
|
||||
(li "Write new components (essays, examples, specs)")
|
||||
(li "Modify existing components with structural diffs")
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
(~docs/section :title "Workflow" :id "workflow"
|
||||
(p "A typical session — adding a new essay:")
|
||||
(ol :class "space-y-3 text-stone-600 list-decimal pl-5"
|
||||
(ol (~tw :tokens "space-y-3 text-stone-600 list-decimal pl-5")
|
||||
(li (strong "Author: ") "Open Claude Code session on sx-web.org. "
|
||||
"Describe the essay topic. Claude writes the defcomp in SX.")
|
||||
(li (strong "Preview: ") "The component renders live in the browser. "
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
(~docs/section :title "Content Types" :id "content-types"
|
||||
(p "Anything that can be a defcomp can be authored on the platform:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Essays") " — opinion pieces, rationales, explorations")
|
||||
(li (strong "Examples") " — interactive demos with live code")
|
||||
(li (strong "Specs") " — new spec modules authored and tested in-browser")
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
(~docs/section :title "Prerequisites" :id "prerequisites"
|
||||
(p "Systems that must be complete before the platform can work:")
|
||||
(ul :class "space-y-2 text-stone-600 list-disc pl-5"
|
||||
(ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
|
||||
(li (strong "Reactive islands (L2+)") " — for the editor and preview panes")
|
||||
(li (strong "Content-addressed components") " — CID computation and IPFS storage")
|
||||
(li (strong "sx-activity") " — federated publish/subscribe")
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
(defcomp ~plans/sx-web/plan-sx-web-content ()
|
||||
(~docs/page :title "sx-web: Federated Component Web"
|
||||
|
||||
(p :class "text-stone-500 text-sm italic mb-8"
|
||||
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
|
||||
"A two-tier peer network where server nodes pin to IPFS and browser tabs "
|
||||
"participate via WebTransport. Components, tests, and content are all "
|
||||
"content-addressed SX expressions verified by CID regardless of source. "
|
||||
@@ -19,16 +19,16 @@
|
||||
|
||||
(p "Two tiers, one logical network. Trust anchored on CIDs, not sources.")
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "") (th :class "text-left p-2" "Server Node") (th :class "text-left p-2" "Browser Node")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "") (th (~tw :tokens "text-left p-2") "Server Node") (th (~tw :tokens "text-left p-2") "Browser Node")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "Runtime") (td :class "p-2" "OCaml + Python") (td :class "p-2" "JavaScript (sx-browser.js)"))
|
||||
(tr (td :class "p-2 font-medium" "Storage") (td :class "p-2" "IPFS daemon + PostgreSQL") (td :class "p-2" "IndexedDB + Cache API"))
|
||||
(tr (td :class "p-2 font-medium" "IPFS") (td :class "p-2" "Full DHT peer, pins content") (td :class "p-2" "Verifies CIDs, caches locally"))
|
||||
(tr (td :class "p-2 font-medium" "Transport") (td :class "p-2" "WebSocket + HTTP federation") (td :class "p-2" "WebSocket to home + WebRTC to peers"))
|
||||
(tr (td :class "p-2 font-medium" "Availability") (td :class "p-2" "Long-lived, always on") (td :class "p-2" "Ephemeral, online when open"))
|
||||
(tr (td :class "p-2 font-medium" "Trust") (td :class "p-2" "CID verification + blockchain anchor") (td :class "p-2" "CID verification (same guarantee)"))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Runtime") (td (~tw :tokens "p-2") "OCaml + Python") (td (~tw :tokens "p-2") "JavaScript (sx-browser.js)"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Storage") (td (~tw :tokens "p-2") "IPFS daemon + PostgreSQL") (td (~tw :tokens "p-2") "IndexedDB + Cache API"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "IPFS") (td (~tw :tokens "p-2") "Full DHT peer, pins content") (td (~tw :tokens "p-2") "Verifies CIDs, caches locally"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Transport") (td (~tw :tokens "p-2") "WebSocket + HTTP federation") (td (~tw :tokens "p-2") "WebSocket to home + WebRTC to peers"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Availability") (td (~tw :tokens "p-2") "Long-lived, always on") (td (~tw :tokens "p-2") "Ephemeral, online when open"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Trust") (td (~tw :tokens "p-2") "CID verification + blockchain anchor") (td (~tw :tokens "p-2") "CID verification (same guarantee)"))))
|
||||
|
||||
(p "A component fetched from IPFS, from a server, from another browser tab, "
|
||||
"or from local IndexedDB cache is verified the same way: hash it, check the CID. "
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
(~docs/section :title "Phase 1: Browser Node Foundation" :id "phase-1"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Make the browser a first-class participant, not just a viewer.")
|
||||
|
||||
(~docs/subsection :title "1a. Content Store (IndexedDB)"
|
||||
@@ -52,7 +52,7 @@
|
||||
"lisp"))
|
||||
|
||||
(p "Two IndexedDB object stores:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (strong "blobs") " — CID → SX source bytes (content-addressed, immutable)")
|
||||
(li (strong "pins") " — CID → {pinned, last-accessed, size, source-node} (eviction metadata)"))
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"Everything else (verification, pinning, eviction) is pure SX."))
|
||||
|
||||
(~docs/subsection :title "1d. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/content-store.sx") " — browser content store (~120 LOC)")
|
||||
(li (code "web/lib/sw-routes.sx") " — Service Worker CID routing (~80 LOC)")
|
||||
(li "Platform primitive: " (code "content-hash") " in JS host (~15 LOC)")
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
(~docs/section :title "Phase 2: Peer Network" :id "phase-2"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Three transports, one SX protocol. Browser↔server via WebSocket, "
|
||||
"browser↔browser via WebRTC data channels, server↔server via federation. "
|
||||
"The protocol layer doesn't know or care which pipe carries it.")
|
||||
@@ -110,13 +110,13 @@
|
||||
|
||||
(~docs/subsection :title "2b. Three Transports"
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Link") (th :class "text-left p-2" "Transport") (th :class "text-left p-2" "Why")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Link") (th (~tw :tokens "text-left p-2") "Transport") (th (~tw :tokens "text-left p-2") "Why")))
|
||||
(tbody
|
||||
(tr (td :class "p-2 font-medium" "Browser ↔ Home node") (td :class "p-2" "WebSocket") (td :class "p-2" "Reliable, always-on, works today, no infrastructure changes"))
|
||||
(tr (td :class "p-2 font-medium" "Browser ↔ Browser") (td :class "p-2" "WebRTC data channel") (td :class "p-2" "True peer-to-peer, NAT traversal via ICE/STUN, UDP + TCP modes"))
|
||||
(tr (td :class "p-2 font-medium" "Server ↔ Server") (td :class "p-2" "HTTP federation") (td :class "p-2" "Existing sx-pub protocol, signed activities"))))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Browser ↔ Home node") (td (~tw :tokens "p-2") "WebSocket") (td (~tw :tokens "p-2") "Reliable, always-on, works today, no infrastructure changes"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Browser ↔ Browser") (td (~tw :tokens "p-2") "WebRTC data channel") (td (~tw :tokens "p-2") "True peer-to-peer, NAT traversal via ICE/STUN, UDP + TCP modes"))
|
||||
(tr (td (~tw :tokens "p-2 font-medium") "Server ↔ Server") (td (~tw :tokens "p-2") "HTTP federation") (td (~tw :tokens "p-2") "Existing sx-pub protocol, signed activities"))))
|
||||
|
||||
(p "The SX protocol layer is transport-agnostic. " (code "peer-send") " and " (code "peer-listen") " "
|
||||
"work identically whether the peer is connected via WebSocket, WebRTC, or HTTP. "
|
||||
@@ -144,7 +144,7 @@
|
||||
"lisp"))
|
||||
|
||||
(p "Peer-to-peer means:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Game state at LAN-like latency — no server round-trip")
|
||||
(li "Content exchange between nearby browsers without hitting IPFS")
|
||||
(li "Collaborative editing with sub-frame update propagation")
|
||||
@@ -179,7 +179,7 @@
|
||||
"The networking is invisible to the reactive layer."))
|
||||
|
||||
(~docs/subsection :title "2f. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/sx-sync.sx") " — protocol definition incl. shared signals (~300 LOC)")
|
||||
(li (code "web/lib/peer.sx") " — transport-agnostic peer abstraction (~200 LOC)")
|
||||
(li (code "web/lib/shared-signal.sx") " — shared signal primitive (~120 LOC)")
|
||||
@@ -193,7 +193,7 @@
|
||||
|
||||
(~docs/section :title "Phase 3: In-Browser Authoring" :id "phase-3"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Edit, preview, test, and publish from a browser tab. No local dev environment.")
|
||||
|
||||
(~docs/subsection :title "3a. Live Editor"
|
||||
@@ -230,7 +230,7 @@
|
||||
"Clean split: browsers create, servers distribute."))
|
||||
|
||||
(~docs/subsection :title "3d. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/editor.sx") " — live editor island (~300 LOC)")
|
||||
(li (code "web/lib/test-runner.sx") " — browser test runner (~150 LOC)")
|
||||
(li (code "web/lib/publish.sx") " — publish flow (~100 LOC)")
|
||||
@@ -242,7 +242,7 @@
|
||||
|
||||
(~docs/section :title "Phase 4: Component Discovery & Resolution" :id "phase-4"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Find, resolve, and depend on components across the federation.")
|
||||
|
||||
(~docs/subsection :title "4a. Dependency Resolution"
|
||||
@@ -267,7 +267,7 @@
|
||||
(p "The component browser is itself an SX component published to sx-pub."))
|
||||
|
||||
(~docs/subsection :title "4c. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/resolver.sx") " — dependency resolution chain (~200 LOC)")
|
||||
(li (code "sx/sx/tools/component-browser.sx") " — federated browser (~400 LOC)")
|
||||
(li (code "sx/sx/tools/search.sx") " — search interface (~150 LOC)")
|
||||
@@ -279,7 +279,7 @@
|
||||
|
||||
(~docs/section :title "Phase 5: Node Bootstrap" :id "phase-5"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"Standing up a new node: one command, minutes to first render.")
|
||||
|
||||
(~docs/subsection :title "5a. Server Node Bootstrap"
|
||||
@@ -301,7 +301,7 @@
|
||||
"The barrier to entry is typing an address."))
|
||||
|
||||
(~docs/subsection :title "5c. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Docker image: " (code "sx-node") " with OCaml host, IPFS, Caddy, PostgreSQL")
|
||||
(li "Bootstrap script: fetch specs, verify CIDs, initialize DB")
|
||||
(li "Seed peer protocol: advertise available specs + popular components")
|
||||
@@ -313,7 +313,7 @@
|
||||
|
||||
(~docs/section :title "Phase 6: AI Composition Layer" :id "phase-6"
|
||||
|
||||
(p :class "text-stone-600 italic mb-4"
|
||||
(p (~tw :tokens "text-stone-600 italic mb-4")
|
||||
"The federated graph is simultaneously training data, retrieval context, and composition target.")
|
||||
|
||||
(~docs/subsection :title "6a. Component Retrieval"
|
||||
@@ -333,14 +333,14 @@
|
||||
"more tests to validate against, more usage patterns to learn from.")
|
||||
|
||||
(p "The network effect works for both humans and AI:")
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li "Every component published is more material for AI composition")
|
||||
(li "Every test attached is more validation for AI output")
|
||||
(li "Every fork is a preference signal for AI ranking")
|
||||
(li "Every pin is a popularity signal for AI retrieval")))
|
||||
|
||||
(~docs/subsection :title "6c. Deliverables"
|
||||
(ul :class "list-disc pl-6 mb-4 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-6 mb-4 space-y-1")
|
||||
(li (code "web/lib/ai-compose.sx") " — composition interface (~200 LOC)")
|
||||
(li "Embedding index over component metadata + signatures")
|
||||
(li "Retrieval API: search by intent, filter by quality signals")
|
||||
@@ -366,19 +366,19 @@
|
||||
|
||||
(~docs/section :title "What Already Exists (sx-pub Phases 1–4)" :id "existing"
|
||||
|
||||
(table :class "w-full mb-6 text-sm"
|
||||
(table (~tw :tokens "w-full mb-6 text-sm")
|
||||
(thead
|
||||
(tr (th :class "text-left p-2" "Capability") (th :class "text-left p-2" "Status") (th :class "text-left p-2" "Location")))
|
||||
(tr (th (~tw :tokens "text-left p-2") "Capability") (th (~tw :tokens "text-left p-2") "Status") (th (~tw :tokens "text-left p-2") "Location")))
|
||||
(tbody
|
||||
(tr (td :class "p-2" "IPFS async client") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/ipfs_client.py"))
|
||||
(tr (td :class "p-2" "Merkle tree + proofs") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/anchoring.py"))
|
||||
(tr (td :class "p-2" "OpenTimestamps anchoring") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/utils/anchoring.py"))
|
||||
(tr (td :class "p-2" "ActivityPub federation") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "shared/infrastructure/activitypub.py"))
|
||||
(tr (td :class "p-2" "sx-pub HTTP handlers") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "sx/sx/handlers/pub-api.sx"))
|
||||
(tr (td :class "p-2" "Publishing + CID resolution") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "sx/sxc/pages/pub_helpers.py"))
|
||||
(tr (td :class "p-2" "SX wire format (aser)") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "web/adapter-sx.sx"))
|
||||
(tr (td :class "p-2" "Content store (in-memory)") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "lib/content.sx"))
|
||||
(tr (td :class "p-2" "Component localStorage cache") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "web/boot.sx"))
|
||||
(tr (td :class "p-2" "Docker dev environment") (td :class "p-2 text-green-700" "Done") (td :class "p-2 font-mono text-xs" "docker-compose.dev-pub.yml"))))
|
||||
(tr (td (~tw :tokens "p-2") "IPFS async client") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "shared/utils/ipfs_client.py"))
|
||||
(tr (td (~tw :tokens "p-2") "Merkle tree + proofs") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "shared/utils/anchoring.py"))
|
||||
(tr (td (~tw :tokens "p-2") "OpenTimestamps anchoring") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "shared/utils/anchoring.py"))
|
||||
(tr (td (~tw :tokens "p-2") "ActivityPub federation") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "shared/infrastructure/activitypub.py"))
|
||||
(tr (td (~tw :tokens "p-2") "sx-pub HTTP handlers") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "sx/sx/handlers/pub-api.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Publishing + CID resolution") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "sx/sxc/pages/pub_helpers.py"))
|
||||
(tr (td (~tw :tokens "p-2") "SX wire format (aser)") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "web/adapter-sx.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Content store (in-memory)") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "lib/content.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Component localStorage cache") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "web/boot.sx"))
|
||||
(tr (td (~tw :tokens "p-2") "Docker dev environment") (td (~tw :tokens "p-2 text-green-700") "Done") (td (~tw :tokens "p-2 font-mono text-xs") "docker-compose.dev-pub.yml"))))
|
||||
|
||||
(p "The server-side infrastructure is complete. The plan above extends it to browsers."))))
|
||||
|
||||
@@ -4,26 +4,26 @@
|
||||
|
||||
;; Helper: render a Phase 1 result row
|
||||
(defcomp ~plans/theorem-prover/prove-phase1-row (&key (name :as string) (status :as string))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "py-1.5 px-3 font-mono text-xs text-stone-700" name)
|
||||
(td :class "py-1.5 px-3 text-xs"
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "py-1.5 px-3 font-mono text-xs text-stone-700") name)
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs")
|
||||
(if (= status "sat")
|
||||
(span :class "text-emerald-600 font-medium" "sat")
|
||||
(span :class "text-red-600 font-medium" status)))))
|
||||
(span (~tw :tokens "text-emerald-600 font-medium") "sat")
|
||||
(span (~tw :tokens "text-red-600 font-medium") status)))))
|
||||
|
||||
;; Helper: render a Phase 2 result row
|
||||
(defcomp ~plans/theorem-prover/prove-phase2-row (&key (name :as string) (status :as string) (tested :as number) (skipped :as number) (counterexample :as string?))
|
||||
(tr :class "border-t border-stone-100"
|
||||
(td :class "py-1.5 px-3 font-mono text-xs text-stone-700" name)
|
||||
(td :class "py-1.5 px-3 text-xs"
|
||||
(tr (~tw :tokens "border-t border-stone-100")
|
||||
(td (~tw :tokens "py-1.5 px-3 font-mono text-xs text-stone-700") name)
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs")
|
||||
(if (= status "verified")
|
||||
(span :class "text-emerald-600 font-medium" "verified")
|
||||
(span :class "text-red-600 font-medium" "falsified")))
|
||||
(td :class "py-1.5 px-3 text-xs text-stone-500 text-right tabular-nums"
|
||||
(span (~tw :tokens "text-emerald-600 font-medium") "verified")
|
||||
(span (~tw :tokens "text-red-600 font-medium") "falsified")))
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs text-stone-500 text-right tabular-nums")
|
||||
(str tested))
|
||||
(td :class "py-1.5 px-3 text-xs text-stone-400 text-right tabular-nums"
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs text-stone-400 text-right tabular-nums")
|
||||
(str skipped))
|
||||
(td :class "py-1.5 px-3 text-xs font-mono text-red-600"
|
||||
(td (~tw :tokens "py-1.5 px-3 text-xs font-mono text-red-600")
|
||||
(or counterexample ""))))
|
||||
|
||||
|
||||
@@ -32,38 +32,38 @@
|
||||
|
||||
;; --- Intro ---
|
||||
(~docs/section :title "SX proves itself" :id "intro"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(code "prove.sx") " is a constraint solver and property prover written in SX. It takes the SX specification (" (code "primitives.sx") "), translates it to formal logic via " (code "z3.sx") ", and proves properties about the result. Every step in the pipeline is an s-expression program operating on other s-expressions.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Two proof modes. " (strong "Phase 1") " verifies that every primitive definition is satisfiable " (em "by construction") " — the definition is the model. " (strong "Phase 2") " goes further: it states algebraic properties (commutativity, associativity, transitivity, bounds) and verifies them by " (em "bounded model checking") " — exhaustive evaluation over integer domains, searching for counterexamples.")
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-sm text-violet-800 font-semibold mb-2" "Everything is SX")
|
||||
(p :class "text-sm text-violet-700"
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-sm text-violet-800 font-semibold mb-2") "Everything is SX")
|
||||
(p (~tw :tokens "text-sm text-violet-700")
|
||||
"No external solver. No Python proof logic. " (code "prove.sx") " is 400+ lines of s-expressions that parse SMT-LIB, evaluate expressions, generate test domains, compute cartesian products, search for counterexamples, and produce verification conditions. The same code would work client-side via the bootstrapped JavaScript evaluator.")))
|
||||
|
||||
;; --- Phase 1 Results ---
|
||||
(~docs/section :title "Phase 1: Definitional satisfiability" :id "phase1"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Every " (code "define-primitive") " with a " (code ":body") " produces a " (code "forall") " assertion in SMT-LIB. For example, " (code "(define-primitive \"inc\" :params (n) :body (+ n 1))") " becomes:")
|
||||
(~docs/code :src (highlight "; inc\n(declare-fun inc (Int) Int)\n(assert (forall (((n Int)))\n (= (inc n) (+ n 1))))\n(check-sat)" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"This is satisfiable by construction: define " (code "inc(n) = n + 1") " and the assertion holds. " (code "prove.sx") " verifies this mechanically for every primitive — it parses the SMT-LIB, extracts the definition, builds a model, and evaluates it with test values.")
|
||||
|
||||
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4 my-4"
|
||||
(p :class "text-sm font-semibold"
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-sm font-semibold")
|
||||
(if phase1-all-sat
|
||||
(span :class "text-emerald-800" (str phase1-sat "/" phase1-total " primitives: all sat"))
|
||||
(span :class "text-red-800" (str phase1-sat "/" phase1-total " sat (some failed)"))))
|
||||
(p :class "text-xs text-emerald-700 mt-1"
|
||||
(span (~tw :tokens "text-emerald-800") (str phase1-sat "/" phase1-total " primitives: all sat"))
|
||||
(span (~tw :tokens "text-red-800") (str phase1-sat "/" phase1-total " sat (some failed)"))))
|
||||
(p (~tw :tokens "text-xs text-emerald-700 mt-1")
|
||||
"Computed live in " (str phase1-ms) "ms"))
|
||||
|
||||
(~docs/subsection :title "Results"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 max-h-64 overflow-y-auto"
|
||||
(table :class "w-full text-left"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 max-h-64 overflow-y-auto")
|
||||
(table (~tw :tokens "w-full text-left")
|
||||
(thead
|
||||
(tr :class "bg-stone-100 sticky top-0"
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Primitive")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Status")))
|
||||
(tr (~tw :tokens "bg-stone-100 sticky top-0")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Primitive")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Status")))
|
||||
(tbody
|
||||
(map (fn (r)
|
||||
(~plans/theorem-prover/prove-phase1-row
|
||||
@@ -73,29 +73,29 @@
|
||||
|
||||
;; --- Phase 2 Results ---
|
||||
(~docs/section :title "Phase 2: Algebraic properties" :id "phase2"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Phase 1 proves internal consistency. Phase 2 proves " (em "external properties") " — mathematical laws that should hold across all inputs. Each property is defined as a test function evaluated over a bounded integer domain.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"For arity 1, the domain is [-50, 50] (101 values). For arity 2, [-20, 20] (1,681 pairs). For arity 3, [-8, 8] (4,913 triples). Properties with preconditions (like " (code "clamp-in-range") " requiring " (code "(<= lo hi)") ") skip value combinations that don't satisfy the precondition.")
|
||||
|
||||
(div :class "rounded border border-emerald-200 bg-emerald-50 p-4 my-4"
|
||||
(p :class "text-sm font-semibold"
|
||||
(div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4 my-4")
|
||||
(p (~tw :tokens "text-sm font-semibold")
|
||||
(if phase2-all-verified
|
||||
(span :class "text-emerald-800" (str phase2-verified "/" phase2-total " properties: all verified"))
|
||||
(span :class "text-red-800" (str phase2-verified "/" phase2-total " verified (" phase2-falsified " falsified)"))))
|
||||
(p :class "text-xs text-emerald-700 mt-1"
|
||||
(span (~tw :tokens "text-emerald-800") (str phase2-verified "/" phase2-total " properties: all verified"))
|
||||
(span (~tw :tokens "text-red-800") (str phase2-verified "/" phase2-total " verified (" phase2-falsified " falsified)"))))
|
||||
(p (~tw :tokens "text-xs text-emerald-700 mt-1")
|
||||
(str phase2-total-tested " constraint evaluations in " phase2-ms "ms")))
|
||||
|
||||
(~docs/subsection :title "Results"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 max-h-96 overflow-y-auto"
|
||||
(table :class "w-full text-left"
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 max-h-96 overflow-y-auto")
|
||||
(table (~tw :tokens "w-full text-left")
|
||||
(thead
|
||||
(tr :class "bg-stone-100 sticky top-0"
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Property")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Status")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium text-right" "Tested")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium text-right" "Skipped")
|
||||
(th :class "py-2 px-3 text-xs text-stone-500 font-medium" "Counterexample")))
|
||||
(tr (~tw :tokens "bg-stone-100 sticky top-0")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Property")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Status")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium text-right") "Tested")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium text-right") "Skipped")
|
||||
(th (~tw :tokens "py-2 px-3 text-xs text-stone-500 font-medium") "Counterexample")))
|
||||
(tbody
|
||||
(map (fn (r)
|
||||
(~plans/theorem-prover/prove-phase2-row
|
||||
@@ -108,13 +108,13 @@
|
||||
|
||||
;; --- What the properties prove ---
|
||||
(~docs/section :title "What the properties prove" :id "properties"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"34 properties across seven categories. Each encodes a mathematical law that the SX primitives must obey.")
|
||||
|
||||
(div :class "space-y-4 mt-4"
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "Arithmetic")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "space-y-4 mt-4")
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "Arithmetic")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(+ a b) = (+ b a)") " commutativity. "
|
||||
(code "(+ (+ a b) c) = (+ a (+ b c))") " associativity. "
|
||||
(code "(+ a 0) = a") " identity. "
|
||||
@@ -122,40 +122,40 @@
|
||||
"Same for " (code "*") " — commutative, associative, identity, zero annihilation. "
|
||||
(code "(- a a) = 0") " subtraction inverse. Nine properties, all verified."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "inc / dec")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "inc / dec")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(inc n) = (+ n 1)") " and " (code "(dec n) = (- n 1)") " — definitional relationships. "
|
||||
(code "(dec (inc n)) = n") " and " (code "(inc (dec n)) = n") " — mutual inverses. "
|
||||
"These confirm that " (code "inc") " and " (code "dec") " are exactly " (code "+1") " and " (code "-1") ", not off-by-one."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "abs")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "abs")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(abs n) >= 0") " non-negativity. "
|
||||
(code "(abs (abs n)) = (abs n)") " idempotence. "
|
||||
(code "(abs n) = (abs (- 0 n))") " symmetry. "
|
||||
"Three properties that together characterize absolute value."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "Predicates")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "Predicates")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(odd? n) = (not (even? n))") " — " (code "odd?") " and " (code "even?") " are complementary. "
|
||||
(code "(even? n) = (= (mod n 2) 0)") " — " (code "even?") " matches modular arithmetic. "
|
||||
(code "(zero? n) = (= n 0)") " — " (code "zero?") " is exactly equality with zero. "
|
||||
(code "(not (not p)) = p") " — double negation elimination."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "min / max / clamp")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "min / max / clamp")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "min") " and " (code "max") " are commutative and satisfy their bounds: "
|
||||
(code "(min a b) <= a") " and " (code "(max a b) >= a") ". "
|
||||
(code "(min a b) + (max a b) = a + b") " — the min-max sum identity. "
|
||||
(code "clamp") " with precondition " (code "lo <= hi") ": result is always in " (code "[lo, hi]") ", equals " (code "x") " when already in range, and is idempotent."))
|
||||
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(p :class "text-sm font-semibold text-stone-800 mb-2" "Comparison & inequality")
|
||||
(p :class "text-sm text-stone-600"
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(p (~tw :tokens "text-sm font-semibold text-stone-800 mb-2") "Comparison & inequality")
|
||||
(p (~tw :tokens "text-sm text-stone-600")
|
||||
(code "(< a b) = (> b a)") " — flipped arguments. "
|
||||
(code "(<= a b) = (not (> a b))") " and " (code "(>= a b) = (not (< a b))") " — duality. "
|
||||
"Trichotomy: exactly one of " (code "<") ", " (code "=") ", " (code ">") " holds. "
|
||||
@@ -164,81 +164,81 @@
|
||||
|
||||
;; --- SMT-LIB output ---
|
||||
(~docs/section :title "SMT-LIB verification conditions" :id "smtlib"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Each property also generates SMT-LIB for unbounded verification by an external solver. The strategy: assert the " (em "negation") " of the universal property. If Z3 returns " (code "unsat") ", the property holds for " (em "all") " integers — not just the bounded domain.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
(code "prove.sx") " reuses " (code "z3-expr") " from " (code "z3.sx") " to translate the property AST to SMT-LIB. Properties with preconditions use " (code "=>") " (implication). The same SX expression is both the bounded test and the formal verification condition.")
|
||||
(~docs/code :src (highlight smtlib-sample "lisp")))
|
||||
|
||||
;; --- What it tells us ---
|
||||
(~docs/section :title "What this tells us about SX" :id "implications"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Three things, at increasing depth.")
|
||||
|
||||
(~docs/subsection :title "1. The spec is internally consistent"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Phase 1 proves every " (code "define-primitive") " with a " (code ":body") " is satisfiable. The definition doesn't contradict itself. This is necessary but weak — it's true by construction. The value is mechanical verification: no typo, no copy-paste error, no accidental negation in any of the 91 definitions."))
|
||||
|
||||
(~docs/subsection :title "2. The primitives obey algebraic laws"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Phase 2 proves real mathematical properties hold across bounded domains. These aren't tautologies — they're constraints that " (em "could") " fail. "
|
||||
(code "(+ a b) = (+ b a)") " could fail if " (code "+") " had a subtle bug. "
|
||||
(code "clamp-in-range") " could fail if " (code "max") "/" (code "min") " were swapped. "
|
||||
"The " (str phase2-total-tested) " evaluations found zero counterexamples.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"Bounded model checking is not a mathematical proof — it verifies over a finite domain. The SMT-LIB output bridges the gap: feed it to Z3 for a universal proof over all integers."))
|
||||
|
||||
(~docs/subsection :title "3. SX can reason about itself"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The deep result. The SX evaluator executes " (code "z3.sx") ", which reads SX spec files and emits formal logic. Then the SX evaluator executes " (code "prove.sx") ", which parses that logic and proves properties about it. The specification, the translator, and the prover are all written in the same language, operating on the same data structures.")
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The pipeline: " (code "SX spec") " → " (code "SX translator") " → " (code "formal logic") " → " (code "SX prover") " → " (code "proof") ". At every step, SX is both the subject and the tool. The system is verifying its own foundations using its own machinery.")
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4 mt-3"
|
||||
(p :class "text-sm text-amber-800 font-semibold mb-2" "The strange loop")
|
||||
(p :class "text-sm text-amber-700"
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4 mt-3")
|
||||
(p (~tw :tokens "text-sm text-amber-800 font-semibold mb-2") "The strange loop")
|
||||
(p (~tw :tokens "text-sm text-amber-700")
|
||||
"The SX spec defines primitives. " (code "z3.sx") " (written in SX, using those primitives) translates the spec to formal logic. " (code "prove.sx") " (written in SX, using those same primitives) proves properties about the logic. The primitives being verified are the same primitives doing the verifying. This is not circular — it's a fixed point. If the primitives were wrong, the proofs would fail."))))
|
||||
|
||||
;; --- The pipeline ---
|
||||
(~docs/section :title "The full pipeline" :id "pipeline"
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(table :class "w-full text-sm"
|
||||
(thead (tr :class "text-left text-stone-500"
|
||||
(th :class "pb-2 pr-4" "Step")
|
||||
(th :class "pb-2 pr-4" "Input")
|
||||
(th :class "pb-2 pr-4" "Tool")
|
||||
(th :class "pb-2" "Output")))
|
||||
(div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
|
||||
(table (~tw :tokens "w-full text-sm")
|
||||
(thead (tr (~tw :tokens "text-left text-stone-500")
|
||||
(th (~tw :tokens "pb-2 pr-4") "Step")
|
||||
(th (~tw :tokens "pb-2 pr-4") "Input")
|
||||
(th (~tw :tokens "pb-2 pr-4") "Tool")
|
||||
(th (~tw :tokens "pb-2") "Output")))
|
||||
(tbody
|
||||
(tr :class "text-stone-700 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "1")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "primitives.sx")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "z3.sx")
|
||||
(td :class "py-2" "SMT-LIB (formal logic)"))
|
||||
(tr :class "text-stone-700 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "2a")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "SMT-LIB")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "prove.sx (Phase 1)")
|
||||
(td :class "py-2" "sat (definitional consistency)"))
|
||||
(tr :class "text-stone-700 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "2b")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "property defs")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "prove.sx (Phase 2)")
|
||||
(td :class "py-2" "verified (algebraic laws)"))
|
||||
(tr :class "text-stone-700 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "3")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "properties")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "prove.sx → z3-expr")
|
||||
(td :class "py-2" "SMT-LIB for Z3 (unbounded)"))
|
||||
(tr :class "text-stone-400 border-t border-stone-200"
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "4")
|
||||
(td :class "py-2 pr-4 font-mono text-xs" "SMT-LIB")
|
||||
(td :class "py-2 pr-4 font-mono text-xs italic" "Z3 (external)")
|
||||
(td :class "py-2 italic" "unsat (universal proof)")))))
|
||||
(p :class "text-stone-600 mt-4"
|
||||
(tr (~tw :tokens "text-stone-700 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "1")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "primitives.sx")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "z3.sx")
|
||||
(td (~tw :tokens "py-2") "SMT-LIB (formal logic)"))
|
||||
(tr (~tw :tokens "text-stone-700 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "2a")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "SMT-LIB")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "prove.sx (Phase 1)")
|
||||
(td (~tw :tokens "py-2") "sat (definitional consistency)"))
|
||||
(tr (~tw :tokens "text-stone-700 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "2b")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "property defs")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "prove.sx (Phase 2)")
|
||||
(td (~tw :tokens "py-2") "verified (algebraic laws)"))
|
||||
(tr (~tw :tokens "text-stone-700 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "3")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "properties")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "prove.sx → z3-expr")
|
||||
(td (~tw :tokens "py-2") "SMT-LIB for Z3 (unbounded)"))
|
||||
(tr (~tw :tokens "text-stone-400 border-t border-stone-200")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "4")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs") "SMT-LIB")
|
||||
(td (~tw :tokens "py-2 pr-4 font-mono text-xs italic") "Z3 (external)")
|
||||
(td (~tw :tokens "py-2 italic") "unsat (universal proof)")))))
|
||||
(p (~tw :tokens "text-stone-600 mt-4")
|
||||
"Steps 1-3 run on this page, live, in the SX evaluator. Step 4 requires an external Z3 installation — the SMT-LIB output above is ready to feed to it."))
|
||||
|
||||
;; --- Source ---
|
||||
(~docs/section :title "The source: prove.sx" :id "source"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The entire constraint solver is a single SX file. Key sections: "
|
||||
(code "smt-eval") " evaluates SMT-LIB expressions. "
|
||||
(code "prove-tuples") " generates cartesian products for bounded checking. "
|
||||
@@ -248,6 +248,6 @@
|
||||
(~docs/code :src (highlight prove-source "lisp")))
|
||||
|
||||
(~docs/section :title "The translator: z3.sx" :id "z3-source"
|
||||
(p :class "text-stone-600"
|
||||
(p (~tw :tokens "text-stone-600")
|
||||
"The translator that " (code "prove.sx") " depends on. SX expressions that walk other SX expressions and emit SMT-LIB strings. Both files together: ~760 lines of s-expressions, no host language logic.")
|
||||
(~docs/code :src (highlight z3-source "lisp")))))
|
||||
|
||||
@@ -8,48 +8,48 @@
|
||||
(~docs/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 "/sx/(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."))
|
||||
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/sx/(etc.(plan.theorem-prover))" (~tw :tokens "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
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "What Already Exists" :id "existing"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Feature")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Where")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Types today")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Feature")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Where")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Types today")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitive return types")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "primitives.sx :returns")
|
||||
(td :class "px-3 py-2 text-stone-600" "\"number\", \"string\", \"boolean\", \"list\", \"dict\", \"any\""))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "IO primitive return types")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "boundary.sx :returns")
|
||||
(td :class "px-3 py-2 text-stone-600" "Same + \"dict?\", \"string?\", \"element\" — nullable types already appear"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitive param names")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "primitives.sx :params")
|
||||
(td :class "px-3 py-2 text-stone-600" "Named but untyped: " (code "(a b)") ", " (code "(&rest args)")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component param names")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "eval.sx parse-comp-params")
|
||||
(td :class "px-3 py-2 text-stone-600" (code "&key") " params, " (code "&rest") " children — named, untyped"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Runtime type predicates")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "primitives.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" (code "number?") ", " (code "string?") ", " (code "list?") ", " (code "dict?") ", " (code "nil?") ", " (code "symbol?") " — all exist"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Purity classification")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "deps.sx + boundary.py")
|
||||
(td :class "px-3 py-2 text-stone-600" "Pure vs IO — a binary type at the component level"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Property verification")
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "prove.sx")
|
||||
(td :class "px-3 py-2 text-stone-600" "Algebraic properties (commutativity, transitivity) verified by bounded model checking")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitive return types")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "primitives.sx :returns")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "\"number\", \"string\", \"boolean\", \"list\", \"dict\", \"any\""))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "IO primitive return types")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "boundary.sx :returns")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Same + \"dict?\", \"string?\", \"element\" — nullable types already appear"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitive param names")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "primitives.sx :params")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Named but untyped: " (code "(a b)") ", " (code "(&rest args)")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component param names")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "eval.sx parse-comp-params")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") (code "&key") " params, " (code "&rest") " children — named, untyped"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Runtime type predicates")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "primitives.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") (code "number?") ", " (code "string?") ", " (code "list?") ", " (code "dict?") ", " (code "nil?") ", " (code "symbol?") " — all exist"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Purity classification")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "deps.sx + boundary.py")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Pure vs IO — a binary type at the component level"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Property verification")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "prove.sx")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Algebraic properties (commutativity, transitivity) verified by bounded model checking")))))
|
||||
|
||||
(p "The foundation is solid. Primitives already have return types. Params already have names. Boundary enforcement already does structural analysis. Types extend this — they don't replace it."))
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
(p "The checker runs at registration time — after " (code "compute_all_deps") ", before serving. It walks every component's body AST and verifies that call sites match declared signatures.")
|
||||
|
||||
(~docs/subsection :title "What it checks"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Primitive calls:") " " (code "(+ \"hello\" 3)") " — " (code "+") " expects numbers, got a string. Error.")
|
||||
(li (strong "Component calls:") " " (code "(~plans/typed-sx/product-card :title 42)") " — " (code ":title") " declared as " (code "string") ", got " (code "number") ". Error.")
|
||||
(li (strong "Missing required params:") " " (code "(~plans/typed-sx/product-card :price 29.99)") " — " (code ":title") " not provided, no default. Error.")
|
||||
@@ -113,16 +113,16 @@
|
||||
(li (strong "Thread-first type flow:") " " (code "(-> items (filter active?) (map name) (join \", \"))") " — checks each step's output matches the next step's input.")))
|
||||
|
||||
(~docs/subsection :title "What it does NOT check"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "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 "/sx/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") "'s domain.")
|
||||
(li (strong "Termination.") " That's " (a :href "/sx/(etc.(plan.theorem-prover))" (~tw :tokens "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.")))
|
||||
|
||||
|
||||
(~docs/subsection :title "Inference"
|
||||
(p "Most types are inferred, not annotated. The checker knows:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Literal types: " (code "42") " → " (code "number") ", " (code "\"hi\"") " → " (code "string") ", " (code "true") " → " (code "boolean") ", " (code "nil") " → " (code "nil"))
|
||||
(li "Primitive return types: " (code "(+ a b)") " → " (code "number") ", " (code "(str x)") " → " (code "string") ", " (code "(empty? x)") " → " (code "boolean"))
|
||||
(li "Let bindings: " (code "(let ((x 42)) ...)") " → " (code "x : number"))
|
||||
@@ -138,7 +138,7 @@
|
||||
(~docs/section :title "Gradual Semantics" :id "gradual"
|
||||
(p "The type " (code "any") " is the escape hatch. It's compatible with everything — passes every check, accepts every value. Unannotated params are " (code "any") ". The return type of " (code "get") " is " (code "any") ". This means:")
|
||||
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-2"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Fully untyped code:") " All params are " (code "any") ", all returns are " (code "any") ". The checker has nothing to verify. No errors, no warnings. Exactly the same as today.")
|
||||
(li (strong "Partially typed:") " Some components annotate params, others don't. The checker verifies annotated call sites and skips untyped ones. You get value proportional to effort.")
|
||||
(li (strong "Fully typed:") " Every component, every lambda, every let binding. The checker catches every composition error. Maximum value, maximum annotation cost."))
|
||||
@@ -157,25 +157,25 @@
|
||||
(~docs/code :src (highlight ";; Example error output:\n;;\n;; TYPE ERROR in ~checkout-summary (checkout.sx:34)\n;;\n;; (str \"Total: \" (compute-total items))\n;; ^^^^^^^^^^^^^^^^^\n;; Argument 2 of `str` expects: string\n;; Got: number (from compute-total :returns number)\n;;\n;; Fix: (str \"Total: \" (str (compute-total items)))\n;;\n;;\n;; TYPE ERROR in ~reactive-islands/event-bridge/product-page (products.sx:12)\n;;\n;; (~plans/typed-sx/product-card :title product-name :price \"29.99\")\n;; ^^^^^^\n;; Keyword :price of ~plans/typed-sx/product-card expects: number\n;; Got: string (literal \"29.99\")\n;;\n;; Fix: (~plans/typed-sx/product-card :title product-name :price 29.99)" "lisp"))
|
||||
|
||||
(p "Severity levels:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Level")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "When")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Behavior")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Level")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "When")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Behavior")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-red-700" "Error")
|
||||
(td :class "px-3 py-2 text-stone-700" "Definite type mismatch: " (code "number") " where " (code "string") " expected")
|
||||
(td :class "px-3 py-2 text-stone-600" "Strict mode: startup crash. Permissive: logged warning."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-amber-700" "Warning")
|
||||
(td :class "px-3 py-2 text-stone-700" "Possible mismatch: " (code "any") " where " (code "number") " expected, unknown kwarg")
|
||||
(td :class "px-3 py-2 text-stone-600" "Logged, never crashes"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-500" "Info")
|
||||
(td :class "px-3 py-2 text-stone-700" "Annotation suggestion: frequently-called untyped component")
|
||||
(td :class "px-3 py-2 text-stone-600" "Dev mode only")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-red-700") "Error")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Definite type mismatch: " (code "number") " where " (code "string") " expected")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Strict mode: startup crash. Permissive: logged warning."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-amber-700") "Warning")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Possible mismatch: " (code "any") " where " (code "number") " expected, unknown kwarg")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Logged, never crashes"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-500") "Info")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Annotation suggestion: frequently-called untyped component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Dev mode only")))))
|
||||
|
||||
(p (code "SX_TYPE_STRICT=1") " (env var, like " (code "SX_BOUNDARY_STRICT") ") makes type errors fatal at startup. Absent = permissive. Same pattern as boundary enforcement."))
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
(~docs/code :src (highlight ";; Before: runtime error if user is nil\n(defcomp ~plans/typed-sx/greeting (&key (user : dict?))\n (h1 (str \"Hello, \" (get user \"name\"))))\n ;; ^^^ TYPE WARNING: user is dict?, get needs non-nil first arg\n\n;; After: checker enforces nil handling\n(defcomp ~plans/typed-sx/greeting (&key (user : dict?))\n (if user\n (h1 (str \"Hello, \" (get user \"name\")))\n ;; In this branch, checker narrows user to `dict` (not nil)\n (h1 \"Hello, guest\")))\n ;; No warning — nil case handled" "lisp"))
|
||||
|
||||
(p "Narrowing rules:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "(if x then else)") " — in " (code "then") ", " (code "x") " is narrowed to exclude " (code "nil") " and " (code "false"))
|
||||
(li (code "(when x body)") " — in " (code "body") ", " (code "x") " is narrowed")
|
||||
(li (code "(nil? x)") " in an if test — " (code "then") " branch: " (code "x") " is " (code "nil") ", " (code "else") " branch: " (code "x") " is non-nil")
|
||||
@@ -206,7 +206,7 @@
|
||||
(~docs/code :src (highlight ";; Definition\n(defcomp ~plans/typed-sx/product-card (&key (title : string)\n (price : number)\n (image-url : string?)\n &rest children)\n ...)\n\n;; Call site checks:\n(~plans/typed-sx/product-card :title \"Widget\" :price 29.99) ;; OK\n(~plans/typed-sx/product-card :title \"Widget\") ;; ERROR: :price required\n(~plans/typed-sx/product-card :title 42 :price 29.99) ;; ERROR: :title expects string\n(~plans/typed-sx/product-card :title \"Widget\" :price 29.99\n (p \"Description\") (p \"Details\")) ;; OK: children\n(~plans/typed-sx/product-card :titel \"Widget\" :price 29.99) ;; WARNING: :titel unknown\n ;; (did you mean :title?)" "lisp"))
|
||||
|
||||
(p "The checker walks every component call in every component body. For each call:")
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-1"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-1")
|
||||
(li "Look up the callee component in env")
|
||||
(li "Match provided keyword args against declared params")
|
||||
(li "Check each arg's inferred type against the param's declared type")
|
||||
@@ -231,35 +231,35 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Types vs Proofs" :id "types-vs-proofs"
|
||||
(p (a :href "/sx/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:")
|
||||
(p (a :href "/sx/(etc.(plan.theorem-prover))" (~tw :tokens "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"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "types.sx")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "prove.sx")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "types.sx")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "prove.sx")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Checks")
|
||||
(td :class "px-3 py-2 text-stone-700" "Composition: does A's output fit B's input?")
|
||||
(td :class "px-3 py-2 text-stone-700" "Properties: is + commutative? Is sort stable?"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Scope")
|
||||
(td :class "px-3 py-2 text-stone-700" "All component bodies, every call site")
|
||||
(td :class "px-3 py-2 text-stone-700" "Primitives only (declared in primitives.sx)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Method")
|
||||
(td :class "px-3 py-2 text-stone-700" "Type inference + checking (fast, O(n) AST walk)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Bounded model checking (exhaustive, slower)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "When")
|
||||
(td :class "px-3 py-2 text-stone-700" "Registration time (every startup)")
|
||||
(td :class "px-3 py-2 text-stone-700" "CI / on-demand (not every startup)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Catches")
|
||||
(td :class "px-3 py-2 text-stone-700" "Wrong arg type, missing param, nil misuse")
|
||||
(td :class "px-3 py-2 text-stone-700" "Algebraic law violations, edge case failures")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Checks")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Composition: does A's output fit B's input?")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Properties: is + commutative? Is sort stable?"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Scope")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "All component bodies, every call site")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Primitives only (declared in primitives.sx)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Method")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Type inference + checking (fast, O(n) AST walk)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bounded model checking (exhaustive, slower)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "When")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Registration time (every startup)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "CI / on-demand (not every startup)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Catches")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Wrong arg type, missing param, nil misuse")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Algebraic law violations, edge case failures")))))
|
||||
|
||||
(p "Types answer: " (em "\"does this code fit together?\"") " Proofs answer: " (em "\"does this code do the right thing?\"") " Both are spec modules, both bootstrapped, both run without external tools."))
|
||||
|
||||
@@ -297,32 +297,32 @@
|
||||
(~docs/code :src (highlight ";; Declare named effects\n(defeffect io) ;; Database, HTTP, file system\n(defeffect dom) ;; Browser DOM manipulation\n(defeffect async) ;; Asynchronous operations\n(defeffect state) ;; Mutable state (set!, dict-set!, append!)\n\n;; Functions declare their effects in brackets\n(define fetch-user : (-> number user) [io async]\n (fn (id) (query \"SELECT * FROM users WHERE id = $1\" id)))\n\n(define toggle-class : (-> element string nil) [dom]\n (fn (el cls) (set-attr! el :class cls)))\n\n;; Pure by default — no annotation means no effects\n(define add-prices : (-> (list-of number) number)\n (fn (prices) (reduce + 0 prices)))" "lisp")))
|
||||
|
||||
(~docs/subsection :title "What it checks"
|
||||
(ol :class "list-decimal pl-5 text-stone-700 space-y-2"
|
||||
(ol (~tw :tokens "list-decimal pl-5 text-stone-700 space-y-2")
|
||||
(li (strong "Pure functions can't call effectful ones.") " A function with no effect annotation calling " (code "fetch-user") " (which has " (code "[io async]") ") is an error. The IO leaked into pure code.")
|
||||
(li (strong "Components declare their effect ceiling.") " A " (code "[pure]") " component can only call pure functions. A " (code "[io]") " component can call IO but not DOM. This is the render-mode safety guarantee.")
|
||||
(li (strong "Render modes enforce effect sets.") " " (code "render-to-html") " (server) allows " (code "[io]") " but not " (code "[dom]") ". " (code "render-to-dom") " (browser) allows " (code "[dom]") " but not " (code "[io]") ". " (code "aser") " (wire format) allows " (code "[io]") " for evaluation but serializes the result.")
|
||||
(li (strong "Islands are the effect boundary.") " Server effects (" (code "io") ") can't cross into client island code. Client effects (" (code "dom") ") can't leak into server rendering. Currently this is convention — effects make it a proof.")))
|
||||
|
||||
(~docs/subsection :title "Three effect sets match three render modes"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Render mode")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Allowed effects")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Forbidden")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Render mode")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Allowed effects")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Forbidden")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "render-to-html")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "pure") ", " (code "io") ", " (code "async") ", " (code "state"))
|
||||
(td :class "px-3 py-2 text-red-600" (code "dom")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "render-to-dom")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "pure") ", " (code "dom") ", " (code "state"))
|
||||
(td :class "px-3 py-2 text-red-600" (code "io") ", " (code "async")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "aser (wire)")
|
||||
(td :class "px-3 py-2 text-stone-700" (code "pure") ", " (code "io") ", " (code "async") ", " (code "state"))
|
||||
(td :class "px-3 py-2 text-red-600" (code "dom"))))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "render-to-html")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "pure") ", " (code "io") ", " (code "async") ", " (code "state"))
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") (code "dom")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "render-to-dom")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "pure") ", " (code "dom") ", " (code "state"))
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") (code "io") ", " (code "async")))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "aser (wire)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") (code "pure") ", " (code "io") ", " (code "async") ", " (code "state"))
|
||||
(td (~tw :tokens "px-3 py-2 text-red-600") (code "dom"))))))
|
||||
|
||||
(p "This is exactly the information " (code "deps.sx") " already computes — which components have IO refs. Effects promote it from a runtime classification to a static type-level property. Pure components get an ironclad guarantee: memoize, cache, SSR anywhere, serialize for client — provably safe."))
|
||||
|
||||
@@ -338,22 +338,22 @@
|
||||
|
||||
(~docs/subsection :title "Relationship to deps.sx and boundary.sx"
|
||||
(p "Effects don't replace the existing systems — they formalize them:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "boundary.sx") " declares which primitives are IO. Effects declare which " (em "functions") " use IO.")
|
||||
(li (code "deps.sx") " computes transitive IO refs at registration time. Effects check them at type-check time — earlier, with better error messages.")
|
||||
(li "The boundary is still the source of truth for " (em "what is IO") ". Effects are the enforcement mechanism for " (em "who can use it") "."))
|
||||
(p "Long term, " (code "deps.sx") "'s IO classification can be derived from effect annotations. In the short term, both coexist — effects are checked, deps are computed, both must agree."))
|
||||
|
||||
(~docs/subsection :title "What it does NOT do"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (strong "No algebraic effect handlers.") " You can't intercept and resume effects. This would require delimited continuations in every bootstrapper target — massive complexity for marginal UI benefit.")
|
||||
(li (strong "No effect polymorphism.") " You can't write a function generic over effects (" (code "forall e. (-> a [e] b)") "). This needs higher-kinded effect types — the same complexity as type classes.")
|
||||
(li (strong "No effect inference.") " Effects are declared, not inferred. Inference would require whole-program analysis and produce confusing error messages.")
|
||||
(li (strong "No runtime cost.") " Effects are erased after checking. The bootstrapped code has zero overhead. This is purely a static guarantee."))
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "The door stays open.")
|
||||
(p :class "text-violet-800" "Static effect checking is the pragmatic middle — 80% of the benefit of a full effect system, 20% of the complexity. If SX ever needs algebraic handlers (e.g. for suspense, or cooperative scheduling beyond what " (code "shift") "/" (code "reset") " provides), the annotation syntax is already in place. Add handlers later without changing any existing code."))))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "The door stays open.")
|
||||
(p (~tw :tokens "text-violet-800") "Static effect checking is the pragmatic middle — 80% of the benefit of a full effect system, 20% of the complexity. If SX ever needs algebraic handlers (e.g. for suspense, or cooperative scheduling beyond what " (code "shift") "/" (code "reset") " provides), the annotation syntax is already in place. Add handlers later without changing any existing code."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Implementation
|
||||
@@ -363,7 +363,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 1: Type Registry (done)"
|
||||
(p "Build the type registry from existing declarations.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Parse " (code ":returns") " from " (code "primitives.sx") " and " (code "boundary.sx") " into a type map: " (code "primitive-name → return-type"))
|
||||
(li "Parse " (code ":params") " declarations into param type maps (currently untyped — default to " (code "any") ")")
|
||||
(li "Compute component signatures from " (code "parse-comp-params") " + any type annotations")
|
||||
@@ -371,7 +371,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 2: Type Inference Engine (done)"
|
||||
(p "Walk AST, infer types bottom-up.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Literals → concrete types")
|
||||
(li "Primitive calls → look up return type in registry")
|
||||
(li "Component calls → " (code "element"))
|
||||
@@ -382,7 +382,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 3: Type Checker (done)"
|
||||
(p "Compare inferred types at call sites against declared types.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li "Subtype check: " (code "number") " <: " (code "any") ", " (code "string") " <: " (code "string?") ", " (code "nil") " <: " (code "string?"))
|
||||
(li "Error on definite mismatch: " (code "number") " vs " (code "string"))
|
||||
(li "Warn on possible mismatch: " (code "any") " vs " (code "number") " (might work, might not)")
|
||||
@@ -390,7 +390,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 4: Annotation Parsing (done)"
|
||||
(p "Extend " (code "parse-comp-params") " and " (code "sf-defcomp") " to recognize type annotations.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "(name : type)") " in param lists → extract type, store in component metadata")
|
||||
(li (code ":returns type") " in lambda/fn bodies → store as declared return type")
|
||||
(li "Backward compatible: unannotated params remain " (code "any"))))
|
||||
@@ -402,7 +402,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 6: User-Defined Types (deftype)"
|
||||
(p "Extend the type system with named user-defined types.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "deftype") " special form — parsed by evaluator, stored in type registry")
|
||||
(li "Type aliases: " (code "(deftype price number)") " → transparent substitution")
|
||||
(li "Union types: " (code "(deftype renderable (union string number nil))") " → checked via " (code "subtype?"))
|
||||
@@ -414,7 +414,7 @@
|
||||
|
||||
(~docs/subsection :title "Phase 7: Static Effect System"
|
||||
(p "Add effect annotations and static checking. No handlers, no runtime cost.")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (code "defeffect") " declaration form — registers named effects: " (code "io") ", " (code "dom") ", " (code "async") ", " (code "state"))
|
||||
(li "Effect annotation syntax: " (code "[io async]") " after function type or on " (code "defcomp"))
|
||||
(li "Extend " (code "check-body-walk") " — verify called functions' effects are subset of caller's declared effects")
|
||||
@@ -432,48 +432,48 @@
|
||||
(~docs/section :title "Spec Module" :id "spec-module"
|
||||
(p (code "types.sx") " — the type checker, written in SX, bootstrapped to every host.")
|
||||
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Function")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Function")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "infer-type")
|
||||
(td :class "px-3 py-2 text-stone-700" "Infer the type of an AST node in a given type environment"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-call")
|
||||
(td :class "px-3 py-2 text-stone-700" "Check a function/component call against declared signature"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-component")
|
||||
(td :class "px-3 py-2 text-stone-700" "Type-check an entire component body"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-all")
|
||||
(td :class "px-3 py-2 text-stone-700" "Check all registered components, return error/warning list"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "subtype?")
|
||||
(td :class "px-3 py-2 text-stone-700" "Is type A a subtype of type B?"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "narrow-type")
|
||||
(td :class "px-3 py-2 text-stone-700" "Narrow a type based on a predicate test (nil?, string?, etc)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "type-union")
|
||||
(td :class "px-3 py-2 text-stone-700" "Compute the union of two types"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "parse-type-annotation")
|
||||
(td :class "px-3 py-2 text-stone-700" "Parse a type expression from annotation syntax"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "build-type-registry")
|
||||
(td :class "px-3 py-2 text-stone-700" "Build type map from primitives.sx + boundary.sx declarations"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-primitive-call")
|
||||
(td :class "px-3 py-2 text-stone-700" "Check a primitive call against declared param types (positional + rest)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "resolve-type")
|
||||
(td :class "px-3 py-2 text-stone-700" "Resolve a named type through the deftype registry (Phase 6)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700" "check-effects")
|
||||
(td :class "px-3 py-2 text-stone-700" "Verify effect annotations: callee effects ⊆ caller effects (Phase 7)")))))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "infer-type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Infer the type of an AST node in a given type environment"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-call")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Check a function/component call against declared signature"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-component")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Type-check an entire component body"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-all")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Check all registered components, return error/warning list"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "subtype?")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Is type A a subtype of type B?"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "narrow-type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Narrow a type based on a predicate test (nil?, string?, etc)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "type-union")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Compute the union of two types"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "parse-type-annotation")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Parse a type expression from annotation syntax"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "build-type-registry")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Build type map from primitives.sx + boundary.sx declarations"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-primitive-call")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Check a primitive call against declared param types (positional + rest)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "resolve-type")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Resolve a named type through the deftype registry (Phase 6)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "check-effects")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Verify effect annotations: callee effects ⊆ caller effects (Phase 7)")))))
|
||||
|
||||
(p "The checker is a growing spec module — currently ~650 lines of SX. It's an AST walk with a type environment, structurally similar to " (code "deps.sx") " (which walks ASTs to find IO refs) and " (code "prove.sx") " (which walks ASTs to generate verification conditions). Same pattern, different question."))
|
||||
|
||||
@@ -482,15 +482,15 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Relationships" :id "relationships"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li (a :href "/sx/(etc.(plan.theorem-prover))" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
|
||||
(li (a :href "/sx/(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 "/sx/(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 "/sx/(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."))
|
||||
(ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
|
||||
(li (a :href "/sx/(etc.(plan.theorem-prover))" (~tw :tokens "text-violet-700 underline") "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
|
||||
(li (a :href "/sx/(etc.(plan.content-addressed-components))" (~tw :tokens "text-violet-700 underline") "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.")
|
||||
(li (a :href "/sx/(etc.(plan.environment-images))" (~tw :tokens "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 "/sx/(etc.(plan.runtime-slicing))" (~tw :tokens "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."))
|
||||
(div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3 mt-2")
|
||||
(p (~tw :tokens "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."))
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mt-4"
|
||||
(p :class "text-violet-900 font-medium" "Why not a Haskell host?")
|
||||
(p :class "text-violet-800" "A Haskell SX host would type-check the " (em "host") " code (the evaluator, renderer, parser). But it can't type-check " (em "SX programs") " — those are dynamically typed values passing through " (code "SxVal") ". " (code "types.sx") " checks SX programs directly, on every host. One spec, all hosts benefit. The type system lives where it matters — in the language, not in any particular implementation of it."))))))
|
||||
(div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 mt-4")
|
||||
(p (~tw :tokens "text-violet-900 font-medium") "Why not a Haskell host?")
|
||||
(p (~tw :tokens "text-violet-800") "A Haskell SX host would type-check the " (em "host") " code (the evaluator, renderer, parser). But it can't type-check " (em "SX programs") " — those are dynamically typed values passing through " (code "SxVal") ". " (code "types.sx") " checks SX programs directly, on every host. One spec, all hosts benefit. The type system lives where it matters — in the language, not in any particular implementation of it."))))))
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Why" :id "why"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "Wire size") " — bytecode is far more compact than source text. No redundant whitespace, no comments, no repeated symbol names. A component bundle that's 40KB of SX source might be 8KB of bytecode.")
|
||||
(li (strong "No parse overhead") " — the browser currently parses every SX source string (tokenize → AST → eval). Bytecode skips parsing entirely.")
|
||||
(li (strong "Eval performance") " — a Rust VM with a tight dispatch loop is significantly faster than tree-walking in JavaScript. Matters for compute-heavy islands, large list rendering, complex CSSX calculations.")
|
||||
@@ -29,14 +29,14 @@
|
||||
(~docs/section :title "Architecture" :id "architecture"
|
||||
(p "Three new layers, all specced in " (code ".sx") " and bootstrapped:")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "1. Bytecode format — bytecode.sx")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "1. Bytecode format — bytecode.sx")
|
||||
(p "A spec for the bytecode instruction set. Stack-based VM (simpler than register-based, natural fit for s-expressions). Instructions:")
|
||||
(~docs/code :src (highlight ";; Core instructions\nPUSH_CONST idx ;; push constant from pool\nPUSH_NIL ;; push nil\nPUSH_TRUE / PUSH_FALSE\nLOOKUP idx ;; look up symbol by index\nSET idx ;; define/set symbol\nCALL n ;; call top-of-stack with n args\nTAIL_CALL n ;; tail call (TCO)\nRETURN\nJUMP offset ;; unconditional jump\nJUMP_IF_FALSE offset ;; conditional jump\nMAKE_LAMBDA idx n_params ;; create closure\nMAKE_LIST n ;; collect n stack values into list\nMAKE_DICT n ;; collect 2n stack values into dict\nPOP ;; discard top\nDUP ;; duplicate top" "lisp"))
|
||||
(p "Bytecode modules contain: a " (strong "constant pool") " (strings, numbers, symbols), a " (strong "code section") " (instruction bytes), and a " (strong "metadata section") " (source maps, component/island declarations for the host to register).")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "2. Compiler — compile.sx")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "2. Compiler — compile.sx")
|
||||
(p "An SX-to-bytecode compiler, " (strong "written in SX") ". Takes parsed AST, emits bytecode modules. Handles:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Macro expansion") " — all macros expanded at compile time. The VM never sees macros.")
|
||||
(li (strong "Constant folding") " — pure expressions with known values computed at compile time.")
|
||||
(li (strong "Closure analysis") " — determines free variables for each lambda, emits efficient capture instructions.")
|
||||
@@ -44,9 +44,9 @@
|
||||
(li (strong "Component metadata") " — defcomp/defisland declarations are extracted and stored in the module metadata, so the host can register them without evaluating the body."))
|
||||
(p "Bootstrapped to Python (server-side compilation) and Rust (if self-compilation is needed).")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "3. VM — bootstrap_rs.py → Rust/WASM")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "3. VM — bootstrap_rs.py → Rust/WASM")
|
||||
(p "A Rust implementation of the SX platform interface. The bootstrapper (" (code "bootstrap_rs.py") ") translates the spec to Rust source, which compiles to both:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Native binary") " — for server-side evaluation (replaces Python evaluators entirely)")
|
||||
(li (strong "WASM module") " — for browser-side evaluation (replaces sx-browser.js)")))
|
||||
|
||||
@@ -57,11 +57,11 @@
|
||||
(~docs/section :title "DOM Interop" :id "dom-interop"
|
||||
(p "The main engineering challenge. Every DOM operation crosses the WASM↔JS boundary. Two strategies:")
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Strategy A: Direct calls")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Strategy A: Direct calls")
|
||||
(p "Each DOM operation (" (code "createElement") ", " (code "setAttribute") ", " (code "appendChild") ") is a separate WASM→JS call. Simple, works, but ~50ns overhead per call. For a page with 1,000 DOM operations, that's ~50μs — negligible.")
|
||||
(~docs/code :src (highlight "// JS side — imported by WASM\nfunction domCreateElement(tag_ptr, tag_len) {\n const tag = readString(tag_ptr, tag_len);\n return storeHandle(document.createElement(tag));\n}\n\n// Rust side\nextern \"C\" { fn dom_create_element(tag: *const u8, len: u32) -> u32; }" "javascript"))
|
||||
|
||||
(h4 :class "font-semibold mt-4 mb-2" "Strategy B: Command buffer")
|
||||
(h4 (~tw :tokens "font-semibold mt-4 mb-2") "Strategy B: Command buffer")
|
||||
(p "Batch DOM operations in WASM memory as a command buffer. Flush to JS in one call. JS walks the buffer and applies all operations. Fewer boundary crossings, but more complex.")
|
||||
(~docs/code :src (highlight ";; Command buffer format (in shared WASM memory)\n;; [CREATE_ELEMENT, tag_idx, handle_out]\n;; [SET_ATTR, handle, key_idx, val_idx]\n;; [APPEND_CHILD, parent_handle, child_handle]\n;; [SET_TEXT, handle, text_idx]\n;; Then: (flush-dom-commands)" "lisp"))
|
||||
(p "Strategy A is simpler and sufficient for SX workloads. Strategy B is an optimisation if profiling shows the boundary crossing matters. " (strong "Start with A, measure, switch to B only if needed.")))
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
(~docs/section :title "String Handling" :id "strings"
|
||||
(p "WASM has no native string type. Strings must cross the boundary via shared " (code "ArrayBuffer") " memory. Options:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Copy on crossing") " — encode to UTF-8 in WASM linear memory, JS reads via " (code "TextDecoder") ". Simple, safe, ~1μs per string.")
|
||||
(li (strong "String interning") " — the constant pool already interns all string literals. Assign each a numeric ID. DOM operations reference strings by ID. JS maintains a parallel string table. Strings never cross the boundary — only IDs do.")
|
||||
(li (strong "Hybrid") " — intern constants (attribute names, tag names, class names), copy dynamic strings (computed CSS values, interpolated text)."))
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
(~docs/section :title "Memory & Closures" :id "memory"
|
||||
(p "SX values that the VM must manage:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "Closures") " — lambda captures free variables. Rust: " (code "Rc<Closure>") " with captured env as " (code "Vec<Value>") ".")
|
||||
(li (strong "Signals") " — reference-counted mutable cells with subscriber lists. Subscribers hold weak references to computed nodes to avoid cycles.")
|
||||
(li (strong "Lists/Dicts") " — immutable by convention (SX doesn't mutate collections). Arena-allocate per evaluation, free the arena when done.")
|
||||
@@ -97,33 +97,33 @@
|
||||
|
||||
(~docs/section :title "What Gets Compiled" :id "compilation"
|
||||
(p "Not everything needs bytecode. The compilation boundary follows the existing server/client split:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Content")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Format")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Why")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Content")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Format")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Why")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Component definitions")
|
||||
(td :class "px-3 py-2 text-stone-700" "Bytecode")
|
||||
(td :class "px-3 py-2 text-stone-600" "Evaluated on every page load, benefits from fast dispatch"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Client library (@client files)")
|
||||
(td :class "px-3 py-2 text-stone-700" "Bytecode")
|
||||
(td :class "px-3 py-2 text-stone-600" "CSSX functions, colour computation — pure code that runs client-side"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Page content (SX wire responses)")
|
||||
(td :class "px-3 py-2 text-stone-700" "SX source or bytecode")
|
||||
(td :class "px-3 py-2 text-stone-600" "Wire responses are small, parse overhead minimal. Bytecode optional."))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Macros")
|
||||
(td :class "px-3 py-2 text-stone-700" "Expanded at compile time")
|
||||
(td :class "px-3 py-2 text-stone-600" "VM never sees macros — they're pure compile-time constructs"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Component definitions")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Evaluated on every page load, benefits from fast dispatch"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client library (@client files)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "CSSX functions, colour computation — pure code that runs client-side"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Page content (SX wire responses)")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX source or bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Wire responses are small, parse overhead minimal. Bytecode optional."))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Macros")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Expanded at compile time")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "VM never sees macros — they're pure compile-time constructs"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "Server-affinity components")
|
||||
(td :class "px-3 py-2 text-stone-700" "Not compiled")
|
||||
(td :class "px-3 py-2 text-stone-600" "Expanded server-side, never sent to client"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Server-affinity components")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Not compiled")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Expanded server-side, never sent to client"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Bytecode vs direct WASM compilation
|
||||
@@ -131,37 +131,37 @@
|
||||
|
||||
(~docs/section :title "Bytecode VM vs Direct WASM Compilation" :id "vm-vs-direct"
|
||||
(p "Two paths to WASM. The choice matters:")
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Bytecode VM in WASM")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Compile SX → WASM directly")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Bytecode VM in WASM")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Compile SX → WASM directly")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Complexity")
|
||||
(td :class "px-3 py-2 text-stone-700" "Standard VM design — proven pattern")
|
||||
(td :class "px-3 py-2 text-stone-700" "Full compiler backend (SSA, register alloc, WASM codegen)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Dynamic loading")
|
||||
(td :class "px-3 py-2 text-stone-700" "Trivial — load bytecode module, eval")
|
||||
(td :class "px-3 py-2 text-stone-700" "Hard — must instantiate new WASM module per chunk"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "eval / REPL")
|
||||
(td :class "px-3 py-2 text-stone-700" "Works — compile + eval at runtime")
|
||||
(td :class "px-3 py-2 text-stone-700" "Impossible without bundling a compiler"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Performance")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fast — WASM dispatch loop, no JS overhead")
|
||||
(td :class "px-3 py-2 text-stone-700" "Fastest — native WASM speed, no dispatch"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Module size")
|
||||
(td :class "px-3 py-2 text-stone-700" "One VM module (~100KB) + bytecode per page")
|
||||
(td :class "px-3 py-2 text-stone-700" "Per-page WASM modules, each self-contained"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Complexity")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Standard VM design — proven pattern")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Full compiler backend (SSA, register alloc, WASM codegen)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Dynamic loading")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Trivial — load bytecode module, eval")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Hard — must instantiate new WASM module per chunk"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "eval / REPL")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Works — compile + eval at runtime")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Impossible without bundling a compiler"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Performance")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fast — WASM dispatch loop, no JS overhead")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Fastest — native WASM speed, no dispatch"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Module size")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "One VM module (~100KB) + bytecode per page")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Per-page WASM modules, each self-contained"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 font-semibold text-stone-700" "Debugging")
|
||||
(td :class "px-3 py-2 text-stone-700" "Source maps over bytecode")
|
||||
(td :class "px-3 py-2 text-stone-700" "DWARF debug info in WASM")))))
|
||||
(td (~tw :tokens "px-3 py-2 font-semibold text-stone-700") "Debugging")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Source maps over bytecode")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "DWARF debug info in WASM")))))
|
||||
(p (strong "Bytecode VM is the right choice.") " SX needs dynamic loading (HTMX responses inject new components), runtime eval (islands, reactive updates), and incremental compilation (page-by-page). Direct WASM compilation is better for static, ahead-of-time scenarios — not for a live hypermedia system."))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -172,7 +172,7 @@
|
||||
(p "The key insight: this is " (strong "not a replacement") " for the JS evaluator. It's " (strong "another compilation target from the same spec") ". The existing bootstrapper pipeline already proves this pattern:")
|
||||
(~docs/code :src (highlight "eval.sx ──→ bootstrap_js.py ──→ sx-ref.js (browser, JS eval)\n ──→ bootstrap_py.py ──→ sx_ref.py (server, Python eval)\n ──→ bootstrap_rs.py ──→ sx-vm.wasm (browser, WASM eval) ← new" "text"))
|
||||
(p "All three outputs have identical semantics because they're compiled from the same source. The choice of which to use is a " (strong "deployment decision") ", not an architectural one:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li (strong "JS-only") " — current default. Works everywhere. Zero WASM dependency. Ship sx-browser.js + SX source text.")
|
||||
(li (strong "WASM-only") " — maximum performance. Ship sx-vm.wasm + bytecode. Requires WASM support (99%+ of browsers).")
|
||||
(li (strong "Progressive") " — try WASM, fall back to JS. Ship both. The server sends bytecode in a " (code "<script type=\"text/sx-bytecode\">") " tag and source in " (code "<script type=\"text/sx\">") ". The boot script picks whichever runtime loaded.")
|
||||
@@ -185,48 +185,48 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Implementation Phases" :id "phases"
|
||||
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||
(table :class "w-full text-left text-sm"
|
||||
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Phase")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "What")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Deliverable")))
|
||||
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200 mb-4")
|
||||
(table (~tw :tokens "w-full text-left text-sm")
|
||||
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Phase")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "What")
|
||||
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Deliverable")))
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "1")
|
||||
(td :class "px-3 py-2 text-stone-700" "Spec the bytecode format in bytecode.sx. Instruction set, constant pool layout, module structure, encoding.")
|
||||
(td :class "px-3 py-2 text-stone-600" "bytecode.sx — format spec + serializer/deserializer"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "2")
|
||||
(td :class "px-3 py-2 text-stone-700" "Write the compiler in SX. AST → bytecode. Macro expansion, constant folding, tail call detection, closure analysis.")
|
||||
(td :class "px-3 py-2 text-stone-600" "compile.sx — bootstrapped to Python for server-side compilation"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "3")
|
||||
(td :class "px-3 py-2 text-stone-700" "Write bootstrap_rs.py — Rust bootstrapper. Translates spec to Rust source implementing the platform interface.")
|
||||
(td :class "px-3 py-2 text-stone-600" "bootstrap_rs.py + generated Rust crate"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "4")
|
||||
(td :class "px-3 py-2 text-stone-700" "Implement bytecode VM in Rust. Dispatch loop, value representation, closure/env model, GC strategy.")
|
||||
(td :class "px-3 py-2 text-stone-600" "sx-vm crate — native binary + WASM target"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "5")
|
||||
(td :class "px-3 py-2 text-stone-700" "JS bindings for DOM. WASM imports for createElement, setAttribute, appendChild, event listeners. Handle table.")
|
||||
(td :class "px-3 py-2 text-stone-600" "sx-vm-dom.js — JS glue layer (~2KB)"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "6")
|
||||
(td :class "px-3 py-2 text-stone-700" "Server-side bytecode compilation pipeline. Component registration emits bytecode alongside source. Bytecode hash for caching.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Bytecode in data-components script tags, fallback to source"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "1")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Spec the bytecode format in bytecode.sx. Instruction set, constant pool layout, module structure, encoding.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bytecode.sx — format spec + serializer/deserializer"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "2")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Write the compiler in SX. AST → bytecode. Macro expansion, constant folding, tail call detection, closure analysis.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "compile.sx — bootstrapped to Python for server-side compilation"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "3")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Write bootstrap_rs.py — Rust bootstrapper. Translates spec to Rust source implementing the platform interface.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "bootstrap_rs.py + generated Rust crate"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "4")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Implement bytecode VM in Rust. Dispatch loop, value representation, closure/env model, GC strategy.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "sx-vm crate — native binary + WASM target"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "5")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS bindings for DOM. WASM imports for createElement, setAttribute, appendChild, event listeners. Handle table.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "sx-vm-dom.js — JS glue layer (~2KB)"))
|
||||
(tr (~tw :tokens "border-b border-stone-100")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "6")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Server-side bytecode compilation pipeline. Component registration emits bytecode alongside source. Bytecode hash for caching.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Bytecode in data-components script tags, fallback to source"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700" "7")
|
||||
(td :class "px-3 py-2 text-stone-700" "Shadow-compare: run JS evaluator and WASM VM in parallel, assert identical DOM output on every page render.")
|
||||
(td :class "px-3 py-2 text-stone-600" "Confidence to switch over"))))))
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "7")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-700") "Shadow-compare: run JS evaluator and WASM VM in parallel, assert identical DOM output on every page render.")
|
||||
(td (~tw :tokens "px-3 py-2 text-stone-600") "Confidence to switch over"))))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Interaction with existing plans
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Interaction with Other Plans" :id "interactions"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "Async Eval Convergence") " — must complete first. The spec must be the single evaluator before we add another target. Otherwise we'd be bootstrapping a fork.")
|
||||
(li (strong "Runtime Slicing") " — the WASM module can be tiered just like the JS runtime. L0 hypermedia needs no VM at all (pure HTML). L1 DOM ops needs a minimal VM. L2 islands needs signals. The WASM module should be tree-shakeable.")
|
||||
(li (strong "Content-Addressed Components") " — bytecode modules are ideal for content addressing. Deterministic compilation means the same SX source always produces the same bytecode → same CID. Fetch bytecode by CID from IPFS.")
|
||||
@@ -238,7 +238,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~docs/section :title "Principles" :id "principles"
|
||||
(ul :class "list-disc list-inside space-y-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2")
|
||||
(li (strong "The spec remains the single source of truth.") " The bytecode format, compiler, and VM semantics are all specced in .sx. The Rust VM is just another host, like Python and JavaScript.")
|
||||
(li (strong "Bytecode is an optimisation, not a requirement.") " SX source text remains a valid wire format. The system degrades gracefully — if WASM isn't available, fall back to the JS evaluator. Progressive enhancement.")
|
||||
(li (strong "The VM is dumb, the compiler is smart.") " Macro expansion, constant folding, tail call detection — all done at compile time. The VM is a simple dispatch loop. This keeps the WASM module small and the compilation fast.")
|
||||
@@ -251,7 +251,7 @@
|
||||
|
||||
(~docs/section :title "Outcome" :id "outcome"
|
||||
(p "After completion:")
|
||||
(ul :class "list-disc list-inside space-y-2 mt-2"
|
||||
(ul (~tw :tokens "list-disc list-inside space-y-2 mt-2")
|
||||
(li "SX compiles to four targets: JavaScript, Python, Rust (native), Rust (WASM)")
|
||||
(li "Client wire format is ~5x smaller (bytecode vs source text)")
|
||||
(li "No parsing overhead on the client — bytecode loads directly into the VM")
|
||||
|
||||
Reference in New Issue
Block a user