Restructure SX docs nav into 4 top-level sections with nested routing

New hierarchy: Geography (Reactive Islands, Hypermedia Lakes, Marshes,
Isomorphism), Language (Docs, Specs, Bootstrappers, Testing),
Applications (CSSX, Protocols), Etc (Essays, Philosophy, Plans).

All routes updated to match: /reactive/* → /geography/reactive/*,
/docs/* → /language/docs/*, /essays/* → /etc/essays/*, etc.
Updates nav-data.sx, all defpage routes, API endpoints, internal links
across 43 files. Enhanced find-nav-match for nested group resolution.

Also includes: page-helpers-demo sf-total fix (reduce instead of set!),
rebootstrapped sx-browser.js and sx_ref.py, defensive slice/rest guards.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 18:50:31 +00:00
parent 0174fbfea3
commit 4d54be6b6b
43 changed files with 813 additions and 746 deletions

View File

@@ -14,7 +14,7 @@
// ========================================================================= // =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-11T16:56:11Z"; var SX_VERSION = "2026-03-11T17:38:00Z";
function isNil(x) { return x === NIL || x === null || x === undefined; } function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); } function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -275,6 +275,7 @@
function error(msg) { throw new Error(msg); } function error(msg) { throw new Error(msg); }
function inspect(x) { return JSON.stringify(x); } function inspect(x) { return JSON.stringify(x); }
function debugLog() { console.error.apply(console, ["[sx-debug]"].concat(Array.prototype.slice.call(arguments))); }
@@ -355,7 +356,7 @@
PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); }; PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); };
PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; }; PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; };
PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; }; PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; };
PRIMITIVES["slice"] = function(c, a, b) { return b !== undefined ? c.slice(a, b) : c.slice(a); }; PRIMITIVES["slice"] = function(c, a, b) { if (!c || typeof c.slice !== "function") { console.error("[sx-debug] slice called on non-sliceable:", typeof c, c, "a=", a, "b=", b, new Error().stack); return []; } return b !== undefined ? c.slice(a, b) : c.slice(a); };
PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); }; PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); };
PRIMITIVES["string-length"] = function(s) { return String(s).length; }; PRIMITIVES["string-length"] = function(s) { return String(s).length; };
PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; }; PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; };
@@ -382,7 +383,7 @@
PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; }; PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; };
PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; }; PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; };
PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; }; PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; };
PRIMITIVES["rest"] = function(c) { return c ? c.slice(1) : []; }; PRIMITIVES["rest"] = function(c) { if (c && typeof c.slice !== "function") { console.error("[sx-debug] rest called on non-sliceable:", typeof c, c, new Error().stack); return []; } return c ? c.slice(1) : []; };
PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; }; PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; };
PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); }; PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); };
PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); }; PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); };
@@ -719,7 +720,7 @@
// eval-expr // eval-expr
var evalExpr = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() { var evalExpr = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() {
var name = symbolName(expr); var name = symbolName(expr);
return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name)))))))); return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : (debugLog("Undefined symbol:", name, "primitive?:", isPrimitive(name)), error((String("Undefined symbol: ") + String(name)))))))));
})(); if (_m == "keyword") return keywordName(expr); if (_m == "dict") return mapDict(function(k, v) { return trampoline(evalExpr(v, env)); }, expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : evalList(expr, env)); return expr; })(); }; })(); if (_m == "keyword") return keywordName(expr); if (_m == "dict") return mapDict(function(k, v) { return trampoline(evalExpr(v, env)); }, expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : evalList(expr, env)); return expr; })(); };
// eval-list // eval-list
@@ -5216,6 +5217,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse; if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse;
if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs; if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs;
PRIMITIVES["sx-parse"] = sxParse; PRIMITIVES["sx-parse"] = sxParse;
PRIMITIVES["console-log"] = function() { console.log.apply(console, ["[sx]"].concat(Array.prototype.slice.call(arguments))); return arguments.length > 0 ? arguments[0] : NIL; };
// Expose deps module functions as primitives so runtime-evaluated SX code // Expose deps module functions as primitives so runtime-evaluated SX code
// (e.g. test-deps.sx in browser) can call them // (e.g. test-deps.sx in browser) can call them

View File

@@ -76,7 +76,7 @@ def register_page_helpers(service: str, helpers: dict[str, Any]) -> None:
Then in .sx:: Then in .sx::
(defpage docs-page (defpage docs-page
:path "/docs/<slug>" :path "/language/docs/<slug>"
:auth :public :auth :public
:content (docs-content slug)) :content (docs-content slug))
""" """

View File

@@ -91,7 +91,8 @@
(= name "true") true (= name "true") true
(= name "false") false (= name "false") false
(= name "nil") nil (= name "nil") nil
:else (error (str "Undefined symbol: " name)))) :else (do (debug-log "Undefined symbol:" name "primitive?:" (primitive? name))
(error (str "Undefined symbol: " name)))))
;; --- keyword → its string name --- ;; --- keyword → its string name ---
"keyword" (keyword-name expr) "keyword" (keyword-name expr)

View File

@@ -92,7 +92,7 @@
(define build-ref-items-with-href (define build-ref-items-with-href
(fn (items base-path detail-keys n-fields) (fn (items base-path detail-keys n-fields)
;; items: list of lists (tuples), each with n-fields elements ;; items: list of lists (tuples), each with n-fields elements
;; base-path: e.g. "/hypermedia/reference/attributes/" ;; base-path: e.g. "/geography/hypermedia/reference/attributes/"
;; detail-keys: list of strings (keys that have detail pages) ;; detail-keys: list of strings (keys that have detail pages)
;; n-fields: 2 or 3 (number of fields per tuple) ;; n-fields: 2 or 3 (number of fields per tuple)
(map (map
@@ -128,26 +128,26 @@
"attributes" "attributes"
{"req-attrs" (build-ref-items-with-href {"req-attrs" (build-ref-items-with-href
(get raw-data "req-attrs") (get raw-data "req-attrs")
"/hypermedia/reference/attributes/" detail-keys 3) "/geography/hypermedia/reference/attributes/" detail-keys 3)
"beh-attrs" (build-ref-items-with-href "beh-attrs" (build-ref-items-with-href
(get raw-data "beh-attrs") (get raw-data "beh-attrs")
"/hypermedia/reference/attributes/" detail-keys 3) "/geography/hypermedia/reference/attributes/" detail-keys 3)
"uniq-attrs" (build-ref-items-with-href "uniq-attrs" (build-ref-items-with-href
(get raw-data "uniq-attrs") (get raw-data "uniq-attrs")
"/hypermedia/reference/attributes/" detail-keys 3)} "/geography/hypermedia/reference/attributes/" detail-keys 3)}
"headers" "headers"
{"req-headers" (build-ref-items-with-href {"req-headers" (build-ref-items-with-href
(get raw-data "req-headers") (get raw-data "req-headers")
"/hypermedia/reference/headers/" detail-keys 3) "/geography/hypermedia/reference/headers/" detail-keys 3)
"resp-headers" (build-ref-items-with-href "resp-headers" (build-ref-items-with-href
(get raw-data "resp-headers") (get raw-data "resp-headers")
"/hypermedia/reference/headers/" detail-keys 3)} "/geography/hypermedia/reference/headers/" detail-keys 3)}
"events" "events"
{"events-list" (build-ref-items-with-href {"events-list" (build-ref-items-with-href
(get raw-data "events-list") (get raw-data "events-list")
"/hypermedia/reference/events/" detail-keys 2)} "/geography/hypermedia/reference/events/" detail-keys 2)}
"js-api" "js-api"
{"js-api-list" (map (fn (item) {"name" (nth item 0) "desc" (nth item 1)}) {"js-api-list" (map (fn (item) {"name" (nth item 0) "desc" (nth item 1)})
@@ -157,13 +157,13 @@
:else :else
{"req-attrs" (build-ref-items-with-href {"req-attrs" (build-ref-items-with-href
(get raw-data "req-attrs") (get raw-data "req-attrs")
"/hypermedia/reference/attributes/" detail-keys 3) "/geography/hypermedia/reference/attributes/" detail-keys 3)
"beh-attrs" (build-ref-items-with-href "beh-attrs" (build-ref-items-with-href
(get raw-data "beh-attrs") (get raw-data "beh-attrs")
"/hypermedia/reference/attributes/" detail-keys 3) "/geography/hypermedia/reference/attributes/" detail-keys 3)
"uniq-attrs" (build-ref-items-with-href "uniq-attrs" (build-ref-items-with-href
(get raw-data "uniq-attrs") (get raw-data "uniq-attrs")
"/hypermedia/reference/attributes/" detail-keys 3)}))) "/geography/hypermedia/reference/attributes/" detail-keys 3)})))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------

View File

@@ -959,7 +959,7 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); }; PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); };
PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; }; PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; };
PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; }; PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; };
PRIMITIVES["slice"] = function(c, a, b) { return b !== undefined ? c.slice(a, b) : c.slice(a); }; PRIMITIVES["slice"] = function(c, a, b) { if (!c || typeof c.slice !== "function") { console.error("[sx-debug] slice called on non-sliceable:", typeof c, c, "a=", a, "b=", b, new Error().stack); return []; } return b !== undefined ? c.slice(a, b) : c.slice(a); };
PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); }; PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); };
PRIMITIVES["string-length"] = function(s) { return String(s).length; }; PRIMITIVES["string-length"] = function(s) { return String(s).length; };
PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; }; PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; };
@@ -987,7 +987,7 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; }; PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; };
PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; }; PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; };
PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; }; PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; };
PRIMITIVES["rest"] = function(c) { return c ? c.slice(1) : []; }; PRIMITIVES["rest"] = function(c) { if (c && typeof c.slice !== "function") { console.error("[sx-debug] rest called on non-sliceable:", typeof c, c, new Error().stack); return []; } return c ? c.slice(1) : []; };
PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; }; PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; };
PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); }; PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); };
PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); }; PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); };
@@ -1265,6 +1265,7 @@ PLATFORM_JS_PRE = '''
function error(msg) { throw new Error(msg); } function error(msg) { throw new Error(msg); }
function inspect(x) { return JSON.stringify(x); } function inspect(x) { return JSON.stringify(x); }
function debugLog() { console.error.apply(console, ["[sx-debug]"].concat(Array.prototype.slice.call(arguments))); }
''' '''
@@ -2898,7 +2899,8 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_
if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent; if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent;
if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse; if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse;
if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs; if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs;
PRIMITIVES["sx-parse"] = sxParse;''') PRIMITIVES["sx-parse"] = sxParse;
PRIMITIVES["console-log"] = function() { console.log.apply(console, ["[sx]"].concat(Array.prototype.slice.call(arguments))); return arguments.length > 0 ? arguments[0] : NIL; };''')
if has_deps: if has_deps:
lines.append(''' lines.append('''
// Expose deps module functions as primitives so runtime-evaluated SX code // Expose deps module functions as primitives so runtime-evaluated SX code

View File

@@ -1184,6 +1184,7 @@ def eval_expr(expr, env):
elif sx_truthy((name == 'nil')): elif sx_truthy((name == 'nil')):
return NIL return NIL
else: else:
debug_log('Undefined symbol:', name, 'primitive?:', is_primitive(name))
return error(sx_str('Undefined symbol: ', name)) return error(sx_str('Undefined symbol: ', name))
elif _match == 'keyword': elif _match == 'keyword':
return keyword_name(expr) return keyword_name(expr)

View File

@@ -22,7 +22,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# Example API endpoints (for live demos) # Example API endpoints (for live demos)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@bp.get("/hypermedia/examples/api/click") @bp.get("/geography/hypermedia/examples/api/click")
async def api_click(): async def api_click():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -35,7 +35,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/form") @bp.post("/geography/hypermedia/examples/api/form")
async def api_form(): async def api_form():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -51,7 +51,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_poll_count = {"n": 0} _poll_count = {"n": 0}
@bp.get("/hypermedia/examples/api/poll") @bp.get("/geography/hypermedia/examples/api/poll")
async def api_poll(): async def api_poll():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -66,7 +66,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@csrf_exempt @csrf_exempt
@bp.delete("/hypermedia/examples/api/delete/<item_id>") @bp.delete("/geography/hypermedia/examples/api/delete/<item_id>")
async def api_delete(item_id: str): async def api_delete(item_id: str):
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -78,7 +78,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob_comp = _oob_code("delete-comp", comp_text) oob_comp = _oob_code("delete-comp", comp_text)
return sx_response(f'(<> {oob_wire} {oob_comp})') return sx_response(f'(<> {oob_wire} {oob_comp})')
@bp.get("/hypermedia/examples/api/edit") @bp.get("/geography/hypermedia/examples/api/edit")
async def api_edit_form(): async def api_edit_form():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -92,7 +92,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/edit") @bp.post("/geography/hypermedia/examples/api/edit")
async def api_edit_save(): async def api_edit_save():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -106,7 +106,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob_comp = _oob_code("edit-comp", comp_text) oob_comp = _oob_code("edit-comp", comp_text)
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@bp.get("/hypermedia/examples/api/edit/cancel") @bp.get("/geography/hypermedia/examples/api/edit/cancel")
async def api_edit_cancel(): async def api_edit_cancel():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -119,7 +119,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob_comp = _oob_code("edit-comp", comp_text) oob_comp = _oob_code("edit-comp", comp_text)
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@bp.get("/hypermedia/examples/api/oob") @bp.get("/geography/hypermedia/examples/api/oob")
async def api_oob(): async def api_oob():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _full_wire_text from sxc.pages.renders import _oob_code, _full_wire_text
@@ -138,7 +138,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Lazy Loading --- # --- Lazy Loading ---
@bp.get("/hypermedia/examples/api/lazy") @bp.get("/geography/hypermedia/examples/api/lazy")
async def api_lazy(): async def api_lazy():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -152,7 +152,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Infinite Scroll --- # --- Infinite Scroll ---
@bp.get("/hypermedia/examples/api/scroll") @bp.get("/geography/hypermedia/examples/api/scroll")
async def api_scroll(): async def api_scroll():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _full_wire_text from sxc.pages.renders import _oob_code, _full_wire_text
@@ -166,7 +166,7 @@ def register(url_prefix: str = "/") -> Blueprint:
if next_page <= 6: if next_page <= 6:
sentinel = ( sentinel = (
f'(div :id "scroll-sentinel"' f'(div :id "scroll-sentinel"'
f' :sx-get "/hypermedia/examples/api/scroll?page={next_page}"' f' :sx-get "/geography/hypermedia/examples/api/scroll?page={next_page}"'
f' :sx-trigger "intersect once"' f' :sx-trigger "intersect once"'
f' :sx-target "#scroll-items"' f' :sx-target "#scroll-items"'
f' :sx-swap "beforeend"' f' :sx-swap "beforeend"'
@@ -188,7 +188,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_jobs: dict[str, int] = {} _jobs: dict[str, int] = {}
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/progress/start") @bp.post("/geography/hypermedia/examples/api/progress/start")
async def api_progress_start(): async def api_progress_start():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -201,7 +201,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob_comp = _oob_code("progress-comp", comp_text) oob_comp = _oob_code("progress-comp", comp_text)
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@bp.get("/hypermedia/examples/api/progress/status") @bp.get("/geography/hypermedia/examples/api/progress/status")
async def api_progress_status(): async def api_progress_status():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -218,7 +218,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Active Search --- # --- Active Search ---
@bp.get("/hypermedia/examples/api/search") @bp.get("/geography/hypermedia/examples/api/search")
async def api_search(): async def api_search():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -241,7 +241,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_TAKEN_EMAILS = {"admin@example.com", "test@example.com", "user@example.com"} _TAKEN_EMAILS = {"admin@example.com", "test@example.com", "user@example.com"}
@bp.get("/hypermedia/examples/api/validate") @bp.get("/geography/hypermedia/examples/api/validate")
async def api_validate(): async def api_validate():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -267,7 +267,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/validate/submit") @bp.post("/geography/hypermedia/examples/api/validate/submit")
async def api_validate_submit(): async def api_validate_submit():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
form = await request.form form = await request.form
@@ -279,7 +279,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Value Select --- # --- Value Select ---
@bp.get("/hypermedia/examples/api/values") @bp.get("/geography/hypermedia/examples/api/values")
async def api_values(): async def api_values():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _full_wire_text from sxc.pages.renders import _oob_code, _full_wire_text
@@ -297,7 +297,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Reset on Submit --- # --- Reset on Submit ---
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/reset-submit") @bp.post("/geography/hypermedia/examples/api/reset-submit")
async def api_reset_submit(): async def api_reset_submit():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -323,7 +323,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_edit_rows[r["id"]] = dict(r) _edit_rows[r["id"]] = dict(r)
return _edit_rows return _edit_rows
@bp.get("/hypermedia/examples/api/editrow/<row_id>") @bp.get("/geography/hypermedia/examples/api/editrow/<row_id>")
async def api_editrow_form(row_id: str): async def api_editrow_form(row_id: str):
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -338,7 +338,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/editrow/<row_id>") @bp.post("/geography/hypermedia/examples/api/editrow/<row_id>")
async def api_editrow_save(row_id: str): async def api_editrow_save(row_id: str):
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -359,7 +359,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob_comp = _oob_code("editrow-comp", comp_text) oob_comp = _oob_code("editrow-comp", comp_text)
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@bp.get("/hypermedia/examples/api/editrow/<row_id>/cancel") @bp.get("/geography/hypermedia/examples/api/editrow/<row_id>/cancel")
async def api_editrow_cancel(row_id: str): async def api_editrow_cancel(row_id: str):
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -385,7 +385,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return _bulk_users return _bulk_users
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/bulk") @bp.post("/geography/hypermedia/examples/api/bulk")
async def api_bulk(): async def api_bulk():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -415,7 +415,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_swap_count = {"n": 0} _swap_count = {"n": 0}
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/swap-log") @bp.post("/geography/hypermedia/examples/api/swap-log")
async def api_swap_log(): async def api_swap_log():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _full_wire_text from sxc.pages.renders import _oob_code, _full_wire_text
@@ -435,7 +435,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Select Filter (dashboard) --- # --- Select Filter (dashboard) ---
@bp.get("/hypermedia/examples/api/dashboard") @bp.get("/geography/hypermedia/examples/api/dashboard")
async def api_dashboard(): async def api_dashboard():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _full_wire_text from sxc.pages.renders import _oob_code, _full_wire_text
@@ -480,7 +480,7 @@ def register(url_prefix: str = "/") -> Blueprint:
' (li "Wire format v2")))'), ' (li "Wire format v2")))'),
} }
@bp.get("/hypermedia/examples/api/tabs/<tab>") @bp.get("/geography/hypermedia/examples/api/tabs/<tab>")
async def api_tabs(tab: str): async def api_tabs(tab: str):
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _full_wire_text from sxc.pages.renders import _oob_code, _full_wire_text
@@ -500,7 +500,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Animations --- # --- Animations ---
@bp.get("/hypermedia/examples/api/animate") @bp.get("/geography/hypermedia/examples/api/animate")
async def api_animate(): async def api_animate():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -516,7 +516,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Dialogs --- # --- Dialogs ---
@bp.get("/hypermedia/examples/api/dialog") @bp.get("/geography/hypermedia/examples/api/dialog")
async def api_dialog(): async def api_dialog():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -527,7 +527,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob_comp = _oob_code("dialog-comp", comp_text) oob_comp = _oob_code("dialog-comp", comp_text)
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@bp.get("/hypermedia/examples/api/dialog/close") @bp.get("/geography/hypermedia/examples/api/dialog/close")
async def api_dialog_close(): async def api_dialog_close():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _full_wire_text from sxc.pages.renders import _oob_code, _full_wire_text
@@ -543,7 +543,7 @@ def register(url_prefix: str = "/") -> Blueprint:
"h": "Help panel opened", "h": "Help panel opened",
} }
@bp.get("/hypermedia/examples/api/keyboard") @bp.get("/geography/hypermedia/examples/api/keyboard")
async def api_keyboard(): async def api_keyboard():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -568,7 +568,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_profile.update(PROFILE_DEFAULT) _profile.update(PROFILE_DEFAULT)
return _profile return _profile
@bp.get("/hypermedia/examples/api/putpatch/edit-all") @bp.get("/geography/hypermedia/examples/api/putpatch/edit-all")
async def api_pp_edit_all(): async def api_pp_edit_all():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -581,7 +581,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@csrf_exempt @csrf_exempt
@bp.put("/hypermedia/examples/api/putpatch") @bp.put("/geography/hypermedia/examples/api/putpatch")
async def api_pp_put(): async def api_pp_put():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -597,7 +597,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob_comp = _oob_code("pp-comp", comp_text) oob_comp = _oob_code("pp-comp", comp_text)
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@bp.get("/hypermedia/examples/api/putpatch/cancel") @bp.get("/geography/hypermedia/examples/api/putpatch/cancel")
async def api_pp_cancel(): async def api_pp_cancel():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -612,7 +612,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- JSON Encoding --- # --- JSON Encoding ---
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/examples/api/json-echo") @bp.post("/geography/hypermedia/examples/api/json-echo")
async def api_json_echo(): async def api_json_echo():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -630,7 +630,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Vals & Headers --- # --- Vals & Headers ---
@bp.get("/hypermedia/examples/api/echo-vals") @bp.get("/geography/hypermedia/examples/api/echo-vals")
async def api_echo_vals(): async def api_echo_vals():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -644,7 +644,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob_comp = _oob_code("vals-comp", comp_text) oob_comp = _oob_code("vals-comp", comp_text)
return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})') return sx_response(f'(<> {sx_src} {oob_wire} {oob_comp})')
@bp.get("/hypermedia/examples/api/echo-headers") @bp.get("/geography/hypermedia/examples/api/echo-headers")
async def api_echo_headers(): async def api_echo_headers():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -659,7 +659,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Loading States --- # --- Loading States ---
@bp.get("/hypermedia/examples/api/slow") @bp.get("/geography/hypermedia/examples/api/slow")
async def api_slow(): async def api_slow():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -674,7 +674,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Request Abort (sync replace) --- # --- Request Abort (sync replace) ---
@bp.get("/hypermedia/examples/api/slow-search") @bp.get("/geography/hypermedia/examples/api/slow-search")
async def api_slow_search(): async def api_slow_search():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -694,7 +694,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_flaky = {"n": 0} _flaky = {"n": 0}
@bp.get("/hypermedia/examples/api/flaky") @bp.get("/geography/hypermedia/examples/api/flaky")
async def api_flaky(): async def api_flaky():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
@@ -718,7 +718,7 @@ def register(url_prefix: str = "/") -> Blueprint:
from sxc.pages.renders import _oob_code from sxc.pages.renders import _oob_code
return _oob_code(f"ref-wire-{wire_id}", sx_src) return _oob_code(f"ref-wire-{wire_id}", sx_src)
@bp.get("/hypermedia/reference/api/time") @bp.get("/geography/hypermedia/reference/api/time")
async def ref_time(): async def ref_time():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
now = datetime.now().strftime("%H:%M:%S") now = datetime.now().strftime("%H:%M:%S")
@@ -727,7 +727,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/reference/api/greet") @bp.post("/geography/hypermedia/reference/api/greet")
async def ref_greet(): async def ref_greet():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
form = await request.form form = await request.form
@@ -737,7 +737,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@csrf_exempt @csrf_exempt
@bp.put("/hypermedia/reference/api/status") @bp.put("/geography/hypermedia/reference/api/status")
async def ref_status(): async def ref_status():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
form = await request.form form = await request.form
@@ -747,7 +747,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@csrf_exempt @csrf_exempt
@bp.patch("/hypermedia/reference/api/theme") @bp.patch("/geography/hypermedia/reference/api/theme")
async def ref_theme(): async def ref_theme():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
form = await request.form form = await request.form
@@ -757,13 +757,13 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@csrf_exempt @csrf_exempt
@bp.delete("/hypermedia/reference/api/item/<item_id>") @bp.delete("/geography/hypermedia/reference/api/item/<item_id>")
async def ref_delete(item_id: str): async def ref_delete(item_id: str):
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
oob = _ref_wire("sx-delete", '""') oob = _ref_wire("sx-delete", '""')
return sx_response(f'(<> {oob})') return sx_response(f'(<> {oob})')
@bp.get("/hypermedia/reference/api/trigger-search") @bp.get("/geography/hypermedia/reference/api/trigger-search")
async def ref_trigger_search(): async def ref_trigger_search():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
q = request.args.get("q", "") q = request.args.get("q", "")
@@ -774,7 +774,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob = _ref_wire("sx-trigger", sx_src) oob = _ref_wire("sx-trigger", sx_src)
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@bp.get("/hypermedia/reference/api/swap-item") @bp.get("/geography/hypermedia/reference/api/swap-item")
async def ref_swap_item(): async def ref_swap_item():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
now = datetime.now().strftime("%H:%M:%S") now = datetime.now().strftime("%H:%M:%S")
@@ -782,7 +782,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob = _ref_wire("sx-swap", sx_src) oob = _ref_wire("sx-swap", sx_src)
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@bp.get("/hypermedia/reference/api/oob") @bp.get("/geography/hypermedia/reference/api/oob")
async def ref_oob(): async def ref_oob():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
now = datetime.now().strftime("%H:%M:%S") now = datetime.now().strftime("%H:%M:%S")
@@ -794,7 +794,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob = _ref_wire("sx-swap-oob", sx_src) oob = _ref_wire("sx-swap-oob", sx_src)
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@bp.get("/hypermedia/reference/api/select-page") @bp.get("/geography/hypermedia/reference/api/select-page")
async def ref_select_page(): async def ref_select_page():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
now = datetime.now().strftime("%H:%M:%S") now = datetime.now().strftime("%H:%M:%S")
@@ -808,7 +808,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob = _ref_wire("sx-select", sx_src) oob = _ref_wire("sx-select", sx_src)
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@bp.get("/hypermedia/reference/api/slow-echo") @bp.get("/geography/hypermedia/reference/api/slow-echo")
async def ref_slow_echo(): async def ref_slow_echo():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
await asyncio.sleep(0.8) await asyncio.sleep(0.8)
@@ -818,7 +818,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/reference/api/upload-name") @bp.post("/geography/hypermedia/reference/api/upload-name")
async def ref_upload_name(): async def ref_upload_name():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
files = await request.files files = await request.files
@@ -828,7 +828,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob = _ref_wire("sx-encoding", sx_src) oob = _ref_wire("sx-encoding", sx_src)
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@bp.get("/hypermedia/reference/api/echo-headers") @bp.get("/geography/hypermedia/reference/api/echo-headers")
async def ref_echo_headers(): async def ref_echo_headers():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
custom = [(k, v) for k, v in request.headers if k.lower().startswith("x-")] custom = [(k, v) for k, v in request.headers if k.lower().startswith("x-")]
@@ -841,7 +841,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob = _ref_wire("sx-headers", sx_src) oob = _ref_wire("sx-headers", sx_src)
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@bp.get("/hypermedia/reference/api/echo-vals") @bp.get("/geography/hypermedia/reference/api/echo-vals")
async def ref_echo_vals_get(): async def ref_echo_vals_get():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
vals = list(request.args.items()) vals = list(request.args.items())
@@ -855,7 +855,7 @@ def register(url_prefix: str = "/") -> Blueprint:
return sx_response(f'(<> {sx_src} {oob_include})') return sx_response(f'(<> {sx_src} {oob_include})')
@csrf_exempt @csrf_exempt
@bp.post("/hypermedia/reference/api/echo-vals") @bp.post("/geography/hypermedia/reference/api/echo-vals")
async def ref_echo_vals_post(): async def ref_echo_vals_post():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
form = await request.form form = await request.form
@@ -871,7 +871,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_ref_flaky = {"n": 0} _ref_flaky = {"n": 0}
@bp.get("/hypermedia/reference/api/flaky") @bp.get("/geography/hypermedia/reference/api/flaky")
async def ref_flaky(): async def ref_flaky():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
_ref_flaky["n"] += 1 _ref_flaky["n"] += 1
@@ -882,7 +882,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob = _ref_wire("sx-retry", sx_src) oob = _ref_wire("sx-retry", sx_src)
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@bp.get("/hypermedia/reference/api/prompt-echo") @bp.get("/geography/hypermedia/reference/api/prompt-echo")
async def ref_prompt_echo(): async def ref_prompt_echo():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
name = request.headers.get("SX-Prompt", "anonymous") name = request.headers.get("SX-Prompt", "anonymous")
@@ -890,7 +890,7 @@ def register(url_prefix: str = "/") -> Blueprint:
oob = _ref_wire("sx-prompt", sx_src) oob = _ref_wire("sx-prompt", sx_src)
return sx_response(f'(<> {sx_src} {oob})') return sx_response(f'(<> {sx_src} {oob})')
@bp.get("/hypermedia/reference/api/sse-time") @bp.get("/geography/hypermedia/reference/api/sse-time")
async def ref_sse_time(): async def ref_sse_time():
async def generate(): async def generate():
for _ in range(30): # stream for 60 seconds max for _ in range(30): # stream for 60 seconds max
@@ -905,7 +905,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_marsh_sale_idx = {"n": 0} _marsh_sale_idx = {"n": 0}
@bp.get("/reactive/api/flash-sale") @bp.get("/geography/reactive/api/flash-sale")
async def api_marsh_flash_sale(): async def api_marsh_flash_sale():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
prices = [14.99, 9.99, 24.99, 12.49, 7.99, 29.99, 4.99, 16.50] prices = [14.99, 9.99, 24.99, 12.49, 7.99, 29.99, 4.99, 16.50]
@@ -927,7 +927,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_settle_counter = {"n": 0} _settle_counter = {"n": 0}
@bp.get("/reactive/api/settle-data") @bp.get("/geography/reactive/api/settle-data")
async def api_settle_data(): async def api_settle_data():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
_settle_counter["n"] += 1 _settle_counter["n"] += 1
@@ -943,7 +943,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Demo 4: signal-bound URL endpoints --- # --- Demo 4: signal-bound URL endpoints ---
@bp.get("/reactive/api/search/products") @bp.get("/geography/reactive/api/search/products")
async def api_search_products(): async def api_search_products():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
q = request.args.get("q", "") q = request.args.get("q", "")
@@ -962,7 +962,7 @@ def register(url_prefix: str = "/") -> Blueprint:
) )
return sx_response(sx_src) return sx_response(sx_src)
@bp.get("/reactive/api/search/events") @bp.get("/geography/reactive/api/search/events")
async def api_search_events(): async def api_search_events():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
q = request.args.get("q", "") q = request.args.get("q", "")
@@ -981,7 +981,7 @@ def register(url_prefix: str = "/") -> Blueprint:
) )
return sx_response(sx_src) return sx_response(sx_src)
@bp.get("/reactive/api/search/posts") @bp.get("/geography/reactive/api/search/posts")
async def api_search_posts(): async def api_search_posts():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
q = request.args.get("q", "") q = request.args.get("q", "")
@@ -1002,7 +1002,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Demo 5: marsh transform endpoint --- # --- Demo 5: marsh transform endpoint ---
@bp.get("/reactive/api/catalog") @bp.get("/geography/reactive/api/catalog")
async def api_catalog(): async def api_catalog():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
items = [ items = [
@@ -1031,7 +1031,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Header demos --- # --- Header demos ---
@bp.get("/hypermedia/reference/api/trigger-event") @bp.get("/geography/hypermedia/reference/api/trigger-event")
async def ref_trigger_event(): async def ref_trigger_event():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
now = datetime.now().strftime("%H:%M:%S") now = datetime.now().strftime("%H:%M:%S")
@@ -1040,7 +1040,7 @@ def register(url_prefix: str = "/") -> Blueprint:
resp.headers["SX-Trigger"] = "showNotice" resp.headers["SX-Trigger"] = "showNotice"
return resp return resp
@bp.get("/hypermedia/reference/api/retarget") @bp.get("/geography/hypermedia/reference/api/retarget")
async def ref_retarget(): async def ref_retarget():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
now = datetime.now().strftime("%H:%M:%S") now = datetime.now().strftime("%H:%M:%S")
@@ -1051,7 +1051,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Event demos --- # --- Event demos ---
@bp.get("/hypermedia/reference/api/error-500") @bp.get("/geography/hypermedia/reference/api/error-500")
async def ref_error_500(): async def ref_error_500():
return Response("Server error", status=500, content_type="text/plain") return Response("Server error", status=500, content_type="text/plain")

View File

@@ -10,79 +10,79 @@ from __future__ import annotations
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
DOCS_NAV = [ DOCS_NAV = [
("Introduction", "/docs/introduction"), ("Introduction", "/language/docs/introduction"),
("Getting Started", "/docs/getting-started"), ("Getting Started", "/language/docs/getting-started"),
("Components", "/docs/components"), ("Components", "/language/docs/components"),
("Evaluator", "/docs/evaluator"), ("Evaluator", "/language/docs/evaluator"),
("Primitives", "/docs/primitives"), ("Primitives", "/language/docs/primitives"),
("CSS", "/docs/css"), ("CSS", "/language/docs/css"),
("Server Rendering", "/docs/server-rendering"), ("Server Rendering", "/language/docs/server-rendering"),
] ]
REFERENCE_NAV = [ REFERENCE_NAV = [
("Attributes", "/hypermedia/reference/attributes"), ("Attributes", "/geography/hypermedia/reference/attributes"),
("Headers", "/hypermedia/reference/headers"), ("Headers", "/geography/hypermedia/reference/headers"),
("Events", "/hypermedia/reference/events"), ("Events", "/geography/hypermedia/reference/events"),
("JS API", "/hypermedia/reference/js-api"), ("JS API", "/geography/hypermedia/reference/js-api"),
] ]
PROTOCOLS_NAV = [ PROTOCOLS_NAV = [
("Wire Format", "/protocols/wire-format"), ("Wire Format", "/applications/protocols/wire-format"),
("Fragments", "/protocols/fragments"), ("Fragments", "/applications/protocols/fragments"),
("Resolver I/O", "/protocols/resolver-io"), ("Resolver I/O", "/applications/protocols/resolver-io"),
("Internal Services", "/protocols/internal-services"), ("Internal Services", "/applications/protocols/internal-services"),
("ActivityPub", "/protocols/activitypub"), ("ActivityPub", "/applications/protocols/activitypub"),
("Future", "/protocols/future"), ("Future", "/applications/protocols/future"),
] ]
EXAMPLES_NAV = [ EXAMPLES_NAV = [
("Click to Load", "/hypermedia/examples/click-to-load"), ("Click to Load", "/geography/hypermedia/examples/click-to-load"),
("Form Submission", "/hypermedia/examples/form-submission"), ("Form Submission", "/geography/hypermedia/examples/form-submission"),
("Polling", "/hypermedia/examples/polling"), ("Polling", "/geography/hypermedia/examples/polling"),
("Delete Row", "/hypermedia/examples/delete-row"), ("Delete Row", "/geography/hypermedia/examples/delete-row"),
("Inline Edit", "/hypermedia/examples/inline-edit"), ("Inline Edit", "/geography/hypermedia/examples/inline-edit"),
("OOB Swaps", "/hypermedia/examples/oob-swaps"), ("OOB Swaps", "/geography/hypermedia/examples/oob-swaps"),
("Lazy Loading", "/hypermedia/examples/lazy-loading"), ("Lazy Loading", "/geography/hypermedia/examples/lazy-loading"),
("Infinite Scroll", "/hypermedia/examples/infinite-scroll"), ("Infinite Scroll", "/geography/hypermedia/examples/infinite-scroll"),
("Progress Bar", "/hypermedia/examples/progress-bar"), ("Progress Bar", "/geography/hypermedia/examples/progress-bar"),
("Active Search", "/hypermedia/examples/active-search"), ("Active Search", "/geography/hypermedia/examples/active-search"),
("Inline Validation", "/hypermedia/examples/inline-validation"), ("Inline Validation", "/geography/hypermedia/examples/inline-validation"),
("Value Select", "/hypermedia/examples/value-select"), ("Value Select", "/geography/hypermedia/examples/value-select"),
("Reset on Submit", "/hypermedia/examples/reset-on-submit"), ("Reset on Submit", "/geography/hypermedia/examples/reset-on-submit"),
("Edit Row", "/hypermedia/examples/edit-row"), ("Edit Row", "/geography/hypermedia/examples/edit-row"),
("Bulk Update", "/hypermedia/examples/bulk-update"), ("Bulk Update", "/geography/hypermedia/examples/bulk-update"),
("Swap Positions", "/hypermedia/examples/swap-positions"), ("Swap Positions", "/geography/hypermedia/examples/swap-positions"),
("Select Filter", "/hypermedia/examples/select-filter"), ("Select Filter", "/geography/hypermedia/examples/select-filter"),
("Tabs", "/hypermedia/examples/tabs"), ("Tabs", "/geography/hypermedia/examples/tabs"),
("Animations", "/hypermedia/examples/animations"), ("Animations", "/geography/hypermedia/examples/animations"),
("Dialogs", "/hypermedia/examples/dialogs"), ("Dialogs", "/geography/hypermedia/examples/dialogs"),
("Keyboard Shortcuts", "/hypermedia/examples/keyboard-shortcuts"), ("Keyboard Shortcuts", "/geography/hypermedia/examples/keyboard-shortcuts"),
("PUT / PATCH", "/hypermedia/examples/put-patch"), ("PUT / PATCH", "/geography/hypermedia/examples/put-patch"),
("JSON Encoding", "/hypermedia/examples/json-encoding"), ("JSON Encoding", "/geography/hypermedia/examples/json-encoding"),
("Vals & Headers", "/hypermedia/examples/vals-and-headers"), ("Vals & Headers", "/geography/hypermedia/examples/vals-and-headers"),
("Loading States", "/hypermedia/examples/loading-states"), ("Loading States", "/geography/hypermedia/examples/loading-states"),
("Request Abort", "/hypermedia/examples/sync-replace"), ("Request Abort", "/geography/hypermedia/examples/sync-replace"),
("Retry", "/hypermedia/examples/retry"), ("Retry", "/geography/hypermedia/examples/retry"),
] ]
ESSAYS_NAV = [ ESSAYS_NAV = [
("sx sucks", "/essays/sx-sucks"), ("sx sucks", "/etc/essays/sx-sucks"),
("Why S-Expressions", "/essays/why-sexps"), ("Why S-Expressions", "/etc/essays/why-sexps"),
("The htmx/React Hybrid", "/essays/htmx-react-hybrid"), ("The htmx/React Hybrid", "/etc/essays/htmx-react-hybrid"),
("On-Demand CSS", "/essays/on-demand-css"), ("On-Demand CSS", "/etc/essays/on-demand-css"),
("Client Reactivity", "/essays/client-reactivity"), ("Client Reactivity", "/etc/essays/client-reactivity"),
("SX Native", "/essays/sx-native"), ("SX Native", "/etc/essays/sx-native"),
("The SX Manifesto", "/philosophy/sx-manifesto"), ("The SX Manifesto", "/etc/philosophy/sx-manifesto"),
("Tail-Call Optimization", "/essays/tail-call-optimization"), ("Tail-Call Optimization", "/etc/essays/tail-call-optimization"),
("Continuations", "/essays/continuations"), ("Continuations", "/etc/essays/continuations"),
] ]
MAIN_NAV = [ MAIN_NAV = [
("Docs", "/docs/introduction"), ("Docs", "/language/docs/introduction"),
("Reference", "/hypermedia/reference/"), ("Reference", "/geography/hypermedia/reference/"),
("Protocols", "/protocols/wire-format"), ("Protocols", "/applications/protocols/wire-format"),
("Examples", "/hypermedia/examples/click-to-load"), ("Examples", "/geography/hypermedia/examples/click-to-load"),
("Essays", "/essays/sx-sucks"), ("Essays", "/etc/essays/sx-sucks"),
] ]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -539,7 +539,7 @@ HEADER_DETAILS: dict[str, dict] = {
';; SX-Location: /docs/introduction\n' ';; SX-Location: /docs/introduction\n'
';;\n' ';;\n'
';; With options:\n' ';; With options:\n'
';; SX-Location: {"path": "/docs/intro", "target": "#sidebar", "swap": "innerHTML"}' ';; SX-Location: {"path": "/language/docs/intro", "target": "#sidebar", "swap": "innerHTML"}'
), ),
}, },
"SX-Replace-Url": { "SX-Replace-Url": {
@@ -676,8 +676,8 @@ EVENT_DETAILS: dict[str, dict] = {
';; sx-boost containers try client routing first.\n' ';; sx-boost containers try client routing first.\n'
';; On success, sx:clientRoute fires on the swap target.\n' ';; On success, sx:clientRoute fires on the swap target.\n'
'(nav :sx-boost "#main-panel"\n' '(nav :sx-boost "#main-panel"\n'
' (a :href "/essays/" "Essays")\n' ' (a :href "/etc/essays/" "Essays")\n'
' (a :href "/plans/" "Plans"))\n' ' (a :href "/etc/plans/" "Plans"))\n'
'\n' '\n'
';; Listen in body.js:\n' ';; Listen in body.js:\n'
';; document.body.addEventListener("sx:clientRoute",\n' ';; document.body.addEventListener("sx:clientRoute",\n'
@@ -744,7 +744,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-get-demo", "demo": "ref-get-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/time"\n' '(button :sx-get "/geography/hypermedia/reference/api/time"\n'
' :sx-target "#ref-get-result"\n' ' :sx-target "#ref-get-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Load server time")' ' "Load server time")'
@@ -764,7 +764,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-post-demo", "demo": "ref-post-demo",
"example": ( "example": (
'(form :sx-post "/hypermedia/reference/api/greet"\n' '(form :sx-post "/geography/hypermedia/reference/api/greet"\n'
' :sx-target "#ref-post-result"\n' ' :sx-target "#ref-post-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' (input :type "text" :name "name"\n' ' (input :type "text" :name "name"\n'
@@ -786,7 +786,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-put-demo", "demo": "ref-put-demo",
"example": ( "example": (
'(button :sx-put "/hypermedia/reference/api/status"\n' '(button :sx-put "/geography/hypermedia/reference/api/status"\n'
' :sx-target "#ref-put-view"\n' ' :sx-target "#ref-put-view"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-vals "{\\"status\\": \\"published\\"}"\n' ' :sx-vals "{\\"status\\": \\"published\\"}"\n'
@@ -807,7 +807,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-delete-demo", "demo": "ref-delete-demo",
"example": ( "example": (
'(button :sx-delete "/hypermedia/reference/api/item/1"\n' '(button :sx-delete "/geography/hypermedia/reference/api/item/1"\n'
' :sx-target "#ref-del-1"\n' ' :sx-target "#ref-del-1"\n'
' :sx-swap "delete"\n' ' :sx-swap "delete"\n'
' "Remove")' ' "Remove")'
@@ -826,7 +826,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-patch-demo", "demo": "ref-patch-demo",
"example": ( "example": (
'(button :sx-patch "/hypermedia/reference/api/theme"\n' '(button :sx-patch "/geography/hypermedia/reference/api/theme"\n'
' :sx-vals "{\\"theme\\": \\"dark\\"}"\n' ' :sx-vals "{\\"theme\\": \\"dark\\"}"\n'
' :sx-target "#ref-patch-val"\n' ' :sx-target "#ref-patch-val"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -852,7 +852,7 @@ ATTR_DETAILS: dict[str, dict] = {
"example": ( "example": (
'(input :type "text" :name "q"\n' '(input :type "text" :name "q"\n'
' :placeholder "Type to search..."\n' ' :placeholder "Type to search..."\n'
' :sx-get "/hypermedia/reference/api/trigger-search"\n' ' :sx-get "/geography/hypermedia/reference/api/trigger-search"\n'
' :sx-trigger "input changed delay:300ms"\n' ' :sx-trigger "input changed delay:300ms"\n'
' :sx-target "#ref-trigger-result"\n' ' :sx-target "#ref-trigger-result"\n'
' :sx-swap "innerHTML")' ' :sx-swap "innerHTML")'
@@ -874,12 +874,12 @@ ATTR_DETAILS: dict[str, dict] = {
"demo": "ref-target-demo", "demo": "ref-target-demo",
"example": ( "example": (
';; Two buttons targeting different elements\n' ';; Two buttons targeting different elements\n'
'(button :sx-get "/hypermedia/reference/api/time"\n' '(button :sx-get "/geography/hypermedia/reference/api/time"\n'
' :sx-target "#ref-target-a"\n' ' :sx-target "#ref-target-a"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Update Box A")\n' ' "Update Box A")\n'
'\n' '\n'
'(button :sx-get "/hypermedia/reference/api/time"\n' '(button :sx-get "/geography/hypermedia/reference/api/time"\n'
' :sx-target "#ref-target-b"\n' ' :sx-target "#ref-target-b"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Update Box B")' ' "Update Box B")'
@@ -894,13 +894,13 @@ ATTR_DETAILS: dict[str, dict] = {
"demo": "ref-swap-demo", "demo": "ref-swap-demo",
"example": ( "example": (
';; Append to the end of a list\n' ';; Append to the end of a list\n'
'(button :sx-get "/hypermedia/reference/api/swap-item"\n' '(button :sx-get "/geography/hypermedia/reference/api/swap-item"\n'
' :sx-target "#ref-swap-list"\n' ' :sx-target "#ref-swap-list"\n'
' :sx-swap "beforeend"\n' ' :sx-swap "beforeend"\n'
' "beforeend")\n' ' "beforeend")\n'
'\n' '\n'
';; Prepend to the start\n' ';; Prepend to the start\n'
'(button :sx-get "/hypermedia/reference/api/swap-item"\n' '(button :sx-get "/geography/hypermedia/reference/api/swap-item"\n'
' :sx-target "#ref-swap-list"\n' ' :sx-target "#ref-swap-list"\n'
' :sx-swap "afterbegin"\n' ' :sx-swap "afterbegin"\n'
' "afterbegin")' ' "afterbegin")'
@@ -921,7 +921,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-oob-demo", "demo": "ref-oob-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/oob"\n' '(button :sx-get "/geography/hypermedia/reference/api/oob"\n'
' :sx-target "#ref-oob-main"\n' ' :sx-target "#ref-oob-main"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Update both boxes")' ' "Update both boxes")'
@@ -944,7 +944,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-select-demo", "demo": "ref-select-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/select-page"\n' '(button :sx-get "/geography/hypermedia/reference/api/select-page"\n'
' :sx-target "#ref-select-result"\n' ' :sx-target "#ref-select-result"\n'
' :sx-select "#the-content"\n' ' :sx-select "#the-content"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -968,7 +968,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-confirm-demo", "demo": "ref-confirm-demo",
"example": ( "example": (
'(button :sx-delete "/hypermedia/reference/api/item/confirm"\n' '(button :sx-delete "/geography/hypermedia/reference/api/item/confirm"\n'
' :sx-target "#ref-confirm-item"\n' ' :sx-target "#ref-confirm-item"\n'
' :sx-swap "delete"\n' ' :sx-swap "delete"\n'
' :sx-confirm "Are you sure you want to delete this file?"\n' ' :sx-confirm "Are you sure you want to delete this file?"\n'
@@ -983,8 +983,8 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-pushurl-demo", "demo": "ref-pushurl-demo",
"example": ( "example": (
'(a :href "/hypermedia/reference/attributes/sx-get"\n' '(a :href "/geography/hypermedia/reference/attributes/sx-get"\n'
' :sx-get "/hypermedia/reference/attributes/sx-get"\n' ' :sx-get "/geography/hypermedia/reference/attributes/sx-get"\n'
' :sx-target "#main-panel"\n' ' :sx-target "#main-panel"\n'
' :sx-select "#main-panel"\n' ' :sx-select "#main-panel"\n'
' :sx-swap "outerHTML"\n' ' :sx-swap "outerHTML"\n'
@@ -1003,7 +1003,7 @@ ATTR_DETAILS: dict[str, dict] = {
"example": ( "example": (
'(input :type "text" :name "q"\n' '(input :type "text" :name "q"\n'
' :placeholder "Type quickly..."\n' ' :placeholder "Type quickly..."\n'
' :sx-get "/hypermedia/reference/api/slow-echo"\n' ' :sx-get "/geography/hypermedia/reference/api/slow-echo"\n'
' :sx-trigger "input changed delay:100ms"\n' ' :sx-trigger "input changed delay:100ms"\n'
' :sx-sync "replace"\n' ' :sx-sync "replace"\n'
' :sx-target "#ref-sync-result"\n' ' :sx-target "#ref-sync-result"\n'
@@ -1024,7 +1024,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-encoding-demo", "demo": "ref-encoding-demo",
"example": ( "example": (
'(form :sx-post "/hypermedia/reference/api/upload-name"\n' '(form :sx-post "/geography/hypermedia/reference/api/upload-name"\n'
' :sx-encoding "multipart/form-data"\n' ' :sx-encoding "multipart/form-data"\n'
' :sx-target "#ref-encoding-result"\n' ' :sx-target "#ref-encoding-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -1044,7 +1044,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-headers-demo", "demo": "ref-headers-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/echo-headers"\n' '(button :sx-get "/geography/hypermedia/reference/api/echo-headers"\n'
' :sx-headers \'{"X-Custom-Token": "abc123", "X-Request-Source": "demo"}\'\n' ' :sx-headers \'{"X-Custom-Token": "abc123", "X-Request-Source": "demo"}\'\n'
' :sx-target "#ref-headers-result"\n' ' :sx-target "#ref-headers-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -1073,7 +1073,7 @@ ATTR_DETAILS: dict[str, dict] = {
' (option :value "books" "Books")\n' ' (option :value "books" "Books")\n'
' (option :value "tools" "Tools"))\n' ' (option :value "tools" "Tools"))\n'
'\n' '\n'
'(button :sx-get "/hypermedia/reference/api/echo-vals"\n' '(button :sx-get "/geography/hypermedia/reference/api/echo-vals"\n'
' :sx-include "#ref-inc-cat"\n' ' :sx-include "#ref-inc-cat"\n'
' :sx-target "#ref-include-result"\n' ' :sx-target "#ref-include-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -1097,7 +1097,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-vals-demo", "demo": "ref-vals-demo",
"example": ( "example": (
'(button :sx-post "/hypermedia/reference/api/echo-vals"\n' '(button :sx-post "/geography/hypermedia/reference/api/echo-vals"\n'
' :sx-vals \'{"source": "demo", "page": "3"}\'\n' ' :sx-vals \'{"source": "demo", "page": "3"}\'\n'
' :sx-target "#ref-vals-result"\n' ' :sx-target "#ref-vals-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -1112,8 +1112,8 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-media-demo", "demo": "ref-media-demo",
"example": ( "example": (
'(a :href "/hypermedia/reference/attributes/sx-get"\n' '(a :href "/geography/hypermedia/reference/attributes/sx-get"\n'
' :sx-get "/hypermedia/reference/attributes/sx-get"\n' ' :sx-get "/geography/hypermedia/reference/attributes/sx-get"\n'
' :sx-target "#main-panel"\n' ' :sx-target "#main-panel"\n'
' :sx-select "#main-panel"\n' ' :sx-select "#main-panel"\n'
' :sx-swap "outerHTML"\n' ' :sx-swap "outerHTML"\n'
@@ -1133,7 +1133,7 @@ ATTR_DETAILS: dict[str, dict] = {
';; Left box: sx works normally\n' ';; Left box: sx works normally\n'
';; Right box: sx-disable prevents any sx behavior\n' ';; Right box: sx-disable prevents any sx behavior\n'
'(div :sx-disable "true"\n' '(div :sx-disable "true"\n'
' (button :sx-get "/hypermedia/reference/api/time"\n' ' (button :sx-get "/geography/hypermedia/reference/api/time"\n'
' :sx-target "#ref-dis-b"\n' ' :sx-target "#ref-dis-b"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Load")\n' ' "Load")\n'
@@ -1166,7 +1166,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-retry-demo", "demo": "ref-retry-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/flaky"\n' '(button :sx-get "/geography/hypermedia/reference/api/flaky"\n'
' :sx-target "#ref-retry-result"\n' ' :sx-target "#ref-retry-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-retry "true"\n' ' :sx-retry "true"\n'
@@ -1221,9 +1221,9 @@ ATTR_DETAILS: dict[str, dict] = {
"example": ( "example": (
';; Boost with configurable target\n' ';; Boost with configurable target\n'
'(nav :sx-boost "#main-panel"\n' '(nav :sx-boost "#main-panel"\n'
' (a :href "/docs/introduction" "Introduction")\n' ' (a :href "/language/docs/introduction" "Introduction")\n'
' (a :href "/docs/components" "Components")\n' ' (a :href "/language/docs/components" "Components")\n'
' (a :href "/docs/evaluator" "Evaluator"))\n' ' (a :href "/language/docs/evaluator" "Evaluator"))\n'
'\n' '\n'
';; All links swap into #main-panel automatically.\n' ';; All links swap into #main-panel automatically.\n'
';; Pure pages render client-side (no server request).' ';; Pure pages render client-side (no server request).'
@@ -1239,7 +1239,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-preload-demo", "demo": "ref-preload-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/time"\n' '(button :sx-get "/geography/hypermedia/reference/api/time"\n'
' :sx-target "#ref-preload-result"\n' ' :sx-target "#ref-preload-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-preload "mouseover"\n' ' :sx-preload "mouseover"\n'
@@ -1275,7 +1275,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-indicator-demo", "demo": "ref-indicator-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/slow-echo"\n' '(button :sx-get "/geography/hypermedia/reference/api/slow-echo"\n'
' :sx-target "#ref-indicator-result"\n' ' :sx-target "#ref-indicator-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-indicator "#ref-spinner"\n' ' :sx-indicator "#ref-spinner"\n'
@@ -1302,7 +1302,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-validate-demo", "demo": "ref-validate-demo",
"example": ( "example": (
'(form :sx-post "/hypermedia/reference/api/greet"\n' '(form :sx-post "/geography/hypermedia/reference/api/greet"\n'
' :sx-target "#ref-validate-result"\n' ' :sx-target "#ref-validate-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-validate "true"\n' ' :sx-validate "true"\n'
@@ -1340,7 +1340,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-optimistic-demo", "demo": "ref-optimistic-demo",
"example": ( "example": (
'(button :sx-delete "/hypermedia/reference/api/item/opt1"\n' '(button :sx-delete "/geography/hypermedia/reference/api/item/opt1"\n'
' :sx-target "#ref-opt-item"\n' ' :sx-target "#ref-opt-item"\n'
' :sx-swap "delete"\n' ' :sx-swap "delete"\n'
' :sx-optimistic "remove"\n' ' :sx-optimistic "remove"\n'
@@ -1362,7 +1362,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-replace-url-demo", "demo": "ref-replace-url-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/time"\n' '(button :sx-get "/geography/hypermedia/reference/api/time"\n'
' :sx-target "#ref-replurl-result"\n' ' :sx-target "#ref-replurl-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-replace-url "true"\n' ' :sx-replace-url "true"\n'
@@ -1378,7 +1378,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-disabled-elt-demo", "demo": "ref-disabled-elt-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/slow-echo"\n' '(button :sx-get "/geography/hypermedia/reference/api/slow-echo"\n'
' :sx-target "#ref-diselt-result"\n' ' :sx-target "#ref-diselt-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-disabled-elt "this"\n' ' :sx-disabled-elt "this"\n'
@@ -1394,7 +1394,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-prompt-demo", "demo": "ref-prompt-demo",
"example": ( "example": (
'(button :sx-get "/hypermedia/reference/api/prompt-echo"\n' '(button :sx-get "/geography/hypermedia/reference/api/prompt-echo"\n'
' :sx-target "#ref-prompt-result"\n' ' :sx-target "#ref-prompt-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-prompt "Enter your name:"\n' ' :sx-prompt "Enter your name:"\n'
@@ -1414,7 +1414,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-params-demo", "demo": "ref-params-demo",
"example": ( "example": (
'(form :sx-post "/hypermedia/reference/api/echo-vals"\n' '(form :sx-post "/geography/hypermedia/reference/api/echo-vals"\n'
' :sx-target "#ref-params-result"\n' ' :sx-target "#ref-params-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-params "name"\n' ' :sx-params "name"\n'
@@ -1433,7 +1433,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-sse-demo", "demo": "ref-sse-demo",
"example": ( "example": (
'(div :sx-sse "/hypermedia/reference/api/sse-time"\n' '(div :sx-sse "/geography/hypermedia/reference/api/sse-time"\n'
' :sx-sse-swap "time"\n' ' :sx-sse-swap "time"\n'
' :sx-target "#ref-sse-result"\n' ' :sx-target "#ref-sse-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'

View File

@@ -12,7 +12,7 @@
"Each bar shows how many of the " "Each bar shows how many of the "
(strong (str total-components)) (strong (str total-components))
" total components a page actually needs, computed by the " " total components a page actually needs, computed by the "
(a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx") (a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
" transitive closure algorithm. " " transitive closure algorithm. "
"Click a page to see its component tree; expand a component to see its SX source.") "Click a page to see its component tree; expand a component to see its SX source.")

View File

@@ -373,7 +373,7 @@
"directly. They arrive as part of the rendered HTML — keyframes, custom properties, " "directly. They arrive as part of the rendered HTML — keyframes, custom properties, "
"scoped rules, anything.") "scoped rules, anything.")
(li (strong "External stylesheets: ") "Components can reference pre-loaded CSS files " (li (strong "External stylesheets: ") "Components can reference pre-loaded CSS files "
"or lazy-load them via " (code "~suspense") " (see " (a :href "/cssx/async" "Async CSS") ").") "or lazy-load them via " (code "~suspense") " (see " (a :href "/applications/cssx/async" "Async CSS") ").")
(li (strong "Custom properties: ") "A " (code "~theme") " component sets " (li (strong "Custom properties: ") "A " (code "~theme") " component sets "
(code "--color-primary") " etc. via a " (code "<style>") " block. Everything " (code "--color-primary") " etc. via a " (code "<style>") " block. Everything "
"using " (code "var()") " repaints automatically.")) "using " (code "var()") " repaints automatically."))

View File

@@ -79,7 +79,7 @@
(p :class "text-stone-600" (p :class "text-stone-600"
"Special forms are syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface.") "Special forms are syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface.")
(p :class "text-stone-600" (p :class "text-stone-600"
"Forms marked with a tail position enable " (a :href "/essays/tco" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.") "Forms marked with a tail position enable " (a :href "/etc/essays/tco" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.")
(div :class "space-y-10" forms)))) (div :class "space-y-10" forms))))
(defcomp ~docs-server-rendering-content () (defcomp ~docs-server-rendering-content ()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@
(p :class "text-stone-600" (p :class "text-stone-600"
"The property these systems lack has a name: " (a :href "https://en.wikipedia.org/wiki/Reflection_(computer_programming)" :class "text-violet-600 hover:underline" "reflexivity") ". A reflexive system can represent itself, reason about its own structure, and modify itself based on that reasoning. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " has had this property " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" :class "text-violet-600 hover:underline" "since 1958") ". The web has never had it.") "The property these systems lack has a name: " (a :href "https://en.wikipedia.org/wiki/Reflection_(computer_programming)" :class "text-violet-600 hover:underline" "reflexivity") ". A reflexive system can represent itself, reason about its own structure, and modify itself based on that reasoning. " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)" :class "text-violet-600 hover:underline" "Lisp") " has had this property " (a :href "https://en.wikipedia.org/wiki/Lisp_(programming_language)#History" :class "text-violet-600 hover:underline" "since 1958") ". The web has never had it.")
(p :class "text-stone-600" (p :class "text-stone-600"
"SX is a complete Lisp. It has " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " — code is data, data is code. It has a " (a :href "/specs/core" :class "text-violet-600 hover:underline" "self-hosting specification") " — SX defined in SX. It has " (code "eval") " and " (code "quote") " and " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" :class "text-violet-600 hover:underline" "macros") ". And it runs on the wire — the format that travels between server and client IS the language. This combination has consequences.")) "SX is a complete Lisp. It has " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " — code is data, data is code. It has a " (a :href "/language/specs/core" :class "text-violet-600 hover:underline" "self-hosting specification") " — SX defined in SX. It has " (code "eval") " and " (code "quote") " and " (a :href "https://en.wikipedia.org/wiki/Macro_(computer_science)#Syntactic_macros" :class "text-violet-600 hover:underline" "macros") ". And it runs on the wire — the format that travels between server and client IS the language. This combination has consequences."))
(~doc-section :title "What homoiconicity changes" :id "homoiconicity" (~doc-section :title "What homoiconicity changes" :id "homoiconicity"
(p :class "text-stone-600" (p :class "text-stone-600"
@@ -27,7 +27,7 @@
(p :class "text-stone-600" (p :class "text-stone-600"
"In an SX web, the AI reads the same s-expressions the browser reads. The component definitions " (em "are") " the documentation — a " (code "defcomp") " declares its parameters, its structure, and its semantics in one expression. There is no " (a :href "https://en.wikipedia.org/wiki/OpenAPI_Specification" :class "text-violet-600 hover:underline" "Swagger spec") " describing an API. The API " (em "is") " the language, and the language is self-describing.") "In an SX web, the AI reads the same s-expressions the browser reads. The component definitions " (em "are") " the documentation — a " (code "defcomp") " declares its parameters, its structure, and its semantics in one expression. There is no " (a :href "https://en.wikipedia.org/wiki/OpenAPI_Specification" :class "text-violet-600 hover:underline" "Swagger spec") " describing an API. The API " (em "is") " the language, and the language is self-describing.")
(p :class "text-stone-600" (p :class "text-stone-600"
"An AI that understands SX understands the " (a :href "/specs/core" :class "text-violet-600 hover:underline" "spec") ". And the spec is written in SX. So the AI understands the definition of the language it is using, in the language it is using. This " (a :href "https://en.wikipedia.org/wiki/Reflexivity_(social_theory)" :class "text-violet-600 hover:underline" "reflexive") " property means the AI does not need a separate mental model for \"the web\" and \"the language\" — they are the same thing.")) "An AI that understands SX understands the " (a :href "/language/specs/core" :class "text-violet-600 hover:underline" "spec") ". And the spec is written in SX. So the AI understands the definition of the language it is using, in the language it is using. This " (a :href "https://en.wikipedia.org/wiki/Reflexivity_(social_theory)" :class "text-violet-600 hover:underline" "reflexive") " property means the AI does not need a separate mental model for \"the web\" and \"the language\" — they are the same thing."))
(~doc-section :title "Live system modification" :id "live-modification" (~doc-section :title "Live system modification" :id "live-modification"
(p :class "text-stone-600" (p :class "text-stone-600"
@@ -51,7 +51,7 @@
(p :class "text-stone-600" (p :class "text-stone-600"
"A macro is a function that takes code and returns code. An AI generating macros is writing programs that write programs. With " (code "eval") ", those generated programs can generate more programs at runtime. This is not a metaphor — it is the literal mechanism.") "A macro is a function that takes code and returns code. An AI generating macros is writing programs that write programs. With " (code "eval") ", those generated programs can generate more programs at runtime. This is not a metaphor — it is the literal mechanism.")
(p :class "text-stone-600" (p :class "text-stone-600"
"The " (a :href "/philosophy/godel-escher-bach" :class "text-violet-600 hover:underline" "Gödel numbering") " parallel is not incidental. " (a :href "https://en.wikipedia.org/wiki/Kurt_G%C3%B6del" :class "text-violet-600 hover:underline" "Gödel") " showed that any sufficiently powerful formal system can encode statements about itself. A complete Lisp on the wire is a sufficiently powerful formal system. The web can make statements about itself — components that inspect other components, macros that rewrite the page structure, expressions that generate new expressions based on the current state of the system.") "The " (a :href "/etc/philosophy/godel-escher-bach" :class "text-violet-600 hover:underline" "Gödel numbering") " parallel is not incidental. " (a :href "https://en.wikipedia.org/wiki/Kurt_G%C3%B6del" :class "text-violet-600 hover:underline" "Gödel") " showed that any sufficiently powerful formal system can encode statements about itself. A complete Lisp on the wire is a sufficiently powerful formal system. The web can make statements about itself — components that inspect other components, macros that rewrite the page structure, expressions that generate new expressions based on the current state of the system.")
(p :class "text-stone-600" (p :class "text-stone-600"
"Consider what this enables for AI:") "Consider what this enables for AI:")
(ul :class "space-y-2 text-stone-600 mt-2" (ul :class "space-y-2 text-stone-600 mt-2"
@@ -63,7 +63,7 @@
(p :class "text-stone-600" (p :class "text-stone-600"
"The same " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " that makes this powerful makes it dangerous. Code-as-data means an AI can inject " (em "behaviour") ", not just content. A malicious expression evaluated in the wrong context could exfiltrate data, modify other components, or disrupt the system.") "The same " (a :href "https://en.wikipedia.org/wiki/Homoiconicity" :class "text-violet-600 hover:underline" "homoiconicity") " that makes this powerful makes it dangerous. Code-as-data means an AI can inject " (em "behaviour") ", not just content. A malicious expression evaluated in the wrong context could exfiltrate data, modify other components, or disrupt the system.")
(p :class "text-stone-600" (p :class "text-stone-600"
"This is why the " (a :href "/specs/primitives" :class "text-violet-600 hover:underline" "primitive set") " is the critical security boundary. The spec defines exactly which operations are available. A sandboxed evaluator that only exposes pure primitives (arithmetic, string operations, list manipulation) cannot perform I/O. Cannot access the network. Cannot modify the DOM outside its designated target. The language is " (a :href "https://en.wikipedia.org/wiki/Turing_completeness" :class "text-violet-600 hover:underline" "Turing-complete") " within the sandbox and powerless outside it.") "This is why the " (a :href "/language/specs/primitives" :class "text-violet-600 hover:underline" "primitive set") " is the critical security boundary. The spec defines exactly which operations are available. A sandboxed evaluator that only exposes pure primitives (arithmetic, string operations, list manipulation) cannot perform I/O. Cannot access the network. Cannot modify the DOM outside its designated target. The language is " (a :href "https://en.wikipedia.org/wiki/Turing_completeness" :class "text-violet-600 hover:underline" "Turing-complete") " within the sandbox and powerless outside it.")
(p :class "text-stone-600" (p :class "text-stone-600"
"Different contexts grant different primitive sets. A component evaluated in a page slot gets rendering primitives. A macro gets code-transformation primitives. A federated expression from an untrusted node gets the minimal safe set. The sandbox is not bolted on — it is inherent in the language's architecture. What you can do depends on which primitives are in scope.") "Different contexts grant different primitive sets. A component evaluated in a page slot gets rendering primitives. A macro gets code-transformation primitives. A federated expression from an untrusted node gets the minimal safe set. The sandbox is not bolted on — it is inherent in the language's architecture. What you can do depends on which primitives are in scope.")
(p :class "text-stone-600" (p :class "text-stone-600"

View File

@@ -36,7 +36,7 @@
(p :class "text-stone-600" (p :class "text-stone-600"
"This makes it trivial to build AI pipelines that generate SX. Parse the output. If it parses, evaluate it in a sandbox. If it does not parse, the error is always the same kind — unmatched parentheses — and the fix is always mechanical. There is no \"your JSX is invalid because you used " (code "class") " instead of " (code "className") "\" or \"you forgot the semicolon after the type annotation but before the generic constraint.\"") "This makes it trivial to build AI pipelines that generate SX. Parse the output. If it parses, evaluate it in a sandbox. If it does not parse, the error is always the same kind — unmatched parentheses — and the fix is always mechanical. There is no \"your JSX is invalid because you used " (code "class") " instead of " (code "className") "\" or \"you forgot the semicolon after the type annotation but before the generic constraint.\"")
(p :class "text-stone-600" (p :class "text-stone-600"
"Beyond parsing, the SX " (a :href "/specs/primitives" :class "text-violet-600 hover:underline" "boundary system") " provides semantic validation. A pure component cannot call IO primitives — not by convention, but by the evaluator refusing to resolve them. An AI generating a component can produce whatever expressions it wants; the sandbox ensures only permitted operations execute. Validation is not a separate step bolted onto the pipeline. It is the language.")) "Beyond parsing, the SX " (a :href "/language/specs/primitives" :class "text-violet-600 hover:underline" "boundary system") " provides semantic validation. A pure component cannot call IO primitives — not by convention, but by the evaluator refusing to resolve them. An AI generating a component can produce whatever expressions it wants; the sandbox ensures only permitted operations execute. Validation is not a separate step bolted onto the pipeline. It is the language."))
(~doc-section :title "Components are self-documenting" :id "self-documenting" (~doc-section :title "Components are self-documenting" :id "self-documenting"
(p :class "text-stone-600" (p :class "text-stone-600"
@@ -98,7 +98,7 @@
(p :class "text-stone-600" (p :class "text-stone-600"
"This is only possible because the representation is uniform. The AI does not need to switch between \"writing HTML mode\" and \"writing CSS mode\" and \"writing JavaScript mode\" and \"writing deployment config mode.\" It is always writing s-expressions. The cognitive load is constant. The error rate is constant. The speed is constant — regardless of whether it is generating a page layout, a macro expander, or a Docker healthcheck.") "This is only possible because the representation is uniform. The AI does not need to switch between \"writing HTML mode\" and \"writing CSS mode\" and \"writing JavaScript mode\" and \"writing deployment config mode.\" It is always writing s-expressions. The cognitive load is constant. The error rate is constant. The speed is constant — regardless of whether it is generating a page layout, a macro expander, or a Docker healthcheck.")
(p :class "text-stone-600" (p :class "text-stone-600"
"The " (a :href "/essays/sx-sucks" :class "text-violet-600 hover:underline" "sx sucks") " essay copped to the AI authorship and framed it as a weakness — microwave dinner on a nice plate. But the framing was wrong. If a language is so well-suited to machine generation that one person with no Lisp experience can build a self-hosting language, a multi-target bootstrapper, a reactive component framework, and a full documentation site through pure agentic AI — that is not a weakness of the language. That is the language working exactly as it should.")) "The " (a :href "/etc/essays/sx-sucks" :class "text-violet-600 hover:underline" "sx sucks") " essay copped to the AI authorship and framed it as a weakness — microwave dinner on a nice plate. But the framing was wrong. If a language is so well-suited to machine generation that one person with no Lisp experience can build a self-hosting language, a multi-target bootstrapper, a reactive component framework, and a full documentation site through pure agentic AI — that is not a weakness of the language. That is the language working exactly as it should."))
(~doc-section :title "What this changes" :id "what-changes" (~doc-section :title "What this changes" :id "what-changes"
(p :class "text-stone-600" (p :class "text-stone-600"

File diff suppressed because one or more lines are too long

View File

@@ -127,7 +127,7 @@
(p :class "text-stone-600" (p :class "text-stone-600"
"Every page you are reading was produced through conversation with an agentic AI. The SX evaluator — a self-hosting interpreter with tail-call optimization, delimited continuations, macro expansion, and three rendering backends — was developed without opening a code editor. The specification files that define the language were written without an IDE. The bootstrappers that compile the spec to JavaScript and Python were produced without syntax highlighting or autocomplete. The test suite — hundreds of tests across evaluator, parser, renderer, router, dependency analyzer, and engine — was written without a test runner GUI. This documentation site — with its navigation, its code examples, its live demos — was built without a web development framework's CLI.") "Every page you are reading was produced through conversation with an agentic AI. The SX evaluator — a self-hosting interpreter with tail-call optimization, delimited continuations, macro expansion, and three rendering backends — was developed without opening a code editor. The specification files that define the language were written without an IDE. The bootstrappers that compile the spec to JavaScript and Python were produced without syntax highlighting or autocomplete. The test suite — hundreds of tests across evaluator, parser, renderer, router, dependency analyzer, and engine — was written without a test runner GUI. This documentation site — with its navigation, its code examples, its live demos — was built without a web development framework's CLI.")
(p :class "text-stone-600" (p :class "text-stone-600"
"The developer sat in a terminal. They described what they wanted. The AI produced the code. When something was wrong, they described what was wrong. The AI fixed it. When something needed to change, they described the change. The AI made the change. Across thousands of files, tens of thousands of lines of code, and months of development. Even the jokes — the " (a :href "/essays/sx-sucks" :class "text-violet-600 hover:underline" "self-deprecating essay") " about everything wrong with SX, the deadpan tone of the documentation, the essay you are reading right now — all produced through conversation, not through typing.") "The developer sat in a terminal. They described what they wanted. The AI produced the code. When something was wrong, they described what was wrong. The AI fixed it. When something needed to change, they described the change. The AI made the change. Across thousands of files, tens of thousands of lines of code, and months of development. Even the jokes — the " (a :href "/etc/essays/sx-sucks" :class "text-violet-600 hover:underline" "self-deprecating essay") " about everything wrong with SX, the deadpan tone of the documentation, the essay you are reading right now — all produced through conversation, not through typing.")
(p :class "text-stone-600" (p :class "text-stone-600"
"No build step. No bundler. No transpiler. No package manager. No CSS preprocessor. No dev server. No linter. No formatter. No type checker. No framework CLI. No code editor.") "No build step. No bundler. No transpiler. No package manager. No CSS preprocessor. No dev server. No linter. No formatter. No type checker. No framework CLI. No code editor.")
(p :class "text-stone-600" (p :class "text-stone-600"

View File

@@ -8,8 +8,8 @@
:description "The simplest sx interaction: click a button, fetch content from the server, swap it in." :description "The simplest sx interaction: click a button, fetch content from the server, swap it in."
:demo-description "Click the button to load server-rendered content." :demo-description "Click the button to load server-rendered content."
:demo (~click-to-load-demo) :demo (~click-to-load-demo)
:sx-code "(button\n :sx-get \"/hypermedia/examples/api/click\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")" :sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/click\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/click\")\nasync def api_click():\n now = datetime.now().strftime(...)\n return sx_response(\n f'(~click-result :time \"{now}\")')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/click\")\nasync def api_click():\n now = datetime.now().strftime(...)\n return sx_response(\n f'(~click-result :time \"{now}\")')"
:comp-placeholder-id "click-comp" :comp-placeholder-id "click-comp"
:wire-placeholder-id "click-wire" :wire-placeholder-id "click-wire"
:wire-note "The server responds with content-type text/sx. New CSS rules are prepended as a style tag. Clear the component cache to see component definitions included in the wire response.")) :wire-note "The server responds with content-type text/sx. New CSS rules are prepended as a style tag. Clear the component cache to see component definitions included in the wire response."))
@@ -20,8 +20,8 @@
:description "Forms with sx-post submit via AJAX and swap the response into a target." :description "Forms with sx-post submit via AJAX and swap the response into a target."
:demo-description "Enter a name and submit." :demo-description "Enter a name and submit."
:demo (~form-demo) :demo (~form-demo)
:sx-code "(form\n :sx-post \"/hypermedia/examples/api/form\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))" :sx-code "(form\n :sx-post \"/geography/hypermedia/examples/api/form\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))"
:handler-code "@bp.post(\"/hypermedia/examples/api/form\")\nasync def api_form():\n form = await request.form\n name = form.get(\"name\", \"\")\n return sx_response(\n f'(~form-result :name \"{name}\")')" :handler-code "@bp.post(\"/geography/hypermedia/examples/api/form\")\nasync def api_form():\n form = await request.form\n name = form.get(\"name\", \"\")\n return sx_response(\n f'(~form-result :name \"{name}\")')"
:comp-placeholder-id "form-comp" :comp-placeholder-id "form-comp"
:wire-placeholder-id "form-wire")) :wire-placeholder-id "form-wire"))
@@ -31,8 +31,8 @@
:description "Use sx-trigger with \"every\" to poll the server at regular intervals." :description "Use sx-trigger with \"every\" to poll the server at regular intervals."
:demo-description "This div polls the server every 2 seconds." :demo-description "This div polls the server every 2 seconds."
:demo (~polling-demo) :demo (~polling-demo)
:sx-code "(div\n :sx-get \"/hypermedia/examples/api/poll\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")" :sx-code "(div\n :sx-get \"/geography/hypermedia/examples/api/poll\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/poll\")\nasync def api_poll():\n poll_count[\"n\"] += 1\n now = datetime.now().strftime(\"%H:%M:%S\")\n count = min(poll_count[\"n\"], 10)\n return sx_response(\n f'(~poll-result :time \"{now}\" :count {count})')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/poll\")\nasync def api_poll():\n poll_count[\"n\"] += 1\n now = datetime.now().strftime(\"%H:%M:%S\")\n count = min(poll_count[\"n\"], 10)\n return sx_response(\n f'(~poll-result :time \"{now}\" :count {count})')"
:comp-placeholder-id "poll-comp" :comp-placeholder-id "poll-comp"
:wire-placeholder-id "poll-wire" :wire-placeholder-id "poll-wire"
:wire-note "Updates every 2 seconds — watch the time and count change.")) :wire-note "Updates every 2 seconds — watch the time and count change."))
@@ -49,7 +49,7 @@
(list "4" "Deploy to production") (list "4" "Deploy to production")
(list "5" "Add unit tests"))) (list "5" "Add unit tests")))
:sx-code "(button\n :sx-delete \"/api/delete/1\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")" :sx-code "(button\n :sx-delete \"/api/delete/1\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")"
:handler-code "@bp.delete(\"/hypermedia/examples/api/delete/<item_id>\")\nasync def api_delete(item_id: str):\n # Empty response — outerHTML swap removes the row\n return Response(\"\", status=200,\n content_type=\"text/sx\")" :handler-code "@bp.delete(\"/geography/hypermedia/examples/api/delete/<item_id>\")\nasync def api_delete(item_id: str):\n # Empty response — outerHTML swap removes the row\n return Response(\"\", status=200,\n content_type=\"text/sx\")"
:comp-placeholder-id "delete-comp" :comp-placeholder-id "delete-comp"
:wire-placeholder-id "delete-wire" :wire-placeholder-id "delete-wire"
:wire-note "Empty body — outerHTML swap replaces the target element with nothing.")) :wire-note "Empty body — outerHTML swap replaces the target element with nothing."))
@@ -61,7 +61,7 @@
:demo-description "Click edit, modify the text, save or cancel." :demo-description "Click edit, modify the text, save or cancel."
:demo (~inline-edit-demo) :demo (~inline-edit-demo)
:sx-code ";; View mode — shows text + edit button\n(~inline-view :value \"some text\")\n\n;; Edit mode — returned by server on click\n(~inline-edit-form :value \"some text\")" :sx-code ";; View mode — shows text + edit button\n(~inline-view :value \"some text\")\n\n;; Edit mode — returned by server on click\n(~inline-edit-form :value \"some text\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/edit\")\nasync def api_edit_form():\n value = request.args.get(\"value\", \"\")\n return sx_response(\n f'(~inline-edit-form :value \"{value}\")')\n\n@bp.post(\"/hypermedia/examples/api/edit\")\nasync def api_edit_save():\n form = await request.form\n value = form.get(\"value\", \"\")\n return sx_response(\n f'(~inline-view :value \"{value}\")')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/edit\")\nasync def api_edit_form():\n value = request.args.get(\"value\", \"\")\n return sx_response(\n f'(~inline-edit-form :value \"{value}\")')\n\n@bp.post(\"/geography/hypermedia/examples/api/edit\")\nasync def api_edit_save():\n form = await request.form\n value = form.get(\"value\", \"\")\n return sx_response(\n f'(~inline-view :value \"{value}\")')"
:comp-placeholder-id "edit-comp" :comp-placeholder-id "edit-comp"
:comp-heading "Components" :comp-heading "Components"
:handler-heading "Server handlers" :handler-heading "Server handlers"
@@ -73,8 +73,8 @@
:description "sx-swap-oob lets a single response update multiple elements anywhere in the DOM." :description "sx-swap-oob lets a single response update multiple elements anywhere in the DOM."
:demo-description "One request updates both Box A (via sx-target) and Box B (via sx-swap-oob)." :demo-description "One request updates both Box A (via sx-target) and Box B (via sx-swap-oob)."
:demo (~oob-demo) :demo (~oob-demo)
:sx-code ";; Button targets Box A\n(button\n :sx-get \"/hypermedia/examples/api/oob\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")" :sx-code ";; Button targets Box A\n(button\n :sx-get \"/geography/hypermedia/examples/api/oob\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/oob\")\nasync def api_oob():\n now = datetime.now().strftime(\"%H:%M:%S\")\n return sx_response(\n f'(<>'\n f' (p \"Box A updated at {now}\")'\n f' (div :id \"oob-box-b\"'\n f' :sx-swap-oob \"innerHTML\"'\n f' (p \"Box B updated at {now}\")))')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/oob\")\nasync def api_oob():\n now = datetime.now().strftime(\"%H:%M:%S\")\n return sx_response(\n f'(<>'\n f' (p \"Box A updated at {now}\")'\n f' (div :id \"oob-box-b\"'\n f' :sx-swap-oob \"innerHTML\"'\n f' (p \"Box B updated at {now}\")))')"
:wire-placeholder-id "oob-wire" :wire-placeholder-id "oob-wire"
:wire-note "The fragment contains both the main content and an OOB element. sx.js splits them: main content goes to sx-target, OOB elements find their targets by ID.")) :wire-note "The fragment contains both the main content and an OOB element. sx.js splits them: main content goes to sx-target, OOB elements find their targets by ID."))
@@ -84,8 +84,8 @@
:description "Use sx-trigger=\"load\" to fetch content as soon as the element enters the DOM. Great for deferring expensive content below the fold." :description "Use sx-trigger=\"load\" to fetch content as soon as the element enters the DOM. Great for deferring expensive content below the fold."
:demo-description "Content loads automatically when the page renders." :demo-description "Content loads automatically when the page renders."
:demo (~lazy-loading-demo) :demo (~lazy-loading-demo)
:sx-code "(div\n :sx-get \"/hypermedia/examples/api/lazy\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))" :sx-code "(div\n :sx-get \"/geography/hypermedia/examples/api/lazy\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))"
:handler-code "@bp.get(\"/hypermedia/examples/api/lazy\")\nasync def api_lazy():\n now = datetime.now().strftime(...)\n return sx_response(\n f'(~lazy-result :time \"{now}\")')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/lazy\")\nasync def api_lazy():\n now = datetime.now().strftime(...)\n return sx_response(\n f'(~lazy-result :time \"{now}\")')"
:comp-placeholder-id "lazy-comp" :comp-placeholder-id "lazy-comp"
:wire-placeholder-id "lazy-wire")) :wire-placeholder-id "lazy-wire"))
@@ -95,8 +95,8 @@
:description "A sentinel element at the bottom uses sx-trigger=\"intersect once\" to load the next page when scrolled into view. Each response appends items and a new sentinel." :description "A sentinel element at the bottom uses sx-trigger=\"intersect once\" to load the next page when scrolled into view. Each response appends items and a new sentinel."
:demo-description "Scroll down in the container to load more items (5 pages total)." :demo-description "Scroll down in the container to load more items (5 pages total)."
:demo (~infinite-scroll-demo) :demo (~infinite-scroll-demo)
:sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/hypermedia/examples/api/scroll?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")" :sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/geography/hypermedia/examples/api/scroll?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/scroll\")\nasync def api_scroll():\n page = int(request.args.get(\"page\", 2))\n items = [f\"Item {i}\" for i in range(...)]\n # Include next sentinel if more pages\n return sx_response(items_sx + sentinel_sx)" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/scroll\")\nasync def api_scroll():\n page = int(request.args.get(\"page\", 2))\n items = [f\"Item {i}\" for i in range(...)]\n # Include next sentinel if more pages\n return sx_response(items_sx + sentinel_sx)"
:comp-placeholder-id "scroll-comp" :comp-placeholder-id "scroll-comp"
:wire-placeholder-id "scroll-wire")) :wire-placeholder-id "scroll-wire"))
@@ -106,8 +106,8 @@
:description "Start a server-side job, then poll for progress using sx-trigger=\"load delay:500ms\" on each response. The bar fills up and stops when complete." :description "Start a server-side job, then poll for progress using sx-trigger=\"load delay:500ms\" on each response. The bar fills up and stops when complete."
:demo-description "Click start to begin a simulated job." :demo-description "Click start to begin a simulated job."
:demo (~progress-bar-demo) :demo (~progress-bar-demo)
:sx-code ";; Start the job\n(button\n :sx-post \"/hypermedia/examples/api/progress/start\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")\n\n;; Each response re-polls via sx-trigger=\"load\"\n(div :sx-get \"/api/progress/status?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")" :sx-code ";; Start the job\n(button\n :sx-post \"/geography/hypermedia/examples/api/progress/start\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")\n\n;; Each response re-polls via sx-trigger=\"load\"\n(div :sx-get \"/api/progress/status?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")"
:handler-code "@bp.post(\"/hypermedia/examples/api/progress/start\")\nasync def api_progress_start():\n job_id = str(uuid4())[:8]\n _jobs[job_id] = 0\n return sx_response(\n f'(~progress-status :percent 0 :job-id \"{job_id}\")')" :handler-code "@bp.post(\"/geography/hypermedia/examples/api/progress/start\")\nasync def api_progress_start():\n job_id = str(uuid4())[:8]\n _jobs[job_id] = 0\n return sx_response(\n f'(~progress-status :percent 0 :job-id \"{job_id}\")')"
:comp-placeholder-id "progress-comp" :comp-placeholder-id "progress-comp"
:wire-placeholder-id "progress-wire")) :wire-placeholder-id "progress-wire"))
@@ -117,8 +117,8 @@
:description "An input with sx-trigger=\"keyup delay:300ms changed\" debounces keystrokes and only fires when the value changes. The server filters a list of programming languages." :description "An input with sx-trigger=\"keyup delay:300ms changed\" debounces keystrokes and only fires when the value changes. The server filters a list of programming languages."
:demo-description "Type to search through 20 programming languages." :demo-description "Type to search through 20 programming languages."
:demo (~active-search-demo) :demo (~active-search-demo)
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/hypermedia/examples/api/search\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")" :sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/geography/hypermedia/examples/api/search\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/search\")\nasync def api_search():\n q = request.args.get(\"q\", \"\").lower()\n results = [l for l in LANGUAGES if q in l.lower()]\n return sx_response(\n f'(~search-results :items (...) :query \"{q}\")')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/search\")\nasync def api_search():\n q = request.args.get(\"q\", \"\").lower()\n results = [l for l in LANGUAGES if q in l.lower()]\n return sx_response(\n f'(~search-results :items (...) :query \"{q}\")')"
:comp-placeholder-id "search-comp" :comp-placeholder-id "search-comp"
:wire-placeholder-id "search-wire")) :wire-placeholder-id "search-wire"))
@@ -128,8 +128,8 @@
:description "Validate an email field on blur. The server checks format and whether it is taken, returning green or red feedback inline." :description "Validate an email field on blur. The server checks format and whether it is taken, returning green or red feedback inline."
:demo-description "Enter an email and click away (blur) to validate." :demo-description "Enter an email and click away (blur) to validate."
:demo (~inline-validation-demo) :demo (~inline-validation-demo)
:sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/hypermedia/examples/api/validate\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")" :sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/geography/hypermedia/examples/api/validate\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/validate\")\nasync def api_validate():\n email = request.args.get(\"email\", \"\")\n if \"@\" not in email:\n return sx_response('(~validation-error ...)')\n return sx_response('(~validation-ok ...)')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/validate\")\nasync def api_validate():\n email = request.args.get(\"email\", \"\")\n if \"@\" not in email:\n return sx_response('(~validation-error ...)')\n return sx_response('(~validation-ok ...)')"
:comp-placeholder-id "validate-comp" :comp-placeholder-id "validate-comp"
:wire-placeholder-id "validate-wire")) :wire-placeholder-id "validate-wire"))
@@ -139,8 +139,8 @@
:description "Two linked selects: pick a category and the second select updates with matching items via sx-get." :description "Two linked selects: pick a category and the second select updates with matching items via sx-get."
:demo-description "Select a category to populate the item dropdown." :demo-description "Select a category to populate the item dropdown."
:demo (~value-select-demo) :demo (~value-select-demo)
:sx-code "(select :name \"category\"\n :sx-get \"/hypermedia/examples/api/values\"\n :sx-trigger \"change\"\n :sx-target \"#value-items\"\n :sx-swap \"innerHTML\"\n (option \"Languages\")\n (option \"Frameworks\")\n (option \"Databases\"))" :sx-code "(select :name \"category\"\n :sx-get \"/geography/hypermedia/examples/api/values\"\n :sx-trigger \"change\"\n :sx-target \"#value-items\"\n :sx-swap \"innerHTML\"\n (option \"Languages\")\n (option \"Frameworks\")\n (option \"Databases\"))"
:handler-code "@bp.get(\"/hypermedia/examples/api/values\")\nasync def api_values():\n cat = request.args.get(\"category\", \"\")\n items = VALUE_SELECT_DATA.get(cat, [])\n return sx_response(\n f'(~value-options :items (list ...))')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/values\")\nasync def api_values():\n cat = request.args.get(\"category\", \"\")\n items = VALUE_SELECT_DATA.get(cat, [])\n return sx_response(\n f'(~value-options :items (list ...))')"
:comp-placeholder-id "values-comp" :comp-placeholder-id "values-comp"
:wire-placeholder-id "values-wire")) :wire-placeholder-id "values-wire"))
@@ -150,8 +150,8 @@
:description "Use sx-on:afterSwap=\"this.reset()\" to clear form inputs after a successful submission." :description "Use sx-on:afterSwap=\"this.reset()\" to clear form inputs after a successful submission."
:demo-description "Submit a message — the input resets after each send." :demo-description "Submit a message — the input resets after each send."
:demo (~reset-on-submit-demo) :demo (~reset-on-submit-demo)
:sx-code "(form :id \"reset-form\"\n :sx-post \"/hypermedia/examples/api/reset-submit\"\n :sx-target \"#reset-result\"\n :sx-swap \"innerHTML\"\n :sx-on:afterSwap \"this.reset()\"\n (input :type \"text\" :name \"message\")\n (button :type \"submit\" \"Send\"))" :sx-code "(form :id \"reset-form\"\n :sx-post \"/geography/hypermedia/examples/api/reset-submit\"\n :sx-target \"#reset-result\"\n :sx-swap \"innerHTML\"\n :sx-on:afterSwap \"this.reset()\"\n (input :type \"text\" :name \"message\")\n (button :type \"submit\" \"Send\"))"
:handler-code "@bp.post(\"/hypermedia/examples/api/reset-submit\")\nasync def api_reset_submit():\n form = await request.form\n msg = form.get(\"message\", \"\")\n return sx_response(\n f'(~reset-message :message \"{msg}\" :time \"...\")')" :handler-code "@bp.post(\"/geography/hypermedia/examples/api/reset-submit\")\nasync def api_reset_submit():\n form = await request.form\n msg = form.get(\"message\", \"\")\n return sx_response(\n f'(~reset-message :message \"{msg}\" :time \"...\")')"
:comp-placeholder-id "reset-comp" :comp-placeholder-id "reset-comp"
:wire-placeholder-id "reset-wire")) :wire-placeholder-id "reset-wire"))
@@ -165,8 +165,8 @@
(list "2" "Widget B" "24.50" "89") (list "2" "Widget B" "24.50" "89")
(list "3" "Widget C" "12.00" "305") (list "3" "Widget C" "12.00" "305")
(list "4" "Widget D" "45.00" "67"))) (list "4" "Widget D" "45.00" "67")))
:sx-code "(button\n :sx-get \"/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n \"edit\")\n\n;; Save sends form data via POST\n(button\n :sx-post \"/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")" :sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n \"edit\")\n\n;; Save sends form data via POST\n(button\n :sx-post \"/geography/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/editrow/<id>\")\nasync def api_editrow_form(id):\n row = EDIT_ROW_DATA[id]\n return sx_response(\n f'(~edit-row-form :id ... :name ...)')\n\n@bp.post(\"/hypermedia/examples/api/editrow/<id>\")\nasync def api_editrow_save(id):\n form = await request.form\n return sx_response(\n f'(~edit-row-view :id ... :name ...)')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/editrow/<id>\")\nasync def api_editrow_form(id):\n row = EDIT_ROW_DATA[id]\n return sx_response(\n f'(~edit-row-form :id ... :name ...)')\n\n@bp.post(\"/geography/hypermedia/examples/api/editrow/<id>\")\nasync def api_editrow_save(id):\n form = await request.form\n return sx_response(\n f'(~edit-row-view :id ... :name ...)')"
:comp-placeholder-id "editrow-comp" :comp-placeholder-id "editrow-comp"
:wire-placeholder-id "editrow-wire")) :wire-placeholder-id "editrow-wire"))
@@ -181,8 +181,8 @@
(list "3" "Carol Zhang" "carol@example.com" "active") (list "3" "Carol Zhang" "carol@example.com" "active")
(list "4" "Dan Okafor" "dan@example.com" "inactive") (list "4" "Dan Okafor" "dan@example.com" "inactive")
(list "5" "Eve Larsson" "eve@example.com" "active"))) (list "5" "Eve Larsson" "eve@example.com" "active")))
:sx-code "(button\n :sx-post \"/hypermedia/examples/api/bulk?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")" :sx-code "(button\n :sx-post \"/geography/hypermedia/examples/api/bulk?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")"
:handler-code "@bp.post(\"/hypermedia/examples/api/bulk\")\nasync def api_bulk():\n action = request.args.get(\"action\")\n form = await request.form\n ids = form.getlist(\"ids\")\n # Update matching users\n return sx_response(updated_rows)" :handler-code "@bp.post(\"/geography/hypermedia/examples/api/bulk\")\nasync def api_bulk():\n action = request.args.get(\"action\")\n form = await request.form\n ids = form.getlist(\"ids\")\n # Update matching users\n return sx_response(updated_rows)"
:comp-placeholder-id "bulk-comp" :comp-placeholder-id "bulk-comp"
:wire-placeholder-id "bulk-wire")) :wire-placeholder-id "bulk-wire"))
@@ -193,7 +193,7 @@
:demo-description "Try each button to see different swap behaviours." :demo-description "Try each button to see different swap behaviours."
:demo (~swap-positions-demo) :demo (~swap-positions-demo)
:sx-code ";; Append to end\n(button :sx-post \"/api/swap-log?mode=beforeend\"\n :sx-target \"#swap-log\" :sx-swap \"beforeend\"\n \"Add to End\")\n\n;; Prepend to start\n(button :sx-post \"/api/swap-log?mode=afterbegin\"\n :sx-target \"#swap-log\" :sx-swap \"afterbegin\"\n \"Add to Start\")\n\n;; No swap — OOB counter update only\n(button :sx-post \"/api/swap-log?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")" :sx-code ";; Append to end\n(button :sx-post \"/api/swap-log?mode=beforeend\"\n :sx-target \"#swap-log\" :sx-swap \"beforeend\"\n \"Add to End\")\n\n;; Prepend to start\n(button :sx-post \"/api/swap-log?mode=afterbegin\"\n :sx-target \"#swap-log\" :sx-swap \"afterbegin\"\n \"Add to Start\")\n\n;; No swap — OOB counter update only\n(button :sx-post \"/api/swap-log?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")"
:handler-code "@bp.post(\"/hypermedia/examples/api/swap-log\")\nasync def api_swap_log():\n mode = request.args.get(\"mode\")\n # OOB counter updates on every request\n oob = f'(span :id \"swap-counter\" :sx-swap-oob \"innerHTML\" \"Count: {n}\")'\n return sx_response(entry + oob)" :handler-code "@bp.post(\"/geography/hypermedia/examples/api/swap-log\")\nasync def api_swap_log():\n mode = request.args.get(\"mode\")\n # OOB counter updates on every request\n oob = f'(span :id \"swap-counter\" :sx-swap-oob \"innerHTML\" \"Count: {n}\")'\n return sx_response(entry + oob)"
:wire-placeholder-id "swap-wire")) :wire-placeholder-id "swap-wire"))
(defcomp ~example-select-filter () (defcomp ~example-select-filter ()
@@ -202,8 +202,8 @@
:description "sx-select lets the client pick a specific section from the server response by CSS selector. The server always returns the full dashboard — the client filters." :description "sx-select lets the client pick a specific section from the server response by CSS selector. The server always returns the full dashboard — the client filters."
:demo-description "Different buttons select different parts of the same server response." :demo-description "Different buttons select different parts of the same server response."
:demo (~select-filter-demo) :demo (~select-filter-demo)
:sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n :sx-select \"#dash-stats\"\n \"Stats Only\")\n\n;; No sx-select — get the full response\n(button\n :sx-get \"/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")" :sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/geography/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n :sx-select \"#dash-stats\"\n \"Stats Only\")\n\n;; No sx-select — get the full response\n(button\n :sx-get \"/geography/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/dashboard\")\nasync def api_dashboard():\n # Returns header + stats + footer\n # Client uses sx-select to pick sections\n return sx_response(\n '(<> (div :id \"dash-header\" ...) '\n ' (div :id \"dash-stats\" ...) '\n ' (div :id \"dash-footer\" ...))')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/dashboard\")\nasync def api_dashboard():\n # Returns header + stats + footer\n # Client uses sx-select to pick sections\n return sx_response(\n '(<> (div :id \"dash-header\" ...) '\n ' (div :id \"dash-stats\" ...) '\n ' (div :id \"dash-footer\" ...))')"
:wire-placeholder-id "filter-wire")) :wire-placeholder-id "filter-wire"))
(defcomp ~example-tabs () (defcomp ~example-tabs ()
@@ -212,8 +212,8 @@
:description "Tab navigation using sx-push-url to update the browser URL. Back/forward buttons navigate between previously visited tabs." :description "Tab navigation using sx-push-url to update the browser URL. Back/forward buttons navigate between previously visited tabs."
:demo-description "Click tabs to switch content. Watch the browser URL change." :demo-description "Click tabs to switch content. Watch the browser URL change."
:demo (~tabs-demo) :demo (~tabs-demo)
:sx-code "(button\n :sx-get \"/hypermedia/examples/api/tabs/tab1\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/hypermedia/examples/tabs?tab=tab1\"\n \"Overview\")" :sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/tabs/tab1\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/geography/hypermedia/examples/tabs?tab=tab1\"\n \"Overview\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/tabs/<tab>\")\nasync def api_tabs(tab: str):\n content = TAB_CONTENT[tab]\n return sx_response(content)" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/tabs/<tab>\")\nasync def api_tabs(tab: str):\n content = TAB_CONTENT[tab]\n return sx_response(content)"
:wire-placeholder-id "tabs-wire")) :wire-placeholder-id "tabs-wire"))
(defcomp ~example-animations () (defcomp ~example-animations ()
@@ -222,8 +222,8 @@
:description "CSS animations play on swap. The component injects a style tag with a keyframe animation and applies the class. Each click picks a random background colour." :description "CSS animations play on swap. The component injects a style tag with a keyframe animation and applies the class. Each click picks a random background colour."
:demo-description "Click to swap in content with a fade-in animation." :demo-description "Click to swap in content with a fade-in animation."
:demo (~animations-demo) :demo (~animations-demo)
:sx-code "(button\n :sx-get \"/hypermedia/examples/api/animate\"\n :sx-target \"#anim-target\"\n :sx-swap \"innerHTML\"\n \"Load with animation\")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class \"sx-fade-in ...\"\n (style \".sx-fade-in { animation: sxFadeIn 0.5s }\")\n (p \"Faded in!\")))" :sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/animate\"\n :sx-target \"#anim-target\"\n :sx-swap \"innerHTML\"\n \"Load with animation\")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class \"sx-fade-in ...\"\n (style \".sx-fade-in { animation: sxFadeIn 0.5s }\")\n (p \"Faded in!\")))"
:handler-code "@bp.get(\"/hypermedia/examples/api/animate\")\nasync def api_animate():\n colors = [\"bg-violet-100\", \"bg-emerald-100\", ...]\n color = random.choice(colors)\n return sx_response(\n f'(~anim-result :color \"{color}\" :time \"{now}\")')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/animate\")\nasync def api_animate():\n colors = [\"bg-violet-100\", \"bg-emerald-100\", ...]\n color = random.choice(colors)\n return sx_response(\n f'(~anim-result :color \"{color}\" :time \"{now}\")')"
:comp-placeholder-id "anim-comp" :comp-placeholder-id "anim-comp"
:wire-placeholder-id "anim-wire")) :wire-placeholder-id "anim-wire"))
@@ -233,8 +233,8 @@
:description "Open a modal dialog by swapping in the dialog component. Close by swapping in empty content. Pure sx — no JavaScript library needed." :description "Open a modal dialog by swapping in the dialog component. Close by swapping in empty content. Pure sx — no JavaScript library needed."
:demo-description "Click to open a modal dialog." :demo-description "Click to open a modal dialog."
:demo (~dialogs-demo) :demo (~dialogs-demo)
:sx-code "(button\n :sx-get \"/hypermedia/examples/api/dialog\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Open Dialog\")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get \"/hypermedia/examples/api/dialog/close\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")" :sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/dialog\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Open Dialog\")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get \"/geography/hypermedia/examples/api/dialog/close\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/dialog\")\nasync def api_dialog():\n return sx_response(\n '(~dialog-modal :title \"Confirm\"'\n ' :message \"Are you sure?\")')\n\n@bp.get(\"/hypermedia/examples/api/dialog/close\")\nasync def api_dialog_close():\n return sx_response(\"\")" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/dialog\")\nasync def api_dialog():\n return sx_response(\n '(~dialog-modal :title \"Confirm\"'\n ' :message \"Are you sure?\")')\n\n@bp.get(\"/geography/hypermedia/examples/api/dialog/close\")\nasync def api_dialog_close():\n return sx_response(\"\")"
:comp-placeholder-id "dialog-comp" :comp-placeholder-id "dialog-comp"
:wire-placeholder-id "dialog-wire")) :wire-placeholder-id "dialog-wire"))
@@ -244,8 +244,8 @@
:description "Use sx-trigger with keyup event filters and from:body to listen for global keyboard shortcuts. The filter prevents firing when typing in inputs." :description "Use sx-trigger with keyup event filters and from:body to listen for global keyboard shortcuts. The filter prevents firing when typing in inputs."
:demo-description "Press s, n, or h on your keyboard." :demo-description "Press s, n, or h on your keyboard."
:demo (~keyboard-shortcuts-demo) :demo (~keyboard-shortcuts-demo)
:sx-code "(div :id \"kbd-target\"\n :sx-get \"/hypermedia/examples/api/keyboard?key=s\"\n :sx-trigger \"keyup[key=='s'&&!event.target.matches('input,textarea')] from:body\"\n :sx-swap \"innerHTML\"\n \"Press a shortcut key...\")" :sx-code "(div :id \"kbd-target\"\n :sx-get \"/geography/hypermedia/examples/api/keyboard?key=s\"\n :sx-trigger \"keyup[key=='s'&&!event.target.matches('input,textarea')] from:body\"\n :sx-swap \"innerHTML\"\n \"Press a shortcut key...\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/keyboard\")\nasync def api_keyboard():\n key = request.args.get(\"key\", \"\")\n actions = {\"s\": \"Search\", \"n\": \"New item\", \"h\": \"Help\"}\n return sx_response(\n f'(~kbd-result :key \"{key}\" :action \"{actions[key]}\")')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/keyboard\")\nasync def api_keyboard():\n key = request.args.get(\"key\", \"\")\n actions = {\"s\": \"Search\", \"n\": \"New item\", \"h\": \"Help\"}\n return sx_response(\n f'(~kbd-result :key \"{key}\" :action \"{actions[key]}\")')"
:comp-placeholder-id "kbd-comp" :comp-placeholder-id "kbd-comp"
:wire-placeholder-id "kbd-wire")) :wire-placeholder-id "kbd-wire"))
@@ -255,8 +255,8 @@
:description "sx-put replaces the entire resource. This example shows a profile card with an Edit All button that sends a PUT with all fields." :description "sx-put replaces the entire resource. This example shows a profile card with an Edit All button that sends a PUT with all fields."
:demo-description "Click Edit All to replace the full profile via PUT." :demo-description "Click Edit All to replace the full profile via PUT."
:demo (~put-patch-demo :name "Ada Lovelace" :email "ada@example.com" :role "Engineer") :demo (~put-patch-demo :name "Ada Lovelace" :email "ada@example.com" :role "Engineer")
:sx-code ";; Replace entire resource\n(form :sx-put \"/hypermedia/examples/api/putpatch\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))" :sx-code ";; Replace entire resource\n(form :sx-put \"/geography/hypermedia/examples/api/putpatch\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))"
:handler-code "@bp.put(\"/hypermedia/examples/api/putpatch\")\nasync def api_put():\n form = await request.form\n # Full replacement\n return sx_response('(~pp-view ...)')" :handler-code "@bp.put(\"/geography/hypermedia/examples/api/putpatch\")\nasync def api_put():\n form = await request.form\n # Full replacement\n return sx_response('(~pp-view ...)')"
:comp-placeholder-id "pp-comp" :comp-placeholder-id "pp-comp"
:wire-placeholder-id "pp-wire")) :wire-placeholder-id "pp-wire"))
@@ -266,8 +266,8 @@
:description "Use sx-encoding=\"json\" to send form data as a JSON body instead of URL-encoded form data. The server echoes back what it received." :description "Use sx-encoding=\"json\" to send form data as a JSON body instead of URL-encoded form data. The server echoes back what it received."
:demo-description "Submit the form and see the JSON body the server received." :demo-description "Submit the form and see the JSON body the server received."
:demo (~json-encoding-demo) :demo (~json-encoding-demo)
:sx-code "(form\n :sx-post \"/hypermedia/examples/api/json-echo\"\n :sx-target \"#json-result\"\n :sx-swap \"innerHTML\"\n :sx-encoding \"json\"\n (input :name \"name\" :value \"Ada\")\n (input :type \"number\" :name \"age\" :value \"36\")\n (button \"Submit as JSON\"))" :sx-code "(form\n :sx-post \"/geography/hypermedia/examples/api/json-echo\"\n :sx-target \"#json-result\"\n :sx-swap \"innerHTML\"\n :sx-encoding \"json\"\n (input :name \"name\" :value \"Ada\")\n (input :type \"number\" :name \"age\" :value \"36\")\n (button \"Submit as JSON\"))"
:handler-code "@bp.post(\"/hypermedia/examples/api/json-echo\")\nasync def api_json_echo():\n data = await request.get_json()\n body = json.dumps(data, indent=2)\n ct = request.content_type\n return sx_response(\n f'(~json-result :body \"{body}\" :content-type \"{ct}\")')" :handler-code "@bp.post(\"/geography/hypermedia/examples/api/json-echo\")\nasync def api_json_echo():\n data = await request.get_json()\n body = json.dumps(data, indent=2)\n ct = request.content_type\n return sx_response(\n f'(~json-result :body \"{body}\" :content-type \"{ct}\")')"
:comp-placeholder-id "json-comp" :comp-placeholder-id "json-comp"
:wire-placeholder-id "json-wire")) :wire-placeholder-id "json-wire"))
@@ -277,8 +277,8 @@
:description "sx-vals adds extra key/value pairs to the request parameters. sx-headers adds custom HTTP headers. The server echoes back what it received." :description "sx-vals adds extra key/value pairs to the request parameters. sx-headers adds custom HTTP headers. The server echoes back what it received."
:demo-description "Click each button to see what the server receives." :demo-description "Click each button to see what the server receives."
:demo (~vals-headers-demo) :demo (~vals-headers-demo)
:sx-code ";; Send extra values with the request\n(button\n :sx-get \"/hypermedia/examples/api/echo-vals\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/hypermedia/examples/api/echo-headers\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")" :sx-code ";; Send extra values with the request\n(button\n :sx-get \"/geography/hypermedia/examples/api/echo-vals\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/geography/hypermedia/examples/api/echo-headers\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/echo-vals\")\nasync def api_echo_vals():\n vals = dict(request.args)\n return sx_response(\n f'(~echo-result :label \"values\" :items (...))')\n\n@bp.get(\"/hypermedia/examples/api/echo-headers\")\nasync def api_echo_headers():\n custom = {k: v for k, v in request.headers\n if k.startswith(\"X-\")}\n return sx_response(\n f'(~echo-result :label \"headers\" :items (...))')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/echo-vals\")\nasync def api_echo_vals():\n vals = dict(request.args)\n return sx_response(\n f'(~echo-result :label \"values\" :items (...))')\n\n@bp.get(\"/geography/hypermedia/examples/api/echo-headers\")\nasync def api_echo_headers():\n custom = {k: v for k, v in request.headers\n if k.startswith(\"X-\")}\n return sx_response(\n f'(~echo-result :label \"headers\" :items (...))')"
:comp-placeholder-id "vals-comp" :comp-placeholder-id "vals-comp"
:wire-placeholder-id "vals-wire")) :wire-placeholder-id "vals-wire"))
@@ -288,8 +288,8 @@
:description "sx.js adds the .sx-request CSS class to any element that has an active request. Use pure CSS to show spinners, disable buttons, or change opacity during loading." :description "sx.js adds the .sx-request CSS class to any element that has an active request. Use pure CSS to show spinners, disable buttons, or change opacity during loading."
:demo-description "Click the button — it shows a spinner during the 2-second request." :demo-description "Click the button — it shows a spinner during the 2-second request."
:demo (~loading-states-demo) :demo (~loading-states-demo)
:sx-code ";; .sx-request class added during request\n(style \".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }\")\n\n(button :class \"sx-loading-btn\"\n :sx-get \"/hypermedia/examples/api/slow\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")" :sx-code ";; .sx-request class added during request\n(style \".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }\")\n\n(button :class \"sx-loading-btn\"\n :sx-get \"/geography/hypermedia/examples/api/slow\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/slow\")\nasync def api_slow():\n await asyncio.sleep(2)\n return sx_response(\n f'(~loading-result :time \"{now}\")')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/slow\")\nasync def api_slow():\n await asyncio.sleep(2)\n return sx_response(\n f'(~loading-result :time \"{now}\")')"
:comp-placeholder-id "loading-comp" :comp-placeholder-id "loading-comp"
:wire-placeholder-id "loading-wire")) :wire-placeholder-id "loading-wire"))
@@ -299,8 +299,8 @@
:description "sx-sync=\"replace\" aborts any in-flight request before sending a new one. This prevents stale responses from overwriting newer ones, even with random server delays." :description "sx-sync=\"replace\" aborts any in-flight request before sending a new one. This prevents stale responses from overwriting newer ones, even with random server delays."
:demo-description "Type quickly — only the latest result appears despite random 0.5-2s server delays." :demo-description "Type quickly — only the latest result appears despite random 0.5-2s server delays."
:demo (~sync-replace-demo) :demo (~sync-replace-demo)
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/hypermedia/examples/api/slow-search\"\n :sx-trigger \"keyup delay:200ms changed\"\n :sx-target \"#sync-result\"\n :sx-swap \"innerHTML\"\n :sx-sync \"replace\"\n \"Type to search...\")" :sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/geography/hypermedia/examples/api/slow-search\"\n :sx-trigger \"keyup delay:200ms changed\"\n :sx-target \"#sync-result\"\n :sx-swap \"innerHTML\"\n :sx-sync \"replace\"\n \"Type to search...\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/slow-search\")\nasync def api_slow_search():\n delay = random.uniform(0.5, 2.0)\n await asyncio.sleep(delay)\n q = request.args.get(\"q\", \"\")\n return sx_response(\n f'(~sync-result :query \"{q}\" :delay \"{delay_ms}\")')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/slow-search\")\nasync def api_slow_search():\n delay = random.uniform(0.5, 2.0)\n await asyncio.sleep(delay)\n q = request.args.get(\"q\", \"\")\n return sx_response(\n f'(~sync-result :query \"{q}\" :delay \"{delay_ms}\")')"
:comp-placeholder-id "sync-comp" :comp-placeholder-id "sync-comp"
:wire-placeholder-id "sync-wire")) :wire-placeholder-id "sync-wire"))
@@ -310,7 +310,7 @@
:description "sx-retry=\"exponential:1000:8000\" retries failed requests with exponential backoff starting at 1s up to 8s. The endpoint fails the first 2 attempts and succeeds on the 3rd." :description "sx-retry=\"exponential:1000:8000\" retries failed requests with exponential backoff starting at 1s up to 8s. The endpoint fails the first 2 attempts and succeeds on the 3rd."
:demo-description "Click the button — watch it retry automatically after failures." :demo-description "Click the button — watch it retry automatically after failures."
:demo (~retry-demo) :demo (~retry-demo)
:sx-code "(button\n :sx-get \"/hypermedia/examples/api/flaky\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")" :sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/flaky\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")"
:handler-code "@bp.get(\"/hypermedia/examples/api/flaky\")\nasync def api_flaky():\n _flaky[\"n\"] += 1\n if _flaky[\"n\"] % 3 != 0:\n return Response(\"\", status=503)\n return sx_response(\n f'(~retry-result :attempt {n} ...)')" :handler-code "@bp.get(\"/geography/hypermedia/examples/api/flaky\")\nasync def api_flaky():\n _flaky[\"n\"] += 1\n if _flaky[\"n\"] % 3 != 0:\n return Response(\"\", status=503)\n return sx_response(\n f'(~retry-result :attempt {n} ...)')"
:comp-placeholder-id "retry-comp" :comp-placeholder-id "retry-comp"
:wire-placeholder-id "retry-wire")) :wire-placeholder-id "retry-wire"))

View File

@@ -32,26 +32,26 @@
(p :class "text-stone-600 mb-6" (p :class "text-stone-600 mb-6"
"Complete reference for the sx client library.") "Complete reference for the sx client library.")
(div :class "grid gap-4 sm:grid-cols-2" (div :class "grid gap-4 sm:grid-cols-2"
(a :href "/hypermedia/reference/attributes" (a :href "/geography/hypermedia/reference/attributes"
:sx-get "/hypermedia/reference/attributes" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/geography/hypermedia/reference/attributes" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline" :class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Attributes") (h3 :class "text-lg font-semibold text-violet-700 mb-1" "Attributes")
(p :class "text-stone-600 text-sm" "All sx attributes — request verbs, behavior modifiers, and sx-unique features.")) (p :class "text-stone-600 text-sm" "All sx attributes — request verbs, behavior modifiers, and sx-unique features."))
(a :href "/hypermedia/reference/headers" (a :href "/geography/hypermedia/reference/headers"
:sx-get "/hypermedia/reference/headers" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/geography/hypermedia/reference/headers" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline" :class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Headers") (h3 :class "text-lg font-semibold text-violet-700 mb-1" "Headers")
(p :class "text-stone-600 text-sm" "Custom HTTP headers used to coordinate between the sx client and server.")) (p :class "text-stone-600 text-sm" "Custom HTTP headers used to coordinate between the sx client and server."))
(a :href "/hypermedia/reference/events" (a :href "/geography/hypermedia/reference/events"
:sx-get "/hypermedia/reference/events" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/geography/hypermedia/reference/events" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline" :class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Events") (h3 :class "text-lg font-semibold text-violet-700 mb-1" "Events")
(p :class "text-stone-600 text-sm" "DOM events fired during the sx request lifecycle.")) (p :class "text-stone-600 text-sm" "DOM events fired during the sx request lifecycle."))
(a :href "/hypermedia/reference/js-api" (a :href "/geography/hypermedia/reference/js-api"
:sx-get "/hypermedia/reference/js-api" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/geography/hypermedia/reference/js-api" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline" :class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "JS API") (h3 :class "text-lg font-semibold text-violet-700 mb-1" "JS API")

View File

@@ -3,232 +3,230 @@
;; @css aria-selected:bg-violet-200 aria-selected:text-violet-900 ;; @css aria-selected:bg-violet-200 aria-selected:text-violet-900
(define docs-nav-items (list (define docs-nav-items (list
(dict :label "Introduction" :href "/docs/introduction") (dict :label "Introduction" :href "/language/docs/introduction")
(dict :label "Getting Started" :href "/docs/getting-started") (dict :label "Getting Started" :href "/language/docs/getting-started")
(dict :label "Components" :href "/docs/components") (dict :label "Components" :href "/language/docs/components")
(dict :label "Evaluator" :href "/docs/evaluator") (dict :label "Evaluator" :href "/language/docs/evaluator")
(dict :label "Primitives" :href "/docs/primitives") (dict :label "Primitives" :href "/language/docs/primitives")
(dict :label "Special Forms" :href "/docs/special-forms") (dict :label "Special Forms" :href "/language/docs/special-forms")
(dict :label "Server Rendering" :href "/docs/server-rendering"))) (dict :label "Server Rendering" :href "/language/docs/server-rendering")))
(define reference-nav-items (list (define reference-nav-items (list
(dict :label "Attributes" :href "/hypermedia/reference/attributes") (dict :label "Attributes" :href "/geography/hypermedia/reference/attributes")
(dict :label "Headers" :href "/hypermedia/reference/headers") (dict :label "Headers" :href "/geography/hypermedia/reference/headers")
(dict :label "Events" :href "/hypermedia/reference/events") (dict :label "Events" :href "/geography/hypermedia/reference/events")
(dict :label "JS API" :href "/hypermedia/reference/js-api"))) (dict :label "JS API" :href "/geography/hypermedia/reference/js-api")))
(define protocols-nav-items (list (define protocols-nav-items (list
(dict :label "Wire Format" :href "/protocols/wire-format") (dict :label "Wire Format" :href "/applications/protocols/wire-format")
(dict :label "Fragments" :href "/protocols/fragments") (dict :label "Fragments" :href "/applications/protocols/fragments")
(dict :label "Resolver I/O" :href "/protocols/resolver-io") (dict :label "Resolver I/O" :href "/applications/protocols/resolver-io")
(dict :label "Internal Services" :href "/protocols/internal-services") (dict :label "Internal Services" :href "/applications/protocols/internal-services")
(dict :label "ActivityPub" :href "/protocols/activitypub") (dict :label "ActivityPub" :href "/applications/protocols/activitypub")
(dict :label "Future" :href "/protocols/future"))) (dict :label "Future" :href "/applications/protocols/future")))
(define examples-nav-items (list (define examples-nav-items (list
(dict :label "Click to Load" :href "/hypermedia/examples/click-to-load") (dict :label "Click to Load" :href "/geography/hypermedia/examples/click-to-load")
(dict :label "Form Submission" :href "/hypermedia/examples/form-submission") (dict :label "Form Submission" :href "/geography/hypermedia/examples/form-submission")
(dict :label "Polling" :href "/hypermedia/examples/polling") (dict :label "Polling" :href "/geography/hypermedia/examples/polling")
(dict :label "Delete Row" :href "/hypermedia/examples/delete-row") (dict :label "Delete Row" :href "/geography/hypermedia/examples/delete-row")
(dict :label "Inline Edit" :href "/hypermedia/examples/inline-edit") (dict :label "Inline Edit" :href "/geography/hypermedia/examples/inline-edit")
(dict :label "OOB Swaps" :href "/hypermedia/examples/oob-swaps") (dict :label "OOB Swaps" :href "/geography/hypermedia/examples/oob-swaps")
(dict :label "Lazy Loading" :href "/hypermedia/examples/lazy-loading") (dict :label "Lazy Loading" :href "/geography/hypermedia/examples/lazy-loading")
(dict :label "Infinite Scroll" :href "/hypermedia/examples/infinite-scroll") (dict :label "Infinite Scroll" :href "/geography/hypermedia/examples/infinite-scroll")
(dict :label "Progress Bar" :href "/hypermedia/examples/progress-bar") (dict :label "Progress Bar" :href "/geography/hypermedia/examples/progress-bar")
(dict :label "Active Search" :href "/hypermedia/examples/active-search") (dict :label "Active Search" :href "/geography/hypermedia/examples/active-search")
(dict :label "Inline Validation" :href "/hypermedia/examples/inline-validation") (dict :label "Inline Validation" :href "/geography/hypermedia/examples/inline-validation")
(dict :label "Value Select" :href "/hypermedia/examples/value-select") (dict :label "Value Select" :href "/geography/hypermedia/examples/value-select")
(dict :label "Reset on Submit" :href "/hypermedia/examples/reset-on-submit") (dict :label "Reset on Submit" :href "/geography/hypermedia/examples/reset-on-submit")
(dict :label "Edit Row" :href "/hypermedia/examples/edit-row") (dict :label "Edit Row" :href "/geography/hypermedia/examples/edit-row")
(dict :label "Bulk Update" :href "/hypermedia/examples/bulk-update") (dict :label "Bulk Update" :href "/geography/hypermedia/examples/bulk-update")
(dict :label "Swap Positions" :href "/hypermedia/examples/swap-positions") (dict :label "Swap Positions" :href "/geography/hypermedia/examples/swap-positions")
(dict :label "Select Filter" :href "/hypermedia/examples/select-filter") (dict :label "Select Filter" :href "/geography/hypermedia/examples/select-filter")
(dict :label "Tabs" :href "/hypermedia/examples/tabs") (dict :label "Tabs" :href "/geography/hypermedia/examples/tabs")
(dict :label "Animations" :href "/hypermedia/examples/animations") (dict :label "Animations" :href "/geography/hypermedia/examples/animations")
(dict :label "Dialogs" :href "/hypermedia/examples/dialogs") (dict :label "Dialogs" :href "/geography/hypermedia/examples/dialogs")
(dict :label "Keyboard Shortcuts" :href "/hypermedia/examples/keyboard-shortcuts") (dict :label "Keyboard Shortcuts" :href "/geography/hypermedia/examples/keyboard-shortcuts")
(dict :label "PUT / PATCH" :href "/hypermedia/examples/put-patch") (dict :label "PUT / PATCH" :href "/geography/hypermedia/examples/put-patch")
(dict :label "JSON Encoding" :href "/hypermedia/examples/json-encoding") (dict :label "JSON Encoding" :href "/geography/hypermedia/examples/json-encoding")
(dict :label "Vals & Headers" :href "/hypermedia/examples/vals-and-headers") (dict :label "Vals & Headers" :href "/geography/hypermedia/examples/vals-and-headers")
(dict :label "Loading States" :href "/hypermedia/examples/loading-states") (dict :label "Loading States" :href "/geography/hypermedia/examples/loading-states")
(dict :label "Request Abort" :href "/hypermedia/examples/sync-replace") (dict :label "Request Abort" :href "/geography/hypermedia/examples/sync-replace")
(dict :label "Retry" :href "/hypermedia/examples/retry"))) (dict :label "Retry" :href "/geography/hypermedia/examples/retry")))
(define cssx-nav-items (list (define cssx-nav-items (list
(dict :label "Overview" :href "/cssx/") (dict :label "Overview" :href "/applications/cssx/")
(dict :label "Patterns" :href "/cssx/patterns") (dict :label "Patterns" :href "/applications/cssx/patterns")
(dict :label "Delivery" :href "/cssx/delivery") (dict :label "Delivery" :href "/applications/cssx/delivery")
(dict :label "Async CSS" :href "/cssx/async") (dict :label "Async CSS" :href "/applications/cssx/async")
(dict :label "Live Styles" :href "/cssx/live") (dict :label "Live Styles" :href "/applications/cssx/live")
(dict :label "Comparisons" :href "/cssx/comparisons") (dict :label "Comparisons" :href "/applications/cssx/comparisons")
(dict :label "Philosophy" :href "/cssx/philosophy"))) (dict :label "Philosophy" :href "/applications/cssx/philosophy")))
(define essays-nav-items (list (define essays-nav-items (list
(dict :label "Why S-Expressions" :href "/essays/why-sexps" (dict :label "Why S-Expressions" :href "/etc/essays/why-sexps"
:summary "Why SX uses s-expressions instead of HTML templates, JSX, or any other syntax.") :summary "Why SX uses s-expressions instead of HTML templates, JSX, or any other syntax.")
(dict :label "The htmx/React Hybrid" :href "/essays/htmx-react-hybrid" (dict :label "The htmx/React Hybrid" :href "/etc/essays/htmx-react-hybrid"
:summary "How SX combines the server-driven simplicity of htmx with the component model of React.") :summary "How SX combines the server-driven simplicity of htmx with the component model of React.")
(dict :label "On-Demand CSS" :href "/essays/on-demand-css" (dict :label "On-Demand CSS" :href "/etc/essays/on-demand-css"
:summary "How SX delivers only the CSS each page needs — server scans rendered classes, sends the delta.") :summary "How SX delivers only the CSS each page needs — server scans rendered classes, sends the delta.")
(dict :label "Client Reactivity" :href "/essays/client-reactivity" (dict :label "Client Reactivity" :href "/etc/essays/client-reactivity"
:summary "Reactive UI updates without a virtual DOM, diffing library, or build step.") :summary "Reactive UI updates without a virtual DOM, diffing library, or build step.")
(dict :label "SX Native" :href "/essays/sx-native" (dict :label "SX Native" :href "/etc/essays/sx-native"
:summary "Extending SX beyond the browser — native desktop and mobile rendering from the same source.") :summary "Extending SX beyond the browser — native desktop and mobile rendering from the same source.")
(dict :label "Tail-Call Optimization" :href "/essays/tail-call-optimization" (dict :label "Tail-Call Optimization" :href "/etc/essays/tail-call-optimization"
:summary "How SX implements proper tail calls via trampolining in a language that doesn't have them.") :summary "How SX implements proper tail calls via trampolining in a language that doesn't have them.")
(dict :label "Continuations" :href "/essays/continuations" (dict :label "Continuations" :href "/etc/essays/continuations"
:summary "First-class continuations in a tree-walking evaluator — theory and implementation.") :summary "First-class continuations in a tree-walking evaluator — theory and implementation.")
(dict :label "The Reflexive Web" :href "/essays/reflexive-web" (dict :label "The Reflexive Web" :href "/etc/essays/reflexive-web"
:summary "A web where pages can inspect, modify, and extend their own rendering pipeline.") :summary "A web where pages can inspect, modify, and extend their own rendering pipeline.")
(dict :label "Server Architecture" :href "/essays/server-architecture" (dict :label "Server Architecture" :href "/etc/essays/server-architecture"
:summary "How SX enforces the boundary between host and embedded language, and what it looks like across targets.") :summary "How SX enforces the boundary between host and embedded language, and what it looks like across targets.")
(dict :label "Separate your Own Concerns" :href "/essays/separation-of-concerns" (dict :label "Separate your Own Concerns" :href "/etc/essays/separation-of-concerns"
:summary "The web's HTML/CSS/JS split separates the framework's concerns, not your application's. Real separation is domain-specific.") :summary "The web's HTML/CSS/JS split separates the framework's concerns, not your application's. Real separation is domain-specific.")
(dict :label "SX and AI" :href "/essays/sx-and-ai" (dict :label "SX and AI" :href "/etc/essays/sx-and-ai"
:summary "Why s-expressions are the most AI-friendly representation for web interfaces.") :summary "Why s-expressions are the most AI-friendly representation for web interfaces.")
(dict :label "There Is No Alternative" :href "/essays/no-alternative" (dict :label "There Is No Alternative" :href "/etc/essays/no-alternative"
:summary "Every attempt to escape s-expressions leads back to s-expressions. This is not an accident.") :summary "Every attempt to escape s-expressions leads back to s-expressions. This is not an accident.")
(dict :label "sx sucks" :href "/essays/sx-sucks" (dict :label "sx sucks" :href "/etc/essays/sx-sucks"
:summary "An honest accounting of everything wrong with SX and why you probably shouldn't use it.") :summary "An honest accounting of everything wrong with SX and why you probably shouldn't use it.")
(dict :label "Tools for Fools" :href "/essays/zero-tooling" (dict :label "Tools for Fools" :href "/etc/essays/zero-tooling"
:summary "SX was built without a code editor. No IDE, no build tools, no linters, no bundlers. What zero-tooling web development looks like.") :summary "SX was built without a code editor. No IDE, no build tools, no linters, no bundlers. What zero-tooling web development looks like.")
(dict :label "React is Hypermedia" :href "/essays/react-is-hypermedia" (dict :label "React is Hypermedia" :href "/etc/essays/react-is-hypermedia"
:summary "A React Island is a hypermedia control. Its behavior is specified in SX.") :summary "A React Island is a hypermedia control. Its behavior is specified in SX.")
(dict :label "The Hegelian Synthesis" :href "/essays/hegelian-synthesis" (dict :label "The Hegelian Synthesis" :href "/etc/essays/hegelian-synthesis"
:summary "On the dialectical resolution of the hypertext/reactive contradiction. Thesis: the server renders. Antithesis: the client reacts. Synthesis: the island in the lake."))) :summary "On the dialectical resolution of the hypertext/reactive contradiction. Thesis: the server renders. Antithesis: the client reacts. Synthesis: the island in the lake.")))
(define philosophy-nav-items (list (define philosophy-nav-items (list
(dict :label "The SX Manifesto" :href "/philosophy/sx-manifesto" (dict :label "The SX Manifesto" :href "/etc/philosophy/sx-manifesto"
:summary "The design principles behind SX: simplicity, self-hosting, and s-expressions all the way down.") :summary "The design principles behind SX: simplicity, self-hosting, and s-expressions all the way down.")
(dict :label "Strange Loops" :href "/philosophy/godel-escher-bach" (dict :label "Strange Loops" :href "/etc/philosophy/godel-escher-bach"
:summary "Self-reference, and the tangled hierarchy of a language that defines itself.") :summary "Self-reference, and the tangled hierarchy of a language that defines itself.")
(dict :label "SX and Wittgenstein" :href "/philosophy/wittgenstein" (dict :label "SX and Wittgenstein" :href "/etc/philosophy/wittgenstein"
:summary "The limits of my language are the limits of my world — Wittgenstein's philosophy and what it means for SX.") :summary "The limits of my language are the limits of my world — Wittgenstein's philosophy and what it means for SX.")
(dict :label "SX and Dennett" :href "/philosophy/dennett" (dict :label "SX and Dennett" :href "/etc/philosophy/dennett"
:summary "Real patterns, intentional stance, and multiple drafts — Dennett's philosophy of mind as a framework for understanding SX.") :summary "Real patterns, intentional stance, and multiple drafts — Dennett's philosophy of mind as a framework for understanding SX.")
(dict :label "S-Existentialism" :href "/philosophy/existentialism" (dict :label "S-Existentialism" :href "/etc/philosophy/existentialism"
:summary "Existence precedes essence — Sartre, Camus, and the absurd freedom of writing a Lisp for the web."))) :summary "Existence precedes essence — Sartre, Camus, and the absurd freedom of writing a Lisp for the web.")))
(define specs-nav-items (list (define specs-nav-items (list
(dict :label "Architecture" :href "/specs/") (dict :label "Architecture" :href "/language/specs/")
(dict :label "Core" :href "/specs/core") (dict :label "Core" :href "/language/specs/core")
(dict :label "Parser" :href "/specs/parser") (dict :label "Parser" :href "/language/specs/parser")
(dict :label "Evaluator" :href "/specs/evaluator") (dict :label "Evaluator" :href "/language/specs/evaluator")
(dict :label "Primitives" :href "/specs/primitives") (dict :label "Primitives" :href "/language/specs/primitives")
(dict :label "Special Forms" :href "/specs/special-forms") (dict :label "Special Forms" :href "/language/specs/special-forms")
(dict :label "Renderer" :href "/specs/renderer") (dict :label "Renderer" :href "/language/specs/renderer")
(dict :label "Adapters" :href "/specs/adapters") (dict :label "Adapters" :href "/language/specs/adapters")
(dict :label "DOM Adapter" :href "/specs/adapter-dom") (dict :label "DOM Adapter" :href "/language/specs/adapter-dom")
(dict :label "HTML Adapter" :href "/specs/adapter-html") (dict :label "HTML Adapter" :href "/language/specs/adapter-html")
(dict :label "SX Wire Adapter" :href "/specs/adapter-sx") (dict :label "SX Wire Adapter" :href "/language/specs/adapter-sx")
(dict :label "Browser" :href "/specs/browser") (dict :label "Browser" :href "/language/specs/browser")
(dict :label "SxEngine" :href "/specs/engine") (dict :label "SxEngine" :href "/language/specs/engine")
(dict :label "Orchestration" :href "/specs/orchestration") (dict :label "Orchestration" :href "/language/specs/orchestration")
(dict :label "Boot" :href "/specs/boot") (dict :label "Boot" :href "/language/specs/boot")
(dict :label "Continuations" :href "/specs/continuations") (dict :label "Continuations" :href "/language/specs/continuations")
(dict :label "call/cc" :href "/specs/callcc") (dict :label "call/cc" :href "/language/specs/callcc")
(dict :label "Deps" :href "/specs/deps") (dict :label "Deps" :href "/language/specs/deps")
(dict :label "Router" :href "/specs/router"))) (dict :label "Router" :href "/language/specs/router")))
(define testing-nav-items (list (define testing-nav-items (list
(dict :label "Overview" :href "/testing/") (dict :label "Overview" :href "/language/testing/")
(dict :label "Evaluator" :href "/testing/eval") (dict :label "Evaluator" :href "/language/testing/eval")
(dict :label "Parser" :href "/testing/parser") (dict :label "Parser" :href "/language/testing/parser")
(dict :label "Router" :href "/testing/router") (dict :label "Router" :href "/language/testing/router")
(dict :label "Renderer" :href "/testing/render") (dict :label "Renderer" :href "/language/testing/render")
(dict :label "Dependencies" :href "/testing/deps") (dict :label "Dependencies" :href "/language/testing/deps")
(dict :label "Engine" :href "/testing/engine") (dict :label "Engine" :href "/language/testing/engine")
(dict :label "Orchestration" :href "/testing/orchestration") (dict :label "Orchestration" :href "/language/testing/orchestration")
(dict :label "Runners" :href "/testing/runners"))) (dict :label "Runners" :href "/language/testing/runners")))
(define isomorphism-nav-items (list (define isomorphism-nav-items (list
(dict :label "Roadmap" :href "/isomorphism/") (dict :label "Roadmap" :href "/geography/isomorphism/")
(dict :label "Bundle Analyzer" :href "/isomorphism/bundle-analyzer") (dict :label "Bundle Analyzer" :href "/geography/isomorphism/bundle-analyzer")
(dict :label "Routing Analyzer" :href "/isomorphism/routing-analyzer") (dict :label "Routing Analyzer" :href "/geography/isomorphism/routing-analyzer")
(dict :label "Data Test" :href "/isomorphism/data-test") (dict :label "Data Test" :href "/geography/isomorphism/data-test")
(dict :label "Async IO" :href "/isomorphism/async-io") (dict :label "Async IO" :href "/geography/isomorphism/async-io")
(dict :label "Streaming" :href "/isomorphism/streaming") (dict :label "Streaming" :href "/geography/isomorphism/streaming")
(dict :label "Affinity" :href "/isomorphism/affinity") (dict :label "Affinity" :href "/geography/isomorphism/affinity")
(dict :label "Optimistic" :href "/isomorphism/optimistic") (dict :label "Optimistic" :href "/geography/isomorphism/optimistic")
(dict :label "Offline" :href "/isomorphism/offline"))) (dict :label "Offline" :href "/geography/isomorphism/offline")))
(define plans-nav-items (list (define plans-nav-items (list
(dict :label "Status" :href "/plans/status" (dict :label "Status" :href "/etc/plans/status"
:summary "Audit of all plans — what's done, what's in progress, and what remains.") :summary "Audit of all plans — what's done, what's in progress, and what remains.")
(dict :label "Reader Macros" :href "/plans/reader-macros" (dict :label "Reader Macros" :href "/etc/plans/reader-macros"
:summary "Extensible parse-time transformations via # dispatch — datum comments, raw strings, and quote shorthand.") :summary "Extensible parse-time transformations via # dispatch — datum comments, raw strings, and quote shorthand.")
(dict :label "Reader Macro Demo" :href "/plans/reader-macro-demo" (dict :label "Reader Macro Demo" :href "/etc/plans/reader-macro-demo"
:summary "Live demo: #z3 translates SX spec declarations to SMT-LIB verification conditions.") :summary "Live demo: #z3 translates SX spec declarations to SMT-LIB verification conditions.")
(dict :label "Theorem Prover" :href "/plans/theorem-prover" (dict :label "Theorem Prover" :href "/etc/plans/theorem-prover"
:summary "prove.sx — constraint solver and property prover for SX primitives, written in SX.") :summary "prove.sx — constraint solver and property prover for SX primitives, written in SX.")
(dict :label "Self-Hosting Bootstrapper" :href "/plans/self-hosting-bootstrapper" (dict :label "Self-Hosting Bootstrapper" :href "/etc/plans/self-hosting-bootstrapper"
:summary "py.sx — an SX-to-Python translator written in SX. Complete: G0 == G1, 128/128 defines match.") :summary "py.sx — an SX-to-Python translator written in SX. Complete: G0 == G1, 128/128 defines match.")
(dict :label "JS Bootstrapper" :href "/plans/js-bootstrapper" (dict :label "JS Bootstrapper" :href "/etc/plans/js-bootstrapper"
:summary "js.sx — SX-to-JavaScript translator + ahead-of-time component compiler. Zero-runtime static sites.") :summary "js.sx — SX-to-JavaScript translator + ahead-of-time component compiler. Zero-runtime static sites.")
(dict :label "SX-Activity" :href "/plans/sx-activity" (dict :label "SX-Activity" :href "/etc/plans/sx-activity"
:summary "A new web built on SX — executable content, shared components, parsers, and logic on IPFS, provenance on Bitcoin, all running within your own security context.") :summary "A new web built on SX — executable content, shared components, parsers, and logic on IPFS, provenance on Bitcoin, all running within your own security context.")
(dict :label "Predictive Prefetching" :href "/plans/predictive-prefetch" (dict :label "Predictive Prefetching" :href "/etc/plans/predictive-prefetch"
:summary "Prefetch missing component definitions before the user clicks — hover a link, fetch its deps, navigate client-side.") :summary "Prefetch missing component definitions before the user clicks — hover a link, fetch its deps, navigate client-side.")
(dict :label "Content-Addressed Components" :href "/plans/content-addressed-components" (dict :label "Content-Addressed Components" :href "/etc/plans/content-addressed-components"
:summary "Components identified by CID, stored on IPFS, fetched from anywhere. Canonical serialization, content verification, federated sharing.") :summary "Components identified by CID, stored on IPFS, fetched from anywhere. Canonical serialization, content verification, federated sharing.")
(dict :label "Environment Images" :href "/plans/environment-images" (dict :label "Environment Images" :href "/etc/plans/environment-images"
:summary "Serialize evaluated environments as content-addressed images. Spec CID → image CID → every endpoint is fully executable and verifiable.") :summary "Serialize evaluated environments as content-addressed images. Spec CID → image CID → every endpoint is fully executable and verifiable.")
(dict :label "Runtime Slicing" :href "/plans/runtime-slicing" (dict :label "Runtime Slicing" :href "/etc/plans/runtime-slicing"
:summary "Tier the client runtime by need: L0 hypermedia (~5KB), L1 DOM ops (~8KB), L2 islands (~15KB), L3 full eval (~44KB). Sliced by slice.sx, translated by js.sx.") :summary "Tier the client runtime by need: L0 hypermedia (~5KB), L1 DOM ops (~8KB), L2 islands (~15KB), L3 full eval (~44KB). Sliced by slice.sx, translated by js.sx.")
(dict :label "Typed SX" :href "/plans/typed-sx" (dict :label "Typed SX" :href "/etc/plans/typed-sx"
:summary "Gradual type system for SX. Optional annotations, checked at registration time, zero runtime cost. types.sx — specced, bootstrapped, catches composition errors.") :summary "Gradual type system for SX. Optional annotations, checked at registration time, zero runtime cost. types.sx — specced, bootstrapped, catches composition errors.")
(dict :label "Nav Redesign" :href "/plans/nav-redesign" (dict :label "Nav Redesign" :href "/etc/plans/nav-redesign"
:summary "Replace menu bars with vertical breadcrumb navigation. Logo → section → page, arrows for siblings, children below. No dropdowns, no hamburger, infinite depth.") :summary "Replace menu bars with vertical breadcrumb navigation. Logo → section → page, arrows for siblings, children below. No dropdowns, no hamburger, infinite depth.")
(dict :label "Fragment Protocol" :href "/plans/fragment-protocol" (dict :label "Fragment Protocol" :href "/etc/plans/fragment-protocol"
:summary "Structured sexp request/response for cross-service component transfer.") :summary "Structured sexp request/response for cross-service component transfer.")
(dict :label "Glue Decoupling" :href "/plans/glue-decoupling" (dict :label "Glue Decoupling" :href "/etc/plans/glue-decoupling"
:summary "Eliminate all cross-app model imports via glue service layer.") :summary "Eliminate all cross-app model imports via glue service layer.")
(dict :label "Social Sharing" :href "/plans/social-sharing" (dict :label "Social Sharing" :href "/etc/plans/social-sharing"
:summary "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon.") :summary "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon.")
(dict :label "SX CI Pipeline" :href "/plans/sx-ci" (dict :label "SX CI Pipeline" :href "/etc/plans/sx-ci"
:summary "Build, test, and deploy in s-expressions — CI pipelines as SX components.") :summary "Build, test, and deploy in s-expressions — CI pipelines as SX components.")
(dict :label "Live Streaming" :href "/plans/live-streaming" (dict :label "Live Streaming" :href "/etc/plans/live-streaming"
:summary "SSE and WebSocket transports for re-resolving suspense slots after initial page load — live data, real-time collaboration.") :summary "SSE and WebSocket transports for re-resolving suspense slots after initial page load — live data, real-time collaboration.")
(dict :label "sx-web Platform" :href "/plans/sx-web-platform" (dict :label "sx-web Platform" :href "/etc/plans/sx-web-platform"
:summary "sx-web.org as online development platform — embedded Claude Code, IPFS storage, sx-activity publishing, sx-ci testing. Author, stage, test, deploy from the browser.") :summary "sx-web.org as online development platform — embedded Claude Code, IPFS storage, sx-activity publishing, sx-ci testing. Author, stage, test, deploy from the browser.")
(dict :label "sx-forge" :href "/plans/sx-forge" (dict :label "sx-forge" :href "/etc/plans/sx-forge"
:summary "Git forge in SX — repositories, issues, pull requests, CI, permissions, and federation. Configuration as macros, diffs as components.") :summary "Git forge in SX — repositories, issues, pull requests, CI, permissions, and federation. Configuration as macros, diffs as components.")
(dict :label "sx-swarm" :href "/plans/sx-swarm" (dict :label "sx-swarm" :href "/etc/plans/sx-swarm"
:summary "Container orchestration in SX — service definitions, environment macros, deploy pipelines. Replace YAML with a real language.") :summary "Container orchestration in SX — service definitions, environment macros, deploy pipelines. Replace YAML with a real language.")
(dict :label "sx-proxy" :href "/plans/sx-proxy" (dict :label "sx-proxy" :href "/etc/plans/sx-proxy"
:summary "Reverse proxy in SX — routes, TLS, middleware chains, load balancing. Macros generate config from the same service definitions as the orchestrator.") :summary "Reverse proxy in SX — routes, TLS, middleware chains, load balancing. Macros generate config from the same service definitions as the orchestrator.")
(dict :label "Async Eval Convergence" :href "/plans/async-eval-convergence" (dict :label "Async Eval Convergence" :href "/etc/plans/async-eval-convergence"
:summary "Eliminate hand-written evaluators — bootstrap async_eval.py from the spec via an async adapter layer. One spec, one truth, zero divergence.") :summary "Eliminate hand-written evaluators — bootstrap async_eval.py from the spec via an async adapter layer. One spec, one truth, zero divergence.")
(dict :label "WASM Bytecode VM" :href "/plans/wasm-bytecode-vm" (dict :label "WASM Bytecode VM" :href "/etc/plans/wasm-bytecode-vm"
:summary "Compile SX to bytecode, run in a Rust/WASM VM. Compact wire format, no parse overhead, near-native speed, DOM via JS bindings.") :summary "Compile SX to bytecode, run in a Rust/WASM VM. Compact wire format, no parse overhead, near-native speed, DOM via JS bindings.")
(dict :label "Generative SX" :href "/plans/generative-sx" (dict :label "Generative SX" :href "/etc/plans/generative-sx"
:summary "Programs that write themselves as they run — self-compiling specs, runtime self-extension, generative testing, seed networks.") :summary "Programs that write themselves as they run — self-compiling specs, runtime self-extension, generative testing, seed networks.")
(dict :label "Art DAG on SX" :href "/plans/art-dag-sx" (dict :label "Art DAG on SX" :href "/etc/plans/art-dag-sx"
:summary "SX endpoints as portals into media processing environments — recipes as programs, split execution across GPU/cache/live boundaries, streaming AV output."))) :summary "SX endpoints as portals into media processing environments — recipes as programs, split execution across GPU/cache/live boundaries, streaming AV output.")))
(define reactive-islands-nav-items (list (define reactive-islands-nav-items (list
(dict :label "Overview" :href "/reactive/" (dict :label "Overview" :href "/geography/reactive/"
:summary "Architecture, four levels (L0-L3), and current implementation status.") :summary "Architecture, four levels (L0-L3), and current implementation status.")
(dict :label "Demo" :href "/reactive/demo" (dict :label "Demo" :href "/geography/reactive/demo"
:summary "Live demonstration of signals, computed, effects, batch, and defisland — all transpiled from spec.") :summary "Live demonstration of signals, computed, effects, batch, and defisland — all transpiled from spec.")
(dict :label "Event Bridge" :href "/reactive/event-bridge" (dict :label "Event Bridge" :href "/geography/reactive/event-bridge"
:summary "DOM events for htmx lake → island communication. Server-rendered buttons dispatch custom events that island effects listen for.") :summary "DOM events for htmx lake → island communication. Server-rendered buttons dispatch custom events that island effects listen for.")
(dict :label "Named Stores" :href "/reactive/named-stores" (dict :label "Named Stores" :href "/geography/reactive/named-stores"
:summary "Page-level signal containers via def-store/use-store — persist across island destruction/recreation.") :summary "Page-level signal containers via def-store/use-store — persist across island destruction/recreation.")
(dict :label "Plan" :href "/reactive/plan" (dict :label "Plan" :href "/geography/reactive/plan"
:summary "The full design document — rendering boundary, state flow, signal primitives, island lifecycle.") :summary "The full design document — rendering boundary, state flow, signal primitives, island lifecycle.")
(dict :label "Marshes" :href "/reactive/marshes" (dict :label "Phase 2" :href "/geography/reactive/phase2"
:summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted.")
(dict :label "Phase 2" :href "/reactive/phase2"
:summary "Input binding, keyed lists, reactive class/style, refs, portals, error boundaries, suspense, transitions."))) :summary "Input binding, keyed lists, reactive class/style, refs, portals, error boundaries, suspense, transitions.")))
(define bootstrappers-nav-items (list (define bootstrappers-nav-items (list
(dict :label "Overview" :href "/bootstrappers/") (dict :label "Overview" :href "/language/bootstrappers/")
(dict :label "JavaScript" :href "/bootstrappers/javascript") (dict :label "JavaScript" :href "/language/bootstrappers/javascript")
(dict :label "Python" :href "/bootstrappers/python") (dict :label "Python" :href "/language/bootstrappers/python")
(dict :label "Self-Hosting (py.sx)" :href "/bootstrappers/self-hosting") (dict :label "Self-Hosting (py.sx)" :href "/language/bootstrappers/self-hosting")
(dict :label "Self-Hosting JS (js.sx)" :href "/bootstrappers/self-hosting-js") (dict :label "Self-Hosting JS (js.sx)" :href "/language/bootstrappers/self-hosting-js")
(dict :label "Page Helpers" :href "/bootstrappers/page-helpers"))) (dict :label "Page Helpers" :href "/language/bootstrappers/page-helpers")))
;; Spec file registry — canonical metadata for spec viewer pages. ;; Spec file registry — canonical metadata for spec viewer pages.
;; Python only handles file I/O (read-spec-file); all metadata lives here. ;; Python only handles file I/O (read-spec-file); all metadata lives here.
@@ -327,21 +325,31 @@
(define sx-nav-tree (define sx-nav-tree
{:label "sx" :href "/" {:label "sx" :href "/"
:children (list :children (list
{:label "Reactive" :href "/reactive/" :children reactive-islands-nav-items} {:label "Geography" :href "/geography/"
{:label "Hypermedia" :href "/hypermedia/"
:children (list :children (list
{:label "Reference" :href "/hypermedia/reference/" :children reference-nav-items} {:label "Reactive Islands" :href "/geography/reactive/" :children reactive-islands-nav-items}
{:label "Examples" :href "/hypermedia/examples/" :children examples-nav-items})} {:label "Hypermedia Lakes" :href "/geography/hypermedia/"
{:label "Docs" :href "/docs/" :children docs-nav-items} :children (list
{:label "CSSX" :href "/cssx/" :children cssx-nav-items} {:label "Reference" :href "/geography/hypermedia/reference/" :children reference-nav-items}
{:label "Protocols" :href "/protocols/" :children protocols-nav-items} {:label "Examples" :href "/geography/hypermedia/examples/" :children examples-nav-items})}
{:label "Essays" :href "/essays/" :children essays-nav-items} {:label "Marshes" :href "/geography/marshes/"
{:label "Philosophy" :href "/philosophy/" :children philosophy-nav-items} :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted."}
{:label "Specs" :href "/specs/" :children specs-nav-items} {:label "Isomorphism" :href "/geography/isomorphism/" :children isomorphism-nav-items})}
{:label "Bootstrappers" :href "/bootstrappers/" :children bootstrappers-nav-items} {:label "Language" :href "/language/"
{:label "Testing" :href "/testing/" :children testing-nav-items} :children (list
{:label "Isomorphism" :href "/isomorphism/" :children isomorphism-nav-items} {:label "Docs" :href "/language/docs/" :children docs-nav-items}
{:label "Plans" :href "/plans/" :children plans-nav-items})}) {:label "Specs" :href "/language/specs/" :children specs-nav-items}
{:label "Bootstrappers" :href "/language/bootstrappers/" :children bootstrappers-nav-items}
{:label "Testing" :href "/language/testing/" :children testing-nav-items})}
{:label "Applications" :href "/applications/"
:children (list
{:label "CSSX" :href "/applications/cssx/" :children cssx-nav-items}
{:label "Protocols" :href "/applications/protocols/" :children protocols-nav-items})}
{:label "Etc" :href "/etc/"
:children (list
{:label "Essays" :href "/etc/essays/" :children essays-nav-items}
{:label "Philosophy" :href "/etc/philosophy/" :children philosophy-nav-items}
{:label "Plans" :href "/etc/plans/" :children plans-nav-items})})})
;; Resolve a URL path to a nav trail + children. ;; Resolve a URL path to a nav trail + children.
;; Returns {:trail [{:node N :siblings S} ...] :children [...] :depth N} ;; Returns {:trail [{:node N :siblings S} ...] :children [...] :depth N}
@@ -384,11 +392,15 @@
item))) item)))
items) items)
;; Path contains section: /plans/typed-sx matches section with /plans/ children ;; Path contains section: /plans/typed-sx matches section with /plans/ children
;; Also handles prefix matching on children for nested groups (e.g. Geography → Reactive Islands)
(some (fn (item) (some (fn (item)
(let ((children (get item "children"))) (let ((children (get item "children")))
(when children (when children
(when (some (fn (child) (when (some (fn (child)
(= (get child "href") path)) (let ((href (get child "href")))
(or (= href path)
(and (ends-with? href "/")
(starts-with? path href)))))
children) children)
item)))) item))))
items)))) items))))

View File

@@ -78,13 +78,13 @@
;; Post-process sf-result: count forms per category ;; Post-process sf-result: count forms per category
(for-each (fn (k) (for-each (fn (k)
(let ((count (len (get sf-result k)))) (dict-set! sf-cats k (len (get sf-result k))))
(set! sf-cats (assoc sf-cats k count))
(set! sf-total (+ sf-total count))))
(keys sf-result)) (keys sf-result))
(reset! results (reset! results
{"sf-cats" sf-cats "sf-total" sf-total "sf-ms" sf-ms {"sf-cats" sf-cats
"sf-total" (reduce (fn (acc k) (+ acc (get sf-cats k))) 0 (keys sf-cats))
"sf-ms" sf-ms
"ref-sample" ref-sample "ref-ms" ref-ms "ref-sample" ref-sample "ref-ms" ref-ms
"attr-result" attr-result "attr-ms" attr-ms "attr-result" attr-result "attr-ms" attr-ms
"comp-result" comp-result "comp-ms" comp-ms "comp-result" comp-result "comp-ms" comp-ms

View File

@@ -279,8 +279,8 @@
(~doc-subsection :title "CID References in Page Registry" (~doc-subsection :title "CID References in Page Registry"
(p "The page registry (shipped to the client as " (code "<script type=\"text/sx-pages\">") ") currently lists deps by name. Extend to include CIDs:") (p "The page registry (shipped to the client as " (code "<script type=\"text/sx-pages\">") ") currently lists deps by name. Extend to include CIDs:")
(~doc-code :code (highlight "{:name \"docs-page\" :path \"/docs/<slug>\"\n :auth \"public\" :has-data false\n :deps ({:name \"~essay-foo\" :cid \"bafy...essay\"}\n {:name \"~doc-code\" :cid \"bafy...doccode\"})\n :content \"(case slug ...)\" :closure {}}" "lisp")) (~doc-code :code (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :deps ({:name \"~essay-foo\" :cid \"bafy...essay\"}\n {:name \"~doc-code\" :cid \"bafy...doccode\"})\n :content \"(case slug ...)\" :closure {}}" "lisp"))
(p "The " (a :href "/plans/predictive-prefetch" :class "text-violet-700 underline" "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint.")) (p "The " (a :href "/etc/plans/predictive-prefetch" :class "text-violet-700 underline" "predictive prefetch system") " uses these CIDs to fetch components from the resolution cascade rather than only from the origin server's " (code "/sx/components") " endpoint."))
(~doc-subsection :title "SX Response Component Headers" (~doc-subsection :title "SX Response Component Headers"
(p "Currently, " (code "SX-Components") " header lists loaded component names. Extend to support CIDs:") (p "Currently, " (code "SX-Components") " header lists loaded component names. Extend to support CIDs:")
@@ -407,9 +407,9 @@
(~doc-section :title "Relationships" :id "relationships" (~doc-section :title "Relationships" :id "relationships"
(p "This plan is the foundation for several other plans and roadmaps:") (p "This plan is the foundation for several other plans and roadmaps:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/plans/sx-activity" :class "text-violet-700 underline" "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.") (li (a :href "/etc/plans/sx-activity" :class "text-violet-700 underline" "SX-Activity") " Phase 2 (content-addressed components on IPFS) is a summary of this plan. This plan supersedes that section with full detail.")
(li (a :href "/plans/predictive-prefetch" :class "text-violet-700 underline" "Predictive prefetching") " gains CID-based resolution — the " (code "/sx/components") " endpoint and IPFS gateway become alternative resolution paths in the prefetch cascade.") (li (a :href "/etc/plans/predictive-prefetch" :class "text-violet-700 underline" "Predictive prefetching") " gains CID-based resolution — the " (code "/sx/components") " endpoint and IPFS gateway become alternative resolution paths in the prefetch cascade.")
(li (a :href "/plans/isomorphic-architecture" :class "text-violet-700 underline" "Isomorphic architecture") " Phase 1 (component distribution) is enhanced — CIDs make per-page bundles verifiable and cross-server shareable.") (li (a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "Isomorphic architecture") " Phase 1 (component distribution) is enhanced — CIDs make per-page bundles verifiable and cross-server shareable.")
(li "The SX-Activity vision of " (strong "serverless applications on IPFS") " depends entirely on this plan. Without content-addressed components, applications can't be pinned to IPFS as self-contained artifacts.")) (li "The SX-Activity vision of " (strong "serverless applications on IPFS") " depends entirely on this plan. Without content-addressed components, applications can't be pinned to IPFS as self-contained artifacts."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2" (div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "deps.sx (complete), boundary enforcement (complete), IPFS infrastructure (exists in artdag, needs wiring to web platform).")))))) (p :class "text-amber-800 text-sm" (strong "Depends on: ") "deps.sx (complete), boundary enforcement (complete), IPFS infrastructure (exists in artdag, needs wiring to web platform)."))))))

View File

@@ -278,11 +278,11 @@
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Canonical serialization") (td :class "px-3 py-2 text-stone-700" "Canonical serialization")
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started")) (td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started"))
(td :class "px-3 py-2" (a :href "/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 1")) (td :class "px-3 py-2" (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 1"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Component CIDs") (td :class "px-3 py-2 text-stone-700" "Component CIDs")
(td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started")) (td :class "px-3 py-2 text-stone-700" (span :class "text-red-700 font-medium" "Not started"))
(td :class "px-3 py-2" (a :href "/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 2")) (td :class "px-3 py-2" (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " Phase 2"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Purity verification") (td :class "px-3 py-2 text-stone-700" "Purity verification")
(td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete")) (td :class "px-3 py-2 text-stone-700" (span :class "text-green-700 font-medium" "Complete"))
@@ -301,4 +301,4 @@
(td :class "px-3 py-2 text-stone-600" "artdag L1/L2, IPFSPin model"))))) (td :class "px-3 py-2 text-stone-600" "artdag L1/L2, IPFSPin model")))))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2" (div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Builds on: ") (a :href "/plans/content-addressed-components" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/plans/self-hosting-bootstrapper" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/plans/sx-activity" :class "underline" "SX-Activity") " (serverless applications on IPFS)."))))) (p :class "text-amber-800 text-sm" (strong "Builds on: ") (a :href "/etc/plans/content-addressed-components" :class "underline" "Content-Addressed Components") " (canonical serialization + CIDs), " (a :href "/etc/plans/self-hosting-bootstrapper" :class "underline" "self-hosting bootstrappers") " (spec-first architecture). " (strong "Enables: ") (a :href "/etc/plans/sx-activity" :class "underline" "SX-Activity") " (serverless applications on IPFS).")))))

View File

@@ -7,7 +7,7 @@
(~doc-section :title "Context" :id "context" (~doc-section :title "Context" :id "context"
(p "SX has a working server-client pipeline: server evaluates pages with IO (DB, fragments), serializes as SX wire format, client parses and renders to DOM. The language and primitives are already isomorphic " (em "— same spec, same semantics, both sides.") " What's missing is the " (strong "plumbing") " that makes the boundary between server and client a sliding window rather than a fixed wall.") (p "SX has a working server-client pipeline: server evaluates pages with IO (DB, fragments), serializes as SX wire format, client parses and renders to DOM. The language and primitives are already isomorphic " (em "— same spec, same semantics, both sides.") " What's missing is the " (strong "plumbing") " that makes the boundary between server and client a sliding window rather than a fixed wall.")
(p "The key insight: " (strong "s-expressions can partially unfold on the server after IO, then finish unfolding on the client.") " The system knows which components have data fetches (via IO detection in " (a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx") "), resolves those server-side, and sends the rest as pure SX for client rendering. The boundary slides automatically based on what each component actually needs.")) (p "The key insight: " (strong "s-expressions can partially unfold on the server after IO, then finish unfolding on the client.") " The system knows which components have data fetches (via IO detection in " (a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx") "), resolves those server-side, and sends the rest as pure SX for client rendering. The boundary slides automatically based on what each component actually needs."))
(~doc-section :title "Current State" :id "current-state" (~doc-section :title "Current State" :id "current-state"
(ul :class "space-y-2 text-stone-700 list-disc pl-5" (ul :class "space-y-2 text-stone-700 list-disc pl-5"
@@ -32,8 +32,8 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4" (div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2" (div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx") (a :href "/language/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/isomorphism/bundle-analyzer" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer")) (a :href "/geography/isomorphism/bundle-analyzer" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer"))
(p :class "text-green-900 font-medium" "What it enables") (p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Per-page component bundles instead of sending every definition to every page. Smaller payloads, faster boot, better cache hit rates.")) (p :class "text-green-800" "Per-page component bundles instead of sending every definition to every page. Smaller payloads, faster boot, better cache hit rates."))
@@ -43,7 +43,7 @@
(~doc-subsection :title "Implementation" (~doc-subsection :title "Implementation"
(p "The dependency analysis algorithm is defined in " (p "The dependency analysis algorithm is defined in "
(a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx") (a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
" — a spec module bootstrapped to every host. Each host loads it via " (code "--spec-modules deps") " and provides 6 platform functions. The spec is the single source of truth; hosts are interchangeable.") " — a spec module bootstrapped to every host. Each host loads it via " (code "--spec-modules deps") " and provides 6 platform functions. The spec is the single source of truth; hosts are interchangeable.")
(div :class "space-y-4" (div :class "space-y-4"
@@ -85,7 +85,7 @@
(li "15 dedicated tests: scan, transitive closure, circular deps, compute-all, components-needed") (li "15 dedicated tests: scan, transitive closure, circular deps, compute-all, components-needed")
(li "Bootstrapped output verified on both host targets") (li "Bootstrapped output verified on both host targets")
(li "Full test suite passes with zero regressions") (li "Full test suite passes with zero regressions")
(li (a :href "/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app")))) (li (a :href "/geography/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows real per-page savings on this app"))))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
;; Phase 2 ;; Phase 2
@@ -96,14 +96,14 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4" (div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2" (div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx") (a :href "/language/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
(a :href "/isomorphism/bundle-analyzer" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer with IO")) (a :href "/geography/isomorphism/bundle-analyzer" :class "text-green-700 underline text-sm font-medium" "Live bundle analyzer with IO"))
(p :class "text-green-900 font-medium" "What it enables") (p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Automatic IO detection and selective expansion. Server expands IO-dependent components, serializes pure ones for client. Per-component intelligence replaces global toggle.")) (p :class "text-green-800" "Automatic IO detection and selective expansion. Server expands IO-dependent components, serializes pure ones for client. Per-component intelligence replaces global toggle."))
(~doc-subsection :title "IO Detection in the Spec" (~doc-subsection :title "IO Detection in the Spec"
(p "Five new functions in " (p "Five new functions in "
(a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx") (a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
" extend the Phase 1 walker to detect IO primitive references:") " extend the Phase 1 walker to detect IO primitive references:")
(div :class "space-y-4" (div :class "space-y-4"
@@ -145,7 +145,7 @@
(li "Pure components (HTML-only) classified pure with empty io_refs") (li "Pure components (HTML-only) classified pure with empty io_refs")
(li "Transitive IO detection: component calling ~other where ~other calls (current-user) → IO-dependent") (li "Transitive IO detection: component calling ~other where ~other calls (current-user) → IO-dependent")
(li "Bootstrapped to both hosts (sx_ref.py + sx-ref.js)") (li "Bootstrapped to both hosts (sx_ref.py + sx-ref.js)")
(li (a :href "/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification")))) (li (a :href "/geography/isomorphism/bundle-analyzer" :class "text-violet-700 underline" "Live bundle analyzer") " shows per-page IO classification"))))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
;; Phase 3 ;; Phase 3
@@ -156,8 +156,8 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4" (div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2" (div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/specs/router" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx") (a :href "/language/specs/router" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
(a :href "/isomorphism/routing-analyzer" :class "text-green-700 underline text-sm font-medium" "Live routing analyzer")) (a :href "/geography/isomorphism/routing-analyzer" :class "text-green-700 underline text-sm font-medium" "Live routing analyzer"))
(p :class "text-green-900 font-medium" "What it enables") (p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "After initial page load, pure pages render instantly without server roundtrips. Client matches routes locally, evaluates content expressions with cached components, and only falls back to server for pages with :data dependencies.")) (p :class "text-green-800" "After initial page load, pure pages render instantly without server roundtrips. Client matches routes locally, evaluates content expressions with cached components, and only falls back to server for pages with :data dependencies."))
@@ -168,13 +168,13 @@
(div (div
(h4 :class "font-semibold text-stone-700" "1. Route matching spec (router.sx)") (h4 :class "font-semibold text-stone-700" "1. Route matching spec (router.sx)")
(p "New spec module with pure functions for Flask-style route pattern matching:") (p "New spec module with pure functions for Flask-style route pattern matching:")
(~doc-code :code (highlight "(define split-path-segments ;; \"/docs/hello\" → (\"docs\" \"hello\")\n(define parse-route-pattern ;; \"/docs/<slug>\" → segment descriptors\n(define match-route-segments ;; segments + pattern → params dict or nil\n(define find-matching-route ;; path + route table → first match" "lisp")) (~doc-code :code (highlight "(define split-path-segments ;; \"/language/docs/hello\" → (\"docs\" \"hello\")\n(define parse-route-pattern ;; \"/language/docs/<slug>\" → segment descriptors\n(define match-route-segments ;; segments + pattern → params dict or nil\n(define find-matching-route ;; path + route table → first match" "lisp"))
(p "No platform interface needed — uses only pure string and list primitives. Bootstrapped to both hosts via " (code "--spec-modules deps,router") ".")) (p "No platform interface needed — uses only pure string and list primitives. Bootstrapped to both hosts via " (code "--spec-modules deps,router") "."))
(div (div
(h4 :class "font-semibold text-stone-700" "2. Page registry") (h4 :class "font-semibold text-stone-700" "2. Page registry")
(p "Server serializes defpage metadata as SX dict literals inside " (code "<script type=\"text/sx-pages\">") ". Each entry carries name, path pattern, auth level, has-data flag, serialized content expression, and closure values.") (p "Server serializes defpage metadata as SX dict literals inside " (code "<script type=\"text/sx-pages\">") ". Each entry carries name, path pattern, auth level, has-data flag, serialized content expression, and closure values.")
(~doc-code :code (highlight "{:name \"docs-page\" :path \"/docs/<slug>\"\n :auth \"public\" :has-data false\n :content \"(case slug ...)\" :closure {}}" "lisp")) (~doc-code :code (highlight "{:name \"docs-page\" :path \"/language/docs/<slug>\"\n :auth \"public\" :has-data false\n :content \"(case slug ...)\" :closure {}}" "lisp"))
(p "boot.sx processes these at startup using the SX parser — the same " (code "parse") " function from parser.sx — building route entries with parsed patterns into the " (code "_page-routes") " table. No JSON dependency.")) (p "boot.sx processes these at startup using the SX parser — the same " (code "parse") " function from parser.sx — building route entries with parsed patterns into the " (code "_page-routes") " table. No JSON dependency."))
(div (div
@@ -191,16 +191,16 @@
(~doc-subsection :title "What becomes client-routable" (~doc-subsection :title "What becomes client-routable"
(p "All pages with content expressions — most of this docs app. Pure pages render instantly; :data pages fetch data then render client-side (Phase 4):") (p "All pages with content expressions — most of this docs app. Pure pages render instantly; :data pages fetch data then render client-side (Phase 4):")
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (code "/") ", " (code "/docs/") ", " (code "/docs/<slug>") " (most slugs), " (code "/protocols/") ", " (code "/protocols/<slug>")) (li (code "/") ", " (code "/language/docs/") ", " (code "/language/docs/<slug>") " (most slugs), " (code "/applications/protocols/") ", " (code "/applications/protocols/<slug>"))
(li (code "/examples/") ", " (code "/examples/<slug>") ", " (code "/essays/") ", " (code "/essays/<slug>")) (li (code "/examples/") ", " (code "/examples/<slug>") ", " (code "/etc/essays/") ", " (code "/etc/essays/<slug>"))
(li (code "/plans/") ", " (code "/plans/<slug>") ", " (code "/isomorphism/") ", " (code "/bootstrappers/"))) (li (code "/etc/plans/") ", " (code "/etc/plans/<slug>") ", " (code "/geography/isomorphism/") ", " (code "/language/bootstrappers/")))
(p "Pages that fall through to server:") (p "Pages that fall through to server:")
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (code "/docs/primitives") " and " (code "/docs/special-forms") " (call " (code "primitives-data") " / " (code "special-forms-data") " helpers)") (li (code "/language/docs/primitives") " and " (code "/language/docs/special-forms") " (call " (code "primitives-data") " / " (code "special-forms-data") " helpers)")
(li (code "/reference/<slug>") " (has " (code ":data (reference-data slug)") ")") (li (code "/reference/<slug>") " (has " (code ":data (reference-data slug)") ")")
(li (code "/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")") (li (code "/language/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")")
(li (code "/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")") (li (code "/geography/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")")
(li (code "/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/isomorphism/data-test" :class "text-violet-700 underline" "Phase 4 demo") ")"))) (li (code "/geography/isomorphism/data-test") " (has " (code ":data (data-test-data)") " — " (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "Phase 4 demo") ")")))
(~doc-subsection :title "Try-first/fallback design" (~doc-subsection :title "Try-first/fallback design"
(p "Client routing uses a try-first approach: attempt local evaluation in a try/catch, fall back to server fetch on any failure. This avoids needing perfect static analysis of content expressions — if a content expression calls a page helper the client doesn't have, the eval throws, and the server handles it transparently.") (p "Client routing uses a try-first approach: attempt local evaluation in a try/catch, fall back to server fetch on any failure. This avoids needing perfect static analysis of content expressions — if a content expression calls a page helper the client doesn't have, the eval throws, and the server handles it transparently.")
@@ -232,7 +232,7 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4" (div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2" (div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/data-test" :class "text-green-700 underline text-sm font-medium" "Live data test page")) (a :href "/geography/isomorphism/data-test" :class "text-green-700 underline text-sm font-medium" "Live data test page"))
(p :class "text-green-900 font-medium" "What it enables") (p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Client fetches server-evaluated data and renders :data pages locally. Data cached with TTL to avoid redundant fetches on back/forward navigation. All IO stays server-side — no continuations needed.")) (p :class "text-green-800" "Client fetches server-evaluated data and renders :data pages locally. Data cached with TTL to avoid redundant fetches on back/forward navigation. All IO stays server-side — no continuations needed."))
@@ -258,7 +258,7 @@
(li "Cache miss: " (code "sx:route client+data /path") " — fetches from server, caches, renders") (li "Cache miss: " (code "sx:route client+data /path") " — fetches from server, caches, renders")
(li "Cache hit: " (code "sx:route client+cache /path") " — instant render from cached data") (li "Cache hit: " (code "sx:route client+cache /path") " — instant render from cached data")
(li "After TTL: stale entry evicted, fresh fetch on next visit")) (li "After TTL: stale entry evicted, fresh fetch on next visit"))
(p "Try it: navigate to the " (a :href "/isomorphism/data-test" :class "text-violet-700 underline" "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch).")))) (p "Try it: navigate to the " (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "data test page") ", go back, return within 30s — the server-time stays the same (cached). Wait 30s+ and return — new time (fresh fetch)."))))
(~doc-subsection :title "Files" (~doc-subsection :title "Files"
(ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm" (ul :class "list-disc pl-5 text-stone-700 space-y-1 font-mono text-sm"
@@ -273,7 +273,7 @@
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "30 unit tests: serialize roundtrip, kebab-case, deps, full pipeline simulation, cache TTL") (li "30 unit tests: serialize roundtrip, kebab-case, deps, full pipeline simulation, cache TTL")
(li "Console: " (code "sx:route client+data") " on first visit, " (code "sx:route client+cache") " on return within 30s") (li "Console: " (code "sx:route client+data") " on first visit, " (code "sx:route client+cache") " on return within 30s")
(li (a :href "/isomorphism/data-test" :class "text-violet-700 underline" "Live data test page") " exercises the full pipeline with server time + pipeline steps") (li (a :href "/geography/isomorphism/data-test" :class "text-violet-700 underline" "Live data test page") " exercises the full pipeline with server time + pipeline steps")
(li "append! and dict-set! registered as proper primitives in spec + both hosts")))) (li "append! and dict-set! registered as proper primitives in spec + both hosts"))))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
@@ -331,7 +331,7 @@
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4" (div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
(div :class "flex items-center gap-2 mb-2" (div :class "flex items-center gap-2 mb-2"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/streaming" :class "text-green-700 underline text-sm font-medium" "Live streaming demo")) (a :href "/geography/isomorphism/streaming" :class "text-green-700 underline text-sm font-medium" "Live streaming demo"))
(p :class "text-green-900 font-medium" "What it enables") (p :class "text-green-900 font-medium" "What it enables")
(p :class "text-green-800" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately with loading skeletons, fills in suspended parts as data arrives.")) (p :class "text-green-800" "Server streams partially-evaluated SX as IO resolves. Client renders available subtrees immediately with loading skeletons, fills in suspended parts as data arrives."))
@@ -392,9 +392,9 @@
(li "sx/sxc/pages/helpers.py — streaming-demo-data page helper"))) (li "sx/sxc/pages/helpers.py — streaming-demo-data page helper")))
(~doc-subsection :title "Demonstration" (~doc-subsection :title "Demonstration"
(p "The " (a :href "/isomorphism/streaming" :class "text-violet-700 underline" "streaming demo page") " exercises the full pipeline:") (p "The " (a :href "/geography/isomorphism/streaming" :class "text-violet-700 underline" "streaming demo page") " exercises the full pipeline:")
(ol :class "list-decimal pl-5 text-stone-700 space-y-1" (ol :class "list-decimal pl-5 text-stone-700 space-y-1"
(li "Navigate to " (a :href "/isomorphism/streaming" :class "text-violet-700 underline" "/isomorphism/streaming")) (li "Navigate to " (a :href "/geography/isomorphism/streaming" :class "text-violet-700 underline" "/geography/isomorphism/streaming"))
(li "The page skeleton appears " (strong "instantly") " — animated loading skeletons fill the content area") (li "The page skeleton appears " (strong "instantly") " — animated loading skeletons fill the content area")
(li "After ~1.5 seconds, the real content replaces the skeletons (streamed from server)") (li "After ~1.5 seconds, the real content replaces the skeletons (streamed from server)")
(li "Open the Network tab — observe " (code "Transfer-Encoding: chunked") " on the document response") (li "Open the Network tab — observe " (code "Transfer-Encoding: chunked") " on the document response")
@@ -481,7 +481,7 @@
(~doc-subsection :title "Verification" (~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "5 new spec tests (page-render-plan suite)") (li "5 new spec tests (page-render-plan suite)")
(li "Render plans visible on " (a :href "/isomorphism/affinity" "affinity demo page")) (li "Render plans visible on " (a :href "/geography/isomorphism/affinity" "affinity demo page"))
(li "Client page registry includes :render-plan for each page")))) (li "Client page registry includes :render-plan for each page"))))
(~doc-subsection :title "7c. Cache Invalidation & Optimistic Data Updates" (~doc-subsection :title "7c. Cache Invalidation & Optimistic Data Updates"
@@ -518,7 +518,7 @@
(~doc-subsection :title "Verification" (~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Live demo at " (a :href "/isomorphism/optimistic" :class "text-violet-600 hover:underline" "/isomorphism/optimistic")) (li "Live demo at " (a :href "/geography/isomorphism/optimistic" :class "text-violet-600 hover:underline" "/geography/isomorphism/optimistic"))
(li "Console log: " (code "sx:optimistic confirmed") " / " (code "sx:optimistic reverted"))))) (li "Console log: " (code "sx:optimistic confirmed") " / " (code "sx:optimistic reverted")))))
(~doc-subsection :title "7d. Offline Data Layer" (~doc-subsection :title "7d. Offline Data Layer"
@@ -553,7 +553,7 @@
(~doc-subsection :title "Verification" (~doc-subsection :title "Verification"
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li "Live demo at " (a :href "/isomorphism/offline" :class "text-violet-600 hover:underline" "/isomorphism/offline")) (li "Live demo at " (a :href "/geography/isomorphism/offline" :class "text-violet-600 hover:underline" "/geography/isomorphism/offline"))
(li "Test with DevTools Network → Offline mode") (li "Test with DevTools Network → Offline mode")
(li "Console log: " (code "sx:offline queued") ", " (code "sx:offline syncing") ", " (code "sx:offline synced"))))) (li "Console log: " (code "sx:offline queued") ", " (code "sx:offline syncing") ", " (code "sx:offline synced")))))

View File

@@ -479,7 +479,7 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
(code "createElement") ", " (code "appendChild") ", " (code "textContent") (code "createElement") ", " (code "appendChild") ", " (code "textContent")
". This gives SX a compilation target competitive with Svelte's " ". This gives SX a compilation target competitive with Svelte's "
"approach: components compile away, the framework disappears.") "approach: components compile away, the framework disappears.")
(p "Combined with the " (a :href "/plans/content-addressed-components" "content-addressed components") (p "Combined with the " (a :href "/etc/plans/content-addressed-components" "content-addressed components")
" plan, a page's compiled JS could be stored on IPFS by its content hash. " " plan, a page's compiled JS could be stored on IPFS by its content hash. "
"The server returns a CID. The browser fetches and executes pre-compiled JavaScript. " "The server returns a CID. The browser fetches and executes pre-compiled JavaScript. "
"No parser, no evaluator, no network round-trip for component definitions.")) "No parser, no evaluator, no network round-trip for component definitions."))

View File

@@ -78,11 +78,11 @@
(~doc-section :title "Data Model" :id "data" (~doc-section :title "Data Model" :id "data"
(p "The current nav data is flat — each section has its own " (code "define") ". The new model is a single tree:") (p "The current nav data is flat — each section has its own " (code "define") ". The new model is a single tree:")
(~doc-code :code (highlight "(define sx-nav-tree\n {:label \"sx\"\n :href \"/\"\n :children (list\n {:label \"Docs\"\n :href \"/docs/introduction\"\n :children docs-nav-items}\n {:label \"CSSX\"\n :href \"/cssx/\"\n :children cssx-nav-items}\n {:label \"Reference\"\n :href \"/reference/\"\n :children reference-nav-items}\n {:label \"Protocols\"\n :href \"/protocols/wire-format\"\n :children protocols-nav-items}\n {:label \"Examples\"\n :href \"/examples/click-to-load\"\n :children examples-nav-items}\n {:label \"Essays\"\n :href \"/essays/\"\n :children essays-nav-items}\n {:label \"Philosophy\"\n :href \"/philosophy/sx-manifesto\"\n :children philosophy-nav-items}\n {:label \"Specs\"\n :href \"/specs/\"\n :children specs-nav-items}\n {:label \"Bootstrappers\"\n :href \"/bootstrappers/\"\n :children bootstrappers-nav-items}\n {:label \"Testing\"\n :href \"/testing/\"\n :children testing-nav-items}\n {:label \"Isomorphism\"\n :href \"/isomorphism/\"\n :children isomorphism-nav-items}\n {:label \"Plans\"\n :href \"/plans/\"\n :children plans-nav-items}\n {:label \"Reactive Islands\"\n :href \"/reactive-islands/\"\n :children reactive-islands-nav-items})})" "lisp")) (~doc-code :code (highlight "(define sx-nav-tree\n {:label \"sx\"\n :href \"/\"\n :children (list\n {:label \"Docs\"\n :href \"/language/docs/introduction\"\n :children docs-nav-items}\n {:label \"CSSX\"\n :href \"/applications/cssx/\"\n :children cssx-nav-items}\n {:label \"Reference\"\n :href \"/reference/\"\n :children reference-nav-items}\n {:label \"Protocols\"\n :href \"/applications/protocols/wire-format\"\n :children protocols-nav-items}\n {:label \"Examples\"\n :href \"/examples/click-to-load\"\n :children examples-nav-items}\n {:label \"Essays\"\n :href \"/etc/essays/\"\n :children essays-nav-items}\n {:label \"Philosophy\"\n :href \"/etc/philosophy/sx-manifesto\"\n :children philosophy-nav-items}\n {:label \"Specs\"\n :href \"/language/specs/\"\n :children specs-nav-items}\n {:label \"Bootstrappers\"\n :href \"/language/bootstrappers/\"\n :children bootstrappers-nav-items}\n {:label \"Testing\"\n :href \"/language/testing/\"\n :children testing-nav-items}\n {:label \"Isomorphism\"\n :href \"/geography/isomorphism/\"\n :children isomorphism-nav-items}\n {:label \"Plans\"\n :href \"/etc/plans/\"\n :children plans-nav-items}\n {:label \"Reactive Islands\"\n :href \"/reactive-islands/\"\n :children reactive-islands-nav-items})})" "lisp"))
(p "The existing per-section lists (" (code "docs-nav-items") ", " (code "plans-nav-items") ", etc.) remain unchanged — they just become the " (code ":children") " of tree nodes. Sub-sections that have their own sub-items can nest further:") (p "The existing per-section lists (" (code "docs-nav-items") ", " (code "plans-nav-items") ", etc.) remain unchanged — they just become the " (code ":children") " of tree nodes. Sub-sections that have their own sub-items can nest further:")
(~doc-code :code (highlight ";; Future: deeper nesting\n{:label \"Plans\"\n :href \"/plans/\"\n :children (list\n {:label \"Status\" :href \"/plans/status\"}\n {:label \"Bootstrappers\" :href \"/plans/self-hosting-bootstrapper\"\n :children (list\n {:label \"py.sx\" :href \"/plans/self-hosting-bootstrapper\"}\n {:label \"js.sx\" :href \"/plans/js-bootstrapper\"})}\n ;; ...\n )}" "lisp")) (~doc-code :code (highlight ";; Future: deeper nesting\n{:label \"Plans\"\n :href \"/etc/plans/\"\n :children (list\n {:label \"Status\" :href \"/etc/plans/status\"}\n {:label \"Bootstrappers\" :href \"/etc/plans/self-hosting-bootstrapper\"\n :children (list\n {:label \"py.sx\" :href \"/etc/plans/self-hosting-bootstrapper\"}\n {:label \"js.sx\" :href \"/etc/plans/js-bootstrapper\"})}\n ;; ...\n )}" "lisp"))
(p "The tree depth is unlimited. The nav component recurses.")) (p "The tree depth is unlimited. The nav component recurses."))
@@ -116,7 +116,7 @@
(~doc-section :title "Path Resolution" :id "resolution" (~doc-section :title "Path Resolution" :id "resolution"
(p "Given a URL path, compute the breadcrumb trail and children. This is a tree walk:") (p "Given a URL path, compute the breadcrumb trail and children. This is a tree walk:")
(~doc-code :code (highlight "(define resolve-nav-path\n (fn (tree current-href)\n ;; Walk sx-nav-tree, find the node matching current-href,\n ;; return the trail of ancestors + current children.\n ;;\n ;; Returns: {:trail (list of {:node N :siblings S})\n ;; :children (list) or nil\n ;; :depth number}\n ;;\n ;; Example: current-href = \"/plans/typed-sx\"\n ;; → trail: [{:node Plans :siblings [Docs, CSSX, ...]}\n ;; {:node Typed-SX :siblings [Status, Reader-Macros, ...]}]\n ;; → children: nil (leaf node)\n ;; → depth: 2\n (let ((result (walk-nav-tree tree current-href (list))))\n result)))" "lisp")) (~doc-code :code (highlight "(define resolve-nav-path\n (fn (tree current-href)\n ;; Walk sx-nav-tree, find the node matching current-href,\n ;; return the trail of ancestors + current children.\n ;;\n ;; Returns: {:trail (list of {:node N :siblings S})\n ;; :children (list) or nil\n ;; :depth number}\n ;;\n ;; Example: current-href = \"/etc/plans/typed-sx\"\n ;; → trail: [{:node Plans :siblings [Docs, CSSX, ...]}\n ;; {:node Typed-SX :siblings [Status, Reader-Macros, ...]}]\n ;; → children: nil (leaf node)\n ;; → depth: 2\n (let ((result (walk-nav-tree tree current-href (list))))\n result)))" "lisp"))
(p "This runs server-side (it's a pure function, no IO). The layout component calls it with the current URL and passes the result to " (code "~sx-nav") ". Same pattern as the current " (code "find-current") " but produces a richer result.") (p "This runs server-side (it's a pure function, no IO). The layout component calls it with the current URL and passes the result to " (code "~sx-nav") ". Same pattern as the current " (code "find-current") " but produces a richer result.")
@@ -181,7 +181,7 @@
(~doc-section :title "Layout Simplification" :id "layout" (~doc-section :title "Layout Simplification" :id "layout"
(p "The defpage layout declarations currently specify section, sub-label, sub-href, sub-nav, selected — five params to configure two menu bars. The new layout takes one param: the nav trail.") (p "The defpage layout declarations currently specify section, sub-label, sub-href, sub-nav, selected — five params to configure two menu bars. The new layout takes one param: the nav trail.")
(~doc-code :code (highlight ";; Current (verbose, configures two bars)\n(defpage plan-page\n :path \"/plans/<slug>\"\n :layout (:sx-section\n :section \"Plans\"\n :sub-label \"Plans\"\n :sub-href \"/plans/\"\n :sub-nav (~section-nav :items plans-nav-items\n :current (find-current plans-nav-items slug))\n :selected (or (find-current plans-nav-items slug) \"\"))\n :content (...))\n\n;; New (one param, nav computed from URL)\n(defpage plan-page\n :path \"/plans/<slug>\"\n :layout (:sx-docs :path (str \"/plans/\" slug))\n :content (...))" "lisp")) (~doc-code :code (highlight ";; Current (verbose, configures two bars)\n(defpage plan-page\n :path \"/etc/plans/<slug>\"\n :layout (:sx-section\n :section \"Plans\"\n :sub-label \"Plans\"\n :sub-href \"/etc/plans/\"\n :sub-nav (~section-nav :items plans-nav-items\n :current (find-current plans-nav-items slug))\n :selected (or (find-current plans-nav-items slug) \"\"))\n :content (...))\n\n;; New (one param, nav computed from URL)\n(defpage plan-page\n :path \"/etc/plans/<slug>\"\n :layout (:sx-docs :path (str \"/etc/plans/\" slug))\n :content (...))" "lisp"))
(p "The layout component computes the nav trail internally from the path and the nav tree. No more passing section names, sub-labels, or pre-built nav components through layout params.") (p "The layout component computes the nav trail internally from the path and the nav tree. No more passing section names, sub-labels, or pre-built nav components through layout params.")

View File

@@ -96,7 +96,7 @@
(~doc-subsection :title "Eager Bundle" (~doc-subsection :title "Eager Bundle"
(p "The server already computes per-page component bundles. For key navigation paths — the main nav bar, section nav — the server can include " (em "linked routes' components") " in the initial bundle, not just the current page's.") (p "The server already computes per-page component bundles. For key navigation paths — the main nav bar, section nav — the server can include " (em "linked routes' components") " in the initial bundle, not just the current page's.")
(~doc-code :code (highlight ";; defpage metadata declares eager prefetch targets\n(defpage docs-page\n :path \"/docs/<slug>\"\n :auth :public\n :prefetch :eager ;; bundle deps for all linked pure routes\n :content (case slug ...))" "lisp")) (~doc-code :code (highlight ";; defpage metadata declares eager prefetch targets\n(defpage docs-page\n :path \"/language/docs/<slug>\"\n :auth :public\n :prefetch :eager ;; bundle deps for all linked pure routes\n :content (case slug ...))" "lisp"))
(p "Implementation: " (code "components_for_page()") " already scans the page SX for component refs. Extend it to also scan for " (code "href") " attributes, match them against the page registry, and include those pages' deps in the bundle. The cost is a larger initial payload; the benefit is zero-latency navigation within a section.")) (p "Implementation: " (code "components_for_page()") " already scans the page SX for component refs. Extend it to also scan for " (code "href") " attributes, match them against the page registry, and include those pages' deps in the bundle. The cost is a larger initial payload; the benefit is zero-latency navigation within a section."))
(~doc-subsection :title "Idle Timer" (~doc-subsection :title "Idle Timer"
@@ -123,7 +123,7 @@
(~doc-subsection :title "Declarative Configuration" (~doc-subsection :title "Declarative Configuration"
(p "All strategies configured via " (code "defpage") " metadata and " (code "sx-prefetch") " attributes on links/containers:") (p "All strategies configured via " (code "defpage") " metadata and " (code "sx-prefetch") " attributes on links/containers:")
(~doc-code :code (highlight ";; Page-level: what to prefetch for routes linking TO this page\n(defpage docs-page\n :path \"/docs/<slug>\"\n :prefetch :eager) ;; bundle with linking page\n\n(defpage reference-page\n :path \"/reference/<slug>\"\n :prefetch :components) ;; prefetch components, data on click\n\n;; Link-level: override per-link\n(a :href \"/docs/components\"\n :sx-prefetch \"idle\") ;; prefetch after page idle\n\n;; Container-level: approach prediction for nav areas\n(nav :sx-prefetch \"approach\"\n (a :href \"/docs/\") (a :href \"/reference/\") ...)" "lisp")) (~doc-code :code (highlight ";; Page-level: what to prefetch for routes linking TO this page\n(defpage docs-page\n :path \"/language/docs/<slug>\"\n :prefetch :eager) ;; bundle with linking page\n\n(defpage reference-page\n :path \"/reference/<slug>\"\n :prefetch :components) ;; prefetch components, data on click\n\n;; Link-level: override per-link\n(a :href \"/language/docs/components\"\n :sx-prefetch \"idle\") ;; prefetch after page idle\n\n;; Container-level: approach prediction for nav areas\n(nav :sx-prefetch \"approach\"\n (a :href \"/language/docs/\") (a :href \"/reference/\") ...)" "lisp"))
(p "Priority cascade: explicit " (code "sx-prefetch") " on link > " (code ":prefetch") " on target defpage > default (hover). The system never prefetches the same components twice — " (code "_prefetch-pending") " and " (code "loaded-component-names") " handle dedup."))) (p "Priority cascade: explicit " (code "sx-prefetch") " on link > " (code ":prefetch") " on target defpage > default (hover). The system never prefetches the same components twice — " (code "_prefetch-pending") " and " (code "loaded-component-names") " handle dedup.")))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
@@ -189,7 +189,7 @@
(~doc-section :title "Request Flow" :id "request-flow" (~doc-section :title "Request Flow" :id "request-flow"
(p "End-to-end example: user hovers a link, components prefetch, click goes client-side.") (p "End-to-end example: user hovers a link, components prefetch, click goes client-side.")
(~doc-code :code (highlight "User hovers link \"/docs/sx-manifesto\"\n |\n +-- bind-prefetch-on-hover fires (150ms debounce)\n |\n +-- compute-missing-deps(\"/docs/sx-manifesto\")\n | +-- find-matching-route -> page with deps:\n | | [\"~essay-sx-manifesto\", \"~doc-code\"]\n | +-- loaded-component-names -> [\"~nav\", \"~footer\", \"~doc-code\"]\n | +-- missing: [\"~essay-sx-manifesto\"]\n |\n +-- prefetch-components([\"~essay-sx-manifesto\"])\n | +-- GET /sx/components?names=~essay-sx-manifesto\n | | Headers: SX-Components: ~nav,~footer,~doc-code\n | +-- Server resolves transitive deps\n | | (also needs ~rich-text, subtracts already-loaded)\n | +-- Response:\n | (defcomp ~essay-sx-manifesto ...) \n | (defcomp ~rich-text ...)\n |\n +-- sx-process-component-text registers defcomps in env\n |\n +-- User clicks link\n +-- try-client-route(\"/docs/sx-manifesto\")\n +-- has-all-deps? -> true (prefetched!)\n +-- eval content -> DOM\n +-- Client-side render, no server roundtrip" "text"))) (~doc-code :code (highlight "User hovers link \"/language/docs/sx-manifesto\"\n |\n +-- bind-prefetch-on-hover fires (150ms debounce)\n |\n +-- compute-missing-deps(\"/language/docs/sx-manifesto\")\n | +-- find-matching-route -> page with deps:\n | | [\"~essay-sx-manifesto\", \"~doc-code\"]\n | +-- loaded-component-names -> [\"~nav\", \"~footer\", \"~doc-code\"]\n | +-- missing: [\"~essay-sx-manifesto\"]\n |\n +-- prefetch-components([\"~essay-sx-manifesto\"])\n | +-- GET /sx/components?names=~essay-sx-manifesto\n | | Headers: SX-Components: ~nav,~footer,~doc-code\n | +-- Server resolves transitive deps\n | | (also needs ~rich-text, subtracts already-loaded)\n | +-- Response:\n | (defcomp ~essay-sx-manifesto ...) \n | (defcomp ~rich-text ...)\n |\n +-- sx-process-component-text registers defcomps in env\n |\n +-- User clicks link\n +-- try-client-route(\"/language/docs/sx-manifesto\")\n +-- has-all-deps? -> true (prefetched!)\n +-- eval content -> DOM\n +-- Client-side render, no server roundtrip" "text")))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
;; File changes ;; File changes
@@ -248,7 +248,7 @@
(~doc-section :title "Relationship to Isomorphic Roadmap" :id "relationship" (~doc-section :title "Relationship to Isomorphic Roadmap" :id "relationship"
(p "This plan sits between Phase 3 (client-side routing) and Phase 4 (client async & IO bridge) of the " (p "This plan sits between Phase 3 (client-side routing) and Phase 4 (client async & IO bridge) of the "
(a :href "/plans/isomorphic-architecture" :class "text-violet-700 underline" "isomorphic architecture roadmap") (a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "isomorphic architecture roadmap")
". It extends Phase 3 by making more navigations go client-side without needing any IO bridge — purely by ensuring component definitions are available before they're needed.") ". It extends Phase 3 by making more navigations go client-side without needing any IO bridge — purely by ensuring component definitions are available before they're needed.")
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2" (div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 3 (client-side routing with deps checking). No dependency on Phase 4."))))) (p :class "text-amber-800 text-sm" (strong "Depends on: ") "Phase 3 (client-side routing with deps checking). No dependency on Phase 4.")))))

View File

@@ -28,7 +28,7 @@
(~doc-code :code (highlight "#'my-function → (quote my-function)" "lisp"))) (~doc-code :code (highlight "#'my-function → (quote my-function)" "lisp")))
(~doc-subsection :title "Extensible dispatch: #name" (~doc-subsection :title "Extensible dispatch: #name"
(p "User-defined reader macros via " (code "#name expr") ". The parser reads an identifier after " (code "#") ", looks up a handler in the reader macro registry, and calls it with the next parsed expression. See the " (a :href "/plans/reader-macro-demo" :class "text-violet-600 hover:underline" "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB."))) (p "User-defined reader macros via " (code "#name expr") ". The parser reads an identifier after " (code "#") ", looks up a handler in the reader macro registry, and calls it with the next parsed expression. See the " (a :href "/etc/plans/reader-macro-demo" :class "text-violet-600 hover:underline" "#z3 demo") " for a working example that translates SX spec declarations to SMT-LIB.")))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------

View File

@@ -40,7 +40,7 @@
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
(~doc-section :title "Tiers" :id "tiers" (~doc-section :title "Tiers" :id "tiers"
(p "Four tiers, matching the " (a :href "/reactive/plan" :class "text-violet-700 underline" "reactive islands") " levels:") (p "Four tiers, matching the " (a :href "/geography/reactive/plan" :class "text-violet-700 underline" "reactive islands") " levels:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4" (div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm" (table :class "w-full text-left text-sm"
@@ -82,7 +82,7 @@
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
(~doc-section :title "The Slicer is SX" :id "slicer-is-sx" (~doc-section :title "The Slicer is SX" :id "slicer-is-sx"
(p "Per the " (a :href "/plans/self-hosting-bootstrapper" :class "text-violet-700 underline" "self-hosting principle") ", the slicer is not a build tool — it's a spec module. " (code "slice.sx") " analyzes the spec's own dependency graph and determines which defines belong to which tier.") (p "Per the " (a :href "/etc/plans/self-hosting-bootstrapper" :class "text-violet-700 underline" "self-hosting principle") ", the slicer is not a build tool — it's a spec module. " (code "slice.sx") " analyzes the spec's own dependency graph and determines which defines belong to which tier.")
(p (code "js.sx") " (the self-hosting SX-to-JavaScript translator) already compiles the full spec. Slicing is a filter: " (code "js.sx") " translates only the defines that " (code "slice.sx") " selects for a given tier.") (p (code "js.sx") " (the self-hosting SX-to-JavaScript translator) already compiles the full spec. Slicing is a filter: " (code "js.sx") " translates only the defines that " (code "slice.sx") " selects for a given tier.")
(~doc-code :code (highlight ";; slice.sx — determine which defines each tier needs\n;;\n;; Input: the full list of defines from all spec files\n;; Output: a filtered list for the requested tier\n\n(define tier-deps\n ;; Which spec modules each tier requires\n {:L0 (list \"engine\" \"boot-partial\")\n :L1 (list \"engine\" \"boot-partial\" \"dom-partial\")\n :L2 (list \"engine\" \"boot-partial\" \"dom-partial\"\n \"signals\" \"dom-island\")\n :L3 (list \"eval\" \"render\" \"parser\"\n \"engine\" \"orchestration\" \"boot\"\n \"dom\" \"signals\" \"router\")})\n\n(define slice-defines\n (fn (tier all-defines)\n ;; 1. Get the module list for this tier\n ;; 2. Walk each define's dependency references\n ;; 3. Include a define only if ALL its deps are\n ;; satisfiable within the tier's module set\n ;; 4. Return the filtered define list\n (let ((modules (get tier-deps tier)))\n (filter\n (fn (d) (tier-satisfies? modules (define-deps d)))\n all-defines))))" "lisp")) (~doc-code :code (highlight ";; slice.sx — determine which defines each tier needs\n;;\n;; Input: the full list of defines from all spec files\n;; Output: a filtered list for the requested tier\n\n(define tier-deps\n ;; Which spec modules each tier requires\n {:L0 (list \"engine\" \"boot-partial\")\n :L1 (list \"engine\" \"boot-partial\" \"dom-partial\")\n :L2 (list \"engine\" \"boot-partial\" \"dom-partial\"\n \"signals\" \"dom-island\")\n :L3 (list \"eval\" \"render\" \"parser\"\n \"engine\" \"orchestration\" \"boot\"\n \"dom\" \"signals\" \"router\")})\n\n(define slice-defines\n (fn (tier all-defines)\n ;; 1. Get the module list for this tier\n ;; 2. Walk each define's dependency references\n ;; 3. Include a define only if ALL its deps are\n ;; satisfiable within the tier's module set\n ;; 4. Return the filtered define list\n (let ((modules (get tier-deps tier)))\n (filter\n (fn (d) (tier-satisfies? modules (define-deps d)))\n all-defines))))" "lisp"))
@@ -161,7 +161,7 @@
(~doc-subsection :title "Cache Behavior" (~doc-subsection :title "Cache Behavior"
(p "Each tier file is content-hashed (like the current " (code "sx_js_hash") " mechanism). Cache-forever semantics. A user who visits any L0 page caches the L0 runtime permanently. If they later visit an L2 page, only the ~10KB delta downloads.") (p "Each tier file is content-hashed (like the current " (code "sx_js_hash") " mechanism). Cache-forever semantics. A user who visits any L0 page caches the L0 runtime permanently. If they later visit an L2 page, only the ~10KB delta downloads.")
(p "Combined with " (a :href "/plans/environment-images" :class "text-violet-700 underline" "environment images") ": the image CID includes the tier. An L0 image is smaller than an L3 image — it contains fewer primitives, no parser state, no evaluator. The standalone HTML bundle for an L0 page is tiny."))) (p "Combined with " (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "environment images") ": the image CID includes the tier. An L0 image is smaller than an L3 image — it contains fewer primitives, no parser state, no evaluator. The standalone HTML bundle for an L0 page is tiny.")))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
;; Automatic tier detection ;; Automatic tier detection
@@ -273,10 +273,10 @@
(~doc-section :title "Relationships" :id "relationships" (~doc-section :title "Relationships" :id "relationships"
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/plans/environment-images" :class "text-violet-700 underline" "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.") (li (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "Environment Images") " — tiered images are smaller. An L0 image omits the parser, evaluator, and most primitives.")
(li (a :href "/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.") (li (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " — component CID resolution is L3-only. L0 pages don't resolve components client-side.")
(li (a :href "/reactive/plan" :class "text-violet-700 underline" "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.") (li (a :href "/geography/reactive/plan" :class "text-violet-700 underline" "Reactive Islands") " — L2 tier is defined by island presence. The signal runtime is the L1→L2 delta.")
(li (a :href "/plans/isomorphic-architecture" :class "text-violet-700 underline" "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it.")) (li (a :href "/etc/plans/isomorphic-architecture" :class "text-violet-700 underline" "Isomorphic Architecture") " — client-side page rendering is L3. Most pages don't need it."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2" (div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") (code "js.sx") " (complete), " (code "deps.sx") " (complete), " (code "bootstrap_js.py") " adapter selection (exists). " (strong "New: ") (code "slice.sx") " spec module."))))) (p :class "text-amber-800 text-sm" (strong "Depends on: ") (code "js.sx") " (complete), " (code "deps.sx") " (complete), " (code "bootstrap_js.py") " adapter selection (exists). " (strong "New: ") (code "slice.sx") " spec module.")))))

View File

@@ -17,7 +17,7 @@
(p :class "text-green-700 text-sm" (p :class "text-green-700 text-sm"
(code "py.sx") " is implemented and verified. G0 == G1: 128/128 defines match, " (code "py.sx") " is implemented and verified. G0 == G1: 128/128 defines match, "
"1490 lines, 88,955 bytes — byte-for-byte identical. " "1490 lines, 88,955 bytes — byte-for-byte identical. "
(a :href "/bootstrappers/self-hosting" :class "underline text-green-600 font-medium" (a :href "/language/bootstrappers/self-hosting" :class "underline text-green-600 font-medium"
"See live verification.")))) "See live verification."))))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------

View File

@@ -37,37 +37,37 @@
(div :class "rounded border border-green-200 bg-green-50 p-4" (div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 1: Dependency Analysis")) (a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 1: Dependency Analysis"))
(p :class "text-sm text-stone-600" "Per-page component bundles via deps.sx. Transitive closure, scan-refs, components-needed, page-css-classes. 15 tests, bootstrapped to both hosts.")) (p :class "text-sm text-stone-600" "Per-page component bundles via deps.sx. Transitive closure, scan-refs, components-needed, page-css-classes. 15 tests, bootstrapped to both hosts."))
(div :class "rounded border border-green-200 bg-green-50 p-4" (div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 2: IO Detection")) (a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 2: IO Detection"))
(p :class "text-sm text-stone-600" "Automatic IO classification. scan-io-refs, transitive-io-refs, compute-all-io-refs. Server expands IO components, serializes pure ones for client.")) (p :class "text-sm text-stone-600" "Automatic IO classification. scan-io-refs, transitive-io-refs, compute-all-io-refs. Server expands IO components, serializes pure ones for client."))
(div :class "rounded border border-green-200 bg-green-50 p-4" (div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing")) (a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 3: Client-Side Routing"))
(p :class "text-sm text-stone-600" "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips.")) (p :class "text-sm text-stone-600" "router.sx spec, page registry via <script type=\"text/sx-pages\">, client route matching, try-first/fallback to server. Pure pages render without server roundtrips."))
(div :class "rounded border border-green-200 bg-green-50 p-4" (div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 4: Client Async & IO Bridge")) (a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 4: Client Async & IO Bridge"))
(p :class "text-sm text-stone-600" "Server evaluates :data expressions, serializes as SX wire format. Client fetches pre-evaluated data, caches with 30s TTL, renders :content locally. 30 unit tests.")) (p :class "text-sm text-stone-600" "Server evaluates :data expressions, serializes as SX wire format. Client fetches pre-evaluated data, caches with 30s TTL, renders :content locally. 30 unit tests."))
(div :class "rounded border border-green-200 bg-green-50 p-4" (div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 5: Client IO Proxy")) (a :href "/geography/isomorphism/" :class "font-semibold text-green-800 underline" "Isomorphic Phase 5: Client IO Proxy"))
(p :class "text-sm text-stone-600" "IO primitives (highlight, asset-url, etc.) proxied to server via registerIoDeps(). Async DOM renderer handles promises through the render tree. Components with IO deps render client-side via server round-trips — no continuations needed.")) (p :class "text-sm text-stone-600" "IO primitives (highlight, asset-url, etc.) proxied to server via registerIoDeps(). Async DOM renderer handles promises through the render tree. Components with IO deps render client-side via server round-trips — no continuations needed."))
(div :class "rounded border border-green-200 bg-green-50 p-4" (div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/testing/" :class "font-semibold text-green-800 underline" "Modular Test Architecture")) (a :href "/language/testing/" :class "font-semibold text-green-800 underline" "Modular Test Architecture"))
(p :class "text-sm text-stone-600" "Per-module test specs (eval, parser, router, render) with 161 tests. Three runners: Python, Node.js, browser. 5 platform functions, everything else pure SX.")))) (p :class "text-sm text-stone-600" "Per-module test specs (eval, parser, router, render) with 161 tests. Three runners: Python, Node.js, browser. 5 platform functions, everything else pure SX."))))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
@@ -81,7 +81,7 @@
(div :class "rounded border border-amber-200 bg-amber-50 p-4" (div :class "rounded border border-amber-200 bg-amber-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-amber-600 text-white uppercase" "Partial")
(a :href "/plans/fragment-protocol" :class "font-semibold text-amber-900 underline" "Fragment Protocol")) (a :href "/etc/plans/fragment-protocol" :class "font-semibold text-amber-900 underline" "Fragment Protocol"))
(p :class "text-sm text-stone-600" "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers.")))) (p :class "text-sm text-stone-600" "Fragment GET infrastructure works. The planned POST/sexp structured protocol for transferring component definitions between services is not yet implemented. Fragment endpoints still use legacy GET + X-Fragment-Request headers."))))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
@@ -95,42 +95,42 @@
(div :class "rounded border border-stone-200 bg-stone-50 p-4" (div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-700 text-white uppercase" "Done") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-700 text-white uppercase" "Done")
(a :href "/plans/reader-macros" :class "font-semibold text-stone-800 underline" "Reader Macros")) (a :href "/etc/plans/reader-macros" :class "font-semibold text-stone-800 underline" "Reader Macros"))
(p :class "text-sm text-stone-600" "# dispatch in parser.sx spec, Python parser.py, hand-written sx.js. Three built-ins (#;, #|...|, #') plus extensible #name dispatch. #z3 demo translates define-primitive to SMT-LIB.") (p :class "text-sm text-stone-600" "# dispatch in parser.sx spec, Python parser.py, hand-written sx.js. Three built-ins (#;, #|...|, #') plus extensible #name dispatch. #z3 demo translates define-primitive to SMT-LIB.")
(p :class "text-sm text-stone-500 mt-1" "48 parser tests (SX + Python), all passing. Rebootstrapped to JS and Python.")) (p :class "text-sm text-stone-500 mt-1" "48 parser tests (SX + Python), all passing. Rebootstrapped to JS and Python."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4" (div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/plans/sx-activity" :class "font-semibold text-stone-800 underline" "SX-Activity")) (a :href "/etc/plans/sx-activity" :class "font-semibold text-stone-800 underline" "SX-Activity"))
(p :class "text-sm text-stone-600" "Federated SX over ActivityPub — 6 phases from SX wire format for activities to the evaluable web on IPFS. Existing AP infrastructure provides the foundation but no SX-specific federation code exists.") (p :class "text-sm text-stone-600" "Federated SX over ActivityPub — 6 phases from SX wire format for activities to the evaluable web on IPFS. Existing AP infrastructure provides the foundation but no SX-specific federation code exists.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: shared/sx/activity.py (SX<->JSON-LD), shared/sx/ipfs.py, shared/sx/ref/ipfs-resolve.sx, shared/sx/registry.py, shared/sx/anchor.py.")) (p :class "text-sm text-stone-500 mt-1" "Remaining: shared/sx/activity.py (SX<->JSON-LD), shared/sx/ipfs.py, shared/sx/ref/ipfs-resolve.sx, shared/sx/registry.py, shared/sx/anchor.py."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4" (div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/plans/glue-decoupling" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue")) (a :href "/etc/plans/glue-decoupling" :class "font-semibold text-stone-800 underline" "Cross-App Decoupling via Glue"))
(p :class "text-sm text-stone-600" "Eliminate all cross-app model imports by routing through a glue service layer. No glue/ directory exists. Apps are currently decoupled via HTTP interfaces and DTOs instead.") (p :class "text-sm text-stone-600" "Eliminate all cross-app model imports by routing through a glue service layer. No glue/ directory exists. Apps are currently decoupled via HTTP interfaces and DTOs instead.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: glue/services/ for pages, page_config, calendars, marketplaces, cart_items, products, post_associations. 25+ cross-app imports to eliminate.")) (p :class "text-sm text-stone-500 mt-1" "Remaining: glue/services/ for pages, page_config, calendars, marketplaces, cart_items, products, post_associations. 25+ cross-app imports to eliminate."))
(div :class "rounded border border-stone-200 bg-stone-50 p-4" (div :class "rounded border border-stone-200 bg-stone-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-stone-500 text-white uppercase" "Not Started")
(a :href "/plans/social-sharing" :class "font-semibold text-stone-800 underline" "Social Network Sharing")) (a :href "/etc/plans/social-sharing" :class "font-semibold text-stone-800 underline" "Social Network Sharing"))
(p :class "text-sm text-stone-600" "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon via the account service. No models, blueprints, or platform clients created.") (p :class "text-sm text-stone-600" "OAuth-based sharing to Facebook, Instagram, Threads, Twitter/X, LinkedIn, and Mastodon via the account service. No models, blueprints, or platform clients created.")
(p :class "text-sm text-stone-500 mt-1" "Remaining: SocialConnection model, social_crypto.py, platform OAuth clients (6), account/bp/social/ blueprint, share button fragment.")) (p :class "text-sm text-stone-500 mt-1" "Remaining: SocialConnection model, social_crypto.py, platform OAuth clients (6), account/bp/social/ blueprint, share button fragment."))
(div :class "rounded border border-green-200 bg-green-50 p-4" (div :class "rounded border border-green-200 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Streaming & Suspense")) (a :href "/geography/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 6: Streaming & Suspense"))
(p :class "text-sm text-stone-600" "Server streams partially-evaluated SX as IO resolves. ~suspense component renders fallbacks, inline resolution scripts fill in content. Concurrent IO via asyncio, chunked transfer encoding.") (p :class "text-sm text-stone-600" "Server streams partially-evaluated SX as IO resolves. ~suspense component renders fallbacks, inline resolution scripts fill in content. Concurrent IO via asyncio, chunked transfer encoding.")
(p :class "text-sm text-stone-500 mt-1" "Demo: " (a :href "/isomorphism/streaming" "/isomorphism/streaming"))) (p :class "text-sm text-stone-500 mt-1" "Demo: " (a :href "/geography/isomorphism/streaming" "/geography/isomorphism/streaming")))
(div :class "rounded border border-green-300 bg-green-50 p-4" (div :class "rounded border border-green-300 bg-green-50 p-4"
(div :class "flex items-center gap-2 mb-1" (div :class "flex items-center gap-2 mb-1"
(span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete") (span :class "inline-block px-2 py-0.5 rounded text-xs font-bold bg-green-600 text-white uppercase" "Complete")
(a :href "/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 7: Full Isomorphism")) (a :href "/geography/isomorphism/" :class "font-semibold text-stone-800 underline" "Isomorphic Phase 7: Full Isomorphism"))
(p :class "text-sm text-stone-600" "Affinity annotations, render plans, optimistic data updates, offline mutation queue, isomorphic testing harness, universal page descriptor.") (p :class "text-sm text-stone-600" "Affinity annotations, render plans, optimistic data updates, offline mutation queue, isomorphic testing harness, universal page descriptor.")
(p :class "text-sm text-stone-500 mt-1" "All 6 sub-phases (7a7f) complete.")))))) (p :class "text-sm text-stone-500 mt-1" "All 6 sub-phases (7a7f) complete."))))))

View File

@@ -8,7 +8,7 @@
(~doc-section :title "The Opportunity" :id "opportunity" (~doc-section :title "The Opportunity" :id "opportunity"
(p "SX already has types. Every primitive in " (code "primitives.sx") " declares " (code ":returns \"number\"") " or " (code ":returns \"boolean\"") ". Every IO primitive in " (code "boundary.sx") " declares " (code ":returns \"dict?\"") " or " (code ":returns \"any\"") ". Component params are named. The information exists — nobody checks it.") (p "SX already has types. Every primitive in " (code "primitives.sx") " declares " (code ":returns \"number\"") " or " (code ":returns \"boolean\"") ". Every IO primitive in " (code "boundary.sx") " declares " (code ":returns \"dict?\"") " or " (code ":returns \"any\"") ". Component params are named. The information exists — nobody checks it.")
(p "A gradual type system makes this information useful. Annotations are optional. Unannotated code works exactly as before. Annotated code gets checked at registration time — zero runtime cost, errors before any request is served. The checker is a spec module (" (code "types.sx") "), bootstrapped to every host.") (p "A gradual type system makes this information useful. Annotations are optional. Unannotated code works exactly as before. Annotated code gets checked at registration time — zero runtime cost, errors before any request is served. The checker is a spec module (" (code "types.sx") "), bootstrapped to every host.")
(p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") " already verifies primitive properties by exhaustive search. Types serve a different purpose: they catch " (strong "composition errors") " — wrong argument passed to a component, mismatched return type piped into another function, missing keyword arg. The kind of bug you find by reading the stack trace and slapping your forehead.")) (p "This is not Haskell. SX doesn't need a type system to be correct — " (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") " already verifies primitive properties by exhaustive search. Types serve a different purpose: they catch " (strong "composition errors") " — wrong argument passed to a component, mismatched return type piped into another function, missing keyword arg. The kind of bug you find by reading the stack trace and slapping your forehead."))
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
;; What already exists ;; What already exists
@@ -111,7 +111,7 @@
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (strong "Runtime values.") " " (code "(if condition 42 \"hello\")") " — the type is " (code "(or number string)") ". The checker doesn't know which branch executes.") (li (strong "Runtime values.") " " (code "(if condition 42 \"hello\")") " — the type is " (code "(or number string)") ". The checker doesn't know which branch executes.")
(li (strong "Dict key presence.") " " (code "(get user \"name\")") " — the checker knows " (code "get") " returns " (code "any") " but doesn't track which keys a dict has. (Future: typed dicts/records.)") (li (strong "Dict key presence.") " " (code "(get user \"name\")") " — the checker knows " (code "get") " returns " (code "any") " but doesn't track which keys a dict has. (Future: typed dicts/records.)")
(li (strong "Termination.") " That's " (a :href "/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") "'s domain.") (li (strong "Termination.") " That's " (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") "'s domain.")
(li (strong "Effects.") " Purity is already enforced by " (code "deps.sx") " + boundary. Types don't duplicate it."))) (li (strong "Effects.") " Purity is already enforced by " (code "deps.sx") " + boundary. Types don't duplicate it.")))
(~doc-subsection :title "Inference" (~doc-subsection :title "Inference"
@@ -225,7 +225,7 @@
;; ----------------------------------------------------------------------- ;; -----------------------------------------------------------------------
(~doc-section :title "Types vs Proofs" :id "types-vs-proofs" (~doc-section :title "Types vs Proofs" :id "types-vs-proofs"
(p (a :href "/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:") (p (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "prove.sx") " and types.sx are complementary, not competing:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4" (div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm" (table :class "w-full text-left text-sm"
@@ -351,10 +351,10 @@
(~doc-section :title "Relationships" :id "relationships" (~doc-section :title "Relationships" :id "relationships"
(ul :class "list-disc pl-5 text-stone-700 space-y-1" (ul :class "list-disc pl-5 text-stone-700 space-y-1"
(li (a :href "/plans/theorem-prover" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.") (li (a :href "/etc/plans/theorem-prover" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
(li (a :href "/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.") (li (a :href "/etc/plans/content-addressed-components" :class "text-violet-700 underline" "Content-Addressed Components") " — component manifests gain type signatures. A consumer knows param types before fetching the source.")
(li (a :href "/plans/environment-images" :class "text-violet-700 underline" "Environment Images") " — the type registry serializes into the image. Type checking happens once at image build time, not on every startup.") (li (a :href "/etc/plans/environment-images" :class "text-violet-700 underline" "Environment Images") " — the type registry serializes into the image. Type checking happens once at image build time, not on every startup.")
(li (a :href "/plans/runtime-slicing" :class "text-violet-700 underline" "Runtime Slicing") " — types.sx is a registration-time module, not a runtime module. It doesn't ship to the client. Zero impact on bundle size.")) (li (a :href "/etc/plans/runtime-slicing" :class "text-violet-700 underline" "Runtime Slicing") " — types.sx is a registration-time module, not a runtime module. It doesn't ship to the client. Zero impact on bundle size."))
(div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2" (div :class "rounded border border-amber-200 bg-amber-50 p-3 mt-2"
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "primitives.sx (return types exist), boundary.sx (IO return types exist), eval.sx (defcomp parsing). " (strong "New: ") (code "types.sx") " spec module, type annotations in " (code "parse-comp-params") ".")) (p :class "text-amber-800 text-sm" (strong "Depends on: ") "primitives.sx (return types exist), boundary.sx (IO return types exist), eval.sx (defcomp parsing). " (strong "New: ") (code "types.sx") " spec module, type annotations in " (code "parse-comp-params") "."))

View File

@@ -28,7 +28,7 @@
(ul :class "space-y-2 text-stone-600 list-disc pl-5" (ul :class "space-y-2 text-stone-600 list-disc pl-5"
(li (strong "Swap inside island: ") "Signals survive. The lake content is replaced but the island's signal closures are untouched. Effects re-bind to new DOM nodes if needed.") (li (strong "Swap inside island: ") "Signals survive. The lake content is replaced but the island's signal closures are untouched. Effects re-bind to new DOM nodes if needed.")
(li (strong "Swap outside island: ") "Signals survive. The island is not affected by swaps to other parts of the page.") (li (strong "Swap outside island: ") "Signals survive. The island is not affected by swaps to other parts of the page.")
(li (strong "Swap replaces island: ") "Signals are " (em "lost") ". The island is disposed. This is where " (a :href "/reactive/named-stores" :sx-get "/reactive/named-stores" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "named stores") " come in — they persist at page level, surviving island destruction."))) (li (strong "Swap replaces island: ") "Signals are " (em "lost") ". The island is disposed. This is where " (a :href "/geography/reactive/named-stores" :sx-get "/geography/reactive/named-stores" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "named stores") " come in — they persist at page level, surviving island destruction.")))
(~doc-section :title "Spec" :id "spec" (~doc-section :title "Spec" :id "spec"
(p "The event bridge is spec'd in " (code "signals.sx") " (sections 12-13). Three functions:") (p "The event bridge is spec'd in " (code "signals.sx") " (sections 12-13). Three functions:")

View File

@@ -145,7 +145,7 @@
(td :class "px-3 py-2 text-stone-700" "Phase 2 remaining") (td :class "px-3 py-2 text-stone-700" "Phase 2 remaining")
(td :class "px-3 py-2 text-stone-500 font-medium" "P2") (td :class "px-3 py-2 text-stone-500 font-medium" "P2")
(td :class "px-3 py-2 font-mono text-xs text-stone-500" (td :class "px-3 py-2 font-mono text-xs text-stone-500"
(a :href "/reactive/phase2" :sx-get "/reactive/phase2" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "Error boundaries + resource + patterns"))) (a :href "/geography/reactive/phase2" :sx-get "/geography/reactive/phase2" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "Error boundaries + resource + patterns")))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Error boundaries") (td :class "px-3 py-2 text-stone-700" "Error boundaries")
(td :class "px-3 py-2 text-green-700 font-medium" "Done") (td :class "px-3 py-2 text-green-700 font-medium" "Done")

View File

@@ -291,7 +291,7 @@
(~demo-marsh-product) (~demo-marsh-product)
(~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/reactive/api/flash-sale\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp")) (~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/geography/reactive/api/flash-sale\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp"))
(~doc-code :code (highlight ";; Server returns SX content + a data-init script:\n;;\n;; (<>\n;; (p \"Flash sale! Price: $14.99\")\n;; (script :type \"text/sx\" :data-init\n;; \"(reset! (use-store \\\"demo-price\\\") 14.99)\"))\n;;\n;; The <p> is swapped in as normal hypermedia content.\n;; The script writes to the store signal.\n;; The island's (deref price), total, and savings\n;; all update reactively — no re-render, no diffing." "lisp")) (~doc-code :code (highlight ";; Server returns SX content + a data-init script:\n;;\n;; (<>\n;; (p \"Flash sale! Price: $14.99\")\n;; (script :type \"text/sx\" :data-init\n;; \"(reset! (use-store \\\"demo-price\\\") 14.99)\"))\n;;\n;; The <p> is swapped in as normal hypermedia content.\n;; The script writes to the store signal.\n;; The island's (deref price), total, and savings\n;; all update reactively — no re-render, no diffing." "lisp"))
@@ -315,7 +315,7 @@
(div :id "marsh-flash-target" (div :id "marsh-flash-target"
:class "min-h-[2rem]") :class "min-h-[2rem]")
(button :class "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" (button :class "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/reactive/api/flash-sale" :sx-get "/geography/reactive/api/flash-sale"
:sx-target "#marsh-flash-target" :sx-target "#marsh-flash-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
"Fetch from server")) "Fetch from server"))
@@ -334,7 +334,7 @@
(~demo-marsh-settle) (~demo-marsh-settle)
(~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/reactive/api/settle-data\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp")) (~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/geography/reactive/api/settle-data\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp"))
(p "The server knows nothing about signals or counters. It returns plain content. The " (code "sx-on-settle") " hook is a client-side concern — it runs in the global SX environment with access to all primitives.")) (p "The server knows nothing about signals or counters. It returns plain content. The " (code "sx-on-settle") " hook is a client-side concern — it runs in the global SX environment with access to all primitives."))
@@ -348,7 +348,7 @@
(~demo-marsh-signal-url) (~demo-marsh-signal-url)
(~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/reactive/api/search/\"\n (deref mode) \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp")) (~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/geography/reactive/api/search/\"\n (deref mode) \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp"))
(p "No custom plumbing. The same " (code "reactive-attr") " mechanism that makes " (code ":class") " reactive also makes " (code ":sx-get") " reactive. " (code "get-verb-info") " reads " (code "dom-get-attr") " at trigger time — it sees the current URL because the effect already updated the DOM attribute.")) (p "No custom plumbing. The same " (code "reactive-attr") " mechanism that makes " (code ":class") " reactive also makes " (code ":sx-get") " reactive. " (code "get-verb-info") " reads " (code "dom-get-attr") " at trigger time — it sees the current URL because the effect already updated the DOM attribute."))
@@ -361,7 +361,7 @@
(~demo-marsh-view-transform) (~demo-marsh-view-transform)
(~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/reactive/api/catalog\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp")) (~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/geography/reactive/api/catalog\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp"))
(p "The view signal doesn't just toggle CSS classes — it fundamentally reshapes the DOM. List view shows description. Grid view arranges in columns. Compact view shows names only. All from the same server data, transformed by client state.")) (p "The view signal doesn't just toggle CSS classes — it fundamentally reshapes the DOM. List view shows description. Grid view arranges in columns. Compact view shows names only. All from the same server data, transformed by client state."))
@@ -408,7 +408,7 @@
(div :class "border-t border-stone-200 pt-3" (div :class "border-t border-stone-200 pt-3"
(div :class "flex items-center gap-3" (div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" (button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/reactive/api/flash-sale" :sx-get "/geography/reactive/api/flash-sale"
:sx-target "#marsh-server-msg" :sx-target "#marsh-server-msg"
:sx-swap "innerHTML" :sx-swap "innerHTML"
"Fetch Price from Server") "Fetch Price from Server")
@@ -471,7 +471,7 @@
(span :class "text-sm text-stone-600" " times"))) (span :class "text-sm text-stone-600" " times")))
(div :class "flex items-center gap-3" (div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" (button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/reactive/api/settle-data" :sx-get "/geography/reactive/api/settle-data"
:sx-target "#settle-result" :sx-target "#settle-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on-settle "(swap! (use-store \"settle-count\") inc)" :sx-on-settle "(swap! (use-store \"settle-count\") inc)"
@@ -516,7 +516,7 @@
:class "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400") :class "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400")
(button :class "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" (button :class "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get (computed (fn () :sx-get (computed (fn ()
(str "/reactive/api/search/" (deref mode) (str "/geography/reactive/api/search/" (deref mode)
"?q=" (deref query)))) "?q=" (deref query))))
:sx-target "#signal-results" :sx-target "#signal-results"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -524,7 +524,7 @@
;; Current URL display ;; Current URL display
(p :class "text-xs text-stone-400 font-mono" (p :class "text-xs text-stone-400 font-mono"
"URL: " (computed (fn () "URL: " (computed (fn ()
(str "/reactive/api/search/" (deref mode) "?q=" (deref query))))) (str "/geography/reactive/api/search/" (deref mode) "?q=" (deref query)))))
;; Results ;; Results
(div :id "signal-results" :class "min-h-[3rem] rounded bg-stone-50 p-2" (div :id "signal-results" :class "min-h-[3rem] rounded bg-stone-50 p-2"
(p :class "text-sm text-stone-400 italic" "Select a category and search."))))) (p :class "text-sm text-stone-400 italic" "Select a category and search.")))))
@@ -561,7 +561,7 @@
;; Fetch button — response writes structured data to store via data-init ;; Fetch button — response writes structured data to store via data-init
(div :class "flex items-center gap-3" (div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700" (button :class "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700"
:sx-get "/reactive/api/catalog" :sx-get "/geography/reactive/api/catalog"
:sx-target "#catalog-msg" :sx-target "#catalog-msg"
:sx-swap "innerHTML" :sx-swap "innerHTML"
"Fetch Catalog") "Fetch Catalog")

View File

@@ -46,7 +46,7 @@
(~doc-section :title "htmx Lakes" :id "lakes" (~doc-section :title "htmx Lakes" :id "lakes"
(p "An htmx lake is server-driven content " (em "inside") " a reactive island. The island provides the reactive boundary; the lake content is swapped via " (code "sx-get") "/" (code "sx-post") " like normal hypermedia.") (p "An htmx lake is server-driven content " (em "inside") " a reactive island. The island provides the reactive boundary; the lake content is swapped via " (code "sx-get") "/" (code "sx-post") " like normal hypermedia.")
(p "This works because signals live in JavaScript closures, not in the DOM. When a swap replaces lake content, the island's signals are unaffected. The lake can communicate back to the island via the " (a :href "/reactive/event-bridge" :sx-get "/reactive/event-bridge" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "event bridge") ".") (p "This works because signals live in JavaScript closures, not in the DOM. When a swap replaces lake content, the island's signals are unaffected. The lake can communicate back to the island via the " (a :href "/geography/reactive/event-bridge" :sx-get "/geography/reactive/event-bridge" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "event bridge") ".")
(~doc-subsection :title "Navigation scenarios" (~doc-subsection :title "Navigation scenarios"
(div :class "space-y-3" (div :class "space-y-3"
@@ -155,7 +155,7 @@
(li (strong "Spec-first.") " Signal semantics live in " (code "signals.sx") ". Bootstrapped to JS and Python. The same primitives will work in future hosts — Go, Rust, native.") (li (strong "Spec-first.") " Signal semantics live in " (code "signals.sx") ". Bootstrapped to JS and Python. The same primitives will work in future hosts — Go, Rust, native.")
(li (strong "No build step.") " Reactive bindings are created at runtime during DOM rendering. No JSX compilation, no Babel transforms, no Vite plugins.")) (li (strong "No build step.") " Reactive bindings are created at runtime during DOM rendering. No JSX compilation, no Babel transforms, no Vite plugins."))
(p :class "mt-4" "The recommendation from the " (a :href "/essays/client-reactivity" :class "text-violet-700 underline" "Client Reactivity") " essay was: \"Tier 4 probably never.\" This plan is what happens when the answer changes. The design avoids every footgun that essay warns about — no useState cascading to useEffect cascading to Context cascading to a state management library. Signals are one primitive. Islands are one boundary. The rest is composition.")))) (p :class "mt-4" "The recommendation from the " (a :href "/etc/essays/client-reactivity" :class "text-violet-700 underline" "Client Reactivity") " essay was: \"Tier 4 probably never.\" This plan is what happens when the answer changes. The design avoids every footgun that essay warns about — no useState cascading to useEffect cascading to Context cascading to a state management library. Signals are one primitive. Islands are one boundary. The rest is composition."))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------

View File

@@ -14,9 +14,9 @@
"Pages with data dependencies fall back to " "Pages with data dependencies fall back to "
(span :class "text-amber-700 font-medium" "server fetch") (span :class "text-amber-700 font-medium" "server fetch")
" transparently. Powered by " " transparently. Powered by "
(a :href "/specs/router" :class "text-violet-700 underline" "router.sx") (a :href "/language/specs/router" :class "text-violet-700 underline" "router.sx")
" route matching and " " route matching and "
(a :href "/specs/deps" :class "text-violet-700 underline" "deps.sx") (a :href "/language/specs/deps" :class "text-violet-700 underline" "deps.sx")
" IO detection.") " IO detection.")
(div :class "mb-8 grid grid-cols-4 gap-4" (div :class "mb-8 grid grid-cols-4 gap-4"

View File

@@ -28,36 +28,36 @@
(tbody (tbody
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/parser" :class "hover:underline" (a :href "/language/specs/parser" :class "hover:underline"
:sx-get "/specs/parser" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/parser" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"parser.sx")) "parser.sx"))
(td :class "px-3 py-2 text-stone-700" "Tokenization and parsing of SX source text into AST")) (td :class "px-3 py-2 text-stone-700" "Tokenization and parsing of SX source text into AST"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/evaluator" :class "hover:underline" (a :href "/language/specs/evaluator" :class "hover:underline"
:sx-get "/specs/evaluator" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/evaluator" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"eval.sx")) "eval.sx"))
(td :class "px-3 py-2 text-stone-700" "Tree-walking evaluation of SX expressions")) (td :class "px-3 py-2 text-stone-700" "Tree-walking evaluation of SX expressions"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/primitives" :class "hover:underline" (a :href "/language/specs/primitives" :class "hover:underline"
:sx-get "/specs/primitives" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/primitives" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"primitives.sx")) "primitives.sx"))
(td :class "px-3 py-2 text-stone-700" "All built-in pure functions and their signatures")) (td :class "px-3 py-2 text-stone-700" "All built-in pure functions and their signatures"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/special-forms" :class "hover:underline" (a :href "/language/specs/special-forms" :class "hover:underline"
:sx-get "/specs/special-forms" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/special-forms" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"special-forms.sx")) "special-forms.sx"))
(td :class "px-3 py-2 text-stone-700" "All special forms — syntactic constructs with custom evaluation rules")) (td :class "px-3 py-2 text-stone-700" "All special forms — syntactic constructs with custom evaluation rules"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/renderer" :class "hover:underline" (a :href "/language/specs/renderer" :class "hover:underline"
:sx-get "/specs/renderer" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/renderer" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"render.sx")) "render.sx"))
(td :class "px-3 py-2 text-stone-700" "Shared rendering registries and utilities used by all adapters")))))) (td :class "px-3 py-2 text-stone-700" "Shared rendering registries and utilities used by all adapters"))))))
@@ -75,24 +75,24 @@
(tbody (tbody
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/adapter-dom" :class "hover:underline" (a :href "/language/specs/adapter-dom" :class "hover:underline"
:sx-get "/specs/adapter-dom" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/adapter-dom" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"adapter-dom.sx")) "adapter-dom.sx"))
(td :class "px-3 py-2 text-stone-700" "Live DOM nodes") (td :class "px-3 py-2 text-stone-700" "Live DOM nodes")
(td :class "px-3 py-2 text-stone-500" "Browser")) (td :class "px-3 py-2 text-stone-500" "Browser"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/adapter-html" :class "hover:underline" (a :href "/language/specs/adapter-html" :class "hover:underline"
:sx-get "/specs/adapter-html" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/adapter-html" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"adapter-html.sx")) "adapter-html.sx"))
(td :class "px-3 py-2 text-stone-700" "HTML strings") (td :class "px-3 py-2 text-stone-700" "HTML strings")
(td :class "px-3 py-2 text-stone-500" "Server")) (td :class "px-3 py-2 text-stone-500" "Server"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/adapter-sx" :class "hover:underline" (a :href "/language/specs/adapter-sx" :class "hover:underline"
:sx-get "/specs/adapter-sx" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/adapter-sx" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"adapter-sx.sx")) "adapter-sx.sx"))
(td :class "px-3 py-2 text-stone-700" "SX wire format") (td :class "px-3 py-2 text-stone-700" "SX wire format")
@@ -116,15 +116,15 @@
(tbody (tbody
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/engine" :class "hover:underline" (a :href "/language/specs/engine" :class "hover:underline"
:sx-get "/specs/engine" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/engine" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"engine.sx")) "engine.sx"))
(td :class "px-3 py-2 text-stone-700" "Pure logic — trigger parsing, swap algorithms, morph, history, SSE, indicators")) (td :class "px-3 py-2 text-stone-700" "Pure logic — trigger parsing, swap algorithms, morph, history, SSE, indicators"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/orchestration" :class "hover:underline" (a :href "/language/specs/orchestration" :class "hover:underline"
:sx-get "/specs/orchestration" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/orchestration" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"orchestration.sx")) "orchestration.sx"))
(td :class "px-3 py-2 text-stone-700" "Browser wiring — binds engine to DOM events, fetch, request lifecycle")))))) (td :class "px-3 py-2 text-stone-700" "Browser wiring — binds engine to DOM events, fetch, request lifecycle"))))))
@@ -145,15 +145,15 @@
(tbody (tbody
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/boot" :class "hover:underline" (a :href "/language/specs/boot" :class "hover:underline"
:sx-get "/specs/boot" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/boot" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"boot.sx")) "boot.sx"))
(td :class "px-3 py-2 text-stone-700" "Browser startup lifecycle — mount, hydrate, script processing, head hoisting")) (td :class "px-3 py-2 text-stone-700" "Browser startup lifecycle — mount, hydrate, script processing, head hoisting"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/cssx" :class "hover:underline" (a :href "/language/specs/cssx" :class "hover:underline"
:sx-get "/specs/cssx" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/cssx" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"cssx.sx")) "cssx.sx"))
(td :class "px-3 py-2 text-stone-700" "On-demand CSS — style dictionary, keyword resolution, rule injection")))))) (td :class "px-3 py-2 text-stone-700" "On-demand CSS — style dictionary, keyword resolution, rule injection"))))))
@@ -198,16 +198,16 @@ router.sx (standalone — pure string/list ops)")))
(tbody (tbody
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/continuations" :class "hover:underline" (a :href "/language/specs/continuations" :class "hover:underline"
:sx-get "/specs/continuations" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/continuations" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"continuations.sx")) "continuations.sx"))
(td :class "px-3 py-2 text-stone-700" "Delimited continuations — shift/reset") (td :class "px-3 py-2 text-stone-700" "Delimited continuations — shift/reset")
(td :class "px-3 py-2 text-stone-500" "All targets")) (td :class "px-3 py-2 text-stone-500" "All targets"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/specs/callcc" :class "hover:underline" (a :href "/language/specs/callcc" :class "hover:underline"
:sx-get "/specs/callcc" :sx-target "#main-panel" :sx-select "#main-panel" :sx-get "/language/specs/callcc" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
"callcc.sx")) "callcc.sx"))
(td :class "px-3 py-2 text-stone-700" "Full first-class continuations — call/cc") (td :class "px-3 py-2 text-stone-700" "Full first-class continuations — call/cc")
@@ -306,21 +306,21 @@ router.sx (standalone — pure string/list ops)")))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "JavaScript") (td :class "px-3 py-2 text-stone-700" "JavaScript")
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/bootstrappers/javascript" :class "hover:underline" (a :href "/language/bootstrappers/javascript" :class "hover:underline"
"bootstrap_js.py")) "bootstrap_js.py"))
(td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx-browser.js") (td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx-browser.js")
(td :class "px-3 py-2 text-green-600" "Live")) (td :class "px-3 py-2 text-green-600" "Live"))
(tr :class "border-b border-stone-100" (tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Python") (td :class "px-3 py-2 text-stone-700" "Python")
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/bootstrappers/python" :class "hover:underline" (a :href "/language/bootstrappers/python" :class "hover:underline"
"bootstrap_py.py")) "bootstrap_py.py"))
(td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx_ref.py") (td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx_ref.py")
(td :class "px-3 py-2 text-green-600" "Live")) (td :class "px-3 py-2 text-green-600" "Live"))
(tr :class "border-b border-stone-100 bg-green-50" (tr :class "border-b border-stone-100 bg-green-50"
(td :class "px-3 py-2 text-stone-700" "Python (self-hosting)") (td :class "px-3 py-2 text-stone-700" "Python (self-hosting)")
(td :class "px-3 py-2 font-mono text-sm text-violet-700" (td :class "px-3 py-2 font-mono text-sm text-violet-700"
(a :href "/bootstrappers/self-hosting" :class "hover:underline" (a :href "/language/bootstrappers/self-hosting" :class "hover:underline"
"py.sx")) "py.sx"))
(td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx_ref.py") (td :class "px-3 py-2 font-mono text-sm text-stone-500" "sx_ref.py")
(td :class "px-3 py-2 text-green-600" "G0 == G1")) (td :class "px-3 py-2 text-green-600" "G0 == G1"))

View File

@@ -98,27 +98,27 @@ Per-spec platform functions:
(div :class "space-y-3" (div :class "space-y-3"
(h2 :class "text-2xl font-semibold text-stone-800" "Test specs") (h2 :class "text-2xl font-semibold text-stone-800" "Test specs")
(div :class "grid grid-cols-1 md:grid-cols-2 gap-4" (div :class "grid grid-cols-1 md:grid-cols-2 gap-4"
(a :href "/testing/eval" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors" (a :href "/language/testing/eval" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Evaluator") (h3 :class "font-semibold text-stone-800" "Evaluator")
(p :class "text-sm text-stone-500" "81 tests — literals, arithmetic, strings, lists, dicts, special forms, lambdas, components, macros") (p :class "text-sm text-stone-500" "81 tests — literals, arithmetic, strings, lists, dicts, special forms, lambdas, components, macros")
(p :class "text-xs text-violet-600 mt-1" "test-eval.sx")) (p :class "text-xs text-violet-600 mt-1" "test-eval.sx"))
(a :href "/testing/parser" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors" (a :href "/language/testing/parser" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Parser") (h3 :class "font-semibold text-stone-800" "Parser")
(p :class "text-sm text-stone-500" "39 tests — tokenization, parsing, escape sequences, quote sugar, serialization, round-trips") (p :class "text-sm text-stone-500" "39 tests — tokenization, parsing, escape sequences, quote sugar, serialization, round-trips")
(p :class "text-xs text-violet-600 mt-1" "test-parser.sx")) (p :class "text-xs text-violet-600 mt-1" "test-parser.sx"))
(a :href "/testing/router" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors" (a :href "/language/testing/router" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Router") (h3 :class "font-semibold text-stone-800" "Router")
(p :class "text-sm text-stone-500" "18 tests — path splitting, pattern parsing, segment matching, parameter extraction") (p :class "text-sm text-stone-500" "18 tests — path splitting, pattern parsing, segment matching, parameter extraction")
(p :class "text-xs text-violet-600 mt-1" "test-router.sx")) (p :class "text-xs text-violet-600 mt-1" "test-router.sx"))
(a :href "/testing/render" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors" (a :href "/language/testing/render" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Renderer") (h3 :class "font-semibold text-stone-800" "Renderer")
(p :class "text-sm text-stone-500" "23 tests — elements, attributes, void elements, fragments, escaping, control flow, components") (p :class "text-sm text-stone-500" "23 tests — elements, attributes, void elements, fragments, escaping, control flow, components")
(p :class "text-xs text-violet-600 mt-1" "test-render.sx")) (p :class "text-xs text-violet-600 mt-1" "test-render.sx"))
(a :href "/testing/deps" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors" (a :href "/language/testing/deps" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Dependencies") (h3 :class "font-semibold text-stone-800" "Dependencies")
(p :class "text-sm text-stone-500" "33 tests — scan-refs, transitive-deps, components-needed, IO detection, purity classification") (p :class "text-sm text-stone-500" "33 tests — scan-refs, transitive-deps, components-needed, IO detection, purity classification")
(p :class "text-xs text-violet-600 mt-1" "test-deps.sx")) (p :class "text-xs text-violet-600 mt-1" "test-deps.sx"))
(a :href "/testing/engine" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors" (a :href "/language/testing/engine" :class "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors"
(h3 :class "font-semibold text-stone-800" "Engine") (h3 :class "font-semibold text-stone-800" "Engine")
(p :class "text-sm text-stone-500" "37 tests — parse-time, trigger specs, swap specs, retry logic, param filtering") (p :class "text-sm text-stone-500" "37 tests — parse-time, trigger specs, swap specs, retry logic, param filtering")
(p :class "text-xs text-violet-600 mt-1" "test-engine.sx")))) (p :class "text-xs text-violet-600 mt-1" "test-engine.sx"))))

View File

@@ -22,7 +22,7 @@
(div :id "click-result" :class "p-4 rounded bg-stone-100 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.") "Click the button to load content.")
(button (button
:sx-get "/hypermedia/examples/api/click" :sx-get "/geography/hypermedia/examples/api/click"
:sx-target "#click-result" :sx-target "#click-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors"
@@ -39,7 +39,7 @@
(defcomp ~form-demo () (defcomp ~form-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(form (form
:sx-post "/hypermedia/examples/api/form" :sx-post "/geography/hypermedia/examples/api/form"
:sx-target "#form-result" :sx-target "#form-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "space-y-3" :class "space-y-3"
@@ -63,7 +63,7 @@
(defcomp ~polling-demo () (defcomp ~polling-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(div :id "poll-target" (div :id "poll-target"
:sx-get "/hypermedia/examples/api/poll" :sx-get "/geography/hypermedia/examples/api/poll"
:sx-trigger "load, every 2s" :sx-trigger "load, every 2s"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono"
@@ -99,7 +99,7 @@
(td :class "px-3 py-2 text-stone-700" name) (td :class "px-3 py-2 text-stone-700" name)
(td :class "px-3 py-2" (td :class "px-3 py-2"
(button (button
:sx-delete (str "/hypermedia/examples/api/delete/" id) :sx-delete (str "/geography/hypermedia/examples/api/delete/" id)
:sx-target (str "#row-" id) :sx-target (str "#row-" id)
:sx-swap "outerHTML" :sx-swap "outerHTML"
:sx-confirm "Delete this item?" :sx-confirm "Delete this item?"
@@ -116,7 +116,7 @@
(div :class "flex items-center justify-between p-3 rounded border border-stone-200" (div :class "flex items-center justify-between p-3 rounded border border-stone-200"
(span :class "text-stone-800" value) (span :class "text-stone-800" value)
(button (button
:sx-get (str "/hypermedia/examples/api/edit?value=" value) :sx-get (str "/geography/hypermedia/examples/api/edit?value=" value)
:sx-target "#edit-target" :sx-target "#edit-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "text-sm text-violet-600 hover:text-violet-800" :class "text-sm text-violet-600 hover:text-violet-800"
@@ -124,7 +124,7 @@
(defcomp ~inline-edit-form (&key value) (defcomp ~inline-edit-form (&key value)
(form (form
:sx-post "/hypermedia/examples/api/edit" :sx-post "/geography/hypermedia/examples/api/edit"
:sx-target "#edit-target" :sx-target "#edit-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "flex items-center gap-2 p-3 rounded border border-violet-300 bg-violet-50" :class "flex items-center gap-2 p-3 rounded border border-violet-300 bg-violet-50"
@@ -134,7 +134,7 @@
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"save") "save")
(button :type "button" (button :type "button"
:sx-get (str "/hypermedia/examples/api/edit/cancel?value=" value) :sx-get (str "/geography/hypermedia/examples/api/edit/cancel?value=" value)
:sx-target "#edit-target" :sx-target "#edit-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" :class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
@@ -152,7 +152,7 @@
(p :class "text-stone-500" "Box B") (p :class "text-stone-500" "Box B")
(p :class "text-sm text-stone-400" "Waiting..."))) (p :class "text-sm text-stone-400" "Waiting...")))
(button (button
:sx-get "/hypermedia/examples/api/oob" :sx-get "/geography/hypermedia/examples/api/oob"
:sx-target "#oob-box-a" :sx-target "#oob-box-a"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -164,7 +164,7 @@
(div :class "space-y-4" (div :class "space-y-4"
(p :class "text-sm text-stone-500" "The content below loads automatically when the page renders.") (p :class "text-sm text-stone-500" "The content below loads automatically when the page renders.")
(div :id "lazy-target" (div :id "lazy-target"
:sx-get "/hypermedia/examples/api/lazy" :sx-get "/geography/hypermedia/examples/api/lazy"
:sx-trigger "load" :sx-trigger "load"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "p-6 rounded border border-stone-200 bg-stone-100 text-center" :class "p-6 rounded border border-stone-200 bg-stone-100 text-center"
@@ -187,7 +187,7 @@
(str "Item " (+ i 1) " — loaded with the page"))) (str "Item " (+ i 1) " — loaded with the page")))
(list 1 2 3 4 5)) (list 1 2 3 4 5))
(div :id "scroll-sentinel" (div :id "scroll-sentinel"
:sx-get "/hypermedia/examples/api/scroll?page=2" :sx-get "/geography/hypermedia/examples/api/scroll?page=2"
:sx-trigger "intersect once" :sx-trigger "intersect once"
:sx-target "#scroll-items" :sx-target "#scroll-items"
:sx-swap "beforeend" :sx-swap "beforeend"
@@ -201,7 +201,7 @@
items) items)
(when (<= page 5) (when (<= page 5)
(div :id "scroll-sentinel" (div :id "scroll-sentinel"
:sx-get (str "/hypermedia/examples/api/scroll?page=" page) :sx-get (str "/geography/hypermedia/examples/api/scroll?page=" page)
:sx-trigger "intersect once" :sx-trigger "intersect once"
:sx-target "#scroll-items" :sx-target "#scroll-items"
:sx-swap "beforeend" :sx-swap "beforeend"
@@ -217,7 +217,7 @@
(div :class "bg-violet-600 h-4 rounded-full transition-all" :style "width: 0%")) (div :class "bg-violet-600 h-4 rounded-full transition-all" :style "width: 0%"))
(p :class "text-sm text-stone-500 text-center" "Click start to begin.")) (p :class "text-sm text-stone-500 text-center" "Click start to begin."))
(button (button
:sx-post "/hypermedia/examples/api/progress/start" :sx-post "/geography/hypermedia/examples/api/progress/start"
:sx-target "#progress-target" :sx-target "#progress-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -230,7 +230,7 @@
:style (str "width: " percent "%"))) :style (str "width: " percent "%")))
(p :class "text-sm text-stone-500 text-center" (str percent "% complete")) (p :class "text-sm text-stone-500 text-center" (str percent "% complete"))
(when (< percent 100) (when (< percent 100)
(div :sx-get (str "/hypermedia/examples/api/progress/status?job=" job-id) (div :sx-get (str "/geography/hypermedia/examples/api/progress/status?job=" job-id)
:sx-trigger "load delay:500ms" :sx-trigger "load delay:500ms"
:sx-target "#progress-target" :sx-target "#progress-target"
:sx-swap "innerHTML")) :sx-swap "innerHTML"))
@@ -242,7 +242,7 @@
(defcomp ~active-search-demo () (defcomp ~active-search-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(input :type "text" :name "q" (input :type "text" :name "q"
:sx-get "/hypermedia/examples/api/search" :sx-get "/geography/hypermedia/examples/api/search"
:sx-trigger "keyup delay:300ms changed" :sx-trigger "keyup delay:300ms changed"
:sx-target "#search-results" :sx-target "#search-results"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -262,11 +262,11 @@
;; --- Inline validation demo --- ;; --- Inline validation demo ---
(defcomp ~inline-validation-demo () (defcomp ~inline-validation-demo ()
(form :class "space-y-4" :sx-post "/hypermedia/examples/api/validate/submit" :sx-target "#validation-result" :sx-swap "innerHTML" (form :class "space-y-4" :sx-post "/geography/hypermedia/examples/api/validate/submit" :sx-target "#validation-result" :sx-swap "innerHTML"
(div (div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Email") (label :class "block text-sm font-medium text-stone-700 mb-1" "Email")
(input :type "text" :name "email" :placeholder "user@example.com" (input :type "text" :name "email" :placeholder "user@example.com"
:sx-get "/hypermedia/examples/api/validate" :sx-get "/geography/hypermedia/examples/api/validate"
:sx-trigger "blur" :sx-trigger "blur"
:sx-target "#email-feedback" :sx-target "#email-feedback"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -290,7 +290,7 @@
(div (div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Category") (label :class "block text-sm font-medium text-stone-700 mb-1" "Category")
(select :name "category" (select :name "category"
:sx-get "/hypermedia/examples/api/values" :sx-get "/geography/hypermedia/examples/api/values"
:sx-trigger "change" :sx-trigger "change"
:sx-target "#value-items" :sx-target "#value-items"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -314,7 +314,7 @@
(defcomp ~reset-on-submit-demo () (defcomp ~reset-on-submit-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form :id "reset-form" (form :id "reset-form"
:sx-post "/hypermedia/examples/api/reset-submit" :sx-post "/geography/hypermedia/examples/api/reset-submit"
:sx-target "#reset-result" :sx-target "#reset-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:afterSwap "this.reset()" :sx-on:afterSwap "this.reset()"
@@ -354,7 +354,7 @@
(td :class "px-3 py-2 text-stone-700" stock) (td :class "px-3 py-2 text-stone-700" stock)
(td :class "px-3 py-2" (td :class "px-3 py-2"
(button (button
:sx-get (str "/hypermedia/examples/api/editrow/" id) :sx-get (str "/geography/hypermedia/examples/api/editrow/" id)
:sx-target (str "#erow-" id) :sx-target (str "#erow-" id)
:sx-swap "outerHTML" :sx-swap "outerHTML"
:class "text-sm text-violet-600 hover:text-violet-800" :class "text-sm text-violet-600 hover:text-violet-800"
@@ -373,14 +373,14 @@
:class "w-20 px-2 py-1 border border-stone-300 rounded text-sm")) :class "w-20 px-2 py-1 border border-stone-300 rounded text-sm"))
(td :class "px-3 py-2 space-x-1" (td :class "px-3 py-2 space-x-1"
(button (button
:sx-post (str "/hypermedia/examples/api/editrow/" id) :sx-post (str "/geography/hypermedia/examples/api/editrow/" id)
:sx-target (str "#erow-" id) :sx-target (str "#erow-" id)
:sx-swap "outerHTML" :sx-swap "outerHTML"
:sx-include (str "#erow-" id) :sx-include (str "#erow-" id)
:class "text-sm text-emerald-600 hover:text-emerald-800" :class "text-sm text-emerald-600 hover:text-emerald-800"
"save") "save")
(button (button
:sx-get (str "/hypermedia/examples/api/editrow/" id "/cancel") :sx-get (str "/geography/hypermedia/examples/api/editrow/" id "/cancel")
:sx-target (str "#erow-" id) :sx-target (str "#erow-" id)
:sx-swap "outerHTML" :sx-swap "outerHTML"
:class "text-sm text-stone-500 hover:text-stone-700" :class "text-sm text-stone-500 hover:text-stone-700"
@@ -393,14 +393,14 @@
(form :id "bulk-form" (form :id "bulk-form"
(div :class "flex gap-2 mb-3" (div :class "flex gap-2 mb-3"
(button :type "button" (button :type "button"
:sx-post "/hypermedia/examples/api/bulk?action=activate" :sx-post "/geography/hypermedia/examples/api/bulk?action=activate"
:sx-target "#bulk-table" :sx-target "#bulk-table"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-include "#bulk-form" :sx-include "#bulk-form"
:class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700" :class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
"Activate") "Activate")
(button :type "button" (button :type "button"
:sx-post "/hypermedia/examples/api/bulk?action=deactivate" :sx-post "/geography/hypermedia/examples/api/bulk?action=deactivate"
:sx-target "#bulk-table" :sx-target "#bulk-table"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-include "#bulk-form" :sx-include "#bulk-form"
@@ -437,19 +437,19 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2" (div :class "flex gap-2"
(button (button
:sx-post "/hypermedia/examples/api/swap-log?mode=beforeend" :sx-post "/geography/hypermedia/examples/api/swap-log?mode=beforeend"
:sx-target "#swap-log" :sx-target "#swap-log"
:sx-swap "beforeend" :sx-swap "beforeend"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Add to End") "Add to End")
(button (button
:sx-post "/hypermedia/examples/api/swap-log?mode=afterbegin" :sx-post "/geography/hypermedia/examples/api/swap-log?mode=afterbegin"
:sx-target "#swap-log" :sx-target "#swap-log"
:sx-swap "afterbegin" :sx-swap "afterbegin"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Add to Start") "Add to Start")
(button (button
:sx-post "/hypermedia/examples/api/swap-log?mode=none" :sx-post "/geography/hypermedia/examples/api/swap-log?mode=none"
:sx-target "#swap-log" :sx-target "#swap-log"
:sx-swap "none" :sx-swap "none"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700" :class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
@@ -469,21 +469,21 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2" (div :class "flex gap-2"
(button (button
:sx-get "/hypermedia/examples/api/dashboard" :sx-get "/geography/hypermedia/examples/api/dashboard"
:sx-target "#filter-target" :sx-target "#filter-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-select "#dash-stats" :sx-select "#dash-stats"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Stats Only") "Stats Only")
(button (button
:sx-get "/hypermedia/examples/api/dashboard" :sx-get "/geography/hypermedia/examples/api/dashboard"
:sx-target "#filter-target" :sx-target "#filter-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-select "#dash-header" :sx-select "#dash-header"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Header Only") "Header Only")
(button (button
:sx-get "/hypermedia/examples/api/dashboard" :sx-get "/geography/hypermedia/examples/api/dashboard"
:sx-target "#filter-target" :sx-target "#filter-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700" :class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
@@ -505,10 +505,10 @@
(defcomp ~tab-btn (&key tab label active) (defcomp ~tab-btn (&key tab label active)
(button (button
:sx-get (str "/hypermedia/examples/api/tabs/" tab) :sx-get (str "/geography/hypermedia/examples/api/tabs/" tab)
:sx-target "#tab-content" :sx-target "#tab-content"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-push-url (str "/hypermedia/examples/tabs?tab=" tab) :sx-push-url (str "/geography/hypermedia/examples/tabs?tab=" tab)
:class (str "px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors " :class (str "px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors "
(if (= active "true") (if (= active "true")
"border-violet-600 text-violet-600" "border-violet-600 text-violet-600"
@@ -520,7 +520,7 @@
(defcomp ~animations-demo () (defcomp ~animations-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(button (button
:sx-get "/hypermedia/examples/api/animate" :sx-get "/geography/hypermedia/examples/api/animate"
:sx-target "#anim-target" :sx-target "#anim-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -539,7 +539,7 @@
(defcomp ~dialogs-demo () (defcomp ~dialogs-demo ()
(div (div
(button (button
:sx-get "/hypermedia/examples/api/dialog" :sx-get "/geography/hypermedia/examples/api/dialog"
:sx-target "#dialog-container" :sx-target "#dialog-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -549,7 +549,7 @@
(defcomp ~dialog-modal (&key title message) (defcomp ~dialog-modal (&key title message)
(div :class "fixed inset-0 z-50 flex items-center justify-center" (div :class "fixed inset-0 z-50 flex items-center justify-center"
(div :class "absolute inset-0 bg-black/50" (div :class "absolute inset-0 bg-black/50"
:sx-get "/hypermedia/examples/api/dialog/close" :sx-get "/geography/hypermedia/examples/api/dialog/close"
:sx-target "#dialog-container" :sx-target "#dialog-container"
:sx-swap "innerHTML") :sx-swap "innerHTML")
(div :class "relative bg-stone-100 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"
@@ -557,13 +557,13 @@
(p :class "text-stone-600" message) (p :class "text-stone-600" message)
(div :class "flex justify-end gap-2" (div :class "flex justify-end gap-2"
(button (button
:sx-get "/hypermedia/examples/api/dialog/close" :sx-get "/geography/hypermedia/examples/api/dialog/close"
:sx-target "#dialog-container" :sx-target "#dialog-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" :class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
"Cancel") "Cancel")
(button (button
:sx-get "/hypermedia/examples/api/dialog/close" :sx-get "/geography/hypermedia/examples/api/dialog/close"
:sx-target "#dialog-container" :sx-target "#dialog-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
@@ -586,16 +586,16 @@
(kbd :class "px-2 py-0.5 bg-stone-100 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")))) (span :class "text-sm text-stone-500" "Help"))))
(div :id "kbd-target" (div :id "kbd-target"
:sx-get "/hypermedia/examples/api/keyboard?key=s" :sx-get "/geography/hypermedia/examples/api/keyboard?key=s"
:sx-trigger "keyup[key=='s'&&!event.target.matches('input,textarea')] from:body" :sx-trigger "keyup[key=='s'&&!event.target.matches('input,textarea')] from:body"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-stone-100 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...")) (p :class "text-stone-400 text-sm" "Press a shortcut key..."))
(div :sx-get "/hypermedia/examples/api/keyboard?key=n" (div :sx-get "/geography/hypermedia/examples/api/keyboard?key=n"
:sx-trigger "keyup[key=='n'&&!event.target.matches('input,textarea')] from:body" :sx-trigger "keyup[key=='n'&&!event.target.matches('input,textarea')] from:body"
:sx-target "#kbd-target" :sx-target "#kbd-target"
:sx-swap "innerHTML") :sx-swap "innerHTML")
(div :sx-get "/hypermedia/examples/api/keyboard?key=h" (div :sx-get "/geography/hypermedia/examples/api/keyboard?key=h"
:sx-trigger "keyup[key=='h'&&!event.target.matches('input,textarea')] from:body" :sx-trigger "keyup[key=='h'&&!event.target.matches('input,textarea')] from:body"
:sx-target "#kbd-target" :sx-target "#kbd-target"
:sx-swap "innerHTML"))) :sx-swap "innerHTML")))
@@ -619,7 +619,7 @@
(p :class "text-sm text-stone-500" email) (p :class "text-sm text-stone-500" email)
(p :class "text-sm text-stone-500" role)) (p :class "text-sm text-stone-500" role))
(button (button
:sx-get "/hypermedia/examples/api/putpatch/edit-all" :sx-get "/geography/hypermedia/examples/api/putpatch/edit-all"
:sx-target "#pp-target" :sx-target "#pp-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "text-sm text-violet-600 hover:text-violet-800" :class "text-sm text-violet-600 hover:text-violet-800"
@@ -627,7 +627,7 @@
(defcomp ~pp-form-full (&key name email role) (defcomp ~pp-form-full (&key name email role)
(form (form
:sx-put "/hypermedia/examples/api/putpatch" :sx-put "/geography/hypermedia/examples/api/putpatch"
:sx-target "#pp-target" :sx-target "#pp-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "space-y-3" :class "space-y-3"
@@ -648,7 +648,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Save All (PUT)") "Save All (PUT)")
(button :type "button" (button :type "button"
:sx-get "/hypermedia/examples/api/putpatch/cancel" :sx-get "/geography/hypermedia/examples/api/putpatch/cancel"
:sx-target "#pp-target" :sx-target "#pp-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" :class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
@@ -659,7 +659,7 @@
(defcomp ~json-encoding-demo () (defcomp ~json-encoding-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(form (form
:sx-post "/hypermedia/examples/api/json-echo" :sx-post "/geography/hypermedia/examples/api/json-echo"
:sx-target "#json-result" :sx-target "#json-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-encoding "json" :sx-encoding "json"
@@ -691,7 +691,7 @@
(div :class "space-y-2" (div :class "space-y-2"
(h4 :class "text-sm font-semibold text-stone-700" "sx-vals — send extra values") (h4 :class "text-sm font-semibold text-stone-700" "sx-vals — send extra values")
(button (button
:sx-get "/hypermedia/examples/api/echo-vals" :sx-get "/geography/hypermedia/examples/api/echo-vals"
:sx-target "#vals-result" :sx-target "#vals-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}" :sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}"
@@ -702,7 +702,7 @@
(div :class "space-y-2" (div :class "space-y-2"
(h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers") (h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers")
(button (button
:sx-get "/hypermedia/examples/api/echo-headers" :sx-get "/geography/hypermedia/examples/api/echo-headers"
:sx-target "#headers-result" :sx-target "#headers-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"} :sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
@@ -723,7 +723,7 @@
(defcomp ~loading-states-demo () (defcomp ~loading-states-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(button (button
:sx-get "/hypermedia/examples/api/slow" :sx-get "/geography/hypermedia/examples/api/slow"
:sx-target "#loading-result" :sx-target "#loading-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
: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" :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"
@@ -742,7 +742,7 @@
(defcomp ~sync-replace-demo () (defcomp ~sync-replace-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(input :type "text" :name "q" (input :type "text" :name "q"
:sx-get "/hypermedia/examples/api/slow-search" :sx-get "/geography/hypermedia/examples/api/slow-search"
:sx-trigger "keyup delay:200ms changed" :sx-trigger "keyup delay:200ms changed"
:sx-target "#sync-result" :sx-target "#sync-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -762,7 +762,7 @@
(defcomp ~retry-demo () (defcomp ~retry-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(button (button
:sx-get "/hypermedia/examples/api/flaky" :sx-get "/geography/hypermedia/examples/api/flaky"
:sx-target "#retry-result" :sx-target "#retry-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-retry "exponential:1000:8000" :sx-retry "exponential:1000:8000"

View File

@@ -89,7 +89,7 @@
(range start (+ start 5))) (range start (+ start 5)))
(if (<= next 6) (if (<= next 6)
(div :id "scroll-sentinel" (div :id "scroll-sentinel"
:sx-get (str "/hypermedia/examples/api/scroll?page=" next) :sx-get (str "/geography/hypermedia/examples/api/scroll?page=" next)
:sx-trigger "intersect once" :sx-trigger "intersect once"
:sx-target "#scroll-items" :sx-target "#scroll-items"
:sx-swap "beforeend" :sx-swap "beforeend"

View File

@@ -13,20 +13,30 @@
:content (~sx-doc :path "/" (~sx-home-content))) :content (~sx-doc :path "/" (~sx-home-content)))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Docs section ;; Language section (parent of Docs, Specs, Bootstrappers, Testing)
;; ---------------------------------------------------------------------------
(defpage language-index
:path "/language/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/language/"))
;; ---------------------------------------------------------------------------
;; Docs section (under Language)
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage docs-index (defpage docs-index
:path "/docs/" :path "/language/docs/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/docs/" (~docs-introduction-content))) :content (~sx-doc :path "/language/docs/" (~docs-introduction-content)))
(defpage docs-page (defpage docs-page
:path "/docs/<slug>" :path "/language/docs/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/docs/" slug) :content (~sx-doc :path (str "/language/docs/" slug)
(case slug (case slug
"introduction" (~docs-introduction-content) "introduction" (~docs-introduction-content)
"getting-started" (~docs-getting-started-content) "getting-started" (~docs-getting-started-content)
@@ -44,23 +54,23 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage hypermedia-index (defpage hypermedia-index
:path "/hypermedia/" :path "/geography/hypermedia/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/hypermedia/")) :content (~sx-doc :path "/geography/hypermedia/"))
(defpage reference-index (defpage reference-index
:path "/hypermedia/reference/" :path "/geography/hypermedia/reference/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/hypermedia/reference/" (~reference-index-content))) :content (~sx-doc :path "/geography/hypermedia/reference/" (~reference-index-content)))
(defpage reference-page (defpage reference-page
:path "/hypermedia/reference/<slug>" :path "/geography/hypermedia/reference/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (reference-data slug) :data (reference-data slug)
:content (~sx-doc :path (str "/hypermedia/reference/" slug) :content (~sx-doc :path (str "/geography/hypermedia/reference/" slug)
(case slug (case slug
"attributes" (~reference-attrs-content "attributes" (~reference-attrs-content
:req-table (~doc-attr-table-from-data :title "Request Attributes" :attrs req-attrs) :req-table (~doc-attr-table-from-data :title "Request Attributes" :attrs req-attrs)
@@ -83,11 +93,11 @@
:uniq-table (~doc-attr-table-from-data :title "Unique to sx" :attrs uniq-attrs))))) :uniq-table (~doc-attr-table-from-data :title "Unique to sx" :attrs uniq-attrs)))))
(defpage reference-attr-detail (defpage reference-attr-detail
:path "/hypermedia/reference/attributes/<slug>" :path "/geography/hypermedia/reference/attributes/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (attr-detail-data slug) :data (attr-detail-data slug)
:content (~sx-doc :path "/hypermedia/reference/attributes" :content (~sx-doc :path "/geography/hypermedia/reference/attributes"
(if attr-not-found (if attr-not-found
(~reference-attr-not-found :slug slug) (~reference-attr-not-found :slug slug)
(~reference-attr-detail-content (~reference-attr-detail-content
@@ -99,11 +109,11 @@
:wire-placeholder-id attr-wire-id)))) :wire-placeholder-id attr-wire-id))))
(defpage reference-header-detail (defpage reference-header-detail
:path "/hypermedia/reference/headers/<slug>" :path "/geography/hypermedia/reference/headers/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (header-detail-data slug) :data (header-detail-data slug)
:content (~sx-doc :path "/hypermedia/reference/headers" :content (~sx-doc :path "/geography/hypermedia/reference/headers"
(if header-not-found (if header-not-found
(~reference-attr-not-found :slug slug) (~reference-attr-not-found :slug slug)
(~reference-header-detail-content (~reference-header-detail-content
@@ -114,11 +124,11 @@
:demo header-demo)))) :demo header-demo))))
(defpage reference-event-detail (defpage reference-event-detail
:path "/hypermedia/reference/events/<slug>" :path "/geography/hypermedia/reference/events/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (event-detail-data slug) :data (event-detail-data slug)
:content (~sx-doc :path "/hypermedia/reference/events" :content (~sx-doc :path "/geography/hypermedia/reference/events"
(if event-not-found (if event-not-found
(~reference-attr-not-found :slug slug) (~reference-attr-not-found :slug slug)
(~reference-event-detail-content (~reference-event-detail-content
@@ -128,20 +138,30 @@
:demo event-demo)))) :demo event-demo))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Protocols section ;; Applications section (parent of CSSX, Protocols)
;; ---------------------------------------------------------------------------
(defpage applications-index
:path "/applications/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/applications/"))
;; ---------------------------------------------------------------------------
;; Protocols section (under Applications)
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage protocols-index (defpage protocols-index
:path "/protocols/" :path "/applications/protocols/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/protocols/" (~protocol-wire-format-content))) :content (~sx-doc :path "/applications/protocols/" (~protocol-wire-format-content)))
(defpage protocol-page (defpage protocol-page
:path "/protocols/<slug>" :path "/applications/protocols/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/protocols/" slug) :content (~sx-doc :path (str "/applications/protocols/" slug)
(case slug (case slug
"wire-format" (~protocol-wire-format-content) "wire-format" (~protocol-wire-format-content)
"fragments" (~protocol-fragments-content) "fragments" (~protocol-fragments-content)
@@ -156,16 +176,16 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage examples-index (defpage examples-index
:path "/hypermedia/examples/" :path "/geography/hypermedia/examples/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/hypermedia/examples/")) :content (~sx-doc :path "/geography/hypermedia/examples/"))
(defpage examples-page (defpage examples-page
:path "/hypermedia/examples/<slug>" :path "/geography/hypermedia/examples/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/hypermedia/examples/" slug) :content (~sx-doc :path (str "/geography/hypermedia/examples/" slug)
(case slug (case slug
"click-to-load" (~example-click-to-load) "click-to-load" (~example-click-to-load)
"form-submission" (~example-form-submission) "form-submission" (~example-form-submission)
@@ -197,20 +217,30 @@
:else (~example-click-to-load)))) :else (~example-click-to-load))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Essays section ;; Etc section (parent of Essays, Philosophy, Plans)
;; ---------------------------------------------------------------------------
(defpage etc-index
:path "/etc/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/etc/"))
;; ---------------------------------------------------------------------------
;; Essays section (under Etc)
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage essays-index (defpage essays-index
:path "/essays/" :path "/etc/essays/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/essays/" (~essays-index-content))) :content (~sx-doc :path "/etc/essays/" (~essays-index-content)))
(defpage essay-page (defpage essay-page
:path "/essays/<slug>" :path "/etc/essays/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/essays/" slug) :content (~sx-doc :path (str "/etc/essays/" slug)
(case slug (case slug
"sx-sucks" (~essay-sx-sucks) "sx-sucks" (~essay-sx-sucks)
"why-sexps" (~essay-why-sexps) "why-sexps" (~essay-why-sexps)
@@ -235,16 +265,16 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage philosophy-index (defpage philosophy-index
:path "/philosophy/" :path "/etc/philosophy/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/philosophy/" (~philosophy-index-content))) :content (~sx-doc :path "/etc/philosophy/" (~philosophy-index-content)))
(defpage philosophy-page (defpage philosophy-page
:path "/philosophy/<slug>" :path "/etc/philosophy/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/philosophy/" slug) :content (~sx-doc :path (str "/etc/philosophy/" slug)
(case slug (case slug
"sx-manifesto" (~essay-sx-manifesto) "sx-manifesto" (~essay-sx-manifesto)
"godel-escher-bach" (~essay-godel-escher-bach) "godel-escher-bach" (~essay-godel-escher-bach)
@@ -258,16 +288,16 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage cssx-index (defpage cssx-index
:path "/cssx/" :path "/applications/cssx/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/cssx/" (~cssx-overview-content))) :content (~sx-doc :path "/applications/cssx/" (~cssx-overview-content)))
(defpage cssx-page (defpage cssx-page
:path "/cssx/<slug>" :path "/applications/cssx/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/cssx/" slug) :content (~sx-doc :path (str "/applications/cssx/" slug)
(case slug (case slug
"patterns" (~cssx-patterns-content) "patterns" (~cssx-patterns-content)
"delivery" (~cssx-delivery-content) "delivery" (~cssx-delivery-content)
@@ -282,23 +312,23 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage specs-index (defpage specs-index
:path "/specs/" :path "/language/specs/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/specs/" (~spec-architecture-content))) :content (~sx-doc :path "/language/specs/" (~spec-architecture-content)))
(defpage specs-page (defpage specs-page
:path "/specs/<slug>" :path "/language/specs/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/specs/" slug) :content (~sx-doc :path (str "/language/specs/" slug)
(case slug (case slug
"core" (~spec-overview-content "core" (~spec-overview-content
:spec-title "Core Language" :spec-title "Core Language"
:spec-files (map (fn (item) :spec-files (map (fn (item)
(dict :title (get item "title") :desc (get item "desc") (dict :title (get item "title") :desc (get item "desc")
:prose (get item "prose") :prose (get item "prose")
:filename (get item "filename") :href (str "/specs/" (get item "slug")) :filename (get item "filename") :href (str "/language/specs/" (get item "slug"))
:source (read-spec-file (get item "filename")))) :source (read-spec-file (get item "filename"))))
core-spec-items)) core-spec-items))
"adapters" (~spec-overview-content "adapters" (~spec-overview-content
@@ -306,7 +336,7 @@
:spec-files (map (fn (item) :spec-files (map (fn (item)
(dict :title (get item "title") :desc (get item "desc") (dict :title (get item "title") :desc (get item "desc")
:prose (get item "prose") :prose (get item "prose")
:filename (get item "filename") :href (str "/specs/" (get item "slug")) :filename (get item "filename") :href (str "/language/specs/" (get item "slug"))
:source (read-spec-file (get item "filename")))) :source (read-spec-file (get item "filename"))))
adapter-spec-items)) adapter-spec-items))
"browser" (~spec-overview-content "browser" (~spec-overview-content
@@ -314,7 +344,7 @@
:spec-files (map (fn (item) :spec-files (map (fn (item)
(dict :title (get item "title") :desc (get item "desc") (dict :title (get item "title") :desc (get item "desc")
:prose (get item "prose") :prose (get item "prose")
:filename (get item "filename") :href (str "/specs/" (get item "slug")) :filename (get item "filename") :href (str "/language/specs/" (get item "slug"))
:source (read-spec-file (get item "filename")))) :source (read-spec-file (get item "filename"))))
browser-spec-items)) browser-spec-items))
"extensions" (~spec-overview-content "extensions" (~spec-overview-content
@@ -322,7 +352,7 @@
:spec-files (map (fn (item) :spec-files (map (fn (item)
(dict :title (get item "title") :desc (get item "desc") (dict :title (get item "title") :desc (get item "desc")
:prose (get item "prose") :prose (get item "prose")
:filename (get item "filename") :href (str "/specs/" (get item "slug")) :filename (get item "filename") :href (str "/language/specs/" (get item "slug"))
:source (read-spec-file (get item "filename")))) :source (read-spec-file (get item "filename"))))
extension-spec-items)) extension-spec-items))
:else (let ((spec (find-spec slug))) :else (let ((spec (find-spec slug)))
@@ -340,17 +370,17 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage bootstrappers-index (defpage bootstrappers-index
:path "/bootstrappers/" :path "/language/bootstrappers/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/bootstrappers/" (~bootstrappers-index-content))) :content (~sx-doc :path "/language/bootstrappers/" (~bootstrappers-index-content)))
(defpage bootstrapper-page (defpage bootstrapper-page
:path "/bootstrappers/<slug>" :path "/language/bootstrappers/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (bootstrapper-data slug) :data (bootstrapper-data slug)
:content (~sx-doc :path (str "/bootstrappers/" slug) :content (~sx-doc :path (str "/language/bootstrappers/" slug)
(if bootstrapper-not-found (if bootstrapper-not-found
(~spec-not-found :slug slug) (~spec-not-found :slug slug)
(case slug (case slug
@@ -385,53 +415,53 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage isomorphism-index (defpage isomorphism-index
:path "/isomorphism/" :path "/geography/isomorphism/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/isomorphism/" (~plan-isomorphic-content))) :content (~sx-doc :path "/geography/isomorphism/" (~plan-isomorphic-content)))
(defpage bundle-analyzer (defpage bundle-analyzer
:path "/isomorphism/bundle-analyzer" :path "/geography/isomorphism/bundle-analyzer"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (bundle-analyzer-data) :data (bundle-analyzer-data)
:content (~sx-doc :path "/isomorphism/bundle-analyzer" :content (~sx-doc :path "/geography/isomorphism/bundle-analyzer"
(~bundle-analyzer-content (~bundle-analyzer-content
:pages pages :total-components total-components :total-macros total-macros :pages pages :total-components total-components :total-macros total-macros
:pure-count pure-count :io-count io-count))) :pure-count pure-count :io-count io-count)))
(defpage routing-analyzer (defpage routing-analyzer
:path "/isomorphism/routing-analyzer" :path "/geography/isomorphism/routing-analyzer"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (routing-analyzer-data) :data (routing-analyzer-data)
:content (~sx-doc :path "/isomorphism/routing-analyzer" :content (~sx-doc :path "/geography/isomorphism/routing-analyzer"
(~routing-analyzer-content (~routing-analyzer-content
:pages pages :total-pages total-pages :client-count client-count :pages pages :total-pages total-pages :client-count client-count
:server-count server-count :registry-sample registry-sample))) :server-count server-count :registry-sample registry-sample)))
(defpage data-test (defpage data-test
:path "/isomorphism/data-test" :path "/geography/isomorphism/data-test"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (data-test-data) :data (data-test-data)
:content (~sx-doc :path "/isomorphism/data-test" :content (~sx-doc :path "/geography/isomorphism/data-test"
(~data-test-content (~data-test-content
:server-time server-time :items items :server-time server-time :items items
:phase phase :transport transport))) :phase phase :transport transport)))
(defpage async-io-demo (defpage async-io-demo
:path "/isomorphism/async-io" :path "/geography/isomorphism/async-io"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/isomorphism/async-io" (~async-io-demo-content))) :content (~sx-doc :path "/geography/isomorphism/async-io" (~async-io-demo-content)))
(defpage streaming-demo (defpage streaming-demo
:path "/isomorphism/streaming" :path "/geography/isomorphism/streaming"
:auth :public :auth :public
:stream true :stream true
:layout :sx-docs :layout :sx-docs
:shell (~sx-doc :path "/isomorphism/streaming" :shell (~sx-doc :path "/geography/isomorphism/streaming"
(~streaming-demo-layout (~streaming-demo-layout
(~suspense :id "stream-fast" :fallback (~stream-skeleton)) (~suspense :id "stream-fast" :fallback (~stream-skeleton))
(~suspense :id "stream-medium" :fallback (~stream-skeleton)) (~suspense :id "stream-medium" :fallback (~stream-skeleton))
@@ -444,35 +474,35 @@
:stream-time stream-time)) :stream-time stream-time))
(defpage affinity-demo (defpage affinity-demo
:path "/isomorphism/affinity" :path "/geography/isomorphism/affinity"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (affinity-demo-data) :data (affinity-demo-data)
:content (~sx-doc :path "/isomorphism/affinity" :content (~sx-doc :path "/geography/isomorphism/affinity"
(~affinity-demo-content :components components :page-plans page-plans))) (~affinity-demo-content :components components :page-plans page-plans)))
(defpage optimistic-demo (defpage optimistic-demo
:path "/isomorphism/optimistic" :path "/geography/isomorphism/optimistic"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (optimistic-demo-data) :data (optimistic-demo-data)
:content (~sx-doc :path "/isomorphism/optimistic" :content (~sx-doc :path "/geography/isomorphism/optimistic"
(~optimistic-demo-content :items items :server-time server-time))) (~optimistic-demo-content :items items :server-time server-time)))
(defpage offline-demo (defpage offline-demo
:path "/isomorphism/offline" :path "/geography/isomorphism/offline"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (offline-demo-data) :data (offline-demo-data)
:content (~sx-doc :path "/isomorphism/offline" :content (~sx-doc :path "/geography/isomorphism/offline"
(~offline-demo-content :notes notes :server-time server-time))) (~offline-demo-content :notes notes :server-time server-time)))
;; Wildcard must come AFTER specific routes (first-match routing) ;; Wildcard must come AFTER specific routes (first-match routing)
(defpage isomorphism-page (defpage isomorphism-page
:path "/isomorphism/<slug>" :path "/geography/isomorphism/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/isomorphism/" slug) :content (~sx-doc :path (str "/geography/isomorphism/" slug)
(case slug (case slug
"bundle-analyzer" (~bundle-analyzer-content "bundle-analyzer" (~bundle-analyzer-content
:pages pages :total-components total-components :total-macros total-macros :pages pages :total-components total-components :total-macros total-macros
@@ -487,19 +517,19 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage plans-index (defpage plans-index
:path "/plans/" :path "/etc/plans/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/plans/" (~plans-index-content))) :content (~sx-doc :path "/etc/plans/" (~plans-index-content)))
(defpage plan-page (defpage plan-page
:path "/plans/<slug>" :path "/etc/plans/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (case slug :data (case slug
"theorem-prover" (prove-data) "theorem-prover" (prove-data)
:else nil) :else nil)
:content (~sx-doc :path (str "/plans/" slug) :content (~sx-doc :path (str "/etc/plans/" slug)
(case slug (case slug
"status" (~plan-status-content) "status" (~plan-status-content)
"reader-macros" (~plan-reader-macros-content) "reader-macros" (~plan-reader-macros-content)
@@ -530,39 +560,58 @@
:else (~plans-index-content)))) :else (~plans-index-content))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Reactive Islands section ;; Geography section (parent of Reactive Islands, Hypermedia Lakes, Marshes)
;; ---------------------------------------------------------------------------
(defpage geography-index
:path "/geography/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/geography/"))
;; ---------------------------------------------------------------------------
;; Reactive Islands section (under Geography)
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage reactive-islands-index (defpage reactive-islands-index
:path "/reactive/" :path "/geography/reactive/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path "/reactive/" (~reactive-islands-index-content))) :content (~sx-doc :path "/geography/reactive/" (~reactive-islands-index-content)))
(defpage reactive-islands-page (defpage reactive-islands-page
:path "/reactive/<slug>" :path "/geography/reactive/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:content (~sx-doc :path (str "/reactive/" slug) :content (~sx-doc :path (str "/geography/reactive/" slug)
(case slug (case slug
"demo" (~reactive-islands-demo-content) "demo" (~reactive-islands-demo-content)
"event-bridge" (~reactive-islands-event-bridge-content) "event-bridge" (~reactive-islands-event-bridge-content)
"named-stores" (~reactive-islands-named-stores-content) "named-stores" (~reactive-islands-named-stores-content)
"plan" (~reactive-islands-plan-content) "plan" (~reactive-islands-plan-content)
"marshes" (~reactive-islands-marshes-content)
"phase2" (~reactive-islands-phase2-content) "phase2" (~reactive-islands-phase2-content)
:else (~reactive-islands-index-content)))) :else (~reactive-islands-index-content))))
;; ---------------------------------------------------------------------------
;; Marshes section (under Geography)
;; ---------------------------------------------------------------------------
(defpage marshes-index
:path "/geography/marshes/"
:auth :public
:layout :sx-docs
:content (~sx-doc :path "/geography/marshes/" (~reactive-islands-marshes-content)))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Bootstrapped page helpers demo ;; Bootstrapped page helpers demo
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage page-helpers-demo (defpage page-helpers-demo
:path "/bootstrappers/page-helpers" :path "/language/bootstrappers/page-helpers"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (page-helpers-demo-data) :data (page-helpers-demo-data)
:content (~sx-doc :path "/bootstrappers/page-helpers" :content (~sx-doc :path "/language/bootstrappers/page-helpers"
(~page-helpers-demo-content (~page-helpers-demo-content
:sf-categories sf-categories :sf-total sf-total :sf-ms sf-ms :sf-categories sf-categories :sf-total sf-total :sf-ms sf-ms
:ref-sample ref-sample :ref-ms ref-ms :ref-sample ref-sample :ref-ms ref-ms
@@ -580,11 +629,11 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defpage testing-index (defpage testing-index
:path "/testing/" :path "/language/testing/"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (run-modular-tests "all") :data (run-modular-tests "all")
:content (~sx-doc :path "/testing/" :content (~sx-doc :path "/language/testing/"
(~testing-overview-content (~testing-overview-content
:server-results server-results :server-results server-results
:framework-source framework-source :framework-source framework-source
@@ -596,7 +645,7 @@
:engine-source engine-source))) :engine-source engine-source)))
(defpage testing-page (defpage testing-page
:path "/testing/<slug>" :path "/language/testing/<slug>"
:auth :public :auth :public
:layout :sx-docs :layout :sx-docs
:data (case slug :data (case slug
@@ -608,7 +657,7 @@
"engine" (run-modular-tests "engine") "engine" (run-modular-tests "engine")
"orchestration" (run-modular-tests "orchestration") "orchestration" (run-modular-tests "orchestration")
:else (dict)) :else (dict))
:content (~sx-doc :path (str "/testing/" slug) :content (~sx-doc :path (str "/language/testing/" slug)
(case slug (case slug
"eval" (~testing-spec-content "eval" (~testing-spec-content
:spec-name "eval" :spec-name "eval"

View File

@@ -10,7 +10,7 @@
(defcomp ~ref-get-demo () (defcomp ~ref-get-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/time" :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-get-result" :sx-target "#ref-get-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -26,7 +26,7 @@
(defcomp ~ref-post-demo () (defcomp ~ref-post-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form (form
:sx-post "/hypermedia/reference/api/greet" :sx-post "/geography/hypermedia/reference/api/greet"
:sx-target "#ref-post-result" :sx-target "#ref-post-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "flex gap-2" :class "flex gap-2"
@@ -48,7 +48,7 @@
(div :class "flex items-center justify-between p-3 bg-stone-100 rounded" (div :class "flex items-center justify-between p-3 bg-stone-100 rounded"
(span :class "text-stone-700 text-sm" "Status: " (strong "draft")) (span :class "text-stone-700 text-sm" "Status: " (strong "draft"))
(button (button
:sx-put "/hypermedia/reference/api/status" :sx-put "/geography/hypermedia/reference/api/status"
:sx-target "#ref-put-view" :sx-target "#ref-put-view"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-vals "{\"status\": \"published\"}" :sx-vals "{\"status\": \"published\"}"
@@ -63,17 +63,17 @@
(div :class "space-y-2" (div :class "space-y-2"
(div :id "ref-del-1" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (div :id "ref-del-1" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item A") (span :class "text-sm text-stone-700" "Item A")
(button :sx-delete "/hypermedia/reference/api/item/1" (button :sx-delete "/geography/hypermedia/reference/api/item/1"
:sx-target "#ref-del-1" :sx-swap "delete" :sx-target "#ref-del-1" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove")) :class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-del-2" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (div :id "ref-del-2" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item B") (span :class "text-sm text-stone-700" "Item B")
(button :sx-delete "/hypermedia/reference/api/item/2" (button :sx-delete "/geography/hypermedia/reference/api/item/2"
:sx-target "#ref-del-2" :sx-swap "delete" :sx-target "#ref-del-2" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove")) :class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-del-3" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (div :id "ref-del-3" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item C") (span :class "text-sm text-stone-700" "Item C")
(button :sx-delete "/hypermedia/reference/api/item/3" (button :sx-delete "/geography/hypermedia/reference/api/item/3"
:sx-target "#ref-del-3" :sx-swap "delete" :sx-target "#ref-del-3" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove")))) :class "text-red-500 text-sm hover:text-red-700" "Remove"))))
@@ -86,11 +86,11 @@
(div :class "p-3 bg-stone-100 rounded" (div :class "p-3 bg-stone-100 rounded"
(span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light"))) (span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light")))
(div :class "flex gap-2" (div :class "flex gap-2"
(button :sx-patch "/hypermedia/reference/api/theme" (button :sx-patch "/geography/hypermedia/reference/api/theme"
:sx-vals "{\"theme\": \"dark\"}" :sx-vals "{\"theme\": \"dark\"}"
:sx-target "#ref-patch-val" :sx-swap "innerHTML" :sx-target "#ref-patch-val" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-800 text-white rounded text-sm" "Dark") :class "px-3 py-1 bg-stone-800 text-white rounded text-sm" "Dark")
(button :sx-patch "/hypermedia/reference/api/theme" (button :sx-patch "/geography/hypermedia/reference/api/theme"
:sx-vals "{\"theme\": \"light\"}" :sx-vals "{\"theme\": \"light\"}"
:sx-target "#ref-patch-val" :sx-swap "innerHTML" :sx-target "#ref-patch-val" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-100 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"))))
@@ -102,7 +102,7 @@
(defcomp ~ref-trigger-demo () (defcomp ~ref-trigger-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(input :type "text" :name "q" :placeholder "Type to search..." (input :type "text" :name "q" :placeholder "Type to search..."
:sx-get "/hypermedia/reference/api/trigger-search" :sx-get "/geography/hypermedia/reference/api/trigger-search"
:sx-trigger "input changed delay:300ms" :sx-trigger "input changed delay:300ms"
:sx-target "#ref-trigger-result" :sx-target "#ref-trigger-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -118,12 +118,12 @@
(defcomp ~ref-target-demo () (defcomp ~ref-target-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2" (div :class "flex gap-2"
(button :sx-get "/hypermedia/reference/api/time" (button :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-target-a" :sx-target "#ref-target-a"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Update Box A") "Update Box A")
(button :sx-get "/hypermedia/reference/api/time" (button :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-target-b" :sx-target "#ref-target-b"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-3 py-1 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700" :class "px-3 py-1 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
@@ -141,13 +141,13 @@
(defcomp ~ref-swap-demo () (defcomp ~ref-swap-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2 flex-wrap" (div :class "flex gap-2 flex-wrap"
(button :sx-get "/hypermedia/reference/api/swap-item" (button :sx-get "/geography/hypermedia/reference/api/swap-item"
:sx-target "#ref-swap-list" :sx-swap "beforeend" :sx-target "#ref-swap-list" :sx-swap "beforeend"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "beforeend") :class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "beforeend")
(button :sx-get "/hypermedia/reference/api/swap-item" (button :sx-get "/geography/hypermedia/reference/api/swap-item"
:sx-target "#ref-swap-list" :sx-swap "afterbegin" :sx-target "#ref-swap-list" :sx-swap "afterbegin"
:class "px-3 py-1 bg-emerald-600 text-white rounded text-sm" "afterbegin") :class "px-3 py-1 bg-emerald-600 text-white rounded text-sm" "afterbegin")
(button :sx-get "/hypermedia/reference/api/swap-item" (button :sx-get "/geography/hypermedia/reference/api/swap-item"
:sx-target "#ref-swap-list" :sx-swap "innerHTML" :sx-target "#ref-swap-list" :sx-swap "innerHTML"
:class "px-3 py-1 bg-blue-600 text-white rounded text-sm" "innerHTML")) :class "px-3 py-1 bg-blue-600 text-white rounded text-sm" "innerHTML"))
(div :id "ref-swap-list" (div :id "ref-swap-list"
@@ -160,7 +160,7 @@
(defcomp ~ref-oob-demo () (defcomp ~ref-oob-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-get "/hypermedia/reference/api/oob" (button :sx-get "/geography/hypermedia/reference/api/oob"
:sx-target "#ref-oob-main" :sx-target "#ref-oob-main"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
@@ -179,7 +179,7 @@
(defcomp ~ref-select-demo () (defcomp ~ref-select-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-get "/hypermedia/reference/api/select-page" (button :sx-get "/geography/hypermedia/reference/api/select-page"
:sx-target "#ref-select-result" :sx-target "#ref-select-result"
:sx-select "#the-content" :sx-select "#the-content"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -198,7 +198,7 @@
(div :id "ref-confirm-item" (div :id "ref-confirm-item"
:class "flex items-center justify-between p-3 border border-stone-200 rounded" :class "flex items-center justify-between p-3 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Important file.txt") (span :class "text-sm text-stone-700" "Important file.txt")
(button :sx-delete "/hypermedia/reference/api/item/confirm" (button :sx-delete "/geography/hypermedia/reference/api/item/confirm"
:sx-target "#ref-confirm-item" :sx-swap "delete" :sx-target "#ref-confirm-item" :sx-swap "delete"
:sx-confirm "Are you sure you want to delete this file?" :sx-confirm "Are you sure you want to delete this file?"
:class "px-3 py-1 text-red-500 text-sm border border-red-200 rounded hover:bg-red-50" :class "px-3 py-1 text-red-500 text-sm border border-red-200 rounded hover:bg-red-50"
@@ -211,14 +211,14 @@
(defcomp ~ref-pushurl-demo () (defcomp ~ref-pushurl-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2" (div :class "flex gap-2"
(a :href "/hypermedia/reference/attributes/sx-get" (a :href "/geography/hypermedia/reference/attributes/sx-get"
:sx-get "/hypermedia/reference/attributes/sx-get" :sx-get "/geography/hypermedia/reference/attributes/sx-get"
:sx-target "#main-panel" :sx-select "#main-panel" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"sx-get page") "sx-get page")
(a :href "/hypermedia/reference/attributes/sx-post" (a :href "/geography/hypermedia/reference/attributes/sx-post"
:sx-get "/hypermedia/reference/attributes/sx-post" :sx-get "/geography/hypermedia/reference/attributes/sx-post"
:sx-target "#main-panel" :sx-select "#main-panel" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
@@ -233,7 +233,7 @@
(defcomp ~ref-sync-demo () (defcomp ~ref-sync-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(input :type "text" :name "q" :placeholder "Type quickly..." (input :type "text" :name "q" :placeholder "Type quickly..."
:sx-get "/hypermedia/reference/api/slow-echo" :sx-get "/geography/hypermedia/reference/api/slow-echo"
:sx-trigger "input changed delay:100ms" :sx-trigger "input changed delay:100ms"
:sx-sync "replace" :sx-sync "replace"
:sx-target "#ref-sync-result" :sx-target "#ref-sync-result"
@@ -251,7 +251,7 @@
(defcomp ~ref-encoding-demo () (defcomp ~ref-encoding-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form :sx-post "/hypermedia/reference/api/upload-name" (form :sx-post "/geography/hypermedia/reference/api/upload-name"
:sx-encoding "multipart/form-data" :sx-encoding "multipart/form-data"
:sx-target "#ref-encoding-result" :sx-target "#ref-encoding-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -271,7 +271,7 @@
(defcomp ~ref-headers-demo () (defcomp ~ref-headers-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-get "/hypermedia/reference/api/echo-headers" (button :sx-get "/geography/hypermedia/reference/api/echo-headers"
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"} :sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
:sx-target "#ref-headers-result" :sx-target "#ref-headers-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -295,7 +295,7 @@
(option :value "all" "All") (option :value "all" "All")
(option :value "books" "Books") (option :value "books" "Books")
(option :value "tools" "Tools"))) (option :value "tools" "Tools")))
(button :sx-get "/hypermedia/reference/api/echo-vals" (button :sx-get "/geography/hypermedia/reference/api/echo-vals"
:sx-include "#ref-inc-cat" :sx-include "#ref-inc-cat"
:sx-target "#ref-include-result" :sx-target "#ref-include-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -311,7 +311,7 @@
(defcomp ~ref-vals-demo () (defcomp ~ref-vals-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-post "/hypermedia/reference/api/echo-vals" (button :sx-post "/geography/hypermedia/reference/api/echo-vals"
:sx-vals "{\"source\": \"demo\", \"page\": \"3\"}" :sx-vals "{\"source\": \"demo\", \"page\": \"3\"}"
:sx-target "#ref-vals-result" :sx-target "#ref-vals-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -327,8 +327,8 @@
(defcomp ~ref-media-demo () (defcomp ~ref-media-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(a :href "/hypermedia/reference/attributes/sx-get" (a :href "/geography/hypermedia/reference/attributes/sx-get"
:sx-get "/hypermedia/reference/attributes/sx-get" :sx-get "/geography/hypermedia/reference/attributes/sx-get"
:sx-target "#main-panel" :sx-select "#main-panel" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:sx-media "(min-width: 768px)" :sx-media "(min-width: 768px)"
@@ -346,13 +346,13 @@
(div :class "grid grid-cols-2 gap-3" (div :class "grid grid-cols-2 gap-3"
(div :class "p-3 border border-stone-200 rounded" (div :class "p-3 border border-stone-200 rounded"
(p :class "text-xs text-stone-400 mb-2" "sx enabled") (p :class "text-xs text-stone-400 mb-2" "sx enabled")
(button :sx-get "/hypermedia/reference/api/time" (button :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-dis-a" :sx-swap "innerHTML" :sx-target "#ref-dis-a" :sx-swap "innerHTML"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "Load") :class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "Load")
(div :id "ref-dis-a" :class "mt-2 text-sm text-stone-500" "—")) (div :id "ref-dis-a" :class "mt-2 text-sm text-stone-500" "—"))
(div :sx-disable "true" :class "p-3 border border-stone-200 rounded" (div :sx-disable "true" :class "p-3 border border-stone-200 rounded"
(p :class "text-xs text-stone-400 mb-2" "sx disabled") (p :class "text-xs text-stone-400 mb-2" "sx disabled")
(button :sx-get "/hypermedia/reference/api/time" (button :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-dis-b" :sx-swap "innerHTML" :sx-target "#ref-dis-b" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-400 text-white rounded text-sm" "Load") :class "px-3 py-1 bg-stone-400 text-white rounded text-sm" "Load")
(div :id "ref-dis-b" :class "mt-2 text-sm text-stone-500" (div :id "ref-dis-b" :class "mt-2 text-sm text-stone-500"
@@ -378,7 +378,7 @@
(defcomp ~ref-retry-demo () (defcomp ~ref-retry-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-get "/hypermedia/reference/api/flaky" (button :sx-get "/geography/hypermedia/reference/api/flaky"
:sx-target "#ref-retry-result" :sx-target "#ref-retry-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-retry "true" :sx-retry "true"
@@ -414,13 +414,13 @@
(defcomp ~ref-boost-demo () (defcomp ~ref-boost-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(nav :sx-boost "true" :class "flex gap-3" (nav :sx-boost "true" :class "flex gap-3"
(a :href "/hypermedia/reference/attributes/sx-get" (a :href "/geography/hypermedia/reference/attributes/sx-get"
:class "text-violet-600 hover:text-violet-800 underline text-sm" :class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-get") "sx-get")
(a :href "/hypermedia/reference/attributes/sx-post" (a :href "/geography/hypermedia/reference/attributes/sx-post"
:class "text-violet-600 hover:text-violet-800 underline text-sm" :class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-post") "sx-post")
(a :href "/hypermedia/reference/attributes/sx-target" (a :href "/geography/hypermedia/reference/attributes/sx-target"
:class "text-violet-600 hover:text-violet-800 underline text-sm" :class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-target")) "sx-target"))
(p :class "text-xs text-stone-400" (p :class "text-xs text-stone-400"
@@ -434,7 +434,7 @@
(defcomp ~ref-preload-demo () (defcomp ~ref-preload-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/time" :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-preload-result" :sx-target "#ref-preload-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-preload "mouseover" :sx-preload "mouseover"
@@ -452,7 +452,7 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2 items-center" (div :class "flex gap-2 items-center"
(button (button
:sx-get "/hypermedia/reference/api/time" :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-preserve-container" :sx-target "#ref-preserve-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -473,7 +473,7 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-3 items-center" (div :class "flex gap-3 items-center"
(button (button
:sx-get "/hypermedia/reference/api/slow-echo" :sx-get "/geography/hypermedia/reference/api/slow-echo"
:sx-target "#ref-indicator-result" :sx-target "#ref-indicator-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-indicator "#ref-spinner" :sx-indicator "#ref-spinner"
@@ -495,7 +495,7 @@
(defcomp ~ref-validate-demo () (defcomp ~ref-validate-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form (form
:sx-post "/hypermedia/reference/api/greet" :sx-post "/geography/hypermedia/reference/api/greet"
:sx-target "#ref-validate-result" :sx-target "#ref-validate-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-validate "true" :sx-validate "true"
@@ -517,7 +517,7 @@
(defcomp ~ref-ignore-demo () (defcomp ~ref-ignore-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/time" :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-ignore-container" :sx-target "#ref-ignore-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -539,14 +539,14 @@
(div :id "ref-opt-item-1" (div :id "ref-opt-item-1"
:class "flex items-center justify-between p-2 border border-stone-200 rounded" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Optimistic item A") (span :class "text-sm text-stone-700" "Optimistic item A")
(button :sx-delete "/hypermedia/reference/api/item/opt1" (button :sx-delete "/geography/hypermedia/reference/api/item/opt1"
:sx-target "#ref-opt-item-1" :sx-swap "delete" :sx-target "#ref-opt-item-1" :sx-swap "delete"
:sx-optimistic "remove" :sx-optimistic "remove"
:class "text-red-500 text-sm hover:text-red-700" "Remove")) :class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-opt-item-2" (div :id "ref-opt-item-2"
:class "flex items-center justify-between p-2 border border-stone-200 rounded" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Optimistic item B") (span :class "text-sm text-stone-700" "Optimistic item B")
(button :sx-delete "/hypermedia/reference/api/item/opt2" (button :sx-delete "/geography/hypermedia/reference/api/item/opt2"
:sx-target "#ref-opt-item-2" :sx-swap "delete" :sx-target "#ref-opt-item-2" :sx-swap "delete"
:sx-optimistic "remove" :sx-optimistic "remove"
:class "text-red-500 text-sm hover:text-red-700" "Remove")) :class "text-red-500 text-sm hover:text-red-700" "Remove"))
@@ -560,7 +560,7 @@
(defcomp ~ref-replace-url-demo () (defcomp ~ref-replace-url-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/time" :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-replurl-result" :sx-target "#ref-replurl-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-replace-url "true" :sx-replace-url "true"
@@ -578,7 +578,7 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-3 items-center" (div :class "flex gap-3 items-center"
(button :id "ref-diselt-btn" (button :id "ref-diselt-btn"
:sx-get "/hypermedia/reference/api/slow-echo" :sx-get "/geography/hypermedia/reference/api/slow-echo"
:sx-target "#ref-diselt-result" :sx-target "#ref-diselt-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-disabled-elt "#ref-diselt-btn" :sx-disabled-elt "#ref-diselt-btn"
@@ -597,7 +597,7 @@
(defcomp ~ref-prompt-demo () (defcomp ~ref-prompt-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/prompt-echo" :sx-get "/geography/hypermedia/reference/api/prompt-echo"
:sx-target "#ref-prompt-result" :sx-target "#ref-prompt-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-prompt "Enter your name:" :sx-prompt "Enter your name:"
@@ -614,7 +614,7 @@
(defcomp ~ref-params-demo () (defcomp ~ref-params-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form (form
:sx-post "/hypermedia/reference/api/echo-vals" :sx-post "/geography/hypermedia/reference/api/echo-vals"
:sx-target "#ref-params-result" :sx-target "#ref-params-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-params "name" :sx-params "name"
@@ -636,7 +636,7 @@
(defcomp ~ref-sse-demo () (defcomp ~ref-sse-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :sx-sse "/hypermedia/reference/api/sse-time" (div :sx-sse "/geography/hypermedia/reference/api/sse-time"
:sx-sse-swap "time" :sx-sse-swap "time"
:sx-swap "innerHTML" :sx-swap "innerHTML"
(div :id "ref-sse-result" (div :id "ref-sse-result"
@@ -656,7 +656,7 @@
(defcomp ~ref-header-prompt-demo () (defcomp ~ref-header-prompt-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/prompt-echo" :sx-get "/geography/hypermedia/reference/api/prompt-echo"
:sx-target "#ref-hdr-prompt-result" :sx-target "#ref-hdr-prompt-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-prompt "Enter your name:" :sx-prompt "Enter your name:"
@@ -673,7 +673,7 @@
(defcomp ~ref-header-trigger-demo () (defcomp ~ref-header-trigger-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/trigger-event" :sx-get "/geography/hypermedia/reference/api/trigger-event"
:sx-target "#ref-hdr-trigger-result" :sx-target "#ref-hdr-trigger-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -690,7 +690,7 @@
(defcomp ~ref-header-retarget-demo () (defcomp ~ref-header-retarget-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/retarget" :sx-get "/geography/hypermedia/reference/api/retarget"
:sx-target "#ref-hdr-retarget-main" :sx-target "#ref-hdr-retarget-main"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -717,7 +717,7 @@
(input :id "ref-evt-br-input" :type "text" :placeholder "Type something first..." (input :id "ref-evt-br-input" :type "text" :placeholder "Type something first..."
:class "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500") :class "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(button (button
:sx-get "/hypermedia/reference/api/time" :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-evt-br-result" :sx-target "#ref-evt-br-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:beforeRequest "if (!document.getElementById('ref-evt-br-input').value) { event.preventDefault(); document.getElementById('ref-evt-br-result').textContent = 'Cancelled — input is empty!'; }" :sx-on:sx:beforeRequest "if (!document.getElementById('ref-evt-br-input').value) { event.preventDefault(); document.getElementById('ref-evt-br-result').textContent = 'Cancelled — input is empty!'; }"
@@ -734,7 +734,7 @@
(defcomp ~ref-event-after-request-demo () (defcomp ~ref-event-after-request-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/time" :sx-get "/geography/hypermedia/reference/api/time"
:sx-target "#ref-evt-ar-result" :sx-target "#ref-evt-ar-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:afterRequest "document.getElementById('ref-evt-ar-log').textContent = 'Response status: ' + (event.detail ? event.detail.status : '?')" :sx-on:sx:afterRequest "document.getElementById('ref-evt-ar-log').textContent = 'Response status: ' + (event.detail ? event.detail.status : '?')"
@@ -754,7 +754,7 @@
(defcomp ~ref-event-after-swap-demo () (defcomp ~ref-event-after-swap-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/swap-item" :sx-get "/geography/hypermedia/reference/api/swap-item"
:sx-target "#ref-evt-as-list" :sx-target "#ref-evt-as-list"
:sx-swap "beforeend" :sx-swap "beforeend"
:sx-on:sx:afterSwap "var items = document.querySelectorAll('#ref-evt-as-list > div'); if (items.length) items[items.length-1].scrollIntoView({behavior:'smooth'}); document.getElementById('ref-evt-as-count').textContent = items.length + ' items'" :sx-on:sx:afterSwap "var items = document.querySelectorAll('#ref-evt-as-list > div'); if (items.length) items[items.length-1].scrollIntoView({behavior:'smooth'}); document.getElementById('ref-evt-as-count').textContent = items.length + ' items'"
@@ -774,7 +774,7 @@
(defcomp ~ref-event-response-error-demo () (defcomp ~ref-event-response-error-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/hypermedia/reference/api/error-500" :sx-get "/geography/hypermedia/reference/api/error-500"
:sx-target "#ref-evt-err-result" :sx-target "#ref-evt-err-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:responseError "var s=document.getElementById('ref-evt-err-status'); s.style.display='block'; s.textContent='Error ' + (event.detail ? event.detail.status || '?' : '?') + ' received'" :sx-on:sx:responseError "var s=document.getElementById('ref-evt-err-status'); s.style.display='block'; s.textContent='Error ' + (event.detail ? event.detail.status || '?' : '?') + ' received'"
@@ -796,7 +796,7 @@
(defcomp ~ref-event-validation-failed-demo () (defcomp ~ref-event-validation-failed-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form (form
:sx-post "/hypermedia/reference/api/greet" :sx-post "/geography/hypermedia/reference/api/greet"
:sx-target "#ref-evt-vf-result" :sx-target "#ref-evt-vf-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-validate "true" :sx-validate "true"
@@ -847,13 +847,13 @@
"Open DevTools console, then navigate to a pure page (no :data expression). " "Open DevTools console, then navigate to a pure page (no :data expression). "
"You'll see \"sx:route client /path\" in the console — no network request is made.") "You'll see \"sx:route client /path\" in the console — no network request is made.")
(div :class "flex gap-2 flex-wrap" (div :class "flex gap-2 flex-wrap"
(a :href "/essays/" (a :href "/etc/essays/"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Essays") "Essays")
(a :href "/plans/" (a :href "/etc/plans/"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Plans") "Plans")
(a :href "/protocols/" (a :href "/applications/protocols/"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Protocols")) "Protocols"))
(p :class "text-xs text-stone-400" (p :class "text-xs text-stone-400"
@@ -866,7 +866,7 @@
(defcomp ~ref-event-sse-open-demo () (defcomp ~ref-event-sse-open-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :sx-sse "/hypermedia/reference/api/sse-time" (div :sx-sse "/geography/hypermedia/reference/api/sse-time"
:sx-sse-swap "time" :sx-sse-swap "time"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:sseOpen "document.getElementById('ref-evt-sseopen-status').textContent = 'Connected'; document.getElementById('ref-evt-sseopen-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-700'" :sx-on:sx:sseOpen "document.getElementById('ref-evt-sseopen-status').textContent = 'Connected'; document.getElementById('ref-evt-sseopen-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-700'"
@@ -884,7 +884,7 @@
(defcomp ~ref-event-sse-message-demo () (defcomp ~ref-event-sse-message-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :sx-sse "/hypermedia/reference/api/sse-time" (div :sx-sse "/geography/hypermedia/reference/api/sse-time"
:sx-sse-swap "time" :sx-sse-swap "time"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:sseMessage "var c = parseInt(document.getElementById('ref-evt-ssemsg-count').dataset.count || '0') + 1; document.getElementById('ref-evt-ssemsg-count').dataset.count = c; document.getElementById('ref-evt-ssemsg-count').textContent = c + ' messages received'" :sx-on:sx:sseMessage "var c = parseInt(document.getElementById('ref-evt-ssemsg-count').dataset.count || '0') + 1; document.getElementById('ref-evt-ssemsg-count').dataset.count = c; document.getElementById('ref-evt-ssemsg-count').textContent = c + ' messages received'"
@@ -902,7 +902,7 @@
(defcomp ~ref-event-sse-error-demo () (defcomp ~ref-event-sse-error-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :sx-sse "/hypermedia/reference/api/sse-time" (div :sx-sse "/geography/hypermedia/reference/api/sse-time"
:sx-sse-swap "time" :sx-sse-swap "time"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:sseError "document.getElementById('ref-evt-sseerr-status').textContent = 'Disconnected'; document.getElementById('ref-evt-sseerr-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-red-100 text-red-700'" :sx-on:sx:sseError "document.getElementById('ref-evt-sseerr-status').textContent = 'Disconnected'; document.getElementById('ref-evt-sseerr-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-red-100 text-red-700'"