Add Specs section, Reflexive Web essay, fix highlight and dev caching

- Fix highlight() returning SxExpr so syntax-highlighted code renders
  as DOM elements instead of leaking SX source text into the page
- Add Specs section that reads and displays canonical SX spec files
  from shared/sx/ref/ with syntax highlighting
- Add "The Reflexive Web" essay on SX becoming a complete LISP with
  AI as native participant
- Change logo from (<x>) to (<sx>) everywhere
- Unify all backgrounds to bg-stone-100, center code blocks
- Skip component/style cookie cache in dev mode so .sx edits are
  visible immediately on refresh without clearing localStorage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 11:49:05 +00:00
parent 6fa843016b
commit 7ecbf19c11
14 changed files with 319 additions and 73 deletions

View File

@@ -627,8 +627,9 @@ def sx_page(ctx: dict, page_sx: str, *,
component_hash = get_component_hash()
# Check if client already has this version cached (via cookie)
# In dev mode, always send full source so edits are visible immediately
client_hash = _get_sx_comp_cookie()
if client_hash and client_hash == component_hash:
if not _is_dev_mode() and client_hash and client_hash == component_hash:
# Client has current components cached — send empty source
component_defs = ""
else:
@@ -675,7 +676,7 @@ def sx_page(ctx: dict, page_sx: str, *,
# Style dictionary for client-side css primitive
styles_hash = _get_style_dict_hash()
client_styles_hash = _get_sx_styles_cookie()
if client_styles_hash and client_styles_hash == styles_hash:
if not _is_dev_mode() and client_styles_hash and client_styles_hash == styles_hash:
styles_json = "" # Client has cached version
else:
styles_json = _build_style_dict_json()

View File

@@ -236,14 +236,15 @@ def _tokenize_bash(code: str) -> list[tuple[str, str]]:
return tokens
def highlight(code: str, language: str = "lisp") -> str:
"""Highlight code in the given language. Returns sx source."""
def highlight(code: str, language: str = "lisp"):
"""Highlight code in the given language. Returns SxExpr for wire format."""
from shared.sx.parser import SxExpr
if language in ("lisp", "sx", "sexp"):
return highlight_sx(code)
return SxExpr(highlight_sx(code))
elif language in ("python", "py"):
return highlight_python(code)
return SxExpr(highlight_python(code))
elif language in ("bash", "sh", "shell"):
return highlight_bash(code)
return SxExpr(highlight_bash(code))
# Fallback: no highlighting, just escaped text
escaped = code.replace("\\", "\\\\").replace('"', '\\"')
return f'(span "{escaped}")'
return SxExpr(f'(span "{escaped}")')

View File

@@ -2,14 +2,14 @@
(defcomp ~doc-placeholder (&key id)
(div :id id
(div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3"
(div :class "bg-stone-100 rounded p-4 mt-3"
(p :class "text-stone-400 italic text-sm"
"Trigger the demo to see the actual content."))))
(defcomp ~doc-oob-code (&key target-id text)
(div :id target-id :sx-swap-oob "innerHTML"
(div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3 overflow-x-auto"
(pre :class "text-sm whitespace-pre-wrap"
(div :class "bg-stone-100 rounded p-4 mt-3"
(pre :class "text-sm whitespace-pre-wrap break-words"
(code text)))))
(defcomp ~doc-attr-table (&key title rows)
@@ -17,7 +17,7 @@
(h3 :class "text-xl font-semibold text-stone-700" title)
(div :class "overflow-x-auto rounded border border-stone-200"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-50"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "Attribute")
(th :class "px-3 py-2 font-medium text-stone-600" "Description")
(th :class "px-3 py-2 font-medium text-stone-600 text-center w-20" "In sx?")))
@@ -28,7 +28,7 @@
(h3 :class "text-xl font-semibold text-stone-700" title)
(div :class "overflow-x-auto rounded border border-stone-200"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-50"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "Header")
(th :class "px-3 py-2 font-medium text-stone-600" "Value")
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
@@ -51,13 +51,13 @@
(when intro (p :class "text-stone-600 mb-6" intro))
(div :class "overflow-x-auto rounded border border-stone-200"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-50"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" (or col1 "Name"))
(th :class "px-3 py-2 font-medium text-stone-600" (or col2 "Description"))))
(tbody rows)))))
(defcomp ~sx-docs-label ()
(span :class "font-mono" "(<x>)"))
(span :class "font-mono" "(<sx>)"))
(defcomp ~doc-clear-cache-btn ()
(button :onclick "localStorage.removeItem('sx-components-hash');localStorage.removeItem('sx-components-src');var e=Sx.getEnv();Object.keys(e).forEach(function(k){if(k.charAt(0)==='~')delete e[k]});var b=this;b.textContent='Cleared!';setTimeout(function(){b.textContent='Clear component cache'},2000)"

File diff suppressed because one or more lines are too long

View File

@@ -11,7 +11,8 @@
(dict :label "Reference" :href "/reference/")
(dict :label "Protocols" :href "/protocols/wire-format")
(dict :label "Examples" :href "/examples/click-to-load")
(dict :label "Essays" :href "/essays/sx-sucks"))))
(dict :label "Essays" :href "/essays/sx-sucks")
(dict :label "Specs" :href "/specs/core"))))
(<> (map (lambda (item)
(~nav-link
:href (get item "href")

View File

@@ -64,7 +64,15 @@
(dict :label "The SX Manifesto" :href "/essays/sx-manifesto")
(dict :label "Tail-Call Optimization" :href "/essays/tail-call-optimization")
(dict :label "Continuations" :href "/essays/continuations")
(dict :label "Godel, Escher, Bach" :href "/essays/godel-escher-bach")))
(dict :label "Godel, Escher, Bach" :href "/essays/godel-escher-bach")
(dict :label "The Reflexive Web" :href "/essays/reflexive-web")))
(define specs-nav-items (list
(dict :label "Core" :href "/specs/core")
(dict :label "Parser" :href "/specs/parser")
(dict :label "Evaluator" :href "/specs/evaluator")
(dict :label "Primitives" :href "/specs/primitives")
(dict :label "Renderer" :href "/specs/renderer")))
;; Find the current nav label for a slug by matching href suffix.
;; Returns the label string or nil if no match.

