Fix CSSX styling: trampoline wiring + scope-emit!/emitted for adapter-html.sx

Root causes of missing CSSX classes in SSR:

1. _sx_trampoline_fn in sx_primitives.ml was never wired — call_any in
   HO forms (map/filter/for-each) returned unresolved Thunks, so callbacks
   like render-lambda-html's param binding never executed. Fixed in
   bootstrap.py FIXUPS: wire Sx_primitives._sx_trampoline_fn after eval_expr.

2. adapter-html.sx used (emit! ...) and (emitted ...) which are CEK special
   forms (walk kont for ScopeAccFrame), but scope-push!/scope-pop! use the
   hashtable. CEK frames and hashtable are two different scope systems.
   Fixed: adapter uses scope-emit!/scope-emitted (hashtable primitives).

3. env-* operations (env-has?, env-get, env-bind!, env-set!, env-extend,
   env-merge) only accepted Env type. adapter-html.sx passes Dict as env.
   Fixed: all env ops go through unwrap_env which handles Dict/Nil.

Also: fix merge conflict in sx/sx/geography/index.sx, remove duplicate
scope primitives from sx_primitives.ml (sx_server.ml registers them).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 02:23:00 +00:00
parent e4cabcbb59
commit 7d793ec76c
8 changed files with 151 additions and 87 deletions

View File

@@ -194,6 +194,43 @@ let () = Sx_primitives.register "clear-collected!" (fun args ->
Nil
| _ -> Nil)
(* emit!/emitted — adapter-html.sx uses these for spread attr collection *)
let () = Sx_primitives.register "scope-emit!" (fun args ->
match args with
| [String name; value] ->
let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in
(match stack with
| List items :: rest ->
Hashtbl.replace _scope_stacks name (List (items @ [value]) :: rest)
| v :: rest ->
(* Non-list top — wrap current entries as list + new value *)
Hashtbl.replace _scope_stacks name (List [value] :: v :: rest)
| [] ->
Hashtbl.replace _scope_stacks name [List [value]]);
Nil
| _ -> Nil)
let () = Sx_primitives.register "emitted" (fun args ->
match args with
| [String name] ->
let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in
(match stack with List items :: _ -> List items | _ -> List [])
| _ -> List [])
let () = Sx_primitives.register "scope-emitted" (fun args ->
match args with
| [String name] ->
let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in
(match stack with List items :: _ -> List items | _ -> List [])
| _ -> List [])
let () = Sx_primitives.register "provide-push!" (fun args ->
match Sx_primitives.get_primitive "scope-push!" with
| NativeFn (_, fn) -> fn args | _ -> Nil)
let () = Sx_primitives.register "provide-pop!" (fun args ->
match Sx_primitives.get_primitive "scope-pop!" with
| NativeFn (_, fn) -> fn args | _ -> Nil)
let () = Sx_primitives.register "scope-emit!" (fun args ->
match args with
| [String name; value] ->
@@ -510,7 +547,7 @@ let make_server_env () =
Route to the OCaml CEK machine. *)
bind "eval-expr" (fun args ->
match args with
| [expr; Env e] -> Sx_ref.eval_expr expr (Env e)
| [expr; e] -> Sx_ref.eval_expr expr (Env (Sx_runtime.unwrap_env e))
| [expr] -> Sx_ref.eval_expr expr (Env env)
| _ -> raise (Eval_error "eval-expr: expected (expr env?)"));
bind "trampoline" (fun args ->
@@ -792,34 +829,35 @@ let make_server_env () =
| [v] -> String (inspect v)
| _ -> raise (Eval_error "sx-serialize: expected 1 arg"));
(* Env operations *)
(* Env operations — accept both Env and Dict (adapter-html.sx passes dicts) *)
let uw = Sx_runtime.unwrap_env in
bind "env-get" (fun args ->
match args with
| [Env e; String k] -> env_get e k
| [Env e; Keyword k] -> env_get e k
| [e; String k] -> Sx_types.env_get (uw e) k
| [e; Keyword k] -> Sx_types.env_get (uw e) k
| _ -> raise (Eval_error "env-get: expected env and string"));
bind "env-has?" (fun args ->
match args with
| [Env e; String k] -> Bool (env_has e k)
| [Env e; Keyword k] -> Bool (env_has e k)
| [e; String k] -> Bool (Sx_types.env_has (uw e) k)
| [e; Keyword k] -> Bool (Sx_types.env_has (uw e) k)
| _ -> raise (Eval_error "env-has?: expected env and string"));
bind "env-bind!" (fun args ->
match args with
| [Env e; String k; v] -> env_bind e k v
| [Env e; Keyword k; v] -> env_bind e k v
| [e; String k; v] -> Sx_types.env_bind (uw e) k v
| [e; Keyword k; v] -> Sx_types.env_bind (uw e) k v
| _ -> raise (Eval_error "env-bind!: expected env, key, value"));
bind "env-set!" (fun args ->
match args with
| [Env e; String k; v] -> env_set e k v
| [Env e; Keyword k; v] -> env_set e k v
| [e; String k; v] -> Sx_types.env_set (uw e) k v
| [e; Keyword k; v] -> Sx_types.env_set (uw e) k v
| _ -> raise (Eval_error "env-set!: expected env, key, value"));
bind "env-extend" (fun args ->
match args with
| [Env e] -> Env (env_extend e)
| [e] -> Env (Sx_types.env_extend (uw e))
| _ -> raise (Eval_error "env-extend: expected env"));
bind "env-merge" (fun args ->