Add header and event detail pages, fix copyright, rename essay

- Detail pages for all 18 HTTP headers with descriptions, example usage,
  direction badges (request/response/both), and live demos for SX-Prompt,
  SX-Trigger, SX-Retarget
- Detail pages for all 10 DOM events with descriptions, example usage,
  and live demos for beforeRequest, afterSettle, responseError,
  validationFailed
- Header and event table rows now link to their detail pages
- Fix copyright symbol on home page (was literal \u00a9, now actual ©)
- Rename "Godel, Escher, Bach" essay to "Strange Loops" with updated summary
- Remove duplicate script injection from bootstrapper page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 16:25:15 +00:00
parent 1797bd4b16
commit 5fff83ae79
11 changed files with 779 additions and 18 deletions

View File

@@ -261,6 +261,463 @@ EDIT_ROW_DATA = [
{"id": "4", "name": "Widget D", "price": "45.00", "stock": "67"},
]
# ---------------------------------------------------------------------------
# Reference: Header detail pages
# ---------------------------------------------------------------------------
HEADER_DETAILS: dict[str, dict] = {
# --- Request Headers ---
"SX-Request": {
"direction": "request",
"description": (
"Sent on every sx-initiated request. Allows the server to distinguish "
"AJAX partial requests from full page loads, and return the appropriate "
"response format (fragment vs full page)."
),
"example": (
';; Server-side: check for sx request\n'
'(if (header "SX-Request")\n'
' ;; Return a fragment\n'
' (div :class "result" "Partial content")\n'
' ;; Return full page\n'
' (~full-page-layout ...))'
),
},
"SX-Current-URL": {
"direction": "request",
"description": (
"Sends the browser's current URL so the server knows where the user is. "
"Useful for server-side logic that depends on context — e.g. highlighting "
"the current nav item, or returning context-appropriate content."
),
"example": (
';; Server reads the current URL to decide context\n'
'(let ((url (header "SX-Current-URL")))\n'
' (nav\n'
' (a :href "/docs" :class (if (starts-with? url "/docs") "active" "") "Docs")\n'
' (a :href "/api" :class (if (starts-with? url "/api") "active" "") "API")))'
),
},
"SX-Target": {
"direction": "request",
"description": (
"Tells the server which element will receive the response. "
"The server can use this to tailor the response — for example, "
"returning different content depending on whether the target is "
"a sidebar, modal, or main panel."
),
"example": (
';; Server checks target to decide response format\n'
'(let ((target (header "SX-Target")))\n'
' (if (= target "#sidebar")\n'
' (~compact-summary :data data)\n'
' (~full-detail :data data)))'
),
},
"SX-Components": {
"direction": "request",
"description": (
"Comma-separated list of component names the client already has cached. "
"The server can skip sending defcomp definitions the client already knows, "
"reducing response size. This is the component caching protocol."
),
"example": (
';; Client sends: SX-Components: ~card,~nav-link,~footer\n'
';; Server omits those defcomps from the response.\n'
';; Only new/changed components are sent.\n'
'(response\n'
' :components (filter-new known-components)\n'
' :content (~page-content))'
),
},
"SX-Css": {
"direction": "request",
"description": (
"Sends the CSS classes or hash the client already has. "
"The server uses this to send only new CSS rules the client needs, "
"avoiding duplicate rule injection. Part of the on-demand CSS protocol."
),
"example": (
';; Client sends hash of known CSS classes\n'
';; Server compares and only returns new classes\n'
'(let ((client-css (header "SX-Css")))\n'
' (set-header "SX-Css-Add"\n'
' (join "," (diff new-classes client-css))))'
),
},
"SX-History-Restore": {
"direction": "request",
"description": (
"Set to \"true\" when the browser restores a page from history (back/forward). "
"The server can use this to return cached content or skip side effects "
"that should only happen on initial navigation."
),
"example": (
';; Skip analytics on history restore\n'
'(when (not (header "SX-History-Restore"))\n'
' (track-page-view url))\n'
'(~page-content :data data)'
),
},
"SX-Css-Hash": {
"direction": "both",
"description": (
"Request: 8-character hash of the client's known CSS class set. "
"Response: hash of the cumulative CSS set after this response. "
"Client stores the response hash and sends it on the next request, "
"enabling efficient CSS delta tracking."
),
"example": (
';; Request header: SX-Css-Hash: a1b2c3d4\n'
';; Server compares hash to decide if CSS diff needed\n'
';;\n'
';; Response header: SX-Css-Hash: e5f6g7h8\n'
';; Client stores new hash for next request'
),
},
"SX-Prompt": {
"direction": "request",
"description": (
"Contains the value entered by the user in a window.prompt() dialog, "
"triggered by the sx-prompt attribute. Allows collecting a single text "
"input without a form."
),
"example": (
';; Button triggers a prompt dialog\n'
'(button :sx-get "/api/rename"\n'
' :sx-prompt "Enter new name:"\n'
' "Rename")\n'
'\n'
';; Server reads the prompted value\n'
'(let ((name (header "SX-Prompt")))\n'
' (span "Renamed to: " (strong name)))'
),
"demo": "ref-header-prompt-demo",
},
# --- Response Headers ---
"SX-Css-Add": {
"direction": "response",
"description": (
"Comma-separated list of new CSS class names added by this response. "
"The client injects the corresponding CSS rules into the document. "
"Only classes the client doesn't already have are included."
),
"example": (
';; Server response includes new CSS classes\n'
';; SX-Css-Add: bg-emerald-500,text-white,rounded-xl\n'
';;\n'
';; Client automatically injects rules for these\n'
';; classes from the style dictionary.'
),
},
"SX-Trigger": {
"direction": "response",
"description": (
"Dispatch custom DOM event(s) on the target element after the response "
"is received. Can be a simple event name or JSON for multiple events "
"with detail data. Useful for coordinating UI updates across components."
),
"example": (
';; Simple event\n'
';; SX-Trigger: itemAdded\n'
';;\n'
';; Multiple events with data\n'
';; SX-Trigger: {"itemAdded": {"id": 42}, "showNotification": {"message": "Saved!"}}\n'
';;\n'
';; Listen in SX:\n'
'(div :sx-on:itemAdded "this.querySelector(\'.count\').textContent = event.detail.id")'
),
"demo": "ref-header-trigger-demo",
},
"SX-Trigger-After-Swap": {
"direction": "response",
"description": (
"Like SX-Trigger, but fires after the DOM swap completes. "
"Use this when your event handler needs to reference the new DOM content "
"that was just swapped in."
),
"example": (
';; Server signals that new content needs initialization\n'
';; SX-Trigger-After-Swap: contentReady\n'
';;\n'
';; Client initializes after swap\n'
'(div :sx-on:contentReady "initCharts(this)")'
),
},
"SX-Trigger-After-Settle": {
"direction": "response",
"description": (
"Like SX-Trigger, but fires after the DOM has fully settled — "
"scripts executed, transitions complete. The latest point to react "
"to a response."
),
"example": (
';; SX-Trigger-After-Settle: animationReady\n'
';;\n'
';; Trigger animations after everything has settled\n'
'(div :sx-on:animationReady "this.classList.add(\'fade-in\')")'
),
},
"SX-Retarget": {
"direction": "response",
"description": (
"Override the target element for this response. The server can redirect "
"content to a different element than what the client specified in sx-target. "
"Useful for error messages or redirecting content dynamically."
),
"example": (
';; Client targets a form result area\n'
'(form :sx-post "/api/save"\n'
' :sx-target "#result" ...)\n'
'\n'
';; Server redirects errors to a different element\n'
';; SX-Retarget: #error-banner\n'
'(div :class "error" "Validation failed")'
),
"demo": "ref-header-retarget-demo",
},
"SX-Reswap": {
"direction": "response",
"description": (
"Override the swap strategy for this response. The server can change "
"how content is inserted regardless of what the client specified in sx-swap. "
"Useful when the server decides the swap mode based on the result."
),
"example": (
';; Client expects innerHTML swap\n'
'(button :sx-get "/api/check"\n'
' :sx-target "#panel" :sx-swap "innerHTML" ...)\n'
'\n'
';; Server overrides to append instead\n'
';; SX-Reswap: beforeend\n'
'(div :class "notification" "New item added")'
),
},
"SX-Redirect": {
"direction": "response",
"description": (
"Redirect the browser to a new URL using full page navigation. "
"Unlike sx-push-url which does client-side history, this triggers "
"a real browser navigation — useful after form submissions like login or checkout."
),
"example": (
';; After successful login, redirect to dashboard\n'
';; SX-Redirect: /dashboard\n'
';;\n'
';; Server handler:\n'
'(when (valid-credentials? user pass)\n'
' (set-header "SX-Redirect" "/dashboard")\n'
' (span "Redirecting..."))'
),
},
"SX-Refresh": {
"direction": "response",
"description": (
"Set to \"true\" to reload the current page. "
"A blunt tool — useful when server-side state has changed significantly "
"and a partial update won't suffice."
),
"example": (
';; After a major state change, force refresh\n'
';; SX-Refresh: true\n'
';;\n'
';; Server handler:\n'
'(when (deploy-complete?)\n'
' (set-header "SX-Refresh" "true")\n'
' (span "Deployed — refreshing..."))'
),
},
"SX-Location": {
"direction": "response",
"description": (
"Trigger client-side navigation: fetch the given URL, swap it into "
"#main-panel, and push to browser history. Like clicking an sx-boosted link, "
"but triggered from the server. Can be a URL string or JSON with options."
),
"example": (
';; Simple: navigate to a page\n'
';; SX-Location: /docs/introduction\n'
';;\n'
';; With options:\n'
';; SX-Location: {"path": "/docs/intro", "target": "#sidebar", "swap": "innerHTML"}'
),
},
"SX-Replace-Url": {
"direction": "response",
"description": (
"Replace the current URL using history.replaceState without creating "
"a new history entry. Useful for normalizing URLs after redirects, "
"or updating the URL to reflect server-resolved state."
),
"example": (
';; Normalize URL after slug resolution\n'
';; SX-Replace-Url: /docs/introduction\n'
';;\n'
';; Server handler:\n'
'(let ((canonical (resolve-slug slug)))\n'
' (set-header "SX-Replace-Url" canonical)\n'
' (~doc-content :slug canonical))'
),
},
}
# ---------------------------------------------------------------------------
# Reference: Event detail pages
# ---------------------------------------------------------------------------
EVENT_DETAILS: dict[str, dict] = {
"sx:beforeRequest": {
"description": (
"Fired on the triggering element before an sx request is issued. "
"Call event.preventDefault() to cancel the request entirely. "
"Useful for validation, confirmation, or conditional request logic."
),
"example": (
';; Cancel request if form is empty\n'
'(form :sx-post "/api/save"\n'
' :sx-target "#result"\n'
' :sx-on:sx:beforeRequest "if (!this.querySelector(\'input\').value) event.preventDefault()"\n'
' (input :name "data" :placeholder "Required")\n'
' (button :type "submit" "Save"))'
),
"demo": "ref-event-before-request-demo",
},
"sx:afterRequest": {
"description": (
"Fired on the triggering element after a successful sx response is received, "
"before the swap happens. The response data is available on event.detail. "
"Use this for logging, analytics, or pre-swap side effects."
),
"example": (
';; Log successful requests\n'
'(button :sx-get "/api/data"\n'
' :sx-target "#result"\n'
' :sx-on:sx:afterRequest "console.log(\'Response received\', event.detail)"\n'
' "Load data")'
),
},
"sx:afterSwap": {
"description": (
"Fired after the response content has been swapped into the DOM. "
"The new content is in place but scripts may not have executed yet. "
"Use this to initialize UI on newly inserted content."
),
"example": (
';; Initialize tooltips on new content\n'
'(div :sx-on:sx:afterSwap "initTooltips(this)"\n'
' (button :sx-get "/api/items"\n'
' :sx-target "#item-list"\n'
' "Load items")\n'
' (div :id "item-list"))'
),
},
"sx:afterSettle": {
"description": (
"Fired after the DOM has fully settled — all scripts executed, transitions "
"complete. This is the safest point to run code that depends on the final "
"state of the DOM after a swap."
),
"example": (
';; Scroll to new content after settle\n'
'(div :sx-on:sx:afterSettle "document.getElementById(\'new-item\').scrollIntoView()"\n'
' (button :sx-get "/api/append"\n'
' :sx-target "#list" :sx-swap "beforeend"\n'
' "Add item")\n'
' (div :id "list"))'
),
"demo": "ref-event-after-settle-demo",
},
"sx:responseError": {
"description": (
"Fired when the server responds with an HTTP error (4xx or 5xx). "
"event.detail contains the status code and response. "
"Use this for error handling, showing notifications, or retry logic."
),
"example": (
';; Show error notification\n'
'(div :sx-on:sx:responseError "alert(\'Error: \' + event.detail.status)"\n'
' (button :sx-get "/api/risky"\n'
' :sx-target "#result"\n'
' "Try it")\n'
' (div :id "result"))'
),
"demo": "ref-event-response-error-demo",
},
"sx:sendError": {
"description": (
"Fired when the request fails to send — typically a network error, "
"DNS failure, or CORS issue. Unlike sx:responseError, no HTTP response "
"was received at all."
),
"example": (
';; Handle network failures\n'
'(div :sx-on:sx:sendError "this.querySelector(\'.status\').textContent = \'Offline\'"\n'
' (button :sx-get "/api/data"\n'
' :sx-target "#result"\n'
' "Load")\n'
' (span :class "status")\n'
' (div :id "result"))'
),
},
"sx:validationFailed": {
"description": (
"Fired when sx-validate is set and the form fails HTML5 validation. "
"The request is not sent. Use this to show custom validation UI "
"or highlight invalid fields."
),
"example": (
';; Highlight invalid fields\n'
'(form :sx-post "/api/save"\n'
' :sx-validate "true"\n'
' :sx-on:sx:validationFailed "this.classList.add(\'shake\')"\n'
' (input :type "email" :required "true" :name "email"\n'
' :placeholder "Email (required)")\n'
' (button :type "submit" "Save"))'
),
"demo": "ref-event-validation-failed-demo",
},
"sx:sseOpen": {
"description": (
"Fired when a Server-Sent Events connection is successfully established. "
"Use this to update connection status indicators."
),
"example": (
';; Show connected status\n'
'(div :sx-sse "/api/stream"\n'
' :sx-on:sx:sseOpen "this.querySelector(\'.status\').textContent = \'Connected\'"\n'
' (span :class "status" "Connecting...")\n'
' (div :id "messages"))'
),
},
"sx:sseMessage": {
"description": (
"Fired when an SSE message is received and swapped into the DOM. "
"event.detail contains the message data. Fires for each individual message."
),
"example": (
';; Count received messages\n'
'(div :sx-sse "/api/stream"\n'
' :sx-sse-swap "update"\n'
' :sx-on:sx:sseMessage "this.dataset.count = (parseInt(this.dataset.count||0)+1); this.querySelector(\'.count\').textContent = this.dataset.count"\n'
' (span :class "count" "0") " messages received"\n'
' (div :id "stream-content"))'
),
},
"sx:sseError": {
"description": (
"Fired when an SSE connection encounters an error or is closed unexpectedly. "
"Use this to show reconnection status or fall back to polling."
),
"example": (
';; Show disconnected status\n'
'(div :sx-sse "/api/stream"\n'
' :sx-on:sx:sseError "this.querySelector(\'.status\').textContent = \'Disconnected\'"\n'
' (span :class "status" "Connecting...")\n'
' (div :id "messages"))'
),
},
}
# ---------------------------------------------------------------------------
# Reference: Attribute detail pages
# ---------------------------------------------------------------------------