Fix ListRef handling in streaming data — list from SX is ListRef in OCaml

The streaming render matched `List items` but SX's `(list ...)` produces
`ListRef` (mutable list) in the OCaml runtime. Data items were rejected
with "returned list, expected dict or list" — 0 resolve chunks sent.

Fixed both streaming render and AJAX paths to handle ListRef.
Added sandbox test for streaming-demo-data return type validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 16:34:50 +00:00
parent 3cada3f8fe
commit d4c0be52b1
2 changed files with 44 additions and 13 deletions

View File

@@ -2099,16 +2099,17 @@ let http_render_page_streaming env path _headers fd page_name =
let t3_data = Unix.gettimeofday () in
(* Determine single-stream vs multi-stream *)
let data_items = match data_result with
let data_items =
let extract_items items = List.map (fun item ->
let stream_id = match item with
| Dict d -> (match Hashtbl.find_opt d "stream-id" with
| Some (String s) -> s | _ -> "stream-content")
| _ -> "stream-content" in
(item, stream_id)) items in
match data_result with
| Dict _ -> [(data_result, "stream-content")]
| List items ->
List.map (fun item ->
let stream_id = match item with
| Dict d -> (match Hashtbl.find_opt d "stream-id" with
| Some (String s) -> s | _ -> "stream-content")
| _ -> "stream-content" in
(item, stream_id)
) items
| List items -> extract_items items
| ListRef { contents = items } -> extract_items items
| _ ->
Printf.eprintf "[sx-stream] :data returned %s, expected dict or list\n%!"
(Sx_runtime.type_of data_result |> Sx_runtime.value_to_str);
@@ -3366,12 +3367,14 @@ let http_mode port =
(* If we have data+content, resolve all slots and embed as OOB swaps *)
let resolve_oob = if data_ast <> Nil && content_ast <> Nil then begin
let data_result = try Sx_ref.eval_expr data_ast (Env env) with _ -> Nil in
let extract_sid items = List.map (fun item ->
let sid = match item with Dict d ->
(match Hashtbl.find_opt d "stream-id" with Some (String s) -> s | _ -> "stream-content")
| _ -> "stream-content" in (item, sid)) items in
let data_items = match data_result with
| Dict _ -> [(data_result, "stream-content")]
| List items -> List.map (fun item ->
let sid = match item with Dict d ->
(match Hashtbl.find_opt d "stream-id" with Some (String s) -> s | _ -> "stream-content")
| _ -> "stream-content" in (item, sid)) items
| List items -> extract_sid items
| ListRef { contents = items } -> extract_sid items
| _ -> [] in
let buf = Buffer.create 1024 in
List.iter (fun (item, stream_id) ->