Add 5 MCP tools, refactor nav-data, fix deep path bug, fix Playwright failures

Nav refactoring:
- Split nav-data.sx (32 forms) into 6 files: nav-geography, nav-language,
  nav-applications, nav-etc, nav-tools, nav-tree
- Add Tools top-level nav category with SX Tools and Services pages
- New services-tools.sx page documenting the rose-ash-services MCP server

JS build fixes (fixes 5 Playwright failures):
- Wire web/web-signals.sx into JS build (stores, events, resources)
- Add cek-try primitive to JS platform (island hydration error handling)
- Merge PRIMITIVES into getRenderEnv (island env was missing primitives)
- Rename web/signals.sx → web/web-signals.sx to avoid spec/ collision

New MCP tools:
- sx_trace: step-through CEK evaluation showing lookups, calls, returns
- sx_deps: dependency analysis — free symbols + cross-file resolution
- sx_build_manifest: show build contents for JS and OCaml targets
- sx_harness_eval extended: multi-file loading + setup expressions

Deep path bug fix:
- Native OCaml list-replace and navigate bypass CEK callback chain
- Fixes sx_replace_node and sx_read_subtree corruption on paths 6+ deep

Tests: 1478/1478 JS full suite, 91/91 Playwright

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 12:09:22 +00:00
parent 4e88b8a9dd
commit 5ed984e7e3
18 changed files with 3620 additions and 112 deletions

View File

