window.confirm blocks the JS event loop, which froze companion remote input while open — the remote user could raise the mesh "Clear" prompt (or reboot / backup-delete / uninstall confirms) and then never dismiss it, because the synthetic events that would dismiss it queue behind the dialog itself. New promise-based appConfirm() (useAppConfirm.ts) + one AppConfirmModal mounted globally in App.vue, built on BaseModal (Teleport-to-body, full-viewport backdrop, glass card — the canonical modal contract). All six native confirm() call sites migrated: mesh clear-all, mesh message delete, dashboard reboot, backup delete, backup USB copy, app uninstall. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
/**
|
|
* Promise-based in-app replacement for window.confirm(). Native browser
|
|
* modals block the JS event loop while open, which freezes the companion
|
|
* remote-input relay (and every other async path) — a remote user could
|
|
* raise a confirm they can never dismiss, since the synthetic events that
|
|
* would dismiss it are queued behind the very dialog they need to close.
|
|
*
|
|
* State is module-level so the single <AppConfirmModal /> mounted in
|
|
* App.vue serves every caller:
|
|
*
|
|
* const ok = await appConfirm('Clear everything? This cannot be undone.')
|
|
* const ok = await appConfirm({ message: '…', confirmLabel: 'Delete', danger: true })
|
|
*/
|
|
import { ref } from 'vue'
|
|
|
|
export interface AppConfirmOptions {
|
|
title?: string
|
|
message: string
|
|
confirmLabel?: string
|
|
cancelLabel?: string
|
|
/** Styles the confirm button as destructive (orange/warning). */
|
|
danger?: boolean
|
|
}
|
|
|
|
interface ActiveConfirm extends AppConfirmOptions {
|
|
_resolve: (value: boolean) => void
|
|
}
|
|
|
|
export const confirmState = ref<ActiveConfirm | null>(null)
|
|
|
|
export function appConfirm(options: AppConfirmOptions | string): Promise<boolean> {
|
|
const opts = typeof options === 'string' ? { message: options } : options
|
|
// A second confirm while one is open cancels the first — the browser
|
|
// primitive this replaces could only ever show one at a time.
|
|
confirmState.value?._resolve(false)
|
|
return new Promise<boolean>((resolve) => {
|
|
confirmState.value = { ...opts, _resolve: resolve }
|
|
})
|
|
}
|
|
|
|
export function resolveConfirm(result: boolean) {
|
|
confirmState.value?._resolve(result)
|
|
confirmState.value = null
|
|
}
|