;; SX Expression URLs — GraphSX
;; Plan: replace path-based routing with s-expression URLs where the URL
;; IS the query, the render instruction, and the address — all at once.

(defcomp ()
  (~docs/page :title "SX Expression URLs"

    (~docs/section :title "Vision" :id "vision"
      (p "URLs become s-expressions. The entire routing layer collapses into eval. "
         "Every page is a function, every URL is a function call, and the nav tree hierarchy "
         "is encoded directly in the nesting of the expression.")
      (p "Current URLs like " (code "/language/docs/introduction") " become "
         (code "/sx/(language.(doc.introduction))") ". Dots replace spaces as the URL-friendly "
         "separator — they are unreserved in RFC 3986, never percent-encoded, and visually clean. "
         "The parser treats dot as whitespace: " (code "s/./ /") " before parsing as SX.")
      (~docs/code :src (highlight
        ";; Current → SX URLs (dots = spaces)\n/language/specs/signals        →  /(language.(spec.signals))\n/language/specs/explore/signals →  /(language.(spec.(explore.signals)))\n/language/docs/introduction    →  /(language.(doc.introduction))\n/etc/plans/spec-explorer       →  /(etc.(plan.spec-explorer))\n\n;; Direct component access — any defcomp is addressable\n/(~essays/sx-sucks/essay-sx-sucks)\n/(~plans/sx-urls/plan-sx-urls-content)\n/(~analyzer/bundle-analyzer-content)"
        "lisp")))

    (~docs/section :title "Scoping — The 30-Year Ambiguity, Fixed" :id "scoping"
      (p "REST URLs have an inherent ambiguity: does a filter/parameter apply to "
         "the last segment, or the whole path? Consider:")
      (~docs/code :src (highlight
        ";; REST — ambiguous:\n/users/123/posts?filter=published\n;; Is the filter scoped to posts? Or to the user? Or the whole query?\n;; Nobody knows. Conventions vary. Documentation required.\n\n;; SX URLs — explicit scoping via nesting:\n/(hello.(sailor.(filter.hhh)))   ;; filter scoped to sailor\n/(hello.sailor.(filter.hhh))     ;; filter scoped to hello\n\n;; These mean different things, both expressible.\n;; Parens make scope visible. No ambiguity. No documentation needed."
        "lisp"))
      (p "This is not a minor syntactic preference. REST has never been able to express "
         "the difference between a parameter scoped to a sub-resource and one scoped to the "
         "parent. Query strings are flat — " (code "?filter=x") " applies to... what, exactly? "
         "The last path segment? The whole URL? It depends on the API.")
      (p "S-expression nesting makes scope " (em "structural") ". "
         "The paren boundaries are the scope boundaries. "
         "What took REST 30 years of convention documents to approximate, "
         "SX URLs express in the syntax itself."))

    (~docs/section :title "Dots as URL-Safe Whitespace" :id "dots"
      (p "Spaces in URLs are ugly — they become " (code "%20") " in copy-paste, curl, logs, and proxies. "
         "Dots are unreserved in RFC 3986, never encoded, and read naturally as \"drill down.\"")
      (p "The rule is simple: " (strong "dot = space, nothing more") ". "
         "Parens carry all the structural meaning. Dots are syntactic sugar for URLs only:")
      (~docs/code :src (highlight
        ";; These are identical after dot→space transform:\n/(language.(doc.introduction))     → (language (doc introduction))\n/(geography.(hypermedia.(reference.attributes)))\n                                   → (geography (hypermedia (reference attributes)))\n\n;; Parens are still required for nesting:\n/(language.doc.introduction)       → (language doc introduction)\n;;                                  = language(\"doc\", \"introduction\") — WRONG\n\n;; Correct nesting:\n/(language.(doc.introduction))     → (language (doc introduction))\n;;                                  = language(doc(\"introduction\")) — RIGHT"
        "lisp"))
      (p "The server's URL handler does one thing before parsing: "
         (code "url_expr = raw_path[1:].replace('.', ' ')") ". Then standard SX parsing takes over."))

    (~docs/section :title "The Lisp Tax" :id "parens"
      (p "People will hate the parentheses. But consider what developers already accept:")
      (~docs/code :src (highlight
        ";; Developers happily 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 they 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: who is reading these URLs?")
      (ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
        (li (strong "End users") " barely look at URLs anymore — they live in address bars people ignore")
        (li (strong "Developers") " will love it once they get it — it is powerful and consistent")
        (li (strong "Crawlers and bots") " do not care at all"))
      (p "And this site is " (em "about SX, implemented in SX") ". "
         "The audience is exactly the people who will love the parens. "
         "Every URL on the site is a live example of SX in action. "
         "Visiting a page is evaluating an expression."))

    (~docs/section :title "The Site Is a REPL" :id "repl"
      (p "The address bar becomes the input line of a REPL. The page is the output.")
      (~docs/code :src (highlight
        "/sx/(about)                              ;; renders the about page\n/(source.(about))                      ;; returns the SX source for the about page\n/(eval.(source.(about)))               ;; re-evaluates it live\n\n;; The killer demo:\n/(eval.(map.double.(list.1.2.3)))      ;; actually returns (2 4 6)\n\n;; The website IS a REPL. The address bar IS the input."
        "lisp"))
      (p "You do not need to explain what SX is. You show someone a URL and they "
         "immediately understand the entire language and philosophy. "
         "The whole site becomes a self-hosting proof of concept — "
         "that is not just elegant, that is the pitch."))

    (~docs/section :title "Components as Query 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 Lispy and composable.")
      (~docs/code :src (highlight
        ";; ~get is a component that fetches, processes, and returns\n/(~get.everything-under-the-sun)\n\n;; The flow:\n;; 1. Server finds ~get component in env\n;; 2. ~get makes onward queries\n;; 3. Processes and transforms results\n;; 4. Returns composed hypermedia\n\n;; Because it's all SX, you nest and compose:\n/(~page.home\n  .(~hero.banner)\n  .(~get.latest-posts.(limit.5))\n  .(~get.featured.(filter.pinned)))\n\n;; Each ~component is independently:\n;; - cacheable (by its expression)\n;; - reusable (same component, different args)\n;; - testable (evaluate in isolation)"
        "lisp"))
      (p "This is what React Server Components are trying to do — server-side data resolution "
         "composed with client-side rendering. Except React needs a framework, a bundler, "
         "a serialization protocol, and \"use server\" pragmas. "
         "SX gets it from a sigil and an evaluator."))

    (~docs/section :title "HTTP Semantics — REST Re-Aligned" :id "http"
      (p "GraphQL uses POST for queries even though they are pure reads — "
         "because queries can be long and the body feels more natural for structured data. "
         "But this violates HTTP semantics: POST implies side effects, "
         "GET requests are cacheable, bookmarkable, shareable.")
      (p "SX URLs naturally align with HTTP because the query " (em "is") " the URL:")
      (div (~tw :tokens "overflow-x-auto mt-4")
        (table (~tw :tokens "w-full text-sm text-left")
          (thead
            (tr (~tw :tokens "border-b border-stone-200")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Method")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Semantics")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Example")))
          (tbody (~tw :tokens "text-stone-600")
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "GET")
              (td (~tw :tokens "py-2 px-3") "Pure evaluation — cacheable, bookmarkable")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "GET /(language.(doc.intro))"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "POST")
              (td (~tw :tokens "py-2 px-3") "Side effects — mutations, submissions")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "POST /(submit-post)")))))
      (p "CDN caching of Lisp hypermedia just works. "
         "This is what REST always wanted but GraphQL abandoned. "
         "SX re-aligns with HTTP while being more powerful than both."))

    (~docs/section :title "GraphSX — This Is a Query Language" :id "graphsx"
      (p "The SX URL scheme is not just a routing convention — it is the emergence of "
         (strong "GraphSX") ": GraphQL but Lisp. The structural parallel is exact:")
      (div (~tw :tokens "overflow-x-auto mt-4")
        (table (~tw :tokens "w-full text-sm text-left")
          (thead
            (tr (~tw :tokens "border-b border-stone-200")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Concept")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "GraphQL")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "GraphSX")))
          (tbody (~tw :tokens "text-stone-600")
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "Single endpoint")
              (td (~tw :tokens "py-2 px-3") (code "/graphql"))
              (td (~tw :tokens "py-2 px-3") "Catch-all " (code "/<path:expr>")))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "Query structure")
              (td (~tw :tokens "py-2 px-3") "Nested fields " (code "{ language { doc { ... } } }"))
              (td (~tw :tokens "py-2 px-3") "Nested s-expressions " (code "(language.(doc....))")))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "Resolvers")
              (td (~tw :tokens "py-2 px-3") "Per-field functions")
              (td (~tw :tokens "py-2 px-3") "Page/section functions + " (code "~components")))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "Fragments")
              (td (~tw :tokens "py-2 px-3") "Named reusable selections")
              (td (~tw :tokens "py-2 px-3") "Components (" (code "defcomp") ")"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "Schema")
              (td (~tw :tokens "py-2 px-3") "Type definitions")
              (td (~tw :tokens "py-2 px-3") "Page function registry + component env"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "Scoping")
              (td (~tw :tokens "py-2 px-3") "Flat — " (code "?filter=x") " applies to... what?")
              (td (~tw :tokens "py-2 px-3") "Structural — parens ARE scope boundaries"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "Transport")
              (td (~tw :tokens "py-2 px-3") "POST JSON (violates HTTP GET semantics)")
              (td (~tw :tokens "py-2 px-3") "GET " (code "/sx/(expr)") " — cacheable, bookmarkable"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-semibold") "Response")
              (td (~tw :tokens "py-2 px-3") "JSON data (needs separate rendering)")
              (td (~tw :tokens "py-2 px-3") "Content " (em "and") " data — response is already meaningful")))))
      (p "The killer difference: in GraphQL, query and rendering are separate concerns — "
         "you fetch JSON, then a frontend renders it. In GraphSX, "
         (strong "the query language and the rendering language are the same thing") ". "
         (code "(language.(doc.introduction))") " is simultaneously:")
      (ol (~tw :tokens "space-y-1 text-stone-600 list-decimal pl-5")
        (li "A " (strong "query") " — resolve the docs/introduction content within the language section")
        (li "A " (strong "render instruction") " — render it inside a language section wrapper with nav context")
        (li "A " (strong "URL") " — addressable, bookmarkable, shareable, cacheable"))
      (p "This is what Lisp does. The uniform syntax means there is no boundary between "
         "routing DSL, query language, template language, and component system. "
         "They are all s-expressions evaluated in the same environment. "
         "GraphQL had to invent a special syntax for queries because JSON is data, not code. "
         "S-expressions are both."))

    (~docs/section :title "Direct Component URLs" :id "components"
      (p "Any " (code "defcomp") " is directly addressable via its " (code "~plans/content-addressed-components/name") ". "
         "The URL evaluator sees " (code "~essays/sx-sucks/essay-sx-sucks") ", looks it up in the component env, "
         "evaluates it, wraps in " (code "~layouts/doc") ", and returns.")
      (~docs/code :src (highlight
        ";; Page functions are convenience wrappers:\n/(etc.(essay.sx-sucks))          ;; dispatches via case statement\n\n;; But you can bypass them entirely:\n/(~essays/sx-sucks/essay-sx-sucks)               ;; direct component — no routing needed\n\n;; Every defcomp is instantly URL-accessible:\n/(~plans/sx-urls/plan-sx-urls-content)         ;; this very page\n/(~analyzer/bundle-analyzer-content)      ;; tools\n/(~docs-content/docs-evaluator-content)       ;; docs"
        "lisp"))
      (p "New components are instantly URL-accessible without routing wiring. "
         "Debugging is trivial — render any component in isolation."))

    (~docs/section :title "URL Special Forms" :id "special-forms"
      (p "URL-level functions that transform how content is resolved or displayed:")
      (div (~tw :tokens "overflow-x-auto mt-4")
        (table (~tw :tokens "w-full text-sm text-left")
          (thead
            (tr (~tw :tokens "border-b border-stone-200")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Form")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Example")
              (th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "Effect")))
          (tbody (~tw :tokens "text-stone-600")
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "source")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(source.(~essays/sx-sucks/essay-sx-sucks))")
              (td (~tw :tokens "py-2 px-3") "Show defcomp source instead of rendering"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "inspect")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(inspect.(language.(doc.primitives)))")
              (td (~tw :tokens "py-2 px-3") "Deps, CSS classes, render plan, IO requirements"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "diff")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(diff.(spec.signals).(spec.eval))")
              (td (~tw :tokens "py-2 px-3") "Side-by-side two pages"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "search")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(search.\"define\".:in.(spec.signals))")
              (td (~tw :tokens "py-2 px-3") "Grep within a page or spec"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "raw")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(raw.(~some-component))")
              (td (~tw :tokens "py-2 px-3") "Skip ~layouts/doc nav wrapping"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "eval")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(eval.(map.double.(list.1.2.3)))")
              (td (~tw :tokens "py-2 px-3") "Arbitrary evaluation — the URL bar is a REPL"))
            (tr (~tw :tokens "border-b border-stone-100")
              (td (~tw :tokens "py-2 px-3 font-mono text-violet-700") "json")
              (td (~tw :tokens "py-2 px-3 font-mono text-sm") "/sx/(json.(language.(doc.primitives)))")
              (td (~tw :tokens "py-2 px-3") "Return data as JSON — pure query mode"))))))

    (~docs/section :title "Evaluation Model" :id "eval"
      (p "The URL path (after stripping " (code "/") " and replacing dots with spaces) "
         "is parsed as SX and evaluated with a " (strong "soft eval") ": "
         "known function names are called; unknown symbols self-evaluate to their name as a string; "
         "components (" (code "~plans/content-addressed-components/name") ") are looked up in the component env.")
      (~docs/code :src (highlight
        "/sx/(language.(doc.introduction))\n\n;; After dot→space: (language (doc introduction))\n;; 1. Eval `introduction` → not a known function → \"introduction\"\n;; 2. Eval (doc \"introduction\") → call doc(\"introduction\") → page content\n;; 3. Eval (language content) → call language(content) → passes through\n;; 4. Router wraps result in (~layouts/doc :path \"(language (doc introduction))\" ...)\n\n/(~essays/sx-sucks/essay-sx-sucks)\n;; 1. Eval ~essays/sx-sucks/essay-sx-sucks → component lookup → evaluate → content\n;; 2. Router wraps in ~layouts/doc"
        "lisp"))

      (~docs/subsection :title "Section Functions"
        (p "Structural functions that encode hierarchy and pass through content:")
        (~docs/code :src (highlight
          "(define language\n  (fn (&rest args)\n    (if (empty? args) (language-index-content) (first args))))\n\n(define geography\n  (fn (&rest args)\n    (if (empty? args) (geography-index-content) (first args))))\n\n;; Sub-sections also pass through\n(define hypermedia\n  (fn (&rest args)\n    (if (empty? args) (hypermedia-index-content) (first args))))"
          "lisp")))

      (~docs/subsection :title "Page Functions"
        (p "Leaf functions that dispatch to content components. "
           "Data-dependent pages call helpers directly — the async evaluator handles IO:")
        (~docs/code :src (highlight
          "(define doc\n  (fn (&rest args)\n    (let ((slug (first-or-nil args)))\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          ...)))))\n\n(define bootstrapper\n  (fn (&rest args)\n    (let ((slug (first-or-nil args))\n          (data (when slug (bootstrapper-data slug))))\n      (if (nil? slug)\n        (~specs/bootstrappers-index-content)\n        (if (get data \"bootstrapper-not-found\")\n          (~specs/not-found :slug slug)\n          (case slug\n            \"python\" (~specs/bootstrapper-py-content ...)\n            ...))))))"
          "lisp"))))

    (~docs/section :title "The Catch-All Route" :id "route"
      (p "The entire routing layer becomes one handler:")
      (~docs/code :src (highlight
        "@app.get(\"/\")\nasync def sx_home():\n    return await eval_sx_url(\"/\")\n\n@app.get(\"/<path:expr>\")\nasync def sx_eval_route(expr):\n    return await eval_sx_url(f\"/{expr}\")"
        "python"))
      (p (code "eval_sx_url") " in seven steps:")
      (ol (~tw :tokens "space-y-1 text-stone-600 list-decimal pl-5")
        (li "URL-decode the path")
        (li "Replace dots with spaces")
        (li "Parse as SX expression")
        (li "Auto-quote unknown symbols (slugs become strings)")
        (li "Evaluate with components + helpers + page/section functions in env")
        (li "Wrap result in " (code "~layouts/doc") " with the URL expression as " (code ":path"))
        (li "Return HTML or SX wire format depending on HTMX request"))
      (p "Defhandler API endpoints and Python demo routes are registered " (em "before") " the catch-all, "
         "so they match first."))

    (~docs/section :title "Composability" :id "composability"
      (~docs/code :src (highlight
        ";; Direct component access\n/(~essays/sx-sucks/essay-sx-sucks)\n/(~specs-explorer/spec-explorer-content)\n\n;; URL special forms\n/(source.(~essays/sx-sucks/essay-sx-sucks))                          ;; view defcomp source\n/(inspect.(language.(doc.primitives)))                 ;; deps, render plan\n/(diff.(language.(spec.signals)).(language.(spec.eval))) ;; side by side\n/(eval.(map.double.(list.1.2.3)))                     ;; REPL in the URL bar\n\n;; Components as query resolvers\n/(~page.home\n  .(~hero.banner)\n  .(~get.latest-posts.(limit.5))\n  .(~get.featured.(filter.pinned)))\n\n;; Scoping is explicit\n/(users.(posts.123.(filter.published)))  ;; filter scoped to posts\n/(users.posts.123.(filter.published))    ;; filter scoped to users\n\n;; Cross-service (future)\n/(market.(product.42.:fields.(name.price)))\n/(subscribe.(etc.(plan.status)))"
        "lisp")))

    (~docs/section :title "Implementation Phases" :id "phases"
      (div (~tw :tokens "space-y-4")
        (div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4")
          (p (~tw :tokens "font-semibold text-violet-800 mb-2") "Phase 1: Page Functions + Catch-All Route")
          (ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
            (li "Create " (code "page-functions.sx") " — ~20 section + ~15 page functions")
            (li "Create " (code "sx_router.py") " — URL parser (dot→space), soft eval, streaming detection")
            (li "Replace " (code "auto_mount_pages") " with catch-all")
            (li "Direct " (code "~component") " URL support")))
        (div (~tw :tokens "rounded border border-blue-200 bg-blue-50 p-4")
          (p (~tw :tokens "font-semibold text-blue-800 mb-2") "Phase 2: Navigation Data")
          (ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
            (li "Rewrite ~200 " (code ":href") " values to dot-separated SX URLs")
            (li "Rewrite " (code "resolve-nav-path") " for tree-descent matching")))
        (div (~tw :tokens "rounded border border-emerald-200 bg-emerald-50 p-4")
          (p (~tw :tokens "font-semibold text-emerald-800 mb-2") "Phase 3: Backward Compatibility")
          (ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
            (li "301 redirects from all old paths to new SX URLs")
            (li "Algorithmic pattern matching — ~25 regex rules")))
        (div (~tw :tokens "rounded border border-amber-200 bg-amber-50 p-4")
          (p (~tw :tokens "font-semibold text-amber-800 mb-2") "Phase 4: URL Special Forms")
          (ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
            (li (code "source") ", " (code "inspect") ", " (code "diff") ", " (code "raw") ", " (code "eval") ", " (code "json"))
            (li "Components as query resolvers (" (code "~get") ", " (code "~page") ")")))
        (div (~tw :tokens "rounded border border-rose-200 bg-rose-50 p-4")
          (p (~tw :tokens "font-semibold text-rose-800 mb-2") "Phase 5: Client-Side Routing")
          (ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
            (li "Update " (code "_build_pages_sx()") " for function-based page registry")
            (li "Client-side dot→space→parse→eval pipeline")
            (li "Rebootstrap " (code "sx-ref.js") " and " (code "sx_ref.py"))))
        (div (~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4")
          (p (~tw :tokens "font-semibold text-stone-800 mb-2") "Phase 6: Cleanup")
          (ul (~tw :tokens "list-disc pl-5 text-stone-700 space-y-1")
            (li "Delete " (code "docs.sx") " (all 46 defpages)")
            (li "Grep content files for stale old-style hrefs")))))

    (~docs/section :title "What Stays the Same" :id "unchanged"
      (ul (~tw :tokens "space-y-2 text-stone-600 list-disc pl-5")
        (li (strong "Defhandler API paths") " — registered before the catch-all, match first")
        (li (strong "Python demo routes") " — registered via blueprint before the catch-all")
        (li (strong "All other services") " — blog, market, etc. keep defpage routing")
        (li (strong "Component definitions") " — all " (code ".sx") " files unchanged")
        (li (strong "Page helpers") " — called from page functions instead of defpage " (code ":data"))
        (li (strong "Layout, CSS, shell, rendering pipeline") " — no changes")))))
