Files
rose-ash/sx/sx/cssx.sx
giles b0920a1121 Rename all 1,169 components to path-based names with namespace support
Component names now reflect filesystem location using / as path separator
and : as namespace separator for shared components:
  ~sx-header → ~layouts/header
  ~layout-app-body → ~shared:layout/app-body
  ~blog-admin-dashboard → ~admin/dashboard

209 files, 4,941 replacements across all services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:00:12 +00:00

422 lines
29 KiB
Plaintext

;; CSSX — Styling as Components
;; Documentation for the CSSX approach: no parallel style infrastructure,
;; just defcomp components that decide how to style their children.
;; ---------------------------------------------------------------------------
;; Overview
;; ---------------------------------------------------------------------------
(defcomp ~cssx/overview-content ()
(~docs/page :title "CSSX Components"
(~docs/section :title "The Idea" :id "idea"
(p (strong "Styling is just components.") " A CSSX component is a regular "
(code "defcomp") " that decides how to style its children. It might apply "
"Tailwind classes, or hand-written CSS classes, or inline styles, or generate "
"rules at runtime. The implementation is the component's private business. "
"The consumer just calls " (code "(~cssx/btn :variant \"primary\" \"Submit\")") " and doesn't care.")
(p "Because it's " (code "defcomp") ", you get everything for free: caching, bundling, "
"dependency scanning, server/client rendering, composition. No parallel infrastructure."))
(~docs/section :title "Why Not a Style Dictionary?" :id "why"
(p "SX previously had a parallel CSS system: a style dictionary (JSON blob of "
"atom-to-declaration mappings), a " (code "StyleValue") " type threaded through "
"the evaluator and renderer, content-addressed hash class names (" (code "sx-a3f2b1")
"), runtime CSS injection, and a separate caching pipeline (cookies, localStorage).")
(p "This was ~3,000 lines of code across the spec, bootstrappers, and host implementations. "
"It was never adopted. The codebase voted with its feet: " (code ":class") " strings "
"with " (code "defcomp") " already covered every real use case.")
(p "The result of that system: elements in the DOM got opaque class names like "
(code "class=\"sx-a3f2b1\"") ". DevTools became useless. You couldn't inspect an "
"element and understand its styling. " (strong "That was a deal breaker.")))
(~docs/section :title "Key Advantages" :id "advantages"
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
(li (strong "Readable DOM: ") "Elements have real class names, not content-addressed "
"hashes. DevTools works.")
(li (strong "Data-driven styling: ") "Components receive data and decide styling. "
(code "(~cssx/metric :value 150)") " renders red because " (code "value > 100")
" — logic lives in the component, not a CSS preprocessor.")
(li (strong "One system: ") "No separate " (code "StyleValue") " type, no style "
"dictionary JSON, no injection pipeline. Components ARE the styling abstraction.")
(li (strong "One cache: ") "Component hash/localStorage handles everything. No "
"separate style dict caching.")
(li (strong "Composable: ") (code "(~card :elevated true (~cssx/metric :value v))")
" — styling composes like any other component.")
(li (strong "Strategy-agnostic: ") "A component can apply Tailwind classes, emit "
(code "<style>") " blocks, use inline styles, generate CSS custom properties, or "
"any combination. Swap strategies without touching call sites.")))
(~docs/section :title "What Changed" :id "changes"
(~docs/subsection :title "Removed (~3,000 lines)"
(ul :class "list-disc pl-5 space-y-1 text-stone-600 text-sm"
(li (code "StyleValue") " type and all plumbing (type checks in eval, render, serialize)")
(li (code "cssx.sx") " spec module (resolve-style, resolve-atom, split-variant, hash, injection)")
(li "Style dictionary JSON format, loading, caching (" (code "<script type=\"text/sx-styles\">") ", localStorage)")
(li (code "style_dict.py") " (782 lines) and " (code "style_resolver.py") " (254 lines)")
(li (code "css") " and " (code "merge-styles") " primitives")
(li "Platform interface: " (code "fnv1a-hash") ", " (code "compile-regex") ", " (code "make-style-value") ", " (code "inject-style-value"))
(li (code "defkeyframes") " special form")
(li "Style dict cookies and localStorage keys")))
(~docs/subsection :title "Kept"
(ul :class "list-disc pl-5 space-y-1 text-stone-600 text-sm"
(li (code "defstyle") " — simplified to bind any value (string, function, etc.)")
(li (code "tw.css") " — the compiled Tailwind stylesheet, delivered via CSS class tracking")
(li (code ":class") " attribute — just takes strings, no special-casing")
(li "CSS class delivery (" (code "SX-Css") " headers, " (code "<style id=\"sx-css\">") ")")
(li "All component infrastructure (defcomp, caching, bundling, deps)")))
(~docs/subsection :title "Added"
(p "Nothing. CSSX components are just " (code "defcomp") ". The only new thing is "
"a convention: components whose primary purpose is styling.")))))
;; ---------------------------------------------------------------------------
;; Patterns
;; ---------------------------------------------------------------------------
(defcomp ~cssx/patterns-content ()
(~docs/page :title "Patterns"
(~docs/section :title "Class Mapping" :id "class-mapping"
(p "The simplest pattern: a component that maps semantic keywords to class strings.")
(highlight
"(defcomp ~cssx/btn (&key variant disabled &rest children)\n (button\n :class (str \"px-4 py-2 rounded font-medium transition \"\n (case variant\n \"primary\" \"bg-blue-600 text-white hover:bg-blue-700\"\n \"danger\" \"bg-red-600 text-white hover:bg-red-700\"\n \"ghost\" \"bg-transparent hover:bg-stone-100\"\n \"bg-stone-200 hover:bg-stone-300\")\n (when disabled \" opacity-50 cursor-not-allowed\"))\n :disabled disabled\n children))"
"lisp")
(p "Consumers call " (code "(~cssx/btn :variant \"primary\" \"Submit\")") ". The Tailwind "
"classes are readable in DevTools but never repeated across call sites."))
(~docs/section :title "Data-Driven Styling" :id "data-driven"
(p "Styling that responds to data values — impossible with static CSS:")
(highlight
"(defcomp ~cssx/metric (&key value label threshold)\n (let ((t (or threshold 10)))\n (div :class (str \"p-3 rounded font-bold \"\n (cond\n ((> value (* t 10)) \"bg-red-500 text-white\")\n ((> value t) \"bg-amber-200 text-amber-900\")\n (:else \"bg-green-100 text-green-800\")))\n (span :class \"text-sm\" label) \": \" (span (str value)))))"
"lisp")
(p "The component makes a " (em "decision") " about styling based on data. "
"No CSS preprocessor or class name convention can express \"red when value > 100\"."))
(~docs/section :title "Style Functions" :id "style-functions"
(p "Reusable style logic that returns class strings — no wrapping element needed:")
(highlight
"(define card-classes\n (fn (&key elevated bordered)\n (str \"rounded-lg p-4 \"\n (if elevated \"shadow-lg\" \"shadow-sm\")\n (when bordered \" border border-stone-200\"))))\n\n;; Usage:\n(div :class (card-classes :elevated true) ...)\n(article :class (card-classes :bordered true) ...)"
"lisp")
(p "Or with " (code "defstyle") " for named bindings:")
(highlight
"(defstyle card-base \"rounded-lg p-4 shadow-sm\")\n(defstyle card-elevated \"rounded-lg p-4 shadow-lg\")\n\n(div :class card-base ...)"
"lisp"))
(~docs/section :title "Responsive Layouts" :id "responsive"
(p "Components that encode responsive breakpoints:")
(highlight
"(defcomp ~cssx/responsive-grid (&key cols &rest children)\n (div :class (str \"grid gap-4 \"\n (case (or cols 3)\n 1 \"grid-cols-1\"\n 2 \"grid-cols-1 md:grid-cols-2\"\n 3 \"grid-cols-1 md:grid-cols-2 lg:grid-cols-3\"\n 4 \"grid-cols-2 md:grid-cols-3 lg:grid-cols-4\"))\n children))"
"lisp"))
(~docs/section :title "Emitting CSS Directly" :id "emitting-css"
(p "Components are not limited to referencing existing classes. They can generate "
"CSS — " (code "<style>") " tags, keyframes, custom properties — as part of their output:")
(highlight
"(defcomp ~cssx/pulse (&key color duration &rest children)\n (<>\n (style (str \"@keyframes sx-pulse {\"\n \"0%,100% { opacity:1 } 50% { opacity:.5 } }\"))\n (div :style (str \"animation: sx-pulse \" (or duration \"2s\") \" infinite;\"\n \"color:\" (or color \"inherit\"))\n children)))"
"lisp")
(highlight
"(defcomp ~cssx/theme (&key primary surface &rest children)\n (<>\n (style (str \":root {\"\n \"--color-primary:\" (or primary \"#7c3aed\") \";\"\n \"--color-surface:\" (or surface \"#fafaf9\") \"}\"))\n children))"
"lisp")
(p "The CSS strategy is the component's private implementation detail. Consumers call "
(code "(~cssx/pulse :color \"red\" \"Loading...\")") " or "
(code "(~cssx/theme :primary \"#2563eb\" ...)") " without knowing or caring whether the "
"component uses classes, inline styles, generated rules, or all three."))))
;; ---------------------------------------------------------------------------
;; Async CSS
;; ---------------------------------------------------------------------------
(defcomp ~cssx/async-content ()
(~docs/page :title "Async CSS"
(~docs/section :title "The Pattern" :id "pattern"
(p "A CSSX component that needs CSS it doesn't have yet can "
(strong "fetch and cache it before rendering") ". This is just "
(code "~shared:pages/suspense") " combined with a style component — no new infrastructure:")
(highlight
"(defcomp ~cssx/styled (&key css-url css-hash fallback &rest children)\n (if (css-cached? css-hash)\n ;; Already have it — render immediately\n children\n ;; Don't have it — suspense while we fetch\n (~shared:pages/suspense :id (str \"css-\" css-hash)\n :fallback (or fallback (span \"\"))\n (do\n (fetch-css css-url css-hash)\n children))))"
"lisp")
(p "The consumer never knows:")
(highlight
"(~cssx/styled :css-url \"/css/charts.css\" :css-hash \"abc123\"\n (~bar-chart :data metrics))"
"lisp"))
(~docs/section :title "Use Cases" :id "use-cases"
(~docs/subsection :title "Federated Components"
(p "A " (code "~cssx/btn") " from another site arrives via IPFS with a CID pointing "
"to its CSS. The component fetches and caches it before rendering. "
"No coordination needed between sites.")
(highlight
"(defcomp ~cssx/federated-widget (&key cid &rest children)\n (let ((css-cid (str cid \"/style.css\"))\n (cached (css-cached? css-cid)))\n (if cached\n children\n (~shared:pages/suspense :id (str \"fed-\" cid)\n :fallback (div :class \"animate-pulse bg-stone-100 rounded h-20\")\n (do (fetch-css (str \"https://ipfs.io/ipfs/\" css-cid) css-cid)\n children)))))"
"lisp"))
(~docs/subsection :title "Heavy UI Libraries"
(p "Code editors, chart libraries, rich text editors — their CSS only loads "
"when the component actually appears on screen:")
(highlight
"(defcomp ~cssx/code-editor (&key language value on-change)\n (~cssx/styled :css-url \"/css/codemirror.css\" :css-hash (asset-hash \"codemirror\")\n :fallback (pre :class \"p-4 bg-stone-900 text-stone-300 rounded\" value)\n (div :class \"cm-editor\"\n :data-language language\n :data-value value)))"
"lisp"))
(~docs/subsection :title "Lazy Themes"
(p "Theme CSS loads on first use, then is instant on subsequent visits:")
(highlight
"(defcomp ~cssx/lazy-theme (&key name &rest children)\n (let ((css-url (str \"/css/themes/\" name \".css\"))\n (hash (str \"theme-\" name)))\n (~cssx/styled :css-url css-url :css-hash hash\n :fallback children ;; render unstyled immediately\n children)))"
"lisp")))
(~docs/section :title "How It Composes" :id "composition"
(p "Async CSS composes with everything already in SX:")
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
(li (code "~shared:pages/suspense") " handles the async gap with fallback content")
(li "localStorage handles caching across sessions")
(li (code "<style id=\"sx-css\">") " is the injection target (same as CSS class delivery)")
(li "Component content-hashing tracks what the client has")
(li "No new types, no new delivery protocol, no new spec code")))))
;; ---------------------------------------------------------------------------
;; Live Styles
;; ---------------------------------------------------------------------------
(defcomp ~cssx/live-content ()
(~docs/page :title "Live Styles"
(~docs/section :title "Styles That Respond to Events" :id "concept"
(p "Combine " (code "~live") " (SSE) or " (code "~ws") " (WebSocket) with style "
"components, and you get styles that change in real-time in response to server "
"events. No new infrastructure — just components receiving data through a "
"persistent transport."))
(~docs/section :title "SSE: Live Theme Updates" :id "sse-theme"
(p "A " (code "~live") " component declares a persistent connection to an SSE "
"endpoint. When the server pushes a new event, " (code "resolveSuspense")
" replaces the content:")
(highlight
"(~live :src \"/api/stream/brand\"\n (~shared:pages/suspense :id \"theme\"\n (~cssx/theme :primary \"#7c3aed\" :surface \"#fafaf9\")))"
"lisp")
(p "Server pushes a new theme:")
(highlight
"event: sx-resolve\ndata: {\"id\": \"theme\", \"sx\": \"(~cssx/theme :primary \\\"#2563eb\\\" :surface \\\"#1e1e2e\\\")\"}"
"text")
(p "The " (code "~cssx/theme") " component emits CSS custom properties. Everything "
"using " (code "var(--color-primary)") " repaints instantly:")
(highlight
"(defcomp ~cssx/theme (&key primary surface)\n (style (str \":root {\"\n \"--color-primary:\" (or primary \"#7c3aed\") \";\"\n \"--color-surface:\" (or surface \"#fafaf9\") \"}\")))"
"lisp"))
(~docs/section :title "SSE: Live Dashboard Metrics" :id "sse-metrics"
(p "Style changes driven by live data — the component decides the visual treatment:")
(highlight
"(~live :src \"/api/stream/dashboard\"\n (~shared:pages/suspense :id \"cpu\"\n (~cssx/metric :value 0 :label \"CPU\" :threshold 80))\n (~shared:pages/suspense :id \"memory\"\n (~cssx/metric :value 0 :label \"Memory\" :threshold 90))\n (~shared:pages/suspense :id \"requests\"\n (~cssx/metric :value 0 :label \"RPS\" :threshold 1000)))"
"lisp")
(p "Server pushes updated values. " (code "~cssx/metric") " turns red when "
(code "value > threshold") " — the styling logic lives in the component, "
"not in CSS selectors or JavaScript event handlers."))
(~docs/section :title "WebSocket: Collaborative Design" :id "ws-design"
(p "Bidirectional channel for real-time collaboration. A designer adjusts a color, "
"all connected clients see the change:")
(highlight
"(~ws :src \"/ws/design-studio\"\n (~shared:pages/suspense :id \"canvas-theme\"\n (~cssx/theme :primary \"#7c3aed\")))"
"lisp")
(p "Client sends a color change:")
(highlight
";; Designer picks a new primary color\n(sx-send ws-conn '(theme-update :primary \"#dc2626\"))"
"lisp")
(p "Server broadcasts to all connected clients via " (code "sx-resolve") " — "
"every client's " (code "~cssx/theme") " component re-renders with the new color."))
(~docs/section :title "Why This Works" :id "why"
(p "Every one of these patterns is just a " (code "defcomp") " receiving data "
"through a persistent transport. The styling strategy — CSS custom properties, "
"class swaps, inline styles, " (code "<style>") " blocks — is the component's "
"private business. The transport doesn't know or care.")
(p "A parallel style system would have needed its own streaming, its own caching, "
"its own delta protocol for each of these use cases — duplicating what components "
"already do.")
(p :class "mt-4 text-stone-500 italic"
"Note: ~live and ~ws are planned (see Live Streaming). The patterns shown here "
"will work as described once the streaming transport is implemented. The component "
"and suspense infrastructure they depend on already exists."))))
;; ---------------------------------------------------------------------------
;; Comparison with CSS Technologies
;; ---------------------------------------------------------------------------
(defcomp ~cssx/comparison-content ()
(~docs/page :title "Comparisons"
(~docs/section :title "styled-components / Emotion" :id "styled-components"
(p (a :href "https://styled-components.com" :class "text-violet-600 hover:underline" "styled-components")
" pioneered the idea that styling belongs in components. But it generates CSS "
"at runtime, injects " (code "<style>") " tags, and produces opaque hashed class "
"names (" (code "class=\"sc-bdfBwQ fNMpVx\"") "). Open DevTools and you see gibberish. "
"It also carries significant runtime cost — parsing CSS template literals, hashing, "
"deduplicating — and needs a separate SSR extraction step (" (code "ServerStyleSheet") ").")
(p "CSSX components share the core insight (" (em "styling is a component concern")
") but without the runtime machinery. When a component applies Tailwind classes, "
"there's zero CSS generation overhead. When it does emit " (code "<style>")
" blocks, it's explicit — not hidden behind a tagged template literal. "
"And the DOM is always readable."))
(~docs/section :title "CSS Modules" :id "css-modules"
(p (a :href "https://github.com/css-modules/css-modules" :class "text-violet-600 hover:underline" "CSS Modules")
" scope class names to avoid collisions by rewriting them at build time: "
(code ".button") " becomes " (code ".button_abc123")
". This solves the global namespace problem but creates the same opacity issue — "
"hashed names in the DOM that you can't grep for or reason about.")
(p "CSSX components don't need scoping because component boundaries already provide "
"isolation. A " (code "~cssx/btn") " owns its markup. There's nothing to collide with."))
(~docs/section :title "Tailwind CSS" :id "tailwind"
(p "Tailwind is " (em "complementary") ", not competitive. CSSX components are the "
"semantic layer on top. Raw Tailwind in markup — "
(code ":class \"px-4 py-2 bg-blue-600 text-white font-medium rounded hover:bg-blue-700\"")
" — is powerful but verbose and duplicated across call sites.")
(p "A CSSX component wraps that string once: " (code "(~cssx/btn :variant \"primary\" \"Submit\")")
". The Tailwind classes are still there, readable in DevTools, but consumers don't "
"repeat them. This is the same pattern Tailwind's own docs recommend ("
(em "\"extracting components\"") ") — CSSX components are just SX's native way of doing it."))
(~docs/section :title "Vanilla Extract" :id "vanilla-extract"
(p (a :href "https://vanilla-extract.style" :class "text-violet-600 hover:underline" "Vanilla Extract")
" is zero-runtime CSS-in-JS: styles are written in TypeScript, compiled to static "
"CSS at build time, and referenced by generated class names. It avoids the runtime "
"cost of styled-components but still requires a build step, a bundler plugin, and "
"TypeScript. The generated class names are again opaque.")
(p "CSSX components need no build step for styling — they're evaluated at render time "
"like any other component. And since the component chooses its own strategy, it can "
"reference pre-built classes (zero runtime) " (em "or") " generate CSS on the fly — "
"same API either way."))
(~docs/section :title "Design Tokens / Style Dictionary" :id "design-tokens"
(p "The " (a :href "https://amzn.github.io/style-dictionary/" :class "text-violet-600 hover:underline" "Style Dictionary")
" pattern — a JSON/YAML file mapping token names to values, compiled to "
"platform-specific output — is essentially what the old CSSX was. It's the "
"industry standard for design systems.")
(p "The problem is that it's a parallel system: separate file format, separate build "
"pipeline, separate caching, separate tooling. CSSX components eliminate all of "
"that by expressing tokens as component parameters: "
(code "(~cssx/theme :primary \"#7c3aed\")") " instead of "
(code "{\"color\": {\"primary\": {\"value\": \"#7c3aed\"}}}")
". Same result, no parallel infrastructure."))))
;; ---------------------------------------------------------------------------
;; Philosophy
;; ---------------------------------------------------------------------------
(defcomp ~cssx/philosophy-content ()
(~docs/page :title "Philosophy"
(~docs/section :title "The Collapse" :id "collapse"
(p "The web has spent two decades building increasingly complex CSS tooling: "
"preprocessors, CSS-in-JS, atomic CSS, utility frameworks, design tokens, style "
"dictionaries. Each solves a real problem but adds a new system with its own "
"caching, bundling, and mental model.")
(p "CSSX components collapse all of this back to the simplest possible thing: "
(strong "a function that takes data and returns markup with classes.")
" That's what a component already is. There is no separate styling system because "
"there doesn't need to be."))
(~docs/section :title "Proof by Deletion" :id "proof"
(p "The strongest validation: we built the full parallel system — style dictionary, "
"StyleValue type, content-addressed hashing, runtime injection, localStorage "
"caching — and then deleted it because nobody used it. The codebase already had "
"the answer: " (code "defcomp") " with " (code ":class") " strings.")
(p "3,000 lines of infrastructure removed. Zero lines added. Every use case still works."))
(~docs/section :title "The Right Abstraction Level" :id "abstraction"
(p "CSS-in-JS puts styling " (em "below") " components — you style elements, then compose "
"them. Utility CSS puts styling " (em "beside") " components — classes in markup, logic "
"elsewhere. Both create a seam between what something does and how it looks.")
(p "CSSX components put styling " (em "inside") " components — at the same level as "
"structure and behavior. A " (code "~cssx/metric") " component knows its own thresholds, "
"its own color scheme, its own responsive behavior. Styling is just another "
"decision the component makes, not a separate concern."))
(~docs/section :title "Relationship to Other Plans" :id "relationships"
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
(li (strong "Content-Addressed Components: ") "CSSX components get CIDs like any "
"other component. A " (code "~cssx/btn") " from one site can be shared to another via "
"IPFS. No " (code ":css-atoms") " manifest field needed — the component carries "
"its own styling logic.")
(li (strong "Isomorphic Rendering: ") "Components render the same on server and client. "
"No style injection timing issues, no FOUC from late CSS loading.")
(li (strong "Component Bundling: ") "deps.sx already handles transitive component deps. "
"Style components are just more components in the bundle — no separate style bundling.")
(li (strong "Live Streaming: ") "SSE and WebSocket transports push data to components. "
"Style components react to that data like any other component — no separate style "
"streaming protocol.")))))
;; ---------------------------------------------------------------------------
;; CSS Delivery
;; ---------------------------------------------------------------------------
(defcomp ~cssx/delivery-content ()
(~docs/page :title "CSS Delivery"
(~docs/section :title "Multiple Strategies" :id "strategies"
(p "A CSSX component chooses its own styling strategy — and each strategy has its "
"own delivery path:")
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
(li (strong "Tailwind classes: ") "Delivered via the on-demand protocol below. "
"The server ships only the rules the page actually uses.")
(li (strong "Inline styles: ") "No delivery needed — " (code ":style") " attributes "
"are part of the markup.")
(li (strong "Emitted " (code "<style>") " blocks: ") "Components can emit CSS rules "
"directly. They arrive as part of the rendered HTML — keyframes, custom properties, "
"scoped rules, anything.")
(li (strong "External stylesheets: ") "Components can reference pre-loaded CSS files "
"or lazy-load them via " (code "~shared:pages/suspense") " (see " (a :href "/sx/(applications.(cssx.async))" "Async CSS") ").")
(li (strong "Custom properties: ") "A " (code "~cssx/theme") " component sets "
(code "--color-primary") " etc. via a " (code "<style>") " block. Everything "
"using " (code "var()") " repaints automatically."))
(p "The protocol below handles the Tailwind utility class case — the most common "
"strategy — but it's not the only game in town."))
(~docs/section :title "On-Demand Tailwind Delivery" :id "on-demand"
(p "When components use Tailwind utility classes, SX ships " (em "only the CSS rules "
"actually used on the page") ", computed at render time. No build step, no purging, "
"no unused CSS.")
(p "The server pre-parses the full Tailwind CSS file into an in-memory registry at "
"startup. When a response is rendered, SX scans all " (code ":class") " values in "
"the output, looks up only those classes, and embeds the matching rules."))
(~docs/section :title "The Protocol" :id "protocol"
(p "First page load gets the full set of used rules. Subsequent navigations send a "
"hash of what the client already has, and the server ships only the delta:")
(highlight
"# First page load:\nGET / HTTP/1.1\n\nHTTP/1.1 200 OK\nContent-Type: text/html\n# Full CSS in <style id=\"sx-css\"> + hash in <meta name=\"sx-css-classes\">\n\n# Subsequent navigation:\nGET /about HTTP/1.1\nSX-Css: a1b2c3d4\n\nHTTP/1.1 200 OK\nContent-Type: text/sx\nSX-Css-Hash: e5f6g7h8\nSX-Css-Add: bg-blue-500,text-white,rounded-lg\n# Only new rules in <style data-sx-css>"
"bash")
(p "The client merges new rules into the existing " (code "<style id=\"sx-css\">")
" block and updates its hash. No flicker, no duplicate rules."))
(~docs/section :title "Component-Aware Scanning" :id "scanning"
(p "CSS scanning happens at two levels:")
(ul :class "list-disc pl-5 space-y-1 text-stone-700"
(li (strong "Registration time: ") "When a component is defined via " (code "defcomp")
", its body is scanned for class strings. The component records which CSS classes "
"it uses.")
(li (strong "Render time: ") "The rendered output is scanned for any classes the "
"static scan missed — dynamically constructed class strings, conditional classes, etc."))
(p "This means the CSS registry knows roughly what a page needs before rendering, "
"and catches any stragglers after."))
(~docs/section :title "Trade-offs" :id "tradeoffs"
(ul :class "list-disc pl-5 space-y-2 text-stone-700"
(li (strong "Full Tailwind in memory: ") "The parsed CSS registry is ~4MB. This is "
"a one-time startup cost per app instance.")
(li (strong "Regex scanning: ") "Class detection uses regex, so dynamically "
"constructed class names (e.g. " (code "(str \"bg-\" color \"-500\")") ") can be "
"missed. Use complete class strings in " (code "case") "/" (code "cond") " branches.")
(li (strong "No @apply: ") "Tailwind's " (code "@apply") " is a build-time feature. "
"On-demand delivery works with utility classes directly.")
(li (strong "Tailwind-shaped: ") "The registry parser understands Tailwind's naming "
"conventions. Non-Tailwind CSS works via " (code "<style>") " blocks in components.")))))