Merge main into macros: resolve nav restructure conflicts
Take HEAD's updated typed-sx content (deftype, effect system details) with main's /etc/plans/ path prefix. Take main's newer sx-browser.js timestamp. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-11T17:12:38Z";
|
||||
var SX_VERSION = "2026-03-11T17:38:00Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -275,6 +275,7 @@
|
||||
|
||||
function error(msg) { throw new Error(msg); }
|
||||
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["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["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["string-length"] = function(s) { return String(s).length; };
|
||||
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["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["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["cons"] = function(x, c) { return [x].concat(c || []); };
|
||||
PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); };
|
||||
@@ -719,7 +720,7 @@
|
||||
// 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 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; })(); };
|
||||
|
||||
// eval-list
|
||||
@@ -5226,6 +5227,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||
if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse;
|
||||
if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs;
|
||||
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
|
||||
// (e.g. test-deps.sx in browser) can call them
|
||||
|
||||
@@ -76,7 +76,7 @@ def register_page_helpers(service: str, helpers: dict[str, Any]) -> None:
|
||||
Then in .sx::
|
||||
|
||||
(defpage docs-page
|
||||
:path "/docs/<slug>"
|
||||
:path "/language/docs/<slug>"
|
||||
:auth :public
|
||||
:content (docs-content slug))
|
||||
"""
|
||||
|
||||
@@ -91,7 +91,8 @@
|
||||
(= name "true") true
|
||||
(= name "false") false
|
||||
(= 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" (keyword-name expr)
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
(define build-ref-items-with-href
|
||||
(fn (items base-path detail-keys n-fields)
|
||||
;; 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)
|
||||
;; n-fields: 2 or 3 (number of fields per tuple)
|
||||
(map
|
||||
@@ -128,26 +128,26 @@
|
||||
"attributes"
|
||||
{"req-attrs" (build-ref-items-with-href
|
||||
(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
|
||||
(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
|
||||
(get raw-data "uniq-attrs")
|
||||
"/hypermedia/reference/attributes/" detail-keys 3)}
|
||||
"/geography/hypermedia/reference/attributes/" detail-keys 3)}
|
||||
|
||||
"headers"
|
||||
{"req-headers" (build-ref-items-with-href
|
||||
(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
|
||||
(get raw-data "resp-headers")
|
||||
"/hypermedia/reference/headers/" detail-keys 3)}
|
||||
"/geography/hypermedia/reference/headers/" detail-keys 3)}
|
||||
|
||||
"events"
|
||||
{"events-list" (build-ref-items-with-href
|
||||
(get raw-data "events-list")
|
||||
"/hypermedia/reference/events/" detail-keys 2)}
|
||||
"/geography/hypermedia/reference/events/" detail-keys 2)}
|
||||
|
||||
"js-api"
|
||||
{"js-api-list" (map (fn (item) {"name" (nth item 0) "desc" (nth item 1)})
|
||||
@@ -157,13 +157,13 @@
|
||||
:else
|
||||
{"req-attrs" (build-ref-items-with-href
|
||||
(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
|
||||
(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
|
||||
(get raw-data "uniq-attrs")
|
||||
"/hypermedia/reference/attributes/" detail-keys 3)})))
|
||||
"/geography/hypermedia/reference/attributes/" detail-keys 3)})))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
@@ -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["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["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["string-length"] = function(s) { return String(s).length; };
|
||||
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["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["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["cons"] = function(x, c) { return [x].concat(c || []); };
|
||||
PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); };
|
||||
@@ -1265,6 +1265,7 @@ PLATFORM_JS_PRE = '''
|
||||
|
||||
function error(msg) { throw new Error(msg); }
|
||||
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 jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse;
|
||||
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:
|
||||
lines.append('''
|
||||
// Expose deps module functions as primitives so runtime-evaluated SX code
|
||||
|
||||
@@ -1192,6 +1192,7 @@ def eval_expr(expr, env):
|
||||
elif sx_truthy((name == 'nil')):
|
||||
return NIL
|
||||
else:
|
||||
debug_log('Undefined symbol:', name, 'primitive?:', is_primitive(name))
|
||||
return error(sx_str('Undefined symbol: ', name))
|
||||
elif _match == 'keyword':
|
||||
return keyword_name(expr)
|
||||
|
||||
@@ -22,7 +22,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
# Example API endpoints (for live demos)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@bp.get("/hypermedia/examples/api/click")
|
||||
@bp.get("/geography/hypermedia/examples/api/click")
|
||||
async def api_click():
|
||||
from shared.sx.helpers import sx_response
|
||||
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})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/examples/api/form")
|
||||
@bp.post("/geography/hypermedia/examples/api/form")
|
||||
async def api_form():
|
||||
from shared.sx.helpers import sx_response
|
||||
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}
|
||||
|
||||
@bp.get("/hypermedia/examples/api/poll")
|
||||
@bp.get("/geography/hypermedia/examples/api/poll")
|
||||
async def api_poll():
|
||||
from shared.sx.helpers import sx_response
|
||||
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})')
|
||||
|
||||
@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):
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
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})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/examples/api/edit")
|
||||
@bp.post("/geography/hypermedia/examples/api/edit")
|
||||
async def api_edit_save():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
from sxc.pages.renders import _oob_code, _full_wire_text
|
||||
@@ -138,7 +138,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Lazy Loading ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/lazy")
|
||||
@bp.get("/geography/hypermedia/examples/api/lazy")
|
||||
async def api_lazy():
|
||||
from shared.sx.helpers import sx_response
|
||||
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 ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/scroll")
|
||||
@bp.get("/geography/hypermedia/examples/api/scroll")
|
||||
async def api_scroll():
|
||||
from shared.sx.helpers import sx_response
|
||||
from sxc.pages.renders import _oob_code, _full_wire_text
|
||||
@@ -166,7 +166,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
if next_page <= 6:
|
||||
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-target "#scroll-items"'
|
||||
f' :sx-swap "beforeend"'
|
||||
@@ -188,7 +188,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
_jobs: dict[str, int] = {}
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/examples/api/progress/start")
|
||||
@bp.post("/geography/hypermedia/examples/api/progress/start")
|
||||
async def api_progress_start():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
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 ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/search")
|
||||
@bp.get("/geography/hypermedia/examples/api/search")
|
||||
async def api_search():
|
||||
from shared.sx.helpers import sx_response
|
||||
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"}
|
||||
|
||||
@bp.get("/hypermedia/examples/api/validate")
|
||||
@bp.get("/geography/hypermedia/examples/api/validate")
|
||||
async def api_validate():
|
||||
from shared.sx.helpers import sx_response
|
||||
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})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/examples/api/validate/submit")
|
||||
@bp.post("/geography/hypermedia/examples/api/validate/submit")
|
||||
async def api_validate_submit():
|
||||
from shared.sx.helpers import sx_response
|
||||
form = await request.form
|
||||
@@ -279,7 +279,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Value Select ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/values")
|
||||
@bp.get("/geography/hypermedia/examples/api/values")
|
||||
async def api_values():
|
||||
from shared.sx.helpers import sx_response
|
||||
from sxc.pages.renders import _oob_code, _full_wire_text
|
||||
@@ -297,7 +297,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
# --- Reset on Submit ---
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/examples/api/reset-submit")
|
||||
@bp.post("/geography/hypermedia/examples/api/reset-submit")
|
||||
async def api_reset_submit():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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):
|
||||
from shared.sx.helpers import sx_response
|
||||
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})')
|
||||
|
||||
@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):
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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):
|
||||
from shared.sx.helpers import sx_response
|
||||
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
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/examples/api/bulk")
|
||||
@bp.post("/geography/hypermedia/examples/api/bulk")
|
||||
async def api_bulk():
|
||||
from shared.sx.helpers import sx_response
|
||||
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}
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/examples/api/swap-log")
|
||||
@bp.post("/geography/hypermedia/examples/api/swap-log")
|
||||
async def api_swap_log():
|
||||
from shared.sx.helpers import sx_response
|
||||
from sxc.pages.renders import _oob_code, _full_wire_text
|
||||
@@ -435,7 +435,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Select Filter (dashboard) ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/dashboard")
|
||||
@bp.get("/geography/hypermedia/examples/api/dashboard")
|
||||
async def api_dashboard():
|
||||
from shared.sx.helpers import sx_response
|
||||
from sxc.pages.renders import _oob_code, _full_wire_text
|
||||
@@ -480,7 +480,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
' (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):
|
||||
from shared.sx.helpers import sx_response
|
||||
from sxc.pages.renders import _oob_code, _full_wire_text
|
||||
@@ -500,7 +500,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Animations ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/animate")
|
||||
@bp.get("/geography/hypermedia/examples/api/animate")
|
||||
async def api_animate():
|
||||
from shared.sx.helpers import sx_response
|
||||
from sxc.pages.renders import _oob_code, _component_source_text, _full_wire_text
|
||||
@@ -516,7 +516,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Dialogs ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/dialog")
|
||||
@bp.get("/geography/hypermedia/examples/api/dialog")
|
||||
async def api_dialog():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
from sxc.pages.renders import _oob_code, _full_wire_text
|
||||
@@ -543,7 +543,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
"h": "Help panel opened",
|
||||
}
|
||||
|
||||
@bp.get("/hypermedia/examples/api/keyboard")
|
||||
@bp.get("/geography/hypermedia/examples/api/keyboard")
|
||||
async def api_keyboard():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
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})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.put("/hypermedia/examples/api/putpatch")
|
||||
@bp.put("/geography/hypermedia/examples/api/putpatch")
|
||||
async def api_pp_put():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
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 ---
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/examples/api/json-echo")
|
||||
@bp.post("/geography/hypermedia/examples/api/json-echo")
|
||||
async def api_json_echo():
|
||||
from shared.sx.helpers import sx_response
|
||||
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 ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/echo-vals")
|
||||
@bp.get("/geography/hypermedia/examples/api/echo-vals")
|
||||
async def api_echo_vals():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
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 ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/slow")
|
||||
@bp.get("/geography/hypermedia/examples/api/slow")
|
||||
async def api_slow():
|
||||
from shared.sx.helpers import sx_response
|
||||
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) ---
|
||||
|
||||
@bp.get("/hypermedia/examples/api/slow-search")
|
||||
@bp.get("/geography/hypermedia/examples/api/slow-search")
|
||||
async def api_slow_search():
|
||||
from shared.sx.helpers import sx_response
|
||||
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}
|
||||
|
||||
@bp.get("/hypermedia/examples/api/flaky")
|
||||
@bp.get("/geography/hypermedia/examples/api/flaky")
|
||||
async def api_flaky():
|
||||
from shared.sx.helpers import sx_response
|
||||
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
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
now = datetime.now().strftime("%H:%M:%S")
|
||||
@@ -727,7 +727,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
return sx_response(f'(<> {sx_src} {oob})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/reference/api/greet")
|
||||
@bp.post("/geography/hypermedia/reference/api/greet")
|
||||
async def ref_greet():
|
||||
from shared.sx.helpers import sx_response
|
||||
form = await request.form
|
||||
@@ -737,7 +737,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
return sx_response(f'(<> {sx_src} {oob})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.put("/hypermedia/reference/api/status")
|
||||
@bp.put("/geography/hypermedia/reference/api/status")
|
||||
async def ref_status():
|
||||
from shared.sx.helpers import sx_response
|
||||
form = await request.form
|
||||
@@ -747,7 +747,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
return sx_response(f'(<> {sx_src} {oob})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.patch("/hypermedia/reference/api/theme")
|
||||
@bp.patch("/geography/hypermedia/reference/api/theme")
|
||||
async def ref_theme():
|
||||
from shared.sx.helpers import sx_response
|
||||
form = await request.form
|
||||
@@ -757,13 +757,13 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
return sx_response(f'(<> {sx_src} {oob})')
|
||||
|
||||
@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):
|
||||
from shared.sx.helpers import sx_response
|
||||
oob = _ref_wire("sx-delete", '""')
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
q = request.args.get("q", "")
|
||||
@@ -774,7 +774,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
oob = _ref_wire("sx-trigger", sx_src)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
now = datetime.now().strftime("%H:%M:%S")
|
||||
@@ -782,7 +782,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
oob = _ref_wire("sx-swap", sx_src)
|
||||
return sx_response(f'(<> {sx_src} {oob})')
|
||||
|
||||
@bp.get("/hypermedia/reference/api/oob")
|
||||
@bp.get("/geography/hypermedia/reference/api/oob")
|
||||
async def ref_oob():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
now = datetime.now().strftime("%H:%M:%S")
|
||||
@@ -808,7 +808,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
oob = _ref_wire("sx-select", sx_src)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
await asyncio.sleep(0.8)
|
||||
@@ -818,7 +818,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
return sx_response(f'(<> {sx_src} {oob})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/reference/api/upload-name")
|
||||
@bp.post("/geography/hypermedia/reference/api/upload-name")
|
||||
async def ref_upload_name():
|
||||
from shared.sx.helpers import sx_response
|
||||
files = await request.files
|
||||
@@ -828,7 +828,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
oob = _ref_wire("sx-encoding", sx_src)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
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)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
vals = list(request.args.items())
|
||||
@@ -855,7 +855,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
return sx_response(f'(<> {sx_src} {oob_include})')
|
||||
|
||||
@csrf_exempt
|
||||
@bp.post("/hypermedia/reference/api/echo-vals")
|
||||
@bp.post("/geography/hypermedia/reference/api/echo-vals")
|
||||
async def ref_echo_vals_post():
|
||||
from shared.sx.helpers import sx_response
|
||||
form = await request.form
|
||||
@@ -871,7 +871,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
_ref_flaky = {"n": 0}
|
||||
|
||||
@bp.get("/hypermedia/reference/api/flaky")
|
||||
@bp.get("/geography/hypermedia/reference/api/flaky")
|
||||
async def ref_flaky():
|
||||
from shared.sx.helpers import sx_response
|
||||
_ref_flaky["n"] += 1
|
||||
@@ -882,7 +882,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
oob = _ref_wire("sx-retry", sx_src)
|
||||
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():
|
||||
from shared.sx.helpers import sx_response
|
||||
name = request.headers.get("SX-Prompt", "anonymous")
|
||||
@@ -890,7 +890,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
oob = _ref_wire("sx-prompt", sx_src)
|
||||
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 generate():
|
||||
for _ in range(30): # stream for 60 seconds max
|
||||
@@ -905,7 +905,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
_marsh_sale_idx = {"n": 0}
|
||||
|
||||
@bp.get("/reactive/api/flash-sale")
|
||||
@bp.get("/geography/reactive/api/flash-sale")
|
||||
async def api_marsh_flash_sale():
|
||||
from shared.sx.helpers import sx_response
|
||||
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}
|
||||
|
||||
@bp.get("/reactive/api/settle-data")
|
||||
@bp.get("/geography/reactive/api/settle-data")
|
||||
async def api_settle_data():
|
||||
from shared.sx.helpers import sx_response
|
||||
_settle_counter["n"] += 1
|
||||
@@ -943,7 +943,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Demo 4: signal-bound URL endpoints ---
|
||||
|
||||
@bp.get("/reactive/api/search/products")
|
||||
@bp.get("/geography/reactive/api/search/products")
|
||||
async def api_search_products():
|
||||
from shared.sx.helpers import sx_response
|
||||
q = request.args.get("q", "")
|
||||
@@ -962,7 +962,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
)
|
||||
return sx_response(sx_src)
|
||||
|
||||
@bp.get("/reactive/api/search/events")
|
||||
@bp.get("/geography/reactive/api/search/events")
|
||||
async def api_search_events():
|
||||
from shared.sx.helpers import sx_response
|
||||
q = request.args.get("q", "")
|
||||
@@ -981,7 +981,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
)
|
||||
return sx_response(sx_src)
|
||||
|
||||
@bp.get("/reactive/api/search/posts")
|
||||
@bp.get("/geography/reactive/api/search/posts")
|
||||
async def api_search_posts():
|
||||
from shared.sx.helpers import sx_response
|
||||
q = request.args.get("q", "")
|
||||
@@ -1002,7 +1002,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Demo 5: marsh transform endpoint ---
|
||||
|
||||
@bp.get("/reactive/api/catalog")
|
||||
@bp.get("/geography/reactive/api/catalog")
|
||||
async def api_catalog():
|
||||
from shared.sx.helpers import sx_response
|
||||
items = [
|
||||
@@ -1031,7 +1031,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Header demos ---
|
||||
|
||||
@bp.get("/hypermedia/reference/api/trigger-event")
|
||||
@bp.get("/geography/hypermedia/reference/api/trigger-event")
|
||||
async def ref_trigger_event():
|
||||
from shared.sx.helpers import sx_response
|
||||
now = datetime.now().strftime("%H:%M:%S")
|
||||
@@ -1040,7 +1040,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
resp.headers["SX-Trigger"] = "showNotice"
|
||||
return resp
|
||||
|
||||
@bp.get("/hypermedia/reference/api/retarget")
|
||||
@bp.get("/geography/hypermedia/reference/api/retarget")
|
||||
async def ref_retarget():
|
||||
from shared.sx.helpers import sx_response
|
||||
now = datetime.now().strftime("%H:%M:%S")
|
||||
@@ -1051,7 +1051,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Event demos ---
|
||||
|
||||
@bp.get("/hypermedia/reference/api/error-500")
|
||||
@bp.get("/geography/hypermedia/reference/api/error-500")
|
||||
async def ref_error_500():
|
||||
return Response("Server error", status=500, content_type="text/plain")
|
||||
|
||||
|
||||
@@ -10,79 +10,79 @@ from __future__ import annotations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
DOCS_NAV = [
|
||||
("Introduction", "/docs/introduction"),
|
||||
("Getting Started", "/docs/getting-started"),
|
||||
("Components", "/docs/components"),
|
||||
("Evaluator", "/docs/evaluator"),
|
||||
("Primitives", "/docs/primitives"),
|
||||
("CSS", "/docs/css"),
|
||||
("Server Rendering", "/docs/server-rendering"),
|
||||
("Introduction", "/language/docs/introduction"),
|
||||
("Getting Started", "/language/docs/getting-started"),
|
||||
("Components", "/language/docs/components"),
|
||||
("Evaluator", "/language/docs/evaluator"),
|
||||
("Primitives", "/language/docs/primitives"),
|
||||
("CSS", "/language/docs/css"),
|
||||
("Server Rendering", "/language/docs/server-rendering"),
|
||||
]
|
||||
|
||||
REFERENCE_NAV = [
|
||||
("Attributes", "/hypermedia/reference/attributes"),
|
||||
("Headers", "/hypermedia/reference/headers"),
|
||||
("Events", "/hypermedia/reference/events"),
|
||||
("JS API", "/hypermedia/reference/js-api"),
|
||||
("Attributes", "/geography/hypermedia/reference/attributes"),
|
||||
("Headers", "/geography/hypermedia/reference/headers"),
|
||||
("Events", "/geography/hypermedia/reference/events"),
|
||||
("JS API", "/geography/hypermedia/reference/js-api"),
|
||||
]
|
||||
|
||||
PROTOCOLS_NAV = [
|
||||
("Wire Format", "/protocols/wire-format"),
|
||||
("Fragments", "/protocols/fragments"),
|
||||
("Resolver I/O", "/protocols/resolver-io"),
|
||||
("Internal Services", "/protocols/internal-services"),
|
||||
("ActivityPub", "/protocols/activitypub"),
|
||||
("Future", "/protocols/future"),
|
||||
("Wire Format", "/applications/protocols/wire-format"),
|
||||
("Fragments", "/applications/protocols/fragments"),
|
||||
("Resolver I/O", "/applications/protocols/resolver-io"),
|
||||
("Internal Services", "/applications/protocols/internal-services"),
|
||||
("ActivityPub", "/applications/protocols/activitypub"),
|
||||
("Future", "/applications/protocols/future"),
|
||||
]
|
||||
|
||||
EXAMPLES_NAV = [
|
||||
("Click to Load", "/hypermedia/examples/click-to-load"),
|
||||
("Form Submission", "/hypermedia/examples/form-submission"),
|
||||
("Polling", "/hypermedia/examples/polling"),
|
||||
("Delete Row", "/hypermedia/examples/delete-row"),
|
||||
("Inline Edit", "/hypermedia/examples/inline-edit"),
|
||||
("OOB Swaps", "/hypermedia/examples/oob-swaps"),
|
||||
("Lazy Loading", "/hypermedia/examples/lazy-loading"),
|
||||
("Infinite Scroll", "/hypermedia/examples/infinite-scroll"),
|
||||
("Progress Bar", "/hypermedia/examples/progress-bar"),
|
||||
("Active Search", "/hypermedia/examples/active-search"),
|
||||
("Inline Validation", "/hypermedia/examples/inline-validation"),
|
||||
("Value Select", "/hypermedia/examples/value-select"),
|
||||
("Reset on Submit", "/hypermedia/examples/reset-on-submit"),
|
||||
("Edit Row", "/hypermedia/examples/edit-row"),
|
||||
("Bulk Update", "/hypermedia/examples/bulk-update"),
|
||||
("Swap Positions", "/hypermedia/examples/swap-positions"),
|
||||
("Select Filter", "/hypermedia/examples/select-filter"),
|
||||
("Tabs", "/hypermedia/examples/tabs"),
|
||||
("Animations", "/hypermedia/examples/animations"),
|
||||
("Dialogs", "/hypermedia/examples/dialogs"),
|
||||
("Keyboard Shortcuts", "/hypermedia/examples/keyboard-shortcuts"),
|
||||
("PUT / PATCH", "/hypermedia/examples/put-patch"),
|
||||
("JSON Encoding", "/hypermedia/examples/json-encoding"),
|
||||
("Vals & Headers", "/hypermedia/examples/vals-and-headers"),
|
||||
("Loading States", "/hypermedia/examples/loading-states"),
|
||||
("Request Abort", "/hypermedia/examples/sync-replace"),
|
||||
("Retry", "/hypermedia/examples/retry"),
|
||||
("Click to Load", "/geography/hypermedia/examples/click-to-load"),
|
||||
("Form Submission", "/geography/hypermedia/examples/form-submission"),
|
||||
("Polling", "/geography/hypermedia/examples/polling"),
|
||||
("Delete Row", "/geography/hypermedia/examples/delete-row"),
|
||||
("Inline Edit", "/geography/hypermedia/examples/inline-edit"),
|
||||
("OOB Swaps", "/geography/hypermedia/examples/oob-swaps"),
|
||||
("Lazy Loading", "/geography/hypermedia/examples/lazy-loading"),
|
||||
("Infinite Scroll", "/geography/hypermedia/examples/infinite-scroll"),
|
||||
("Progress Bar", "/geography/hypermedia/examples/progress-bar"),
|
||||
("Active Search", "/geography/hypermedia/examples/active-search"),
|
||||
("Inline Validation", "/geography/hypermedia/examples/inline-validation"),
|
||||
("Value Select", "/geography/hypermedia/examples/value-select"),
|
||||
("Reset on Submit", "/geography/hypermedia/examples/reset-on-submit"),
|
||||
("Edit Row", "/geography/hypermedia/examples/edit-row"),
|
||||
("Bulk Update", "/geography/hypermedia/examples/bulk-update"),
|
||||
("Swap Positions", "/geography/hypermedia/examples/swap-positions"),
|
||||
("Select Filter", "/geography/hypermedia/examples/select-filter"),
|
||||
("Tabs", "/geography/hypermedia/examples/tabs"),
|
||||
("Animations", "/geography/hypermedia/examples/animations"),
|
||||
("Dialogs", "/geography/hypermedia/examples/dialogs"),
|
||||
("Keyboard Shortcuts", "/geography/hypermedia/examples/keyboard-shortcuts"),
|
||||
("PUT / PATCH", "/geography/hypermedia/examples/put-patch"),
|
||||
("JSON Encoding", "/geography/hypermedia/examples/json-encoding"),
|
||||
("Vals & Headers", "/geography/hypermedia/examples/vals-and-headers"),
|
||||
("Loading States", "/geography/hypermedia/examples/loading-states"),
|
||||
("Request Abort", "/geography/hypermedia/examples/sync-replace"),
|
||||
("Retry", "/geography/hypermedia/examples/retry"),
|
||||
]
|
||||
|
||||
ESSAYS_NAV = [
|
||||
("sx sucks", "/essays/sx-sucks"),
|
||||
("Why S-Expressions", "/essays/why-sexps"),
|
||||
("The htmx/React Hybrid", "/essays/htmx-react-hybrid"),
|
||||
("On-Demand CSS", "/essays/on-demand-css"),
|
||||
("Client Reactivity", "/essays/client-reactivity"),
|
||||
("SX Native", "/essays/sx-native"),
|
||||
("The SX Manifesto", "/philosophy/sx-manifesto"),
|
||||
("Tail-Call Optimization", "/essays/tail-call-optimization"),
|
||||
("Continuations", "/essays/continuations"),
|
||||
("sx sucks", "/etc/essays/sx-sucks"),
|
||||
("Why S-Expressions", "/etc/essays/why-sexps"),
|
||||
("The htmx/React Hybrid", "/etc/essays/htmx-react-hybrid"),
|
||||
("On-Demand CSS", "/etc/essays/on-demand-css"),
|
||||
("Client Reactivity", "/etc/essays/client-reactivity"),
|
||||
("SX Native", "/etc/essays/sx-native"),
|
||||
("The SX Manifesto", "/etc/philosophy/sx-manifesto"),
|
||||
("Tail-Call Optimization", "/etc/essays/tail-call-optimization"),
|
||||
("Continuations", "/etc/essays/continuations"),
|
||||
]
|
||||
|
||||
MAIN_NAV = [
|
||||
("Docs", "/docs/introduction"),
|
||||
("Reference", "/hypermedia/reference/"),
|
||||
("Protocols", "/protocols/wire-format"),
|
||||
("Examples", "/hypermedia/examples/click-to-load"),
|
||||
("Essays", "/essays/sx-sucks"),
|
||||
("Docs", "/language/docs/introduction"),
|
||||
("Reference", "/geography/hypermedia/reference/"),
|
||||
("Protocols", "/applications/protocols/wire-format"),
|
||||
("Examples", "/geography/hypermedia/examples/click-to-load"),
|
||||
("Essays", "/etc/essays/sx-sucks"),
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -539,7 +539,7 @@ HEADER_DETAILS: dict[str, dict] = {
|
||||
';; SX-Location: /docs/introduction\n'
|
||||
';;\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": {
|
||||
@@ -676,8 +676,8 @@ EVENT_DETAILS: dict[str, dict] = {
|
||||
';; sx-boost containers try client routing first.\n'
|
||||
';; On success, sx:clientRoute fires on the swap target.\n'
|
||||
'(nav :sx-boost "#main-panel"\n'
|
||||
' (a :href "/essays/" "Essays")\n'
|
||||
' (a :href "/plans/" "Plans"))\n'
|
||||
' (a :href "/etc/essays/" "Essays")\n'
|
||||
' (a :href "/etc/plans/" "Plans"))\n'
|
||||
'\n'
|
||||
';; Listen in body.js:\n'
|
||||
';; document.body.addEventListener("sx:clientRoute",\n'
|
||||
@@ -744,7 +744,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-get-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' "Load server time")'
|
||||
@@ -764,7 +764,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-post-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' (input :type "text" :name "name"\n'
|
||||
@@ -786,7 +786,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-put-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-vals "{\\"status\\": \\"published\\"}"\n'
|
||||
@@ -807,7 +807,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-delete-demo",
|
||||
"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-swap "delete"\n'
|
||||
' "Remove")'
|
||||
@@ -826,7 +826,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-patch-demo",
|
||||
"example": (
|
||||
'(button :sx-patch "/hypermedia/reference/api/theme"\n'
|
||||
'(button :sx-patch "/geography/hypermedia/reference/api/theme"\n'
|
||||
' :sx-vals "{\\"theme\\": \\"dark\\"}"\n'
|
||||
' :sx-target "#ref-patch-val"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -852,7 +852,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"example": (
|
||||
'(input :type "text" :name "q"\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-target "#ref-trigger-result"\n'
|
||||
' :sx-swap "innerHTML")'
|
||||
@@ -874,12 +874,12 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"demo": "ref-target-demo",
|
||||
"example": (
|
||||
';; 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-swap "innerHTML"\n'
|
||||
' "Update Box A")\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-swap "innerHTML"\n'
|
||||
' "Update Box B")'
|
||||
@@ -894,13 +894,13 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"demo": "ref-swap-demo",
|
||||
"example": (
|
||||
';; 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-swap "beforeend"\n'
|
||||
' "beforeend")\n'
|
||||
'\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-swap "afterbegin"\n'
|
||||
' "afterbegin")'
|
||||
@@ -921,7 +921,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-oob-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' "Update both boxes")'
|
||||
@@ -944,7 +944,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-select-demo",
|
||||
"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-select "#the-content"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -968,7 +968,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-confirm-demo",
|
||||
"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-swap "delete"\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",
|
||||
"example": (
|
||||
'(a :href "/hypermedia/reference/attributes/sx-get"\n'
|
||||
' :sx-get "/hypermedia/reference/attributes/sx-get"\n'
|
||||
'(a :href "/geography/hypermedia/reference/attributes/sx-get"\n'
|
||||
' :sx-get "/geography/hypermedia/reference/attributes/sx-get"\n'
|
||||
' :sx-target "#main-panel"\n'
|
||||
' :sx-select "#main-panel"\n'
|
||||
' :sx-swap "outerHTML"\n'
|
||||
@@ -1003,7 +1003,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"example": (
|
||||
'(input :type "text" :name "q"\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-sync "replace"\n'
|
||||
' :sx-target "#ref-sync-result"\n'
|
||||
@@ -1024,7 +1024,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-encoding-demo",
|
||||
"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-target "#ref-encoding-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -1044,7 +1044,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-headers-demo",
|
||||
"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-target "#ref-headers-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -1073,7 +1073,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
' (option :value "books" "Books")\n'
|
||||
' (option :value "tools" "Tools"))\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-target "#ref-include-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -1097,7 +1097,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-vals-demo",
|
||||
"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-target "#ref-vals-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -1112,8 +1112,8 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-media-demo",
|
||||
"example": (
|
||||
'(a :href "/hypermedia/reference/attributes/sx-get"\n'
|
||||
' :sx-get "/hypermedia/reference/attributes/sx-get"\n'
|
||||
'(a :href "/geography/hypermedia/reference/attributes/sx-get"\n'
|
||||
' :sx-get "/geography/hypermedia/reference/attributes/sx-get"\n'
|
||||
' :sx-target "#main-panel"\n'
|
||||
' :sx-select "#main-panel"\n'
|
||||
' :sx-swap "outerHTML"\n'
|
||||
@@ -1133,7 +1133,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
';; Left box: sx works normally\n'
|
||||
';; Right box: sx-disable prevents any sx behavior\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-swap "innerHTML"\n'
|
||||
' "Load")\n'
|
||||
@@ -1166,7 +1166,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-retry-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-retry "true"\n'
|
||||
@@ -1221,9 +1221,9 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"example": (
|
||||
';; Boost with configurable target\n'
|
||||
'(nav :sx-boost "#main-panel"\n'
|
||||
' (a :href "/docs/introduction" "Introduction")\n'
|
||||
' (a :href "/docs/components" "Components")\n'
|
||||
' (a :href "/docs/evaluator" "Evaluator"))\n'
|
||||
' (a :href "/language/docs/introduction" "Introduction")\n'
|
||||
' (a :href "/language/docs/components" "Components")\n'
|
||||
' (a :href "/language/docs/evaluator" "Evaluator"))\n'
|
||||
'\n'
|
||||
';; All links swap into #main-panel automatically.\n'
|
||||
';; Pure pages render client-side (no server request).'
|
||||
@@ -1239,7 +1239,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-preload-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-preload "mouseover"\n'
|
||||
@@ -1275,7 +1275,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-indicator-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-indicator "#ref-spinner"\n'
|
||||
@@ -1302,7 +1302,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-validate-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-validate "true"\n'
|
||||
@@ -1340,7 +1340,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-optimistic-demo",
|
||||
"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-swap "delete"\n'
|
||||
' :sx-optimistic "remove"\n'
|
||||
@@ -1362,7 +1362,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-replace-url-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-replace-url "true"\n'
|
||||
@@ -1378,7 +1378,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-disabled-elt-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-disabled-elt "this"\n'
|
||||
@@ -1394,7 +1394,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-prompt-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-prompt "Enter your name:"\n'
|
||||
@@ -1414,7 +1414,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-params-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-params "name"\n'
|
||||
@@ -1433,7 +1433,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-sse-demo",
|
||||
"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-target "#ref-sse-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"Each bar shows how many of the "
|
||||
(strong (str total-components))
|
||||
" 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. "
|
||||
"Click a page to see its component tree; expand a component to see its SX source.")
|
||||
|
||||
|
||||
@@ -373,7 +373,7 @@
|
||||
"directly. They arrive as part of the rendered HTML — keyframes, custom properties, "
|
||||
"scoped rules, anything.")
|
||||
(li (strong "External stylesheets: ") "Components can reference pre-loaded CSS files "
|
||||
"or lazy-load them via " (code "~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 "
|
||||
(code "--color-primary") " etc. via a " (code "<style>") " block. Everything "
|
||||
"using " (code "var()") " repaints automatically."))
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
(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.")
|
||||
(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))))
|
||||
|
||||
(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
@@ -9,7 +9,7 @@
|
||||
(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.")
|
||||
(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"
|
||||
(p :class "text-stone-600"
|
||||
@@ -27,7 +27,7 @@
|
||||
(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.")
|
||||
(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"
|
||||
(p :class "text-stone-600"
|
||||
@@ -51,7 +51,7 @@
|
||||
(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.")
|
||||
(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"
|
||||
"Consider what this enables for AI:")
|
||||
(ul :class "space-y-2 text-stone-600 mt-2"
|
||||
@@ -63,7 +63,7 @@
|
||||
(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.")
|
||||
(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"
|
||||
"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"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
(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.\"")
|
||||
(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"
|
||||
(p :class "text-stone-600"
|
||||
@@ -98,7 +98,7 @@
|
||||
(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.")
|
||||
(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"
|
||||
(p :class "text-stone-600"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -127,7 +127,7 @@
|
||||
(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.")
|
||||
(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"
|
||||
"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"
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
: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 (~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\")"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
: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."))
|
||||
@@ -20,8 +20,8 @@
|
||||
:description "Forms with sx-post submit via AJAX and swap the response into a target."
|
||||
:demo-description "Enter a name and submit."
|
||||
: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\"))"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
:wire-placeholder-id "form-wire"))
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
:description "Use sx-trigger with \"every\" to poll the server at regular intervals."
|
||||
:demo-description "This div polls the server every 2 seconds."
|
||||
: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...\")"
|
||||
: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})')"
|
||||
: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(\"/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"
|
||||
:wire-placeholder-id "poll-wire"
|
||||
:wire-note "Updates every 2 seconds — watch the time and count change."))
|
||||
@@ -49,7 +49,7 @@
|
||||
(list "4" "Deploy to production")
|
||||
(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\")"
|
||||
: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"
|
||||
:wire-placeholder-id "delete-wire"
|
||||
: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 (~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\")"
|
||||
: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-heading "Components"
|
||||
:handler-heading "Server handlers"
|
||||
@@ -73,8 +73,8 @@
|
||||
: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 (~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\")"
|
||||
: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}\")))')"
|
||||
: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(\"/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-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."
|
||||
:demo-description "Content loads automatically when the page renders."
|
||||
: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...\"))"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Scroll down in the container to load more items (5 pages total)."
|
||||
: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...\")"
|
||||
: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)"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Click start to begin a simulated job."
|
||||
: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\")"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Type to search through 20 programming languages."
|
||||
: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...\")"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Enter an email and click away (blur) to validate."
|
||||
: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\")"
|
||||
: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 ...)')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Select a category to populate the item dropdown."
|
||||
: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\"))"
|
||||
: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 ...))')"
|
||||
: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(\"/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"
|
||||
:wire-placeholder-id "values-wire"))
|
||||
|
||||
@@ -150,8 +150,8 @@
|
||||
: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 (~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\"))"
|
||||
: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 \"...\")')"
|
||||
: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(\"/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"
|
||||
:wire-placeholder-id "reset-wire"))
|
||||
|
||||
@@ -165,8 +165,8 @@
|
||||
(list "2" "Widget B" "24.50" "89")
|
||||
(list "3" "Widget C" "12.00" "305")
|
||||
(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\")"
|
||||
: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 ...)')"
|
||||
: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(\"/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"
|
||||
:wire-placeholder-id "editrow-wire"))
|
||||
|
||||
@@ -181,8 +181,8 @@
|
||||
(list "3" "Carol Zhang" "carol@example.com" "active")
|
||||
(list "4" "Dan Okafor" "dan@example.com" "inactive")
|
||||
(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\")"
|
||||
: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)"
|
||||
: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(\"/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"
|
||||
:wire-placeholder-id "bulk-wire"))
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
:demo-description "Try each button to see different swap behaviours."
|
||||
: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\")"
|
||||
: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"))
|
||||
|
||||
(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."
|
||||
:demo-description "Different buttons select different parts of the same server response."
|
||||
: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\")"
|
||||
: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\" ...))')"
|
||||
: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(\"/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"))
|
||||
|
||||
(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."
|
||||
:demo-description "Click tabs to switch content. Watch the browser URL change."
|
||||
: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\")"
|
||||
: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)"
|
||||
: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(\"/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"))
|
||||
|
||||
(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."
|
||||
:demo-description "Click to swap in content with a fade-in animation."
|
||||
: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!\")))"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Click to open a modal dialog."
|
||||
: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\")"
|
||||
: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(\"\")"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Press s, n, or h on your keyboard."
|
||||
: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...\")"
|
||||
: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]}\")')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
: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")
|
||||
: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)\"))"
|
||||
: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 ...)')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Submit the form and see the JSON body the server received."
|
||||
: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\"))"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Click each button to see what the server receives."
|
||||
: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\")"
|
||||
: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 (...))')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Click the button — it shows a spinner during the 2-second request."
|
||||
: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\")"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Type quickly — only the latest result appears despite random 0.5-2s server delays."
|
||||
: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...\")"
|
||||
: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}\")')"
|
||||
: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(\"/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"
|
||||
: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."
|
||||
:demo-description "Click the button — watch it retry automatically after failures."
|
||||
: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\")"
|
||||
: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} ...)')"
|
||||
: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(\"/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"
|
||||
:wire-placeholder-id "retry-wire"))
|
||||
|
||||
@@ -32,26 +32,26 @@
|
||||
(p :class "text-stone-600 mb-6"
|
||||
"Complete reference for the sx client library.")
|
||||
(div :class "grid gap-4 sm:grid-cols-2"
|
||||
(a :href "/hypermedia/reference/attributes"
|
||||
:sx-get "/hypermedia/reference/attributes" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/geography/hypermedia/reference/attributes"
|
||||
:sx-get "/geography/hypermedia/reference/attributes" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
: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"
|
||||
(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."))
|
||||
(a :href "/hypermedia/reference/headers"
|
||||
:sx-get "/hypermedia/reference/headers" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/geography/hypermedia/reference/headers"
|
||||
:sx-get "/geography/hypermedia/reference/headers" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
: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"
|
||||
(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."))
|
||||
(a :href "/hypermedia/reference/events"
|
||||
:sx-get "/hypermedia/reference/events" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/geography/hypermedia/reference/events"
|
||||
:sx-get "/geography/hypermedia/reference/events" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
: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"
|
||||
(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."))
|
||||
(a :href "/hypermedia/reference/js-api"
|
||||
:sx-get "/hypermedia/reference/js-api" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/geography/hypermedia/reference/js-api"
|
||||
:sx-get "/geography/hypermedia/reference/js-api" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
: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"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "JS API")
|
||||
|
||||
@@ -3,232 +3,230 @@
|
||||
;; @css aria-selected:bg-violet-200 aria-selected:text-violet-900
|
||||
|
||||
(define docs-nav-items (list
|
||||
(dict :label "Introduction" :href "/docs/introduction")
|
||||
(dict :label "Getting Started" :href "/docs/getting-started")
|
||||
(dict :label "Components" :href "/docs/components")
|
||||
(dict :label "Evaluator" :href "/docs/evaluator")
|
||||
(dict :label "Primitives" :href "/docs/primitives")
|
||||
(dict :label "Special Forms" :href "/docs/special-forms")
|
||||
(dict :label "Server Rendering" :href "/docs/server-rendering")))
|
||||
(dict :label "Introduction" :href "/language/docs/introduction")
|
||||
(dict :label "Getting Started" :href "/language/docs/getting-started")
|
||||
(dict :label "Components" :href "/language/docs/components")
|
||||
(dict :label "Evaluator" :href "/language/docs/evaluator")
|
||||
(dict :label "Primitives" :href "/language/docs/primitives")
|
||||
(dict :label "Special Forms" :href "/language/docs/special-forms")
|
||||
(dict :label "Server Rendering" :href "/language/docs/server-rendering")))
|
||||
|
||||
(define reference-nav-items (list
|
||||
(dict :label "Attributes" :href "/hypermedia/reference/attributes")
|
||||
(dict :label "Headers" :href "/hypermedia/reference/headers")
|
||||
(dict :label "Events" :href "/hypermedia/reference/events")
|
||||
(dict :label "JS API" :href "/hypermedia/reference/js-api")))
|
||||
(dict :label "Attributes" :href "/geography/hypermedia/reference/attributes")
|
||||
(dict :label "Headers" :href "/geography/hypermedia/reference/headers")
|
||||
(dict :label "Events" :href "/geography/hypermedia/reference/events")
|
||||
(dict :label "JS API" :href "/geography/hypermedia/reference/js-api")))
|
||||
|
||||
(define protocols-nav-items (list
|
||||
(dict :label "Wire Format" :href "/protocols/wire-format")
|
||||
(dict :label "Fragments" :href "/protocols/fragments")
|
||||
(dict :label "Resolver I/O" :href "/protocols/resolver-io")
|
||||
(dict :label "Internal Services" :href "/protocols/internal-services")
|
||||
(dict :label "ActivityPub" :href "/protocols/activitypub")
|
||||
(dict :label "Future" :href "/protocols/future")))
|
||||
(dict :label "Wire Format" :href "/applications/protocols/wire-format")
|
||||
(dict :label "Fragments" :href "/applications/protocols/fragments")
|
||||
(dict :label "Resolver I/O" :href "/applications/protocols/resolver-io")
|
||||
(dict :label "Internal Services" :href "/applications/protocols/internal-services")
|
||||
(dict :label "ActivityPub" :href "/applications/protocols/activitypub")
|
||||
(dict :label "Future" :href "/applications/protocols/future")))
|
||||
|
||||
(define examples-nav-items (list
|
||||
(dict :label "Click to Load" :href "/hypermedia/examples/click-to-load")
|
||||
(dict :label "Form Submission" :href "/hypermedia/examples/form-submission")
|
||||
(dict :label "Polling" :href "/hypermedia/examples/polling")
|
||||
(dict :label "Delete Row" :href "/hypermedia/examples/delete-row")
|
||||
(dict :label "Inline Edit" :href "/hypermedia/examples/inline-edit")
|
||||
(dict :label "OOB Swaps" :href "/hypermedia/examples/oob-swaps")
|
||||
(dict :label "Lazy Loading" :href "/hypermedia/examples/lazy-loading")
|
||||
(dict :label "Infinite Scroll" :href "/hypermedia/examples/infinite-scroll")
|
||||
(dict :label "Progress Bar" :href "/hypermedia/examples/progress-bar")
|
||||
(dict :label "Active Search" :href "/hypermedia/examples/active-search")
|
||||
(dict :label "Inline Validation" :href "/hypermedia/examples/inline-validation")
|
||||
(dict :label "Value Select" :href "/hypermedia/examples/value-select")
|
||||
(dict :label "Reset on Submit" :href "/hypermedia/examples/reset-on-submit")
|
||||
(dict :label "Edit Row" :href "/hypermedia/examples/edit-row")
|
||||
(dict :label "Bulk Update" :href "/hypermedia/examples/bulk-update")
|
||||
(dict :label "Swap Positions" :href "/hypermedia/examples/swap-positions")
|
||||
(dict :label "Select Filter" :href "/hypermedia/examples/select-filter")
|
||||
(dict :label "Tabs" :href "/hypermedia/examples/tabs")
|
||||
(dict :label "Animations" :href "/hypermedia/examples/animations")
|
||||
(dict :label "Dialogs" :href "/hypermedia/examples/dialogs")
|
||||
(dict :label "Keyboard Shortcuts" :href "/hypermedia/examples/keyboard-shortcuts")
|
||||
(dict :label "PUT / PATCH" :href "/hypermedia/examples/put-patch")
|
||||
(dict :label "JSON Encoding" :href "/hypermedia/examples/json-encoding")
|
||||
(dict :label "Vals & Headers" :href "/hypermedia/examples/vals-and-headers")
|
||||
(dict :label "Loading States" :href "/hypermedia/examples/loading-states")
|
||||
(dict :label "Request Abort" :href "/hypermedia/examples/sync-replace")
|
||||
(dict :label "Retry" :href "/hypermedia/examples/retry")))
|
||||
(dict :label "Click to Load" :href "/geography/hypermedia/examples/click-to-load")
|
||||
(dict :label "Form Submission" :href "/geography/hypermedia/examples/form-submission")
|
||||
(dict :label "Polling" :href "/geography/hypermedia/examples/polling")
|
||||
(dict :label "Delete Row" :href "/geography/hypermedia/examples/delete-row")
|
||||
(dict :label "Inline Edit" :href "/geography/hypermedia/examples/inline-edit")
|
||||
(dict :label "OOB Swaps" :href "/geography/hypermedia/examples/oob-swaps")
|
||||
(dict :label "Lazy Loading" :href "/geography/hypermedia/examples/lazy-loading")
|
||||
(dict :label "Infinite Scroll" :href "/geography/hypermedia/examples/infinite-scroll")
|
||||
(dict :label "Progress Bar" :href "/geography/hypermedia/examples/progress-bar")
|
||||
(dict :label "Active Search" :href "/geography/hypermedia/examples/active-search")
|
||||
(dict :label "Inline Validation" :href "/geography/hypermedia/examples/inline-validation")
|
||||
(dict :label "Value Select" :href "/geography/hypermedia/examples/value-select")
|
||||
(dict :label "Reset on Submit" :href "/geography/hypermedia/examples/reset-on-submit")
|
||||
(dict :label "Edit Row" :href "/geography/hypermedia/examples/edit-row")
|
||||
(dict :label "Bulk Update" :href "/geography/hypermedia/examples/bulk-update")
|
||||
(dict :label "Swap Positions" :href "/geography/hypermedia/examples/swap-positions")
|
||||
(dict :label "Select Filter" :href "/geography/hypermedia/examples/select-filter")
|
||||
(dict :label "Tabs" :href "/geography/hypermedia/examples/tabs")
|
||||
(dict :label "Animations" :href "/geography/hypermedia/examples/animations")
|
||||
(dict :label "Dialogs" :href "/geography/hypermedia/examples/dialogs")
|
||||
(dict :label "Keyboard Shortcuts" :href "/geography/hypermedia/examples/keyboard-shortcuts")
|
||||
(dict :label "PUT / PATCH" :href "/geography/hypermedia/examples/put-patch")
|
||||
(dict :label "JSON Encoding" :href "/geography/hypermedia/examples/json-encoding")
|
||||
(dict :label "Vals & Headers" :href "/geography/hypermedia/examples/vals-and-headers")
|
||||
(dict :label "Loading States" :href "/geography/hypermedia/examples/loading-states")
|
||||
(dict :label "Request Abort" :href "/geography/hypermedia/examples/sync-replace")
|
||||
(dict :label "Retry" :href "/geography/hypermedia/examples/retry")))
|
||||
|
||||
(define cssx-nav-items (list
|
||||
(dict :label "Overview" :href "/cssx/")
|
||||
(dict :label "Patterns" :href "/cssx/patterns")
|
||||
(dict :label "Delivery" :href "/cssx/delivery")
|
||||
(dict :label "Async CSS" :href "/cssx/async")
|
||||
(dict :label "Live Styles" :href "/cssx/live")
|
||||
(dict :label "Comparisons" :href "/cssx/comparisons")
|
||||
(dict :label "Philosophy" :href "/cssx/philosophy")))
|
||||
(dict :label "Overview" :href "/applications/cssx/")
|
||||
(dict :label "Patterns" :href "/applications/cssx/patterns")
|
||||
(dict :label "Delivery" :href "/applications/cssx/delivery")
|
||||
(dict :label "Async CSS" :href "/applications/cssx/async")
|
||||
(dict :label "Live Styles" :href "/applications/cssx/live")
|
||||
(dict :label "Comparisons" :href "/applications/cssx/comparisons")
|
||||
(dict :label "Philosophy" :href "/applications/cssx/philosophy")))
|
||||
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")))
|
||||
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")))
|
||||
|
||||
(define specs-nav-items (list
|
||||
(dict :label "Architecture" :href "/specs/")
|
||||
(dict :label "Core" :href "/specs/core")
|
||||
(dict :label "Parser" :href "/specs/parser")
|
||||
(dict :label "Evaluator" :href "/specs/evaluator")
|
||||
(dict :label "Primitives" :href "/specs/primitives")
|
||||
(dict :label "Special Forms" :href "/specs/special-forms")
|
||||
(dict :label "Renderer" :href "/specs/renderer")
|
||||
(dict :label "Adapters" :href "/specs/adapters")
|
||||
(dict :label "DOM Adapter" :href "/specs/adapter-dom")
|
||||
(dict :label "HTML Adapter" :href "/specs/adapter-html")
|
||||
(dict :label "SX Wire Adapter" :href "/specs/adapter-sx")
|
||||
(dict :label "Browser" :href "/specs/browser")
|
||||
(dict :label "SxEngine" :href "/specs/engine")
|
||||
(dict :label "Orchestration" :href "/specs/orchestration")
|
||||
(dict :label "Boot" :href "/specs/boot")
|
||||
(dict :label "Continuations" :href "/specs/continuations")
|
||||
(dict :label "call/cc" :href "/specs/callcc")
|
||||
(dict :label "Deps" :href "/specs/deps")
|
||||
(dict :label "Router" :href "/specs/router")))
|
||||
(dict :label "Architecture" :href "/language/specs/")
|
||||
(dict :label "Core" :href "/language/specs/core")
|
||||
(dict :label "Parser" :href "/language/specs/parser")
|
||||
(dict :label "Evaluator" :href "/language/specs/evaluator")
|
||||
(dict :label "Primitives" :href "/language/specs/primitives")
|
||||
(dict :label "Special Forms" :href "/language/specs/special-forms")
|
||||
(dict :label "Renderer" :href "/language/specs/renderer")
|
||||
(dict :label "Adapters" :href "/language/specs/adapters")
|
||||
(dict :label "DOM Adapter" :href "/language/specs/adapter-dom")
|
||||
(dict :label "HTML Adapter" :href "/language/specs/adapter-html")
|
||||
(dict :label "SX Wire Adapter" :href "/language/specs/adapter-sx")
|
||||
(dict :label "Browser" :href "/language/specs/browser")
|
||||
(dict :label "SxEngine" :href "/language/specs/engine")
|
||||
(dict :label "Orchestration" :href "/language/specs/orchestration")
|
||||
(dict :label "Boot" :href "/language/specs/boot")
|
||||
(dict :label "Continuations" :href "/language/specs/continuations")
|
||||
(dict :label "call/cc" :href "/language/specs/callcc")
|
||||
(dict :label "Deps" :href "/language/specs/deps")
|
||||
(dict :label "Router" :href "/language/specs/router")))
|
||||
|
||||
(define testing-nav-items (list
|
||||
(dict :label "Overview" :href "/testing/")
|
||||
(dict :label "Evaluator" :href "/testing/eval")
|
||||
(dict :label "Parser" :href "/testing/parser")
|
||||
(dict :label "Router" :href "/testing/router")
|
||||
(dict :label "Renderer" :href "/testing/render")
|
||||
(dict :label "Dependencies" :href "/testing/deps")
|
||||
(dict :label "Engine" :href "/testing/engine")
|
||||
(dict :label "Orchestration" :href "/testing/orchestration")
|
||||
(dict :label "Runners" :href "/testing/runners")))
|
||||
(dict :label "Overview" :href "/language/testing/")
|
||||
(dict :label "Evaluator" :href "/language/testing/eval")
|
||||
(dict :label "Parser" :href "/language/testing/parser")
|
||||
(dict :label "Router" :href "/language/testing/router")
|
||||
(dict :label "Renderer" :href "/language/testing/render")
|
||||
(dict :label "Dependencies" :href "/language/testing/deps")
|
||||
(dict :label "Engine" :href "/language/testing/engine")
|
||||
(dict :label "Orchestration" :href "/language/testing/orchestration")
|
||||
(dict :label "Runners" :href "/language/testing/runners")))
|
||||
|
||||
(define isomorphism-nav-items (list
|
||||
(dict :label "Roadmap" :href "/isomorphism/")
|
||||
(dict :label "Bundle Analyzer" :href "/isomorphism/bundle-analyzer")
|
||||
(dict :label "Routing Analyzer" :href "/isomorphism/routing-analyzer")
|
||||
(dict :label "Data Test" :href "/isomorphism/data-test")
|
||||
(dict :label "Async IO" :href "/isomorphism/async-io")
|
||||
(dict :label "Streaming" :href "/isomorphism/streaming")
|
||||
(dict :label "Affinity" :href "/isomorphism/affinity")
|
||||
(dict :label "Optimistic" :href "/isomorphism/optimistic")
|
||||
(dict :label "Offline" :href "/isomorphism/offline")))
|
||||
(dict :label "Roadmap" :href "/geography/isomorphism/")
|
||||
(dict :label "Bundle Analyzer" :href "/geography/isomorphism/bundle-analyzer")
|
||||
(dict :label "Routing Analyzer" :href "/geography/isomorphism/routing-analyzer")
|
||||
(dict :label "Data Test" :href "/geography/isomorphism/data-test")
|
||||
(dict :label "Async IO" :href "/geography/isomorphism/async-io")
|
||||
(dict :label "Streaming" :href "/geography/isomorphism/streaming")
|
||||
(dict :label "Affinity" :href "/geography/isomorphism/affinity")
|
||||
(dict :label "Optimistic" :href "/geography/isomorphism/optimistic")
|
||||
(dict :label "Offline" :href "/geography/isomorphism/offline")))
|
||||
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(dict :label "Typed SX" :href "/plans/typed-sx"
|
||||
(dict :label "Typed SX" :href "/etc/plans/typed-sx"
|
||||
:summary "Gradual type system with static effect checking. Optional type annotations, deftype (aliases, unions, records), and effect declarations — checked at registration time, zero runtime cost. types.sx — specced, bootstrapped, catches composition and boundary 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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")))
|
||||
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(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.")
|
||||
(dict :label "Marshes" :href "/reactive/marshes"
|
||||
: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"
|
||||
(dict :label "Phase 2" :href "/geography/reactive/phase2"
|
||||
:summary "Input binding, keyed lists, reactive class/style, refs, portals, error boundaries, suspense, transitions.")))
|
||||
|
||||
(define bootstrappers-nav-items (list
|
||||
(dict :label "Overview" :href "/bootstrappers/")
|
||||
(dict :label "JavaScript" :href "/bootstrappers/javascript")
|
||||
(dict :label "Python" :href "/bootstrappers/python")
|
||||
(dict :label "Self-Hosting (py.sx)" :href "/bootstrappers/self-hosting")
|
||||
(dict :label "Self-Hosting JS (js.sx)" :href "/bootstrappers/self-hosting-js")
|
||||
(dict :label "Page Helpers" :href "/bootstrappers/page-helpers")))
|
||||
(dict :label "Overview" :href "/language/bootstrappers/")
|
||||
(dict :label "JavaScript" :href "/language/bootstrappers/javascript")
|
||||
(dict :label "Python" :href "/language/bootstrappers/python")
|
||||
(dict :label "Self-Hosting (py.sx)" :href "/language/bootstrappers/self-hosting")
|
||||
(dict :label "Self-Hosting JS (js.sx)" :href "/language/bootstrappers/self-hosting-js")
|
||||
(dict :label "Page Helpers" :href "/language/bootstrappers/page-helpers")))
|
||||
|
||||
;; Spec file registry — canonical metadata for spec viewer pages.
|
||||
;; Python only handles file I/O (read-spec-file); all metadata lives here.
|
||||
@@ -327,21 +325,31 @@
|
||||
(define sx-nav-tree
|
||||
{:label "sx" :href "/"
|
||||
:children (list
|
||||
{:label "Reactive" :href "/reactive/" :children reactive-islands-nav-items}
|
||||
{:label "Hypermedia" :href "/hypermedia/"
|
||||
{:label "Geography" :href "/geography/"
|
||||
:children (list
|
||||
{:label "Reference" :href "/hypermedia/reference/" :children reference-nav-items}
|
||||
{:label "Examples" :href "/hypermedia/examples/" :children examples-nav-items})}
|
||||
{:label "Docs" :href "/docs/" :children docs-nav-items}
|
||||
{:label "CSSX" :href "/cssx/" :children cssx-nav-items}
|
||||
{:label "Protocols" :href "/protocols/" :children protocols-nav-items}
|
||||
{:label "Essays" :href "/essays/" :children essays-nav-items}
|
||||
{:label "Philosophy" :href "/philosophy/" :children philosophy-nav-items}
|
||||
{:label "Specs" :href "/specs/" :children specs-nav-items}
|
||||
{:label "Bootstrappers" :href "/bootstrappers/" :children bootstrappers-nav-items}
|
||||
{:label "Testing" :href "/testing/" :children testing-nav-items}
|
||||
{:label "Isomorphism" :href "/isomorphism/" :children isomorphism-nav-items}
|
||||
{:label "Plans" :href "/plans/" :children plans-nav-items})})
|
||||
{:label "Reactive Islands" :href "/geography/reactive/" :children reactive-islands-nav-items}
|
||||
{:label "Hypermedia Lakes" :href "/geography/hypermedia/"
|
||||
:children (list
|
||||
{:label "Reference" :href "/geography/hypermedia/reference/" :children reference-nav-items}
|
||||
{:label "Examples" :href "/geography/hypermedia/examples/" :children examples-nav-items})}
|
||||
{:label "Marshes" :href "/geography/marshes/"
|
||||
:summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted."}
|
||||
{:label "Isomorphism" :href "/geography/isomorphism/" :children isomorphism-nav-items})}
|
||||
{:label "Language" :href "/language/"
|
||||
:children (list
|
||||
{:label "Docs" :href "/language/docs/" :children docs-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.
|
||||
;; Returns {:trail [{:node N :siblings S} ...] :children [...] :depth N}
|
||||
@@ -384,11 +392,15 @@
|
||||
item)))
|
||||
items)
|
||||
;; 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)
|
||||
(let ((children (get item "children")))
|
||||
(when children
|
||||
(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)
|
||||
item))))
|
||||
items))))
|
||||
|
||||
@@ -78,13 +78,13 @@
|
||||
|
||||
;; Post-process sf-result: count forms per category
|
||||
(for-each (fn (k)
|
||||
(let ((count (len (get sf-result k))))
|
||||
(set! sf-cats (assoc sf-cats k count))
|
||||
(set! sf-total (+ sf-total count))))
|
||||
(dict-set! sf-cats k (len (get sf-result k))))
|
||||
(keys sf-result))
|
||||
|
||||
(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
|
||||
"attr-result" attr-result "attr-ms" attr-ms
|
||||
"comp-result" comp-result "comp-ms" comp-ms
|
||||
|
||||
@@ -279,8 +279,8 @@
|
||||
|
||||
(~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:")
|
||||
(~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"))
|
||||
(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."))
|
||||
(~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 "/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"
|
||||
(p "Currently, " (code "SX-Components") " header lists loaded component names. Extend to support CIDs:")
|
||||
@@ -407,9 +407,9 @@
|
||||
(~doc-section :title "Relationships" :id "relationships"
|
||||
(p "This plan is the foundation for several other plans and roadmaps:")
|
||||
(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 "/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/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/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/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."))
|
||||
(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)."))))))
|
||||
|
||||
@@ -278,11 +278,11 @@
|
||||
(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" (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"
|
||||
(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" (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"
|
||||
(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"))
|
||||
@@ -301,4 +301,4 @@
|
||||
(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"
|
||||
(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).")))))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
(~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 "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"
|
||||
(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 "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")
|
||||
(a :href "/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 "/language/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
|
||||
(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-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"
|
||||
|
||||
(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.")
|
||||
|
||||
(div :class "space-y-4"
|
||||
@@ -85,7 +85,7 @@
|
||||
(li "15 dedicated tests: scan, transitive closure, circular deps, compute-all, components-needed")
|
||||
(li "Bootstrapped output verified on both host targets")
|
||||
(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
|
||||
@@ -96,14 +96,14 @@
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(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")
|
||||
(a :href "/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 "/language/specs/deps" :class "text-green-700 underline text-sm font-medium" "View canonical spec: deps.sx")
|
||||
(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-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"
|
||||
(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:")
|
||||
|
||||
(div :class "space-y-4"
|
||||
@@ -145,7 +145,7 @@
|
||||
(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 "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
|
||||
@@ -156,8 +156,8 @@
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(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")
|
||||
(a :href "/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 "/language/specs/router" :class "text-green-700 underline text-sm font-medium" "View canonical spec: router.sx")
|
||||
(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-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
|
||||
(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:")
|
||||
(~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") "."))
|
||||
|
||||
(div
|
||||
(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.")
|
||||
(~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."))
|
||||
|
||||
(div
|
||||
@@ -191,16 +191,16 @@
|
||||
(~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):")
|
||||
(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 "/examples/") ", " (code "/examples/<slug>") ", " (code "/essays/") ", " (code "/essays/<slug>"))
|
||||
(li (code "/plans/") ", " (code "/plans/<slug>") ", " (code "/isomorphism/") ", " (code "/bootstrappers/")))
|
||||
(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 "/etc/essays/") ", " (code "/etc/essays/<slug>"))
|
||||
(li (code "/etc/plans/") ", " (code "/etc/plans/<slug>") ", " (code "/geography/isomorphism/") ", " (code "/language/bootstrappers/")))
|
||||
(p "Pages that fall through to server:")
|
||||
(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 "/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")")
|
||||
(li (code "/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 "/language/bootstrappers/<slug>") " (has " (code ":data (bootstrapper-data slug)") ")")
|
||||
(li (code "/geography/isomorphism/bundle-analyzer") " (has " (code ":data (bundle-analyzer-data)") ")")
|
||||
(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"
|
||||
(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 "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")
|
||||
(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-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 hit: " (code "sx:route client+cache /path") " — instant render from cached data")
|
||||
(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"
|
||||
(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"
|
||||
(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 (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"))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -331,7 +331,7 @@
|
||||
(div :class "rounded border border-green-300 bg-green-50 p-4 mb-4"
|
||||
(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")
|
||||
(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-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")))
|
||||
|
||||
(~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"
|
||||
(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 "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")
|
||||
@@ -481,7 +481,7 @@
|
||||
(~doc-subsection :title "Verification"
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
(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"))))
|
||||
|
||||
(~doc-subsection :title "7c. Cache Invalidation & Optimistic Data Updates"
|
||||
@@ -518,7 +518,7 @@
|
||||
|
||||
(~doc-subsection :title "Verification"
|
||||
(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")))))
|
||||
|
||||
(~doc-subsection :title "7d. Offline Data Layer"
|
||||
@@ -553,7 +553,7 @@
|
||||
|
||||
(~doc-subsection :title "Verification"
|
||||
(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 "Console log: " (code "sx:offline queued") ", " (code "sx:offline syncing") ", " (code "sx:offline synced")))))
|
||||
|
||||
|
||||
@@ -479,7 +479,7 @@ python test_js_compile.py # renders both, diffs DOM" "bash")))
|
||||
(code "createElement") ", " (code "appendChild") ", " (code "textContent")
|
||||
". This gives SX a compilation target competitive with Svelte's "
|
||||
"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. "
|
||||
"The server returns a CID. The browser fetches and executes pre-compiled JavaScript. "
|
||||
"No parser, no evaluator, no network round-trip for component definitions."))
|
||||
|
||||
@@ -78,11 +78,11 @@
|
||||
(~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:")
|
||||
|
||||
(~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:")
|
||||
|
||||
(~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."))
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
(~doc-section :title "Path Resolution" :id "resolution"
|
||||
(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.")
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
(~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.")
|
||||
|
||||
(~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.")
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
|
||||
(~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.")
|
||||
(~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."))
|
||||
|
||||
(~doc-subsection :title "Idle Timer"
|
||||
@@ -123,7 +123,7 @@
|
||||
|
||||
(~doc-subsection :title "Declarative Configuration"
|
||||
(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.")))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -189,7 +189,7 @@
|
||||
|
||||
(~doc-section :title "Request Flow" :id "request-flow"
|
||||
(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
|
||||
@@ -248,7 +248,7 @@
|
||||
|
||||
(~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 "
|
||||
(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.")
|
||||
(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.")))))
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
(~doc-code :code (highlight "#'my-function → (quote my-function)" "lisp")))
|
||||
|
||||
(~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.")))
|
||||
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~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"
|
||||
(table :class "w-full text-left text-sm"
|
||||
@@ -82,7 +82,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~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.")
|
||||
|
||||
(~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"
|
||||
(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
|
||||
@@ -273,10 +273,10 @@
|
||||
|
||||
(~doc-section :title "Relationships" :id "relationships"
|
||||
(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 "/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 "/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/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/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 "/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 "/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"
|
||||
(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.")))))
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
(p :class "text-green-700 text-sm"
|
||||
(code "py.sx") " is implemented and verified. G0 == G1: 128/128 defines match, "
|
||||
"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."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
@@ -37,37 +37,37 @@
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(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")
|
||||
(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."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(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")
|
||||
(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."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(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")
|
||||
(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."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(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")
|
||||
(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."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(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")
|
||||
(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."))
|
||||
|
||||
(div :class "rounded border border-green-200 bg-green-50 p-4"
|
||||
(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")
|
||||
(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."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -81,7 +81,7 @@
|
||||
(div :class "rounded border border-amber-200 bg-amber-50 p-4"
|
||||
(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")
|
||||
(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."))))
|
||||
|
||||
;; -----------------------------------------------------------------------
|
||||
@@ -95,42 +95,42 @@
|
||||
(div :class "rounded border border-stone-200 bg-stone-50 p-4"
|
||||
(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")
|
||||
(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-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 "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")
|
||||
(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-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 "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")
|
||||
(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-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 "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")
|
||||
(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-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 "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")
|
||||
(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-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 "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")
|
||||
(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-500 mt-1" "All 6 sub-phases (7a–7f) complete."))))))
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
(~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 "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
|
||||
@@ -116,9 +116,10 @@
|
||||
(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 "Dict key presence (yet).") " " (code "(get user \"name\")") " — the checker knows " (code "get") " returns " (code "any") " but doesn't track which keys a dict has. Phase 6 (" (code "deftype") " records) will enable this.")
|
||||
(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 "Full algebraic effects.") " The effect system (Phase 7) checks static effect annotations — it does not provide algebraic effect handlers, effect polymorphism, or continuation-based effect dispatch. That door remains open for the future.")))
|
||||
|
||||
|
||||
(~doc-subsection :title "Inference"
|
||||
(p "Most types are inferred, not annotated. The checker knows:")
|
||||
(ul :class "list-disc pl-5 text-stone-700 space-y-1"
|
||||
@@ -230,7 +231,7 @@
|
||||
;; -----------------------------------------------------------------------
|
||||
|
||||
(~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"
|
||||
(table :class "w-full text-left text-sm"
|
||||
@@ -482,10 +483,10 @@
|
||||
|
||||
(~doc-section :title "Relationships" :id "relationships"
|
||||
(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 "/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 "/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/theorem-prover" :class "text-violet-700 underline" "Theorem Prover") " — prove.sx verifies primitive properties; types.sx verifies composition. Complementary.")
|
||||
(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 "/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 "/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"
|
||||
(p :class "text-amber-800 text-sm" (strong "Depends on: ") "primitives.sx (return + param types), boundary.sx (IO return types + IO classification), eval.sx (defcomp/deftype/defeffect parsing), deps.sx (IO detection — effects formalize this). " (strong "New: ") (code "types.sx") " spec module, type annotations in " (code "parse-comp-params") ", " (code "deftype") " declaration form, " (code "defeffect") " declaration form."))
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
(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 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"
|
||||
(p "The event bridge is spec'd in " (code "signals.sx") " (sections 12-13). Three functions:")
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
(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 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"
|
||||
(td :class "px-3 py-2 text-stone-700" "Error boundaries")
|
||||
(td :class "px-3 py-2 text-green-700 font-medium" "Done")
|
||||
|
||||
@@ -291,7 +291,7 @@
|
||||
|
||||
(~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"))
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
(div :id "marsh-flash-target"
|
||||
: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"
|
||||
:sx-get "/reactive/api/flash-sale"
|
||||
:sx-get "/geography/reactive/api/flash-sale"
|
||||
:sx-target "#marsh-flash-target"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch from server"))
|
||||
@@ -334,7 +334,7 @@
|
||||
|
||||
(~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."))
|
||||
|
||||
@@ -348,7 +348,7 @@
|
||||
|
||||
(~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."))
|
||||
|
||||
@@ -361,7 +361,7 @@
|
||||
|
||||
(~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."))
|
||||
|
||||
@@ -408,7 +408,7 @@
|
||||
(div :class "border-t border-stone-200 pt-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"
|
||||
:sx-get "/reactive/api/flash-sale"
|
||||
:sx-get "/geography/reactive/api/flash-sale"
|
||||
:sx-target "#marsh-server-msg"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch Price from Server")
|
||||
@@ -471,7 +471,7 @@
|
||||
(span :class "text-sm text-stone-600" " times")))
|
||||
(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"
|
||||
:sx-get "/reactive/api/settle-data"
|
||||
:sx-get "/geography/reactive/api/settle-data"
|
||||
:sx-target "#settle-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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")
|
||||
(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 ()
|
||||
(str "/reactive/api/search/" (deref mode)
|
||||
(str "/geography/reactive/api/search/" (deref mode)
|
||||
"?q=" (deref query))))
|
||||
:sx-target "#signal-results"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -524,7 +524,7 @@
|
||||
;; Current URL display
|
||||
(p :class "text-xs text-stone-400 font-mono"
|
||||
"URL: " (computed (fn ()
|
||||
(str "/reactive/api/search/" (deref mode) "?q=" (deref query)))))
|
||||
(str "/geography/reactive/api/search/" (deref mode) "?q=" (deref query)))))
|
||||
;; Results
|
||||
(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.")))))
|
||||
@@ -561,7 +561,7 @@
|
||||
;; Fetch button — response writes structured data to store via data-init
|
||||
(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"
|
||||
:sx-get "/reactive/api/catalog"
|
||||
:sx-get "/geography/reactive/api/catalog"
|
||||
:sx-target "#catalog-msg"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch Catalog")
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
(~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 "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"
|
||||
(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 "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."))))
|
||||
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
"Pages with data dependencies fall back to "
|
||||
(span :class "text-amber-700 font-medium" "server fetch")
|
||||
" 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 "
|
||||
(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.")
|
||||
|
||||
(div :class "mb-8 grid grid-cols-4 gap-4"
|
||||
|
||||
@@ -28,36 +28,36 @@
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/parser" :class "hover:underline"
|
||||
:sx-get "/specs/parser" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/parser" :class "hover:underline"
|
||||
:sx-get "/language/specs/parser" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"parser.sx"))
|
||||
(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"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/evaluator" :class "hover:underline"
|
||||
:sx-get "/specs/evaluator" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/evaluator" :class "hover:underline"
|
||||
:sx-get "/language/specs/evaluator" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"eval.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "Tree-walking evaluation of SX expressions"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/primitives" :class "hover:underline"
|
||||
:sx-get "/specs/primitives" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/primitives" :class "hover:underline"
|
||||
:sx-get "/language/specs/primitives" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"primitives.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "All built-in pure functions and their signatures"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/special-forms" :class "hover:underline"
|
||||
:sx-get "/specs/special-forms" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/special-forms" :class "hover:underline"
|
||||
:sx-get "/language/specs/special-forms" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"special-forms.sx"))
|
||||
(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"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/renderer" :class "hover:underline"
|
||||
:sx-get "/specs/renderer" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/renderer" :class "hover:underline"
|
||||
:sx-get "/language/specs/renderer" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"render.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "Shared rendering registries and utilities used by all adapters"))))))
|
||||
@@ -75,24 +75,24 @@
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/adapter-dom" :class "hover:underline"
|
||||
:sx-get "/specs/adapter-dom" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/adapter-dom" :class "hover:underline"
|
||||
:sx-get "/language/specs/adapter-dom" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"adapter-dom.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "Live DOM nodes")
|
||||
(td :class "px-3 py-2 text-stone-500" "Browser"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/adapter-html" :class "hover:underline"
|
||||
:sx-get "/specs/adapter-html" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/adapter-html" :class "hover:underline"
|
||||
:sx-get "/language/specs/adapter-html" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"adapter-html.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "HTML strings")
|
||||
(td :class "px-3 py-2 text-stone-500" "Server"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/adapter-sx" :class "hover:underline"
|
||||
:sx-get "/specs/adapter-sx" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/adapter-sx" :class "hover:underline"
|
||||
:sx-get "/language/specs/adapter-sx" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"adapter-sx.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "SX wire format")
|
||||
@@ -116,15 +116,15 @@
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/engine" :class "hover:underline"
|
||||
:sx-get "/specs/engine" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/engine" :class "hover:underline"
|
||||
:sx-get "/language/specs/engine" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"engine.sx"))
|
||||
(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"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/orchestration" :class "hover:underline"
|
||||
:sx-get "/specs/orchestration" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/orchestration" :class "hover:underline"
|
||||
:sx-get "/language/specs/orchestration" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"orchestration.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "Browser wiring — binds engine to DOM events, fetch, request lifecycle"))))))
|
||||
@@ -145,15 +145,15 @@
|
||||
(tbody
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/boot" :class "hover:underline"
|
||||
:sx-get "/specs/boot" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/boot" :class "hover:underline"
|
||||
:sx-get "/language/specs/boot" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"boot.sx"))
|
||||
(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"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/cssx" :class "hover:underline"
|
||||
:sx-get "/specs/cssx" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/cssx" :class "hover:underline"
|
||||
:sx-get "/language/specs/cssx" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"cssx.sx"))
|
||||
(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
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/continuations" :class "hover:underline"
|
||||
:sx-get "/specs/continuations" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/continuations" :class "hover:underline"
|
||||
:sx-get "/language/specs/continuations" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"continuations.sx"))
|
||||
(td :class "px-3 py-2 text-stone-700" "Delimited continuations — shift/reset")
|
||||
(td :class "px-3 py-2 text-stone-500" "All targets"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 font-mono text-sm text-violet-700"
|
||||
(a :href "/specs/callcc" :class "hover:underline"
|
||||
:sx-get "/specs/callcc" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
(a :href "/language/specs/callcc" :class "hover:underline"
|
||||
:sx-get "/language/specs/callcc" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
"callcc.sx"))
|
||||
(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"
|
||||
(td :class "px-3 py-2 text-stone-700" "JavaScript")
|
||||
(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"))
|
||||
(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"))
|
||||
(tr :class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700" "Python")
|
||||
(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"))
|
||||
(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"))
|
||||
(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 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"))
|
||||
(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"))
|
||||
|
||||
@@ -98,27 +98,27 @@ Per-spec platform functions:
|
||||
(div :class "space-y-3"
|
||||
(h2 :class "text-2xl font-semibold text-stone-800" "Test specs")
|
||||
(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")
|
||||
(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"))
|
||||
(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")
|
||||
(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"))
|
||||
(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")
|
||||
(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"))
|
||||
(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")
|
||||
(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"))
|
||||
(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")
|
||||
(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"))
|
||||
(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")
|
||||
(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"))))
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
(div :id "click-result" :class "p-4 rounded bg-stone-100 text-stone-500 text-center"
|
||||
"Click the button to load content.")
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/click"
|
||||
:sx-get "/geography/hypermedia/examples/api/click"
|
||||
:sx-target "#click-result"
|
||||
:sx-swap "innerHTML"
|
||||
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors"
|
||||
@@ -39,7 +39,7 @@
|
||||
(defcomp ~form-demo ()
|
||||
(div :class "space-y-4"
|
||||
(form
|
||||
:sx-post "/hypermedia/examples/api/form"
|
||||
:sx-post "/geography/hypermedia/examples/api/form"
|
||||
:sx-target "#form-result"
|
||||
:sx-swap "innerHTML"
|
||||
:class "space-y-3"
|
||||
@@ -63,7 +63,7 @@
|
||||
(defcomp ~polling-demo ()
|
||||
(div :class "space-y-4"
|
||||
(div :id "poll-target"
|
||||
:sx-get "/hypermedia/examples/api/poll"
|
||||
:sx-get "/geography/hypermedia/examples/api/poll"
|
||||
:sx-trigger "load, every 2s"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
(button
|
||||
:sx-delete (str "/hypermedia/examples/api/delete/" id)
|
||||
:sx-delete (str "/geography/hypermedia/examples/api/delete/" id)
|
||||
:sx-target (str "#row-" id)
|
||||
:sx-swap "outerHTML"
|
||||
:sx-confirm "Delete this item?"
|
||||
@@ -116,7 +116,7 @@
|
||||
(div :class "flex items-center justify-between p-3 rounded border border-stone-200"
|
||||
(span :class "text-stone-800" value)
|
||||
(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-swap "innerHTML"
|
||||
:class "text-sm text-violet-600 hover:text-violet-800"
|
||||
@@ -124,7 +124,7 @@
|
||||
|
||||
(defcomp ~inline-edit-form (&key value)
|
||||
(form
|
||||
:sx-post "/hypermedia/examples/api/edit"
|
||||
:sx-post "/geography/hypermedia/examples/api/edit"
|
||||
:sx-target "#edit-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
"save")
|
||||
(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-swap "innerHTML"
|
||||
: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-sm text-stone-400" "Waiting...")))
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/oob"
|
||||
:sx-get "/geography/hypermedia/examples/api/oob"
|
||||
:sx-target "#oob-box-a"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
(p :class "text-sm text-stone-500" "The content below loads automatically when the page renders.")
|
||||
(div :id "lazy-target"
|
||||
:sx-get "/hypermedia/examples/api/lazy"
|
||||
:sx-get "/geography/hypermedia/examples/api/lazy"
|
||||
:sx-trigger "load"
|
||||
:sx-swap "innerHTML"
|
||||
: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")))
|
||||
(list 1 2 3 4 5))
|
||||
(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-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
@@ -201,7 +201,7 @@
|
||||
items)
|
||||
(when (<= page 5)
|
||||
(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-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
@@ -217,7 +217,7 @@
|
||||
(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."))
|
||||
(button
|
||||
:sx-post "/hypermedia/examples/api/progress/start"
|
||||
:sx-post "/geography/hypermedia/examples/api/progress/start"
|
||||
:sx-target "#progress-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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 "%")))
|
||||
(p :class "text-sm text-stone-500 text-center" (str percent "% complete"))
|
||||
(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-target "#progress-target"
|
||||
:sx-swap "innerHTML"))
|
||||
@@ -242,7 +242,7 @@
|
||||
(defcomp ~active-search-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-target "#search-results"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -262,11 +262,11 @@
|
||||
;; --- 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
|
||||
(label :class "block text-sm font-medium text-stone-700 mb-1" "Email")
|
||||
(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-target "#email-feedback"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -290,7 +290,7 @@
|
||||
(div
|
||||
(label :class "block text-sm font-medium text-stone-700 mb-1" "Category")
|
||||
(select :name "category"
|
||||
:sx-get "/hypermedia/examples/api/values"
|
||||
:sx-get "/geography/hypermedia/examples/api/values"
|
||||
:sx-trigger "change"
|
||||
:sx-target "#value-items"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -314,7 +314,7 @@
|
||||
(defcomp ~reset-on-submit-demo ()
|
||||
(div :class "space-y-3"
|
||||
(form :id "reset-form"
|
||||
:sx-post "/hypermedia/examples/api/reset-submit"
|
||||
:sx-post "/geography/hypermedia/examples/api/reset-submit"
|
||||
:sx-target "#reset-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
(button
|
||||
:sx-get (str "/hypermedia/examples/api/editrow/" id)
|
||||
:sx-get (str "/geography/hypermedia/examples/api/editrow/" id)
|
||||
:sx-target (str "#erow-" id)
|
||||
:sx-swap "outerHTML"
|
||||
: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"))
|
||||
(td :class "px-3 py-2 space-x-1"
|
||||
(button
|
||||
:sx-post (str "/hypermedia/examples/api/editrow/" id)
|
||||
:sx-post (str "/geography/hypermedia/examples/api/editrow/" id)
|
||||
:sx-target (str "#erow-" id)
|
||||
:sx-swap "outerHTML"
|
||||
:sx-include (str "#erow-" id)
|
||||
:class "text-sm text-emerald-600 hover:text-emerald-800"
|
||||
"save")
|
||||
(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-swap "outerHTML"
|
||||
:class "text-sm text-stone-500 hover:text-stone-700"
|
||||
@@ -393,14 +393,14 @@
|
||||
(form :id "bulk-form"
|
||||
(div :class "flex gap-2 mb-3"
|
||||
(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-swap "innerHTML"
|
||||
:sx-include "#bulk-form"
|
||||
:class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
|
||||
"Activate")
|
||||
(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-swap "innerHTML"
|
||||
:sx-include "#bulk-form"
|
||||
@@ -437,19 +437,19 @@
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-2"
|
||||
(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-swap "beforeend"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Add to End")
|
||||
(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-swap "afterbegin"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Add to Start")
|
||||
(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-swap "none"
|
||||
: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 "flex gap-2"
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/dashboard"
|
||||
:sx-get "/geography/hypermedia/examples/api/dashboard"
|
||||
:sx-target "#filter-target"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-select "#dash-stats"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Stats Only")
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/dashboard"
|
||||
:sx-get "/geography/hypermedia/examples/api/dashboard"
|
||||
:sx-target "#filter-target"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-select "#dash-header"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Header Only")
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/dashboard"
|
||||
:sx-get "/geography/hypermedia/examples/api/dashboard"
|
||||
:sx-target "#filter-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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)
|
||||
(button
|
||||
:sx-get (str "/hypermedia/examples/api/tabs/" tab)
|
||||
:sx-get (str "/geography/hypermedia/examples/api/tabs/" tab)
|
||||
:sx-target "#tab-content"
|
||||
: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 "
|
||||
(if (= active "true")
|
||||
"border-violet-600 text-violet-600"
|
||||
@@ -520,7 +520,7 @@
|
||||
(defcomp ~animations-demo ()
|
||||
(div :class "space-y-4"
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/animate"
|
||||
:sx-get "/geography/hypermedia/examples/api/animate"
|
||||
:sx-target "#anim-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/dialog"
|
||||
:sx-get "/geography/hypermedia/examples/api/dialog"
|
||||
:sx-target "#dialog-container"
|
||||
:sx-swap "innerHTML"
|
||||
: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)
|
||||
(div :class "fixed inset-0 z-50 flex items-center justify-center"
|
||||
(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-swap "innerHTML")
|
||||
(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)
|
||||
(div :class "flex justify-end gap-2"
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/dialog/close"
|
||||
:sx-get "/geography/hypermedia/examples/api/dialog/close"
|
||||
:sx-target "#dialog-container"
|
||||
:sx-swap "innerHTML"
|
||||
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
|
||||
"Cancel")
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/dialog/close"
|
||||
:sx-get "/geography/hypermedia/examples/api/dialog/close"
|
||||
:sx-target "#dialog-container"
|
||||
:sx-swap "innerHTML"
|
||||
: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")
|
||||
(span :class "text-sm text-stone-500" "Help"))))
|
||||
(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-swap "innerHTML"
|
||||
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
|
||||
(p :class "text-stone-400 text-sm" "Press a shortcut key..."))
|
||||
(div :sx-get "/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-target "#kbd-target"
|
||||
: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-target "#kbd-target"
|
||||
:sx-swap "innerHTML")))
|
||||
@@ -619,7 +619,7 @@
|
||||
(p :class "text-sm text-stone-500" email)
|
||||
(p :class "text-sm text-stone-500" role))
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/putpatch/edit-all"
|
||||
:sx-get "/geography/hypermedia/examples/api/putpatch/edit-all"
|
||||
:sx-target "#pp-target"
|
||||
:sx-swap "innerHTML"
|
||||
:class "text-sm text-violet-600 hover:text-violet-800"
|
||||
@@ -627,7 +627,7 @@
|
||||
|
||||
(defcomp ~pp-form-full (&key name email role)
|
||||
(form
|
||||
:sx-put "/hypermedia/examples/api/putpatch"
|
||||
:sx-put "/geography/hypermedia/examples/api/putpatch"
|
||||
:sx-target "#pp-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
"Save All (PUT)")
|
||||
(button :type "button"
|
||||
:sx-get "/hypermedia/examples/api/putpatch/cancel"
|
||||
:sx-get "/geography/hypermedia/examples/api/putpatch/cancel"
|
||||
:sx-target "#pp-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-4"
|
||||
(form
|
||||
:sx-post "/hypermedia/examples/api/json-echo"
|
||||
:sx-post "/geography/hypermedia/examples/api/json-echo"
|
||||
:sx-target "#json-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-encoding "json"
|
||||
@@ -691,7 +691,7 @@
|
||||
(div :class "space-y-2"
|
||||
(h4 :class "text-sm font-semibold text-stone-700" "sx-vals — send extra values")
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/echo-vals"
|
||||
:sx-get "/geography/hypermedia/examples/api/echo-vals"
|
||||
:sx-target "#vals-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}"
|
||||
@@ -702,7 +702,7 @@
|
||||
(div :class "space-y-2"
|
||||
(h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers")
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/echo-headers"
|
||||
:sx-get "/geography/hypermedia/examples/api/echo-headers"
|
||||
:sx-target "#headers-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
|
||||
@@ -723,7 +723,7 @@
|
||||
(defcomp ~loading-states-demo ()
|
||||
(div :class "space-y-4"
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/slow"
|
||||
:sx-get "/geography/hypermedia/examples/api/slow"
|
||||
:sx-target "#loading-result"
|
||||
: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"
|
||||
@@ -742,7 +742,7 @@
|
||||
(defcomp ~sync-replace-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-target "#sync-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -762,7 +762,7 @@
|
||||
(defcomp ~retry-demo ()
|
||||
(div :class "space-y-4"
|
||||
(button
|
||||
:sx-get "/hypermedia/examples/api/flaky"
|
||||
:sx-get "/geography/hypermedia/examples/api/flaky"
|
||||
:sx-target "#retry-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-retry "exponential:1000:8000"
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
(range start (+ start 5)))
|
||||
(if (<= next 6)
|
||||
(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-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
|
||||
@@ -13,20 +13,30 @@
|
||||
: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
|
||||
:path "/docs/"
|
||||
:path "/language/docs/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/docs/" (~docs-introduction-content)))
|
||||
:content (~sx-doc :path "/language/docs/" (~docs-introduction-content)))
|
||||
|
||||
(defpage docs-page
|
||||
:path "/docs/<slug>"
|
||||
:path "/language/docs/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/docs/" slug)
|
||||
:content (~sx-doc :path (str "/language/docs/" slug)
|
||||
(case slug
|
||||
"introduction" (~docs-introduction-content)
|
||||
"getting-started" (~docs-getting-started-content)
|
||||
@@ -44,23 +54,23 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage hypermedia-index
|
||||
:path "/hypermedia/"
|
||||
:path "/geography/hypermedia/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/hypermedia/"))
|
||||
:content (~sx-doc :path "/geography/hypermedia/"))
|
||||
|
||||
(defpage reference-index
|
||||
:path "/hypermedia/reference/"
|
||||
:path "/geography/hypermedia/reference/"
|
||||
:auth :public
|
||||
: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
|
||||
:path "/hypermedia/reference/<slug>"
|
||||
:path "/geography/hypermedia/reference/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (reference-data slug)
|
||||
:content (~sx-doc :path (str "/hypermedia/reference/" slug)
|
||||
:content (~sx-doc :path (str "/geography/hypermedia/reference/" slug)
|
||||
(case slug
|
||||
"attributes" (~reference-attrs-content
|
||||
: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)))))
|
||||
|
||||
(defpage reference-attr-detail
|
||||
:path "/hypermedia/reference/attributes/<slug>"
|
||||
:path "/geography/hypermedia/reference/attributes/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (attr-detail-data slug)
|
||||
:content (~sx-doc :path "/hypermedia/reference/attributes"
|
||||
:content (~sx-doc :path "/geography/hypermedia/reference/attributes"
|
||||
(if attr-not-found
|
||||
(~reference-attr-not-found :slug slug)
|
||||
(~reference-attr-detail-content
|
||||
@@ -99,11 +109,11 @@
|
||||
:wire-placeholder-id attr-wire-id))))
|
||||
|
||||
(defpage reference-header-detail
|
||||
:path "/hypermedia/reference/headers/<slug>"
|
||||
:path "/geography/hypermedia/reference/headers/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (header-detail-data slug)
|
||||
:content (~sx-doc :path "/hypermedia/reference/headers"
|
||||
:content (~sx-doc :path "/geography/hypermedia/reference/headers"
|
||||
(if header-not-found
|
||||
(~reference-attr-not-found :slug slug)
|
||||
(~reference-header-detail-content
|
||||
@@ -114,11 +124,11 @@
|
||||
:demo header-demo))))
|
||||
|
||||
(defpage reference-event-detail
|
||||
:path "/hypermedia/reference/events/<slug>"
|
||||
:path "/geography/hypermedia/reference/events/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (event-detail-data slug)
|
||||
:content (~sx-doc :path "/hypermedia/reference/events"
|
||||
:content (~sx-doc :path "/geography/hypermedia/reference/events"
|
||||
(if event-not-found
|
||||
(~reference-attr-not-found :slug slug)
|
||||
(~reference-event-detail-content
|
||||
@@ -128,20 +138,30 @@
|
||||
: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
|
||||
:path "/protocols/"
|
||||
:path "/applications/protocols/"
|
||||
:auth :public
|
||||
: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
|
||||
:path "/protocols/<slug>"
|
||||
:path "/applications/protocols/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/protocols/" slug)
|
||||
:content (~sx-doc :path (str "/applications/protocols/" slug)
|
||||
(case slug
|
||||
"wire-format" (~protocol-wire-format-content)
|
||||
"fragments" (~protocol-fragments-content)
|
||||
@@ -156,16 +176,16 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage examples-index
|
||||
:path "/hypermedia/examples/"
|
||||
:path "/geography/hypermedia/examples/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/hypermedia/examples/"))
|
||||
:content (~sx-doc :path "/geography/hypermedia/examples/"))
|
||||
|
||||
(defpage examples-page
|
||||
:path "/hypermedia/examples/<slug>"
|
||||
:path "/geography/hypermedia/examples/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/hypermedia/examples/" slug)
|
||||
:content (~sx-doc :path (str "/geography/hypermedia/examples/" slug)
|
||||
(case slug
|
||||
"click-to-load" (~example-click-to-load)
|
||||
"form-submission" (~example-form-submission)
|
||||
@@ -197,20 +217,30 @@
|
||||
: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
|
||||
:path "/essays/"
|
||||
:path "/etc/essays/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/essays/" (~essays-index-content)))
|
||||
:content (~sx-doc :path "/etc/essays/" (~essays-index-content)))
|
||||
|
||||
(defpage essay-page
|
||||
:path "/essays/<slug>"
|
||||
:path "/etc/essays/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/essays/" slug)
|
||||
:content (~sx-doc :path (str "/etc/essays/" slug)
|
||||
(case slug
|
||||
"sx-sucks" (~essay-sx-sucks)
|
||||
"why-sexps" (~essay-why-sexps)
|
||||
@@ -235,16 +265,16 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage philosophy-index
|
||||
:path "/philosophy/"
|
||||
:path "/etc/philosophy/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/philosophy/" (~philosophy-index-content)))
|
||||
:content (~sx-doc :path "/etc/philosophy/" (~philosophy-index-content)))
|
||||
|
||||
(defpage philosophy-page
|
||||
:path "/philosophy/<slug>"
|
||||
:path "/etc/philosophy/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/philosophy/" slug)
|
||||
:content (~sx-doc :path (str "/etc/philosophy/" slug)
|
||||
(case slug
|
||||
"sx-manifesto" (~essay-sx-manifesto)
|
||||
"godel-escher-bach" (~essay-godel-escher-bach)
|
||||
@@ -258,16 +288,16 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage cssx-index
|
||||
:path "/cssx/"
|
||||
:path "/applications/cssx/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/cssx/" (~cssx-overview-content)))
|
||||
:content (~sx-doc :path "/applications/cssx/" (~cssx-overview-content)))
|
||||
|
||||
(defpage cssx-page
|
||||
:path "/cssx/<slug>"
|
||||
:path "/applications/cssx/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/cssx/" slug)
|
||||
:content (~sx-doc :path (str "/applications/cssx/" slug)
|
||||
(case slug
|
||||
"patterns" (~cssx-patterns-content)
|
||||
"delivery" (~cssx-delivery-content)
|
||||
@@ -282,23 +312,23 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage specs-index
|
||||
:path "/specs/"
|
||||
:path "/language/specs/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/specs/" (~spec-architecture-content)))
|
||||
:content (~sx-doc :path "/language/specs/" (~spec-architecture-content)))
|
||||
|
||||
(defpage specs-page
|
||||
:path "/specs/<slug>"
|
||||
:path "/language/specs/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/specs/" slug)
|
||||
:content (~sx-doc :path (str "/language/specs/" slug)
|
||||
(case slug
|
||||
"core" (~spec-overview-content
|
||||
:spec-title "Core Language"
|
||||
:spec-files (map (fn (item)
|
||||
(dict :title (get item "title") :desc (get item "desc")
|
||||
: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"))))
|
||||
core-spec-items))
|
||||
"adapters" (~spec-overview-content
|
||||
@@ -306,7 +336,7 @@
|
||||
:spec-files (map (fn (item)
|
||||
(dict :title (get item "title") :desc (get item "desc")
|
||||
: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"))))
|
||||
adapter-spec-items))
|
||||
"browser" (~spec-overview-content
|
||||
@@ -314,7 +344,7 @@
|
||||
:spec-files (map (fn (item)
|
||||
(dict :title (get item "title") :desc (get item "desc")
|
||||
: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"))))
|
||||
browser-spec-items))
|
||||
"extensions" (~spec-overview-content
|
||||
@@ -322,7 +352,7 @@
|
||||
:spec-files (map (fn (item)
|
||||
(dict :title (get item "title") :desc (get item "desc")
|
||||
: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"))))
|
||||
extension-spec-items))
|
||||
:else (let ((spec (find-spec slug)))
|
||||
@@ -340,17 +370,17 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage bootstrappers-index
|
||||
:path "/bootstrappers/"
|
||||
:path "/language/bootstrappers/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/bootstrappers/" (~bootstrappers-index-content)))
|
||||
:content (~sx-doc :path "/language/bootstrappers/" (~bootstrappers-index-content)))
|
||||
|
||||
(defpage bootstrapper-page
|
||||
:path "/bootstrappers/<slug>"
|
||||
:path "/language/bootstrappers/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (bootstrapper-data slug)
|
||||
:content (~sx-doc :path (str "/bootstrappers/" slug)
|
||||
:content (~sx-doc :path (str "/language/bootstrappers/" slug)
|
||||
(if bootstrapper-not-found
|
||||
(~spec-not-found :slug slug)
|
||||
(case slug
|
||||
@@ -385,53 +415,53 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage isomorphism-index
|
||||
:path "/isomorphism/"
|
||||
:path "/geography/isomorphism/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/isomorphism/" (~plan-isomorphic-content)))
|
||||
:content (~sx-doc :path "/geography/isomorphism/" (~plan-isomorphic-content)))
|
||||
|
||||
(defpage bundle-analyzer
|
||||
:path "/isomorphism/bundle-analyzer"
|
||||
:path "/geography/isomorphism/bundle-analyzer"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (bundle-analyzer-data)
|
||||
:content (~sx-doc :path "/isomorphism/bundle-analyzer"
|
||||
:content (~sx-doc :path "/geography/isomorphism/bundle-analyzer"
|
||||
(~bundle-analyzer-content
|
||||
:pages pages :total-components total-components :total-macros total-macros
|
||||
:pure-count pure-count :io-count io-count)))
|
||||
|
||||
(defpage routing-analyzer
|
||||
:path "/isomorphism/routing-analyzer"
|
||||
:path "/geography/isomorphism/routing-analyzer"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (routing-analyzer-data)
|
||||
:content (~sx-doc :path "/isomorphism/routing-analyzer"
|
||||
:content (~sx-doc :path "/geography/isomorphism/routing-analyzer"
|
||||
(~routing-analyzer-content
|
||||
:pages pages :total-pages total-pages :client-count client-count
|
||||
:server-count server-count :registry-sample registry-sample)))
|
||||
|
||||
(defpage data-test
|
||||
:path "/isomorphism/data-test"
|
||||
:path "/geography/isomorphism/data-test"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (data-test-data)
|
||||
:content (~sx-doc :path "/isomorphism/data-test"
|
||||
:content (~sx-doc :path "/geography/isomorphism/data-test"
|
||||
(~data-test-content
|
||||
:server-time server-time :items items
|
||||
:phase phase :transport transport)))
|
||||
|
||||
(defpage async-io-demo
|
||||
:path "/isomorphism/async-io"
|
||||
:path "/geography/isomorphism/async-io"
|
||||
:auth :public
|
||||
: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
|
||||
:path "/isomorphism/streaming"
|
||||
:path "/geography/isomorphism/streaming"
|
||||
:auth :public
|
||||
:stream true
|
||||
:layout :sx-docs
|
||||
:shell (~sx-doc :path "/isomorphism/streaming"
|
||||
:shell (~sx-doc :path "/geography/isomorphism/streaming"
|
||||
(~streaming-demo-layout
|
||||
(~suspense :id "stream-fast" :fallback (~stream-skeleton))
|
||||
(~suspense :id "stream-medium" :fallback (~stream-skeleton))
|
||||
@@ -444,35 +474,35 @@
|
||||
:stream-time stream-time))
|
||||
|
||||
(defpage affinity-demo
|
||||
:path "/isomorphism/affinity"
|
||||
:path "/geography/isomorphism/affinity"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
: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)))
|
||||
|
||||
(defpage optimistic-demo
|
||||
:path "/isomorphism/optimistic"
|
||||
:path "/geography/isomorphism/optimistic"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
: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)))
|
||||
|
||||
(defpage offline-demo
|
||||
:path "/isomorphism/offline"
|
||||
:path "/geography/isomorphism/offline"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
: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)))
|
||||
|
||||
;; Wildcard must come AFTER specific routes (first-match routing)
|
||||
(defpage isomorphism-page
|
||||
:path "/isomorphism/<slug>"
|
||||
:path "/geography/isomorphism/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/isomorphism/" slug)
|
||||
:content (~sx-doc :path (str "/geography/isomorphism/" slug)
|
||||
(case slug
|
||||
"bundle-analyzer" (~bundle-analyzer-content
|
||||
:pages pages :total-components total-components :total-macros total-macros
|
||||
@@ -487,19 +517,19 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage plans-index
|
||||
:path "/plans/"
|
||||
:path "/etc/plans/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path "/plans/" (~plans-index-content)))
|
||||
:content (~sx-doc :path "/etc/plans/" (~plans-index-content)))
|
||||
|
||||
(defpage plan-page
|
||||
:path "/plans/<slug>"
|
||||
:path "/etc/plans/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (case slug
|
||||
"theorem-prover" (prove-data)
|
||||
:else nil)
|
||||
:content (~sx-doc :path (str "/plans/" slug)
|
||||
:content (~sx-doc :path (str "/etc/plans/" slug)
|
||||
(case slug
|
||||
"status" (~plan-status-content)
|
||||
"reader-macros" (~plan-reader-macros-content)
|
||||
@@ -530,39 +560,58 @@
|
||||
: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
|
||||
:path "/reactive/"
|
||||
:path "/geography/reactive/"
|
||||
:auth :public
|
||||
: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
|
||||
:path "/reactive/<slug>"
|
||||
:path "/geography/reactive/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:content (~sx-doc :path (str "/reactive/" slug)
|
||||
:content (~sx-doc :path (str "/geography/reactive/" slug)
|
||||
(case slug
|
||||
"demo" (~reactive-islands-demo-content)
|
||||
"event-bridge" (~reactive-islands-event-bridge-content)
|
||||
"named-stores" (~reactive-islands-named-stores-content)
|
||||
"plan" (~reactive-islands-plan-content)
|
||||
"marshes" (~reactive-islands-marshes-content)
|
||||
"phase2" (~reactive-islands-phase2-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
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage page-helpers-demo
|
||||
:path "/bootstrappers/page-helpers"
|
||||
:path "/language/bootstrappers/page-helpers"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (page-helpers-demo-data)
|
||||
:content (~sx-doc :path "/bootstrappers/page-helpers"
|
||||
:content (~sx-doc :path "/language/bootstrappers/page-helpers"
|
||||
(~page-helpers-demo-content
|
||||
:sf-categories sf-categories :sf-total sf-total :sf-ms sf-ms
|
||||
:ref-sample ref-sample :ref-ms ref-ms
|
||||
@@ -580,11 +629,11 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage testing-index
|
||||
:path "/testing/"
|
||||
:path "/language/testing/"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (run-modular-tests "all")
|
||||
:content (~sx-doc :path "/testing/"
|
||||
:content (~sx-doc :path "/language/testing/"
|
||||
(~testing-overview-content
|
||||
:server-results server-results
|
||||
:framework-source framework-source
|
||||
@@ -596,7 +645,7 @@
|
||||
:engine-source engine-source)))
|
||||
|
||||
(defpage testing-page
|
||||
:path "/testing/<slug>"
|
||||
:path "/language/testing/<slug>"
|
||||
:auth :public
|
||||
:layout :sx-docs
|
||||
:data (case slug
|
||||
@@ -608,7 +657,7 @@
|
||||
"engine" (run-modular-tests "engine")
|
||||
"orchestration" (run-modular-tests "orchestration")
|
||||
:else (dict))
|
||||
:content (~sx-doc :path (str "/testing/" slug)
|
||||
:content (~sx-doc :path (str "/language/testing/" slug)
|
||||
(case slug
|
||||
"eval" (~testing-spec-content
|
||||
:spec-name "eval"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
(defcomp ~ref-get-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/time"
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-target "#ref-get-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/hypermedia/reference/api/greet"
|
||||
:sx-post "/geography/hypermedia/reference/api/greet"
|
||||
:sx-target "#ref-post-result"
|
||||
:sx-swap "innerHTML"
|
||||
:class "flex gap-2"
|
||||
@@ -48,7 +48,7 @@
|
||||
(div :class "flex items-center justify-between p-3 bg-stone-100 rounded"
|
||||
(span :class "text-stone-700 text-sm" "Status: " (strong "draft"))
|
||||
(button
|
||||
:sx-put "/hypermedia/reference/api/status"
|
||||
:sx-put "/geography/hypermedia/reference/api/status"
|
||||
:sx-target "#ref-put-view"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-vals "{\"status\": \"published\"}"
|
||||
@@ -63,17 +63,17 @@
|
||||
(div :class "space-y-2"
|
||||
(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")
|
||||
(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"
|
||||
: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"
|
||||
(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"
|
||||
: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"
|
||||
(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"
|
||||
:class "text-red-500 text-sm hover:text-red-700" "Remove"))))
|
||||
|
||||
@@ -86,11 +86,11 @@
|
||||
(div :class "p-3 bg-stone-100 rounded"
|
||||
(span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light")))
|
||||
(div :class "flex gap-2"
|
||||
(button :sx-patch "/hypermedia/reference/api/theme"
|
||||
(button :sx-patch "/geography/hypermedia/reference/api/theme"
|
||||
:sx-vals "{\"theme\": \"dark\"}"
|
||||
:sx-target "#ref-patch-val" :sx-swap "innerHTML"
|
||||
: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-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"))))
|
||||
@@ -102,7 +102,7 @@
|
||||
(defcomp ~ref-trigger-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-target "#ref-trigger-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -118,12 +118,12 @@
|
||||
(defcomp ~ref-target-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-swap "innerHTML"
|
||||
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Update Box A")
|
||||
(button :sx-get "/hypermedia/reference/api/time"
|
||||
(button :sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-target "#ref-target-b"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-3"
|
||||
(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"
|
||||
: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"
|
||||
: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"
|
||||
:class "px-3 py-1 bg-blue-600 text-white rounded text-sm" "innerHTML"))
|
||||
(div :id "ref-swap-list"
|
||||
@@ -160,7 +160,7 @@
|
||||
|
||||
(defcomp ~ref-oob-demo ()
|
||||
(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-swap "innerHTML"
|
||||
: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 ()
|
||||
(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-select "#the-content"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -198,7 +198,7 @@
|
||||
(div :id "ref-confirm-item"
|
||||
:class "flex items-center justify-between p-3 border border-stone-200 rounded"
|
||||
(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-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"
|
||||
@@ -211,14 +211,14 @@
|
||||
(defcomp ~ref-pushurl-demo ()
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-2"
|
||||
(a :href "/hypermedia/reference/attributes/sx-get"
|
||||
:sx-get "/hypermedia/reference/attributes/sx-get"
|
||||
(a :href "/geography/hypermedia/reference/attributes/sx-get"
|
||||
:sx-get "/geography/hypermedia/reference/attributes/sx-get"
|
||||
:sx-target "#main-panel" :sx-select "#main-panel"
|
||||
: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"
|
||||
"sx-get page")
|
||||
(a :href "/hypermedia/reference/attributes/sx-post"
|
||||
:sx-get "/hypermedia/reference/attributes/sx-post"
|
||||
(a :href "/geography/hypermedia/reference/attributes/sx-post"
|
||||
:sx-get "/geography/hypermedia/reference/attributes/sx-post"
|
||||
:sx-target "#main-panel" :sx-select "#main-panel"
|
||||
: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"
|
||||
@@ -233,7 +233,7 @@
|
||||
(defcomp ~ref-sync-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-sync "replace"
|
||||
:sx-target "#ref-sync-result"
|
||||
@@ -251,7 +251,7 @@
|
||||
|
||||
(defcomp ~ref-encoding-demo ()
|
||||
(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-target "#ref-encoding-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -271,7 +271,7 @@
|
||||
|
||||
(defcomp ~ref-headers-demo ()
|
||||
(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-target "#ref-headers-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -295,7 +295,7 @@
|
||||
(option :value "all" "All")
|
||||
(option :value "books" "Books")
|
||||
(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-target "#ref-include-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -311,7 +311,7 @@
|
||||
|
||||
(defcomp ~ref-vals-demo ()
|
||||
(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-target "#ref-vals-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -327,8 +327,8 @@
|
||||
|
||||
(defcomp ~ref-media-demo ()
|
||||
(div :class "space-y-3"
|
||||
(a :href "/hypermedia/reference/attributes/sx-get"
|
||||
:sx-get "/hypermedia/reference/attributes/sx-get"
|
||||
(a :href "/geography/hypermedia/reference/attributes/sx-get"
|
||||
:sx-get "/geography/hypermedia/reference/attributes/sx-get"
|
||||
:sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
:sx-media "(min-width: 768px)"
|
||||
@@ -346,13 +346,13 @@
|
||||
(div :class "grid grid-cols-2 gap-3"
|
||||
(div :class "p-3 border border-stone-200 rounded"
|
||||
(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"
|
||||
: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 :sx-disable "true" :class "p-3 border border-stone-200 rounded"
|
||||
(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"
|
||||
: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"
|
||||
@@ -378,7 +378,7 @@
|
||||
|
||||
(defcomp ~ref-retry-demo ()
|
||||
(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-swap "innerHTML"
|
||||
:sx-retry "true"
|
||||
@@ -414,13 +414,13 @@
|
||||
(defcomp ~ref-boost-demo ()
|
||||
(div :class "space-y-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"
|
||||
"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"
|
||||
"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"
|
||||
"sx-target"))
|
||||
(p :class "text-xs text-stone-400"
|
||||
@@ -434,7 +434,7 @@
|
||||
(defcomp ~ref-preload-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/time"
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-target "#ref-preload-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-preload "mouseover"
|
||||
@@ -452,7 +452,7 @@
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-2 items-center"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/time"
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-target "#ref-preserve-container"
|
||||
:sx-swap "innerHTML"
|
||||
: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 "flex gap-3 items-center"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/slow-echo"
|
||||
:sx-get "/geography/hypermedia/reference/api/slow-echo"
|
||||
:sx-target "#ref-indicator-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-indicator "#ref-spinner"
|
||||
@@ -495,7 +495,7 @@
|
||||
(defcomp ~ref-validate-demo ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/hypermedia/reference/api/greet"
|
||||
:sx-post "/geography/hypermedia/reference/api/greet"
|
||||
:sx-target "#ref-validate-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-validate "true"
|
||||
@@ -517,7 +517,7 @@
|
||||
(defcomp ~ref-ignore-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/time"
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-target "#ref-ignore-container"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
:class "flex items-center justify-between p-2 border border-stone-200 rounded"
|
||||
(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-optimistic "remove"
|
||||
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
|
||||
(div :id "ref-opt-item-2"
|
||||
:class "flex items-center justify-between p-2 border border-stone-200 rounded"
|
||||
(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-optimistic "remove"
|
||||
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
|
||||
@@ -560,7 +560,7 @@
|
||||
(defcomp ~ref-replace-url-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/time"
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-target "#ref-replurl-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-replace-url "true"
|
||||
@@ -578,7 +578,7 @@
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-3 items-center"
|
||||
(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-swap "innerHTML"
|
||||
:sx-disabled-elt "#ref-diselt-btn"
|
||||
@@ -597,7 +597,7 @@
|
||||
(defcomp ~ref-prompt-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/prompt-echo"
|
||||
:sx-get "/geography/hypermedia/reference/api/prompt-echo"
|
||||
:sx-target "#ref-prompt-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-prompt "Enter your name:"
|
||||
@@ -614,7 +614,7 @@
|
||||
(defcomp ~ref-params-demo ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/hypermedia/reference/api/echo-vals"
|
||||
:sx-post "/geography/hypermedia/reference/api/echo-vals"
|
||||
:sx-target "#ref-params-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-params "name"
|
||||
@@ -636,7 +636,7 @@
|
||||
|
||||
(defcomp ~ref-sse-demo ()
|
||||
(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-swap "innerHTML"
|
||||
(div :id "ref-sse-result"
|
||||
@@ -656,7 +656,7 @@
|
||||
(defcomp ~ref-header-prompt-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/prompt-echo"
|
||||
:sx-get "/geography/hypermedia/reference/api/prompt-echo"
|
||||
:sx-target "#ref-hdr-prompt-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-prompt "Enter your name:"
|
||||
@@ -673,7 +673,7 @@
|
||||
(defcomp ~ref-header-trigger-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/trigger-event"
|
||||
:sx-get "/geography/hypermedia/reference/api/trigger-event"
|
||||
:sx-target "#ref-hdr-trigger-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/retarget"
|
||||
:sx-get "/geography/hypermedia/reference/api/retarget"
|
||||
:sx-target "#ref-hdr-retarget-main"
|
||||
:sx-swap "innerHTML"
|
||||
: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..."
|
||||
: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
|
||||
:sx-get "/hypermedia/reference/api/time"
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-target "#ref-evt-br-result"
|
||||
: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!'; }"
|
||||
@@ -734,7 +734,7 @@
|
||||
(defcomp ~ref-event-after-request-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/time"
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-target "#ref-evt-ar-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/swap-item"
|
||||
:sx-get "/geography/hypermedia/reference/api/swap-item"
|
||||
:sx-target "#ref-evt-as-list"
|
||||
: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'"
|
||||
@@ -774,7 +774,7 @@
|
||||
(defcomp ~ref-event-response-error-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/hypermedia/reference/api/error-500"
|
||||
:sx-get "/geography/hypermedia/reference/api/error-500"
|
||||
:sx-target "#ref-evt-err-result"
|
||||
: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'"
|
||||
@@ -796,7 +796,7 @@
|
||||
(defcomp ~ref-event-validation-failed-demo ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/hypermedia/reference/api/greet"
|
||||
:sx-post "/geography/hypermedia/reference/api/greet"
|
||||
:sx-target "#ref-evt-vf-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-validate "true"
|
||||
@@ -847,13 +847,13 @@
|
||||
"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.")
|
||||
(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"
|
||||
"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"
|
||||
"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"
|
||||
"Protocols"))
|
||||
(p :class "text-xs text-stone-400"
|
||||
@@ -866,7 +866,7 @@
|
||||
|
||||
(defcomp ~ref-event-sse-open-demo ()
|
||||
(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-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'"
|
||||
@@ -884,7 +884,7 @@
|
||||
|
||||
(defcomp ~ref-event-sse-message-demo ()
|
||||
(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-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'"
|
||||
@@ -902,7 +902,7 @@
|
||||
|
||||
(defcomp ~ref-event-sse-error-demo ()
|
||||
(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-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'"
|
||||
|
||||
Reference in New Issue
Block a user