36
sx/sx/specs.sx Normal file
View File

@@ -0,0 +1,36 @@
;; Spec viewer components — display canonical SX specification source
(defcomp ~spec-core-content (&key spec-files)
(~doc-page :title "SX Core Specification"
(p :class "text-stone-600 mb-6"
"SX is defined in SX. These four files constitute the canonical, self-hosting specification of the language. Each file is both documentation and executable definition — bootstrap compilers read them to generate native implementations.")
(div :class "space-y-8"
(map (fn (spec)
(div :class "space-y-3"
(div :class "flex items-baseline gap-3"
(h2 :class "text-2xl font-semibold text-stone-800"
(a :href (get spec "href")
:sx-get (get spec "href") :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true"
:class "text-violet-700 hover:text-violet-900 underline"
(get spec "title")))
(span :class "text-sm text-stone-400 font-mono" (get spec "filename")))
(p :class "text-stone-600" (get spec "desc"))
(div :class "bg-stone-100 rounded-lg p-5 max-h-72 overflow-y-auto"
(pre :class "text-xs leading-relaxed whitespace-pre-wrap break-words"
(code (highlight (get spec "source") "sx"))))))
spec-files))))
(defcomp ~spec-detail-content (&key spec-title spec-desc spec-filename spec-source)
(~doc-page :title spec-title
(div :class "flex items-baseline gap-3 mb-4"
(span :class "text-sm text-stone-400 font-mono" spec-filename)
(span :class "text-sm text-stone-500" spec-desc))
(div :class "bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl"
(pre :class "text-sm leading-relaxed whitespace-pre-wrap break-words"
(code (highlight spec-source "sx"))))))
(defcomp ~spec-not-found (&key slug)
(~doc-page :title "Spec Not Found"
(p :class "text-stone-600"
"No specification found for \"" slug "\". This spec may not exist yet.")))

View File

