Fix empty code blocks: rename ~docs/code param, fix batched IO dispatch

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>
This commit is contained in:
2026-03-25 18:08:40 +00:00
parent 739d04672b
commit 6f96452f70
58 changed files with 440 additions and 437 deletions

View File

@@ -31,12 +31,12 @@
(~docs/subsection :title "The Problem"
(p "JSON-LD activities are verbose and require context resolution:")
(~docs/code :code (highlight "{\"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Create\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"Note\",\n \"content\": \"<p>Hello world</p>\",\n \"attributedTo\": \"https://example.com/users/alice\"\n }}" "json"))
(~docs/code :src (highlight "{\"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Create\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"Note\",\n \"content\": \"<p>Hello world</p>\",\n \"attributedTo\": \"https://example.com/users/alice\"\n }}" "json"))
(p "Every consumer parses JSON, resolves @context, extracts fields, then builds their own UI around the raw data. The content is HTML embedded in a JSON string — two formats nested, neither evaluable."))
(~docs/subsection :title "SX Activity Format"
(p "The same activity as SX:")
(~docs/code :code (highlight "(Create\n :actor \"https://example.com/users/alice\"\n :published \"2026-03-06T12:00:00Z\"\n :object (Note\n :attributed-to \"https://example.com/users/alice\"\n :content (p \"Hello world\")\n :media-type \"text/sx\"))" "lisp"))
(~docs/code :src (highlight "(Create\n :actor \"https://example.com/users/alice\"\n :published \"2026-03-06T12:00:00Z\"\n :object (Note\n :attributed-to \"https://example.com/users/alice\"\n :content (p \"Hello world\")\n :media-type \"text/sx\"))" "lisp"))
(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"
@@ -44,7 +44,7 @@
(div
(h4 :class "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 :code (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")))
(~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")
@@ -84,13 +84,13 @@
(div
(h4 :class "font-semibold text-stone-700" "1. Component CID computation")
(p "Each " (code "defcomp") " definition gets a content address:")
(~docs/code :code (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"))
(~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")
(p "Activities declare which components they need by CID:")
(~docs/code :code (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"))
(~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
@@ -106,7 +106,7 @@
(div
(h4 :class "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 :code (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/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"
@@ -133,18 +133,18 @@
(div
(h4 :class "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 :code (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")))
(~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")
(p "artdag processing results (rendered video, processed images) already have CIDs. Federate them as activities:")
(~docs/code :code (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"))
(~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")
(p "Not just components and media — full page content can be content-addressed. An Article's body is SX, pinned to IPFS:")
(~docs/code :code (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"))
(~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
@@ -177,12 +177,12 @@
(div
(h4 :class "font-semibold text-stone-700" "1. Component collections as AP actors")
(p "Each server exposes a component registry actor:")
(~docs/code :code (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"))
(~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")
(~docs/code :code (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"))
(~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
@@ -197,7 +197,7 @@
(div
(h4 :class "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 :code (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/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"
@@ -232,7 +232,7 @@
(div
(h4 :class "font-semibold text-stone-700" "2. Provenance chain in activities")
(~docs/code :code (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"))
(~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
@@ -246,7 +246,7 @@
(div
(h4 :class "font-semibold text-stone-700" "4. Verification UI")
(p "Client-side provenance badge on federated content:")
(~docs/code :code (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/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"
@@ -282,7 +282,7 @@
(div
(h4 :class "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 :code (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")))
(~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")
@@ -350,7 +350,7 @@
(~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 :code (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"))
(~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"
(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.")