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