Merge sx-tools: test coverage + bug fixes + Playwright fixes

- 7 new test files (~268 tests): stdlib, adapter-html, adapter-dom,
  boot-helpers, page-helpers, layout, tw-layout
- Fix component-pure? transitive scan, render-target crash on unknown
  components, &rest param binding (String vs Symbol), swap! extra args
- Fix 5 Playwright marshes tests: timing + test logic
- 2522/2522 OCaml tests, 173/173 Playwright tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

# Conflicts:
#	shared/static/wasm/sx/orchestration.sxbc
#	shared/static/wasm/sx_browser.bc.js
#	shared/static/wasm/sx_browser.bc.wasm.js
#	sx/sx/not-found.sx
#	tests/playwright/isomorphic.spec.js
This commit is contained in:
2026-04-02 18:59:45 +00:00
29 changed files with 1770 additions and 1359 deletions

View File

@@ -2706,18 +2706,19 @@ let http_mode port =
let req_method = String.uppercase_ascii !_req_method in
let try_key k = try let v = env_get env k in
if v <> Nil then Some (k, v) else None with _ -> None in
let base = "handler:ex-" ^ slug in
(* Try multiple handler name patterns: ex-slug, reactive-slug, slug *)
let prefixes = ["handler:ex-" ^ slug; "handler:reactive-" ^ slug; "handler:" ^ slug] in
let suffixes = match req_method with
| "POST" -> [base; base ^ "-save"; base ^ "-submit"]
| "PUT" | "PATCH" -> [base; base ^ "-put"; base ^ "-save"]
| "DELETE" -> [base]
| _ -> [base; base ^ "-form"; base ^ "-status"] in
| "POST" -> List.concat_map (fun base -> [base; base ^ "-save"; base ^ "-submit"]) prefixes
| "PUT" | "PATCH" -> List.concat_map (fun base -> [base; base ^ "-put"; base ^ "-save"]) prefixes
| "DELETE" -> prefixes
| _ -> List.concat_map (fun base -> [base; base ^ "-form"; base ^ "-status"]) prefixes in
let found = List.fold_left (fun acc k ->
match acc with Some _ -> acc | None -> try_key k) None suffixes in
(match found with
| None ->
http_response ~status:404 ~content_type:"text/sx; charset=utf-8"
(Printf.sprintf "(div :class \"p-4 text-rose-600\" \"Handler not found: %s\")" base)
(Printf.sprintf "(div :class \"p-4 text-rose-600\" \"Handler not found: %s\")" (List.hd prefixes))
| Some (_hk, hdef) ->
(match path_param_val with
| Some pval ->

View File

@@ -413,6 +413,14 @@
"hydrated:", !!islands[j]._sxBoundislandhydrated || !!islands[j]["_sxBound" + "island-hydrated"],
"children:", islands[j].children.length);
}
// Register popstate handler for back/forward navigation
window.addEventListener("popstate", function(e) {
var state = e.state;
var scrollY = (state && state.scrollY) ? state.scrollY : 0;
K.eval("(handle-popstate " + scrollY + ")");
});
// Signal boot complete
document.documentElement.setAttribute("data-sx-ready", "true");
console.log("[sx] boot done");
}
}

View File

@@ -687,6 +687,17 @@ let () =
match args with
| [SxExpr s] -> String s
| [RawHTML s] -> String s
| [Spread pairs] ->
(* Serialize spread values as (make-spread {:key "val" ...}) *)
let dict_parts = List.map (fun (k, v) ->
Printf.sprintf ":%s %s" k (inspect v)) pairs in
String (Printf.sprintf "(make-spread {%s})" (String.concat " " dict_parts))
| [Component c] ->
(* Serialize component values as their ~name reference *)
String (Printf.sprintf "~%s" c.c_name)
| [Island i] ->
String (Printf.sprintf "~%s" i.i_name)
| [Lambda _] -> String "<lambda>"
| [a] -> String (inspect a) (* used for dedup keys in compiler *)
| _ -> raise (Eval_error "serialize: 1 arg"));
register "make-symbol" (fun args ->