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:
@@ -16,7 +16,7 @@
|
||||
"a flat sequence of slash-separated path segments, the URL encodes "
|
||||
"a nested function call that the server evaluates to produce a page.")
|
||||
(p "Every page on this site is addressed by an SX URL. You are currently reading:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
"/sx/(applications.(sx-urls))"
|
||||
"lisp"))
|
||||
(p "This is a function call: " (code "applications") " is called with the result of "
|
||||
@@ -34,7 +34,7 @@
|
||||
(p "The rule: " (strong "dot = space, nothing more") ". "
|
||||
"Before parsing, the server replaces every dot with a space. "
|
||||
"Parens carry all structural meaning.")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; What you type in the browser:\n/(language.(doc.introduction))\n\n;; After dot→space transform:\n(language (doc introduction))\n\n;; This is standard SX. Parens are nesting. Atoms are arguments.\n;; 'introduction' is a string slug, 'doc' is a function, 'language' is a function."
|
||||
"lisp"))
|
||||
|
||||
@@ -58,12 +58,12 @@
|
||||
(p "REST URLs have an inherent ambiguity: "
|
||||
"does a filter apply to the last segment, or the whole path? "
|
||||
"S-expression nesting makes scope explicit.")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; REST — ambiguous:\n/users/123/posts?filter=published\n;; Does 'filter' apply to posts? To the user? To the whole query?\n;; The answer depends on API documentation.\n\n;; SX URLs — explicit scoping:\n/(users.(posts.123.(filter.published))) ;; filter scoped to posts\n/(users.posts.123.(filter.published)) ;; filter scoped to the whole expression\n\n;; These are structurally different. The paren boundaries ARE scope boundaries.\n;; No documentation needed — the syntax tells you."
|
||||
"lisp"))
|
||||
|
||||
(p "This extends to every level of nesting on this site:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; These all have different scoping:\n/(language.(spec.signals)) ;; 'signals' scoped to spec\n/(language.(spec.(explore.signals))) ;; 'signals' scoped to explore\n/(language.(spec.(explore.signals.:section.\"batch\"))) ;; keyword scoped to explore call"
|
||||
"lisp"))
|
||||
(p "What took REST thirty years of convention documents to approximate, "
|
||||
@@ -103,7 +103,7 @@
|
||||
(~docs/subsection :title "Leaf pages"
|
||||
(p "Leaf pages are the innermost function calls. "
|
||||
"The slug becomes a string argument to the page function:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; doc(\"introduction\") — the slug auto-quotes to a string\n/(language.(doc.introduction))\n\n;; spec(\"core\") — same pattern, different function\n/(language.(spec.core))\n\n;; explore(\"signals\") — nested deeper\n/(language.(spec.(explore.signals)))\n\n;; example(\"progress-bar\") — three levels of nesting\n/(geography.(hypermedia.(example.progress-bar)))"
|
||||
"lisp"))
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
(~docs/section :title "Direct Component URLs" :id "direct"
|
||||
(p "Every " (code "defcomp") " in the component environment is directly "
|
||||
"addressable by its " (code "~name") " — no page function, no routing wiring, no case statement.")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Any component is instantly a URL:\n/(~essays/sx-sucks/essay-sx-sucks) ;; the essay\n/(~plans/sx-urls/plan-sx-urls-content) ;; the SX URLs plan\n/(~docs-content/docs-evaluator-content) ;; evaluator docs\n/(~analyzer/bundle-analyzer-content) ;; bundle analyzer tool"
|
||||
"lisp"))
|
||||
(p "Try it:")
|
||||
@@ -176,17 +176,17 @@
|
||||
(~docs/subsection :title "Section functions pass through"
|
||||
(p (code "language") ", " (code "geography") ", " (code "applications") ", " (code "etc")
|
||||
" are identity on their argument. They exist to provide structure:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Section function definition:\n(define language\n (fn (content)\n (if (nil? content) nil content)))\n\n;; /(language.(doc.introduction))\n;; Eval steps:\n;; 1. (doc \"introduction\") → page content AST\n;; 2. (language <content>) → passes content through\n;; 3. Wrap in (~layouts/doc :path \"...\" <content>)"
|
||||
"lisp"))
|
||||
(p "Section functions with no argument return their index page:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
"(define home\n (fn (content)\n (if (nil? content) '(~docs-content/home-content) content)))\n\n;; /(home) → (~docs-content/home-content)\n;; / → same thing"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Page functions dispatch on slug"
|
||||
(p "Page functions take a slug string and return a quoted component expression:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
"(define doc\n (fn (slug)\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 \"components\" '(~docs-content/docs-components-content)\n \"evaluator\" '(~docs-content/docs-evaluator-content)\n \"primitives\"\n (let ((data (primitives-data)))\n `(~docs-content/docs-primitives-content\n :prims (~docs/primitives-tables :primitives ,data)))\n :else '(~docs-content/docs-introduction-content)))))"
|
||||
"lisp"))
|
||||
(p "The " (code "'") " (quote) is critical: page functions return "
|
||||
@@ -197,7 +197,7 @@
|
||||
(~docs/subsection :title "Data-dependent pages"
|
||||
(p "Some pages need server data. The page function calls an IO helper, "
|
||||
"then splices data into the component call with quasiquote:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
"(define isomorphism\n (fn (slug)\n (case slug\n \"bundle-analyzer\"\n (let ((data (bundle-analyzer-data))) ;; IO: reads component env\n `(~analyzer/bundle-analyzer-content\n :pages ,(get data \"pages\")\n :total-components ,(get data \"total-components\")\n :pure-count ,(get data \"pure-count\")\n :io-count ,(get data \"io-count\")))\n :else '(~plans/isomorphic/plan-isomorphic-content))))"
|
||||
"lisp"))
|
||||
(p "Visit "
|
||||
@@ -213,7 +213,7 @@
|
||||
"The handler does four things:")
|
||||
|
||||
(~docs/subsection :title "The handler"
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Python handler (sx_router.py) — simplified:\n\nasync def eval_sx_url(raw_path):\n # 1. Build env: all components + all page functions\n env = get_component_env() | get_page_helpers(\"sx\")\n\n # 2. Spec function: parse URL, auto-quote unknowns\n # This is bootstrapped from router.sx → sx_ref.py\n expr = prepare_url_expr(path, env)\n\n # 3. Evaluate: page functions resolve, data fetched\n page_ast = await async_eval(expr, env, ctx)\n\n # 4. Wrap in layout, render to HTML\n wrapped = [Symbol(\"~layouts/doc\"), Keyword(\"path\"), path, page_ast]\n content_sx = await _eval_slot(wrapped, env, ctx)\n return full_page_sx(content_sx)"
|
||||
"python"))
|
||||
(p "The handler imports " (code "prepare_url_expr") " from the bootstrapped "
|
||||
@@ -225,7 +225,7 @@
|
||||
", all three atoms are symbols. But " (code "introduction")
|
||||
" isn't a function — it's a slug. The " (code "auto-quote-unknowns") " function "
|
||||
"walks the AST and replaces unknown symbols with their string name:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; From router.sx — the auto-quoting spec:\n(define auto-quote-unknowns :effects []\n (fn (expr env)\n (if (not (list? expr)) expr\n (if (empty? expr) expr\n ;; Head stays as symbol (function position)\n (cons (first expr)\n (map (fn (child)\n (cond\n (list? child)\n (auto-quote-unknowns child env)\n (= (type-of child) \"symbol\")\n (let ((name (symbol-name child)))\n (if (or (env-has? env name)\n (starts-with? name \":\")\n (starts-with? name \"~\")\n (starts-with? name \"!\"))\n child ;; known → keep as symbol\n name)) ;; unknown → string\n :else child))\n (rest expr)))))))"
|
||||
"lisp"))
|
||||
(p "This is checked against the " (em "actual environment") " at request time — "
|
||||
@@ -236,7 +236,7 @@
|
||||
(p "API endpoints are defined with " (code "defhandler") " and use the same "
|
||||
"nested SX URL structure. These are live endpoints on this site — "
|
||||
"they return SX wire format that the client renders:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; From sx/sx/handlers/ref-api.sx — live API endpoint:\n(defhandler ref-time :method \"GET\"\n :path \"/sx/(geography.(hypermedia.(reference.(api.time))))\"\n (span :id \"time\" (str \"Server time: \" (format-time (now)))))\n\n;; The endpoint is an SX expression that returns SX.\n;; The URL IS the address. The handler IS the content."
|
||||
"lisp"))
|
||||
(p "Try these live endpoints — each returns SX wire format:")
|
||||
@@ -259,7 +259,7 @@
|
||||
(p "More handler examples from the "
|
||||
(a :href "/sx/(geography.(hypermedia.(reference.attributes)))"
|
||||
:class "text-violet-600 hover:underline" "attributes reference") ":")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; POST handler — receives form data, returns SX:\n(defhandler ref-greet :method \"POST\"\n :path \"/sx/(geography.(hypermedia.(reference.(api.greet))))\"\n (let ((name (request-form \"name\")))\n (div :id \"result\" :class \"p-4 border rounded\"\n (p (str \"Hello, \" (if (empty? name) \"world\" name) \"!\")))))\n\n;; DELETE handler — receives path parameter:\n(defhandler ref-delete-item :method \"DELETE\"\n :path \"/sx/(geography.(hypermedia.(reference.(api.(item.<sx:item_id>)))))\"\n \"\")\n\n;; GET handler with query params:\n(defhandler ref-trigger-search :method \"GET\"\n :path \"/sx/(geography.(hypermedia.(reference.(api.trigger-search))))\"\n (let ((q (request-arg \"q\")))\n (div :id \"search-results\"\n (p (str \"Results for: \" q)))))"
|
||||
"lisp"))))
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
"without a server round-trip.")
|
||||
|
||||
(~docs/subsection :title "The client route check"
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; From orchestration.sx — client-side route decision:\n;;\n;; 1. Match the URL against the page registry\n;; 2. Check if target layout matches current layout\n;; 3. Check if all component dependencies are loaded\n;; 4. If pure (no IO): render client-side, no server request\n;; 5. If data-dependent: fetch data, then render\n;; 6. If layout changes: fall through to server (needs OOB header)\n\n(define try-client-route :effects [mutation io]\n (fn (pathname target-sel)\n (let ((match (find-matching-route pathname _page-routes)))\n (if (nil? match)\n false ;; no match → server handles it\n (if (not (= (get match \"layout\") (current-page-layout)))\n false ;; layout change → server (needs OOB update)\n ;; ... render client-side\n )))))"
|
||||
"lisp"))
|
||||
(p "Pure pages (no " (code "has-data") " flag) render instantly in the browser. "
|
||||
@@ -281,7 +281,7 @@
|
||||
(p "The " (code "prepare-url-expr") " function from " (code "router.sx") " is bootstrapped "
|
||||
"to JavaScript as " (code "Sx.prepareUrlExpr") ". The browser uses it for "
|
||||
"client-side navigation, relative URL resolution, and the address bar REPL:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; From router.sx — bootstrapped to both Python and JavaScript:\n(define prepare-url-expr :effects []\n (fn (url-path env)\n (let ((expr (url-to-expr url-path)))\n (if (empty? expr)\n expr\n (auto-quote-unknowns expr env)))))\n\n;; url-to-expr: strip /, dots→spaces, parse\n;; auto-quote-unknowns: unknown symbols → strings\n;; Result: ready for standard eval"
|
||||
"lisp"))
|
||||
(p "One spec, two hosts. The Python server and JavaScript client share "
|
||||
@@ -298,7 +298,7 @@
|
||||
(~docs/subsection :title "One dot: apply at current level"
|
||||
(p "A single dot appends at the current nesting depth. "
|
||||
"It's like calling a function with an extra argument:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Current page: /(geography.(hypermedia.(example.progress-bar)))\n;;\n;; .click-to-load\n;; = \"at the current level, apply click-to-load\"\n;; → /(geography.(hypermedia.(example.click-to-load)))\n;;\n;; The inner expression (example.progress-bar) becomes (example.click-to-load).\n;; The outer nesting (geography.(hypermedia.(...))) is preserved."
|
||||
"lisp"))
|
||||
(p "This is a sibling navigation — same parent, different leaf."))
|
||||
@@ -306,21 +306,21 @@
|
||||
(~docs/subsection :title "Two dots: pop one level, apply"
|
||||
(p "Two dots remove the innermost nesting level, then optionally apply a new one. "
|
||||
"Like " (code "cd ..") " in a filesystem — but structural, not string-based:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Current: /(geography.(hypermedia.(example.progress-bar)))\n;;\n;; .. → /(geography.(hypermedia.(example)))\n;; ..inline-edit → /(geography.(hypermedia.(example.inline-edit)))\n;; ..reference → /(geography.(hypermedia.(reference)))\n;;\n;; Pop the innermost expression, replace with the new slug.\n;; The outer nesting is preserved."
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Three+ dots: pop multiple levels"
|
||||
(p "Each additional dot pops one more level of nesting. "
|
||||
"N dots = pop N-1 levels:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Current: /(geography.(hypermedia.(example.progress-bar)))\n;;\n;; ... → /(geography.(hypermedia)) ;; pop 2 levels\n;; .... → /(geography) ;; pop 3 levels\n;; ..... → / ;; pop 4 levels (root)\n;;\n;; Combine with a slug to navigate across sections:\n;; ...reactive.(examples) → /(geography.(reactive.(examples))) ;; pop 2, into reactive\n;; ....language.(doc.intro)\n;; → /(language.(doc.intro)) ;; pop 3, into language"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Why this is functional, not textual"
|
||||
(p "REST relative URLs are string operations — remove path segments, append new ones. "
|
||||
"This breaks when the hierarchy is structural rather than linear.")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; REST relative: ../inline-edit on /geography/hypermedia/examples/progress-bar\n;; → /geography/hypermedia/examples/inline-edit\n;; Works! But only because the hierarchy happens to be flat.\n\n;; What about navigating to a sibling section?\n;; REST: ../../reference/attributes\n;; How many ../ do you need? You have to count path segments.\n;; The answer changes if the base path changes.\n\n;; SX relative: ..reference.attributes\n;; from /(geography.(hypermedia.(example.progress-bar)))\n;; → /(geography.(hypermedia.(reference.attributes)))\n;;\n;; The dots operate on nesting depth, not string segments.\n;; Add a level of nesting? The relative URL still works.\n;; Restructure the tree? Relative links within a subtree are unaffected."
|
||||
"lisp"))
|
||||
(p "Relative SX URLs are " (em "structurally stable") " — "
|
||||
@@ -329,7 +329,7 @@
|
||||
(~docs/subsection :title "Keyword arguments: functional parameters"
|
||||
(p "URLs can carry keyword arguments that parameterize the innermost expression. "
|
||||
"Keywords use the same " (code ":name") " syntax as SX function calls:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Set a keyword on a page:\n/(language.(spec.(explore.signals.:page.3)))\n→ (language (spec (explore signals :page 3)))\n\n;; :page is a keyword argument to the explore function.\n;; It's the same syntax you'd use when calling a component:\n;; (~paginator :current-page 3 :total-pages 10)\n\n;; Delta values — relative keyword modification:\n;; .:page.+1 → increment current :page by 1\n;; .:page.-1 → decrement current :page by 1\n;;\n;; This is pagination as URL algebra:\n;; Current: /(language.(spec.(explore.signals.:page.3)))\n;; .:page.+1 → /(language.(spec.(explore.signals.:page.4)))\n;; No JavaScript, no state management. A URL transform."
|
||||
"lisp")))
|
||||
|
||||
@@ -337,7 +337,7 @@
|
||||
(p "Relative resolution is defined in " (code "router.sx") " as a pure function. "
|
||||
"It parses the current URL's expression structure, pops levels, "
|
||||
"splices the new content, and serializes back:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; From router.sx — pure function, no IO:\n(define resolve-relative-url :effects []\n (fn (current-url relative-url)\n ;; Parse current URL's expression\n ;; Count dots to determine pop level\n ;; Pop that many nesting levels\n ;; Append new content\n ;; Serialize back to URL format\n ...))\n\n;; Tested with 50+ cases in test-router.sx:\n(resolve-relative-url\n \"/sx/(geography.(hypermedia.(example.progress-bar)))\"\n \"..inline-edit\")\n→ \"/sx/(geography.(hypermedia.(example.inline-edit)))\"\n\n(resolve-relative-url\n \"/sx/(language.(spec.(explore.signals.:page.3)))\"\n \".:page.+1\")\n→ \"/sx/(language.(spec.(explore.signals.:page.4)))\""
|
||||
"lisp"))
|
||||
(p "This function is bootstrapped to both Python and JavaScript. "
|
||||
@@ -386,7 +386,7 @@
|
||||
(~docs/subsection :title "How special forms are parsed"
|
||||
(p "The " (code "parse-sx-url") " function in " (code "router.sx") " detects and "
|
||||
"decomposes special form URLs:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; From router.sx — special form detection:\n(define parse-sx-url :effects []\n (fn (url)\n ;; Returns a typed descriptor:\n ;; {:type \"special-form\" :form \"!source\" :inner \"(~essay)\"}\n ;; {:type \"absolute\" :raw \"/(language.(doc.intro))\"}\n ;; {:type \"relative\" :raw \"..eval\"}\n ;; {:type \"direct-component\" :name \"~essay-sx-sucks\"}\n ...))\n\n;; The ! prefix is detected in the expression head:\n;; /(!source.(~essay)) → head is !source → special form\n;; /(language.(doc.intro)) → head is language → normal\n\n;; Each special form takes the inner expression as its argument:\n;; !source wraps whatever follows in a source viewer\n;; !inspect wraps whatever follows in an analysis view\n;; !diff takes two inner expressions"
|
||||
"lisp"))
|
||||
(p "Special forms compose with the rest of the URL algebra. "
|
||||
@@ -427,26 +427,26 @@
|
||||
(~docs/subsection :title "Step 1: Parse"
|
||||
(p "Strip the leading " (code "/") ", replace dots with spaces, parse as SX. "
|
||||
"This is the " (code "url-to-expr") " function from " (code "router.sx") ":")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Input: /sx/(language.(doc.introduction))\n;; Strip prefix: (language.(doc.introduction))\n;; Dots→spaces: (language (doc introduction))\n;; Parse: [Symbol(\"language\"), [Symbol(\"doc\"), Symbol(\"introduction\")]]"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Step 2: Auto-quote"
|
||||
(p "Unknown symbols become strings. Known functions stay as symbols. "
|
||||
"This is " (code "auto-quote-unknowns") " — checked against the live env:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; 'language' is in env (section function) → stays Symbol\n;; 'doc' is in env (page function) → stays Symbol\n;; 'introduction' is NOT in env → becomes \"introduction\"\n;;\n;; Result: [Symbol(\"language\"), [Symbol(\"doc\"), \"introduction\"]]"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Step 3: Evaluate"
|
||||
(p "Standard inside-out evaluation — same as any SX expression:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; 1. Eval \"introduction\" → \"introduction\" (string, self-evaluating)\n;; 2. Eval (doc \"introduction\") → calls doc function\n;; → returns '(~docs-content/docs-introduction-content)\n;; 3. Eval (language <ast>) → calls language function\n;; → passes content through (identity)\n;; 4. Result: [Symbol(\"~docs-content/docs-introduction-content\")]"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Step 4: Wrap and render"
|
||||
(p "The router wraps the result in " (code "~layouts/doc") " with the URL as " (code ":path") ":")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Wrap: (~layouts/doc :path \"/sx/(language.(doc.introduction))\" <content>)\n;; Render: aser expands ~layouts/doc → nav, breadcrumbs, layout shell\n;; aser expands ~docs-content/docs-introduction-content → page HTML\n;; Return: full HTML page (or OOB wire format for HTMX requests)"
|
||||
"lisp")))
|
||||
|
||||
@@ -473,14 +473,14 @@
|
||||
|
||||
(~docs/subsection :title "sx-get — HTMX-style fetching"
|
||||
(p (code "sx-get") " fetches content from an SX URL and swaps it into the DOM:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Fetch and swap a section:\n(div :sx-get \"/sx/(geography.(hypermedia.(example.progress-bar)))\"\n :sx-trigger \"click\"\n :sx-target \"#content\"\n :sx-swap \"innerHTML\"\n \"Load Progress Bar Example\")\n\n;; Paginated content with keyword deltas:\n(button :sx-get \".:page.+1\"\n :sx-trigger \"click\"\n :sx-target \"#results\"\n :sx-swap \"innerHTML\"\n \"Next Page\")"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Pagination — URLs as algebra"
|
||||
(p "The relative URL algebra makes pagination trivial. "
|
||||
"No state, no event handlers — just URLs:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Paginator component — pure URL navigation:\n(defcomp ~paginator (&key current-page total-pages)\n (nav :class \"flex gap-2\"\n (when (> current-page 1)\n (a :href \".:page.-1\" \"Previous\")) ;; delta: decrement :page\n (span (str \"Page \" current-page \" of \" total-pages))\n (when (< current-page total-pages)\n (a :href \".:page.+1\" \"Next\")))) ;; delta: increment :page\n\n;; On /(language.(spec.(explore.signals.:page.3))):\n;; \"Previous\" → .:page.-1 → :page becomes 2\n;; \"Next\" → .:page.+1 → :page becomes 4\n;;\n;; Each link is a static href. Server renders the right page.\n;; Back button works. Bookmarkable. Shareable. Cacheable."
|
||||
"lisp"))))
|
||||
|
||||
@@ -548,7 +548,7 @@
|
||||
(li (strong "No client library") " — " (code "curl") " returns content"))
|
||||
|
||||
(p "HTTP verbs align naturally with SX URL semantics:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; GET — pure evaluation, cacheable:\nGET /sx/(language.(doc.introduction))\n\n;; POST — side effects via defhandler:\nPOST /sx/(geography.(hypermedia.(reference.(api.greet))))\n\n;; DELETE — with path parameters:\nDELETE /sx/(geography.(hypermedia.(reference.(api.(item.42)))))\n\n;; PUT — full replacement:\nPUT /sx/(geography.(hypermedia.(example.(api.putpatch))))"
|
||||
"lisp")))
|
||||
|
||||
@@ -560,7 +560,7 @@
|
||||
"special form detection, and auto-quoting as pure functions.")
|
||||
|
||||
(p "Key functions:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; URL → expression (dots→spaces, parse):\n(url-to-expr \"(language.(doc.intro))\")\n→ (language (doc intro))\n\n;; Auto-quote unknowns against env:\n(auto-quote-unknowns '(language (doc intro)) env)\n→ (language (doc \"intro\")) ;; intro not in env → string\n\n;; Full pipeline:\n(prepare-url-expr \"(language.(doc.intro))\" env)\n→ (language (doc \"intro\")) ;; ready for eval\n\n;; Classify URL type:\n(parse-sx-url \"/sx/(language.(doc.intro))\")\n→ {:type \"absolute\" :raw \"/sx/(language.(doc.intro))\"}\n\n(parse-sx-url \"/sx/(!source.(~essay))\")\n→ {:type \"special-form\" :form \"!source\" :inner \"(~essay)\"}\n\n(parse-sx-url \"..eval\")\n→ {:type \"relative\" :raw \"..eval\"}\n\n;; Resolve relative URLs:\n(resolve-relative-url\n \"/sx/(geography.(hypermedia.(example.progress-bar)))\"\n \"..inline-edit\")\n→ \"/sx/(geography.(hypermedia.(example.inline-edit)))\""
|
||||
"lisp"))
|
||||
|
||||
@@ -574,7 +574,7 @@
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "The Lisp Tax" :id "parens"
|
||||
(p "People will object to the parentheses. Consider what they already accept:")
|
||||
(~docs/code :code (highlight
|
||||
(~docs/code :src (highlight
|
||||
";; Developers write this every day:\nhttps://api.site.com/v2/users/123/posts?filter=published&sort=date&order=desc&limit=10\n\n;; And 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"))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user