Files
rose-ash/sx/sx/protocols.sx
giles b0920a1121 Rename all 1,169 components to path-based names with namespace support
Component names now reflect filesystem location using / as path separator
and : as namespace separator for shared components:
  ~sx-header → ~layouts/header
  ~layout-app-body → ~shared:layout/app-body
  ~blog-admin-dashboard → ~admin/dashboard

209 files, 4,941 replacements across all services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:00:12 +00:00

99 lines
7.4 KiB
Plaintext

;; Protocol documentation pages — fully self-contained
(defcomp ~protocols/wire-format-content ()
(~docs/page :title "Wire Format"
(~docs/section :title "The text/sx content type" :id "content-type"
(p :class "text-stone-600"
"sx responses use content type text/sx. The body is s-expression source code. The client parses and evaluates it, then renders the result into the DOM.")
(~docs/code :code (highlight "HTTP/1.1 200 OK\nContent-Type: text/sx\nSX-Css-Hash: a1b2c3d4\n\n(div :class \"p-4\"\n (~card :title \"Hello\"))" "bash")))
(~docs/section :title "Request lifecycle" :id "lifecycle"
(p :class "text-stone-600"
"1. User interacts with an element that has sx-get/sx-post/etc.")
(p :class "text-stone-600"
"2. sx.js fires sx:beforeRequest, then sends the HTTP request with SX-Request: true header.")
(p :class "text-stone-600"
"3. Server builds s-expression tree, scans CSS classes, prepends missing component definitions.")
(p :class "text-stone-600"
"4. Client receives text/sx response, parses it, evaluates it, renders to DOM.")
(p :class "text-stone-600"
"5. sx.js fires sx:afterSwap and sx:afterSettle.")
(p :class "text-stone-600"
"6. Any sx-swap-oob elements are swapped into their targets elsewhere in the DOM."))
(~docs/section :title "Component definitions" :id "components"
(p :class "text-stone-600"
"On full page loads, component definitions are in <script type=\"text/sx\" data-components>. On subsequent sx requests, missing definitions are prepended to the response body. The client caches definitions in localStorage keyed by a content hash."))))
(defcomp ~protocols/fragments-content ()
(~docs/page :title "Cross-Service Fragments"
(~docs/section :title "Fragment protocol" :id "protocol"
(p :class "text-stone-600"
"Rose Ash runs as independent microservices. Each service can expose HTML or sx fragments that other services compose into their pages. Fragment endpoints return text/sx or text/html.")
(p :class "text-stone-600"
"The frag resolver is an I/O primitive in the render tree:")
(~docs/code :code (highlight "(frag \"blog\" \"link-card\" :slug \"hello\")" "lisp")))
(~docs/section :title "SxExpr wrapping" :id "wrapping"
(p :class "text-stone-600"
"When a fragment returns text/sx, the response is wrapped in an SxExpr and embedded directly in the render tree. When it returns text/html, it's wrapped in a ~rich-text component that inserts the HTML via raw!. This allows transparent composition across service boundaries."))
(~docs/section :title "fetch_fragments()" :id "fetch"
(p :class "text-stone-600"
"The Python helper fetch_fragments() fetches multiple fragments in parallel via asyncio.gather(). Fragments are cached in Redis with short TTLs. Each fragment request is HMAC-signed for authentication."))))
(defcomp ~protocols/resolver-io-content ()
(~docs/page :title "Resolver I/O"
(~docs/section :title "Async I/O primitives" :id "primitives"
(p :class "text-stone-600"
"The sx resolver identifies I/O nodes in the render tree, groups them, executes them in parallel via asyncio.gather(), and substitutes results back in.")
(p :class "text-stone-600"
"I/O primitives:")
(ul :class "space-y-2 text-stone-600"
(li (span :class "font-mono text-violet-700" "frag") " — fetch a cross-service fragment")
(li (span :class "font-mono text-violet-700" "query") " — read data from another service")
(li (span :class "font-mono text-violet-700" "action") " — execute a write on another service")
(li (span :class "font-mono text-violet-700" "current-user") " — resolve the current authenticated user")))
(~docs/section :title "Execution model" :id "execution"
(p :class "text-stone-600"
"The render tree is walked to find I/O nodes. All nodes at the same depth are gathered and executed in parallel. Results replace the I/O nodes in the tree. The walk continues until no more I/O nodes are found. This typically completes in 1-2 passes."))))
(defcomp ~protocols/internal-services-content ()
(~docs/page :title "Internal Services"
(~docs/note
(p "Honest note: the internal service protocol is JSON, not sx. Sx is the composition layer on top. The protocols below are the plumbing underneath."))
(~docs/section :title "HMAC-signed HTTP" :id "hmac"
(p :class "text-stone-600"
"Services communicate via HMAC-signed HTTP requests with short timeouts:")
(ul :class "space-y-2 text-stone-600 font-mono text-sm"
(li "GET /internal/data/{query} — read data (3s timeout)")
(li "POST /internal/actions/{action} — execute write (5s timeout)")
(li "POST /internal/inbox — ActivityPub-shaped event delivery")))
(~docs/section :title "fetch_data / call_action" :id "fetch"
(p :class "text-stone-600"
"Python helpers fetch_data() and call_action() handle HMAC signing, serialization, and error handling. They resolve service URLs from environment variables (INTERNAL_URL_BLOG, etc) and fall back to public URLs in development."))))
(defcomp ~protocols/activitypub-content ()
(~docs/page :title "ActivityPub"
(~docs/note
(p "Honest note: ActivityPub wire format is JSON-LD, not sx. This documents how AP integrates with the sx rendering layer."))
(~docs/section :title "AP activities" :id "activities"
(p :class "text-stone-600"
"Rose Ash services communicate cross-domain writes via ActivityPub-shaped activities. Each service has a virtual actor. Activities are JSON-LD objects sent to /internal/inbox endpoints. RSA signatures authenticate the sender."))
(~docs/section :title "Event bus" :id "bus"
(p :class "text-stone-600"
"The event bus dispatches activities to registered handlers. Handlers are async functions that process the activity and may trigger side effects. The bus runs as a background processor in each service."))))
(defcomp ~protocols/future-content ()
(~docs/page :title "Future Possibilities"
(~docs/note
(p "This page is speculative. Nothing here is implemented. It documents ideas that may or may not happen."))
(~docs/section :title "Custom protocol schemes" :id "schemes"
(p :class "text-stone-600"
"sx:// and sxs:// as custom URI schemes for content addressing or deep linking. An sx:// URI could resolve to an sx expression from a federated registry. This is technically feasible but practically unnecessary for a single-site deployment."))
(~docs/section :title "Sx as AP serialization" :id "ap-sx"
(p :class "text-stone-600"
"ActivityPub objects could be serialized as s-expressions instead of JSON-LD. S-expressions are more compact and easier to parse. The practical barrier: the entire AP ecosystem expects JSON-LD."))
(~docs/section :title "Sx-native federation" :id "federation"
(p :class "text-stone-600"
"Federated services could exchange sx fragments directly — render a remote user's profile card by fetching its sx source from their server. This requires trust and standardization that doesn't exist yet."))
(~docs/section :title "Realistic assessment" :id "realistic"
(p :class "text-stone-600"
"The most likely near-term improvement is sx:// deep linking for client-side component resolution. Everything else requires ecosystem adoption that one project can't drive alone."))))