Add s-expression wire format support and test detail view

- HTMX beforeSwap hook intercepts text/sexp responses and renders
  them client-side via sexp.js before HTMX swaps the result in
- sexp_response() helper for returning text/sexp from route handlers
- Test detail page (/test/<nodeid>) with clickable test names
- HTMX navigation to detail returns sexp wire format (4x smaller
  than pre-rendered HTML), full page loads render server-side
- ~test-detail component with back link, outcome badge, and
  error traceback display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 23:45:28 +00:00
parent 269bcc02be
commit fec5ecdfb1
5 changed files with 136 additions and 1 deletions

View File

@@ -9,6 +9,7 @@ from __future__ import annotations
from typing import Any
from markupsafe import escape
from quart import Response
from .jinja_bridge import render
from .page import SEARCH_HEADERS_MOBILE, SEARCH_HEADERS_DESKTOP
@@ -225,6 +226,24 @@ def full_page(ctx: dict, *, header_rows_html: str,
)
def sexp_response(sexp_source: str, status: int = 200,
headers: dict | None = None) -> Response:
"""Return an s-expression wire-format response.
The client-side sexp.js will intercept responses with Content-Type
text/sexp and render them before HTMX swaps the result in.
Usage in a route handler::
return sexp_response('(~test-row :nodeid "test_foo" :outcome "passed")')
"""
resp = Response(sexp_source, status=status, content_type="text/sexp")
if headers:
for k, v in headers.items():
resp.headers[k] = v
return resp
def oob_page(ctx: dict, *, oobs_html: str = "",
filter_html: str = "", aside_html: str = "",
content_html: str = "", menu_html: str = "") -> str:

View File

@@ -1261,6 +1261,18 @@
Sexp.processScripts(e.detail.target);
Sexp.hydrate(e.detail.target);
});
// S-expression wire format: intercept text/sexp responses and render to HTML
// before HTMX swaps them in. Server sends Content-Type: text/sexp with
// s-expression body; sexp.js renders to HTML string for HTMX to swap.
document.addEventListener("htmx:beforeSwap", function (e) {
var xhr = e.detail.xhr;
var ct = xhr.getResponseHeader("Content-Type") || "";
if (ct.indexOf("text/sexp") === -1) return;
// Render s-expression response to HTML string
var html = Sexp.renderToString(xhr.responseText);
e.detail.serverResponse = html;
});
}
})(typeof window !== "undefined" ? window : this);