Fix essay component names to match path-based convention

~doc-page → ~docs/page, ~doc-section → ~docs/section,
~doc-code → ~docs/code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:38:26 +00:00
parent 1dd2d73766
commit b9d85bd797

View File

@@ -4,11 +4,11 @@
;; ---------------------------------------------------------------------------
(defcomp ~essays/hypermedia-age-of-ai/essay-hypermedia-age-of-ai ()
(~doc-page :title "Hypermedia in the Age of AI"
(~docs/page :title "Hypermedia in the Age of AI"
(p :class "text-stone-500 text-sm italic mb-8"
"Nick Blow argues that JSON hypermedia can serve AI agents better than HTML or RPC. He is right about the problem and wrong about the solution. The answer is not a better serialization of links. It is a representation that is simultaneously content, control, and code.")
(~doc-section :title "I. The argument" :id "the-argument"
(~docs/section :title "I. The argument" :id "the-argument"
(p :class "text-stone-600"
(a :href "https://nickblow.tech/posts/hypermedia-in-the-age-of-ai" :class "text-violet-600 hover:underline" "Blow's essay")
" starts from a position familiar to anyone who has followed the hypermedia discourse: Carson Gross's contention that sprinkling link objects into JSON does not make an API truly RESTful, because REST demands generic clients capable of interpreting hypermedia controls. Blow agrees that this has merit but argues the position is too restrictive. HTML is not the universal hypermedia format. LLMs choke on it. The real prize is " (em "progressive discovery") " — a client that learns what it can do by following links, not by reading documentation upfront.")
@@ -19,7 +19,7 @@
(p :class "text-stone-600"
"This is the right diagnosis. The prescription is too weak."))
(~doc-section :title "II. The JSON hypermedia trap" :id "json-trap"
(~docs/section :title "II. The JSON hypermedia trap" :id "json-trap"
(p :class "text-stone-600"
"JSON hypermedia has been proposed, specified, and implemented many times. HAL, JSON:API, Siren, Hydra, UBER, Collection+JSON. Each adds a linking convention on top of JSON. Each generates roughly the same amount of adoption: enough for a conference talk, not enough for an ecosystem. The reason is structural, not cultural.")
(p :class "text-stone-600"
@@ -33,12 +33,12 @@
(p :class "text-stone-600"
"So: HTML is hypermedia but hostile to machines. JSON is machine-friendly but not hypermedia. The obvious question — is there a format that is both? — is the question nobody in this discourse seems to be asking."))
(~doc-section :title "III. The third option" :id "third-option"
(~docs/section :title "III. The third option" :id "third-option"
(p :class "text-stone-600"
"S-expressions.")
(p :class "text-stone-600"
"Consider what an SX response looks like when the server sends a page fragment:")
(~doc-code :code (highlight "(div :class \"space-y-4\"\n (h2 \"Your orders\")\n (ul :class \"divide-y\"\n (li :class \"py-3\"\n (span :class \"font-medium\" \"Order #4281\")\n (span :class \"text-stone-500\" \"3 items · £42.00\")\n (div :class \"mt-2 flex gap-2\"\n (a :sx-get \"/orders/4281\"\n :sx-target \"#main\"\n :class \"text-violet-600\"\n \"View details\")\n (button :sx-post \"/orders/4281/reorder\"\n :sx-target \"#main\"\n :class \"text-violet-600\"\n \"Reorder\"))))\n (a :sx-get \"/orders?page=2\"\n :sx-target \"#main\"\n :sx-swap \"innerHTML\"\n :class \"text-violet-600\"\n \"Next page\"))" "lisp"))
(~docs/code :code (highlight "(div :class \"space-y-4\"\n (h2 \"Your orders\")\n (ul :class \"divide-y\"\n (li :class \"py-3\"\n (span :class \"font-medium\" \"Order #4281\")\n (span :class \"text-stone-500\" \"3 items · £42.00\")\n (div :class \"mt-2 flex gap-2\"\n (a :sx-get \"/orders/4281\"\n :sx-target \"#main\"\n :class \"text-violet-600\"\n \"View details\")\n (button :sx-post \"/orders/4281/reorder\"\n :sx-target \"#main\"\n :class \"text-violet-600\"\n \"Reorder\"))))\n (a :sx-get \"/orders?page=2\"\n :sx-target \"#main\"\n :sx-swap \"innerHTML\"\n :class \"text-violet-600\"\n \"Next page\"))" "lisp"))
(p :class "text-stone-600"
"This is simultaneously:")
(ul :class "space-y-2 text-stone-600"
@@ -51,41 +51,41 @@
(p :class "text-stone-600"
"This is not JSON with links bolted on. This is not HTML simplified. This is a representation where content and control are " (em "the same syntax") " — because s-expressions make no distinction between data and code. A " (code "div") " and an " (code "sx-get") " are both list elements. The AI reads them with the same parser, reasons about them with the same model, and generates them with the same grammar."))
(~doc-section :title "IV. Progressive discovery, natively" :id "progressive-discovery"
(~docs/section :title "IV. Progressive discovery, natively" :id "progressive-discovery"
(p :class "text-stone-600"
"Blow's strongest argument is against MCP's total-disclosure model. A hypermedia API reveals capabilities incrementally: you see what you can do " (em "from where you are") ", not everything the system supports. This is how the web works for humans — you do not receive a manifest of every URL on a site before you can browse it.")
(p :class "text-stone-600"
"SX achieves this without any additional protocol machinery. Each response is a tree of content and controls. The controls present in " (em "this") " response are " (em "this") " resource's available transitions. Follow one, get a new tree with new controls. The state machine Blow describes is not layered on top of the data format — it " (em "is") " the data format.")
(p :class "text-stone-600"
"But SX goes further than any JSON hypermedia format can, because the controls are not just declarations — they are " (em "evaluable") ". Consider a response that includes conditional controls:")
(~doc-code :code (highlight "(div :class \"order-actions\"\n (when can-cancel\n (button :sx-post \"/orders/4281/cancel\"\n :sx-confirm \"Cancel this order?\"\n :class \"text-red-600\"\n \"Cancel order\"))\n (when can-refund\n (button :sx-post \"/orders/4281/refund\"\n :sx-target \"#status\"\n \"Request refund\"))\n (when (and shipped tracking-url)\n (a :href tracking-url\n :class \"text-violet-600\"\n \"Track shipment\")))" "lisp"))
(~docs/code :code (highlight "(div :class \"order-actions\"\n (when can-cancel\n (button :sx-post \"/orders/4281/cancel\"\n :sx-confirm \"Cancel this order?\"\n :class \"text-red-600\"\n \"Cancel order\"))\n (when can-refund\n (button :sx-post \"/orders/4281/refund\"\n :sx-target \"#status\"\n \"Request refund\"))\n (when (and shipped tracking-url)\n (a :href tracking-url\n :class \"text-violet-600\"\n \"Track shipment\")))" "lisp"))
(p :class "text-stone-600"
"The available actions depend on server-evaluated state. An order that has shipped shows a tracking link. An order that can be cancelled shows a cancel button. The client — human or AI — sees only the actions that apply " (em "right now") ". This is not progressive discovery bolted onto a data format. It is the server authoring a state machine in the response itself, using the same language as the content.")
(p :class "text-stone-600"
"A JSON hypermedia format would need a separate " (code "actions") " array with method/href/type metadata, plus a schema for each action's payload, plus documentation for what each action does. SX needs none of this. The button is its own documentation. Its label says what it does. Its attributes say how. An AI reading " (code "(button :sx-post \"/orders/4281/cancel\" \"Cancel order\")") " knows everything it needs to act."))
(~doc-section :title "V. The component advantage" :id "component-advantage"
(~docs/section :title "V. The component advantage" :id "component-advantage"
(p :class "text-stone-600"
"Blow does not discuss the composition problem, but it is where his proposal breaks down hardest. JSON hypermedia formats specify individual resources. They do not specify how resources compose into interfaces. A Siren entity has properties, actions, and links — but no concept of a reusable UI fragment that accepts parameters and renders children.")
(p :class "text-stone-600"
"SX has components:")
(~doc-code :code (highlight "(defcomp ~order-card (&key order &rest actions)\n (div :class \"rounded border p-4\"\n (div :class \"flex justify-between\"\n (span :class \"font-medium\"\n (str \"Order #\" (get order \"id\")))\n (span :class \"text-stone-500\"\n (get order \"status\")))\n (p :class \"text-sm text-stone-600 mt-1\"\n (str (get order \"item-count\") \" items · \"\n (get order \"total\")))\n (div :class \"mt-3 flex gap-2\" actions)))" "lisp"))
(~docs/code :code (highlight "(defcomp ~order-card (&key order &rest actions)\n (div :class \"rounded border p-4\"\n (div :class \"flex justify-between\"\n (span :class \"font-medium\"\n (str \"Order #\" (get order \"id\")))\n (span :class \"text-stone-500\"\n (get order \"status\")))\n (p :class \"text-sm text-stone-600 mt-1\"\n (str (get order \"item-count\") \" items · \"\n (get order \"total\")))\n (div :class \"mt-3 flex gap-2\" actions)))" "lisp"))
(p :class "text-stone-600"
"This is a hypermedia control in the fullest sense — it takes data and actions as parameters and renders a self-contained interactive unit. An AI generating a page can compose it:")
(~doc-code :code (highlight "(map (fn (order)\n (~order-card :order order\n (when (get order \"can-reorder\")\n (button :sx-post (str \"/orders/\" (get order \"id\") \"/reorder\")\n :sx-target \"#main\"\n \"Reorder\"))))\n orders)" "lisp"))
(~docs/code :code (highlight "(map (fn (order)\n (~order-card :order order\n (when (get order \"can-reorder\")\n (button :sx-post (str \"/orders/\" (get order \"id\") \"/reorder\")\n :sx-target \"#main\"\n \"Reorder\"))))\n orders)" "lisp"))
(p :class "text-stone-600"
"No JSON hypermedia format supports this. They cannot, because JSON has no function abstraction. You can parameterize data — templates, schemas — but you cannot express " (em "a reusable piece of interactive UI") " in JSON. You always need a separate rendering layer that interprets the JSON and produces something visual. SX does not have this separation. The component definition " (em "is") " the rendering."))
(~doc-section :title "VI. SX URLs as evaluable affordances" :id "evaluable-affordances"
(~docs/section :title "VI. SX URLs as evaluable affordances" :id "evaluable-affordances"
(p :class "text-stone-600"
"Blow describes REST resources as state machines where hyperlinks represent allowed transitions. This maps cleanly to SX's URL system, which goes further: URLs are not opaque strings but " (a :href "/sx/(applications.(sx-urls))" :class "text-violet-600 hover:underline" "evaluable expressions") ".")
(~doc-code :code (highlight ";; Opaque URL (conventional)\n\"/orders/4281/details\"\n\n;; SX URL (evaluable)\n\"/sx/(etc.(essay.hypermedia-age-of-ai))\"\n\n;; The URL is a program:\n;; 1. Call the 'etc' section function\n;; 2. Which calls the 'essay' page function\n;; 3. With slug \"hypermedia-age-of-ai\"\n;; 4. Returns the component AST to render" "lisp"))
(~docs/code :code (highlight ";; Opaque URL (conventional)\n\"/orders/4281/details\"\n\n;; SX URL (evaluable)\n\"/sx/(etc.(essay.hypermedia-age-of-ai))\"\n\n;; The URL is a program:\n;; 1. Call the 'etc' section function\n;; 2. Which calls the 'essay' page function\n;; 3. With slug \"hypermedia-age-of-ai\"\n;; 4. Returns the component AST to render" "lisp"))
(p :class "text-stone-600"
"The URL itself is a composition of functions. An AI examining the URL can understand the content hierarchy — this is an essay, in the 'etc' section, about a specific topic. It can generate new URLs by composing known functions: " (code "(etc.(essay.new-slug))") " follows the same pattern. The addressing scheme is not a convention imposed from outside. It is the language applied to navigation.")
(p :class "text-stone-600"
"This dissolves the distinction between \"following a link\" and \"calling a function\" that bedevils every attempt to make JSON APIs hypermedia. In SX, following a link " (em "is") " calling a function. The URL evaluates to a component tree. The component tree renders to interactive content. The interactive content contains more URLs. The cycle is closed without any protocol machinery beyond HTTP."))
(~doc-section :title "VII. The AI agent that reads the page" :id "ai-agent"
(~docs/section :title "VII. The AI agent that reads the page" :id "ai-agent"
(p :class "text-stone-600"
"Here is the scenario Blow is really imagining: an LLM agent that interacts with web services not through a bespoke API layer but by reading responses and following controls, the way a human reads a page and clicks links. MCP makes this possible through function calling. Blow argues hypermedia would make it better through progressive discovery.")
(p :class "text-stone-600"
@@ -99,7 +99,7 @@
(p :class "text-stone-600"
"The " (a :href "/sx/(etc.(essay.sx-and-ai))" :class "text-violet-600 hover:underline" "spec fits in a context window") ". The complete SX language — evaluator, parser, renderer, all primitives — is roughly 3,000 lines. An AI agent that loads the spec into its context can not only read SX responses but " (em "generate") " them. It can produce new pages, new components, new interactions — because the language it reads and the language it writes are the same, and both fit in memory at once."))
(~doc-section :title "VIII. What MCP gets wrong" :id "mcp"
(~docs/section :title "VIII. What MCP gets wrong" :id "mcp"
(p :class "text-stone-600"
"MCP's design reflects a fundamental confusion: it treats capability as a catalogue rather than an affordance. The server lists every tool. The model reads the list. The model calls a tool. The response is data. The model must maintain its own mental model of the server's state to know which tools are appropriate next.")
(p :class "text-stone-600"
@@ -109,7 +109,7 @@
(p :class "text-stone-600"
"This is why the htmx camp's insistence on HTML hypermedia is not mere nostalgia. The principle is correct: the server should author the interaction. Where htmx falls short is in choosing a format that is optimized for human visual consumption at the expense of machine interpretability. SX resolves this by using a format that is equally transparent to both — because s-expressions are the " (em "simplest possible") " structured representation, and simplicity is readable by anything."))
(~doc-section :title "IX. Beyond the format wars" :id "beyond"
(~docs/section :title "IX. Beyond the format wars" :id "beyond"
(p :class "text-stone-600"
"The deeper issue with the \"JSON vs HTML for hypermedia\" debate is the assumption that content and control are separate concerns that a format must somehow reunite. HTML reunites them through rendering semantics. JSON formats try to reunite them through metadata conventions. Both accept the premise that there are two things — data and interaction — and the question is how to ship them together.")
(p :class "text-stone-600"