From dc5da2f5edd437357bf224c52e3db03371b9f5ab Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 28 Mar 2026 19:23:44 +0000 Subject: [PATCH] sx-http: header island working, stepper partial, CSSX source included Include cssx.sx source in component-defs for client CSSX runtime. Island placeholders now contain SX call expression for client hydration. escape_sx_string only escapes , tagline, copyright), stepper shows raw SX (cssx/tw not expanding client-side). Geography fully rendered including island hydration. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/bin/sx_server.ml | 52 +++++++++++++++++++++--------------- hosts/ocaml/lib/sx_render.ml | 21 ++++++++++----- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/hosts/ocaml/bin/sx_server.ml b/hosts/ocaml/bin/sx_server.ml index 3860b652..20823d51 100644 --- a/hosts/ocaml/bin/sx_server.ml +++ b/hosts/ocaml/bin/sx_server.ml @@ -34,8 +34,10 @@ let escape_sx_string s = | '\n' -> Buffer.add_string buf "\\n" | '\r' -> Buffer.add_string buf "\\r" | '\t' -> Buffer.add_string buf "\\t" - | '<' when i + 1 < len && s.[i + 1] = '/' -> - (* Escape *) + | '<' when i + 7 < len && s.[i + 1] = '/' && + (s.[i + 2] = 's' || s.[i + 2] = 'S') && + String.lowercase_ascii (String.sub s (i + 2) 6) = "script" -> + (* Escape Buffer.add_char buf c done; @@ -1515,6 +1517,15 @@ let http_render_page env path = | String s | SxExpr s -> s | _ -> serialize_value body_result in + (* Debug: dump aser for homepage *) + if path = "/sx/" then begin + try + let oc = open_out "/tmp/sx-aser-home.txt" in + output_string oc body_str; + close_out oc; + Printf.eprintf "[debug] Wrote aser for %s: %d bytes\n%!" path (String.length body_str) + with _ -> () + end; let t2 = Unix.gettimeofday () in (* Phase 2: SSR — render to HTML using streaming buffer renderer. Writes directly to buffer, no intermediate string allocations. *) @@ -1525,7 +1536,7 @@ let http_render_page env path = | [e] -> e | [] -> Nil | _ -> List (Symbol "<>" :: body_exprs) in let buf = Buffer.create 65536 in (try Sx_render.render_to_buffer buf body_expr env - with e -> Printf.eprintf "[http-ssr] partial: %s\n%!" (Printexc.to_string e)); + with e -> Printf.eprintf "[http-ssr] partial for %s: %s\n%!" path (Printexc.to_string e)); Buffer.contents buf with e -> Printf.eprintf "[http-ssr] failed: %s\n%!" (Printexc.to_string e); "" @@ -1631,8 +1642,7 @@ let read_css_file path = let http_inject_shell_statics env static_dir sx_sxc = (* Component definitions for client *) let buf = Buffer.create 65536 in - Hashtbl.iter (fun sym v -> - let name = Sx_types.unintern sym in + Hashtbl.iter (fun _sym v -> match v with | Component c -> let ps = String.concat " " ( @@ -1646,24 +1656,24 @@ let http_inject_shell_statics env static_dir sx_sxc = (if i.i_has_children then ["&rest"; "children"] else [])) in Buffer.add_string buf (Printf.sprintf "(defisland ~%s (%s) %s)\n" i.i_name ps (serialize_value i.i_body)) - | Lambda l when l.l_name <> None -> - (* Named lambdas — client needs utility functions like cssx-process-token *) - let fn_name = match l.l_name with Some n -> n | None -> name in - let ps = String.concat " " l.l_params in - Buffer.add_string buf (Printf.sprintf "(define %s (fn (%s) %s))\n" - fn_name ps (serialize_value l.l_body)) - | Macro m -> - let ps = String.concat " " m.m_params in - let mname = match m.m_name with Some n -> n | None -> name in - Buffer.add_string buf (Printf.sprintf "(defmacro %s (%s) %s)\n" - mname ps (serialize_value m.m_body)) - | Dict _ | Number _ | String _ | Bool _ | List _ | ListRef _ -> - (* Named values (dicts, lists, constants) — client needs CSSX config etc. *) - if String.length name > 0 && name.[0] <> '_' && name.[0] <> '*' then - Buffer.add_string buf (Printf.sprintf "(define %s %s)\n" - name (serialize_value v)) | _ -> () ) env.bindings; + (* Prepend client library source files — these define functions that + island components need at runtime (CSSX, signals, etc.). + Read directly from .sx files, same as Quart's _CLIENT_LIBRARY_SOURCES. *) + let project_dir = try Sys.getenv "SX_PROJECT_DIR" with Not_found -> + Filename.dirname (Filename.dirname static_dir) in + let templates_dir = project_dir ^ "/shared/sx/templates" in + let client_libs = [ + templates_dir ^ "/cssx.sx"; + ] in + List.iter (fun path -> + if Sys.file_exists path then begin + let src = In_channel.with_open_text path In_channel.input_all in + Buffer.add_string buf src; + Buffer.add_char buf '\n' + end + ) client_libs; let raw_defs = Buffer.contents buf in (* Component-defs are inlined in