Fix streaming: resolve scripts inside </body>, live server tests

The shell HTML included closing </body></html> tags. Resolve script
chunks arrived AFTER the document end — browser ignored them
(ERR_INCOMPLETE_CHUNKED_ENCODING). Now strips </body></html> from
shell, sends resolve scripts inside the body, closes document last.

Added live server Playwright tests that hit the actual streaming
endpoint and verify suspense slots resolve with content.

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

View File

@@ -2079,11 +2079,30 @@ let http_render_page_streaming env path _headers fd page_name =
in
let t1 = Unix.gettimeofday () in
(* Phase 2: Send chunked header + shell HTML *)
(* Phase 2: Send chunked header + shell HTML.
Strip closing </body></html> from shell — resolve scripts must go INSIDE
the body, otherwise the browser's HTML parser ignores them. *)
let shell_body, shell_tail =
(* Find last </body> and split there *)
let s = shell_html in
let body_close = "</body>" in
let rec find_last i found =
if i < 0 then found
else if i + String.length body_close <= String.length s
&& String.sub s i (String.length body_close) = body_close
then find_last (i - 1) i
else find_last (i - 1) found
in
let pos = find_last (String.length s - String.length body_close) (-1) in
if pos >= 0 then
(String.sub s 0 pos, String.sub s pos (String.length s - pos))
else
(s, "")
in
let header = http_chunked_header () in
let header_bytes = Bytes.of_string header in
(try ignore (Unix.write fd header_bytes 0 (Bytes.length header_bytes)) with _ -> ());
write_chunk fd shell_html;
write_chunk fd shell_body;
(* Bootstrap resolve script — must come after shell so suspense elements exist *)
write_chunk fd _sx_streaming_bootstrap;
let t2 = Unix.gettimeofday () in
@@ -2170,7 +2189,8 @@ let http_render_page_streaming env path _headers fd page_name =
end else
Printf.eprintf "[sx-stream] %s shell=%.3fs (no :data/:content)\n%!" path (t1 -. t0);
(* Phase 4: End chunked response *)
(* Phase 4: Send closing tags + end chunked response *)
if shell_tail <> "" then write_chunk fd shell_tail;
end_chunked fd
(* ====================================================================== *)