@@ -99,6 +99,8 @@ def compile_ref_to_js(
spec_mod_set.add(sm)
if "dom" in adapter_set and "signals" in SPEC_MODULES:
spec_mod_set.add("signals")
if "signals-web" in SPEC_MODULES:
spec_mod_set.add("signals-web")
if "boot" in adapter_set:
spec_mod_set.add("router")
spec_mod_set.add("deps")

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Output JS build manifest as structured text for the MCP server."""
from __future__ import annotations
import json
import os
import re
import sys
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", ".."))
if _PROJECT not in sys.path:
sys.path.insert(0, _PROJECT)
from hosts.javascript.platform import (
ADAPTER_FILES, ADAPTER_DEPS, SPEC_MODULES, SPEC_MODULE_ORDER,
PRIMITIVES_JS_MODULES, _ALL_JS_MODULES, EXTENSION_NAMES,
)
def extract_primitives(js_code: str) -> list[str]:
"""Extract PRIMITIVES["name"] registrations from JS code."""
return sorted(set(re.findall(r'PRIMITIVES\["([^"]+)"\]', js_code)))
def main():
# Core spec files (always included)
core_files = [
"evaluator.sx (frames + eval + CEK)",
"freeze.sx (serializable state)",
"content.sx (content-addressed computation)",
"render.sx (core renderer)",
"web-forms.sx (defstyle, deftype, defeffect)",
]
# Adapters
adapter_lines = []
for name, (filename, label) in sorted(ADAPTER_FILES.items()):
deps = ADAPTER_DEPS.get(name, [])
dep_str = f" (deps: {', '.join(deps)})" if deps else ""
adapter_lines.append(f" {name:18s} {filename:22s} {label}{dep_str}")
# Spec modules
module_lines = []
for name in SPEC_MODULE_ORDER:
if name in SPEC_MODULES:
filename, label = SPEC_MODULES[name]
module_lines.append(f" {name:18s} {filename:22s} {label}")
# Extensions
ext_lines = [f" {name}" for name in sorted(EXTENSION_NAMES)]
# Primitive modules
prim_lines = []
for mod_name in sorted(_ALL_JS_MODULES):
if mod_name in PRIMITIVES_JS_MODULES:
prims = extract_primitives(PRIMITIVES_JS_MODULES[mod_name])
prim_lines.append(f" {mod_name} ({len(prims)}): {', '.join(prims)}")
# Current build file
build_path = os.path.join(_PROJECT, "shared", "static", "scripts", "sx-browser.js")
build_info = ""
if os.path.exists(build_path):
size = os.path.getsize(build_path)
mtime = os.path.getmtime(build_path)
from datetime import datetime
ts = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
# Count PRIMITIVES in actual build
with open(build_path) as f:
content = f.read()
actual_prims = extract_primitives(content)
build_info = f"\nCurrent build: {size:,} bytes, {ts}, {len(actual_prims)} primitives registered"
print(f"""JS Build Manifest
=================
{build_info}
Core files (always included):
{chr(10).join(' ' + f for f in core_files)}
Adapters ({len(ADAPTER_FILES)}):
{chr(10).join(adapter_lines)}
Spec modules ({len(SPEC_MODULES)}, order: {''.join(SPEC_MODULE_ORDER)}):
{chr(10).join(module_lines)}
Extensions ({len(EXTENSION_NAMES)}):
{chr(10).join(ext_lines)}
Primitive modules ({len(_ALL_JS_MODULES)}):
{chr(10).join(prim_lines)}""")
if __name__ == "__main__":
main()

View File

@@ -61,6 +61,7 @@ SPEC_MODULES = {
"deps": ("deps.sx", "deps (component dependency analysis)"),
"router": ("router.sx", "router (client-side route matching)"),
"signals": ("signals.sx", "signals (reactive signal runtime)"),
"signals-web": ("web-signals.sx", "signals-web (stores, events, resources)"),
"page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"),
"types": ("types.sx", "types (gradual type system)"),
"vm": ("vm.sx", "vm (bytecode virtual machine)"),
@@ -68,7 +69,7 @@ SPEC_MODULES = {
# Note: frames and cek are now part of evaluator.sx (always loaded as core)
# Explicit ordering for spec modules with dependencies.
SPEC_MODULE_ORDER = ["deps", "page-helpers", "router", "signals", "types", "vm"]
SPEC_MODULE_ORDER = ["deps", "page-helpers", "router", "signals", "signals-web", "types", "vm"]
EXTENSION_NAMES = {"continuations"}
@@ -2665,6 +2666,17 @@ PLATFORM_ORCHESTRATION_JS = """
: catchFn;
try { return t(); } catch (e) { return c(e); }
}
function cekTry(thunkFn, handlerFn) {
try {
var result = _wrapSxFn(thunkFn)();
if (!handlerFn || handlerFn === NIL) return [SYM("ok"), result];
return result;
} catch (e) {
var msg = (e && e.message) ? e.message : String(e);
if (handlerFn && handlerFn !== NIL) return _wrapSxFn(handlerFn)(msg);
return [SYM("error"), msg];
}
}
function errorMessage(e) {
return e && e.message ? e.message : String(e);
}
@@ -3077,7 +3089,7 @@ PLATFORM_BOOT_JS = """
}
function getRenderEnv(extraEnv) {
return extraEnv ? merge(componentEnv, extraEnv) : componentEnv;
return extraEnv ? merge(componentEnv, PRIMITIVES, extraEnv) : merge(componentEnv, PRIMITIVES);
}
function mergeEnvs(base, newEnv) {
@@ -3287,7 +3299,18 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False, has_deps=False, has_
try { localStorage.removeItem(key); } catch (e) {}
return NIL;
};
if (typeof sxParse === "function") PRIMITIVES["sx-parse"] = sxParse;''']
if (typeof sxParse === "function") PRIMITIVES["sx-parse"] = sxParse;
PRIMITIVES["cek-try"] = function(thunkFn, handlerFn) {
try {
var result = _wrapSxFn(thunkFn)();
if (!handlerFn || handlerFn === NIL) return [SYM("ok"), result];
return result;
} catch (e) {
var msg = (e && e.message) ? e.message : String(e);
if (handlerFn && handlerFn !== NIL) return _wrapSxFn(handlerFn)(msg);
return [SYM("error"), msg];
}
};''']
if has_deps:
lines.append('''
// Platform deps functions (native JS, not transpiled — need explicit registration)

View File

@@ -506,6 +506,7 @@
"to-kebab" "toKebab"
"log-info" "logInfo"
"log-warn" "logWarn"
"cek-try" "cekTry"
"log-parse-error" "logParseError"
"_page-routes" "_pageRoutes"
"process-page-scripts" "processPageScripts"

View File

@@ -177,6 +177,27 @@ let setup_env () =
| [f; List l] | [f; ListRef { contents = l }] ->
List (List.mapi (fun i x -> Sx_ref.cek_call f (List [Number (float_of_int i); x])) l)
| _ -> List []);
(* Native list-replace — bypasses CEK map-indexed callback chain for deep tree edits *)
bind "list-replace" (fun args -> match args with
| [List l; Number idx; v] ->
let i = int_of_float idx in
List (List.mapi (fun j x -> if j = i then v else x) l)
| [ListRef { contents = l }; Number idx; v] ->
let i = int_of_float idx in
List (List.mapi (fun j x -> if j = i then v else x) l)
| _ -> Nil);
(* Native navigate — bypasses CEK reduce callback chain for deep path reads *)
bind "navigate" (fun args -> match args with
| [tree; List path] | [tree; ListRef { contents = path }] ->
let nodes = match tree with List _ | ListRef _ -> tree | _ -> List [tree] in
List.fold_left (fun current idx ->
match current, idx with
| (List l | ListRef { contents = l }), Number n ->
let i = int_of_float n in
if i >= 0 && i < List.length l then List.nth l i else Nil
| _ -> Nil
) nodes path
| _ -> Nil);
bind "trim" (fun args -> match args with
| [String s] -> String (String.trim s) | _ -> String "");
bind "split" (fun args -> match args with
@@ -683,38 +704,93 @@ let rec handle_tool name args =
Filename.dirname spec_dir
in
let spec = args |> member "spec" |> to_string_option in
let spec_arg = match spec with Some s -> " " ^ s | None -> "" in
let cmd = Printf.sprintf "cd %s/tests/playwright && npx playwright test%s --reporter=line 2>&1" project_dir spec_arg in
let ic = Unix.open_process_in cmd in
let lines = ref [] in
(try while true do lines := input_line ic :: !lines done with End_of_file -> ());
ignore (Unix.close_process_in ic);
let all_lines = List.rev !lines in
let fails = List.filter (fun l -> let t = String.trim l in
String.length t > 1 && (t.[0] = '\xE2' (**) || (String.length t > 4 && String.sub t 0 4 = "FAIL"))) all_lines in
let summary = List.find_opt (fun l ->
try let _ = Str.search_forward (Str.regexp "passed\\|failed") l 0 in true
with Not_found -> false) (List.rev all_lines) in
let result = match summary with
| Some s ->
if fails = [] then s
else s ^ "\n\nFailures:\n" ^ String.concat "\n" fails
| None ->
let last_n = List.filteri (fun i _ -> i >= List.length all_lines - 10) all_lines in
String.concat "\n" last_n
let mode = args |> member "mode" |> to_string_option in
let url = args |> member "url" |> to_string_option in
let selector = args |> member "selector" |> to_string_option in
let expr = args |> member "expr" |> to_string_option in
let actions = args |> member "actions" |> to_string_option in
(* Determine whether to run specs or the inspector *)
let use_inspector = match mode with
| Some m when m <> "run" -> true
| _ -> spec = None && mode <> None
in
text_result result
if not use_inspector then begin
(* Original spec runner *)
let spec_arg = match spec with Some s -> " " ^ s | None -> "" in
let cmd = Printf.sprintf "cd %s/tests/playwright && npx playwright test%s --reporter=line 2>&1" project_dir spec_arg in
let ic = Unix.open_process_in cmd in
let lines = ref [] in
(try while true do lines := input_line ic :: !lines done with End_of_file -> ());
ignore (Unix.close_process_in ic);
let all_lines = List.rev !lines in
let fails = List.filter (fun l -> let t = String.trim l in
String.length t > 1 && (t.[0] = '\xE2' (**) || (String.length t > 4 && String.sub t 0 4 = "FAIL"))) all_lines in
let summary = List.find_opt (fun l ->
try let _ = Str.search_forward (Str.regexp "passed\\|failed") l 0 in true
with Not_found -> false) (List.rev all_lines) in
let result = match summary with
| Some s ->
if fails = [] then s
else s ^ "\n\nFailures:\n" ^ String.concat "\n" fails
| None ->
let last_n = List.filteri (fun i _ -> i >= List.length all_lines - 10) all_lines in
String.concat "\n" last_n
in
text_result result
end else begin
(* SX-aware inspector *)
let inspector_args = `Assoc (List.filter_map Fun.id [
(match mode with Some m -> Some ("mode", `String m) | None -> Some ("mode", `String "inspect"));
(match url with Some u -> Some ("url", `String u) | None -> None);
(match selector with Some s -> Some ("selector", `String s) | None -> None);
(match expr with Some e -> Some ("expr", `String e) | None -> None);
(match actions with Some a -> Some ("actions", `String a) | None -> None);
]) in
let args_json = Yojson.Safe.to_string (Yojson.Safe.from_string (Yojson.Basic.to_string inspector_args)) in
let cmd = Printf.sprintf "cd %s && node tests/playwright/sx-inspect.js '%s' 2>&1" project_dir (String.escaped args_json) in
let ic = Unix.open_process_in cmd in
let lines = ref [] in
(try while true do lines := input_line ic :: !lines done with End_of_file -> ());
ignore (Unix.close_process_in ic);
let raw = String.concat "\n" (List.rev !lines) in
(* Try to parse as JSON and format nicely *)
try
let json = Yojson.Basic.from_string raw in
let pretty = Yojson.Basic.pretty_to_string json in
text_result pretty
with _ ->
text_result raw
end
| "sx_harness_eval" ->
let expr_str = args |> member "expr" |> to_string in
let mock_str = args |> member "mock" |> to_string_option in
let file = args |> member "file" |> to_string_option in
let setup_str = args |> member "setup" |> to_string_option in
let files_json = try args |> member "files" with _ -> `Null in
let e = !env in
(* Optionally load a file's definitions *)
(match file with
| Some f ->
(try load_sx_file e f
with exn -> Printf.eprintf "[mcp] Warning: %s: %s\n%!" f (Printexc.to_string exn))
let warnings = ref [] in
(* Collect all files to load *)
let all_files = match files_json with
| `List items ->
List.map (fun j -> Yojson.Safe.Util.to_string j) items
| _ -> match file with Some f -> [f] | None -> []
in
(* Load each file *)
List.iter (fun f ->
try load_sx_file e f
with exn ->
warnings := Printf.sprintf "Warning: %s: %s" f (Printexc.to_string exn) :: !warnings
) all_files;
(* Run setup expression if provided *)
(match setup_str with
| Some s ->
let setup_exprs = Sx_parser.parse_all s in
List.iter (fun expr ->
try ignore (Sx_ref.eval_expr expr (Env e))
with exn ->
warnings := Printf.sprintf "Setup error: %s" (Printexc.to_string exn) :: !warnings
) setup_exprs
| None -> ());
(* Create harness with optional mock overrides *)
let mock_arg = match mock_str with
@@ -743,7 +819,10 @@ let rec handle_tool name args =
) items)
| _ -> "\n\n(no IO calls)"
in
text_result (Printf.sprintf "Result: %s%s" (Sx_types.inspect result) log_str)
let warn_str = if !warnings = [] then "" else
"\n\nWarnings:\n" ^ String.concat "\n" (List.rev !warnings)
in
text_result (Printf.sprintf "Result: %s%s%s" (Sx_types.inspect result) log_str warn_str)
| "sx_write_file" ->
let file = args |> member "file" |> to_string in
@@ -912,6 +991,219 @@ let rec handle_tool name args =
) Nil exprs in
text_result (Sx_runtime.value_to_str result)
| "sx_trace" ->
let expr_str = args |> member "expr" |> to_string in
let max_steps = (try args |> member "max_steps" |> to_int with _ -> 200) in
let file = try Some (args |> member "file" |> to_string) with _ -> None in
let e = !env in
(match file with
| Some f -> (try load_sx_file e f with _ -> ())
| None -> ());
let exprs = Sx_parser.parse_all expr_str in
let expr = match exprs with [e] -> e | _ -> List exprs in
let state = ref (Sx_ref.make_cek_state expr (Env e) (List [])) in
let steps = Buffer.create 2048 in
let step_count = ref 0 in
let truncate s n = if String.length s > n then String.sub s 0 n ^ "..." else s in
(try
while !step_count < max_steps do
let s = !state in
(match s with
| CekState cs ->
incr step_count;
let n = !step_count in
if cs.cs_phase = "eval" then begin
let ctrl = cs.cs_control in
(match ctrl with
| Symbol sym_name ->
let resolved = (try
let v = Sx_ref.eval_expr ctrl cs.cs_env in
truncate (Sx_runtime.value_to_str v) 60
with _ -> "???") in
Buffer.add_string steps
(Printf.sprintf "%3d LOOKUP %s → %s\n" n sym_name resolved)
| List (hd :: _) ->
let head_str = truncate (Sx_runtime.value_to_str hd) 30 in
let ctrl_str = truncate (Sx_runtime.value_to_str ctrl) 80 in
Buffer.add_string steps
(Printf.sprintf "%3d CALL %s\n" n ctrl_str);
ignore head_str
| _ ->
Buffer.add_string steps
(Printf.sprintf "%3d LITERAL %s\n" n
(truncate (Sx_runtime.value_to_str ctrl) 60)))
end else begin
(* continue phase *)
let val_str = truncate (Sx_runtime.value_to_str cs.cs_value) 60 in
let kont = cs.cs_kont in
let frame_type = match kont with
| List (Dict d :: _) ->
(match Hashtbl.find_opt d "type" with
| Some (String s) -> s | _ -> "?")
| List (CekState ks :: _) ->
(match ks.cs_control with
| Dict d ->
(match Hashtbl.find_opt d "type" with
| Some (String s) -> s | _ -> "?")
| _ -> "?")
| _ -> "done" in
Buffer.add_string steps
(Printf.sprintf "%3d RETURN %s → %s\n" n val_str frame_type)
end;
(match Sx_ref.cek_terminal_p s with
| Bool true -> raise Exit
| _ -> ());
state := Sx_ref.cek_step s
| _ -> raise Exit)
done
with
| Exit -> ()
| Eval_error msg ->
Buffer.add_string steps (Printf.sprintf "ERROR: %s\n" msg)
| exn ->
Buffer.add_string steps (Printf.sprintf "ERROR: %s\n" (Printexc.to_string exn)));
let final_val = (match !state with
| CekState cs -> Sx_runtime.value_to_str cs.cs_value
| v -> Sx_runtime.value_to_str v) in
text_result (Printf.sprintf "Result: %s\n\nTrace (%d steps):\n%s"
final_val !step_count (Buffer.contents steps))
| "sx_deps" ->
let file = args |> member "file" |> to_string in
let name = try Some (args |> member "name" |> to_string) with _ -> None in
let dir = try args |> member "dir" |> to_string with _ ->
try Sys.getenv "SX_PROJECT_DIR" with Not_found ->
try Sys.getenv "PWD" with Not_found -> "." in
let tree = parse_file file in
(* Find the target subtree *)
let target = match name with
| Some n ->
(* Find the named define/defcomp/defisland *)
let items = match tree with List l | ListRef { contents = l } -> l | _ -> [tree] in
let found = List.find_opt (fun item ->
match item with
| List (Symbol head :: Symbol def_name :: _)
| List (Symbol head :: List (Symbol def_name :: _) :: _)
when (head = "define" || head = "defcomp" || head = "defisland" ||
head = "defmacro" || head = "deftest") ->
def_name = n || ("~" ^ def_name) = n || def_name = String.sub n 1 (String.length n - 1)
| _ -> false
) items in
(match found with Some f -> f | None -> tree)
| None -> tree
in
let free_syms = call_sx "collect-free-symbols" [target] in
let sym_names = match free_syms with
| List items | ListRef { contents = items } ->
List.filter_map (fun v -> match v with String s -> Some s | _ -> None) items
| _ -> []
in
(* Resolve where each symbol is defined *)
let file_defines = Hashtbl.create 32 in
let same_file_items = match tree with List l | ListRef { contents = l } -> l | _ -> [] in
List.iter (fun item ->
match item with
| List (Symbol head :: Symbol def_name :: _)
when (head = "define" || head = "defcomp" || head = "defisland" || head = "defmacro") ->
Hashtbl.replace file_defines def_name true
| _ -> ()
) same_file_items;
(* Check primitives *)
let is_prim name = try ignore (Sx_primitives.get_primitive name); true with _ -> false in
(* Scan directory for definitions *)
let all_sx_files = glob_sx_files dir in
let ext_defs = Hashtbl.create 64 in
List.iter (fun path ->
if path <> file then
try
let t = parse_file path in
let items = match t with List l | ListRef { contents = l } -> l | _ -> [] in
List.iter (fun item ->
match item with
| List (Symbol head :: Symbol def_name :: _)
when (head = "define" || head = "defcomp" || head = "defisland" || head = "defmacro") ->
if not (Hashtbl.mem ext_defs def_name) then
Hashtbl.replace ext_defs def_name (relative_path ~base:dir path)
| _ -> ()
) items
with _ -> ()
) all_sx_files;
(* Format output *)
let lines = List.map (fun sym ->
if Hashtbl.mem file_defines sym then
Printf.sprintf " %-30s (same file)" sym
else if is_prim sym then
Printf.sprintf " %-30s [primitive]" sym
else match Hashtbl.find_opt ext_defs sym with
| Some path -> Printf.sprintf " %-30s %s" sym path
| None -> Printf.sprintf " %-30s ???" sym
) sym_names in
let header = match name with
| Some n -> Printf.sprintf "Dependencies of %s in %s" n file
| None -> Printf.sprintf "Dependencies of %s" file
in
text_result (Printf.sprintf "%s\n%d symbols referenced:\n%s"
header (List.length sym_names) (String.concat "\n" lines))
| "sx_build_manifest" ->
let target = (try args |> member "target" |> to_string with _ -> "js") in
(match target with
| "ocaml" ->
let e = !env in
(* Collect all bindings from the env *)
let bindings = ref [] in
(* Walk env chain collecting all bindings *)
let rec collect_bindings env acc =
Hashtbl.iter (fun k v ->
if not (Hashtbl.mem acc k) then Hashtbl.replace acc k v
) env.bindings;
match env.parent with Some p -> collect_bindings p acc | None -> ()
in
let all = Hashtbl.create 256 in
collect_bindings e all;
Hashtbl.iter (fun k v ->
let kind = match v with
| NativeFn _ -> "native"
| Lambda _ -> "lambda"
| Component _ -> "component"
| Island _ -> "island"
| Macro _ -> "macro"
| _ -> "value"
in
bindings := (k, kind) :: !bindings
) all;
let sorted = List.sort (fun (a,_) (b,_) -> String.compare a b) !bindings in
let by_kind = Hashtbl.create 8 in
List.iter (fun (name, kind) ->
let cur = try Hashtbl.find by_kind kind with Not_found -> [] in
Hashtbl.replace by_kind kind (name :: cur)
) sorted;
let sections = Buffer.create 2048 in
Buffer.add_string sections "OCaml Build Manifest\n====================\n\n";
Buffer.add_string sections (Printf.sprintf "Total bindings: %d\n\n" (List.length sorted));
Buffer.add_string sections "Loaded files: parser.sx, tree-tools.sx, harness.sx\n\n";
List.iter (fun kind ->
match Hashtbl.find_opt by_kind kind with
| Some names ->
let rev_names = List.rev names in
Buffer.add_string sections
(Printf.sprintf "%s (%d):\n %s\n\n" kind (List.length rev_names)
(String.concat ", " rev_names))
| None -> ()
) ["native"; "lambda"; "macro"; "component"; "island"; "value"];
text_result (Buffer.contents sections)
| _ ->
let project_dir = try Sys.getenv "SX_PROJECT_DIR" with Not_found ->
try Sys.getenv "PWD" with Not_found -> "." in
let cmd = Printf.sprintf "cd %s && python3 hosts/javascript/manifest.py 2>&1"
(Filename.quote project_dir) in
let ic = Unix.open_process_in cmd in
let buf = Buffer.create 4096 in
(try while true do Buffer.add_string buf (input_line ic ^ "\n") done
with End_of_file -> ());
ignore (Unix.close_process_in ic);
text_result (Buffer.contents buf))
| _ -> error_result ("Unknown tool: " ^ name)
and write_edit file result =
@@ -980,6 +1272,16 @@ let tool_definitions = `List [
[file_prop; path_prop; ("wrapper", `Assoc [("type", `String "string"); ("description", `String "Wrapper with _ placeholder")])] ["file"; "path"; "wrapper"];
tool "sx_eval" "Evaluate an SX expression. Environment has parser + tree-tools + primitives."
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to evaluate")])] ["expr"];
tool "sx_trace" "Step-through SX evaluation showing each CEK machine step (symbol lookups, function calls, returns). Useful for debugging."
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to trace")]);
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")]);
("max_steps", `Assoc [("type", `String "integer"); ("description", `String "Max CEK steps to show (default: 200)")])] ["expr"];
tool "sx_deps" "Dependency analysis for a component or file. Shows all referenced symbols and where they're defined."
[file_prop;
("name", `Assoc [("type", `String "string"); ("description", `String "Specific define/defcomp/defisland to analyze")]);
("dir", `Assoc [("type", `String "string"); ("description", `String "Directory to search for definitions (default: project root)")])] ["file"];
tool "sx_build_manifest" "Show build manifest: which modules, primitives, adapters, and exports are included in a JS or OCaml build."
[("target", `Assoc [("type", `String "string"); ("description", `String "Build target: \"js\" (default) or \"ocaml\"")])] [];
tool "sx_find_across" "Search for a pattern across all .sx files under a directory. Returns file paths, tree paths, and summaries."
[dir_prop; ("pattern", `Assoc [("type", `String "string"); ("description", `String "Search pattern")])] ["dir"; "pattern"];
tool "sx_comp_list" "List all definitions (defcomp, defisland, defmacro, defpage, define) across .sx files in a directory."
@@ -1042,13 +1344,20 @@ let tool_definitions = `List [
[file_prop; path_prop] ["file"];
tool "sx_doc_gen" "Generate component documentation from all defcomp/defisland/defmacro signatures in a directory."
[dir_prop] ["dir"];
tool "sx_harness_eval" "Evaluate SX in a test harness with mock IO. Returns result + IO trace. Use mock param to override default mock responses."
tool "sx_harness_eval" "Evaluate SX in a test harness with mock IO. Returns result + IO trace. Supports loading multiple files and setup expressions."
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to evaluate")]);
("mock", `Assoc [("type", `String "string"); ("description", `String "Optional mock platform overrides as SX dict, e.g. {:fetch (fn (url) {:status 200})}")]);
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")])]
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")]);
("files", `Assoc [("type", `String "array"); ("items", `Assoc [("type", `String "string")]); ("description", `String "Multiple .sx files to load in order")]);
("setup", `Assoc [("type", `String "string"); ("description", `String "SX setup expression to run before main evaluation")])]
["expr"];
tool "sx_playwright" "Run Playwright browser tests for the SX docs site. Optionally specify a single spec file."
[("spec", `Assoc [("type", `String "string"); ("description", `String "Optional spec file name (e.g. demo-interactions.spec.js)")])]
tool "sx_playwright" "Run Playwright browser tests or inspect SX pages interactively. Modes: run (spec files), inspect (page report), diff (SSR vs hydrated), eval (JS expression), interact (action sequence), screenshot."
[("spec", `Assoc [("type", `String "string"); ("description", `String "Spec file to run (run mode). e.g. stepper.spec.js")]);
("mode", `Assoc [("type", `String "string"); ("description", `String "Mode: run, inspect, diff, eval, interact, screenshot")]);
("url", `Assoc [("type", `String "string"); ("description", `String "URL path to navigate to (default: /)")]);
("selector", `Assoc [("type", `String "string"); ("description", `String "CSS selector to focus on (screenshot mode)")]);
("expr", `Assoc [("type", `String "string"); ("description", `String "JS expression to evaluate (eval mode)")]);
("actions", `Assoc [("type", `String "string"); ("description", `String "Semicolon-separated action sequence (interact mode). Actions: click:sel, fill:sel:val, wait:ms, text:sel, html:sel, attrs:sel, screenshot, screenshot:sel, count:sel, visible:sel")])]
[];
]

View File

@@ -672,4 +672,81 @@
(assert (not (nil? (get result "ok"))))
(let ((found (find-all (get result "ok") "second")))
(assert (>= (len found) 1))))))
)
(deftest "replace-node 6-level deep preserves full tree"
(let ((tree (sx-parse "(outer (a (b (c (d (e x y z) f g h i j k l) m n) o p) q r) s t)")))
(let ((result (replace-node tree (list 0 1 1 1 1 1 1) "replaced")))
(assert (not (nil? (get result "ok"))))
(let ((new-tree (get result "ok")))
(assert-equal "replaced" (symbol-name (navigate new-tree (list 0 1 1 1 1 1 1))))
(assert-equal 1 (len new-tree))
(let ((d-node (navigate new-tree (list 0 1 1 1 1))))
(assert-equal 9 (len d-node)))
(assert-equal "s" (symbol-name (navigate new-tree (list 0 2))))))))
(deftest "collect-free-symbols: let binds exclude variables"
(let ((tree (sx-parse "(let ((x 10)) (+ x y))")))
(let ((syms (collect-free-symbols (first tree))))
(assert (contains? syms "+"))
(assert (contains? syms "y"))
(assert (not (contains? syms "x"))))))
(deftest "collect-free-symbols: fn params excluded"
(let ((tree (sx-parse "(fn (a b) (+ a b c))")))
(let ((syms (collect-free-symbols (first tree))))
(assert (contains? syms "+"))
(assert (contains? syms "c"))
(assert (not (contains? syms "a")))
(assert (not (contains? syms "b"))))))
(deftest "collect-free-symbols: define name excluded"
(let ((tree (sx-parse "(define foo (+ bar 1))")))
(let ((syms (collect-free-symbols (first tree))))
(assert (contains? syms "+"))
(assert (contains? syms "bar"))
(assert (not (contains? syms "foo"))))))
(deftest "collect-free-symbols: letrec bindings visible in body"
(let ((tree (sx-parse "(letrec ((f (fn (x) (g x))) (g (fn (y) y))) (f 1))")))
(let ((syms (collect-free-symbols (first tree))))
(assert (not (contains? syms "f")))
(assert (not (contains? syms "g")))
(assert (not (contains? syms "x")))
(assert (not (contains? syms "y"))))))
(deftest "collect-free-symbols: quote skipped"
(let ((tree (sx-parse "(quote (foo bar baz))")))
(let ((syms (collect-free-symbols (first tree))))
(assert (empty? syms)))))
(deftest "collect-free-symbols: special forms not reported"
(let ((tree (sx-parse "(if (> x 0) (+ x 1) 0)")))
(let ((syms (collect-free-symbols (first tree))))
(assert (not (contains? syms "if")))
(assert (contains? syms ">"))
(assert (contains? syms "x"))
(assert (contains? syms "+")))))
(deftest "collect-free-symbols: defcomp params excluded"
(let ((tree (sx-parse "(defcomp ~card (&key title) (div title))")))
(let ((syms (collect-free-symbols (first tree))))
(assert (contains? syms "div"))
(assert (not (contains? syms "title"))))))
(deftest "native list-replace works at all positions"
(assert-equal (list 99 2 3) (list-replace (list 1 2 3) 0 99))
(assert-equal (list 1 99 3) (list-replace (list 1 2 3) 1 99))
(assert-equal (list 1 2 99) (list-replace (list 1 2 3) 2 99)))
(deftest "native navigate deep path"
(let ((tree (sx-parse "(a (b (c (d (e (f target))))))")))
(let ((result (navigate tree (list 0 1 1 1 1 1 1))))
(assert-equal "target" (symbol-name result)))))
(deftest "native navigate preserves structure"
(let ((tree (sx-parse "(outer (inner x y z) sibling)")))
(assert-equal "inner" (symbol-name (first (navigate tree (list 0 1)))))
(assert-equal "sibling" (symbol-name (navigate tree (list 0 2))))))
)

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-25T00:09:53Z";
var SX_VERSION = "2026-03-26T11:30:03Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -1376,7 +1376,8 @@ PRIMITIVES["parse-comp-params"] = parseCompParams;
var sfDefisland = function(args, env) { return (function() {
var nameSym = first(args);
var paramsRaw = nth(args, 1);
var body = last(args);
var bodyExprs = slice(args, 2);
var body = (isSxTruthy((len(bodyExprs) == 1)) ? first(bodyExprs) : cons(makeSymbol("begin"), bodyExprs));
var compName = stripPrefix(symbolName(nameSym), "~");
var parsed = parseCompParams(paramsRaw);
var params = first(parsed);
@@ -2892,7 +2893,7 @@ PRIMITIVES["render-html-marsh"] = renderHtmlMarsh;
envBind(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children))));
}
return (function() {
var bodyHtml = renderToHtml(componentBody(island), local);
var bodyHtml = cekTry(function() { return renderToHtml(componentBody(island), local); }, function(err) { return ""; });
var stateSx = serializeIslandState(kwargs);
return (String("<span data-sx-island=\"") + String(escapeAttr(islandName)) + String("\"") + String((isSxTruthy(stateSx) ? (String(" data-sx-state=\"") + String(escapeAttr(stateSx)) + String("\"")) : "")) + String(">") + String(bodyHtml) + String("</span>"));
})();
@@ -3149,6 +3150,530 @@ PRIMITIVES["aser-special"] = aserSpecial;
PRIMITIVES["eval-case-aser"] = evalCaseAser;
// === Transpiled from lib/dom (DOM library) ===
// dom-document
var domDocument = function() { return hostGlobal("document"); };
PRIMITIVES["dom-document"] = domDocument;
// dom-window
var domWindow = function() { return hostGlobal("window"); };
PRIMITIVES["dom-window"] = domWindow;
// dom-body
var domBody = function() { return hostGet(domDocument(), "body"); };
PRIMITIVES["dom-body"] = domBody;
// dom-head
var domHead = function() { return hostGet(domDocument(), "head"); };
PRIMITIVES["dom-head"] = domHead;
// dom-create-element
var domCreateElement = function(tag) { var nsArg = Array.prototype.slice.call(arguments, 1); return (function() {
var ns = (isSxTruthy((isSxTruthy(nsArg) && !isSxTruthy(isEmpty(nsArg)))) ? first(nsArg) : NIL);
return (isSxTruthy(ns) ? hostCall(domDocument(), "createElementNS", ns, tag) : hostCall(domDocument(), "createElement", tag));
})(); };
PRIMITIVES["dom-create-element"] = domCreateElement;
// create-text-node
var createTextNode = function(s) { return hostCall(domDocument(), "createTextNode", s); };
PRIMITIVES["create-text-node"] = createTextNode;
// create-fragment
var createFragment = function() { return hostCall(domDocument(), "createDocumentFragment"); };
PRIMITIVES["create-fragment"] = createFragment;
// create-comment
var createComment = function(text) { return hostCall(domDocument(), "createComment", sxOr(text, "")); };
PRIMITIVES["create-comment"] = createComment;
// dom-append
var domAppend = function(parent, child) { return (isSxTruthy((isSxTruthy(parent) && child)) ? hostCall(parent, "appendChild", child) : NIL); };
PRIMITIVES["dom-append"] = domAppend;
// dom-prepend
var domPrepend = function(parent, child) { return (isSxTruthy((isSxTruthy(parent) && child)) ? hostCall(parent, "prepend", child) : NIL); };
PRIMITIVES["dom-prepend"] = domPrepend;
// dom-insert-before
var domInsertBefore = function(parent, child, ref) { return (isSxTruthy((isSxTruthy(parent) && child)) ? hostCall(parent, "insertBefore", child, ref) : NIL); };
PRIMITIVES["dom-insert-before"] = domInsertBefore;
// dom-insert-after
var domInsertAfter = function(ref, node) { "Insert node after ref in the same parent.";
return (function() {
var parent = hostGet(ref, "parentNode");
var next = hostGet(ref, "nextSibling");
return (isSxTruthy(parent) ? (isSxTruthy(next) ? hostCall(parent, "insertBefore", node, next) : hostCall(parent, "appendChild", node)) : NIL);
})(); };
PRIMITIVES["dom-insert-after"] = domInsertAfter;
// dom-remove
var domRemove = function(el) { return (isSxTruthy(el) ? hostCall(el, "remove") : NIL); };
PRIMITIVES["dom-remove"] = domRemove;
// dom-is-active-element?
var domIsActiveElement = function(el) { return (function() {
var active = hostGet(domDocument(), "activeElement");
return (isSxTruthy((isSxTruthy(active) && el)) ? isIdentical(el, active) : false);
})(); };
PRIMITIVES["dom-is-active-element?"] = domIsActiveElement;
// dom-is-input-element?
var domIsInputElement = function(el) { return (function() {
var tag = upper(sxOr(domTagName(el), ""));
return sxOr((tag == "INPUT"), (tag == "TEXTAREA"), (tag == "SELECT"));
})(); };
PRIMITIVES["dom-is-input-element?"] = domIsInputElement;
// dom-is-child-of?
var domIsChildOf = function(child, parent) { return (isSxTruthy(child) && isSxTruthy(parent) && hostCall(parent, "contains", child)); };
PRIMITIVES["dom-is-child-of?"] = domIsChildOf;
// dom-attr-list
var domAttrList = function(el) { return (function() {
var attrs = hostGet(el, "attributes");
var result = [];
if (isSxTruthy(attrs)) {
(function() {
var n = hostGet(attrs, "length");
return (function loop(i) {
return (isSxTruthy((i < n)) ? ((function() {
var attr = hostCall(attrs, "item", i);
return append_b(result, [hostGet(attr, "name"), hostGet(attr, "value")]);
})(), loop((i + 1))) : NIL);
})(0);
})();
}
return result;
})(); };
PRIMITIVES["dom-attr-list"] = domAttrList;
// dom-remove-child
var domRemoveChild = function(parent, child) { return (isSxTruthy((isSxTruthy(parent) && child)) ? hostCall(parent, "removeChild", child) : NIL); };
PRIMITIVES["dom-remove-child"] = domRemoveChild;
// dom-replace-child
var domReplaceChild = function(parent, newChild, oldChild) { return (isSxTruthy((isSxTruthy(parent) && isSxTruthy(newChild) && oldChild)) ? hostCall(parent, "replaceChild", newChild, oldChild) : NIL); };
PRIMITIVES["dom-replace-child"] = domReplaceChild;
// dom-clone
var domClone = function(node, deep) { return hostCall(node, "cloneNode", (isSxTruthy(isNil(deep)) ? true : deep)); };
PRIMITIVES["dom-clone"] = domClone;
// dom-query
var domQuery = function(rootOrSel) { var rest = Array.prototype.slice.call(arguments, 1); return (isSxTruthy(isEmpty(rest)) ? hostCall(domDocument(), "querySelector", rootOrSel) : hostCall(rootOrSel, "querySelector", first(rest))); };
PRIMITIVES["dom-query"] = domQuery;
// dom-query-all
var domQueryAll = function(root, sel) { "Query DOM and return an SX list (not a host NodeList).";
return (function() {
var nodeList = (isSxTruthy(isNil(sel)) ? hostCall(domDocument(), "querySelectorAll", root) : hostCall(root, "querySelectorAll", sel));
return (isSxTruthy(isNil(nodeList)) ? [] : (function() {
var n = hostGet(nodeList, "length");
var result = [];
(function loop(i) {
return (isSxTruthy((i < n)) ? (append_b(result, hostCall(nodeList, "item", i)), loop((i + 1))) : NIL);
})(0);
return result;
})());
})(); };
PRIMITIVES["dom-query-all"] = domQueryAll;
// dom-query-by-id
var domQueryById = function(id) { return hostCall(domDocument(), "getElementById", id); };
PRIMITIVES["dom-query-by-id"] = domQueryById;
// dom-closest
var domClosest = function(el, sel) { return (isSxTruthy(el) ? hostCall(el, "closest", sel) : NIL); };
PRIMITIVES["dom-closest"] = domClosest;
// dom-matches?
var domMatches = function(el, sel) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "matches"))) ? hostCall(el, "matches", sel) : false); };
PRIMITIVES["dom-matches?"] = domMatches;
// dom-get-attr
var domGetAttr = function(el, name) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "getAttribute"))) ? (function() {
var v = hostCall(el, "getAttribute", name);
return (isSxTruthy(isNil(v)) ? NIL : v);
})() : NIL); };
PRIMITIVES["dom-get-attr"] = domGetAttr;
// dom-set-attr
var domSetAttr = function(el, name, val) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "setAttribute"))) ? hostCall(el, "setAttribute", name, val) : NIL); };
PRIMITIVES["dom-set-attr"] = domSetAttr;
// dom-remove-attr
var domRemoveAttr = function(el, name) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "removeAttribute"))) ? hostCall(el, "removeAttribute", name) : NIL); };
PRIMITIVES["dom-remove-attr"] = domRemoveAttr;
// dom-has-attr?
var domHasAttr = function(el, name) { return (isSxTruthy((isSxTruthy(el) && hostGet(el, "hasAttribute"))) ? hostCall(el, "hasAttribute", name) : false); };
PRIMITIVES["dom-has-attr?"] = domHasAttr;
// dom-add-class
var domAddClass = function(el, cls) { return (isSxTruthy(el) ? hostCall(hostGet(el, "classList"), "add", cls) : NIL); };
PRIMITIVES["dom-add-class"] = domAddClass;
// dom-remove-class
var domRemoveClass = function(el, cls) { return (isSxTruthy(el) ? hostCall(hostGet(el, "classList"), "remove", cls) : NIL); };
PRIMITIVES["dom-remove-class"] = domRemoveClass;
// dom-has-class?
var domHasClass = function(el, cls) { return (isSxTruthy(el) ? hostCall(hostGet(el, "classList"), "contains", cls) : false); };
PRIMITIVES["dom-has-class?"] = domHasClass;
// dom-text-content
var domTextContent = function(el) { return hostGet(el, "textContent"); };
PRIMITIVES["dom-text-content"] = domTextContent;
// dom-set-text-content
var domSetTextContent = function(el, val) { return hostSet(el, "textContent", val); };
PRIMITIVES["dom-set-text-content"] = domSetTextContent;
// dom-inner-html
var domInnerHtml = function(el) { return hostGet(el, "innerHTML"); };
PRIMITIVES["dom-inner-html"] = domInnerHtml;
// dom-set-inner-html
var domSetInnerHtml = function(el, val) { return hostSet(el, "innerHTML", val); };
PRIMITIVES["dom-set-inner-html"] = domSetInnerHtml;
// dom-outer-html
var domOuterHtml = function(el) { return hostGet(el, "outerHTML"); };
PRIMITIVES["dom-outer-html"] = domOuterHtml;
// dom-insert-adjacent-html
var domInsertAdjacentHtml = function(el, position, html) { return hostCall(el, "insertAdjacentHTML", position, html); };
PRIMITIVES["dom-insert-adjacent-html"] = domInsertAdjacentHtml;
// dom-get-style
var domGetStyle = function(el, prop) { return hostGet(hostGet(el, "style"), prop); };
PRIMITIVES["dom-get-style"] = domGetStyle;
// dom-set-style
var domSetStyle = function(el, prop, val) { return hostCall(hostGet(el, "style"), "setProperty", prop, val); };
PRIMITIVES["dom-set-style"] = domSetStyle;
// dom-get-prop
var domGetProp = function(el, name) { return hostGet(el, name); };
PRIMITIVES["dom-get-prop"] = domGetProp;
// dom-set-prop
var domSetProp = function(el, name, val) { return hostSet(el, name, val); };
PRIMITIVES["dom-set-prop"] = domSetProp;
// dom-tag-name
var domTagName = function(el) { return (isSxTruthy(el) ? lower(sxOr(hostGet(el, "tagName"), "")) : ""); };
PRIMITIVES["dom-tag-name"] = domTagName;
// dom-node-type
var domNodeType = function(el) { return hostGet(el, "nodeType"); };
PRIMITIVES["dom-node-type"] = domNodeType;
// dom-node-name
var domNodeName = function(el) { return hostGet(el, "nodeName"); };
PRIMITIVES["dom-node-name"] = domNodeName;
// dom-id
var domId = function(el) { return hostGet(el, "id"); };
PRIMITIVES["dom-id"] = domId;
// dom-parent
var domParent = function(el) { return hostGet(el, "parentNode"); };
PRIMITIVES["dom-parent"] = domParent;
// dom-first-child
var domFirstChild = function(el) { return hostGet(el, "firstChild"); };
PRIMITIVES["dom-first-child"] = domFirstChild;
// dom-next-sibling
var domNextSibling = function(el) { return hostGet(el, "nextSibling"); };
PRIMITIVES["dom-next-sibling"] = domNextSibling;
// dom-child-list
var domChildList = function(el) { "Return child nodes as an SX list.";
return (isSxTruthy(el) ? (function() {
var nl = hostGet(el, "childNodes");
var n = hostGet(nl, "length");
var result = [];
(function loop(i) {
return (isSxTruthy((i < n)) ? (append_b(result, hostCall(nl, "item", i)), loop((i + 1))) : NIL);
})(0);
return result;
})() : []); };
PRIMITIVES["dom-child-list"] = domChildList;
// dom-is-fragment?
var domIsFragment = function(el) { return (hostGet(el, "nodeType") == 11); };
PRIMITIVES["dom-is-fragment?"] = domIsFragment;
// dom-child-nodes
var domChildNodes = function(el) { "Return child nodes as an SX list.";
return (isSxTruthy(el) ? (function() {
var nl = hostGet(el, "childNodes");
var n = hostGet(nl, "length");
var result = [];
(function loop(i) {
return (isSxTruthy((i < n)) ? (append_b(result, hostCall(nl, "item", i)), loop((i + 1))) : NIL);
})(0);
return result;
})() : []); };
PRIMITIVES["dom-child-nodes"] = domChildNodes;
// dom-remove-children-after
var domRemoveChildrenAfter = function(marker) { "Remove all siblings after marker node.";
return (function() {
var parent = domParent(marker);
return (isSxTruthy(parent) ? (function loop() {
return (function() {
var next = domNextSibling(marker);
return (isSxTruthy(next) ? (hostCall(parent, "removeChild", next), loop()) : NIL);
})();
})() : NIL);
})(); };
PRIMITIVES["dom-remove-children-after"] = domRemoveChildrenAfter;
// dom-focus
var domFocus = function(el) { return (isSxTruthy(el) ? hostCall(el, "focus") : NIL); };
PRIMITIVES["dom-focus"] = domFocus;
// dom-parse-html
var domParseHtml = function(html) { return (function() {
var parser = hostNew("DOMParser");
var doc = hostCall(parser, "parseFromString", html, "text/html");
return hostGet(hostGet(doc, "body"), "childNodes");
})(); };
PRIMITIVES["dom-parse-html"] = domParseHtml;
// dom-listen
var domListen = function(el, eventName, handler) { return (function() {
var cb = hostCallback(handler);
hostCall(el, "addEventListener", eventName, cb);
return function() { return hostCall(el, "removeEventListener", eventName, cb); };
})(); };
PRIMITIVES["dom-listen"] = domListen;
// dom-add-listener
var domAddListener = function(el, eventName, handler) { var opts = Array.prototype.slice.call(arguments, 3); return (function() {
var cb = hostCallback(handler);
(isSxTruthy((isSxTruthy(opts) && !isSxTruthy(isEmpty(opts)))) ? hostCall(el, "addEventListener", eventName, cb, first(opts)) : hostCall(el, "addEventListener", eventName, cb));
return function() { return hostCall(el, "removeEventListener", eventName, cb); };
})(); };
PRIMITIVES["dom-add-listener"] = domAddListener;
// dom-dispatch
var domDispatch = function(el, eventName, detail) { return (function() {
var evt = hostNew("CustomEvent", eventName, {["detail"]: detail, ["bubbles"]: true});
return hostCall(el, "dispatchEvent", evt);
})(); };
PRIMITIVES["dom-dispatch"] = domDispatch;
// event-detail
var eventDetail = function(evt) { return hostGet(evt, "detail"); };
PRIMITIVES["event-detail"] = eventDetail;
// prevent-default
var preventDefault_ = function(e) { return (isSxTruthy(e) ? hostCall(e, "preventDefault") : NIL); };
PRIMITIVES["prevent-default"] = preventDefault_;
// stop-propagation
var stopPropagation_ = function(e) { return (isSxTruthy(e) ? hostCall(e, "stopPropagation") : NIL); };
PRIMITIVES["stop-propagation"] = stopPropagation_;
// event-modifier-key?
var eventModifierKey_p = function(e) { return (isSxTruthy(e) && sxOr(hostGet(e, "ctrlKey"), hostGet(e, "metaKey"), hostGet(e, "shiftKey"), hostGet(e, "altKey"))); };
PRIMITIVES["event-modifier-key?"] = eventModifierKey_p;
// element-value
var elementValue = function(el) { return (isSxTruthy((isSxTruthy(el) && !isSxTruthy(isNil(hostGet(el, "value"))))) ? hostGet(el, "value") : NIL); };
PRIMITIVES["element-value"] = elementValue;
// error-message
var errorMessage = function(e) { return (isSxTruthy((isSxTruthy(e) && hostGet(e, "message"))) ? hostGet(e, "message") : (String(e))); };
PRIMITIVES["error-message"] = errorMessage;
// dom-get-data
var domGetData = function(el, key) { return (function() {
var store = hostGet(el, "__sx_data");
return (isSxTruthy(store) ? hostGet(store, key) : NIL);
})(); };
PRIMITIVES["dom-get-data"] = domGetData;
// dom-set-data
var domSetData = function(el, key, val) { if (isSxTruthy(!isSxTruthy(hostGet(el, "__sx_data")))) {
hostSet(el, "__sx_data", {});
}
return hostSet(hostGet(el, "__sx_data"), key, val); };
PRIMITIVES["dom-set-data"] = domSetData;
// dom-append-to-head
var domAppendToHead = function(el) { return (isSxTruthy(domHead()) ? hostCall(domHead(), "appendChild", el) : NIL); };
PRIMITIVES["dom-append-to-head"] = domAppendToHead;
// set-document-title
var setDocumentTitle = function(title) { return hostSet(domDocument(), "title", title); };
PRIMITIVES["set-document-title"] = setDocumentTitle;
// === Transpiled from lib/browser (browser API library) ===
// browser-location-href
var browserLocationHref = function() { return hostGet(hostGet(domWindow(), "location"), "href"); };
PRIMITIVES["browser-location-href"] = browserLocationHref;
// browser-location-pathname
var browserLocationPathname = function() { return hostGet(hostGet(domWindow(), "location"), "pathname"); };
PRIMITIVES["browser-location-pathname"] = browserLocationPathname;
// browser-location-origin
var browserLocationOrigin = function() { return hostGet(hostGet(domWindow(), "location"), "origin"); };
PRIMITIVES["browser-location-origin"] = browserLocationOrigin;
// browser-same-origin?
var browserSameOrigin = function(url) { return startsWith(url, browserLocationOrigin()); };
PRIMITIVES["browser-same-origin?"] = browserSameOrigin;
// url-pathname
var urlPathname = function(url) { return hostGet(hostNew("URL", url, browserLocationOrigin()), "pathname"); };
PRIMITIVES["url-pathname"] = urlPathname;
// browser-push-state
var browserPushState = function(urlOrState) { var rest = Array.prototype.slice.call(arguments, 1); return (isSxTruthy(isEmpty(rest)) ? hostCall(hostGet(domWindow(), "history"), "pushState", NIL, "", urlOrState) : hostCall(hostGet(domWindow(), "history"), "pushState", urlOrState, first(rest), nth(rest, 1))); };
PRIMITIVES["browser-push-state"] = browserPushState;
// browser-replace-state
var browserReplaceState = function(urlOrState) { var rest = Array.prototype.slice.call(arguments, 1); return (isSxTruthy(isEmpty(rest)) ? hostCall(hostGet(domWindow(), "history"), "replaceState", NIL, "", urlOrState) : hostCall(hostGet(domWindow(), "history"), "replaceState", urlOrState, first(rest), nth(rest, 1))); };
PRIMITIVES["browser-replace-state"] = browserReplaceState;
// browser-reload
var browserReload = function() { return hostCall(hostGet(domWindow(), "location"), "reload"); };
PRIMITIVES["browser-reload"] = browserReload;
// browser-navigate
var browserNavigate = function(url) { return hostSet(hostGet(domWindow(), "location"), "href", url); };
PRIMITIVES["browser-navigate"] = browserNavigate;
// local-storage-get
var localStorageGet = function(key) { return hostCall(hostGet(domWindow(), "localStorage"), "getItem", key); };
PRIMITIVES["local-storage-get"] = localStorageGet;
// local-storage-set
var localStorageSet = function(key, val) { return hostCall(hostGet(domWindow(), "localStorage"), "setItem", key, val); };
PRIMITIVES["local-storage-set"] = localStorageSet;
// local-storage-remove
var localStorageRemove = function(key) { return hostCall(hostGet(domWindow(), "localStorage"), "removeItem", key); };
PRIMITIVES["local-storage-remove"] = localStorageRemove;
// set-timeout
var setTimeout_ = function(fnVal, ms) { return hostCall(domWindow(), "setTimeout", hostCallback(fnVal), ms); };
PRIMITIVES["set-timeout"] = setTimeout_;
// set-interval
var setInterval_ = function(fnVal, ms) { return hostCall(domWindow(), "setInterval", hostCallback(fnVal), ms); };
PRIMITIVES["set-interval"] = setInterval_;
// clear-timeout
var clearTimeout_ = function(id) { return hostCall(domWindow(), "clearTimeout", id); };
PRIMITIVES["clear-timeout"] = clearTimeout_;
// clear-interval
var clearInterval_ = function(id) { return hostCall(domWindow(), "clearInterval", id); };
PRIMITIVES["clear-interval"] = clearInterval_;
// request-animation-frame
var requestAnimationFrame_ = function(fnVal) { return hostCall(domWindow(), "requestAnimationFrame", hostCallback(fnVal)); };
PRIMITIVES["request-animation-frame"] = requestAnimationFrame_;
// fetch-request
var fetchRequest = function(url, opts) { return hostCall(domWindow(), "fetch", url, opts); };
PRIMITIVES["fetch-request"] = fetchRequest;
// new-abort-controller
var newAbortController = function() { return hostNew("AbortController"); };
PRIMITIVES["new-abort-controller"] = newAbortController;
// controller-signal
var controllerSignal = function(controller) { return hostGet(controller, "signal"); };
PRIMITIVES["controller-signal"] = controllerSignal;
// controller-abort
var controllerAbort = function(controller) { return hostCall(controller, "abort"); };
PRIMITIVES["controller-abort"] = controllerAbort;
// promise-then
var promiseThen = function(p, onResolve, onReject) { return (function() {
var cbResolve = hostCallback(onResolve);
var cbReject = (isSxTruthy(onReject) ? hostCallback(onReject) : NIL);
return (isSxTruthy(cbReject) ? hostCall(hostCall(p, "then", cbResolve), "catch", cbReject) : hostCall(p, "then", cbResolve));
})(); };
PRIMITIVES["promise-then"] = promiseThen;
// promise-resolve
var promiseResolve = function(val) { return hostCall(hostGlobal("Promise"), "resolve", val); };
PRIMITIVES["promise-resolve"] = promiseResolve;
// promise-delayed
var promiseDelayed = function(ms, val) { return hostNew("Promise", hostCallback(function(resolve) { return setTimeout_(function() { return hostCall(resolve, "call", NIL, val); }, ms); })); };
PRIMITIVES["promise-delayed"] = promiseDelayed;
// browser-confirm
var browserConfirm = function(msg) { return hostCall(domWindow(), "confirm", msg); };
PRIMITIVES["browser-confirm"] = browserConfirm;
// browser-prompt
var browserPrompt = function(msg, default_) { return hostCall(domWindow(), "prompt", msg, default_); };
PRIMITIVES["browser-prompt"] = browserPrompt;
// browser-media-matches?
var browserMediaMatches = function(query) { return hostGet(hostCall(domWindow(), "matchMedia", query), "matches"); };
PRIMITIVES["browser-media-matches?"] = browserMediaMatches;
// json-parse
var jsonParse = function(s) { return hostCall(hostGlobal("JSON"), "parse", s); };
PRIMITIVES["json-parse"] = jsonParse;
// log-info
var logInfo = function(msg) { return hostCall(hostGlobal("console"), "log", (String("[sx] ") + String(msg))); };
PRIMITIVES["log-info"] = logInfo;
// log-warn
var logWarn = function(msg) { return hostCall(hostGlobal("console"), "warn", (String("[sx] ") + String(msg))); };
PRIMITIVES["log-warn"] = logWarn;
// console-log
var consoleLog = function() { var args = Array.prototype.slice.call(arguments, 0); return hostCall(hostGlobal("console"), "log", join(" ", cons("[sx]", map(str, args)))); };
PRIMITIVES["console-log"] = consoleLog;
// now-ms
var nowMs = function() { return hostCall(hostGlobal("Date"), "now"); };
PRIMITIVES["now-ms"] = nowMs;
// schedule-idle
var scheduleIdle = function(f) { return (function() {
var cb = hostCallback(function(_deadline) { return f(); });
return (isSxTruthy(hostGet(domWindow(), "requestIdleCallback")) ? hostCall(domWindow(), "requestIdleCallback", cb) : setTimeout_(cb, 0));
})(); };
PRIMITIVES["schedule-idle"] = scheduleIdle;
// set-cookie
var setCookie = function(name, value, days) { return (function() {
var d = sxOr(days, 365);
var expires = hostCall(hostNew("Date", (hostCall(hostGlobal("Date"), "now") + (d * 86400000))), "toUTCString");
return hostSet(domDocument(), "cookie", (String(name) + String("=") + String(hostCall(NIL, "encodeURIComponent", value)) + String(";expires=") + String(expires) + String(";path=/;SameSite=Lax")));
})(); };
PRIMITIVES["set-cookie"] = setCookie;
// get-cookie
var getCookie = function(name) { return (function() {
var cookies = hostGet(domDocument(), "cookie");
var match = hostCall(cookies, "match", hostNew("RegExp", (String("(?:^|;\\s*)") + String(name) + String("=([^;]*)"))));
return (isSxTruthy(match) ? hostCall(NIL, "decodeURIComponent", hostGet(match, 1)) : NIL);
})(); };
PRIMITIVES["get-cookie"] = getCookie;
// === Transpiled from adapter-dom ===
// SVG_NS
@@ -3159,15 +3684,23 @@ PRIMITIVES["SVG_NS"] = SVG_NS;
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
PRIMITIVES["MATH_NS"] = MATH_NS;
// island-scope?
var islandScope_p = function() { return !isSxTruthy(isNil(scopePeek("sx-island-scope"))); };
PRIMITIVES["island-scope?"] = islandScope_p;
// contains-deref?
var containsDeref_p = function(expr) { return (isSxTruthy(!isSxTruthy(isList(expr))) ? false : (isSxTruthy(isEmpty(expr)) ? false : (isSxTruthy((isSxTruthy((typeOf(first(expr)) == "symbol")) && (symbolName(first(expr)) == "deref"))) ? true : some(containsDeref_p, expr)))); };
PRIMITIVES["contains-deref?"] = containsDeref_p;
// dom-on
var domOn = function(el, name, handler) { return domListen(el, name, (isSxTruthy(isLambda(handler)) ? (isSxTruthy((0 == len(lambdaParams(handler)))) ? function() { trampoline(callLambda(handler, []));
var domOn = function(el, name, handler) { return domListen(el, name, (isSxTruthy(isLambda(handler)) ? (isSxTruthy((0 == len(lambdaParams(handler)))) ? function(_e) { trampoline(callLambda(handler, []));
return runPostRenderHooks(); } : function(e) { trampoline(callLambda(handler, [e]));
return runPostRenderHooks(); }) : handler)); };
PRIMITIVES["dom-on"] = domOn;
// render-to-dom
var renderToDom = function(expr, env, ns) { setRenderActiveB(true);
return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(expr)), expr); if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); };
return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "spread") return ((isSxTruthy(!isSxTruthy(islandScope_p())) ? scopeEmit("element-attrs", spreadAttrs(expr)) : NIL), expr); if (_m == "dict") return (isSxTruthy(dictHas(expr, "__host_handle")) ? expr : createFragment()); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(islandScope_p()) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); };
PRIMITIVES["render-to-dom"] = renderToDom;
// render-dom-list
@@ -3176,13 +3709,18 @@ PRIMITIVES["render-to-dom"] = renderToDom;
return (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
var name = symbolName(head);
var args = rest(expr);
return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy((name == "lake")) ? renderDomLake(args, env, ns) : (isSxTruthy((name == "marsh")) ? renderDomMarsh(args, env, ns) : (isSxTruthy(startsWith(name, "html:")) ? renderDomElement(slice(name, 5), args, env, ns) : (isSxTruthy(isRenderDomForm(name)) ? (isSxTruthy((isSxTruthy(contains(HTML_TAGS, name)) && sxOr((isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword")), ns))) ? renderDomElement(name, args, env, ns) : dispatchRenderForm(name, expr, env, ns)) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToDom(expandMacro(envGet(env, name), args, env), env, ns) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderDomIsland(envGet(env, name), args, env, ns) : (isSxTruthy(startsWith(name, "~")) ? (function() {
return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy((name == "lake")) ? renderDomLake(args, env, ns) : (isSxTruthy((name == "marsh")) ? renderDomMarsh(args, env, ns) : (isSxTruthy(startsWith(name, "html:")) ? renderDomElement(slice(name, 5), args, env, ns) : (isSxTruthy(isRenderDomForm(name)) ? (isSxTruthy((isSxTruthy(contains(HTML_TAGS, name)) && sxOr((isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword")), ns))) ? renderDomElement(name, args, env, ns) : dispatchRenderForm(name, expr, env, ns)) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToDom(expandMacro(envGet(env, name), args, env), env, ns) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? (isSxTruthy(scopePeek("sx-render-markers")) ? (function() {
var island = envGet(env, name);
var marker = domCreateElement("span", NIL);
domSetAttr(marker, "data-sx-island", componentName(island));
return marker;
})() : renderDomIsland(envGet(env, name), args, env, ns)) : (isSxTruthy(startsWith(name, "~")) ? (function() {
var comp = envGet(env, name);
return (isSxTruthy(isComponent(comp)) ? renderDomComponent(comp, args, env, ns) : renderDomUnknownComponent(name));
})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && sxContext("sx-island-scope", NIL))) ? (function() {
})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && islandScope_p())) ? (function() {
var sigOrVal = trampoline(evalExpr(first(args), env));
return (isSxTruthy(isSignal(sigOrVal)) ? reactiveText(sigOrVal) : createTextNode((String(deref(sigOrVal)))));
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))))));
})() : (isSxTruthy((isSxTruthy(islandScope_p()) && containsDeref_p(expr))) ? reactiveText(computed(function() { return trampoline(evalExpr(expr, env)); })) : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))));
})() : (isSxTruthy(sxOr(isLambda(head), (typeOf(head) == "list"))) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (function() {
var frag = createFragment();
{ var _c = expr; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; (function() {
@@ -3216,14 +3754,14 @@ PRIMITIVES["render-dom-list"] = renderDomList;
})() : (isSxTruthy((attrName == "key")) ? (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return domSetAttr(el, "key", (String(attrVal)));
})() : (isSxTruthy(sxContext("sx-island-scope", NIL)) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() {
})() : (isSxTruthy(islandScope_p()) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return (isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal))))));
})())))));
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? (function() {
var child = renderToDom(arg, env, newNs);
return (isSxTruthy((isSxTruthy(isSpread(child)) && sxContext("sx-island-scope", NIL))) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? NIL : domAppend(el, child)));
return (isSxTruthy((isSxTruthy(isSpread(child)) && islandScope_p())) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? NIL : domAppend(el, child)));
})() : NIL), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
{ var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; { var _c = keys(spreadDict); for (var _i = 0; _i < _c.length; _i++) { var key = _c[_i]; (function() {
@@ -3298,7 +3836,7 @@ PRIMITIVES["render-dom-raw"] = renderDomRaw;
PRIMITIVES["render-dom-unknown-component"] = renderDomUnknownComponent;
// RENDER_DOM_FORMS
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary", "scope", "provide"];
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "letrec", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary", "scope", "provide", "cyst"];
PRIMITIVES["RENDER_DOM_FORMS"] = RENDER_DOM_FORMS;
// render-dom-form?
@@ -3306,7 +3844,7 @@ PRIMITIVES["RENDER_DOM_FORMS"] = RENDER_DOM_FORMS;
PRIMITIVES["render-dom-form?"] = isRenderDomForm;
// dispatch-render-form
var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(islandScope_p()) ? (function() {
var marker = createComment("r-if");
var currentNodes = [];
var initialResult = NIL;
@@ -3329,7 +3867,7 @@ PRIMITIVES["render-dom-form?"] = isRenderDomForm;
})() : (function() {
var condVal = trampoline(evalExpr(nth(expr, 1), env));
return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment()));
})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(islandScope_p()) ? (function() {
var marker = createComment("r-when");
var currentNodes = [];
var initialResult = NIL;
@@ -3356,7 +3894,7 @@ PRIMITIVES["render-dom-form?"] = isRenderDomForm;
var frag = createFragment();
{ var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
return frag;
})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(islandScope_p()) ? (function() {
var marker = createComment("r-cond");
var currentNodes = [];
var initialResult = NIL;
@@ -3393,6 +3931,22 @@ PRIMITIVES["render-dom-form?"] = isRenderDomForm;
})(); } }
return frag;
})());
})() : (isSxTruthy((name == "letrec")) ? (function() {
var bindings = nth(expr, 1);
var body = slice(expr, 2);
var local = envExtend(env);
{ var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; (function() {
var pname = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair))));
return envBind(local, pname, NIL);
})(); } }
{ var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; (function() {
var pname = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair))));
return envSet(local, pname, trampoline(evalExpr(nth(pair, 1), local)));
})(); } }
if (isSxTruthy((len(body) > 1))) {
{ var _c = init(body); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } }
}
return renderToDom(last(body), local, ns);
})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (isSxTruthy((len(expr) == 2)) ? renderToDom(nth(expr, 1), env, ns) : (function() {
var frag = createFragment();
{ var _c = range(1, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; (function() {
@@ -3402,7 +3956,7 @@ PRIMITIVES["render-dom-form?"] = isRenderDomForm;
return frag;
})()) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), createFragment()) : (isSxTruthy((name == "map")) ? (function() {
var collExpr = nth(expr, 2);
return (isSxTruthy((isSxTruthy(sxContext("sx-island-scope", NIL)) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && isSxTruthy((typeOf(first(collExpr)) == "symbol")) && (symbolName(first(collExpr)) == "deref"))) ? (function() {
return (isSxTruthy((isSxTruthy(islandScope_p()) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && isSxTruthy((typeOf(first(collExpr)) == "symbol")) && (symbolName(first(collExpr)) == "deref"))) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var sig = trampoline(evalExpr(nth(collExpr, 1), env));
return (isSxTruthy(isSignal(sig)) ? reactiveList(f, sig, env, ns) : (function() {
@@ -3461,7 +4015,27 @@ PRIMITIVES["render-dom-form?"] = isRenderDomForm;
{ var _c = range(3, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
scopePop(provName);
return frag;
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))))); };
})() : (isSxTruthy((name == "cyst")) ? (function() {
var cystKey = (isSxTruthy((isSxTruthy((len(expr) > 2)) && isSxTruthy((typeOf(nth(expr, 1)) == "keyword")) && (keywordName(nth(expr, 1)) == "key"))) ? (String(trampoline(evalExpr(nth(expr, 2), env)))) : nextCystId());
var cached = get(_memoCache_, cystKey);
return (isSxTruthy((isSxTruthy(cached) && hostGet(cached, "isConnected"))) ? cached : (function() {
var container = domCreateElement("div", NIL);
var disposers = [];
var bodyExprs = (isSxTruthy((isSxTruthy((len(expr) > 2)) && isSxTruthy((typeOf(nth(expr, 1)) == "keyword")) && (keywordName(nth(expr, 1)) == "key"))) ? slice(expr, 3) : slice(expr, 1));
domSetAttr(container, "data-sx-cyst", cystKey);
return (function() {
var bodyDom = withIslandScope(function(d) { return append_b(disposers, d); }, function() { return (function() {
var frag = createFragment();
{ var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } }
return frag;
})(); });
domAppend(container, bodyDom);
domSetData(container, "sx-disposers", disposers);
_memoCache_[cystKey] = container;
return container;
})();
})());
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))))))); };
PRIMITIVES["dispatch-render-form"] = dispatchRenderForm;
// render-lambda-dom
@@ -3946,7 +4520,7 @@ PRIMITIVES["revert-optimistic"] = revertOptimistic;
PRIMITIVES["find-oob-swaps"] = findOobSwaps;
// morph-node
var morphNode = function(oldNode, newNode) { return (isSxTruthy(sxOr(domHasAttr(oldNode, "sx-preserve"), domHasAttr(oldNode, "sx-ignore"))) ? NIL : (isSxTruthy((isSxTruthy(domHasAttr(oldNode, "data-sx-island")) && isSxTruthy(isProcessed(oldNode, "island-hydrated")) && isSxTruthy(domHasAttr(newNode, "data-sx-island")) && (domGetAttr(oldNode, "data-sx-island") == domGetAttr(newNode, "data-sx-island")))) ? morphIslandChildren(oldNode, newNode) : (isSxTruthy(sxOr(!isSxTruthy((domNodeType(oldNode) == domNodeType(newNode))), !isSxTruthy((domNodeName(oldNode) == domNodeName(newNode))))) ? domReplaceChild(domParent(oldNode), domClone(newNode), oldNode) : (isSxTruthy(sxOr((domNodeType(oldNode) == 3), (domNodeType(oldNode) == 8))) ? (isSxTruthy(!isSxTruthy((domTextContent(oldNode) == domTextContent(newNode)))) ? domSetTextContent(oldNode, domTextContent(newNode)) : NIL) : (isSxTruthy((domNodeType(oldNode) == 1)) ? ((isSxTruthy((isSxTruthy(domHasAttr(oldNode, "data-sx-island")) && isSxTruthy(domHasAttr(newNode, "data-sx-island")) && !isSxTruthy((domGetAttr(oldNode, "data-sx-island") == domGetAttr(newNode, "data-sx-island"))))) ? (disposeIslandsIn(oldNode), clearProcessed(oldNode, "island-hydrated")) : NIL), syncAttrs(oldNode, newNode), (isSxTruthy(!isSxTruthy((isSxTruthy(domIsActiveElement(oldNode)) && domIsInputElement(oldNode)))) ? morphChildren(oldNode, newNode) : NIL)) : NIL))))); };
var morphNode = function(oldNode, newNode) { return (isSxTruthy(sxOr(domHasAttr(oldNode, "sx-preserve"), domHasAttr(oldNode, "sx-ignore"))) ? NIL : (isSxTruthy((isSxTruthy(domHasAttr(oldNode, "data-sx-island")) && isSxTruthy(isProcessed(oldNode, "island-hydrated")) && isSxTruthy(domHasAttr(newNode, "data-sx-island")) && (domGetAttr(oldNode, "data-sx-island") == domGetAttr(newNode, "data-sx-island")))) ? morphIslandChildren(oldNode, newNode) : (isSxTruthy(sxOr(!isSxTruthy((domNodeType(oldNode) == domNodeType(newNode))), !isSxTruthy((domNodeName(oldNode) == domNodeName(newNode))))) ? domReplaceChild(domParent(oldNode), domClone(newNode), oldNode) : (isSxTruthy(sxOr((domNodeType(oldNode) == 3), (domNodeType(oldNode) == 8))) ? (isSxTruthy(!isSxTruthy((domTextContent(oldNode) == domTextContent(newNode)))) ? domSetTextContent(oldNode, domTextContent(newNode)) : NIL) : (isSxTruthy((domNodeType(oldNode) == 1)) ? ((isSxTruthy((isSxTruthy(domHasAttr(oldNode, "data-sx-island")) && isSxTruthy(domHasAttr(newNode, "data-sx-island")) && !isSxTruthy((domGetAttr(oldNode, "data-sx-island") == domGetAttr(newNode, "data-sx-island"))))) ? (disposeIsland(oldNode), disposeIslandsIn(oldNode)) : NIL), syncAttrs(oldNode, newNode), (isSxTruthy(!isSxTruthy((isSxTruthy(domIsActiveElement(oldNode)) && domIsInputElement(oldNode)))) ? morphChildren(oldNode, newNode) : NIL)) : NIL))))); };
PRIMITIVES["morph-node"] = morphNode;
// sync-attrs
@@ -4073,14 +4647,15 @@ PRIMITIVES["process-signal-updates"] = processSignalUpdates;
return morphChildren(target, wrapper);
})()); if (_m == "outerHTML") return (function() {
var parent = domParent(target);
var newEl = domClone(newNodes);
(isSxTruthy(domIsFragment(newNodes)) ? (function() {
var fc = domFirstChild(newNodes);
return (isSxTruthy(fc) ? (morphNode(target, fc), (function() {
return (isSxTruthy(fc) ? ((newEl = domClone(fc)), domReplaceChild(parent, newEl, target), (function() {
var sib = domNextSibling(fc);
return insertRemainingSiblings(parent, target, sib);
return insertRemainingSiblings(parent, newEl, sib);
})()) : domRemoveChild(parent, target));
})() : morphNode(target, newNodes));
return parent;
})() : domReplaceChild(parent, newEl, target));
return newEl;
})(); if (_m == "afterend") return domInsertAfter(target, newNodes); if (_m == "beforeend") return domAppend(target, newNodes); if (_m == "afterbegin") return domPrepend(target, newNodes); if (_m == "beforebegin") return domInsertBefore(domParent(target), newNodes, target); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() {
var wrapper = domCreateElement("div", NIL);
domAppend(wrapper, newNodes);
@@ -4297,8 +4872,10 @@ return processElements(t); });
var selectSel = domGetAttr(el, "sx-select");
var content = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container));
disposeIslandsIn(target);
return withTransition(useTransition, function() { swapDomNodes(target, content, swapStyle);
return postSwap(target); });
return withTransition(useTransition, function() { return (function() {
var swapResult = swapDomNodes(target, content, swapStyle);
return postSwap(sxOr(swapResult, target));
})(); });
})();
})() : NIL);
})();
@@ -5034,7 +5611,14 @@ PRIMITIVES["sx-hydrate-islands"] = sxHydrateIslands;
var local = envMerge(componentClosure(comp), env);
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envBind(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
return (function() {
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); });
var bodyDom = cekTry(function() { return withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); }); }, function(err) { logWarn((String("hydrate-island FAILED: ") + String(compName) + String(" — ") + String(err)));
return (function() {
var errorEl = domCreateElement("div", NIL);
domSetAttr(errorEl, "class", "sx-island-error");
domSetAttr(errorEl, "style", "padding:8px;margin:4px 0;border:1px solid #ef4444;border-radius:4px;background:#fef2f2;color:#b91c1c;font-family:monospace;font-size:12px;white-space:pre-wrap");
domSetTextContent(errorEl, (String("Island error: ") + String(compName) + String("\n") + String(err)));
return errorEl;
})(); });
domSetTextContent(el, "");
domAppend(el, bodyDom);
domSetData(el, "sx-disposers", disposers);
@@ -5093,13 +5677,13 @@ PRIMITIVES["register-post-render-hook"] = registerPostRenderHook;
PRIMITIVES["run-pre-render-hooks"] = runPreRenderHooks;
// run-post-render-hooks
var runPostRenderHooks = function() { logInfo("run-post-render-hooks:", len(_postRenderHooks_), "hooks");
return forEach(function(hook) { logInfo(" hook type:", typeOf(hook), "callable:", isCallable(hook), "lambda:", isLambda(hook));
var runPostRenderHooks = function() { logInfo((String("run-post-render-hooks: ") + String(len(_postRenderHooks_)) + String(" hooks")));
return forEach(function(hook) { logInfo((String(" hook type: ") + String(typeOf(hook)) + String(" callable: ") + String(isCallable(hook)) + String(" lambda: ") + String(isLambda(hook))));
return cekCall(hook, NIL); }, _postRenderHooks_); };
PRIMITIVES["run-post-render-hooks"] = runPostRenderHooks;
// boot-init
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), runPostRenderHooks(), processElements(NIL)); };
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), runPostRenderHooks(), processElements(NIL), domListen(domWindow(), "popstate", function(e) { return handlePopstate(0); })); };
PRIMITIVES["boot-init"] = bootInit;
@@ -5811,7 +6395,7 @@ PRIMITIVES["reset!"] = reset_b;
// swap!
var swap_b = function(s, f) { var args = Array.prototype.slice.call(arguments, 2); return (isSxTruthy(isSignal(s)) ? (function() {
var old = signalValue(s);
var newVal = apply(f, cons(old, args));
var newVal = trampoline(apply(f, cons(old, args)));
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? (signalSetValue(s, newVal), notifySubscribers(s)) : NIL);
})() : NIL); };
PRIMITIVES["swap!"] = swap_b;
@@ -5926,11 +6510,14 @@ PRIMITIVES["with-island-scope"] = withIslandScope;
// register-in-scope
var registerInScope = function(disposable) { return (function() {
var collector = sxContext("sx-island-scope", NIL);
var collector = scopePeek("sx-island-scope");
return (isSxTruthy(collector) ? cekCall(collector, [disposable]) : NIL);
})(); };
PRIMITIVES["register-in-scope"] = registerInScope;
// === Transpiled from signals-web (stores, events, resources) ===
// with-marsh-scope
var withMarshScope = function(marshEl, bodyFn) { return (function() {
var disposers = [];
@@ -6088,6 +6675,17 @@ PRIMITIVES["resource"] = resource;
return NIL;
};
if (typeof sxParse === "function") PRIMITIVES["sx-parse"] = sxParse;
PRIMITIVES["cek-try"] = function(thunkFn, handlerFn) {
try {
var result = _wrapSxFn(thunkFn)();
if (!handlerFn || handlerFn === NIL) return [SYM("ok"), result];
return result;
} catch (e) {
var msg = (e && e.message) ? e.message : String(e);
if (handlerFn && handlerFn !== NIL) return _wrapSxFn(handlerFn)(msg);
return [SYM("error"), msg];
}
};
// Platform deps functions (native JS, not transpiled — need explicit registration)
PRIMITIVES["component-deps"] = componentDeps;
@@ -6961,6 +7559,17 @@ PRIMITIVES["resource"] = resource;
: catchFn;
try { return t(); } catch (e) { return c(e); }
}
function cekTry(thunkFn, handlerFn) {
try {
var result = _wrapSxFn(thunkFn)();
if (!handlerFn || handlerFn === NIL) return [SYM("ok"), result];
return result;
} catch (e) {
var msg = (e && e.message) ? e.message : String(e);
if (handlerFn && handlerFn !== NIL) return _wrapSxFn(handlerFn)(msg);
return [SYM("error"), msg];
}
}
function errorMessage(e) {
return e && e.message ? e.message : String(e);
}
@@ -7371,7 +7980,7 @@ PRIMITIVES["resource"] = resource;
}
function getRenderEnv(extraEnv) {
return extraEnv ? merge(componentEnv, extraEnv) : componentEnv;
return extraEnv ? merge(componentEnv, PRIMITIVES, extraEnv) : merge(componentEnv, PRIMITIVES);
}
function mergeEnvs(base, newEnv) {

View File

@@ -0,0 +1,8 @@
;; Navigation items for the Applications section
(define protocols-nav-items (list (dict :label "Wire Format" :href "/sx/(applications.(protocol.wire-format))") (dict :label "Fragments" :href "/sx/(applications.(protocol.fragments))") (dict :label "Resolver I/O" :href "/sx/(applications.(protocol.resolver-io))") (dict :label "Internal Services" :href "/sx/(applications.(protocol.internal-services))") (dict :label "ActivityPub" :href "/sx/(applications.(protocol.activitypub))") (dict :label "Future" :href "/sx/(applications.(protocol.future))")))
(define cssx-nav-items (list (dict :label "Overview" :href "/sx/(applications.(cssx))") (dict :label "Patterns" :href "/sx/(applications.(cssx.patterns))") (dict :label "Delivery" :href "/sx/(applications.(cssx.delivery))") (dict :label "Async CSS" :href "/sx/(applications.(cssx.async))") (dict :label "Live Styles" :href "/sx/(applications.(cssx.live))") (dict :label "Comparisons" :href "/sx/(applications.(cssx.comparison))") (dict :label "Philosophy" :href "/sx/(applications.(cssx.philosophy))")))
(define reactive-runtime-nav-items (list (dict :label "Ref" :href "/sx/(applications.(reactive-runtime.ref))") (dict :label "Foreign FFI" :href "/sx/(applications.(reactive-runtime.foreign))") (dict :label "State Machines" :href "/sx/(applications.(reactive-runtime.machine))") (dict :label "Commands" :href "/sx/(applications.(reactive-runtime.commands))") (dict :label "Render Loop" :href "/sx/(applications.(reactive-runtime.loop))") (dict :label "Keyed Lists" :href "/sx/(applications.(reactive-runtime.keyed-lists))") (dict :label "App Shell" :href "/sx/(applications.(reactive-runtime.app-shell))")))

8
sx/sx/nav-etc.sx Normal file

File diff suppressed because one or more lines are too long

16
sx/sx/nav-geography.sx Normal file
View File

@@ -0,0 +1,16 @@
;; Navigation items for the Geography section
(define reference-nav-items (list (dict :label "Attributes" :href "/sx/(geography.(hypermedia.(reference.attributes)))") (dict :label "Headers" :href "/sx/(geography.(hypermedia.(reference.headers)))") (dict :label "Events" :href "/sx/(geography.(hypermedia.(reference.events)))") (dict :label "JS API" :href "/sx/(geography.(hypermedia.(reference.js-api)))")))
(define examples-nav-items (list (dict :label "Click to Load" :href "/sx/(geography.(hypermedia.(example.click-to-load)))") (dict :label "Form Submission" :href "/sx/(geography.(hypermedia.(example.form-submission)))") (dict :label "Polling" :href "/sx/(geography.(hypermedia.(example.polling)))") (dict :label "Delete Row" :href "/sx/(geography.(hypermedia.(example.delete-row)))") (dict :label "Inline Edit" :href "/sx/(geography.(hypermedia.(example.inline-edit)))") (dict :label "OOB Swaps" :href "/sx/(geography.(hypermedia.(example.oob-swaps)))") (dict :label "Lazy Loading" :href "/sx/(geography.(hypermedia.(example.lazy-loading)))") (dict :label "Infinite Scroll" :href "/sx/(geography.(hypermedia.(example.infinite-scroll)))") (dict :label "Progress Bar" :href "/sx/(geography.(hypermedia.(example.progress-bar)))") (dict :label "Active Search" :href "/sx/(geography.(hypermedia.(example.active-search)))") (dict :label "Inline Validation" :href "/sx/(geography.(hypermedia.(example.inline-validation)))") (dict :label "Value Select" :href "/sx/(geography.(hypermedia.(example.value-select)))") (dict :label "Reset on Submit" :href "/sx/(geography.(hypermedia.(example.reset-on-submit)))") (dict :label "Edit Row" :href "/sx/(geography.(hypermedia.(example.edit-row)))") (dict :label "Bulk Update" :href "/sx/(geography.(hypermedia.(example.bulk-update)))") (dict :label "Swap Positions" :href "/sx/(geography.(hypermedia.(example.swap-positions)))") (dict :label "Select Filter" :href "/sx/(geography.(hypermedia.(example.select-filter)))") (dict :label "Tabs" :href "/sx/(geography.(hypermedia.(example.tabs)))") (dict :label "Animations" :href "/sx/(geography.(hypermedia.(example.animations)))") (dict :label "Dialogs" :href "/sx/(geography.(hypermedia.(example.dialogs)))") (dict :label "Keyboard Shortcuts" :href "/sx/(geography.(hypermedia.(example.keyboard-shortcuts)))") (dict :label "PUT / PATCH" :href "/sx/(geography.(hypermedia.(example.put-patch)))") (dict :label "JSON Encoding" :href "/sx/(geography.(hypermedia.(example.json-encoding)))") (dict :label "Vals & Headers" :href "/sx/(geography.(hypermedia.(example.vals-and-headers)))") (dict :label "Loading States" :href "/sx/(geography.(hypermedia.(example.loading-states)))") (dict :label "Request Abort" :href "/sx/(geography.(hypermedia.(example.sync-replace)))") (dict :label "Retry" :href "/sx/(geography.(hypermedia.(example.retry)))")))
(define isomorphism-nav-items (list (dict :label "Roadmap" :href "/sx/(geography.(isomorphism))") (dict :label "Bundle Analyzer" :href "/sx/(geography.(isomorphism.bundle-analyzer))") (dict :label "Routing Analyzer" :href "/sx/(geography.(isomorphism.routing-analyzer))") (dict :label "Data Test" :href "/sx/(geography.(isomorphism.data-test))") (dict :label "Async IO" :href "/sx/(geography.(isomorphism.async-io))") (dict :label "Streaming" :href "/sx/(geography.(isomorphism.streaming))") (dict :label "Affinity" :href "/sx/(geography.(isomorphism.affinity))") (dict :label "Optimistic" :href "/sx/(geography.(isomorphism.optimistic))") (dict :label "Offline" :href "/sx/(geography.(isomorphism.offline))")))
(define cek-nav-items (list (dict :label "Overview" :href "/sx/(geography.(cek))" :summary "The CEK machine — explicit evaluator with Control, Environment, Kontinuation. Three registers, pure step function.") (dict :label "Demo" :href "/sx/(geography.(cek.demo))" :summary "Live islands evaluated by the CEK machine. Counter, computed chains, reactive attributes — all through explicit continuation frames.") (dict :label "Freeze / Thaw" :href "/sx/(geography.(cek.freeze))" :summary "Serialize a CEK state to s-expressions. Ship it, store it, content-address it. Thaw and resume anywhere.") (dict :label "Content Addressing" :href "/sx/(geography.(cek.content))" :summary "Hash frozen state to a CID. Same state = same address. Store, share, verify, reproduce.")))
(define reactive-examples-nav-items (list {:href "/sx/(geography.(reactive.(examples.counter)))" :label "Counter"} {:href "/sx/(geography.(reactive.(examples.temperature)))" :label "Temperature"} {:href "/sx/(geography.(reactive.(examples.stopwatch)))" :label "Stopwatch"} {:href "/sx/(geography.(reactive.(examples.imperative)))" :label "Imperative"} {:href "/sx/(geography.(reactive.(examples.reactive-list)))" :label "Reactive List"} {:href "/sx/(geography.(reactive.(examples.input-binding)))" :label "Input Binding"} {:href "/sx/(geography.(reactive.(examples.portal)))" :label "Portals"} {:href "/sx/(geography.(reactive.(examples.error-boundary)))" :label "Error Boundary"} {:href "/sx/(geography.(reactive.(examples.refs)))" :label "Refs"} {:href "/sx/(geography.(reactive.(examples.dynamic-class)))" :label "Dynamic Class"} {:href "/sx/(geography.(reactive.(examples.resource)))" :label "Resource"} {:href "/sx/(geography.(reactive.(examples.transition)))" :label "Transitions"} {:href "/sx/(geography.(reactive.(examples.stores)))" :label "Stores"} {:href "/sx/(geography.(reactive.(examples.event-bridge-demo)))" :label "Event Bridge"} {:href "/sx/(geography.(reactive.(examples.defisland)))" :label "defisland"} {:href "/sx/(geography.(reactive.(examples.tests)))" :label "Tests"} {:href "/sx/(geography.(reactive.(examples.coverage)))" :label "Coverage"} {:href "/sx/(geography.(reactive.(examples.cyst)))" :label "Cyst"} {:href "/sx/(geography.(reactive.(examples.reactive-expressions)))" :label "Reactive Expressions"}))
(define reactive-islands-nav-items (list (dict :label "Examples" :href "/sx/(geography.(reactive.(examples)))" :summary "Live interactive islands — click the buttons, type in the inputs." :children reactive-examples-nav-items)))
(define marshes-examples-nav-items (list {:href "/sx/(geography.(marshes.hypermedia-feeds))" :label "Hypermedia Feeds State"} {:href "/sx/(geography.(marshes.server-signals))" :label "Server Writes to Signals"} {:href "/sx/(geography.(marshes.on-settle))" :label "sx-on-settle"} {:href "/sx/(geography.(marshes.signal-triggers))" :label "Signal-Bound Triggers"} {:href "/sx/(geography.(marshes.view-transform))" :label "Reactive View Transform"}))

26
sx/sx/nav-language.sx Normal file
View File

@@ -0,0 +1,26 @@
;; Navigation items for the Language section (docs, specs, testing, bootstrappers)
(define docs-nav-items (list (dict :label "Introduction" :href "/sx/(language.(doc.introduction))") (dict :label "Getting Started" :href "/sx/(language.(doc.getting-started))") (dict :label "Components" :href "/sx/(language.(doc.components))") (dict :label "Evaluator" :href "/sx/(language.(doc.evaluator))") (dict :label "Primitives" :href "/sx/(language.(doc.primitives))") (dict :label "Special Forms" :href "/sx/(language.(doc.special-forms))") (dict :label "Server Rendering" :href "/sx/(language.(doc.server-rendering))")))
(define specs-nav-items (list {:href "/sx/(language.(spec.core))" :children (list {:href "/sx/(language.(spec.parser))" :label "Parser"} {:href "/sx/(language.(spec.evaluator))" :label "Evaluator"} {:href "/sx/(language.(spec.primitives))" :label "Primitives"} {:href "/sx/(language.(spec.special-forms))" :label "Special Forms"} {:href "/sx/(language.(spec.renderer))" :label "Renderer"}) :label "Core"} {:href "/sx/(language.(spec.adapters))" :children (list {:href "/sx/(language.(spec.adapter-dom))" :label "DOM Adapter"} {:href "/sx/(language.(spec.adapter-html))" :label "HTML Adapter"} {:href "/sx/(language.(spec.adapter-sx))" :label "SX Wire Adapter"} {:href "/sx/(language.(spec.adapter-async))" :label "Async Adapter"}) :label "Adapters"} {:href "/sx/(language.(spec.browser))" :children (list {:href "/sx/(language.(spec.engine))" :label "SxEngine"} {:href "/sx/(language.(spec.orchestration))" :label "Orchestration"} {:href "/sx/(language.(spec.boot))" :label "Boot"} {:href "/sx/(language.(spec.router))" :label "Router"}) :label "Browser"} {:href "/sx/(language.(spec.reactive))" :children (list {:href "/sx/(language.(spec.signals))" :label "Signals"} {:href "/sx/(language.(spec.frames))" :label "CEK Frames"} {:href "/sx/(language.(spec.cek))" :label "CEK Machine"}) :label "Reactive"} {:href "/sx/(language.(spec.host))" :children (list {:href "/sx/(language.(spec.boundary))" :label "Boundary"} {:href "/sx/(language.(spec.forms))" :label "Forms"} {:href "/sx/(language.(spec.page-helpers))" :label "Page Helpers"}) :label "Host Interface"} {:href "/sx/(language.(spec.extensions))" :children (list {:href "/sx/(language.(spec.continuations))" :label "Continuations"} {:href "/sx/(language.(spec.callcc))" :label "call/cc"} {:href "/sx/(language.(spec.types))" :label "Types"} {:href "/sx/(language.(spec.deps))" :label "Deps"}) :label "Extensions"}))
(define testing-nav-items (list (dict :label "Overview" :href "/sx/(language.(test))") (dict :label "Evaluator" :href "/sx/(language.(test.eval))") (dict :label "Parser" :href "/sx/(language.(test.parser))") (dict :label "Router" :href "/sx/(language.(test.router))") (dict :label "Renderer" :href "/sx/(language.(test.render))") (dict :label "Dependencies" :href "/sx/(language.(test.deps))") (dict :label "Engine" :href "/sx/(language.(test.engine))") (dict :label "Orchestration" :href "/sx/(language.(test.orchestration))") (dict :label "Runners" :href "/sx/(language.(test.runners))")))
(define bootstrappers-nav-items (list (dict :label "Overview" :href "/sx/(language.(bootstrapper))") (dict :label "JavaScript" :href "/sx/(language.(bootstrapper.javascript))") (dict :label "Python" :href "/sx/(language.(bootstrapper.python))") (dict :label "Self-Hosting (py.sx)" :href "/sx/(language.(bootstrapper.self-hosting))") (dict :label "Self-Hosting JS (js.sx)" :href "/sx/(language.(bootstrapper.self-hosting-js))") (dict :label "Page Helpers" :href "/sx/(language.(bootstrapper.page-helpers))")))
(define core-spec-items (list (dict :slug "parser" :filename "parser.sx" :title "Parser" :desc "Tokenization and parsing of SX source text into AST." :prose "The parser converts SX source text into an abstract syntax tree. It tokenizes the input into atoms, strings, numbers, keywords, and delimiters, then assembles them into nested list structures. The parser is intentionally minimal — s-expressions need very little syntax to parse. Special reader macros handle quasiquote (\\`), unquote (~), splice (~@), and the quote (') shorthand. The output is a tree of plain lists, symbols, keywords, strings, and numbers that the evaluator can walk directly.") (dict :slug "evaluator" :filename "evaluator.sx" :title "Evaluator" :desc "CEK machine evaluator." :prose "The evaluator walks the AST produced by the parser and reduces it to values. It implements lexical scoping with closures, special forms (define, let, if, cond, fn, defcomp, defmacro, quasiquote, set!, do), and function application. Macros are expanded at eval time. Component definitions (defcomp) create callable component objects that participate in the rendering pipeline. The evaluator delegates rendering expressions — HTML tags, components, fragments — to whichever adapter is active, making the same source renderable to DOM nodes, HTML strings, or SX wire format.") (dict :slug "primitives" :filename "primitives.sx" :title "Primitives" :desc "All built-in pure functions and their signatures." :prose "Primitives are the built-in functions available in every SX environment. Each entry declares a name, parameter signature, and semantics. Bootstrap compilers implement these natively per target (JavaScript, Python, etc.). The registry covers arithmetic, comparison, string manipulation, list operations, dict operations, type predicates, and control flow helpers. All primitives are pure — they take values and return values with no side effects. Platform-specific operations (DOM access, HTTP, file I/O) are provided separately via platform bridge functions, not primitives.") (dict :slug "special-forms" :filename "special-forms.sx" :title "Special Forms" :desc "All special forms — syntactic constructs with custom evaluation rules." :prose "Special forms are the syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface. The registry covers control flow (if, when, cond, case, and, or), binding (let, letrec, define, set!), functions (lambda, defcomp, defmacro), sequencing (begin, do, thread-first), quoting (quote, quasiquote), continuations (reset, shift), guards (dynamic-wind), higher-order forms (map, filter, reduce), and domain-specific definitions (defstyle, defhandler, defpage, defquery, defaction).") (dict :slug "renderer" :filename "render.sx" :title "Renderer" :desc "Shared rendering registries and utilities used by all adapters." :prose "The renderer defines what is renderable and how arguments are parsed, but not the output format. It maintains registries of known HTML tags, SVG tags, void elements, and boolean attributes. It specifies how keyword arguments on elements become HTML attributes, how children are collected, and how special attributes (class, style, data-*) are handled. All three adapters (DOM, HTML, SX wire) share these definitions so they agree on what constitutes valid markup.")))
(define adapter-spec-items (list (dict :slug "adapter-dom" :filename "adapter-dom.sx" :title "DOM Adapter" :desc "Renders SX expressions to live DOM nodes. Browser-only." :prose "The DOM adapter renders evaluated SX expressions into live browser DOM nodes — Elements, Text nodes, and DocumentFragments. It mirrors the HTML adapter's logic but produces DOM objects instead of strings. This is the adapter used by the browser-side SX runtime for initial mount, hydration, and dynamic updates. It handles element creation, attribute setting (including event handlers and style objects), SVG namespace handling, and fragment composition.") (dict :slug "adapter-html" :filename "adapter-html.sx" :title "HTML Adapter" :desc "Renders SX expressions to HTML strings. Server-side." :prose "The HTML adapter renders evaluated SX expressions to HTML strings. It is used server-side to produce complete HTML pages and fragments. It handles void elements (self-closing tags like <br>, <img>), boolean attributes, style serialization, class merging, and proper escaping. The output is standard HTML5 that any browser can parse.") (dict :slug "adapter-sx" :filename "adapter-sx.sx" :title "SX Wire Adapter" :desc "Serializes SX for client-side rendering. Component calls stay unexpanded." :prose "The SX wire adapter serializes expressions as SX source text for transmission to the browser, where sx.js renders them client-side. Unlike the HTML adapter, component calls (~plans/content-addressed-components/name ...) are NOT expanded — they are sent to the client as-is, allowing the browser to render them with its local component registry. HTML tags ARE serialized as s-expression source. This is the format used for SX-over-HTTP responses and the page boot payload.") (dict :slug "adapter-async" :filename "adapter-async.sx" :title "Async Adapter" :desc "Async versions of HTML and SX wire adapters for server-side rendering with I/O." :prose "The async adapter provides async-aware versions of the HTML and SX wire rendering functions. It intercepts I/O operations (database queries, service calls, fragment fetches) during evaluation, awaiting them before continuing. Entry points: async-render (HTML output with awaited I/O), async-aser (SX wire format with awaited I/O). The bootstrapper emits async def and automatic await insertion for all define-async functions. This adapter is what makes server-side SX pages work with real data.")))
(define browser-spec-items (list (dict :slug "engine" :filename "engine.sx" :title "SxEngine" :desc "Pure logic for fetch, swap, history, SSE, triggers, morph, and indicators." :prose "The engine specifies the pure logic of the browser-side fetch/swap/history system. Like HTMX but native to SX. It defines trigger parsing (click, submit, intersect, poll, load, revealed), swap algorithms (innerHTML, outerHTML, morph, beforebegin, etc.), the morph/diff algorithm for patching existing DOM, history management (push-url, replace-url, popstate), out-of-band swap identification, Server-Sent Events parsing, retry logic with exponential backoff, request header building, response header processing, and optimistic UI updates. This file contains no browser API calls — all platform interaction is in orchestration.sx.") (dict :slug "orchestration" :filename "orchestration.sx" :title "Orchestration" :desc "Browser wiring that binds engine logic to DOM events, fetch, and lifecycle." :prose "Orchestration is the browser wiring layer. It binds the pure engine logic to actual browser APIs: DOM event listeners, fetch(), AbortController, setTimeout/setInterval, IntersectionObserver, history.pushState, and EventSource (SSE). It implements the full request lifecycle — from trigger through fetch through swap — including CSS tracking, response type detection (SX vs HTML), OOB swap processing, script activation, element boosting, and preload. Dependency is strictly one-way: orchestration depends on engine, never the reverse.") (dict :slug "boot" :filename "boot.sx" :title "Boot" :desc "Browser startup lifecycle: mount, hydrate, script processing." :prose "Boot handles the browser startup sequence and provides the public API for mounting SX content. On page load it: (1) initializes CSS tracking, (2) processes <script type=\"text/sx\"> tags (component definitions and mount directives), (3) hydrates [data-sx] elements, and (4) activates the engine on all elements. It also provides the public mount/hydrate/update/render-component API, and the head element hoisting logic that moves <meta>, <title>, and <link> tags from rendered content into <head>.") (dict :slug "router" :filename "router.sx" :title "Router" :desc "Client-side route matching — Flask-style pattern parsing, segment matching, route table search." :prose "The router module provides pure functions for matching URL paths against Flask-style route patterns (e.g. /docs/<slug>). Used by client-side routing to determine if a page can be rendered locally without a server roundtrip. split-path-segments breaks a path into segments, parse-route-pattern converts patterns into typed segment descriptors, match-route-segments tests a path against a parsed pattern returning extracted params, and find-matching-route searches a route table for the first match.")))
(define reactive-spec-items (list (dict :slug "signals" :filename "signals.sx" :title "Signals" :desc "Fine-grained reactive primitives — signal, computed, effect, batch." :prose "The signals module defines a fine-grained reactive system for client-side islands. Signals are containers for values that notify subscribers on change. Computed signals derive values lazily from other signals. Effects run side-effects when their dependencies change, with automatic cleanup. Batch coalesces multiple signal writes into a single notification pass. Island scope management ensures all signals, computeds, and effects are cleaned up when an island is removed from the DOM. The spec defines the reactive graph topology and update algorithm — each platform implements the actual signal/tracking types natively.") (dict :slug "frames" :filename "frames.sx" :title "CEK Frames" :desc "Continuation frame types for the explicit CEK machine." :prose "Frames define what to do next when a sub-evaluation completes. Each frame type is a dict with a type key and frame-specific data. IfFrame, WhenFrame, BeginFrame handle control flow. LetFrame, DefineFrame, SetFrame handle bindings. ArgFrame tracks function call argument evaluation. MapFrame, FilterFrame, ReduceFrame, ForEachFrame drive higher-order forms element by element through the CEK machine. ReactiveResetFrame and DerefFrame enable deref-as-shift — the core reactive mechanism where deref inside a reactive boundary captures the continuation as a signal subscriber.") (dict :slug "cek" :filename "cek.sx" :title "CEK Machine" :desc "Explicit CEK machine evaluator — step function, run loop, reactive shift." :prose "The CEK machine makes evaluation explicit. Every step is a pure function from state to state: Control (expression), Environment (bindings), Kontinuation (stack of frames). step-eval dispatches on expression type — literals pass through, symbols are looked up, lists dispatch on head (special forms, higher-order forms, macros, function calls). step-continue dispatches on the top frame type. Higher-order forms (map, filter, reduce, for-each, some, every?) step element by element, so deref-as-shift works inside callbacks. cek-call replaces invoke as the universal function dispatch — lambdas go through cek-run, native callables through apply.")))
(define host-spec-items (list (dict :slug "boundary" :filename "boundary.sx" :title "Boundary" :desc "Language boundary contract — declares I/O primitives the host must provide." :prose "The boundary defines the contract between SX and its host environment. Tier 1 declares pure primitives (from primitives.sx). Tier 2 declares async I/O primitives the host must implement: fetch, async-eval, call-action, send-activity, and other operations that require network or database access. Tier 3 declares page helpers: format, highlight, scan-css-classes, parse-datetime. This is the interface every host must satisfy to run SX — framework-agnostic, universal to all targets. Boundary enforcement validates at registration time that all declared primitives are provided.") (dict :slug "forms" :filename "forms.sx" :title "Forms" :desc "Server-side definition forms — defhandler, defquery, defaction, defpage." :prose "Forms defines the server-side definition macros that compose the application layer. defhandler registers an HTTP route handler. defquery defines a read-only data source. defaction defines a mutation (write). defpage declares a client-routable page with path, auth, layout, data dependencies, and content. Each form parses &key parameter lists and creates typed definition objects. Platform-specific constructors are provided by the host — these have different bindings on server (Python/Quart) vs client (route matching only).") (dict :slug "page-helpers" :filename "page-helpers.sx" :title "Page Helpers" :desc "Pure data-transformation helpers for page rendering." :prose "Page helpers are pure functions that assist page rendering: categorizing special forms by type, formatting numbers and dates, highlighting code, scanning CSS classes, constructing page titles and descriptions. Unlike boundary I/O primitives, these are pure — they take data and return data with no side effects. They run identically on server and client. The host registers native implementations that match these declarations.")))
(define extension-spec-items (list (dict :slug "continuations" :filename "continuations.sx" :title "Continuations" :desc "Delimited continuations — shift/reset for suspendable rendering and cooperative scheduling." :prose "Delimited continuations capture the rest of a computation up to a delimiter. shift captures the continuation to the nearest reset as a first-class callable value. Unlike full call/cc, delimited continuations are composable — invoking one returns a value. This covers the practical use cases: suspendable server rendering, cooperative scheduling, linear async flows, wizard-style multi-step UIs, and undo. Each bootstrapper target implements the mechanism differently — generators in Python/JS, native shift/reset in Scheme, ContT in Haskell, CPS transform in Rust — but the semantics are identical. Optional extension: code that doesn't use continuations pays zero cost.") (dict :slug "callcc" :filename "callcc.sx" :title "call/cc" :desc "Full first-class continuations — call-with-current-continuation." :prose "Full call/cc captures the entire remaining computation as a first-class function — not just up to a delimiter, but all the way to the top level. Invoking the continuation abandons the current computation entirely and resumes from where it was captured. Strictly more powerful than delimited continuations, but harder to implement in targets that don't support it natively. Recommended for Scheme and Haskell targets where it's natural. Python, JavaScript, and Rust targets should prefer delimited continuations (continuations.sx) unless full escape semantics are genuinely needed. Optional extension: the continuation type is shared with continuations.sx if both are loaded.") (dict :slug "types" :filename "types.sx" :title "Types" :desc "Gradual type system — registration-time checking with zero runtime cost." :prose "The types module defines a gradual type system for SX. Type annotations on function parameters and return values are checked at registration time (when defcomp or define is evaluated), not at every call site. Base types include number, string, boolean, nil, symbol, keyword, element, any, and never. Union types (string|nil), function types, and type narrowing through control flow are supported. The system catches composition errors and boundary mismatches at definition time without any runtime overhead — unannotated code is unaffected.") (dict :slug "deps" :filename "deps.sx" :title "Deps" :desc "Component dependency analysis and IO detection — per-page bundling, transitive closure, CSS scoping." :prose "The deps module analyzes component dependency graphs and classifies components as pure or IO-dependent. Phase 1 (bundling): walks component AST bodies to find transitive ~component references, computes the minimal set needed per page, and collects per-page CSS classes from only the used components. Phase 2 (IO detection): scans component ASTs for references to IO primitive names (from boundary.sx declarations), computes transitive IO refs through the component graph, and caches the result. Components with no transitive IO refs are pure — they can render anywhere without server data. IO-dependent components must expand server-side.")))
(define all-spec-items (concat core-spec-items (concat adapter-spec-items (concat browser-spec-items (concat reactive-spec-items (concat host-spec-items extension-spec-items))))))
(define find-spec (fn (slug) (some (fn (item) (when (= (get item "slug") slug) item)) all-spec-items)))

6
sx/sx/nav-tools.sx Normal file
View File

@@ -0,0 +1,6 @@
;; Navigation items for the Tools section
(define tools-nav-items
(list
(dict :label "SX Tools" :href "/sx/(tools.(sx-tools))")
(dict :label "Services" :href "/sx/(tools.(services))")))

16
sx/sx/nav-tree.sx Normal file
View File

@@ -0,0 +1,16 @@
;; Navigation tree structure and utility functions
(define find-current (fn (items slug) (when slug (some (fn (item) (when (ends-with? (get item "href") (str "." slug "))")) (get item "label"))) items))))
(defcomp ~nav-data/section-nav (&key items current) (<> (map (fn (item) (~shared:layout/nav-link :href (get item "href") :label (get item "label") :is-selected (when (= (get item "label") current) "true") :select-colours "aria-selected:bg-violet-200 aria-selected:text-violet-900")) items)))
(define sx-nav-tree {:href "/sx/" :children (list {:href "/sx/(geography)" :children (list {:href "/sx/(geography.(reactive))" :children reactive-islands-nav-items :label "Reactive Islands"} {:href "/sx/(geography.(hypermedia))" :children (list {:href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items :label "Reference"} {:href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items :label "Examples"}) :label "Hypermedia Lakes"} {:href "/sx/(geography.(scopes))" :summary "The unified primitive beneath provide, collect!, spreads, and islands. Named scope with downward value, upward accumulation, and a dedup flag." :label "Scopes"} {:href "/sx/(geography.(provide))" :summary "Sugar for scope-with-value. Render-time dynamic scope — the substrate beneath spreads, CSSX, and script collection." :label "Provide / Emit!"} {:href "/sx/(geography.(spreads))" :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, built on scopes." :label "Spreads"} {:href "/sx/(geography.(marshes))" :children marshes-examples-nav-items :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted." :label "Marshes"} {:href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items :label "Isomorphism"} {:href "/sx/(geography.(cek))" :children cek-nav-items :label "CEK Machine"}) :label "Geography"} {:href "/sx/(language)" :children (list {:href "/sx/(language.(doc))" :children docs-nav-items :label "Docs"} {:href "/sx/(language.(spec))" :children specs-nav-items :label "Specs"} {:href "/sx/(language.(spec.(explore.evaluator)))" :label "Spec Explorer"} {:href "/sx/(language.(bootstrapper))" :children bootstrappers-nav-items :label "Bootstrappers"} {:href "/sx/(language.(test))" :children testing-nav-items :label "Testing"}) :label "Language"} {:href "/sx/(applications)" :children (list {:href "/sx/(applications.(sx-urls))" :label "SX URLs"} {:href "/sx/(applications.(cssx))" :children cssx-nav-items :label "CSSX"} {:href "/sx/(applications.(protocol))" :children protocols-nav-items :label "Protocols"} {:href "/sx/(applications.(sx-pub))" :label "sx-pub"} {:href "/sx/(applications.(reactive-runtime))" :children reactive-runtime-nav-items :label "Reactive Runtime"}) :label "Applications"} {:href "/sx/(tools)" :children tools-nav-items :label "Tools"} {:href "/sx/(etc)" :children (list {:href "/sx/(etc.(essay))" :children essays-nav-items :label "Essays"} {:href "/sx/(etc.(philosophy))" :children philosophy-nav-items :label "Philosophy"} {:href "/sx/(etc.(plan))" :children plans-nav-items :label "Plans"}) :label "Etc"}) :label "sx"})
(define has-descendant-href? (fn (node path) (let ((children (get node "children"))) (when children (some (fn (child) (or (= (get child "href") path) (has-descendant-href? child path))) children)))))
(define find-nav-match (fn (items path) (or (some (fn (item) (when (= (get item "href") path) item)) items) (some (fn (item) (when (has-descendant-href? item path) item)) items))))
(define resolve-nav-path (fn (tree path) (let ((trail (list))) (define walk (fn (node) (let ((children (get node "children"))) (when children (let ((match (find-nav-match children path))) (when match (append! trail {:siblings children :node match}) (when (not (= (get match "href") path)) (walk match)))))))) (walk tree) (let ((depth (len trail))) (if (= depth 0) {:children (get tree "children") :depth 0 :trail trail} (let ((deepest (nth trail (- depth 1)))) {:children (get (get deepest "node") "children") :depth depth :trail trail}))))))
(define find-nav-index (fn (items node) (let ((target-href (get node "href")) (count (len items))) (define find-loop (fn (i) (if (>= i count) 0 (if (= (get (nth items i) "href") target-href) i (find-loop (+ i 1)))))) (find-loop 0))))

View File

@@ -58,6 +58,10 @@
(define sx-tools (fn (&key title &rest args) (quasiquote (~sx-tools/overview-content :title (unquote (or title "SX Tools")) (splice-unquote args)))))
(define tools (fn (content) (if (nil? content) nil content)))
(define services (fn (&key title &rest args) (quasiquote (~services-tools/overview-content :title (unquote (or title "Services")) (splice-unquote args)))))
(define reactive-runtime (make-page-fn "~reactive-runtime/overview-content" "~reactive-runtime/" nil "-content"))
(define essay (make-page-fn "~essays/index/essays-index-content" "~essays/" "/essay-" ""))

76
sx/sx/services-tools.sx Normal file
View File

@@ -0,0 +1,76 @@
(defcomp ~services-tools/overview-content (&key (title "Services") &rest extra)
(~docs/page :title title
(p :class "text-stone-500 text-sm italic mb-8"
"Python-based MCP server for understanding the rose-ash microservice topology. Static analysis — works without running services.")
(~docs/section :title "Service introspection" :id "introspection"
(p "The rose-ash-services MCP server provides 12 tools for exploring the microservice architecture. Every tool operates via static analysis of the codebase — no running containers needed.")
(table :class "min-w-full text-sm mb-6"
(thead
(tr
(th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Tool")
(th :class "text-left pb-2 font-semibold text-stone-700" "Purpose")))
(tbody :class "text-stone-600"
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_status")
(td :class "py-1" "Docker container status for all rose-ash services."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_routes")
(td :class "py-1" "List all HTTP routes for a service by scanning blueprints."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_calls")
(td :class "py-1" "Map inter-service calls: fetch_data, call_action, send_internal_activity, fetch_fragment."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_config")
(td :class "py-1" "Environment variables and config for a service."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_models")
(td :class "py-1" "SQLAlchemy models, columns, and relationships for a service."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_schema")
(td :class "py-1" "Live defquery/defaction manifest from a running service."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_queries")
(td :class "py-1" "List all defquery definitions from queries.sx files."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_actions")
(td :class "py-1" "List all defaction definitions from actions.sx files."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "alembic_status")
(td :class "py-1" "Migration count and latest migration per service."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_logs")
(td :class "py-1" "Recent Docker logs for a service."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_start")
(td :class "py-1" "Start services via dev.sh."))
(tr
(td :class "pr-4 py-1 font-mono text-xs" "svc_stop")
(td :class "py-1" "Stop all services.")))))
(~docs/section :title "Service topology" :id "topology"
(p "The rose-ash platform runs as independent Quart microservices, each with its own database. Services communicate via HMAC-signed internal HTTP and ActivityPub events.")
(table :class "min-w-full text-sm mb-6"
(thead
(tr
(th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Service")
(th :class "text-left pr-4 pb-2 font-semibold text-stone-700" "Domain")
(th :class "text-left pb-2 font-semibold text-stone-700" "Port")))
(tbody :class "text-stone-600"
(tr (td :class "pr-4 py-1 font-mono text-xs" "blog") (td :class "pr-4 py-1" "Content management, Ghost CMS sync") (td :class "py-1" "8001"))
(tr (td :class "pr-4 py-1 font-mono text-xs" "market") (td :class "pr-4 py-1" "Product catalog, marketplace") (td :class "py-1" "8002"))
(tr (td :class "pr-4 py-1 font-mono text-xs" "cart") (td :class "pr-4 py-1" "Shopping cart, checkout") (td :class "py-1" "8003"))
(tr (td :class "pr-4 py-1 font-mono text-xs" "events") (td :class "pr-4 py-1" "Calendar, ticketing") (td :class "py-1" "8004"))
(tr (td :class "pr-4 py-1 font-mono text-xs" "federation") (td :class "pr-4 py-1" "ActivityPub social hub") (td :class "py-1" "8005"))
(tr (td :class "pr-4 py-1 font-mono text-xs" "account") (td :class "pr-4 py-1" "OAuth2, user dashboard") (td :class "py-1" "8006"))
(tr (td :class "pr-4 py-1 font-mono text-xs" "orders") (td :class "pr-4 py-1" "Order history, SumUp payments") (td :class "py-1" "8010"))
(tr (td :class "pr-4 py-1 font-mono text-xs" "relations") (td :class "pr-4 py-1" "Cross-domain relationship tracking (internal)") (td :class "py-1" "8008"))
(tr (td :class "pr-4 py-1 font-mono text-xs" "likes") (td :class "pr-4 py-1" "Unified like/favourite tracking (internal)") (td :class "py-1" "8009")))))
(~docs/section :title "Inter-service communication" :id "communication"
(p "Services communicate through three mechanisms, all HMAC-signed:")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Data queries")
(p "Read-only cross-service data via GET /internal/data/{query}. 3-second timeout. Used for fetching data owned by another service.")
(~docs/code :src ";; Example: blog fetching product data from market\nfetch_data(\"market\", \"products-by-category\", {\"category\": \"prints\"})")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "Actions")
(p "Write operations via POST /internal/actions/{action}. 5-second timeout. Used when one service needs to trigger a state change in another.")
(~docs/code :src ";; Example: cart creating an order in the orders service\ncall_action(\"orders\", \"create-order\", {\"items\": [...], \"customer\": ...})")
(h4 :class "font-semibold text-stone-700 mt-6 mb-2" "ActivityPub inbox")
(p "AP-shaped activities via POST /internal/inbox. Used for cross-service writes that follow the ActivityPub pattern — Create, Update, Delete activities with typed objects."))))

File diff suppressed because one or more lines are too long