Mark Phase 4 complete in sx-docs, link to data-test demo
- Phase 4 section: green "Complete" badge with live data test link - Documents architecture: resolve-page-data, server endpoint, data cache - Lists files, 30 unit tests, verification steps - Renumber: Phase 5 = async continuations, Phase 6 = streaming, Phase 7 = full iso - Update Phase 3 to note :data pages now also client-routable - Add data-test to "pages that fall through" list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
111
sx/sx/plans.sx
111
sx/sx/plans.sx
@@ -1455,7 +1455,7 @@
|
||||
(p (code "handle-popstate") " also tries client routing before server fetch on back/forward."))))
|
||||
|
||||
(~doc-subsection :title "What becomes client-routable"
|
||||
(p "Pages WITHOUT " (code ":data") " that have pure content expressions — most of this docs app:")
|
||||
(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"
|
||||
(li (code "/") ", " (code "/docs/") ", " (code "/docs/<slug>") " (most slugs), " (code "/protocols/") ", " (code "/protocols/<slug>"))
|
||||
(li (code "/examples/") ", " (code "/examples/<slug>") ", " (code "/essays/") ", " (code "/essays/<slug>"))
|
||||
@@ -1465,7 +1465,8 @@
|
||||
(li (code "/docs/primitives") " and " (code "/docs/special-forms") " (call " (code "primitives-data") " / " (code "special-forms-data") " helpers)")
|
||||
(li (code "/reference/<slug>") " (has " (code ":data (reference-data slug)") ")")
|
||||
(li (code "/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")")
|
||||
(li (code "/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")")))
|
||||
(li (code "/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")")
|
||||
(li (code "/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/isomorphism/data-test" :class "text-violet-700 underline" "Phase 4 demo") ")")))
|
||||
|
||||
(~doc-subsection :title "Try-first/fallback design"
|
||||
(p "Client routing uses a try-first approach: attempt local evaluation in a try/catch, fall back to server fetch on any failure. This avoids needing perfect static analysis of content expressions — if a content expression calls a page helper the client doesn't have, the eval throws, and the server handles it transparently.")
|
||||
@@ -1494,19 +1495,71 @@
|
||||
|
||||
(~doc-section :title "Phase 4: Client Async & IO Bridge" :id "phase-4"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
(p :class "text-violet-800" "Client evaluates IO primitives by mapping them to server REST calls. Same SX code, different transport. (query \"market\" \"products\" :ids \"1,2,3\") on server → DB; on client → fetch(\"/internal/data/products?ids=1,2,3\")."))
|
||||
(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 "/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."))
|
||||
|
||||
(~doc-subsection :title "Approach"
|
||||
(~doc-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
|
||||
(h4 :class "font-semibold text-stone-700" "1. Async client evaluator")
|
||||
(p "Two possible mechanisms:")
|
||||
(h4 :class "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:")
|
||||
(~doc-code :code (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")
|
||||
(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")
|
||||
(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"
|
||||
(li (strong "Promise-based: ") "evalExpr returns value or Promise; rendering awaits")
|
||||
(li (strong "Continuation-based: ") "use existing shift/reset to suspend on IO, resume when data arrives (architecturally cleaner, leverages existing spec)")))
|
||||
(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 "/isomorphism/data-test" :class "text-violet-700 underline" "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch)."))))
|
||||
|
||||
(~doc-subsection :title "Files"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
|
||||
(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()")
|
||||
(li "shared/sx/helpers.py — deps for :data pages in page registry")
|
||||
(li "sx/sx/data-test.sx — test component")
|
||||
(li "shared/sx/tests/test_page_data.py — 30 unit tests")))
|
||||
|
||||
(~doc-subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "30 unit tests: serialize roundtrip, kebab-case, deps, full pipeline simulation, cache TTL")
|
||||
(li "Console: " (code "sx:route client+data") " on first visit, " (code "sx:route client+cache") " on return within 30s")
|
||||
(li (a :href "/isomorphism/data-test" :class "text-violet-700 underline" "Live data test page") " exercises the full pipeline with server time + pipeline steps")
|
||||
(li "append! and dict-set! registered as proper primitives in spec + both hosts"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 5
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 5: Async Continuations & Inline IO" :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" "Components call IO primitives directly in their body. The evaluator suspends mid-evaluation via async-aware continuations, fetches data, resumes. Same component source works on both server (Python async/await) and client (continuation-based suspension)."))
|
||||
|
||||
(~doc-subsection :title "The Problem"
|
||||
(p "The existing shift/reset continuations extension is synchronous (throw/catch). Client-side IO via fetch() returns a Promise — you can't throw-catch across an async boundary. The evaluator needs Promise-aware continuations or a CPS transform."))
|
||||
|
||||
(~doc-subsection :title "Approach"
|
||||
(div :class "space-y-4"
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "1. Async-aware shift/reset")
|
||||
(p "Extend the continuations extension: sfShift captures the continuation and returns a Promise, sfReset awaits Promise results in the trampoline. Continuation resume feeds the fetched value back into evaluation."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "2. IO primitive bridge")
|
||||
@@ -1518,27 +1571,17 @@
|
||||
(li "current-user → cached from initial page load")))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Client data cache")
|
||||
(p "Keyed by (service, query, params-hash), configurable TTL, server can invalidate via SX-Invalidate header."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Optimistic updates")
|
||||
(p "Extend existing apply-optimistic/revert-optimistic in engine.sx from DOM-level to data-level."))))
|
||||
(h4 :class "font-semibold text-stone-700" "3. CPS transform option")
|
||||
(p "Alternative: transform the evaluator to continuation-passing style. Every eval step takes a continuation argument. IO primitives call the continuation after fetch resolves. Architecturally cleaner but requires deeper changes."))))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 2 (IO affinity), Phase 3 (routing for when to trigger IO)."))
|
||||
|
||||
(~doc-subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(li "Client (query ...) returns identical data to server-side")
|
||||
(li "Data cache prevents redundant fetches")
|
||||
(li "Same component source → identical output on either side"))))
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 4 (data endpoint infrastructure).")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 5
|
||||
;; Phase 6
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 5: Streaming & Suspense" :id "phase-5"
|
||||
(~doc-section :title "Phase 6: Streaming & Suspense" :id "phase-6"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
@@ -1568,13 +1611,13 @@
|
||||
(p "Above-fold content resolves first. All IO starts concurrently (asyncio.create_task), results flushed in priority order."))))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 4 (client async for filling suspended subtrees), Phase 2 (IO analysis for priority).")))
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 5 (async continuations for filling suspended subtrees), Phase 2 (IO analysis for priority).")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
;; Phase 6
|
||||
;; Phase 7
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~doc-section :title "Phase 6: Full Isomorphism" :id "phase-6"
|
||||
(~doc-section :title "Phase 7: Full Isomorphism" :id "phase-7"
|
||||
|
||||
(div :class "rounded border border-violet-200 bg-violet-50 p-4 mb-4"
|
||||
(p :class "text-violet-900 font-medium" "What it enables")
|
||||
@@ -1593,15 +1636,19 @@
|
||||
(p "Default: auto (runtime decides from IO analysis)."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "3. Offline data layer")
|
||||
(h4 :class "font-semibold text-stone-700" "3. Optimistic data updates")
|
||||
(p "Extend existing apply-optimistic/revert-optimistic in engine.sx from DOM-level to data-level. Client updates cached data optimistically, sends mutation to server, reverts on rejection."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Offline data layer")
|
||||
(p "Service Worker intercepts /internal/data/ requests, serves from IndexedDB when offline, syncs when back online."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "4. Isomorphic testing")
|
||||
(h4 :class "font-semibold text-stone-700" "5. Isomorphic testing")
|
||||
(p "Evaluate same component on Python and JS, compare output. Extends existing test_sx_ref.py cross-evaluator comparison."))
|
||||
|
||||
(div
|
||||
(h4 :class "font-semibold text-stone-700" "5. Universal page descriptor")
|
||||
(h4 :class "font-semibold text-stone-700" "6. Universal page descriptor")
|
||||
(p "defpage is portable: server executes via execute_page(), client executes via route match → fetch data → eval content → render DOM. Same descriptor, different execution environment."))))
|
||||
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
|
||||
@@ -1618,7 +1665,7 @@
|
||||
(li "Phase 1: \"Unknown component\" includes which page expected it and what bundle was sent")
|
||||
(li "Phase 2: Server logs which components expanded server-side vs sent to client")
|
||||
(li "Phase 3: Client route failures include unmatched path and available routes")
|
||||
(li "Phase 4: Client IO errors include query name, params, server response")
|
||||
(li "Phase 4: Client data errors include page name, params, server response status")
|
||||
(li "Source location tracking in parser → propagate through eval → include in error messages")))
|
||||
|
||||
(~doc-subsection :title "Backward Compatibility"
|
||||
|
||||
Reference in New Issue
Block a user