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

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

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

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

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

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-11T16:56:11Z";
var SX_VERSION = "2026-03-11T17:38:00Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function 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
@@ -5216,6 +5217,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

View File

@@ -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))
"""

View File

@@ -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)

View File

@@ -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)})))
;; --------------------------------------------------------------------------

View File

@@ -959,7 +959,7 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); };
PRIMITIVES["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

View File

@@ -1184,6 +1184,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)