@@ -16,8 +16,8 @@
children))
(defcomp ~doc-code (&key code)
(div :class "bg-stone-50 border border-stone-200 rounded-lg p-4 overflow-x-auto"
(pre :class "text-sm" (code code))))
(div :class "bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl"
(pre :class "text-sm leading-relaxed whitespace-pre-wrap break-words" (code code))))
(defcomp ~doc-note (&key &rest children)
(div :class "border-l-4 border-violet-400 bg-violet-50 p-4 text-stone-700 text-sm"
@@ -27,7 +27,7 @@
(div :class "overflow-x-auto rounded border border-stone-200"
(table :class "w-full text-left text-sm"
(thead
(tr :class "border-b border-stone-200 bg-stone-50"
(tr :class "border-b border-stone-200 bg-stone-100"
(map (fn (h) (th :class "px-3 py-2 font-medium text-stone-600" h)) headers)))
(tbody
(map (fn (row)

View File

@@ -2,24 +2,24 @@
(defcomp ~example-card (&key title description &rest children)
(div :class "border border-stone-200 rounded-lg overflow-hidden"
(div :class "bg-stone-50 px-4 py-3 border-b border-stone-200"
(div :class "bg-stone-100 px-4 py-3 border-b border-stone-200"
(h3 :class "font-semibold text-stone-800" title)
(when description
(p :class "text-sm text-stone-500 mt-1" description)))
(div :class "p-4" children)))
(defcomp ~example-demo (&key &rest children)
(div :class "border border-dashed border-stone-300 rounded p-4 bg-white" children))
(div :class "border border-dashed border-stone-300 rounded p-4 bg-stone-100" children))
(defcomp ~example-source (&key code)
(div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3 overflow-x-auto"
(pre :class "text-sm" (code code))))
(div :class "bg-stone-100 rounded p-5 mt-3 mx-auto max-w-3xl"
(pre :class "text-sm leading-relaxed whitespace-pre-wrap break-words" (code code))))
;; --- Click to load demo ---
(defcomp ~click-to-load-demo ()
(div :class "space-y-4"
(div :id "click-result" :class "p-4 rounded bg-stone-50 text-stone-500 text-center"
(div :id "click-result" :class "p-4 rounded bg-stone-100 text-stone-500 text-center"
"Click the button to load content.")
(button
:sx-get "/examples/api/click"
@@ -50,7 +50,7 @@
(button :type "submit"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Submit"))
(div :id "form-result" :class "p-3 rounded bg-stone-50 text-stone-500 text-sm text-center"
(div :id "form-result" :class "p-3 rounded bg-stone-100 text-stone-500 text-sm text-center"
"Submit the form to see the result.")))
(defcomp ~form-result (&key name)
@@ -66,7 +66,7 @@
:sx-get "/examples/api/poll"
:sx-trigger "load, every 2s"
:sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-white text-center font-mono"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono"
"Loading...")))
(defcomp ~poll-result (&key time count)
@@ -145,10 +145,10 @@
(defcomp ~oob-demo ()
(div :class "space-y-4"
(div :class "grid grid-cols-2 gap-4"
(div :id "oob-box-a" :class "p-4 rounded border border-stone-200 bg-white text-center"
(div :id "oob-box-a" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-500" "Box A")
(p :class "text-sm text-stone-400" "Waiting..."))
(div :id "oob-box-b" :class "p-4 rounded border border-stone-200 bg-white text-center"
(div :id "oob-box-b" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-500" "Box B")
(p :class "text-sm text-stone-400" "Waiting...")))
(button
@@ -167,7 +167,7 @@
:sx-get "/examples/api/lazy"
:sx-trigger "load"
:sx-swap "innerHTML"
:class "p-6 rounded border border-stone-200 bg-stone-50 text-center"
:class "p-6 rounded border border-stone-200 bg-stone-100 text-center"
(div :class "animate-pulse space-y-2"
(div :class "h-4 bg-stone-200 rounded w-3/4 mx-auto")
(div :class "h-4 bg-stone-200 rounded w-1/2 mx-auto")))))
@@ -328,7 +328,7 @@
(p :class "text-sm text-stone-400" "Messages will appear here."))))
(defcomp ~reset-message (&key message time)
(div :class "px-3 py-2 bg-stone-50 rounded text-sm text-stone-700"
(div :class "px-3 py-2 bg-stone-100 rounded text-sm text-stone-700"
(str "[" time "] " message)))
;; --- Edit row demo ---
@@ -488,7 +488,7 @@
:sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
"Full Dashboard"))
(div :id "filter-target" :class "border border-stone-200 rounded p-4 bg-white"
(div :id "filter-target" :class "border border-stone-200 rounded p-4 bg-stone-100"
(p :class "text-sm text-stone-400" "Click a button to load content."))))
;; --- Tabs demo ---
@@ -525,7 +525,7 @@
:sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Load with animation")
(div :id "anim-target" :class "p-4 rounded border border-stone-200 bg-white text-center"
(div :id "anim-target" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-400" "Content will fade in here."))))
(defcomp ~anim-result (&key color time)
@@ -552,7 +552,7 @@
:sx-get "/examples/api/dialog/close"
:sx-target "#dialog-container"
:sx-swap "innerHTML")
(div :class "relative bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4"
(div :class "relative bg-stone-100 rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4"
(h3 :class "text-lg font-semibold text-stone-800" title)
(p :class "text-stone-600" message)
(div :class "flex justify-end gap-2"
@@ -573,23 +573,23 @@
(defcomp ~keyboard-shortcuts-demo ()
(div :class "space-y-4"
(div :class "p-4 rounded border border-stone-200 bg-stone-50"
(div :class "p-4 rounded border border-stone-200 bg-stone-100"
(p :class "text-sm text-stone-600 font-medium mb-2" "Keyboard shortcuts:")
(div :class "flex gap-4"
(div :class "flex items-center gap-1"
(kbd :class "px-2 py-0.5 bg-white border border-stone-300 rounded text-xs font-mono" "s")
(kbd :class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono" "s")
(span :class "text-sm text-stone-500" "Search"))
(div :class "flex items-center gap-1"
(kbd :class "px-2 py-0.5 bg-white border border-stone-300 rounded text-xs font-mono" "n")
(kbd :class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono" "n")
(span :class "text-sm text-stone-500" "New item"))
(div :class "flex items-center gap-1"
(kbd :class "px-2 py-0.5 bg-white border border-stone-300 rounded text-xs font-mono" "h")
(kbd :class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono" "h")
(span :class "text-sm text-stone-500" "Help"))))
(div :id "kbd-target"
:sx-get "/examples/api/keyboard?key=s"
:sx-trigger "keyup[key=='s'&&!event.target.matches('input,textarea')] from:body"
:sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-white text-center"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-400 text-sm" "Press a shortcut key..."))
(div :sx-get "/examples/api/keyboard?key=n"
:sx-trigger "keyup[key=='n'&&!event.target.matches('input,textarea')] from:body"
@@ -675,7 +675,7 @@
(button :type "submit"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Submit as JSON"))
(div :id "json-result" :class "p-3 rounded bg-stone-50 text-stone-500 text-sm"
(div :id "json-result" :class "p-3 rounded bg-stone-100 text-stone-500 text-sm"
"Submit the form to see the server echo the parsed JSON.")))
(defcomp ~json-result (&key body content-type)
@@ -697,7 +697,7 @@
:sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Send with vals")
(div :id "vals-result" :class "p-3 rounded bg-stone-50 text-sm text-stone-400"
(div :id "vals-result" :class "p-3 rounded bg-stone-100 text-sm text-stone-400"
"Click to see server-received values."))
(div :class "space-y-2"
(h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers")
@@ -708,7 +708,7 @@
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Send with headers")
(div :id "headers-result" :class "p-3 rounded bg-stone-50 text-sm text-stone-400"
(div :id "headers-result" :class "p-3 rounded bg-stone-100 text-sm text-stone-400"
"Click to see server-received headers."))))
(defcomp ~echo-result (&key label items)
@@ -729,7 +729,7 @@
:class "sx-loading-btn px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm flex items-center gap-2"
(span :class "sx-spinner w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin")
(span "Load slow endpoint"))
(div :id "loading-result" :class "p-4 rounded border border-stone-200 bg-white text-center"
(div :id "loading-result" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-400 text-sm" "Click the button — it takes 2 seconds."))))
(defcomp ~loading-result (&key time)
@@ -749,7 +749,7 @@
:sx-sync "replace"
:placeholder "Type to search (random delay 0.5-2s)..."
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(div :id "sync-result" :class "p-4 rounded border border-stone-200 bg-white"
(div :id "sync-result" :class "p-4 rounded border border-stone-200 bg-stone-100"
(p :class "text-sm text-stone-400" "Type to trigger requests — stale ones get aborted."))))
(defcomp ~sync-result (&key query delay)
@@ -768,7 +768,7 @@
:sx-retry "exponential:1000:8000"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Call flaky endpoint")
(div :id "retry-result" :class "p-4 rounded border border-stone-200 bg-white text-center"
(div :id "retry-result" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-400 text-sm" "Endpoint fails twice, succeeds on 3rd attempt."))))
(defcomp ~retry-result (&key attempt message)

View File

@@ -235,7 +235,7 @@
(div :class "p-3 bg-amber-50 rounded text-center"
(p :class "text-2xl font-bold text-amber-700" "$4.2k")
(p :class "text-xs text-amber-600" "Revenue")))
(div :id "dash-footer" :class "p-3 bg-stone-50 rounded"
(div :id "dash-footer" :class "p-3 bg-stone-100 rounded"
(p :class "text-sm text-stone-500" "Last updated: " now)))))
;; ---------------------------------------------------------------------------

View File

@@ -3,14 +3,14 @@
(defcomp ~sx-hero (&key &rest children)
(div :class "max-w-4xl mx-auto px-6 py-16 text-center"
(h1 :class "text-5xl font-bold text-stone-900 mb-4"
(span :class "text-violet-600 font-mono" "(<x>)"))
(span :class "text-violet-600 font-mono" "(<sx>)"))
(p :class "text-2xl text-stone-600 mb-8"
"s-expressions for the web")
(p :class "text-lg text-stone-500 max-w-2xl mx-auto mb-12"
"A hypermedia-driven UI engine that combines htmx's server-first philosophy "
"with React's component model. S-expressions over the wire — no HTML, no JavaScript frameworks.")
(div :class "bg-stone-50 border border-stone-200 rounded-lg p-6 text-left font-mono text-sm overflow-x-auto"
(pre :class "leading-relaxed" children))))
(div :class "bg-stone-100 rounded-lg p-6 text-left font-mono text-sm mx-auto max-w-2xl"
(pre :class "leading-relaxed whitespace-pre-wrap" children))))
(defcomp ~sx-philosophy ()
(div :class "max-w-4xl mx-auto px-6 py-12"

View File

@@ -240,4 +240,42 @@
"tail-call-optimization" (~essay-tail-call-optimization)
"continuations" (~essay-continuations)
"godel-escher-bach" (~essay-godel-escher-bach)
"reflexive-web" (~essay-reflexive-web)
:else (~essay-sx-sucks)))
;; ---------------------------------------------------------------------------
;; Specs section
;; ---------------------------------------------------------------------------
(defpage specs-index
:path "/specs/"
:auth :public
:layout (:sx-section
:section "Specs"
:sub-label "Specs"
:sub-href "/specs/core"
:sub-nav (~section-nav :items specs-nav-items :current "Core")
:selected "Core")
:data (spec-data "core")
:content (~spec-core-content :spec-files spec-files))
(defpage specs-page
:path "/specs/<slug>"
:auth :public
:layout (:sx-section
:section "Specs"
:sub-label "Specs"
:sub-href "/specs/core"
:sub-nav (~section-nav :items specs-nav-items
:current (find-current specs-nav-items slug))
:selected (or (find-current specs-nav-items slug) ""))
:data (spec-data slug)
:content (if spec-not-found
(~spec-not-found :slug slug)
(case slug
"core" (~spec-core-content :spec-files spec-files)
:else (~spec-detail-content
:spec-title spec-title
:spec-desc spec-desc
:spec-filename spec-filename
:spec-source spec-source))))

View File

@@ -16,6 +16,7 @@ def _register_sx_helpers() -> None:
"primitives-data": _primitives_data,
"reference-data": _reference_data,
"attr-detail-data": _attr_detail_data,
"spec-data": _spec_data,
})
@@ -103,6 +104,63 @@ def _reference_data(slug: str) -> dict:
}
_SPEC_FILES = {
"parser": ("parser.sx", "Parser", "Tokenization and parsing of SX source text into AST."),
"evaluator": ("eval.sx", "Evaluator", "Tree-walking evaluation of SX expressions."),
"primitives": ("primitives.sx", "Primitives", "All built-in pure functions and their signatures."),
"renderer": ("render.sx", "Renderer", "Rendering evaluated expressions to DOM, HTML, or SX wire format."),
}
def _spec_data(slug: str) -> dict:
"""Return spec file source and highlighted version for display."""
import os
from content.highlight import highlight as _highlight
ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref")
# Normalise — inside container shared is at /app/shared
if not os.path.isdir(ref_dir):
ref_dir = "/app/shared/sx/ref"
base = {"spec-not-found": None, "spec-title": None, "spec-desc": None,
"spec-filename": None, "spec-source": None, "spec-files": None}
if slug == "core":
specs = []
for key in ("parser", "evaluator", "primitives", "renderer"):
filename, title, desc = _SPEC_FILES[key]
filepath = os.path.join(ref_dir, filename)
source = _read_spec(filepath)
specs.append({
"title": title,
"desc": desc,
"filename": filename,
"source": source,
"href": f"/specs/{key}",
})
return {**base, "spec-title": "SX Core Specification", "spec-files": specs}
info = _SPEC_FILES.get(slug)
if not info:
return {**base, "spec-not-found": True}
filename, title, desc = info
filepath = os.path.join(ref_dir, filename)
source = _read_spec(filepath)
return {**base,
"spec-title": title, "spec-desc": desc,
"spec-filename": filename, "spec-source": source}
def _read_spec(filepath: str) -> str:
"""Read a spec file, returning empty string if missing."""
try:
with open(filepath, encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
return ";; spec file not found"
def _attr_detail_data(slug: str) -> dict:
"""Return attribute detail data for a specific attribute slug.

View File

@@ -16,7 +16,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Load server time")
(div :id "ref-get-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click to load.")))
;; ---------------------------------------------------------------------------
@@ -36,7 +36,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Greet"))
(div :id "ref-post-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Submit to see greeting.")))
;; ---------------------------------------------------------------------------
@@ -45,7 +45,7 @@
(defcomp ~ref-put-demo ()
(div :id "ref-put-view"
(div :class "flex items-center justify-between p-3 bg-stone-50 rounded"
(div :class "flex items-center justify-between p-3 bg-stone-100 rounded"
(span :class "text-stone-700 text-sm" "Status: " (strong "draft"))
(button
:sx-put "/reference/api/status"
@@ -83,7 +83,7 @@
(defcomp ~ref-patch-demo ()
(div :id "ref-patch-view" :class "space-y-2"
(div :class "p-3 bg-stone-50 rounded"
(div :class "p-3 bg-stone-100 rounded"
(span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light")))
(div :class "flex gap-2"
(button :sx-patch "/reference/api/theme"
@@ -93,7 +93,7 @@
(button :sx-patch "/reference/api/theme"
:sx-vals "{\"theme\": \"light\"}"
:sx-target "#ref-patch-val" :sx-swap "innerHTML"
:class "px-3 py-1 bg-white border border-stone-300 text-stone-700 rounded text-sm" "Light"))))
:class "px-3 py-1 bg-stone-100 border border-stone-300 text-stone-700 rounded text-sm" "Light"))))
;; ---------------------------------------------------------------------------
;; sx-trigger
@@ -108,7 +108,7 @@
:sx-swap "innerHTML"
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(div :id "ref-trigger-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Start typing to trigger a search.")))
;; ---------------------------------------------------------------------------
@@ -186,7 +186,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Load (selecting #the-content)")
(div :id "ref-select-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Only the selected fragment will appear here.")))
;; ---------------------------------------------------------------------------
@@ -242,7 +242,7 @@
(p :class "text-xs text-stone-400"
"With sync:replace, each new keystroke aborts the in-flight request.")
(div :id "ref-sync-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Type to see only the latest result.")))
;; ---------------------------------------------------------------------------
@@ -262,7 +262,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Upload"))
(div :id "ref-encoding-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Select a file and submit.")))
;; ---------------------------------------------------------------------------
@@ -278,7 +278,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Send with custom headers")
(div :id "ref-headers-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click to see echoed headers.")))
;; ---------------------------------------------------------------------------
@@ -302,7 +302,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Filter"))
(div :id "ref-include-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click Filter — the select value is included in the request.")))
;; ---------------------------------------------------------------------------
@@ -318,7 +318,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Send with extra values")
(div :id "ref-vals-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click to see echoed values.")))
;; ---------------------------------------------------------------------------
@@ -369,7 +369,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Click me")
(div :id "ref-on-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click the button — runs JavaScript, no server request.")))
;; ---------------------------------------------------------------------------
@@ -385,7 +385,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Call flaky endpoint")
(div :id "ref-retry-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"This endpoint fails 2 out of 3 times. sx-retry retries automatically.")))
;; ---------------------------------------------------------------------------
@@ -440,7 +440,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Hover then click (preloaded)")
(div :id "ref-preload-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Hover over the button first, then click — the response is instant.")))
;; ---------------------------------------------------------------------------
@@ -461,7 +461,7 @@
(input :id "ref-preserved-input" :sx-preserve "true"
:type "text" :placeholder "Type here — preserved across swaps"
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm")
(div :class "p-2 bg-stone-50 rounded text-sm text-stone-600"
(div :class "p-2 bg-stone-100 rounded text-sm text-stone-600"
"This text will be replaced on swap."))))
;; ---------------------------------------------------------------------------
@@ -484,7 +484,7 @@
:style "display: none"
"Loading..."))
(div :id "ref-indicator-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click to load (indicator shows during request).")))
;; ---------------------------------------------------------------------------
@@ -506,7 +506,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Submit"))
(div :id "ref-validate-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Submit with invalid/empty email to see validation.")))
;; ---------------------------------------------------------------------------
@@ -526,7 +526,7 @@
(p :class "text-sm text-amber-800" "This subtree has sx-ignore — it won't change.")
(input :type "text" :placeholder "Type here — ignored during swap"
:class "mt-1 w-full px-2 py-1 border border-amber-300 rounded text-sm"))
(div :class "p-2 bg-stone-50 rounded text-sm text-stone-600"
(div :class "p-2 bg-stone-100 rounded text-sm text-stone-600"
"This text WILL be replaced on swap."))))
;; ---------------------------------------------------------------------------
@@ -566,7 +566,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Load (replaces URL)")
(div :id "ref-replurl-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click to load — URL changes but no new history entry.")))
;; ---------------------------------------------------------------------------
@@ -586,7 +586,7 @@
"Click (disables during request)")
(span :class "text-xs text-stone-400" "Button is disabled while request is in-flight."))
(div :id "ref-diselt-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click the button to see it disable during the request.")))
;; ---------------------------------------------------------------------------
@@ -603,7 +603,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Prompt & send")
(div :id "ref-prompt-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Click to enter a name via prompt — it is sent as the SX-Prompt header.")))
;; ---------------------------------------------------------------------------
@@ -626,7 +626,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
"Submit"))
(div :id "ref-params-result"
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
:class "p-3 rounded bg-stone-100 text-stone-400 text-sm"
"Only 'name' will be sent — 'secret' is filtered by sx-params.")))
;; ---------------------------------------------------------------------------
@@ -639,7 +639,7 @@
:sx-sse-swap "time"
:sx-swap "innerHTML"
(div :id "ref-sse-result"
:class "p-3 rounded bg-stone-50 text-stone-600 text-sm font-mono"
:class "p-3 rounded bg-stone-100 text-stone-600 text-sm font-mono"
"Connecting to SSE stream..."))
(p :class "text-xs text-stone-400"
"Server pushes time updates every 2 seconds via Server-Sent Events.")))