Merge worktree-api-urls: rewrite SX URLs documentation page
This commit is contained in:
603
sx/sx/sx-urls.sx
603
sx/sx/sx-urls.sx
@@ -38,10 +38,17 @@
|
||||
";; 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"))
|
||||
|
||||
(p "More examples:")
|
||||
(~docs/code :code (highlight
|
||||
";; Geography section, hypermedia subsection, examples area, progress bar page\n/(geography.(hypermedia.(example.progress-bar)))\n→ (geography (hypermedia (example progress-bar)))\n\n;; Language section, spec subsection, signals page\n/(language.(spec.signals))\n→ (language (spec signals))\n\n;; Etc section, essay subsection, specific essay\n/(etc.(essay.sx-sucks))\n→ (etc (essay sx-sucks))"
|
||||
"lisp"))
|
||||
(p "More examples — click any to navigate:")
|
||||
(div :class "space-y-2"
|
||||
(p (a :href "/sx/(geography.(hypermedia.(example.progress-bar)))"
|
||||
:class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(hypermedia.(example.progress-bar)))") " — progress bar example")
|
||||
(p (a :href "/sx/(language.(spec.signals))"
|
||||
:class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(language.(spec.signals))") " — signals spec")
|
||||
(p (a :href "/sx/(etc.(essay.sx-sucks))"
|
||||
:class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(etc.(essay.sx-sucks))") " — the honest critique"))
|
||||
|
||||
(p "The dot-to-space transform is the " (em "only") " URL-specific syntax. "
|
||||
"Everything else is standard s-expression parsing."))
|
||||
@@ -68,231 +75,415 @@
|
||||
"Sections are functions that receive the result of their inner expressions.")
|
||||
|
||||
(~docs/subsection :title "Section functions"
|
||||
(p "Top-level sections — " (code "geography") ", " (code "language") ", "
|
||||
(code "applications") ", " (code "etc") " — are structural. "
|
||||
(p "Top-level sections are structural. "
|
||||
"Called with no arguments, they return their index page. "
|
||||
"Called with content, they pass it through:")
|
||||
(~docs/code :code (highlight
|
||||
"/sx/(geography) ;; geography section index\n/(language) ;; language section index\n/(applications) ;; applications section index\n/(etc) ;; etc section index"
|
||||
"lisp")))
|
||||
"Called with content, they pass it through. Click to navigate:")
|
||||
(div :class "space-y-1 ml-4"
|
||||
(p (a :href "/sx/(language)" :class "font-mono text-violet-600 hover:underline text-sm" "/(language)"))
|
||||
(p (a :href "/sx/(geography)" :class "font-mono text-violet-600 hover:underline text-sm" "/(geography)"))
|
||||
(p (a :href "/sx/(applications)" :class "font-mono text-violet-600 hover:underline text-sm" "/(applications)"))
|
||||
(p (a :href "/sx/(etc)" :class "font-mono text-violet-600 hover:underline text-sm" "/(etc)"))))
|
||||
|
||||
(~docs/subsection :title "Sub-sections"
|
||||
(p "Sub-sections nest inside sections:")
|
||||
(~docs/code :code (highlight
|
||||
"/sx/(geography.(hypermedia)) ;; hypermedia sub-section index\n/(geography.(reactive)) ;; reactive islands sub-section\n/(geography.(isomorphism)) ;; isomorphism sub-section\n/(language.(doc)) ;; documentation sub-section\n/(language.(spec)) ;; specification sub-section\n/(language.(bootstrapper)) ;; bootstrappers sub-section\n/(applications.(cssx)) ;; CSSX sub-section\n/(applications.(protocol)) ;; protocols sub-section"
|
||||
"lisp")))
|
||||
(div :class "space-y-1 ml-4"
|
||||
(p (a :href "/sx/(geography.(hypermedia))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(hypermedia))"))
|
||||
(p (a :href "/sx/(geography.(reactive))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(reactive))"))
|
||||
(p (a :href "/sx/(geography.(isomorphism))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(isomorphism))"))
|
||||
(p (a :href "/sx/(language.(doc))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(language.(doc))"))
|
||||
(p (a :href "/sx/(language.(spec))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(language.(spec))"))
|
||||
(p (a :href "/sx/(applications.(cssx))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(applications.(cssx))"))))
|
||||
|
||||
(~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
|
||||
";; Documentation pages\n/(language.(doc.introduction)) ;; doc(\"introduction\")\n/(language.(doc.getting-started)) ;; doc(\"getting-started\")\n/(language.(doc.components)) ;; doc(\"components\")\n/(language.(doc.evaluator)) ;; doc(\"evaluator\")\n/(language.(doc.primitives)) ;; doc(\"primitives\")\n/(language.(doc.special-forms)) ;; doc(\"special-forms\")\n/(language.(doc.server-rendering)) ;; doc(\"server-rendering\")\n\n;; Specification pages\n/(language.(spec.core)) ;; spec(\"core\")\n/(language.(spec.parser)) ;; spec(\"parser\")\n/(language.(spec.evaluator)) ;; spec(\"evaluator\")\n/(language.(spec.signals)) ;; spec(\"signals\")\n\n;; Deeply nested pages\n/(language.(spec.(explore.signals))) ;; explore(\"signals\") inside spec\n/(geography.(hypermedia.(example.progress-bar))) ;; example(\"progress-bar\")\n/(geography.(hypermedia.(reference.attributes))) ;; reference(\"attributes\")"
|
||||
"lisp")))
|
||||
";; 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"))
|
||||
|
||||
(~docs/subsection :title "All working URLs on this site"
|
||||
(p "Every link in the navigation tree is a live SX URL. Here is a sample:")
|
||||
(~docs/code :code (highlight
|
||||
";; Geography\n/(geography.(reactive)) ;; reactive islands overview\n/(geography.(reactive.demo)) ;; live demo\n/(geography.(hypermedia.(reference.attributes))) ;; attribute reference\n/(geography.(hypermedia.(example.click-to-load))) ;; click-to-load example\n/(geography.(hypermedia.(example.infinite-scroll))) ;; infinite scroll\n/(geography.(isomorphism.bundle-analyzer)) ;; bundle analyzer\n/(geography.(isomorphism.streaming)) ;; streaming demo\n\n;; Language\n/(language.(doc.introduction)) ;; getting started\n/(language.(spec.core)) ;; core spec\n/(language.(spec.(explore.signals))) ;; spec explorer: signals\n/(language.(bootstrapper.python)) ;; python bootstrapper\n/(language.(bootstrapper.self-hosting)) ;; self-hosting bootstrapper\n/(language.(test.eval)) ;; evaluator tests\n/(language.(test.router)) ;; router tests\n\n;; Applications\n/(applications.(cssx)) ;; CSSX overview\n/(applications.(cssx.patterns)) ;; CSSX patterns\n/(applications.(protocol.wire-format)) ;; wire format protocol\n/(applications.(sx-urls)) ;; this page\n\n;; Etc\n/(etc.(essay.sx-sucks)) ;; the honest critique\n/(etc.(essay.self-defining-medium)) ;; the metacircular web\n/(etc.(philosophy.sx-manifesto)) ;; the manifesto\n/(etc.(plan.spec-explorer)) ;; spec explorer plan"
|
||||
"lisp"))))
|
||||
(p "Every page on this site:")
|
||||
(div :class "grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-1 mt-2"
|
||||
;; Language
|
||||
(div
|
||||
(p :class "font-semibold text-stone-700 text-sm mt-2 mb-1" "Language — Docs")
|
||||
(p (a :href "/sx/(language.(doc.introduction))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(doc.introduction))"))
|
||||
(p (a :href "/sx/(language.(doc.getting-started))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(doc.getting-started))"))
|
||||
(p (a :href "/sx/(language.(doc.components))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(doc.components))"))
|
||||
(p (a :href "/sx/(language.(doc.evaluator))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(doc.evaluator))"))
|
||||
(p (a :href "/sx/(language.(doc.primitives))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(doc.primitives))"))
|
||||
(p (a :href "/sx/(language.(doc.special-forms))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(doc.special-forms))"))
|
||||
(p (a :href "/sx/(language.(doc.server-rendering))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(doc.server-rendering))"))
|
||||
(p :class "font-semibold text-stone-700 text-sm mt-2 mb-1" "Language — Specs")
|
||||
(p (a :href "/sx/(language.(spec.core))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(spec.core))"))
|
||||
(p (a :href "/sx/(language.(spec.parser))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(spec.parser))"))
|
||||
(p (a :href "/sx/(language.(spec.evaluator))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(spec.evaluator))"))
|
||||
(p (a :href "/sx/(language.(spec.signals))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(spec.signals))"))
|
||||
(p (a :href "/sx/(language.(spec.(explore.signals)))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(spec.(explore.signals)))"))
|
||||
(p :class "font-semibold text-stone-700 text-sm mt-2 mb-1" "Language — Other")
|
||||
(p (a :href "/sx/(language.(bootstrapper.python))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(bootstrapper.python))"))
|
||||
(p (a :href "/sx/(language.(bootstrapper.self-hosting))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(bootstrapper.self-hosting))"))
|
||||
(p (a :href "/sx/(language.(test.eval))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(test.eval))"))
|
||||
(p (a :href "/sx/(language.(test.router))" :class "font-mono text-violet-600 hover:underline text-xs" "/(language.(test.router))")))
|
||||
;; Geography + Applications + Etc
|
||||
(div
|
||||
(p :class "font-semibold text-stone-700 text-sm mt-2 mb-1" "Geography")
|
||||
(p (a :href "/sx/(geography.(reactive.demo))" :class "font-mono text-violet-600 hover:underline text-xs" "/(geography.(reactive.demo))"))
|
||||
(p (a :href "/sx/(geography.(hypermedia.(reference.attributes)))" :class "font-mono text-violet-600 hover:underline text-xs" "/(geography.(hypermedia.(reference.attributes)))"))
|
||||
(p (a :href "/sx/(geography.(hypermedia.(example.click-to-load)))" :class "font-mono text-violet-600 hover:underline text-xs" "/(geography.(hypermedia.(example.click-to-load)))"))
|
||||
(p (a :href "/sx/(geography.(hypermedia.(example.infinite-scroll)))" :class "font-mono text-violet-600 hover:underline text-xs" "/(geography.(hypermedia.(example.infinite-scroll)))"))
|
||||
(p (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "font-mono text-violet-600 hover:underline text-xs" "/(geography.(isomorphism.bundle-analyzer))"))
|
||||
(p :class "font-semibold text-stone-700 text-sm mt-2 mb-1" "Applications")
|
||||
(p (a :href "/sx/(applications.(sx-urls))" :class "font-mono text-violet-600 hover:underline text-xs" "/(applications.(sx-urls))") " — this page")
|
||||
(p (a :href "/sx/(applications.(cssx.patterns))" :class "font-mono text-violet-600 hover:underline text-xs" "/(applications.(cssx.patterns))"))
|
||||
(p (a :href "/sx/(applications.(protocol.wire-format))" :class "font-mono text-violet-600 hover:underline text-xs" "/(applications.(protocol.wire-format))"))
|
||||
(p :class "font-semibold text-stone-700 text-sm mt-2 mb-1" "Etc")
|
||||
(p (a :href "/sx/(etc.(essay.sx-sucks))" :class "font-mono text-violet-600 hover:underline text-xs" "/(etc.(essay.sx-sucks))"))
|
||||
(p (a :href "/sx/(etc.(essay.self-defining-medium))" :class "font-mono text-violet-600 hover:underline text-xs" "/(etc.(essay.self-defining-medium))"))
|
||||
(p (a :href "/sx/(etc.(philosophy.sx-manifesto))" :class "font-mono text-violet-600 hover:underline text-xs" "/(etc.(philosophy.sx-manifesto))"))
|
||||
(p (a :href "/sx/(etc.(plan.spec-explorer))" :class "font-mono text-violet-600 hover:underline text-xs" "/(etc.(plan.spec-explorer))"))
|
||||
(p (a :href "/sx/(etc.(plan.sx-urls))" :class "font-mono text-violet-600 hover:underline text-xs" "/(etc.(plan.sx-urls))"))))))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "Direct Component URLs" :id "direct"
|
||||
(p "Every " (code "defcomp") " in the component environment is directly "
|
||||
"addressable by its " (code "~plans/content-addressed-components/name") " — no page function, no routing wiring, no case statement.")
|
||||
"addressable by its " (code "~name") " — no page function, no routing wiring, no case statement.")
|
||||
(~docs/code :code (highlight
|
||||
";; Any component is instantly a URL:\n/(~essays/sx-sucks/essay-sx-sucks) ;; renders the essay component\n/(~plans/sx-urls/plan-sx-urls-content) ;; this documentation page\n/(~docs-content/docs-evaluator-content) ;; the evaluator docs\n/(~analyzer/bundle-analyzer-content) ;; the bundle analyzer tool\n/(~reactive-islands/demo/reactive-islands-demo-content) ;; the reactive demo\n\n;; This is not a convenience — it's a consequence of the architecture.\n;; Components are values. Values are addressable. The URL evaluator\n;; sees ~plans/content-addressed-components/name, looks it up in env, evaluates it, wraps in ~sx-doc."
|
||||
";; 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 — click any:")
|
||||
(div :class "space-y-1 ml-4"
|
||||
(p (a :href "/sx/(~essays/sx-sucks/essay-sx-sucks)" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(~essays/sx-sucks/essay-sx-sucks)"))
|
||||
(p (a :href "/sx/(~plans/sx-urls/plan-sx-urls-content)" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(~plans/sx-urls/plan-sx-urls-content)"))
|
||||
(p (a :href "/sx/(~docs-content/docs-evaluator-content)" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(~docs-content/docs-evaluator-content)")))
|
||||
(p "New components are URL-accessible the moment they are defined. "
|
||||
"No registration, no routing table update, no deploy. "
|
||||
"Define a " (code "defcomp") ", refresh the page, visit " (code "/sx/(~your-component)") "."))
|
||||
"No registration, no routing table update, no deploy."))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "Relative URLs" :id "relative"
|
||||
(p "SX URLs support a structural algebra for relative navigation. "
|
||||
"Instead of manipulating path strings, you navigate the nesting hierarchy. "
|
||||
"The leading dots tell you how many levels to move.")
|
||||
(~docs/section :title "Routing Is Functional Application" :id "functional"
|
||||
(p "A URL is a function call. The server evaluates it. "
|
||||
"There is no routing table, no pattern matching, no controller dispatch. "
|
||||
"The URL " (em "is") " the code.")
|
||||
|
||||
(~docs/subsection :title "Same level: one dot"
|
||||
(p "A single dot means \"at the current nesting level\" — append a sibling:")
|
||||
(~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
|
||||
";; Current page: /(geography.(hypermedia.(example.progress-bar)))\n\n(.click-to-load)\n;; → /(geography.(hypermedia.(example.progress-bar.click-to-load)))\n;; Appends 'click-to-load' at the same level as 'progress-bar'\n\n;; In a link:\n(a :href \"(.click-to-load)\" \"Next: Click to Load\")\n;; The client resolves this relative to the current page"
|
||||
";; 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
|
||||
"(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 "Up one level: two dots"
|
||||
(p "Two dots pop one level of nesting — like " (code "cd ..") " in a filesystem:")
|
||||
(~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
|
||||
";; Current: /(geography.(hypermedia.(example.progress-bar)))\n\n(..)\n;; → /(geography.(hypermedia.(example)))\n;; Strips the innermost expression, keeps the rest\n\n;; Up and over — pop then append:\n(..inline-edit)\n;; → /(geography.(hypermedia.(example.inline-edit)))\n;; Pop 'progress-bar', append 'inline-edit'\n\n;; Navigate to a sibling sub-section:\n(..reference)\n;; → /(geography.(hypermedia.(reference)))\n;; Pop the 'example' level, land on 'reference'"
|
||||
"lisp")))
|
||||
"(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 "
|
||||
(em "unevaluated ASTs") ". The router evaluates the page function to get the AST, "
|
||||
"then passes it through " (code "aser") " (the wire-format renderer) which expands "
|
||||
"components and handles HTML tags."))
|
||||
|
||||
(~docs/subsection :title "Up multiple levels"
|
||||
(p "More dots pop more levels. N dots = pop N-1 levels:")
|
||||
(~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
|
||||
";; Current: /(geography.(hypermedia.(example.progress-bar)))\n\n(...)\n;; → /(geography.(hypermedia))\n;; 3 dots = pop 2 levels (example + progress-bar)\n\n(....)\n;; → /(geography)\n;; 4 dots = pop 3 levels\n\n(.....)\n;; → /\n;; 5 dots = pop 4 levels (all the way to root)"
|
||||
"lisp")))
|
||||
"(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 "
|
||||
(a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "text-violet-600 hover:underline"
|
||||
"/(geography.(isomorphism.bundle-analyzer))") " to see this in action — "
|
||||
"the page function calls " (code "bundle-analyzer-data") ", "
|
||||
"which analyzes the live component environment and returns statistics.")))
|
||||
|
||||
(~docs/subsection :title "Up and sideways"
|
||||
(p "Combine dots with a slug to navigate up, then into a different branch:")
|
||||
(~docs/code :code (highlight
|
||||
";; Current: /(language.(spec.(explore.signals)))\n\n(..eval)\n;; → /(language.(spec.eval))\n;; Pop one level (explore.signals → spec), append eval\n\n(...doc.introduction)\n;; → /(language.(doc.introduction))\n;; Pop two levels (to language), descend into doc.introduction\n\n(....geography.(reactive.demo))\n;; → /(geography.(reactive.demo))\n;; Pop three levels (to root), into a completely different section"
|
||||
"lisp")))
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "Server-Side: URL → eval → Response" :id "server"
|
||||
(p "The Python route handler is generic infrastructure. "
|
||||
"All routing semantics live in the SX spec (" (code "router.sx") "). "
|
||||
"The handler does four things:")
|
||||
|
||||
(~docs/subsection :title "Bare-dot shorthand"
|
||||
(p "For brevity, the outer parentheses are optional. "
|
||||
"A URL starting with " (code ".") " is automatically wrapped:")
|
||||
(~docs/subsection :title "The handler"
|
||||
(~docs/code :code (highlight
|
||||
";; These pairs are identical:\n.click-to-load → (.click-to-load)\n.. → (..)\n..inline-edit → (..inline-edit)\n... → (...)\n\n;; In practice:\n(a :href \"..inline-edit\" \"Inline Edit\")\n;; is more natural than:\n(a :href \"(..inline-edit)\" \"Inline Edit\")"
|
||||
";; 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 "
|
||||
(code "sx_ref.py") " — the same function the JavaScript client uses for "
|
||||
"client-side route resolution. One spec, two hosts."))
|
||||
|
||||
(~docs/subsection :title "Auto-quoting: soft eval"
|
||||
(p "When the parser reads " (code "(language (doc introduction))")
|
||||
", 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
|
||||
";; 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 — "
|
||||
"not a hardcoded list. If you define a new page function, "
|
||||
"it's immediately recognized as a function name, not a slug."))
|
||||
|
||||
(~docs/subsection :title "Defhandler endpoints"
|
||||
(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
|
||||
";; 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:")
|
||||
(div :class "space-y-1 ml-4"
|
||||
(p (a :href "/sx/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(hypermedia.(reference.(api.time))))") " — current server time")
|
||||
(p (a :href "/sx/(geography.(hypermedia.(reference.(api.swap-item))))"
|
||||
:class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(hypermedia.(reference.(api.swap-item))))") " — a timestamped item")
|
||||
(p (a :href "/sx/(geography.(hypermedia.(example.(api.click))))"
|
||||
:class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(hypermedia.(example.(api.click))))") " — click-to-load demo content"))
|
||||
|
||||
(p "Notice the URL structure: the API endpoint " (em "is") " a nested expression. "
|
||||
(code "(geography (hypermedia (reference (api time))))") " — "
|
||||
"the handler lives at the same address as its content. "
|
||||
"There is no separation between \"the API\" and \"the page.\"")
|
||||
|
||||
(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
|
||||
";; 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"))))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "Keyword Arguments in URLs" :id "keywords"
|
||||
(p "URLs can carry keyword arguments — named parameters that modify the innermost expression. "
|
||||
"Keywords use the same " (code ":name") " syntax as SX keyword arguments.")
|
||||
(~docs/section :title "Client-Side: eval in the Browser" :id "client"
|
||||
(p "The same URL algebra runs in the browser. When you click a link on this site, "
|
||||
"the client-side router checks if the page can be rendered locally "
|
||||
"without a server round-trip.")
|
||||
|
||||
(~docs/subsection :title "Setting a keyword"
|
||||
(p "Use " (code ".:key.value") " to set a parameter:")
|
||||
(~docs/subsection :title "The client route check"
|
||||
(~docs/code :code (highlight
|
||||
";; Add page number to a spec explorer URL:\n/(language.(spec.(explore.signals.:page.3)))\n→ (language (spec (explore signals :page 3)))\n\n;; Format parameter on a doc page:\n/(language.(doc.primitives.:format.\"print\"))\n→ (language (doc primitives :format \"print\"))\n\n;; Section parameter:\n/(language.(spec.(explore.signals.:section.\"batch\")))\n→ (language (spec (explore signals :section \"batch\")))"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Relative keywords"
|
||||
(p "Keywords combine with relative navigation. "
|
||||
"Set a keyword on the current page without changing the path:")
|
||||
(~docs/code :code (highlight
|
||||
";; Current: /(language.(spec.(explore.signals.:page.1)))\n\n;; Go to page 4 (set keyword, no structural navigation):\n.:page.4\n;; → /(language.(spec.(explore.signals.:page.4)))\n;; One dot + keyword-only = modify current page's keywords\n\n;; Navigate to a sibling AND set a keyword:\n..eval.:page.1\n;; → /(language.(spec.(eval.:page.1)))\n;; Pop one level, append 'eval', set :page to 1"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Delta values"
|
||||
(p "Prefix a keyword value with " (code "+") " or " (code "-") " "
|
||||
"to apply a numeric delta to the current value:")
|
||||
(~docs/code :code (highlight
|
||||
";; Current: /(language.(spec.(explore.signals.:page.3)))\n\n;; Next page:\n.:page.+1\n;; → /(language.(spec.(explore.signals.:page.4)))\n;; Reads current :page (3), adds 1, sets to 4\n\n;; Previous page:\n.:page.-1\n;; → /(language.(spec.(explore.signals.:page.2)))\n;; Reads current :page (3), subtracts 1, sets to 2\n\n;; Jump forward 5:\n.:page.+5\n;; → /(language.(spec.(explore.signals.:page.8)))"
|
||||
";; 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 "This is pagination in the URL algebra. No JavaScript, no event handlers, "
|
||||
"no state management. Just a relative URL that says \"same page, next page number.\"")))
|
||||
(p "Pure pages (no " (code "has-data") " flag) render instantly in the browser. "
|
||||
"Data pages fetch from the server, then render client-side. "
|
||||
"Layout changes always go to the server for OOB shell updates."))
|
||||
|
||||
(~docs/subsection :title "URL parsing in the browser"
|
||||
(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
|
||||
";; 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 "
|
||||
"the same URL parsing, auto-quoting, and resolution logic. "
|
||||
"The spec is in " (code "router.sx") "; the bootstrappers compile it to "
|
||||
(code "sx_ref.py") " and " (code "sx-browser.js") ".")))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "Relative URLs as Function Application" :id "relative"
|
||||
(p "Relative URLs are not string manipulation — they are structural transforms "
|
||||
"on the expression tree. The dots tell you how many levels of nesting to pop. "
|
||||
"This is function application over URL structure.")
|
||||
|
||||
(~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
|
||||
";; 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."))
|
||||
|
||||
(~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
|
||||
";; 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
|
||||
";; 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.demo → /(geography.(reactive.demo)) ;; 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
|
||||
";; 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") " — "
|
||||
"they navigate the expression tree, not the string representation."))
|
||||
|
||||
(~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
|
||||
";; 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")))
|
||||
|
||||
(~docs/subsection :title "The resolve-relative-url spec"
|
||||
(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
|
||||
";; 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. "
|
||||
"Server-side redirects and client-side link resolution use the same code.")))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "URL Special Forms" :id "special-forms"
|
||||
(p "Special forms are meta-operations on URLs, distinguished by the "
|
||||
(code "!") " prefix. They transform how content is resolved or displayed. "
|
||||
"The " (code "!") " sigil prevents collisions with section or page function names.")
|
||||
(p "Special forms are meta-operations on URLs. They transform how content "
|
||||
"is resolved or displayed. The " (code "!") " prefix distinguishes them "
|
||||
"from section and page function names.")
|
||||
|
||||
(div :class "overflow-x-auto mt-4 mb-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 the defcomp source code"))
|
||||
(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" "Dependencies, CSS, render plan, I/O"))
|
||||
(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 comparison"))
|
||||
(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 layout wrapping — raw content"))
|
||||
(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")))))
|
||||
(~docs/subsection :title "The six special forms"
|
||||
(div :class "overflow-x-auto mt-4 mb-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 URL")
|
||||
(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-xs" "/sx/(!source.(~essays/sx-sucks/essay-sx-sucks))")
|
||||
(td :class "py-2 px-3" "Show the defcomp source code"))
|
||||
(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-xs" "/sx/(!inspect.(language.(doc.primitives)))")
|
||||
(td :class "py-2 px-3" "Dependencies, CSS footprint, render plan, IO refs"))
|
||||
(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-xs" "/sx/(!diff.(spec.signals).(spec.eval))")
|
||||
(td :class "py-2 px-3" "Side-by-side comparison of two expressions"))
|
||||
(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-xs" "/sx/(!search.\"define\".:in.(spec.signals))")
|
||||
(td :class "py-2 px-3" "Grep within a page or spec source"))
|
||||
(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-xs" "/sx/(!raw.(~some-component))")
|
||||
(td :class "py-2 px-3" "Skip " (code "~layouts/doc") " wrapping — return raw content"))
|
||||
(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-xs" "/sx/(!json.(language.(doc.primitives)))")
|
||||
(td :class "py-2 px-3" "Return data as JSON instead of rendered content"))))))
|
||||
|
||||
(p "SX has four sigils, each marking a different kind of name:")
|
||||
(ul :class "space-y-1 text-stone-600 list-disc pl-5"
|
||||
(li (code "~") " — component (" (code "~card") ", " (code "~essays/sx-sucks/essay-sx-sucks") ")")
|
||||
(li (code ":") " — keyword (" (code ":title") ", " (code ":page") ")")
|
||||
(li (code ".") " — relative navigation (" (code ".slug") ", " (code "..") ")")
|
||||
(li (code "!") " — URL special form (" (code "!source") ", " (code "!inspect") ")")))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "SX URLs in Hypermedia" :id "hypermedia"
|
||||
(p "SX URLs integrate naturally with the SX hypermedia system. "
|
||||
"Every hypermedia attribute accepts SX URLs — absolute, relative, or special forms.")
|
||||
|
||||
(~docs/subsection :title "Links"
|
||||
(p "Standard anchor tags with SX URL hrefs:")
|
||||
(~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
|
||||
";; Absolute links (the site navigation uses these):\n(a :href \"/sx/(language.(doc.introduction))\" \"Introduction\")\n(a :href \"/sx/(etc.(essay.sx-sucks))\" \"SX Sucks\")\n\n;; Relative links (navigate from current page):\n(a :href \"..inline-edit\" \"Inline Edit\") ;; up and over\n(a :href \".getting-started\" \"Getting Started\") ;; same level\n(a :href \"..\" \"Back\") ;; up one level\n\n;; Direct component links:\n(a :href \"/sx/(~essays/self-defining-medium/essay-self-defining-medium)\" \"The True Hypermedium\")"
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "sx-get — HTMX-style fetching"
|
||||
(p (code "sx-get") " fetches content from an SX URL and swaps it into the DOM. "
|
||||
"SX URLs work exactly like path URLs:")
|
||||
(~docs/code :code (highlight
|
||||
";; Fetch and swap a page 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;; Relative sx-get — resolve relative to the current page:\n(div :sx-get \"..inline-edit\"\n :sx-trigger \"click\"\n :sx-target \"#content\"\n :sx-swap \"innerHTML\"\n \"Load Inline Edit\")\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 "sx-boost — progressive enhancement"
|
||||
(p (code "sx-boost") " upgrades regular links to use HTMX-style fetch+swap "
|
||||
"instead of full-page navigation:")
|
||||
(~docs/code :code (highlight
|
||||
";; A navigation list with boosted links:\n(nav :sx-boost \"true\"\n (ul\n (li (a :href \"/sx/(language.(doc.introduction))\" \"Introduction\"))\n (li (a :href \"/sx/(language.(doc.components))\" \"Components\"))\n (li (a :href \"/sx/(language.(doc.evaluator))\" \"Evaluator\"))))\n\n;; Clicking any link fetches content via SX URL and swaps,\n;; rather than triggering a full page load.\n;; URL bar updates. Back button works. No JavaScript needed."
|
||||
"lisp")))
|
||||
|
||||
(~docs/subsection :title "Pagination pattern"
|
||||
(p "The relative URL algebra makes pagination trivial. "
|
||||
"No state, no event handlers — just URLs:")
|
||||
(~docs/code :code (highlight
|
||||
";; Current URL: /(language.(spec.(explore.signals.:page.3)))\n\n(defcomp ~sx-urls/paginator (&key current-page total-pages)\n (nav :class \"flex gap-2\"\n (when (> current-page 1)\n (a :href \".:page.-1\" :class \"px-3 py-1 border rounded\"\n \"Previous\"))\n (span :class \"px-3 py-1\"\n (str \"Page \" current-page \" of \" total-pages))\n (when (< current-page total-pages)\n (a :href \".:page.+1\" :class \"px-3 py-1 border rounded\"\n \"Next\"))))\n\n;; \"Previous\" resolves to .:page.-1 → :page goes from 3 to 2\n;; \"Next\" resolves to .:page.+1 → :page goes from 3 to 4\n;; Each link is a static href. Server renders the right page.\n;; Back button works. Bookmarkable. Shareable. Cacheable."
|
||||
";; 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 "Compare this to the typical JavaScript pagination component with "
|
||||
(code "useState") ", " (code "useEffect") ", " (code "onClick") " handlers, "
|
||||
"URL sync logic, and loading states. "
|
||||
"In SX, pagination is a URL transform."))
|
||||
(p "Special forms compose with the rest of the URL algebra. "
|
||||
(code "!source") " can wrap any valid SX URL expression — "
|
||||
"a page function call, a direct component, or a nested section:"))
|
||||
|
||||
(~docs/subsection :title "Sibling navigation"
|
||||
(p "Navigate between sibling pages using relative two-dot URLs:")
|
||||
(~docs/code :code (highlight
|
||||
";; Current: /(language.(doc.components))\n;; Sibling pages: introduction, getting-started, components, evaluator, ...\n\n(nav :class \"flex justify-between\"\n (a :href \"..getting-started\" :class \"text-violet-600\"\n \"← Getting Started\")\n (a :href \"..evaluator\" :class \"text-violet-600\"\n \"Evaluator →\"))\n\n;; '..getting-started' from /(language.(doc.components))\n;; = pop 'components', append 'getting-started'\n;; = /(language.(doc.getting-started))"
|
||||
"lisp"))))
|
||||
(~docs/subsection :title "The four SX sigils"
|
||||
(p "SX uses four single-character sigils, each marking a different kind of name:")
|
||||
(div :class "overflow-x-auto mt-2 mb-2"
|
||||
(table :class "w-full text-sm text-left"
|
||||
(thead
|
||||
(tr :class "border-b border-stone-200"
|
||||
(th :class "py-1 px-3 font-semibold text-stone-700" "Sigil")
|
||||
(th :class "py-1 px-3 font-semibold text-stone-700" "Meaning")
|
||||
(th :class "py-1 px-3 font-semibold text-stone-700" "Examples")))
|
||||
(tbody :class "text-stone-600"
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-1 px-3 font-mono text-lg text-violet-700" "~")
|
||||
(td :class "py-1 px-3" "Component")
|
||||
(td :class "py-1 px-3 font-mono text-xs" "~layouts/doc ~essays/sx-sucks/essay-sx-sucks"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-1 px-3 font-mono text-lg text-violet-700" ":")
|
||||
(td :class "py-1 px-3" "Keyword")
|
||||
(td :class "py-1 px-3 font-mono text-xs" ":title :page :format"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-1 px-3 font-mono text-lg text-violet-700" ".")
|
||||
(td :class "py-1 px-3" "Relative navigation")
|
||||
(td :class "py-1 px-3 font-mono text-xs" ".slug .. ...intro"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-1 px-3 font-mono text-lg text-violet-700" "!")
|
||||
(td :class "py-1 px-3" "URL special form")
|
||||
(td :class "py-1 px-3 font-mono text-xs" "!source !inspect !diff")))))))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "The Evaluation Model" :id "eval"
|
||||
(p "When the server receives an SX URL, it evaluates it in four steps:")
|
||||
|
||||
(~docs/subsection :title "Step 1: Parse"
|
||||
(p "Strip the leading " (code "/") ", replace dots with spaces, parse as SX:")
|
||||
(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
|
||||
"/sx/(language.(doc.introduction))\n→ strip / → (language.(doc.introduction))\n→ dots to spaces → (language (doc introduction))\n→ parse → [Symbol(\"language\"), [Symbol(\"doc\"), Symbol(\"introduction\")]]"
|
||||
";; 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:")
|
||||
(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
|
||||
";; 'language' is a known section function → stays as Symbol\n;; 'doc' is a known page function → stays as Symbol\n;; 'introduction' is NOT a known function → becomes \"introduction\"\n\n[Symbol(\"language\"), [Symbol(\"doc\"), \"introduction\"]]"
|
||||
"lisp"))
|
||||
(p "This is " (strong "soft eval") ": the evaluator treats unknown symbols "
|
||||
"as self-quoting strings rather than raising an error. "
|
||||
"Slugs work naturally without explicit quoting."))
|
||||
";; '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:")
|
||||
(p "Standard inside-out evaluation — same as any SX expression:")
|
||||
(~docs/code :code (highlight
|
||||
";; 1. Eval \"introduction\" → \"introduction\" (string, self-evaluating)\n;; 2. Eval (doc \"introduction\") → call doc function → page content AST\n;; 3. Eval (language content) → call language function → passes content through"
|
||||
";; 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"
|
||||
(~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
|
||||
";; Final: (~layouts/doc :path \"(language (doc introduction))\" <content>)\n;; ~layouts/doc provides the layout shell, navigation, breadcrumbs, etc.\n;; The :path tells nav resolution which tree branch to 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")))
|
||||
|
||||
(p "The entire routing layer is one function. There is no routing table, "
|
||||
(p "The entire routing layer is one function call. There is no routing table, "
|
||||
"no URL pattern matching, no middleware chain. The URL is an expression. "
|
||||
"The server evaluates it. The result is a page."))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "SX URLs in Hypermedia" :id "hypermedia"
|
||||
(p "SX URLs integrate with the SX hypermedia attributes. "
|
||||
"Links, fetches, and progressive enhancement all accept SX URLs.")
|
||||
|
||||
(~docs/subsection :title "Links — click any to navigate"
|
||||
(p "Standard anchor tags with SX URL hrefs. Every link below is live:")
|
||||
(div :class "space-y-1 ml-4 mb-3"
|
||||
(p (a :href "/sx/(language.(doc.introduction))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(language.(doc.introduction))") " — Introduction")
|
||||
(p (a :href "/sx/(etc.(essay.sx-sucks))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(etc.(essay.sx-sucks))") " — SX Sucks (the honest critique)")
|
||||
(p (a :href "/sx/(geography.(hypermedia.(example.progress-bar)))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(hypermedia.(example.progress-bar)))") " — Progress bar demo")
|
||||
(p (a :href "/sx/(language.(test.router))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(language.(test.router))") " — Router spec tests (115 tests)")))
|
||||
|
||||
(~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
|
||||
";; 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
|
||||
";; 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"))))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "GraphSX: URLs as Queries" :id "graphsx"
|
||||
(p "The SX URL scheme is not just a routing convention — it is a query language. "
|
||||
@@ -320,14 +511,10 @@
|
||||
(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 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 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" "Arguments")
|
||||
(td :class "py-2 px-3" (code "doc(slug: \"intro\")"))
|
||||
(td :class "py-2 px-3" (code "(doc introduction)") " or " (code "(doc intro :format \"print\")")))
|
||||
(td :class "py-2 px-3" (code "(doc introduction)")))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "py-2 px-3 font-semibold" "Scoping")
|
||||
(td :class "py-2 px-3" "Flat — query-level only")
|
||||
@@ -349,39 +536,8 @@
|
||||
(p "And because SX URLs are GET requests, they are cacheable, bookmarkable, "
|
||||
"shareable, and indexable — everything GraphQL had to sacrifice by using POST."))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "Composability" :id "composability"
|
||||
(p "Because URLs are expressions, they compose. "
|
||||
"The algebra of URLs is the algebra of s-expressions.")
|
||||
|
||||
(~docs/code :code (highlight
|
||||
";; Two specs side by side:\n/(!diff.(language.(spec.signals)).(language.(spec.eval)))\n\n;; Source code of any page:\n/(!source.(~essays/sx-sucks/essay-sx-sucks))\n\n;; Inspect a page's dependency graph:\n/(!inspect.(language.(doc.primitives)))\n\n;; Search within a spec:\n/(!search.\"define\".:in.(language.(spec.signals)))\n\n;; The URL bar is a REPL:\n/(about) ;; the about page\n/(!source.(about)) ;; its source code\n\n;; Scoping composes — these are different queries:\n/(users.(posts.123.(filter.published))) ;; filter on posts\n/(users.posts.123.(filter.published)) ;; filter on the whole query"
|
||||
"lisp"))
|
||||
|
||||
(p "In the REST world, building a \"diff two specs\" page requires a "
|
||||
"custom endpoint, a controller, a route registration, "
|
||||
"and frontend code to load both specs. "
|
||||
"In SX, it is a URL."))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "Components as 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 with composition.")
|
||||
(~docs/code :code (highlight
|
||||
";; A component that fetches data and returns content:\n/(~get.latest-posts.(limit.5))\n\n;; The flow:\n;; 1. Server finds ~get in env\n;; 2. ~get queries the database (async IO)\n;; 3. Returns composed hypermedia\n;; 4. Router wraps in ~sx-doc\n\n;; Components compose naturally:\n/(~dashboard\n .(~get.latest-posts.(limit.5))\n .(~get.notifications)\n .(~stats.weekly))\n\n;; Each sub-expression is:\n;; - independently cacheable\n;; - independently reusable\n;; - independently testable\n;; Visit /(~get.latest-posts.(limit.5)) to test it in isolation."
|
||||
"lisp"))
|
||||
(p "This is what "
|
||||
(a :href "https://react.dev/reference/rsc/server-components" :class "text-violet-600 hover:underline" "React Server Components")
|
||||
" are trying to do — server-side data resolution composed with rendering. "
|
||||
"Except React needs a framework, a bundler, a serialization protocol, and "
|
||||
(code "\"use server\"") " pragmas. SX gets it from the evaluator."))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "HTTP Alignment" :id "http"
|
||||
(p "GraphQL sends queries as POST, even though reads are idempotent. "
|
||||
"This breaks HTTP semantics — POST implies side effects, "
|
||||
"so caches, CDNs, and intermediaries can't help.")
|
||||
(p "SX URLs are GET requests. The query is the URL path. "
|
||||
"This means:")
|
||||
(ul :class "space-y-1 text-stone-600 list-disc pl-5"
|
||||
@@ -389,36 +545,37 @@
|
||||
(li (strong "Bookmarkable") " — save " (code "/sx/(language.(spec.signals))") " in your browser")
|
||||
(li (strong "Shareable") " — paste it in chat, it works")
|
||||
(li (strong "Indexable") " — crawlers follow " (code "<a href>") " links")
|
||||
(li (strong "No client library") " — " (code "curl '/(language.(doc.intro))'") " returns content"))
|
||||
(li (strong "No client library") " — " (code "curl") " returns content"))
|
||||
|
||||
(p "HTTP verbs align naturally with SX URL semantics:")
|
||||
(~docs/code :code (highlight
|
||||
";; HTTP verbs align naturally:\n\n;; GET — pure evaluation, cacheable, bookmarkable:\nGET /(language.(doc.introduction))\n\n;; POST — side effects, mutations:\nPOST /(submit-form)\nPOST /(cart.(add.42))\n\n;; This is what REST always wanted but GraphQL abandoned."
|
||||
";; 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")))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "The Router Spec" :id "spec"
|
||||
(p "SX URLs are not a Python or JavaScript feature — they are specified in SX itself. "
|
||||
(p "SX URLs are specified in SX itself. "
|
||||
"The " (a :href "/sx/(language.(spec.router))" :class "text-violet-600 hover:underline" "router spec")
|
||||
" (" (code "router.sx") ") defines all URL parsing, matching, relative resolution, "
|
||||
"and special form detection as pure functions.")
|
||||
" (" (code "router.sx") ") defines URL parsing, matching, relative resolution, "
|
||||
"special form detection, and auto-quoting as pure functions.")
|
||||
|
||||
(p "Key functions from the spec:")
|
||||
(p "Key functions:")
|
||||
(~docs/code :code (highlight
|
||||
";; Parse a URL into a typed descriptor:\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\"\n \"inner\" \"(~essay)\" \"raw\" \"/sx/(!source.(~essay))\"}\n\n(parse-sx-url \"/sx/(~essays/sx-sucks/essay-sx-sucks)\")\n→ {\"type\" \"direct-component\" \"name\" \"~essay-sx-sucks\"\n \"raw\" \"/sx/(~essays/sx-sucks/essay-sx-sucks)\"}\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)))\"\n\n;; Keyword delta:\n(resolve-relative-url\n \"/sx/(language.(spec.(explore.signals.:page.3)))\"\n \".:page.+1\")\n→ \"/sx/(language.(spec.(explore.signals.:page.4)))\"\n\n;; Check URL type:\n(relative-sx-url? \"..eval\") → true\n(relative-sx-url? \"(.slug)\") → true\n(relative-sx-url? \"/sx/(foo)\") → false\n\n;; Special form inspection:\n(url-special-form? \"!source\") → true\n(url-special-form? \"inspect\") → false\n(url-special-form-name \"/sx/(!source.(~essay))\") → \"!source\"\n(url-special-form-inner \"/sx/(!source.(~essay))\") → \"(~essay)\""
|
||||
";; 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"))
|
||||
|
||||
(p "These functions are "
|
||||
(a :href "/sx/(language.(test.router))" :class "text-violet-600 hover:underline" "tested with 115 SX tests")
|
||||
" covering every edge case — structural navigation, keyword operations, "
|
||||
" covering structural navigation, keyword operations, "
|
||||
"delta values, special form parsing, and bare-dot shorthand. "
|
||||
"The spec is bootstrapped to both JavaScript (" (code "sx-browser.js") ") and "
|
||||
"Python (" (code "sx_ref.py") "), so client and server share the same URL algebra."))
|
||||
"The spec bootstraps to Python (" (code "sx_ref.py") ") and "
|
||||
"JavaScript (" (code "sx-browser.js") ") — server and client share the same URL algebra."))
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "The Lisp Tax" :id "parens"
|
||||
(p "People will object to the parentheses. Consider what they already accept:")
|
||||
(~docs/code :code (highlight
|
||||
";; Developers 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 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."
|
||||
";; 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"))
|
||||
|
||||
(p "The real question is who reads URLs:")
|
||||
@@ -432,10 +589,28 @@
|
||||
|
||||
;; -----------------------------------------------------------------
|
||||
(~docs/section :title "The Site Is a REPL" :id "repl"
|
||||
(p "The address bar is the input line. The page is the output.")
|
||||
(~docs/code :code (highlight
|
||||
"/sx/(about) ;; renders the about page\n/(!source.(about)) ;; returns its source code\n\n/(language.(spec.signals)) ;; the signals spec\n/(!inspect.(language.(spec.signals))) ;; its dependency graph\n\n/(~essays/sx-sucks/essay-sx-sucks) ;; the essay\n/(!source.(~essays/sx-sucks/essay-sx-sucks)) ;; its s-expression source\n\n;; The website IS a REPL. Every page is a function call.\n;; Every function call is a URL. Evaluation is navigation."
|
||||
"lisp"))
|
||||
(p "The address bar is the input line. The page is the output. Try these:")
|
||||
(div :class "space-y-2 ml-4 mb-4"
|
||||
(div
|
||||
(p :class "text-stone-500 text-xs" "A page:")
|
||||
(p (a :href "/sx/(language.(spec.signals))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(language.(spec.signals))")))
|
||||
(div
|
||||
(p :class "text-stone-500 text-xs" "Its tests:")
|
||||
(p (a :href "/sx/(language.(test.router))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(language.(test.router))")))
|
||||
(div
|
||||
(p :class "text-stone-500 text-xs" "A component directly:")
|
||||
(p (a :href "/sx/(~essays/sx-sucks/essay-sx-sucks)" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(~essays/sx-sucks/essay-sx-sucks)")))
|
||||
(div
|
||||
(p :class "text-stone-500 text-xs" "An API endpoint:")
|
||||
(p (a :href "/sx/(geography.(hypermedia.(reference.(api.time))))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(hypermedia.(reference.(api.time))))")))
|
||||
(div
|
||||
(p :class "text-stone-500 text-xs" "A tool that analyzes the live system:")
|
||||
(p (a :href "/sx/(geography.(isomorphism.bundle-analyzer))" :class "font-mono text-violet-600 hover:underline text-sm"
|
||||
"/(geography.(isomorphism.bundle-analyzer))"))))
|
||||
(p "You do not need to explain what SX is. "
|
||||
"Show someone a URL, and they immediately understand the philosophy. "
|
||||
"The entire site is a self-hosting demonstration — "
|
||||
|
||||
Reference in New Issue
Block a user