fix(runtime): show transport failures in app error outlets

Make failed hemx requests visibly recoverable by updating root-scoped data-hemx-error outlets while preserving fail-closed non-2xx handling and inspectable hemx:error events. The v0 generated-app exemplar now includes a transport error outlet alongside generated field-error targets.

req: runtime/005

req: examples/001

req: canonical_authoring/003
This commit is contained in:
slhx agent
2026-06-22 22:26:33 +02:00
parent f1aa232c94
commit de6ecd469f
7 changed files with 31 additions and 4 deletions
+16 -1
View File
@@ -194,6 +194,17 @@
return error;
}
function showError(el, error) {
const root = rootOf(el) || document;
const message = error ? (error.status ? `Request failed (${error.status})` : "Request failed") : "";
forEachElement(root, (outlet) => {
if (!outlet.hasAttribute("data-hemx-error")) return;
outlet.textContent = message;
if (message) outlet.removeAttribute("hidden");
else outlet.setAttribute("hidden", "");
});
}
async function send(el, eventName, source = el) {
if (el.getAttribute("data-hemx-confirm") && !confirm(el.getAttribute("data-hemx-confirm"))) return;
const { form, data, multipart } = formDataFor(el, eventName, source);
@@ -224,6 +235,7 @@
const headers = { "X-HEMX-Partial": "1", "Accept": "application/hemx, text/html" };
if (body instanceof URLSearchParams) headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8";
pending.set(target, { abort, done });
showError(target, null);
showPending(target, true);
try {
const response = await fetch(requestUrl(form, data, method), {
@@ -237,7 +249,10 @@
if (!response.ok) throw httpError(response);
await applyResponse(response, rootOf(target));
} catch (error) {
if (error.name !== "AbortError") emit(rootOf(target), "hemx:error", { message: String(error), status: error.status || null });
if (error.name !== "AbortError") {
showError(target, error);
emit(rootOf(target), "hemx:error", { message: String(error), status: error.status || null });
}
} finally {
if (pending.get(target)?.abort === abort) {
pending.delete(target);