Stepper persistence: use def-store instead of broken cookies

The home stepper's step-idx signal was not persisting across SX
navigation because set-cookie/freeze-to-sx wasn't working in the
WASM kernel. Replace with def-store which uses a global registry
that survives island re-hydration.

Also fix sx_http.exe build: add sx_http back to dune, inline scope
primitives (Sx_scope module was removed), add declarative form
stubs and render stubs, fix /sx/ home route mapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 18:27:08 +00:00
parent ef34122a25
commit 6134bd2ea5
4 changed files with 104 additions and 24 deletions

View File

@@ -1,5 +1,5 @@
(executables
(names run_tests debug_set sx_server integration_tests)
(names run_tests debug_set sx_server sx_http integration_tests)
(libraries sx unix))
(executable

View File

@@ -74,7 +74,61 @@ let setup_io_stubs env =
let make_http_env () =
let env = make_env () in
Sx_render.setup_render_env env;
Sx_scope.setup_scope_env env;
(* Scope primitives — inline since Sx_scope was merged *)
let _scope_stacks : (string, Sx_types.value list) Hashtbl.t = Hashtbl.create 8 in
let bind name fn = ignore (Sx_types.env_bind env name (Sx_types.NativeFn (name, fn))) in
bind "scope-push!" (fun args -> match args with
| [String name; value] -> let s = try Hashtbl.find _scope_stacks name with Not_found -> [] in Hashtbl.replace _scope_stacks name (value :: s); Nil
| [String name] -> let s = try Hashtbl.find _scope_stacks name with Not_found -> [] in Hashtbl.replace _scope_stacks name (Nil :: s); Nil
| _ -> Nil);
bind "scope-pop!" (fun args -> match args with
| [String name] -> (match (try Hashtbl.find _scope_stacks name with Not_found -> []) with _ :: rest -> Hashtbl.replace _scope_stacks name rest | [] -> ()); Nil
| _ -> Nil);
bind "scope-peek" (fun args -> match args with
| [String name] -> (match (try Hashtbl.find _scope_stacks name with Not_found -> []) with v :: _ -> v | [] -> Nil)
| _ -> Nil);
bind "scope-emit!" (fun args -> match args with
| [String name; value] ->
let key = name ^ ":emitted" in
let s = try Hashtbl.find _scope_stacks key with Not_found -> [] in
Hashtbl.replace _scope_stacks key (value :: s); Nil
| _ -> Nil);
bind "scope-emitted" (fun args -> match args with
| [String name] ->
let key = name ^ ":emitted" in
let items = try Hashtbl.find _scope_stacks key with Not_found -> [] in
Hashtbl.replace _scope_stacks key []; List (List.rev items)
| _ -> List []);
bind "collect!" (fun args -> match args with
| [String name; value] ->
let key = name ^ ":collected" in
let s = try Hashtbl.find _scope_stacks key with Not_found -> [] in
Hashtbl.replace _scope_stacks key (value :: s); Nil
| _ -> Nil);
bind "collected" (fun args -> match args with
| [String name] ->
let key = name ^ ":collected" in
let items = try Hashtbl.find _scope_stacks key with Not_found -> [] in
Hashtbl.replace _scope_stacks key []; List (List.rev items)
| _ -> List []);
(* Declarative form stubs — no-ops at runtime *)
bind "define-module" (fun _args -> Nil);
bind "define-primitive" (fun _args -> Nil);
bind "deftype" (fun _args -> Nil);
bind "defeffect" (fun _args -> Nil);
bind "deftest" (fun _args -> Nil);
bind "defstyle" (fun _args -> Nil);
bind "defhandler" (fun _args -> Nil);
bind "defpage" (fun _args -> Nil);
bind "defquery" (fun _args -> Nil);
bind "defaction" (fun _args -> Nil);
bind "defrelation" (fun _args -> Nil);
(* Render stubs *)
bind "set-render-active!" (fun _args -> Nil);
bind "render-active?" (fun _args -> Bool true);
bind "trampoline" (fun args -> match args with
| [Thunk (expr, e)] -> Sx_ref.eval_expr expr (Env e)
| [v] -> v | _ -> Nil);
(* Setup all the standard primitives *)
(* Evaluator bridge — needed for aser, macro expansion *)
ignore (env_bind env "eval-expr" (NativeFn ("eval-expr", fun args ->
@@ -236,7 +290,7 @@ let sx_render_to_html expr env =
let render_page env statics path =
let t0 = Unix.gettimeofday () in
(* Build the page AST: evaluate the URL path as an SX expression *)
let path_expr = if path = "/" || path = "" then "home"
let path_expr = if path = "/" || path = "" || path = "/sx/" || path = "/sx" then "home"
else begin
(* /sx/(geography.(reactive))(geography (reactive)) *)
let p = if String.length path > 4 && String.sub path 0 4 = "/sx/" then