Line references are pinned to main at afbd7c45.
Pre-existing on main. Not introduced by #374 — that PR collapsed two sequential guards into one condition, which preserved the original order. Do not bisect to it. Pre-split proof below.
Where
src/lib/settings-controller/settings-controller.ts L733-757:
delete: (libraryId, confirmationName) =>
runLibraryWork(async () => {
const library = findLibrary(libraryId);
const displayName =
library?.display_name ?? i18next.t("settings.library.thisLibrary");
const message = /* confirmDeleteRemote | confirmDeleteLocal */;
if (!window.confirm(message) || confirmationName !== displayName) {
return; // <- silent on mismatch
}
await librarySession.adoptRegistry(
await backend.librarySetup.deleteLibrary(libraryId),
);
}),
The caller is src/components/Settings/SettingsLibrarySection.tsx L107-115, driven by a generic InputDialog (L339-348).
What goes wrong
The full flow a user sees:
- Click delete. An
InputDialogopens titled "type <name> to confirm delete". - Type the name and confirm. The dialog closes.
- A second, native
window.confirmappears asking to confirm the deletion. - Accept it.
- Nothing happens. No library removed, no error, no message.
Step 5 is the bug: the name comparison runs after the native prompt, and a mismatch returns silently. runLibraryWork has already cleared view.library.error at L286 and the early return writes nothing back, so there is no state for any surface to render. From the user's side, they confirmed a destructive action twice and the app ignored them without explanation.
InputDialog does not validate the typed value against the expected name — src/components/Settings/InputDialog.tsx L84-85 only disables confirm on empty input (disabled={!value.trim()}). So a single typo, or trailing whitespace differences against a display_name, reaches the silent branch.
Conditions
Any mismatch between the typed text and the library's exact display_name. A typo is sufficient. Note also that displayName falls back to the translated string settings.library.thisLibrary when findLibrary(libraryId) misses, in which case the typed name can never match and delete is unconditionally a no-op.
Pre-existing proof
32ec36c6:src/components/Settings/settings-overlay.library-actions.ts L263-269 — the same two guards, in the same order, both returning silently:
if (!window.confirm(confirmationMessage)) {
return;
}
if (confirmationName !== displayName) {
return;
}
Fix direction (my view, not the reviewer's)
The suggestion was to compare the name first and write view.library.error on a mismatch, with a new settings.library.confirmNameMismatch key in every locale. Reordering the guards is right and I would do that regardless — a mismatch should never reach a destructive prompt.
I would not add the error message, though. Surfacing "the name you typed does not match" after the dialog has closed is the wrong place to say it: the input the message refers to is gone, and the user has to reopen the dialog and retype from scratch.
The confirmation belongs in the dialog. Give InputDialog an optional expected value (or add a purpose-built confirm-by-name dialog) so the confirm button stays disabled until the typed text matches, with the mismatch shown inline next to the field the user is typing into. Then the controller's comparison becomes a backstop that is genuinely unreachable from the UI, and it can keep returning early without a message.
That also raises the question of whether the native window.confirm should exist at all once type-to-confirm is enforced properly. Two confirmations for one action, one of them an unstyled OS dialog, reads as belt-and-braces rather than as design. I would drop the window.confirm in the same change, but that is a product-surface decision — see docs/references/product-standards.md — so it wants a maintainer's call rather than a drive-by removal.
Not fixing here
Filed for tracking only.