Fix JIT compiler, CSSX browser support, double-fetch, SPA layout
JIT compiler: - Fix jit_compile_lambda: resolve `compile` via symbol lookup in env instead of embedding VmClosure in AST (CEK dispatches differently) - Register eval-defcomp/eval-defisland/eval-defmacro runtime helpers in browser kernel for bytecoded defcomp forms - Disable broken .sxbc.json path (missing arity in nested code blocks), use .sxbc text format only - Mark JIT-failed closures as sentinel to stop retrying CSSX in browser: - Add cssx.sx symlink + cssx.sxbc to browser web stack - Add flush-cssx! to orchestration.sx post-swap for SPA nav - Add cssx.sx to compile-modules.js and mcp_tree.ml bytecode lists SPA navigation: - Fix double-fetch: check e.defaultPrevented in click delegation (bind-event already handled the click) - Fix layout destruction: change nav links from outerHTML to innerHTML swap (outerHTML destroyed #main-panel when response lacked it) - Guard JS popstate handler when SX engine is booted - Rename sx-platform.js → sx-platform-2.js to bust immutable cache Playwright tests: - Add trackErrors() helper to all test specs - Add SPA DOM comparison test (SPA nav vs fresh load) - Add single-fetch + no-duplicate-elements test - Improve MCP tool output: show failure details and error messages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -606,6 +606,7 @@ let rec handle_tool name args =
|
||||
"render.sx"; "core-signals.sx"; "signals.sx"; "deps.sx"; "router.sx";
|
||||
"page-helpers.sx"; "freeze.sx"; "bytecode.sx"; "compiler.sx"; "vm.sx";
|
||||
"dom.sx"; "browser.sx"; "adapter-html.sx"; "adapter-sx.sx"; "adapter-dom.sx";
|
||||
"cssx.sx";
|
||||
"boot-helpers.sx"; "hypersx.sx"; "harness.sx"; "harness-reactive.sx";
|
||||
"harness-web.sx"; "engine.sx"; "orchestration.sx"; "boot.sx";
|
||||
] in
|
||||
@@ -1225,18 +1226,41 @@ let rec handle_tool name args =
|
||||
(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
|
||||
(* Count passed/failed/skipped from the summary line *)
|
||||
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
|
||||
(* Extract test names that failed *)
|
||||
let fail_names = List.filter_map (fun l ->
|
||||
let t = String.trim l in
|
||||
if String.length t > 2 then
|
||||
try
|
||||
let _ = Str.search_forward (Str.regexp "› .* › ") t 0 in
|
||||
Some (" " ^ t)
|
||||
with Not_found -> None
|
||||
else None) all_lines in
|
||||
(* Extract error messages (lines starting with Error:) *)
|
||||
let errors = List.filter_map (fun l ->
|
||||
let t = String.trim l in
|
||||
if String.length t > 6 then
|
||||
try
|
||||
let _ = Str.search_forward (Str.regexp "expect.*\\(received\\)\\|Expected\\|Received\\|Error:.*expect") t 0 in
|
||||
Some (" " ^ t)
|
||||
with Not_found -> None
|
||||
else None) all_lines in
|
||||
let total = List.length fail_names + (match summary with
|
||||
| Some s -> (try let _ = Str.search_forward (Str.regexp "\\([0-9]+\\) passed") s 0 in
|
||||
int_of_string (Str.matched_group 1 s) with _ -> 0)
|
||||
| None -> 0) in
|
||||
let summary_str = match summary with Some s -> String.trim s | None -> "no summary" in
|
||||
let result =
|
||||
if fail_names = [] then
|
||||
Printf.sprintf "%s (%d total)" summary_str total
|
||||
else
|
||||
Printf.sprintf "%s (%d total)\n\nFailed:\n%s\n\nErrors:\n%s"
|
||||
summary_str total
|
||||
(String.concat "\n" fail_names)
|
||||
(String.concat "\n" (List.filteri (fun i _ -> i < 10) errors))
|
||||
in
|
||||
text_result result
|
||||
end else begin
|
||||
|
||||
@@ -37,6 +37,7 @@ const FILES = [
|
||||
'render.sx', 'core-signals.sx', 'signals.sx', 'deps.sx', 'router.sx',
|
||||
'page-helpers.sx', 'freeze.sx', 'bytecode.sx', 'compiler.sx', 'vm.sx',
|
||||
'dom.sx', 'browser.sx', 'adapter-html.sx', 'adapter-sx.sx', 'adapter-dom.sx',
|
||||
'cssx.sx',
|
||||
'boot-helpers.sx', 'hypersx.sx', 'harness.sx', 'harness-reactive.sx',
|
||||
'harness-web.sx', 'engine.sx', 'orchestration.sx', 'boot.sx',
|
||||
];
|
||||
|
||||
@@ -647,6 +647,17 @@ let () =
|
||||
bind "provide-push!" (fun args -> match args with [n; v] -> Sx_runtime.provide_push n v | _ -> raise (Eval_error "provide-push!"));
|
||||
bind "provide-pop!" (fun args -> match args with [n] -> Sx_runtime.provide_pop n | _ -> raise (Eval_error "provide-pop!"));
|
||||
|
||||
(* Runtime helpers for bytecoded defcomp/defisland/defmacro forms.
|
||||
The compiler emits GLOBAL_GET "eval-defcomp" + CALL — these must
|
||||
exist as callable values for bytecoded .sx files that contain
|
||||
component definitions (e.g. cssx.sx). *)
|
||||
bind "eval-defcomp" (fun args ->
|
||||
match args with [List (_ :: rest)] -> Sx_ref.sf_defcomp (List rest) (Env global_env) | _ -> Nil);
|
||||
bind "eval-defisland" (fun args ->
|
||||
match args with [List (_ :: rest)] -> Sx_ref.sf_defisland (List rest) (Env global_env) | _ -> Nil);
|
||||
bind "eval-defmacro" (fun args ->
|
||||
match args with [List (_ :: rest)] -> Sx_ref.sf_defmacro (List rest) (Env global_env) | _ -> Nil);
|
||||
|
||||
(* --- Fragment / raw HTML --- *)
|
||||
bind "<>" (fun args ->
|
||||
RawHTML (String.concat "" (List.map (fun a ->
|
||||
@@ -781,7 +792,13 @@ let () =
|
||||
(try Some (Sx_vm.call_closure cl args _vm_globals)
|
||||
with Eval_error msg ->
|
||||
let fn_name = match l.l_name with Some n -> n | None -> "?" in
|
||||
Printf.eprintf "[jit] FAIL %s: %s\n%!" fn_name msg;
|
||||
Printf.eprintf "[jit] FAIL %s: %s (bc=%d consts=%d upv=%d)\n%!"
|
||||
fn_name msg
|
||||
(Array.length cl.vm_code.vc_bytecode)
|
||||
(Array.length cl.vm_code.vc_constants)
|
||||
(Array.length cl.vm_upvalues);
|
||||
(* Mark as failed to stop retrying *)
|
||||
l.l_compiled <- Some (Sx_vm.jit_failed_sentinel);
|
||||
None)
|
||||
| Some _ -> None
|
||||
| None ->
|
||||
@@ -796,7 +813,12 @@ let () =
|
||||
(try Some (Sx_vm.call_closure cl args _vm_globals)
|
||||
with Eval_error msg ->
|
||||
let fn_name2 = match l.l_name with Some n -> n | None -> "?" in
|
||||
Printf.eprintf "[jit] FAIL %s: %s\n%!" fn_name2 msg;
|
||||
Printf.eprintf "[jit] FAIL %s: %s (bc=%d consts=%d upv=%d)\n%!"
|
||||
fn_name2 msg
|
||||
(Array.length cl.vm_code.vc_bytecode)
|
||||
(Array.length cl.vm_code.vc_constants)
|
||||
(Array.length cl.vm_upvalues);
|
||||
l.l_compiled <- Some (Sx_vm.jit_failed_sentinel);
|
||||
None)
|
||||
| None -> None)
|
||||
end)
|
||||
|
||||
@@ -577,7 +577,13 @@ let jit_compile_lambda (l : lambda) globals =
|
||||
let param_syms = List (List.map (fun s -> Symbol s) l.l_params) in
|
||||
let fn_expr = List [Symbol "fn"; param_syms; l.l_body] in
|
||||
let quoted = List [Symbol "quote"; fn_expr] in
|
||||
let result = Sx_ref.eval_expr (List [compile_fn; quoted]) (Env (make_env ())) in
|
||||
(* Use Symbol "compile" so the CEK resolves it from the env, not
|
||||
an embedded VmClosure value — the CEK dispatches VmClosure calls
|
||||
differently when the value is resolved from env vs embedded in AST. *)
|
||||
ignore compile_fn;
|
||||
let compile_env = Sx_types.env_extend (Sx_types.make_env ()) in
|
||||
Hashtbl.iter (fun k v -> Hashtbl.replace compile_env.bindings (Sx_types.intern k) v) globals;
|
||||
let result = Sx_ref.eval_expr (List [Symbol "compile"; quoted]) (Env compile_env) in
|
||||
(* Closure vars are accessible via vm_closure_env (set on the VmClosure
|
||||
at line ~617). OP_GLOBAL_GET falls back to vm_closure_env when vars
|
||||
aren't in globals. No injection into the shared globals table —
|
||||
|
||||
Reference in New Issue
Block a user