fix(ui): make Pagination usable server-rendered, surface output errors

Pagination reported the requested page only through its change output, so on a
server-rendered page the controls did nothing: the parent had to own client
state to react, and the emitted page never became a URL. An optional
hrefTemplate now renders the steps and page numbers as anchors, which work
before hydration and without JavaScript and give each page a crawlable URL.
Buttons remain the default for client-owned lists. End steps are clamped and
marked disabled rather than linking past the first or last page.

Output handler errors are no longer swallowed. Nothing awaits invokeOutput, so
a handler that threw became an unhandled rejection that never reached the
console and presented as a control that silently does nothing. Handler errors
are now reported as WRN-DEV-OUTPUT-HANDLER-ERROR.

Regenerates the component reference and the UI visual contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 15:46:00 +05:30
co-authored by Claude Opus 5
parent 2e42be7b31
commit 5afb2d1875
6 changed files with 210 additions and 35 deletions
+37 -1
View File
@@ -18,10 +18,46 @@ export function registerOutputHandler<T>(
};
}
/**
* Surface a throwing output handler.
*
* Callers of `invokeOutput` do not await it — an output call is fire-and-forget
* from the component's point of view. A handler that throws therefore becomes an
* unhandled rejection that never reaches the console, and the symptom is a
* control that silently does nothing. Reporting here turns that into a named
* diagnostic instead of a dead button.
*/
function reportOutputError(host: OutputHost, name: string, error: unknown): void {
const code = "WRN-DEV-OUTPUT-HANDLER-ERROR";
const message = `The handler bound to output '${name}' threw. The control will appear to do nothing.`;
// eslint-disable-next-line no-console
console.error(`[${code}] ${message}`, error);
try {
window.dispatchEvent(
new CustomEvent("wrnexus:diagnostic", {
detail: {
code,
message,
hydrationId: host.getAttribute ? host.getAttribute("data-wrn-hydration") : null,
detail: { output: name, error: String(error) },
},
}),
);
} catch {
// CustomEvent can be unavailable in minimal DOM test environments.
}
}
export async function invokeOutput<T>(host: OutputHost, name: string, payload?: T): Promise<void> {
const handlers = host.__wrnexusOutputHandlers?.get(name);
if (handlers?.size) {
for (const handler of handlers) await handler(payload);
for (const handler of handlers) {
try {
await handler(payload);
} catch (error) {
reportOutputError(host, name, error);
}
}
return;
}
// Compatibility path for legacy listeners outside a hydrated WRN parent.