htmx demos working: activation, fetch, swap, OOB filtering, test runner page
- htmx-boot-subtree! wired into process-elements for auto-activation
- Fixed cond compilation bug in hx-verb-info (Clojure-style flat cond)
- Platform io-fetch upgraded: method/body/headers support, full response dict
- Replaced perform IO ops with browser primitives (set-timeout, browser-confirm, etc)
- SX→HTML rendering in hx-do-swap with OOB section filtering
- hx-collect-params: collects input name/value for all methods
- Handler naming: ex-{slug} convention, removed perform IO dependencies
- Test runner page at (test.(applications.(htmx))) with iframe-based runner
- Header "test" link on every page linking to test URL
- Page file restructure: 285 files moved to URL-matching paths (a/b/c/index.sx)
- page-functions.sx: ~100 component name references updated
- _test added to skip_dirs, test- file prefix convention for test files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2846,13 +2846,9 @@ let http_inject_shell_statics env static_dir sx_sxc =
|
||||
(* Hash each .sxbc module individually and add to the hash index.
|
||||
Each module's content is stored by hash; exported symbols map to the module hash. *)
|
||||
let sxbc_dir = static_dir ^ "/wasm/sx" in
|
||||
let module_manifest_path = sxbc_dir ^ "/module-manifest.json" in
|
||||
let module_manifest_path = sxbc_dir ^ "/module-manifest.sx" in
|
||||
let module_hashes : (string, string) Hashtbl.t = Hashtbl.create 32 in (* module key → hash *)
|
||||
(if Sys.file_exists module_manifest_path then begin
|
||||
let manifest_src = In_channel.with_open_text module_manifest_path In_channel.input_all in
|
||||
(* Simple JSON parse — extract "key": { "file": "...", "exports": [...] } *)
|
||||
let exprs = try Sx_parser.parse_all ("(" ^ manifest_src ^ ")") with _ -> [] in
|
||||
ignore exprs; (* The manifest is JSON, not SX — parse it manually *)
|
||||
(* Read each .sxbc file, hash it, store in hash_to_def *)
|
||||
if Sys.file_exists sxbc_dir && Sys.is_directory sxbc_dir then begin
|
||||
let files = Array.to_list (Sys.readdir sxbc_dir) in
|
||||
@@ -3485,7 +3481,7 @@ let http_mode port =
|
||||
(* Files to skip — declarative metadata, not needed for rendering *)
|
||||
let skip_files = ["primitives.sx"; "types.sx"; "boundary.sx";
|
||||
"harness.sx"; "eval-rules.sx"; "vm-inline.sx"] in
|
||||
let skip_dirs = ["tests"; "test"; "plans"; "essays"; "spec"; "client-libs"] in
|
||||
let skip_dirs = ["tests"; "test"; "plans"; "essays"; "spec"; "client-libs"; "_test"] in
|
||||
let rec load_dir ?(base="") dir =
|
||||
if Sys.file_exists dir && Sys.is_directory dir then begin
|
||||
let entries = Sys.readdir dir in
|
||||
|
||||
@@ -98,8 +98,33 @@
|
||||
try { driveAsync(result.resume(null)); } catch(e) { console.error("[sx] driveAsync:", e.message); }
|
||||
}, typeof arg === "number" ? arg : 0);
|
||||
} else if (opName === "io-fetch") {
|
||||
fetch(typeof arg === "string" ? arg : "").then(function(r) { return r.text(); }).then(function(t) {
|
||||
try { driveAsync(result.resume({ok: true, text: t})); } catch(e) { console.error("[sx] driveAsync:", e.message); }
|
||||
var fetchUrl = typeof arg === "string" ? arg : "";
|
||||
var fetchMethod = (items && items[2]) || "GET";
|
||||
var fetchBody = items && items[3];
|
||||
var fetchHeaders = items && items[4];
|
||||
var fetchOpts = { method: typeof fetchMethod === "string" ? fetchMethod : "GET" };
|
||||
if (fetchBody && typeof fetchBody !== "boolean") {
|
||||
fetchOpts.body = typeof fetchBody === "string" ? fetchBody : JSON.stringify(fetchBody);
|
||||
}
|
||||
if (fetchHeaders && typeof fetchHeaders === "object") {
|
||||
var h = {};
|
||||
var keys = fetchHeaders._keys || Object.keys(fetchHeaders);
|
||||
for (var fi = 0; fi < keys.length; fi++) {
|
||||
var k = keys[fi], v = fetchHeaders[k];
|
||||
if (typeof k === "string" && typeof v === "string") h[k] = v;
|
||||
}
|
||||
fetchOpts.headers = h;
|
||||
}
|
||||
fetch(fetchUrl, fetchOpts).then(function(r) {
|
||||
var hdrs = {};
|
||||
try { r.headers.forEach(function(v, k) { hdrs[k] = v; }); } catch(e) {}
|
||||
return r.text().then(function(t) {
|
||||
return { status: r.status, body: t, headers: hdrs, ok: r.ok };
|
||||
});
|
||||
}).then(function(resp) {
|
||||
try { driveAsync(result.resume(resp)); } catch(e) { console.error("[sx] driveAsync:", e.message); }
|
||||
}).catch(function(e) {
|
||||
try { driveAsync(result.resume({status: 0, body: "", headers: {}, ok: false})); } catch(e2) { console.error("[sx] driveAsync:", e2.message); }
|
||||
});
|
||||
} else if (opName === "io-navigate") {
|
||||
// navigation — don't resume
|
||||
@@ -273,7 +298,7 @@
|
||||
}
|
||||
}
|
||||
// Content-addressed boot: script loaded from /sx/h/{hash}, not /static/wasm/.
|
||||
// Fall back to /static/wasm/ base URL for module-manifest.json and .sx sources.
|
||||
// Fall back to /static/wasm/ base URL for module-manifest.sx and .sx sources.
|
||||
if (!_baseUrl || _baseUrl.indexOf("/sx/h/") !== -1) {
|
||||
_baseUrl = "/static/wasm/";
|
||||
}
|
||||
@@ -522,6 +547,22 @@
|
||||
var _manifest = null;
|
||||
var _loadedLibs = {};
|
||||
|
||||
/**
|
||||
* Convert K.parse output (tagged {_type, ...} objects) to plain JS.
|
||||
* SX nil (from empty lists `()`) becomes [].
|
||||
*/
|
||||
function sxDataToJs(v) {
|
||||
if (v === null || v === undefined) return [];
|
||||
if (typeof v !== "object") return v;
|
||||
if (v._type === "list") return (v.items || []).map(sxDataToJs);
|
||||
if (v._type === "dict") {
|
||||
var out = {};
|
||||
for (var k in v) if (k !== "_type") out[k] = sxDataToJs(v[k]);
|
||||
return out;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and parse the module manifest (library deps + file paths).
|
||||
*/
|
||||
@@ -529,11 +570,14 @@
|
||||
if (_manifest) return _manifest;
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", _baseUrl + "sx/module-manifest.json" + _cacheBust, false);
|
||||
xhr.open("GET", _baseUrl + "sx/module-manifest.sx" + _cacheBust, false);
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
_manifest = JSON.parse(xhr.responseText);
|
||||
return _manifest;
|
||||
var parsed = K.parse(xhr.responseText);
|
||||
if (parsed && parsed.length > 0) {
|
||||
_manifest = sxDataToJs(parsed[0]);
|
||||
return _manifest;
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
console.warn("[sx-platform] No manifest found, falling back to full load");
|
||||
@@ -699,6 +743,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Merge definitions from a new page's manifest (called during navigation)
|
||||
function mergeManifest(el) {
|
||||
if (!el) return;
|
||||
try {
|
||||
var incoming = JSON.parse(el.textContent);
|
||||
var newDefs = incoming.defs || {};
|
||||
// Ensure base manifest is loaded
|
||||
if (!_pageManifest) loadPageManifest();
|
||||
if (!_pageManifest) _pageManifest = { defs: {} };
|
||||
if (!_pageManifest.defs) _pageManifest.defs = {};
|
||||
for (var name in newDefs) {
|
||||
_pageManifest.defs[name] = newDefs[name];
|
||||
_hashToName[newDefs[name]] = name;
|
||||
}
|
||||
// Merge hash store entries
|
||||
if (incoming.store) {
|
||||
if (!_pageManifest.store) _pageManifest.store = {};
|
||||
for (var h in incoming.store) {
|
||||
_pageManifest.store[h] = incoming.store[h];
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.warn("[sx] Failed to merge manifest:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveHash(hash) {
|
||||
// 1. In-memory cache
|
||||
if (_hashCache[hash]) return _hashCache[hash];
|
||||
@@ -833,6 +903,7 @@
|
||||
renderToHtml: function(expr) { return K.renderToHtml(expr); },
|
||||
callFn: function(fn, args) { return K.callFn(fn, args); },
|
||||
engine: function() { return K.engine(); },
|
||||
mergeManifest: function(el) { return mergeManifest(el); },
|
||||
// Boot entry point (called by auto-init or manually)
|
||||
init: function() {
|
||||
if (typeof K.eval === "function") {
|
||||
|
||||
1211
lib/hyperscript/htmx.sx
Normal file
1211
lib/hyperscript/htmx.sx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,257 +1,223 @@
|
||||
// sx-test-runner.js — Run SX test specs in the browser using sx-browser.js.
|
||||
// Supports both legacy (monolithic test.sx) and modular (per-spec) modes.
|
||||
/**
|
||||
* SX Test Runner — drives tests in an iframe, reports results.
|
||||
* Updates [data-test] elements in-place with pass/fail icons.
|
||||
* Expects: #run-btn, #test-status, #test-summary, #test-iframe, [data-test]
|
||||
*/
|
||||
(function() {
|
||||
var NIL = Sx.NIL;
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function deepEqual(a, b) {
|
||||
if (a === b) return true;
|
||||
if (isNil(a) && isNil(b)) return true;
|
||||
if (typeof a !== typeof b) return false;
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
if (a.length !== b.length) return false;
|
||||
for (var i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
||||
return true;
|
||||
"use strict";
|
||||
|
||||
var TESTS = [
|
||||
{ name: "click-to-load", actions: [
|
||||
{ type: "click", selector: "button[hx-get]" },
|
||||
{ type: "wait", ms: 2000 },
|
||||
{ type: "assert-text", selector: "#click-result", contains: "Content loaded!" }
|
||||
]},
|
||||
{ name: "click-no-oob-leak", actions: [
|
||||
{ type: "click", selector: "button[hx-get]" },
|
||||
{ type: "wait", ms: 2000 },
|
||||
{ type: "assert-text", selector: "#click-result", notContains: "defcomp" }
|
||||
]},
|
||||
{ name: "search-debounce", actions: [
|
||||
{ type: "fill", selector: "input[hx-get]", value: "hx-get" },
|
||||
{ type: "wait", ms: 1500 },
|
||||
{ type: "assert-text", selector: "#search-results", contains: "GET request" }
|
||||
]},
|
||||
{ name: "search-no-results", actions: [
|
||||
{ type: "fill", selector: "input[hx-get]", value: "xyznonexistent" },
|
||||
{ type: "wait", ms: 1500 },
|
||||
{ type: "assert-text", selector: "#search-results", contains: "No results" }
|
||||
]},
|
||||
{ name: "tab-overview", actions: [
|
||||
{ type: "click", selector: "button[hx-get*='tab=overview']" },
|
||||
{ type: "wait", ms: 2000 },
|
||||
{ type: "assert-text", selector: "#htmx-tab-content", contains: "htmx gives you access" }
|
||||
]},
|
||||
{ name: "tab-features", actions: [
|
||||
{ type: "click", selector: "button[hx-get*='tab=features']" },
|
||||
{ type: "wait", ms: 2000 },
|
||||
{ type: "assert-text", selector: "#htmx-tab-content", contains: "Any element" }
|
||||
]},
|
||||
{ name: "append-item", actions: [
|
||||
{ type: "click", selector: "button[hx-post*='api.append']" },
|
||||
{ type: "wait", ms: 2000 },
|
||||
{ type: "assert-count", selector: "#item-list > *", gte: 1 }
|
||||
]},
|
||||
{ name: "form-submit", actions: [
|
||||
{ type: "fill", selector: "form[hx-post] input[name='name']", value: "Alice" },
|
||||
{ type: "fill", selector: "form[hx-post] input[name='email']", value: "alice@test.com" },
|
||||
{ type: "click", selector: "form[hx-post] button[type='submit']" },
|
||||
{ type: "wait", ms: 2000 },
|
||||
{ type: "assert-text", selector: "#form-result", contains: "Alice" }
|
||||
]}
|
||||
];
|
||||
|
||||
if (window.__sxTests) TESTS = window.__sxTests;
|
||||
|
||||
var runBtn, statusEl, summaryEl, iframe;
|
||||
|
||||
function init() {
|
||||
runBtn = document.getElementById("run-btn");
|
||||
statusEl = document.getElementById("test-status");
|
||||
summaryEl = document.getElementById("test-summary");
|
||||
iframe = document.getElementById("test-iframe");
|
||||
if (runBtn) {
|
||||
console.log("[sx-test] Runner initialized, " + TESTS.length + " tests");
|
||||
runBtn.addEventListener("click", function() {
|
||||
console.log("[sx-test] Run clicked");
|
||||
runAll();
|
||||
});
|
||||
} else {
|
||||
console.warn("[sx-test] #run-btn not found");
|
||||
}
|
||||
if (a && typeof a === "object" && b && typeof b === "object") {
|
||||
var ka = Object.keys(a), kb = Object.keys(b);
|
||||
if (ka.length !== kb.length) return false;
|
||||
for (var j = 0; j < ka.length; j++) if (!deepEqual(a[ka[j]], b[ka[j]])) return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Platform functions shared across all specs ---
|
||||
function makeEnv() {
|
||||
var stack = [], passed = 0, failed = 0, num = 0, lines = [];
|
||||
var env = {
|
||||
"try-call": function(thunk) {
|
||||
try {
|
||||
Sx.eval([thunk], env);
|
||||
return { ok: true };
|
||||
} catch(e) {
|
||||
return { ok: false, error: e.message || String(e) };
|
||||
function sleep(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }
|
||||
|
||||
function waitForReady(doc, timeout) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var start = Date.now();
|
||||
(function check() {
|
||||
try { if (doc.documentElement.getAttribute("data-sx-ready") === "true") return resolve(); } catch(e) {}
|
||||
if (Date.now() - start > timeout) return reject(new Error("Timeout"));
|
||||
setTimeout(check, 200);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
function reloadIframe() {
|
||||
return new Promise(function(resolve) {
|
||||
iframe.addEventListener("load", function onLoad() {
|
||||
iframe.removeEventListener("load", onLoad);
|
||||
resolve();
|
||||
});
|
||||
iframe.contentWindow.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function updateTestItem(name, state, error) {
|
||||
var item = document.querySelector('[data-test="' + name + '"]');
|
||||
if (!item) return;
|
||||
var icon = item.querySelector('[data-role="test-icon"]');
|
||||
if (!icon) return;
|
||||
|
||||
if (state === "running") {
|
||||
icon.textContent = "⟳";
|
||||
icon.style.color = "#7c3aed";
|
||||
item.style.borderColor = "#c4b5fd";
|
||||
} else if (state === "pass") {
|
||||
icon.textContent = "✓";
|
||||
icon.style.color = "#16a34a";
|
||||
item.style.borderColor = "#bbf7d0";
|
||||
item.style.backgroundColor = "#f0fdf4";
|
||||
} else if (state === "fail") {
|
||||
icon.textContent = "✗";
|
||||
icon.style.color = "#dc2626";
|
||||
item.style.borderColor = "#fecaca";
|
||||
item.style.backgroundColor = "#fef2f2";
|
||||
if (error) {
|
||||
var errEl = item.querySelector('[data-role="test-error"]');
|
||||
if (!errEl) {
|
||||
errEl = document.createElement("div");
|
||||
errEl.setAttribute("data-role", "test-error");
|
||||
errEl.style.cssText = "padding:8px 16px;font-size:12px;color:#dc2626;border-top:1px solid #fecaca;background:#fef2f2";
|
||||
item.querySelector("summary").after(errEl);
|
||||
}
|
||||
},
|
||||
"report-pass": function(name) {
|
||||
num++; passed++;
|
||||
lines.push("ok " + num + " - " + stack.concat([name]).join(" > "));
|
||||
},
|
||||
"report-fail": function(name, error) {
|
||||
num++; failed++;
|
||||
lines.push("not ok " + num + " - " + stack.concat([name]).join(" > "));
|
||||
lines.push(" # " + error);
|
||||
},
|
||||
"push-suite": function(name) { stack.push(name); },
|
||||
"pop-suite": function() { stack.pop(); },
|
||||
|
||||
// Primitives that sx-browser.js may not expose in env
|
||||
"equal?": function(a, b) { return deepEqual(a, b); },
|
||||
"eq?": function(a, b) { return a === b; },
|
||||
"boolean?": function(x) { return typeof x === "boolean"; },
|
||||
"string-length": function(s) { return String(s).length; },
|
||||
"substring": function(s, start, end) { return String(s).slice(start, end); },
|
||||
"string-contains?": function(s, n) { return String(s).indexOf(n) !== -1; },
|
||||
"upcase": function(s) { return String(s).toUpperCase(); },
|
||||
"downcase": function(s) { return String(s).toLowerCase(); },
|
||||
"reverse": function(c) { return c ? c.slice().reverse() : []; },
|
||||
"flatten": function(c) {
|
||||
var r = [];
|
||||
for (var i = 0; i < (c||[]).length; i++) {
|
||||
if (Array.isArray(c[i])) for (var j = 0; j < c[i].length; j++) r.push(c[i][j]);
|
||||
else r.push(c[i]);
|
||||
}
|
||||
return r;
|
||||
},
|
||||
"has-key?": function(d, k) { return d && typeof d === "object" && k in d; },
|
||||
"append": function(c, x) { return Array.isArray(x) ? (c||[]).concat(x) : (c||[]).concat([x]); },
|
||||
"for-each-indexed": function(f, coll) {
|
||||
for (var i = 0; i < (coll||[]).length; i++) Sx.eval([f, i, coll[i]], env);
|
||||
},
|
||||
"for-each": function(f, coll) {
|
||||
for (var i = 0; i < (coll||[]).length; i++) Sx.eval([f, coll[i]], env);
|
||||
},
|
||||
"dict-set!": function(d, k, v) { if (d) d[k] = v; },
|
||||
"dict-has?": function(d, k) { return d && typeof d === "object" && k in d; },
|
||||
"dict-get": function(d, k) { return d ? d[k] : undefined; },
|
||||
"starts-with?": function(s, prefix) { return String(s).indexOf(prefix) === 0; },
|
||||
"ends-with?": function(s, suffix) { var str = String(s); return str.indexOf(suffix) === str.length - suffix.length; },
|
||||
"slice": function(s, start, end) { return end !== undefined ? s.slice(start, end) : s.slice(start); },
|
||||
"inc": function(n) { return n + 1; },
|
||||
"append!": function(arr, item) { if (Array.isArray(arr)) arr.push(item); },
|
||||
"dict": function() { return {}; },
|
||||
|
||||
// --- Parser platform functions ---
|
||||
"sx-parse": function(source) { return Sx.parseAll(source); },
|
||||
"sx-serialize": function(val) {
|
||||
if (val === NIL || val === null || val === undefined) return "nil";
|
||||
if (typeof val === "boolean") return val ? "true" : "false";
|
||||
if (typeof val === "number") return String(val);
|
||||
if (typeof val === "string") return '"' + val.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
||||
if (val && (val._sym || val._sx_symbol)) return val.name;
|
||||
if (val && (val._kw || val._sx_keyword)) return ":" + val.name;
|
||||
if (Array.isArray(val)) return "(" + val.map(function(x) { return env["sx-serialize"](x); }).join(" ") + ")";
|
||||
if (val && typeof val === "object") {
|
||||
var parts = [];
|
||||
Object.keys(val).forEach(function(k) { parts.push(":" + k); parts.push(env["sx-serialize"](val[k])); });
|
||||
return "{" + parts.join(" ") + "}";
|
||||
}
|
||||
return String(val);
|
||||
},
|
||||
"make-symbol": function(name) { return Sx.sym ? Sx.sym(name) : { _sx_symbol: true, name: name, toString: function() { return name; } }; },
|
||||
"make-keyword": function(name) { return Sx.kw ? Sx.kw(name) : { _sx_keyword: true, name: name, toString: function() { return name; } }; },
|
||||
"symbol-name": function(s) { return s && s.name ? s.name : String(s); },
|
||||
"keyword-name": function(k) { return k && k.name ? k.name : String(k); },
|
||||
|
||||
// --- Render platform function ---
|
||||
"render-html": function(sxSource) {
|
||||
if (!Sx.renderToHtml) throw new Error("render-to-html not available");
|
||||
var exprs = Sx.parseAll(sxSource);
|
||||
var result = "";
|
||||
for (var i = 0; i < exprs.length; i++) result += Sx.renderToHtml(exprs[i], env);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
return { env: env, getResults: function() { return { passed: passed, failed: failed, num: num, lines: lines }; } };
|
||||
}
|
||||
|
||||
function evalSource(src, env) {
|
||||
var exprs = Sx.parseAll(src);
|
||||
for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env);
|
||||
}
|
||||
|
||||
function loadRouterFromBootstrap(env) {
|
||||
if (Sx.splitPathSegments) {
|
||||
env["split-path-segments"] = Sx.splitPathSegments;
|
||||
env["parse-route-pattern"] = Sx.parseRoutePattern;
|
||||
env["match-route-segments"] = Sx.matchRouteSegments;
|
||||
env["match-route"] = Sx.matchRoute;
|
||||
env["find-matching-route"] = Sx.findMatchingRoute;
|
||||
env["make-route-segment"] = Sx.makeRouteSegment;
|
||||
errEl.textContent = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadDepsFromBootstrap(env) {
|
||||
if (Sx.scanRefs) {
|
||||
env["scan-refs"] = Sx.scanRefs;
|
||||
env["scan-components-from-source"] = Sx.scanComponentsFromSource;
|
||||
env["transitive-deps"] = Sx.transitiveDeps;
|
||||
env["compute-all-deps"] = Sx.computeAllDeps;
|
||||
env["components-needed"] = Sx.componentsNeeded;
|
||||
env["page-component-bundle"] = Sx.pageComponentBundle;
|
||||
env["page-css-classes"] = Sx.pageCssClasses;
|
||||
env["scan-io-refs"] = Sx.scanIoRefs;
|
||||
env["transitive-io-refs"] = Sx.transitiveIoRefs;
|
||||
env["compute-all-io-refs"] = Sx.computeAllIoRefs;
|
||||
env["component-pure?"] = Sx.componentPure_p;
|
||||
env["test-env"] = function() { return env; };
|
||||
function resetAllItems() {
|
||||
var items = document.querySelectorAll("[data-test]");
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var icon = items[i].querySelector('[data-role="test-icon"]');
|
||||
if (icon) { icon.textContent = "○"; icon.style.color = ""; }
|
||||
items[i].style.borderColor = "";
|
||||
items[i].style.backgroundColor = "";
|
||||
var err = items[i].querySelector('[data-role="test-error"]');
|
||||
if (err) err.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function loadEngineFromBootstrap(env) {
|
||||
if (Sx.parseTime) {
|
||||
env["parse-time"] = Sx.parseTime;
|
||||
env["parse-trigger-spec"] = Sx.parseTriggerSpec;
|
||||
env["default-trigger"] = Sx.defaultTrigger;
|
||||
env["parse-swap-spec"] = Sx.parseSwapSpec;
|
||||
env["parse-retry-spec"] = Sx.parseRetrySpec;
|
||||
env["next-retry-ms"] = function(cur, cap) { return Math.min(cur * 2, cap); };
|
||||
env["filter-params"] = Sx.filterParams;
|
||||
async function runOneTest(test) {
|
||||
var doc = iframe.contentDocument;
|
||||
for (var j = 0; j < test.actions.length; j++) {
|
||||
var a = test.actions[j];
|
||||
if (a.type === "click") {
|
||||
var el = doc.querySelector(a.selector);
|
||||
if (!el) throw new Error("Not found: " + a.selector);
|
||||
el.click();
|
||||
} else if (a.type === "fill") {
|
||||
var el = doc.querySelector(a.selector);
|
||||
if (!el) throw new Error("Not found: " + a.selector);
|
||||
el.focus();
|
||||
el.value = "";
|
||||
el.value = a.value;
|
||||
el.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
el.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
} else if (a.type === "wait") {
|
||||
await sleep(a.ms);
|
||||
} else if (a.type === "assert-text") {
|
||||
var el = doc.querySelector(a.selector);
|
||||
if (!el) throw new Error("Not found: " + a.selector);
|
||||
var txt = el.textContent || "";
|
||||
if (a.contains && txt.indexOf(a.contains) === -1)
|
||||
throw new Error("Expected '" + a.contains + "' in '" + txt.substring(0, 60) + "'");
|
||||
if (a.notContains && txt.indexOf(a.notContains) !== -1)
|
||||
throw new Error("Unexpected '" + a.notContains + "'");
|
||||
} else if (a.type === "assert-count") {
|
||||
var els = doc.querySelectorAll(a.selector);
|
||||
if (a.gte !== undefined && els.length < a.gte)
|
||||
throw new Error("Expected >=" + a.gte + ", got " + els.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Legacy runner (monolithic test.sx) ---
|
||||
window.sxRunTests = function(srcId, outId, btnId) {
|
||||
var src = document.getElementById(srcId).textContent;
|
||||
var out = document.getElementById(outId);
|
||||
var btn = document.getElementById(btnId);
|
||||
var ctx = makeEnv();
|
||||
async function runAll() {
|
||||
resetAllItems();
|
||||
summaryEl.innerHTML = "";
|
||||
runBtn.disabled = true;
|
||||
runBtn.style.opacity = "0.5";
|
||||
var passed = 0, failed = 0;
|
||||
|
||||
try {
|
||||
var t0 = performance.now();
|
||||
evalSource(src, ctx.env);
|
||||
var elapsed = Math.round(performance.now() - t0);
|
||||
var r = ctx.getResults();
|
||||
r.lines.push("");
|
||||
r.lines.push("1.." + r.num);
|
||||
r.lines.push("# tests " + (r.passed + r.failed));
|
||||
r.lines.push("# pass " + r.passed);
|
||||
if (r.failed > 0) r.lines.push("# fail " + r.failed);
|
||||
r.lines.push("# time " + elapsed + "ms");
|
||||
} catch(e) {
|
||||
var r = ctx.getResults();
|
||||
r.lines.push("");
|
||||
r.lines.push("FATAL: " + (e.message || String(e)));
|
||||
}
|
||||
for (var i = 0; i < TESTS.length; i++) {
|
||||
var test = TESTS[i];
|
||||
statusEl.textContent = test.name + " (" + (i+1) + "/" + TESTS.length + ")";
|
||||
updateTestItem(test.name, "running");
|
||||
|
||||
out.textContent = r.lines.join("\n");
|
||||
out.style.display = "block";
|
||||
btn.textContent = r.passed + "/" + (r.passed + r.failed) + " passed" + (r.failed === 0 ? "" : " (" + r.failed + " failed)");
|
||||
btn.className = r.failed > 0
|
||||
? "px-4 py-2 rounded-md bg-red-600 text-white font-medium text-sm cursor-default"
|
||||
: "px-4 py-2 rounded-md bg-green-600 text-white font-medium text-sm cursor-default";
|
||||
};
|
||||
if (i > 0) await reloadIframe();
|
||||
|
||||
// --- Modular runner (per-spec or all) ---
|
||||
var SPECS = {
|
||||
"eval": { needs: [] },
|
||||
"parser": { needs: ["sx-parse"] },
|
||||
"router": { needs: [] },
|
||||
"render": { needs: ["render-html"] },
|
||||
"deps": { needs: [] },
|
||||
"engine": { needs: [] },
|
||||
};
|
||||
|
||||
window.sxRunModularTests = function(specName, outId, btnId) {
|
||||
var out = document.getElementById(outId);
|
||||
var btn = document.getElementById(btnId);
|
||||
var ctx = makeEnv();
|
||||
var specs = specName === "all" ? Object.keys(SPECS) : [specName];
|
||||
|
||||
try {
|
||||
var t0 = performance.now();
|
||||
|
||||
// Load framework
|
||||
var fwEl = document.getElementById("test-framework-source");
|
||||
if (fwEl) {
|
||||
evalSource(fwEl.textContent, ctx.env);
|
||||
try {
|
||||
await waitForReady(iframe.contentDocument, 15000);
|
||||
await sleep(500);
|
||||
} catch(e) {
|
||||
updateTestItem(test.name, "fail", "Page load timeout");
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var si = 0; si < specs.length; si++) {
|
||||
var sn = specs[si];
|
||||
if (!SPECS[sn]) continue;
|
||||
|
||||
// Load module functions from bootstrap
|
||||
if (sn === "router") loadRouterFromBootstrap(ctx.env);
|
||||
if (sn === "deps") loadDepsFromBootstrap(ctx.env);
|
||||
if (sn === "engine") loadEngineFromBootstrap(ctx.env);
|
||||
|
||||
// Find spec source — either per-spec textarea or embedded in overview
|
||||
var specEl = document.getElementById("test-spec-" + sn);
|
||||
if (specEl) {
|
||||
evalSource(specEl.textContent, ctx.env);
|
||||
}
|
||||
try {
|
||||
await runOneTest(test);
|
||||
updateTestItem(test.name, "pass");
|
||||
passed++;
|
||||
} catch(err) {
|
||||
updateTestItem(test.name, "fail", err.message);
|
||||
failed++;
|
||||
}
|
||||
|
||||
var elapsed = Math.round(performance.now() - t0);
|
||||
var r = ctx.getResults();
|
||||
r.lines.push("");
|
||||
r.lines.push("1.." + r.num);
|
||||
r.lines.push("# tests " + (r.passed + r.failed));
|
||||
r.lines.push("# pass " + r.passed);
|
||||
if (r.failed > 0) r.lines.push("# fail " + r.failed);
|
||||
r.lines.push("# time " + elapsed + "ms");
|
||||
} catch(e) {
|
||||
var r = ctx.getResults();
|
||||
r.lines.push("");
|
||||
r.lines.push("FATAL: " + (e.message || String(e)));
|
||||
}
|
||||
|
||||
out.textContent = r.lines.join("\n");
|
||||
out.style.display = "block";
|
||||
btn.textContent = r.passed + "/" + (r.passed + r.failed) + " passed" + (r.failed === 0 ? "" : " (" + r.failed + " failed)");
|
||||
btn.className = r.failed > 0
|
||||
? "px-4 py-2 rounded-md bg-red-600 text-white font-medium text-sm cursor-default"
|
||||
: "px-4 py-2 rounded-md bg-green-600 text-white font-medium text-sm cursor-default";
|
||||
};
|
||||
runBtn.disabled = false;
|
||||
runBtn.style.opacity = "1";
|
||||
statusEl.textContent = "Done";
|
||||
var total = passed + failed;
|
||||
summaryEl.innerHTML = '<div style="padding:12px;border-radius:8px;font-weight:600;' +
|
||||
(failed === 0 ? 'background:#f0fdf4;color:#166534' : 'background:#fef2f2;color:#991b1b') +
|
||||
'">' + passed + '/' + total + ' tests passed</div>';
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -98,8 +98,33 @@
|
||||
try { driveAsync(result.resume(null)); } catch(e) { console.error("[sx] driveAsync:", e.message); }
|
||||
}, typeof arg === "number" ? arg : 0);
|
||||
} else if (opName === "io-fetch") {
|
||||
fetch(typeof arg === "string" ? arg : "").then(function(r) { return r.text(); }).then(function(t) {
|
||||
try { driveAsync(result.resume({ok: true, text: t})); } catch(e) { console.error("[sx] driveAsync:", e.message); }
|
||||
var fetchUrl = typeof arg === "string" ? arg : "";
|
||||
var fetchMethod = (items && items[2]) || "GET";
|
||||
var fetchBody = items && items[3];
|
||||
var fetchHeaders = items && items[4];
|
||||
var fetchOpts = { method: typeof fetchMethod === "string" ? fetchMethod : "GET" };
|
||||
if (fetchBody && typeof fetchBody !== "boolean") {
|
||||
fetchOpts.body = typeof fetchBody === "string" ? fetchBody : JSON.stringify(fetchBody);
|
||||
}
|
||||
if (fetchHeaders && typeof fetchHeaders === "object") {
|
||||
var h = {};
|
||||
var keys = fetchHeaders._keys || Object.keys(fetchHeaders);
|
||||
for (var fi = 0; fi < keys.length; fi++) {
|
||||
var k = keys[fi], v = fetchHeaders[k];
|
||||
if (typeof k === "string" && typeof v === "string") h[k] = v;
|
||||
}
|
||||
fetchOpts.headers = h;
|
||||
}
|
||||
fetch(fetchUrl, fetchOpts).then(function(r) {
|
||||
var hdrs = {};
|
||||
try { r.headers.forEach(function(v, k) { hdrs[k] = v; }); } catch(e) {}
|
||||
return r.text().then(function(t) {
|
||||
return { status: r.status, body: t, headers: hdrs, ok: r.ok };
|
||||
});
|
||||
}).then(function(resp) {
|
||||
try { driveAsync(result.resume(resp)); } catch(e) { console.error("[sx] driveAsync:", e.message); }
|
||||
}).catch(function(e) {
|
||||
try { driveAsync(result.resume({status: 0, body: "", headers: {}, ok: false})); } catch(e2) { console.error("[sx] driveAsync:", e2.message); }
|
||||
});
|
||||
} else if (opName === "io-navigate") {
|
||||
// navigation — don't resume
|
||||
@@ -273,7 +298,7 @@
|
||||
}
|
||||
}
|
||||
// Content-addressed boot: script loaded from /sx/h/{hash}, not /static/wasm/.
|
||||
// Fall back to /static/wasm/ base URL for module-manifest.json and .sx sources.
|
||||
// Fall back to /static/wasm/ base URL for module-manifest.sx and .sx sources.
|
||||
if (!_baseUrl || _baseUrl.indexOf("/sx/h/") !== -1) {
|
||||
_baseUrl = "/static/wasm/";
|
||||
}
|
||||
@@ -522,6 +547,22 @@
|
||||
var _manifest = null;
|
||||
var _loadedLibs = {};
|
||||
|
||||
/**
|
||||
* Convert K.parse output (tagged {_type, ...} objects) to plain JS.
|
||||
* SX nil (from empty lists `()`) becomes [].
|
||||
*/
|
||||
function sxDataToJs(v) {
|
||||
if (v === null || v === undefined) return [];
|
||||
if (typeof v !== "object") return v;
|
||||
if (v._type === "list") return (v.items || []).map(sxDataToJs);
|
||||
if (v._type === "dict") {
|
||||
var out = {};
|
||||
for (var k in v) if (k !== "_type") out[k] = sxDataToJs(v[k]);
|
||||
return out;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and parse the module manifest (library deps + file paths).
|
||||
*/
|
||||
@@ -529,11 +570,14 @@
|
||||
if (_manifest) return _manifest;
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", _baseUrl + "sx/module-manifest.json" + _cacheBust, false);
|
||||
xhr.open("GET", _baseUrl + "sx/module-manifest.sx" + _cacheBust, false);
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
_manifest = JSON.parse(xhr.responseText);
|
||||
return _manifest;
|
||||
var parsed = K.parse(xhr.responseText);
|
||||
if (parsed && parsed.length > 0) {
|
||||
_manifest = sxDataToJs(parsed[0]);
|
||||
return _manifest;
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
console.warn("[sx-platform] No manifest found, falling back to full load");
|
||||
@@ -699,6 +743,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Merge definitions from a new page's manifest (called during navigation)
|
||||
function mergeManifest(el) {
|
||||
if (!el) return;
|
||||
try {
|
||||
var incoming = JSON.parse(el.textContent);
|
||||
var newDefs = incoming.defs || {};
|
||||
// Ensure base manifest is loaded
|
||||
if (!_pageManifest) loadPageManifest();
|
||||
if (!_pageManifest) _pageManifest = { defs: {} };
|
||||
if (!_pageManifest.defs) _pageManifest.defs = {};
|
||||
for (var name in newDefs) {
|
||||
_pageManifest.defs[name] = newDefs[name];
|
||||
_hashToName[newDefs[name]] = name;
|
||||
}
|
||||
// Merge hash store entries
|
||||
if (incoming.store) {
|
||||
if (!_pageManifest.store) _pageManifest.store = {};
|
||||
for (var h in incoming.store) {
|
||||
_pageManifest.store[h] = incoming.store[h];
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.warn("[sx] Failed to merge manifest:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveHash(hash) {
|
||||
// 1. In-memory cache
|
||||
if (_hashCache[hash]) return _hashCache[hash];
|
||||
@@ -833,6 +903,7 @@
|
||||
renderToHtml: function(expr) { return K.renderToHtml(expr); },
|
||||
callFn: function(fn, args) { return K.callFn(fn, args); },
|
||||
engine: function() { return K.engine(); },
|
||||
mergeManifest: function(el) { return mergeManifest(el); },
|
||||
// Boot entry point (called by auto-init or manually)
|
||||
init: function() {
|
||||
if (typeof K.eval === "function") {
|
||||
|
||||
File diff suppressed because one or more lines are too long
1211
shared/static/wasm/sx/hs-htmx.sx
Normal file
1211
shared/static/wasm/sx/hs-htmx.sx
Normal file
File diff suppressed because it is too large
Load Diff
3
shared/static/wasm/sx/hs-htmx.sxbc
Normal file
3
shared/static/wasm/sx/hs-htmx.sxbc
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -415,6 +415,14 @@
|
||||
(swap-dom-nodes t oob s)
|
||||
(post-swap t)))
|
||||
(hoist-head-elements container)
|
||||
(let
|
||||
((manifest-el (dom-query-in container "script[data-sx-manifest]")))
|
||||
(when
|
||||
manifest-el
|
||||
(host-call
|
||||
(host-global "Sx")
|
||||
"mergeManifest"
|
||||
manifest-el)))
|
||||
(let
|
||||
((html (select-from-container container select-sel)))
|
||||
(with-transition
|
||||
@@ -1547,7 +1555,8 @@
|
||||
(process-sse root)
|
||||
(bind-inline-handlers root)
|
||||
(process-emit-elements root)
|
||||
(hs-boot-subtree! (or root (host-global "document")))))
|
||||
(hs-boot-subtree! (or root (host-global "document")))
|
||||
(htmx-boot-subtree! (or root (host-global "document")))))
|
||||
(define
|
||||
process-one
|
||||
:effects (mutation io)
|
||||
@@ -1622,9 +1631,11 @@
|
||||
(pathname (url-pathname url)))
|
||||
(when
|
||||
target
|
||||
(let
|
||||
((headers (dict "SX-Request" "true")))
|
||||
(fetch-and-restore target url headers scrollY))))))
|
||||
(when
|
||||
(not (try-client-route pathname target-sel))
|
||||
(let
|
||||
((headers (build-request-headers target (loaded-component-names))))
|
||||
(fetch-and-restore target url headers scrollY)))))))
|
||||
(define
|
||||
engine-init
|
||||
:effects (mutation io)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1792,7 +1792,7 @@
|
||||
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
|
||||
}
|
||||
(globalThis))
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["re-9a0de245",[2]],["sx-80a20737",[2,3]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,5]],["dune__exe__Sx_browser-653fa705",[2,4,6]],["std_exit-10fb8830",[2]],["start-f808dbe1",0]],"generated":(b=>{var
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["re-9a0de245",[2]],["sx-faa4f9b6",[2,3]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,5]],["dune__exe__Sx_browser-efe9c27c",[2,4,6]],["std_exit-10fb8830",[2]],["start-f808dbe1",0]],"generated":(b=>{var
|
||||
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
|
||||
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
|
||||
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new
|
||||
@@ -1818,4 +1818,4 @@ a()},"Js_of_ocaml__Json.fragments":{"get_JSON":a=>a.JSON,"get_constructor":a=>a.
|
||||
a(b)},"Js_of_ocaml__Dom_svg.fragments":{"get_SVGElement":a=>a.SVGElement,"get_document":a=>a.document,"get_tagName":a=>a.tagName,"meth_call_0_toLowerCase":a=>a.toLowerCase(),"meth_call_1_getElementById":(a,b)=>a.getElementById(b),"meth_call_2_createElementNS":(a,b,c)=>a.createElementNS(b,c)},"Js_of_ocaml__EventSource.fragments":{"get_EventSource":a=>a.EventSource,"obj_9":()=>({}),"set_withCredentials":(a,b)=>a.withCredentials=b},"Js_of_ocaml__Geolocation.fragments":{"get_geolocation":a=>a.geolocation,"get_navigator":a=>a.navigator,"obj_10":()=>({})},"Js_of_ocaml__IntersectionObserver.fragments":{"get_IntersectionObserver":a=>a.IntersectionObserver,"obj_11":()=>({})},"Js_of_ocaml__Intl.fragments":{"get_Collator":a=>a.Collator,"get_DateTimeFormat":a=>a.DateTimeFormat,"get_Intl":a=>a.Intl,"get_NumberFormat":a=>a.NumberFormat,"get_PluralRules":a=>a.PluralRules,"obj_12":a=>({localeMatcher:a}),"obj_13":(a,b,c,d,e,f)=>({localeMatcher:a,usage:b,sensitivity:c,ignorePunctuation:d,numeric:e,caseFirst:f}),"obj_14":(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t)=>({dateStyle:a,timeStyle:b,calendar:c,dayPeriod:d,numberingSystem:e,localeMatcher:f,timeZone:g,hour12:h,hourCycle:i,formatMatcher:j,weekday:k,era:l,year:m,month:n,day:o,hour:p,minute:q,second:r,fractionalSecondDigits:s,timeZoneName:t}),"obj_15":(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u)=>({compactDisplay:a,currency:b,currencyDisplay:c,currencySign:d,localeMatcher:e,notation:f,numberingSystem:g,signDisplay:h,style:i,unit:j,unitDisplay:k,useGrouping:l,roundingMode:m,roundingPriority:n,roundingIncrement:o,trailingZeroDisplay:p,minimumIntegerDigits:q,minimumFractionDigits:r,maximumFractionDigits:s,minimumSignificantDigits:t,maximumSignificantDigits:u}),"obj_16":(a,b)=>({localeMatcher:a,type:b})},"Dune__exe__Sx_browser.fragments":{"fun_call_1":(a,b)=>a(b),"fun_call_3":(a,b,c,d)=>a(b,c,d),"get_Array":a=>a.Array,"get_Object":a=>a.Object,"get___sx_handle":a=>a.__sx_handle,"get__driveAsync":a=>a._driveAsync,"get__type":a=>a._type,"get_console":a=>a.console,"get_items":a=>a.items,"get_length":a=>a.length,"get_name":a=>a.name,"js_expr_10d25c5c":()=>function(a){return function(){b.__sxR=undefined;var
|
||||
c=a.apply(null,arguments);return b.__sxR!==undefined?b.__sxR:c}},"js_expr_1ab4fffb":()=>function(){var
|
||||
b={},d=0;return{put:function(a){var
|
||||
c=d++;b[c]=a;return c},get:function(a){return b[a]}}}(),"js_expr_36506fc1":()=>function(a,b,c){a.__sx_handle=b;a._type=c;return a},"meth_call_1_error":(a,b)=>a.error(b),"meth_call_1_get":(a,b)=>a.get(b),"meth_call_1_isArray":(a,b)=>a.isArray(b),"meth_call_1_keys":(a,b)=>a.keys(b),"meth_call_1_put":(a,b)=>a.put(b),"obj_0":()=>({}),"obj_1":()=>({}),"obj_2":(a,b)=>({_type:a,items:b}),"obj_3":(a,b)=>({_type:a,name:b}),"obj_4":(a,b)=>({_type:a,name:b}),"obj_5":(a,b)=>({_type:a,__sx_handle:b}),"obj_6":()=>({}),"obj_7":()=>({}),"obj_8":()=>({}),"set_SxKernel":(a,b)=>a.SxKernel=b,"set___sxR":(a,b)=>a.__sxR=b,"set__type":(a,b)=>a._type=b,"set_beginModuleLoad":(a,b)=>a.beginModuleLoad=b,"set_callFn":(a,b)=>a.callFn=b,"set_compileModule":(a,b)=>a.compileModule=b,"set_debugEnv":(a,b)=>a.debugEnv=b,"set_endModuleLoad":(a,b)=>a.endModuleLoad=b,"set_engine":(a,b)=>a.engine=b,"set_eval":(a,b)=>a.eval=b,"set_evalExpr":(a,b)=>a.evalExpr=b,"set_evalVM":(a,b)=>a.evalVM=b,"set_fnArity":(a,b)=>a.fnArity=b,"set_inspect":(a,b)=>a.inspect=b,"set_isCallable":(a,b)=>a.isCallable=b,"set_load":(a,b)=>a.load=b,"set_loadModule":(a,b)=>a.loadModule=b,"set_loadSource":(a,b)=>a.loadSource=b,"set_op":(a,b)=>a.op=b,"set_parse":(a,b)=>a.parse=b,"set_registerNative":(a,b)=>a.registerNative=b,"set_renderToHtml":(a,b)=>a.renderToHtml=b,"set_request":(a,b)=>a.request=b,"set_resume":(a,b)=>a.resume=b,"set_scopeTraceDrain":(a,b)=>a.scopeTraceDrain=b,"set_scopeTraceOff":(a,b)=>a.scopeTraceOff=b,"set_scopeTraceOn":(a,b)=>a.scopeTraceOn=b,"set_stringify":(a,b)=>a.stringify=b,"set_suspended":(a,b)=>a.suspended=b,"set_typeOf":(a,b)=>a.typeOf=b}}})(globalThis),"src":"sx_browser.bc.wasm.assets"});
|
||||
c=d++;b[c]=a;return c},get:function(a){return b[a]}}}(),"js_expr_36506fc1":()=>function(a,b,c){a.__sx_handle=b;a._type=c;return a},"meth_call_1_error":(a,b)=>a.error(b),"meth_call_1_get":(a,b)=>a.get(b),"meth_call_1_isArray":(a,b)=>a.isArray(b),"meth_call_1_keys":(a,b)=>a.keys(b),"meth_call_1_put":(a,b)=>a.put(b),"obj_0":()=>({}),"obj_1":()=>({}),"obj_10":(a,b)=>({__sx_error:a,message:b}),"obj_2":(a,b)=>({_type:a,items:b}),"obj_3":(a,b)=>({_type:a,name:b}),"obj_4":(a,b)=>({_type:a,name:b}),"obj_5":(a,b)=>({_type:a,__sx_handle:b}),"obj_6":()=>({}),"obj_7":()=>({}),"obj_8":()=>({}),"obj_9":(a,b)=>({__sx_error:a,message:b}),"set_SxKernel":(a,b)=>a.SxKernel=b,"set___sxR":(a,b)=>a.__sxR=b,"set__type":(a,b)=>a._type=b,"set_beginModuleLoad":(a,b)=>a.beginModuleLoad=b,"set_callFn":(a,b)=>a.callFn=b,"set_compileModule":(a,b)=>a.compileModule=b,"set_debugEnv":(a,b)=>a.debugEnv=b,"set_endModuleLoad":(a,b)=>a.endModuleLoad=b,"set_engine":(a,b)=>a.engine=b,"set_eval":(a,b)=>a.eval=b,"set_evalExpr":(a,b)=>a.evalExpr=b,"set_evalVM":(a,b)=>a.evalVM=b,"set_fnArity":(a,b)=>a.fnArity=b,"set_inspect":(a,b)=>a.inspect=b,"set_isCallable":(a,b)=>a.isCallable=b,"set_load":(a,b)=>a.load=b,"set_loadModule":(a,b)=>a.loadModule=b,"set_loadSource":(a,b)=>a.loadSource=b,"set_op":(a,b)=>a.op=b,"set_parse":(a,b)=>a.parse=b,"set_registerNative":(a,b)=>a.registerNative=b,"set_renderToHtml":(a,b)=>a.renderToHtml=b,"set_request":(a,b)=>a.request=b,"set_resetStepCount":(a,b)=>a.resetStepCount=b,"set_resume":(a,b)=>a.resume=b,"set_scopeTraceDrain":(a,b)=>a.scopeTraceDrain=b,"set_scopeTraceOff":(a,b)=>a.scopeTraceOff=b,"set_scopeTraceOn":(a,b)=>a.scopeTraceOn=b,"set_setStepLimit":(a,b)=>a.setStepLimit=b,"set_stringify":(a,b)=>a.stringify=b,"set_suspended":(a,b)=>a.suspended=b,"set_typeOf":(a,b)=>a.typeOf=b}}})(globalThis),"src":"sx_browser.bc.wasm.assets"});
|
||||
|
||||
176
sx/sx/applications/htmx/runner.sx
Normal file
176
sx/sx/applications/htmx/runner.sx
Normal file
@@ -0,0 +1,176 @@
|
||||
;; Test runner page for the htmx demo
|
||||
;; URL: /sx/(test.(applications.(htmx)))
|
||||
|
||||
(defcomp
|
||||
()
|
||||
(~docs/page
|
||||
:title "Test: htmx demos"
|
||||
(p
|
||||
(~tw :tokens "text-stone-500 mb-4")
|
||||
"Running tests against "
|
||||
(a
|
||||
:href "/sx/(applications.(htmx))"
|
||||
(~tw :tokens "text-violet-600 underline")
|
||||
"/sx/(applications.(htmx))"))
|
||||
(div
|
||||
(~tw :tokens "flex items-center gap-4 mb-4")
|
||||
(button
|
||||
:id "run-btn"
|
||||
(~tw
|
||||
:tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors")
|
||||
"Run All Tests")
|
||||
(span :id "test-status" (~tw :tokens "text-sm text-stone-500") "Ready"))
|
||||
(div :id "test-summary" (~tw :tokens "mb-4"))
|
||||
(div
|
||||
:id "test-list"
|
||||
(~tw :tokens "space-y-2 mb-6")
|
||||
(details
|
||||
:class "sx-test-item"
|
||||
:data-test "click-to-load"
|
||||
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
|
||||
(summary
|
||||
(~tw
|
||||
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
|
||||
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
|
||||
(span (~tw :tokens "font-medium") "click-to-load")
|
||||
(span
|
||||
(~tw :tokens "text-sm text-stone-400 ml-auto")
|
||||
"Click button, verify content loads"))
|
||||
(div
|
||||
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
|
||||
(pre
|
||||
(~tw
|
||||
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
|
||||
"(deftest click-to-load\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-get]\")\n (wait 2000)\n (assert-text \"#click-result\" :contains \"Content loaded!\"))")))
|
||||
(details
|
||||
:class "sx-test-item"
|
||||
:data-test "click-no-oob-leak"
|
||||
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
|
||||
(summary
|
||||
(~tw
|
||||
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
|
||||
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
|
||||
(span (~tw :tokens "font-medium") "click-no-oob-leak")
|
||||
(span
|
||||
(~tw :tokens "text-sm text-stone-400 ml-auto")
|
||||
"OOB swap sections filtered from content"))
|
||||
(div
|
||||
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
|
||||
(pre
|
||||
(~tw
|
||||
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
|
||||
"(deftest click-no-oob-leak\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-get]\")\n (wait 2000)\n (assert-text \"#click-result\" :not-contains \"defcomp\"))")))
|
||||
(details
|
||||
:class "sx-test-item"
|
||||
:data-test "search-debounce"
|
||||
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
|
||||
(summary
|
||||
(~tw
|
||||
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
|
||||
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
|
||||
(span (~tw :tokens "font-medium") "search-debounce")
|
||||
(span
|
||||
(~tw :tokens "text-sm text-stone-400 ml-auto")
|
||||
"Type in search, results appear after debounce"))
|
||||
(div
|
||||
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
|
||||
(pre
|
||||
(~tw
|
||||
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
|
||||
"(deftest search-debounce\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (fill \"input[hx-get]\" \"hx-get\")\n (wait 1500)\n (assert-text \"#search-results\" :contains \"GET request\"))")))
|
||||
(details
|
||||
:class "sx-test-item"
|
||||
:data-test "search-no-results"
|
||||
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
|
||||
(summary
|
||||
(~tw
|
||||
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
|
||||
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
|
||||
(span (~tw :tokens "font-medium") "search-no-results")
|
||||
(span
|
||||
(~tw :tokens "text-sm text-stone-400 ml-auto")
|
||||
"Non-matching query shows no results"))
|
||||
(div
|
||||
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
|
||||
(pre
|
||||
(~tw
|
||||
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
|
||||
"(deftest search-no-results\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (fill \"input[hx-get]\" \"xyznonexistent\")\n (wait 1500)\n (assert-text \"#search-results\" :contains \"No results\"))")))
|
||||
(details
|
||||
:class "sx-test-item"
|
||||
:data-test "tab-overview"
|
||||
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
|
||||
(summary
|
||||
(~tw
|
||||
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
|
||||
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
|
||||
(span (~tw :tokens "font-medium") "tab-overview")
|
||||
(span
|
||||
(~tw :tokens "text-sm text-stone-400 ml-auto")
|
||||
"Click overview tab, verify content"))
|
||||
(div
|
||||
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
|
||||
(pre
|
||||
(~tw
|
||||
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
|
||||
"(deftest tab-overview\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-get*='tab=overview']\")\n (wait 2000)\n (assert-text \"#htmx-tab-content\" :contains \"htmx gives you access\"))")))
|
||||
(details
|
||||
:class "sx-test-item"
|
||||
:data-test "tab-features"
|
||||
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
|
||||
(summary
|
||||
(~tw
|
||||
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
|
||||
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
|
||||
(span (~tw :tokens "font-medium") "tab-features")
|
||||
(span
|
||||
(~tw :tokens "text-sm text-stone-400 ml-auto")
|
||||
"Click features tab, verify list"))
|
||||
(div
|
||||
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
|
||||
(pre
|
||||
(~tw
|
||||
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
|
||||
"(deftest tab-features\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-get*='tab=features']\")\n (wait 2000)\n (assert-text \"#htmx-tab-content\" :contains \"Any element\"))")))
|
||||
(details
|
||||
:class "sx-test-item"
|
||||
:data-test "append-item"
|
||||
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
|
||||
(summary
|
||||
(~tw
|
||||
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
|
||||
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
|
||||
(span (~tw :tokens "font-medium") "append-item")
|
||||
(span
|
||||
(~tw :tokens "text-sm text-stone-400 ml-auto")
|
||||
"POST appends new item to list"))
|
||||
(div
|
||||
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
|
||||
(pre
|
||||
(~tw
|
||||
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
|
||||
"(deftest append-item\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-post*='api.append']\")\n (wait 2000)\n (assert-count \"#item-list > *\" :gte 1))")))
|
||||
(details
|
||||
:class "sx-test-item"
|
||||
:data-test "form-submit"
|
||||
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
|
||||
(summary
|
||||
(~tw
|
||||
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
|
||||
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
|
||||
(span (~tw :tokens "font-medium") "form-submit")
|
||||
(span
|
||||
(~tw :tokens "text-sm text-stone-400 ml-auto")
|
||||
"POST form data, verify name in response"))
|
||||
(div
|
||||
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
|
||||
(pre
|
||||
(~tw
|
||||
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
|
||||
"(deftest form-submit\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (fill \"form[hx-post] input[name='name']\" \"Alice\")\n (fill \"form[hx-post] input[name='email']\" \"alice@test.com\")\n (click \"form[hx-post] button[type='submit']\")\n (wait 2000)\n (assert-text \"#form-result\" :contains \"Alice\"))"))))
|
||||
(iframe
|
||||
:id "test-iframe"
|
||||
:src "/sx/(applications.(htmx))"
|
||||
(~tw :tokens "w-full border border-stone-200 rounded-lg")
|
||||
:style "height:600px")
|
||||
(script :src "/static/scripts/sx-test-runner.js?v=7")))
|
||||
@@ -53,4 +53,11 @@
|
||||
(span
|
||||
(~tw :tokens "text-stone-300 text-xs")
|
||||
:style "margin-left:0.5em;"
|
||||
(a
|
||||
:href (str "/sx/(test." (slice path 4) ")")
|
||||
:data-sx-no-boost "true"
|
||||
:target "_self"
|
||||
(~tw :tokens "text-violet-400 text-xs hover:text-violet-600")
|
||||
:style "text-decoration:none;margin-right:0.4em;"
|
||||
"test")
|
||||
(str "· " path))))))))
|
||||
|
||||
@@ -18,11 +18,7 @@
|
||||
(list (make-symbol default-name))
|
||||
(list (slug->component slug prefix infix suffix))))))
|
||||
|
||||
(define
|
||||
home
|
||||
(fn
|
||||
(content)
|
||||
(if (nil? content) (quote (~docs-content/home-content)) content)))
|
||||
(define home (fn (content) (if (nil? content) (quote (~home)) content)))
|
||||
|
||||
(define language (fn (content) (if (nil? content) (quote (<>)) content)))
|
||||
|
||||
@@ -42,18 +38,13 @@
|
||||
|
||||
(define
|
||||
reactive
|
||||
(fn
|
||||
(content)
|
||||
(if
|
||||
(nil? content)
|
||||
(quote (~reactive-islands/index/reactive-islands-index-content))
|
||||
content)))
|
||||
(fn (content) (if (nil? content) (quote (~geography/reactive)) content)))
|
||||
|
||||
(define
|
||||
examples
|
||||
(make-page-fn
|
||||
"~reactive-islands/demo/reactive-islands-demo-content"
|
||||
"~reactive-islands/demo/example-"
|
||||
"~geography/reactive/examples"
|
||||
"~geography/reactive/examples/"
|
||||
nil
|
||||
""))
|
||||
|
||||
@@ -63,34 +54,28 @@
|
||||
(slug)
|
||||
(if
|
||||
(nil? slug)
|
||||
(quote (~geography/cek/cek-content))
|
||||
(quote (~geography/cek))
|
||||
(case
|
||||
slug
|
||||
"demo"
|
||||
(quote (~geography/cek/cek-demo-content))
|
||||
(quote (~geography/cek/demo))
|
||||
"freeze"
|
||||
(quote (~geography/cek/cek-freeze-content))
|
||||
(quote (~geography/cek/freeze))
|
||||
"content"
|
||||
(quote (~geography/cek/cek-content-address-content))
|
||||
:else (quote (~geography/cek/cek-content))))))
|
||||
(quote (~geography/cek/content))
|
||||
:else (quote (~geography/cek))))))
|
||||
|
||||
(define
|
||||
provide
|
||||
(fn
|
||||
(content)
|
||||
(if (nil? content) (quote (~geography/provide-content)) content)))
|
||||
(fn (content) (if (nil? content) (quote (~geography/provide)) content)))
|
||||
|
||||
(define
|
||||
scopes
|
||||
(fn
|
||||
(content)
|
||||
(if (nil? content) (quote (~geography/scopes-content)) content)))
|
||||
(fn (content) (if (nil? content) (quote (~geography/scopes)) content)))
|
||||
|
||||
(define
|
||||
spreads
|
||||
(fn
|
||||
(content)
|
||||
(if (nil? content) (quote (~geography/spreads-content)) content)))
|
||||
(fn (content) (if (nil? content) (quote (~geography/spreads)) content)))
|
||||
|
||||
(define
|
||||
marshes
|
||||
@@ -98,20 +83,20 @@
|
||||
(slug)
|
||||
(if
|
||||
(nil? slug)
|
||||
(quote (~reactive-islands/marshes/reactive-islands-marshes-content))
|
||||
(quote (~geography/marshes))
|
||||
(case
|
||||
slug
|
||||
"hypermedia-feeds"
|
||||
(quote (~reactive-islands/marshes/example-hypermedia-feeds))
|
||||
(quote (~geography/marshes/hypermedia-feeds))
|
||||
"server-signals"
|
||||
(quote (~reactive-islands/marshes/example-server-signals))
|
||||
(quote (~geography/marshes/server-signals))
|
||||
"on-settle"
|
||||
(quote (~reactive-islands/marshes/example-on-settle))
|
||||
(quote (~geography/marshes/on-settle))
|
||||
"signal-triggers"
|
||||
(quote (~reactive-islands/marshes/example-signal-triggers))
|
||||
(quote (~geography/marshes/signal-triggers))
|
||||
"view-transform"
|
||||
(quote (~reactive-islands/marshes/example-view-transform))
|
||||
:else (quote (~reactive-islands/marshes/reactive-islands-marshes-content))))))
|
||||
(quote (~geography/marshes/view-transform))
|
||||
:else (quote (~geography/marshes))))))
|
||||
|
||||
(define
|
||||
isomorphism
|
||||
@@ -119,14 +104,14 @@
|
||||
(slug)
|
||||
(if
|
||||
(nil? slug)
|
||||
(quote (~plans/isomorphic/plan-isomorphic-content))
|
||||
(quote (~geography/isomorphism))
|
||||
(case
|
||||
slug
|
||||
"bundle-analyzer"
|
||||
(let
|
||||
((data (helper "bundle-analyzer-data")))
|
||||
(quasiquote
|
||||
(~analyzer/bundle-analyzer-content
|
||||
(~geography/isomorphism/bundle-analyzer
|
||||
:pages (unquote (get data "pages"))
|
||||
:total-components (unquote (get data "total-components"))
|
||||
:total-macros (unquote (get data "total-macros"))
|
||||
@@ -136,7 +121,7 @@
|
||||
(let
|
||||
((data (helper "routing-analyzer-data")))
|
||||
(quasiquote
|
||||
(~routing-analyzer/content
|
||||
(~geography/isomorphism/routing-analyzer
|
||||
:pages (unquote (get data "pages"))
|
||||
:total-pages (unquote (get data "total-pages"))
|
||||
:client-count (unquote (get data "client-count"))
|
||||
@@ -146,35 +131,35 @@
|
||||
(let
|
||||
((data (helper "data-test-data")))
|
||||
(quasiquote
|
||||
(~data-test/content
|
||||
(~geography/isomorphism/data-test
|
||||
:server-time (unquote (get data "server-time"))
|
||||
:items (unquote (get data "items"))
|
||||
:phase (unquote (get data "phase"))
|
||||
:transport (unquote (get data "transport")))))
|
||||
"async-io"
|
||||
(quote (~async-io-demo/content))
|
||||
(quote (~geography/isomorphism/async-io))
|
||||
"affinity"
|
||||
(let
|
||||
((data (helper "affinity-demo-data")))
|
||||
(quasiquote
|
||||
(~affinity-demo/content
|
||||
(~geography/isomorphism/affinity
|
||||
:components (unquote (get data "components"))
|
||||
:page-plans (unquote (get data "page-plans")))))
|
||||
"optimistic"
|
||||
(let
|
||||
((data (helper "optimistic-demo-data")))
|
||||
(quasiquote
|
||||
(~optimistic-demo/content
|
||||
(~geography/isomorphism/optimistic
|
||||
:items (unquote (get data "items"))
|
||||
:server-time (unquote (get data "server-time")))))
|
||||
"offline"
|
||||
(let
|
||||
((data (helper "offline-demo-data")))
|
||||
(quasiquote
|
||||
(~offline-demo/content
|
||||
(~geography/isomorphism/offline
|
||||
:notes (unquote (get data "notes"))
|
||||
:server-time (unquote (get data "server-time")))))
|
||||
:else (quote (~plans/isomorphic/plan-isomorphic-content))))))
|
||||
:else (quote (~geography/isomorphism))))))
|
||||
|
||||
(define
|
||||
doc
|
||||
@@ -182,32 +167,34 @@
|
||||
(slug)
|
||||
(if
|
||||
(nil? slug)
|
||||
(quote (~docs-content/docs-introduction-content))
|
||||
(quote (~language/doc/introduction))
|
||||
(case
|
||||
slug
|
||||
"introduction"
|
||||
(quote (~docs-content/docs-introduction-content))
|
||||
(quote (~language/doc/introduction))
|
||||
"getting-started"
|
||||
(quote (~docs-content/docs-getting-started-content))
|
||||
(quote (~language/doc/getting-started))
|
||||
"components"
|
||||
(quote (~docs-content/docs-components-content))
|
||||
(quote (~language/doc/components))
|
||||
"evaluator"
|
||||
(quote (~docs-content/docs-evaluator-content))
|
||||
(quote (~language/doc/evaluator))
|
||||
"primitives"
|
||||
(let
|
||||
((data (helper "primitives-data")))
|
||||
(quasiquote
|
||||
(~docs-content/docs-primitives-content
|
||||
:prims (~docs/primitives-tables :primitives (unquote data)))))
|
||||
(~language/doc/primitives
|
||||
:prims (~language/doc/_shared/primitives-tables
|
||||
:primitives (unquote data)))))
|
||||
"special-forms"
|
||||
(let
|
||||
((data (helper "special-forms-data")))
|
||||
(quasiquote
|
||||
(~docs-content/docs-special-forms-content
|
||||
:forms (~docs/special-forms-tables :forms (unquote data)))))
|
||||
(~language/doc/special-forms
|
||||
:forms (~language/doc/_shared/special-forms-tables
|
||||
:forms (unquote data)))))
|
||||
"server-rendering"
|
||||
(quote (~docs-content/docs-server-rendering-content))
|
||||
:else (quote (~docs-content/docs-introduction-content))))))
|
||||
(quote (~language/doc/server-rendering))
|
||||
:else (quote (~language/doc/introduction))))))
|
||||
|
||||
(define
|
||||
spec
|
||||
@@ -215,7 +202,7 @@
|
||||
(slug)
|
||||
(cond
|
||||
(nil? slug)
|
||||
(quote (~specs/architecture-content))
|
||||
(quote (~language/spec))
|
||||
(not (= (type-of slug) "string"))
|
||||
slug
|
||||
:else (case
|
||||
@@ -224,42 +211,42 @@
|
||||
(let
|
||||
((files (make-spec-files core-spec-items)))
|
||||
(quasiquote
|
||||
(~specs/overview-content
|
||||
(~language/spec/_shared/overview-content
|
||||
:spec-title "Core Language"
|
||||
:spec-files (unquote files))))
|
||||
"adapters"
|
||||
(let
|
||||
((files (make-spec-files adapter-spec-items)))
|
||||
(quasiquote
|
||||
(~specs/overview-content
|
||||
(~language/spec/_shared/overview-content
|
||||
:spec-title "Adapters"
|
||||
:spec-files (unquote files))))
|
||||
"browser"
|
||||
(let
|
||||
((files (make-spec-files browser-spec-items)))
|
||||
(quasiquote
|
||||
(~specs/overview-content
|
||||
(~language/spec/_shared/overview-content
|
||||
:spec-title "Browser Runtime"
|
||||
:spec-files (unquote files))))
|
||||
"reactive"
|
||||
(let
|
||||
((files (make-spec-files reactive-spec-items)))
|
||||
(quasiquote
|
||||
(~specs/overview-content
|
||||
(~language/spec/_shared/overview-content
|
||||
:spec-title "Reactive System"
|
||||
:spec-files (unquote files))))
|
||||
"host"
|
||||
(let
|
||||
((files (make-spec-files host-spec-items)))
|
||||
(quasiquote
|
||||
(~specs/overview-content
|
||||
(~language/spec/_shared/overview-content
|
||||
:spec-title "Host Interface"
|
||||
:spec-files (unquote files))))
|
||||
"extensions"
|
||||
(let
|
||||
((files (make-spec-files extension-spec-items)))
|
||||
(quasiquote
|
||||
(~specs/overview-content
|
||||
(~language/spec/_shared/overview-content
|
||||
:spec-title "Extensions"
|
||||
:spec-files (unquote files))))
|
||||
:else (let
|
||||
@@ -269,13 +256,14 @@
|
||||
(let
|
||||
((src (helper "read-spec-file" (get found-spec "filename"))))
|
||||
(quasiquote
|
||||
(~specs/detail-content
|
||||
(~language/spec/_shared/detail-content
|
||||
:spec-title (unquote (get found-spec "title"))
|
||||
:spec-desc (unquote (get found-spec "desc"))
|
||||
:spec-filename (unquote (get found-spec "filename"))
|
||||
:spec-source (unquote src)
|
||||
:spec-prose (unquote (get found-spec "prose")))))
|
||||
(quasiquote (~specs/not-found :slug (unquote slug)))))))))
|
||||
(quasiquote
|
||||
(~language/spec/_shared/not-found :slug (unquote slug)))))))))
|
||||
|
||||
(define
|
||||
explore
|
||||
@@ -332,17 +320,18 @@
|
||||
(slug)
|
||||
(if
|
||||
(nil? slug)
|
||||
(quote (~specs/bootstrappers-index-content))
|
||||
(quote (~language/bootstrapper))
|
||||
(let
|
||||
((data (helper "bootstrapper-data" slug)))
|
||||
(if
|
||||
(get data "bootstrapper-not-found")
|
||||
(quasiquote (~specs/not-found :slug (unquote slug)))
|
||||
(quasiquote
|
||||
(~language/bootstrapper/_shared/not-found :slug (unquote slug)))
|
||||
(case
|
||||
slug
|
||||
"self-hosting"
|
||||
(quasiquote
|
||||
(~specs/bootstrapper-self-hosting-content
|
||||
(~language/bootstrapper/self-hosting
|
||||
:py-sx-source (unquote (get data "py-sx-source"))
|
||||
:g0-output (unquote (get data "g0-output"))
|
||||
:g1-output (unquote (get data "g1-output"))
|
||||
@@ -353,7 +342,7 @@
|
||||
:verification-status (unquote (get data "verification-status"))))
|
||||
"self-hosting-js"
|
||||
(quasiquote
|
||||
(~specs/bootstrapper-self-hosting-js-content
|
||||
(~language/bootstrapper/self-hosting-js
|
||||
:js-sx-source (unquote (get data "js-sx-source"))
|
||||
:defines-matched (unquote (get data "defines-matched"))
|
||||
:defines-total (unquote (get data "defines-total"))
|
||||
@@ -361,14 +350,14 @@
|
||||
:verification-status (unquote (get data "verification-status"))))
|
||||
"python"
|
||||
(quasiquote
|
||||
(~specs/bootstrapper-py-content
|
||||
(~language/bootstrapper/python
|
||||
:bootstrapper-source (unquote (get data "bootstrapper-source"))
|
||||
:bootstrapped-output (unquote (get data "bootstrapped-output"))))
|
||||
"page-helpers"
|
||||
(let
|
||||
((ph-data (helper "page-helpers-demo-data")))
|
||||
(quasiquote
|
||||
(~page-helpers-demo/content
|
||||
(~language/bootstrapper/page-helpers
|
||||
:sf-categories (unquote (get ph-data "sf-categories"))
|
||||
:sf-total (unquote (get ph-data "sf-total"))
|
||||
:sf-ms (unquote (get ph-data "sf-ms"))
|
||||
@@ -386,7 +375,7 @@
|
||||
:req-attrs (unquote (get ph-data "req-attrs"))
|
||||
:attr-keys (unquote (get ph-data "attr-keys")))))
|
||||
:else (quasiquote
|
||||
(~specs/bootstrapper-js-content
|
||||
(~language/bootstrapper/javascript
|
||||
:bootstrapper-source (unquote (get data "bootstrapper-source"))
|
||||
:bootstrapped-output (unquote (get data "bootstrapped-output"))))))))))
|
||||
|
||||
@@ -397,9 +386,9 @@
|
||||
(if
|
||||
(nil? slug)
|
||||
(let
|
||||
((data (helper "run-modular-tests" "all")))
|
||||
((data (perform (list (quote io-test-data)))))
|
||||
(quasiquote
|
||||
(~testing/overview-content
|
||||
(~language/test
|
||||
:server-results (unquote (get data "server-results"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:eval-source (unquote (get data "eval-source"))
|
||||
@@ -408,80 +397,85 @@
|
||||
:render-source (unquote (get data "render-source"))
|
||||
:deps-source (unquote (get data "deps-source"))
|
||||
:engine-source (unquote (get data "engine-source")))))
|
||||
(case
|
||||
slug
|
||||
"runners"
|
||||
(quote (~testing/runners-content))
|
||||
:else (let
|
||||
((data (helper "run-modular-tests" slug)))
|
||||
(case
|
||||
slug
|
||||
"eval"
|
||||
(quasiquote
|
||||
(~testing/spec-content
|
||||
:spec-name "eval"
|
||||
:spec-title "Evaluator Tests"
|
||||
:spec-desc "81 tests covering the core evaluator and all primitives."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"parser"
|
||||
(quasiquote
|
||||
(~testing/spec-content
|
||||
:spec-name "parser"
|
||||
:spec-title "Parser Tests"
|
||||
:spec-desc "39 tests covering tokenization and parsing."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"router"
|
||||
(quasiquote
|
||||
(~testing/spec-content
|
||||
:spec-name "router"
|
||||
:spec-title "Router Tests"
|
||||
:spec-desc "18 tests covering client-side route matching."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"render"
|
||||
(quasiquote
|
||||
(~testing/spec-content
|
||||
:spec-name "render"
|
||||
:spec-title "Renderer Tests"
|
||||
:spec-desc "23 tests covering HTML rendering."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"deps"
|
||||
(quasiquote
|
||||
(~testing/spec-content
|
||||
:spec-name "deps"
|
||||
:spec-title "Dependency Analysis Tests"
|
||||
:spec-desc "33 tests covering component dependency analysis."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"engine"
|
||||
(quasiquote
|
||||
(~testing/spec-content
|
||||
:spec-name "engine"
|
||||
:spec-title "Engine Tests"
|
||||
:spec-desc "37 tests covering engine pure functions."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"orchestration"
|
||||
(quasiquote
|
||||
(~testing/spec-content
|
||||
:spec-name "orchestration"
|
||||
:spec-title "Orchestration Tests"
|
||||
:spec-desc "17 tests covering orchestration."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
:else (quasiquote
|
||||
(~testing/overview-content
|
||||
:server-results (unquote (get data "server-results"))))))))))
|
||||
(if
|
||||
(not (string? slug))
|
||||
(quote (~applications/htmx/runner))
|
||||
(case
|
||||
slug
|
||||
"runners"
|
||||
(quote (~language/test/runners))
|
||||
"applications"
|
||||
(quote (~applications/htmx/runner))
|
||||
:else (let
|
||||
((data (perform (list (quote io-test-data) slug))))
|
||||
(case
|
||||
slug
|
||||
"eval"
|
||||
(quasiquote
|
||||
(~language/test/_shared/spec-content
|
||||
:spec-name "eval"
|
||||
:spec-title "Evaluator Tests"
|
||||
:spec-desc "81 tests covering the core evaluator."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"parser"
|
||||
(quasiquote
|
||||
(~language/test/_shared/spec-content
|
||||
:spec-name "parser"
|
||||
:spec-title "Parser Tests"
|
||||
:spec-desc "39 tests covering tokenization and parsing."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"router"
|
||||
(quasiquote
|
||||
(~language/test/_shared/spec-content
|
||||
:spec-name "router"
|
||||
:spec-title "Router Tests"
|
||||
:spec-desc "18 tests covering client-side route matching."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"render"
|
||||
(quasiquote
|
||||
(~language/test/_shared/spec-content
|
||||
:spec-name "render"
|
||||
:spec-title "Renderer Tests"
|
||||
:spec-desc "23 tests covering HTML rendering."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"deps"
|
||||
(quasiquote
|
||||
(~language/test/_shared/spec-content
|
||||
:spec-name "deps"
|
||||
:spec-title "Dependency Analysis Tests"
|
||||
:spec-desc "33 tests covering component dependency analysis."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"engine"
|
||||
(quasiquote
|
||||
(~language/test/_shared/spec-content
|
||||
:spec-name "engine"
|
||||
:spec-title "Engine Tests"
|
||||
:spec-desc "37 tests covering engine pure functions."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
"orchestration"
|
||||
(quasiquote
|
||||
(~language/test/_shared/spec-content
|
||||
:spec-name "orchestration"
|
||||
:spec-title "Orchestration Tests"
|
||||
:spec-desc "17 tests covering orchestration."
|
||||
:spec-source (unquote (get data "spec-source"))
|
||||
:framework-source (unquote (get data "framework-source"))
|
||||
:server-results (unquote (get data "server-results"))))
|
||||
:else (quasiquote
|
||||
(~language/test
|
||||
:server-results (unquote (get data "server-results")))))))))))
|
||||
|
||||
(define
|
||||
reference
|
||||
@@ -489,57 +483,57 @@
|
||||
(slug)
|
||||
(if
|
||||
(nil? slug)
|
||||
(quote (~examples/reference-index-content))
|
||||
(quote (~geography/hypermedia/reference))
|
||||
(let
|
||||
((data (helper "reference-data" slug)))
|
||||
(case
|
||||
slug
|
||||
"attributes"
|
||||
(quasiquote
|
||||
(~reference/attrs-content
|
||||
:req-table (~docs/attr-table-from-data
|
||||
(~geography/hypermedia/reference/attributes
|
||||
:req-table (~geography/hypermedia/reference/_shared/attr-table-from-data
|
||||
:title "Request Attributes"
|
||||
:attrs (unquote (get data "req-attrs")))
|
||||
:beh-table (~docs/attr-table-from-data
|
||||
:beh-table (~geography/hypermedia/reference/_shared/attr-table-from-data
|
||||
:title "Behavior Attributes"
|
||||
:attrs (unquote (get data "beh-attrs")))
|
||||
:uniq-table (~docs/attr-table-from-data
|
||||
:uniq-table (~geography/hypermedia/reference/_shared/attr-table-from-data
|
||||
:title "Unique to sx"
|
||||
:attrs (unquote (get data "uniq-attrs")))))
|
||||
"headers"
|
||||
(quasiquote
|
||||
(~reference/headers-content
|
||||
:req-table (~docs/headers-table-from-data
|
||||
(~geography/hypermedia/reference/headers
|
||||
:req-table (~geography/hypermedia/reference/_shared/headers-table-from-data
|
||||
:title "Request Headers"
|
||||
:headers (unquote (get data "req-headers")))
|
||||
:resp-table (~docs/headers-table-from-data
|
||||
:resp-table (~geography/hypermedia/reference/_shared/headers-table-from-data
|
||||
:title "Response Headers"
|
||||
:headers (unquote (get data "resp-headers")))))
|
||||
"events"
|
||||
(quasiquote
|
||||
(~reference/events-content
|
||||
:table (~docs/two-col-table-from-data
|
||||
(~geography/hypermedia/reference/events
|
||||
:table (~geography/hypermedia/reference/_shared/two-col-table-from-data
|
||||
:intro "sx fires custom DOM events at various points in the request lifecycle."
|
||||
:col1 "Event"
|
||||
:col2 "Description"
|
||||
:items (unquote (get data "events-list")))))
|
||||
"js-api"
|
||||
(quasiquote
|
||||
(~reference/js-api-content
|
||||
:table (~docs/two-col-table-from-data
|
||||
(~geography/hypermedia/reference/js-api
|
||||
:table (~geography/hypermedia/reference/_shared/two-col-table-from-data
|
||||
:intro "The client-side sx.js library exposes a public API for programmatic use."
|
||||
:col1 "Method"
|
||||
:col2 "Description"
|
||||
:items (unquote (get data "js-api-list")))))
|
||||
:else (quasiquote
|
||||
(~reference/attrs-content
|
||||
:req-table (~docs/attr-table-from-data
|
||||
(~geography/hypermedia/reference/attributes
|
||||
:req-table (~geography/hypermedia/reference/_shared/attr-table-from-data
|
||||
:title "Request Attributes"
|
||||
:attrs (unquote (get data "req-attrs")))
|
||||
:beh-table (~docs/attr-table-from-data
|
||||
:beh-table (~geography/hypermedia/reference/_shared/attr-table-from-data
|
||||
:title "Behavior Attributes"
|
||||
:attrs (unquote (get data "beh-attrs")))
|
||||
:uniq-table (~docs/attr-table-from-data
|
||||
:uniq-table (~geography/hypermedia/reference/_shared/attr-table-from-data
|
||||
:title "Unique to sx"
|
||||
:attrs (unquote (get data "uniq-attrs"))))))))))
|
||||
|
||||
@@ -557,9 +551,11 @@
|
||||
((data (helper "attr-detail-data" slug)))
|
||||
(if
|
||||
(get data "attr-not-found")
|
||||
(quasiquote (~reference/attr-not-found :slug (unquote slug)))
|
||||
(quasiquote
|
||||
(~reference/attr-detail-content
|
||||
(~geography/hypermedia/reference/_shared/attr-not-found
|
||||
:slug (unquote slug)))
|
||||
(quasiquote
|
||||
(~geography/hypermedia/reference/_shared/attr-detail-content
|
||||
:title (unquote (get data "attr-title"))
|
||||
:description (unquote (get data "attr-description"))
|
||||
:demo (unquote (get data "attr-demo"))
|
||||
@@ -571,9 +567,11 @@
|
||||
((data (helper "header-detail-data" slug)))
|
||||
(if
|
||||
(get data "header-not-found")
|
||||
(quasiquote (~reference/attr-not-found :slug (unquote slug)))
|
||||
(quasiquote
|
||||
(~reference/header-detail-content
|
||||
(~geography/hypermedia/reference/_shared/attr-not-found
|
||||
:slug (unquote slug)))
|
||||
(quasiquote
|
||||
(~geography/hypermedia/reference/_shared/header-detail-content
|
||||
:title (unquote (get data "header-title"))
|
||||
:direction (unquote (get data "header-direction"))
|
||||
:description (unquote (get data "header-description"))
|
||||
@@ -584,9 +582,11 @@
|
||||
((data (helper "event-detail-data" slug)))
|
||||
(if
|
||||
(get data "event-not-found")
|
||||
(quasiquote (~reference/attr-not-found :slug (unquote slug)))
|
||||
(quasiquote
|
||||
(~reference/event-detail-content
|
||||
(~geography/hypermedia/reference/_shared/attr-not-found
|
||||
:slug (unquote slug)))
|
||||
(quasiquote
|
||||
(~geography/hypermedia/reference/_shared/event-detail-content
|
||||
:title (unquote (get data "event-title"))
|
||||
:description (unquote (get data "event-description"))
|
||||
:example-code (unquote (get data "event-example"))
|
||||
@@ -600,26 +600,26 @@
|
||||
(if
|
||||
(nil? slug)
|
||||
""
|
||||
(list (slug->component slug "~examples-content/example-" nil "")))))
|
||||
(list (slug->component slug "~geography/hypermedia/example/" nil "")))))
|
||||
|
||||
(define sx-urls (fn (slug) (quote (~sx-urls/urls-content))))
|
||||
(define sx-urls (fn (slug) (quote (~applications/sx-urls))))
|
||||
|
||||
(define cssx (make-page-fn "~cssx/overview-content" "~cssx/" nil "-content"))
|
||||
(define cssx (make-page-fn "~applications/cssx" "~applications/cssx/" nil ""))
|
||||
|
||||
(define
|
||||
protocol
|
||||
(make-page-fn "~protocols/wire-format-content" "~protocols/" nil "-content"))
|
||||
(make-page-fn "~applications/protocol" "~applications/protocol/" nil ""))
|
||||
|
||||
(define
|
||||
sx-pub
|
||||
(fn (slug) (if (nil? slug) (quote (~sx-pub/overview-content)) nil)))
|
||||
(fn (slug) (if (nil? slug) (quote (~applications/sx-pub)) nil)))
|
||||
|
||||
(define
|
||||
sx-tools
|
||||
(fn
|
||||
(&key title &rest args)
|
||||
(quasiquote
|
||||
(~sx-tools/overview-content
|
||||
(~tools/sx-tools
|
||||
:title (unquote (or title "SX Tools"))
|
||||
(splice-unquote args)))))
|
||||
|
||||
@@ -630,7 +630,7 @@
|
||||
(fn
|
||||
(&key title &rest args)
|
||||
(quasiquote
|
||||
(~playground/content
|
||||
(~tools/playground
|
||||
:title (unquote (or title "Playground"))
|
||||
(splice-unquote args)))))
|
||||
|
||||
@@ -639,29 +639,27 @@
|
||||
(fn
|
||||
(&key title &rest args)
|
||||
(quasiquote
|
||||
(~services-tools/overview-content
|
||||
(~tools/services
|
||||
:title (unquote (or title "Services"))
|
||||
(splice-unquote args)))))
|
||||
|
||||
(define
|
||||
reactive-runtime
|
||||
(make-page-fn
|
||||
"~reactive-runtime/overview-content"
|
||||
"~reactive-runtime/"
|
||||
"~geography/reactive-runtime"
|
||||
"~geography/reactive-runtime/"
|
||||
nil
|
||||
"-content"))
|
||||
""))
|
||||
|
||||
(define
|
||||
native-browser
|
||||
(make-page-fn
|
||||
"~applications/native-browser/content"
|
||||
"~applications/native-browser"
|
||||
"~applications/native-browser/"
|
||||
nil
|
||||
"-content"))
|
||||
""))
|
||||
|
||||
(define
|
||||
essay
|
||||
(make-page-fn "~essays/index/essays-index-content" "~essays/" "/essay-" ""))
|
||||
(define essay (make-page-fn "~etc/essay" "~etc/essay/" nil ""))
|
||||
|
||||
(define
|
||||
philosophy
|
||||
@@ -669,67 +667,53 @@
|
||||
(slug)
|
||||
(if
|
||||
(nil? slug)
|
||||
(quote (~essays/philosophy-index/content))
|
||||
(quote (~etc/philosophy))
|
||||
(case
|
||||
slug
|
||||
"sx-manifesto"
|
||||
(quote (~essay-sx-manifesto))
|
||||
(quote (~etc/philosophy/sx-manifesto))
|
||||
"godel-escher-bach"
|
||||
(quote (~essays/godel-escher-bach/essay-godel-escher-bach))
|
||||
(quote (~etc/philosophy/godel-escher-bach))
|
||||
"wittgenstein"
|
||||
(quote (~essays/sx-and-wittgenstein/essay-sx-and-wittgenstein))
|
||||
(quote (~etc/philosophy/wittgenstein))
|
||||
"dennett"
|
||||
(quote (~essays/sx-and-dennett/essay-sx-and-dennett))
|
||||
(quote (~etc/philosophy/dennett))
|
||||
"existentialism"
|
||||
(quote (~essays/s-existentialism/essay-s-existentialism))
|
||||
(quote (~etc/philosophy/existentialism))
|
||||
"platonic-sx"
|
||||
(quote (~essays/platonic-sx/essay-platonic-sx))
|
||||
:else (quote (~essays/philosophy-index/content))))))
|
||||
(quote (~etc/philosophy/platonic-sx))
|
||||
:else (quote (~etc/philosophy))))))
|
||||
|
||||
(define
|
||||
plan
|
||||
(make-page-fn
|
||||
"~plans/index/plans-index-content"
|
||||
"~plans/"
|
||||
"/plan-"
|
||||
"-content"))
|
||||
(define plan (make-page-fn "~etc/plan" "~etc/plan/" nil ""))
|
||||
|
||||
(define
|
||||
capabilities
|
||||
(fn
|
||||
(&key title &rest args)
|
||||
(quasiquote (~geography/capabilities-content))))
|
||||
(fn (&key title &rest args) (quasiquote (~geography/capabilities))))
|
||||
|
||||
(define
|
||||
modules
|
||||
(fn (&key title &rest args) (quasiquote (~geography/modules-content))))
|
||||
(fn (&key title &rest args) (quasiquote (~geography/modules))))
|
||||
|
||||
(define
|
||||
eval-rules
|
||||
(fn (&key title &rest args) (quasiquote (~geography/eval-rules-content))))
|
||||
(fn (&key title &rest args) (quasiquote (~geography/eval-rules))))
|
||||
|
||||
(define
|
||||
hyperscript
|
||||
(make-page-fn
|
||||
"~hyperscript/playground-content"
|
||||
"~hyperscript/"
|
||||
"~applications/hyperscript"
|
||||
"~applications/hyperscript/"
|
||||
nil
|
||||
"-content"))
|
||||
""))
|
||||
|
||||
(define htmx (make-page-fn "~htmx/demo-content" "~htmx/" nil "-content"))
|
||||
(define htmx (make-page-fn "~applications/htmx" "~applications/htmx/" nil ""))
|
||||
|
||||
(define
|
||||
sxtp
|
||||
(make-page-fn
|
||||
"~applications/sxtp/content"
|
||||
"~applications/sxtp/"
|
||||
nil
|
||||
"-content"))
|
||||
(define sxtp (make-page-fn "~applications/sxtp" "~applications/sxtp/" nil ""))
|
||||
|
||||
(define
|
||||
graphql
|
||||
(make-page-fn "~graphql/demo-content" "~graphql/" nil "-content"))
|
||||
(make-page-fn "~applications/graphql" "~applications/graphql/" nil ""))
|
||||
|
||||
(define
|
||||
pretext
|
||||
(make-page-fn "~pretext-demo/content" "~pretext-demo/" nil "-content"))
|
||||
(make-page-fn "~applications/pretext" "~applications/pretext/" nil ""))
|
||||
|
||||
@@ -415,6 +415,14 @@
|
||||
(swap-dom-nodes t oob s)
|
||||
(post-swap t)))
|
||||
(hoist-head-elements container)
|
||||
(let
|
||||
((manifest-el (dom-query-in container "script[data-sx-manifest]")))
|
||||
(when
|
||||
manifest-el
|
||||
(host-call
|
||||
(host-global "Sx")
|
||||
"mergeManifest"
|
||||
manifest-el)))
|
||||
(let
|
||||
((html (select-from-container container select-sel)))
|
||||
(with-transition
|
||||
@@ -1547,7 +1555,8 @@
|
||||
(process-sse root)
|
||||
(bind-inline-handlers root)
|
||||
(process-emit-elements root)
|
||||
(hs-boot-subtree! (or root (host-global "document")))))
|
||||
(hs-boot-subtree! (or root (host-global "document")))
|
||||
(htmx-boot-subtree! (or root (host-global "document")))))
|
||||
(define
|
||||
process-one
|
||||
:effects (mutation io)
|
||||
@@ -1622,9 +1631,11 @@
|
||||
(pathname (url-pathname url)))
|
||||
(when
|
||||
target
|
||||
(let
|
||||
((headers (dict "SX-Request" "true")))
|
||||
(fetch-and-restore target url headers scrollY))))))
|
||||
(when
|
||||
(not (try-client-route pathname target-sel))
|
||||
(let
|
||||
((headers (build-request-headers target (loaded-component-names))))
|
||||
(fetch-and-restore target url headers scrollY)))))))
|
||||
(define
|
||||
engine-init
|
||||
:effects (mutation io)
|
||||
|
||||
Reference in New Issue
Block a user