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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user