Error pages render within layout instead of bare nil/404

Route errors and missing pages now show a styled error message inside
the normal layout (header, nav still work) instead of bare "nil" text
or a raw "Not Found" page. AJAX errors return renderable SX error
fragments instead of "nil" strings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 23:20:50 +00:00
parent b62dfb25e5
commit 833415b170

View File

@@ -1544,14 +1544,32 @@ let http_render_page env path headers =
Printf.eprintf "[http] route error for %s: %s\n%!" path (Printexc.to_string e);
Nil
in
match route_result with
| Nil -> None
| Dict d ->
let is_ajax = match Hashtbl.find_opt d "is-ajax" with Some (Bool true) -> true | _ -> false in
let nav_path = match Hashtbl.find_opt d "nav-path" with Some (String s) -> s | _ -> path in
let page_ast = match Hashtbl.find_opt d "page-ast" with Some v -> v | _ -> Nil in
if page_ast = Nil then None
else begin
(* Build an error page AST that keeps the layout intact *)
let error_page_ast msg =
List [Symbol "div"; Keyword "class"; String "p-8 max-w-2xl mx-auto";
List [Symbol "h2"; Keyword "class"; String "text-xl font-semibold text-rose-600 mb-4";
String "Page Error"];
List [Symbol "p"; Keyword "class"; String "text-stone-600 mb-2"; String path];
List [Symbol "pre"; Keyword "class"; String "text-sm bg-stone-100 p-4 rounded overflow-x-auto text-stone-700";
String msg]]
in
(* Normalize route result — Nil and non-Dict become error pages *)
let is_ajax_req = List.exists (fun (k,_) -> String.lowercase_ascii k = "sx-request") headers in
let route_dict = match route_result with
| Dict d -> d
| _ ->
let d = Hashtbl.create 4 in
Hashtbl.replace d "is-ajax" (Bool is_ajax_req);
Hashtbl.replace d "nav-path" (String path);
Hashtbl.replace d "page-ast" (error_page_ast "Page not found");
d
in
let d = route_dict in
let is_ajax = match Hashtbl.find_opt d "is-ajax" with Some (Bool true) -> true | _ -> false in
let nav_path = match Hashtbl.find_opt d "nav-path" with Some (String s) -> s | _ -> path in
let page_ast = match Hashtbl.find_opt d "page-ast" with Some v -> v | _ -> Nil in
let page_ast = if page_ast = Nil then error_page_ast "Page returned empty content" else page_ast in
begin
let wrapped = List [Symbol "~layouts/doc"; Keyword "path"; String nav_path; page_ast] in
if is_ajax then begin
(* AJAX: return SX wire format (aser output) with text/sx content type *)
@@ -1618,9 +1636,6 @@ let http_render_page env path headers =
Some html
end
end
| _ ->
Printf.eprintf "[http] unexpected handler result for %s\n%!" path;
None
(* ====================================================================== *)
(* Static file serving + file hashing *)
@@ -2409,10 +2424,13 @@ let http_mode port =
| Some body ->
let resp = http_response ~content_type:"text/sx; charset=utf-8" body in
Hashtbl.replace response_cache cache_key resp; resp
| None -> http_response ~status:404 "nil"
| None -> http_response ~status:404
"(div :class \"p-8\" (h2 :class \"text-rose-600 font-semibold\" \"Page not found\") (p :class \"text-stone-500\" \"No route matched this path\"))"
with e ->
Printf.eprintf "[ajax] Error for %s: %s\n%!" path (Printexc.to_string e);
http_response ~status:500 "nil"
http_response ~status:500
(Printf.sprintf "(div :class \"p-8\" (h2 :class \"text-rose-600 font-semibold\" \"Render Error\") (pre :class \"text-sm bg-stone-100 p-4 rounded\" \"%s\"))"
(escape_sx_string (Printexc.to_string e)))
in
write_response fd response; true
end else begin