fix(ux): confirm modal's Cancel/Confirm were dead on the promise-API path
Release / build-and-push (push) Successful in 3m29s

User report: the Purge Archived dialog ('Permanently delete all
archived items?...') opened, and Cancel did nothing — and Confirm
would have been equally dead, leaving the page stuck behind the
overlay until a reload.

Root cause: confirm.ts wired the modal's buttons ONLY inside the
htmx:confirm interceptor. confirmDialog() — the promise API used by
purgeArchivedItems, remove-account, and other TS call sites — never
wired them. So whenever a page's first confirm interaction came from
the promise path (no hx-confirm element clicked first), both buttons
had no listeners. The 2026-10-30 confirm_probe spec only exercised the
hx-confirm path, which is why it survived.

Fix: wireButtons() now runs at module init (the header renders before
deferred modules) and inside confirmDialog() — both idempotent via the
existing data-wired guard.

Pinned by tests/ui/promise_confirm_probe.spec.ts (no env gating): the
purge POST is route-intercepted, so the probe asserts Cancel closes
the modal and Confirm issues the guarded request without deleting any
dev data. hx-confirm path re-pinned green (confirm_probe with BH_FEED).
Verified manually at 390x844: open -> cancelWired=true -> Cancel
closes -> Confirm fires the intercepted request.
This commit is contained in:
John O'Keefe committed 2026-11-02 11:24:55 -05:00
1 parent 04e855e691
commit 2165829d0e
3 files changed
+74 -1

No files matched your search

+12 -1
View File
@@ -25,6 +25,7 @@ function hide(ok: boolean): void {
* Safe to call from any module (import this instead of window.confirm).
*/
export function confirmDialog(message: string): Promise<boolean> {
wireButtons();
const { overlay, message: messageEl } = modalElements();
if (!overlay || !messageEl) {
// Header missing (bare layouts): degrade to the native dialog
@@ -38,7 +39,13 @@ export function confirmDialog(message: string): Promise<boolean> {
});
}
// Wire the static buttons once.
// Wire the static buttons once. Called at module init AND from both
// entry paths below (idempotent via data-wired): the htmx:confirm
// interceptor and the confirmDialog promise API. Wiring only on the
// htmx path left the promise path (purge archived, remove account, ...)
// with dead Cancel/Confirm buttons whenever no hx-confirm element had
// been clicked first — the modal opened and nothing could close it
// (user report: Purge Archived, 2026-11-02).
function wireButtons(): void {
const { overlay } = modalElements();
const accept = document.getElementById("confirm-modal-accept");
@@ -60,6 +67,10 @@ function wireButtons(): void {
}
}
// The header (and its confirm modal) render before deferred modules run,
// so the elements are present at import time.
wireButtons();
// htmx:confirm interceptor: every element with hx-confirm gets the
// modal instead of the browser dialog; confirming issues the request.
// (Canonical htmx pattern — see htmx docs, "confirming with a modal".)