`dropPlaceholdersForPacks` wraps `showConfirmModal` in `new Promise`,
but only the OK path resolved — Cancel, Escape, and click-outside left
the Promise pending forever (#84 part 2). Caller's `await` never
returned; any future UI state tied to the result would have been stuck.
Two-layered fix so the wider modal contract is sound, not just the one
call site:
- `showConfirmModal` (web/sidebar/modals.js) now fires `onCancel` for
the Cancel button, Escape, AND overlay-click. An idempotent `settled`
flag keeps the callback to a single fire; the OK path flips the flag
before tearing down the overlay so it doesn't trigger onCancel via
the wrapped `overlay.remove`. Previously only the Cancel button ran
the callback — Esc and click-outside silently bypassed it.
- `dropPlaceholdersForPacks` (web/sidebar/canvas_io.js) now passes
`onCancel: () => resolve({ ok: false, reason: "cancelled" })`,
matching the function's other failure shapes (`no-packs`,
`load-failed`).
Side win: same modals.js change closes a latent Esc/click-outside leak
in the existing "Discard offline copy" recovery toast at
`web/koolook_sidebar.js:130`, which had wired `onCancel` for the Cancel
button only.
Refs #84 (part 2 — part 1, stale serialized link cleanup, is on
codex/pr-cleanups).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>