Spec explorer: fix SxExpr rendering bugs, add drill-in UX, Playwright tests
Fix 3 OCaml bugs that caused spec explorer to hang: - sx_types: inspect outputs quoted string for SxExpr (not bare symbol) - sx_primitives: serialize/to_string extract SxExpr/RawHTML content - sx_render: handle SxExpr in both render-to-html paths Restructure spec explorer for performance: - Lightweight overview: name + kind only (was full source for 141 defs) - Drill-in detail: click definition → params, effects, signature - explore() page function accepts optional second arg for drill-in - spec() passes through non-string slugs from nested routing Fix aser map result wrapping: - aser-special map now wraps results in fragment (<> ...) via aser-fragment - Prevents ((div ...) (div ...)) nested lists that caused client "Not callable" 5 Playwright tests: overview load, no errors, SPA nav, drill-in detail+params Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,8 @@ let rec to_string = function
|
||||
| Symbol s -> s
|
||||
| Keyword k -> k
|
||||
| Thunk _ as t -> to_string (!trampoline_hook t)
|
||||
| SxExpr s -> s
|
||||
| RawHTML s -> s
|
||||
| v -> inspect v
|
||||
|
||||
let () =
|
||||
@@ -126,10 +128,23 @@ let () =
|
||||
Number (Float.max lo (Float.min hi x))
|
||||
| _ -> raise (Eval_error "clamp: 3 args"));
|
||||
register "parse-int" (fun args ->
|
||||
let parse_leading_int s =
|
||||
let len = String.length s in
|
||||
let start = ref 0 in
|
||||
let neg = len > 0 && s.[0] = '-' in
|
||||
if neg then start := 1
|
||||
else if len > 0 && s.[0] = '+' then start := 1;
|
||||
let j = ref !start in
|
||||
while !j < len && s.[!j] >= '0' && s.[!j] <= '9' do incr j done;
|
||||
if !j > !start then
|
||||
let n = int_of_string (String.sub s !start (!j - !start)) in
|
||||
Some (if neg then -n else n)
|
||||
else None
|
||||
in
|
||||
match args with
|
||||
| [String s] -> (match int_of_string_opt s with Some n -> Number (float_of_int n) | None -> Nil)
|
||||
| [String s] -> (match parse_leading_int s with Some n -> Number (float_of_int n) | None -> Nil)
|
||||
| [String s; default_val] ->
|
||||
(match int_of_string_opt s with Some n -> Number (float_of_int n) | None -> default_val)
|
||||
(match parse_leading_int s with Some n -> Number (float_of_int n) | None -> default_val)
|
||||
| [Number n] | [Number n; _] -> Number (float_of_int (int_of_float n))
|
||||
| [_; default_val] -> default_val
|
||||
| _ -> Nil);
|
||||
@@ -276,7 +291,17 @@ let () =
|
||||
else if String.sub haystack i nl = needle then Number (float_of_int i)
|
||||
else find (i + 1)
|
||||
in find 0
|
||||
| _ -> raise (Eval_error "index-of: 2 string args"));
|
||||
| [List items; target] | [ListRef { contents = items }; target] ->
|
||||
let eq a b = match a, b with
|
||||
| String x, String y -> x = y | Number x, Number y -> x = y
|
||||
| Symbol x, Symbol y -> x = y | Keyword x, Keyword y -> x = y
|
||||
| Bool x, Bool y -> x = y | Nil, Nil -> true | _ -> a == b in
|
||||
let rec find i = function
|
||||
| [] -> Nil
|
||||
| h :: _ when eq h target -> Number (float_of_int i)
|
||||
| _ :: tl -> find (i + 1) tl
|
||||
in find 0 items
|
||||
| _ -> raise (Eval_error "index-of: 2 string args or list+target"));
|
||||
register "substring" (fun args ->
|
||||
match args with
|
||||
| [String s; Number start; Number end_] ->
|
||||
@@ -655,6 +680,8 @@ let () =
|
||||
match args with [a] -> String (inspect a) | _ -> raise (Eval_error "inspect: 1 arg"));
|
||||
register "serialize" (fun args ->
|
||||
match args with
|
||||
| [SxExpr s] -> String s
|
||||
| [RawHTML s] -> String s
|
||||
| [a] -> String (inspect a) (* used for dedup keys in compiler *)
|
||||
| _ -> raise (Eval_error "serialize: 1 arg"));
|
||||
register "make-symbol" (fun args ->
|
||||
|
||||
@@ -201,6 +201,7 @@ let rec do_render_to_html (expr : value) (env : env) : string =
|
||||
| String s -> escape_html s
|
||||
| Keyword k -> escape_html k
|
||||
| RawHTML s -> s
|
||||
| SxExpr s -> s
|
||||
| Symbol s ->
|
||||
let v = Sx_ref.eval_expr (Symbol s) (Env env) in
|
||||
do_render_to_html v env
|
||||
@@ -280,7 +281,12 @@ and render_list_to_html head args env =
|
||||
| _ ->
|
||||
let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in
|
||||
do_render_to_html result env)
|
||||
with Eval_error _ -> "")
|
||||
with Eval_error _ ->
|
||||
(* Primitive or special form — not in env, delegate to CEK *)
|
||||
(try
|
||||
let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in
|
||||
do_render_to_html result env
|
||||
with Eval_error _ -> ""))
|
||||
| _ ->
|
||||
let result = Sx_ref.eval_expr (List (head :: args)) (Env env) in
|
||||
do_render_to_html result env
|
||||
@@ -456,6 +462,7 @@ let rec render_to_buf buf (expr : value) (env : env) : unit =
|
||||
| String s -> escape_html_buf buf s
|
||||
| Keyword k -> escape_html_buf buf k
|
||||
| RawHTML s -> Buffer.add_string buf s
|
||||
| SxExpr s -> Buffer.add_string buf s
|
||||
| Symbol s ->
|
||||
let v = Sx_ref.eval_expr (Symbol s) (Env env) in
|
||||
render_to_buf buf v env
|
||||
|
||||
@@ -531,9 +531,9 @@ let rec inspect = function
|
||||
| Continuation (_, _) -> "<continuation>"
|
||||
| NativeFn (name, _) -> Printf.sprintf "<native:%s>" name
|
||||
| Signal _ -> "<signal>"
|
||||
| RawHTML s -> Printf.sprintf "<raw-html:%d chars>" (String.length s)
|
||||
| RawHTML s -> Printf.sprintf "\"<raw-html:%d>\"" (String.length s)
|
||||
| Spread _ -> "<spread>"
|
||||
| SxExpr s -> Printf.sprintf "<sx-expr:%d chars>" (String.length s)
|
||||
| SxExpr s -> Printf.sprintf "\"<sx-expr:%d>\"" (String.length s)
|
||||
| Env _ -> "<env>"
|
||||
| CekState _ -> "<cek-state>"
|
||||
| CekFrame f -> Printf.sprintf "<frame:%s>" f.cf_type
|
||||
|
||||
Reference in New Issue
Block a user