fix(documents): stop Backspace deleting a document, and show the key that does

A focused card deleted on Backspace as well as Delete. The timeline's
roving tabindex gives a card focus the moment someone tabs into the
grid, Backspace is the reflex "go back" key, and a card carries no
delete control at all — so the reflex press removed a medical document
with nothing but a transient undo toast behind it.

Delete alone now deletes. The key resolution moves into a pure helper
in vault-utils so the contract is testable without a render harness,
and the card stops keeping that contract in a code comment: the overlay
button carries aria-keyshortcuts, and a kbd chip appears while the card
holds focus. The label is per-locale (Entf / Suppr / Supr / Canc).
This commit is contained in:
Marc-André Bombeck committed 2026-07-27 14:47:56 +02:00
1 parent 20824aa798
commit e4a4c996b7
10 files changed
+111 -9

No files matched your search

@@ -65,6 +65,38 @@ describe("<DocumentCard>", () => {
expect(html).not.toContain("Download only");
});
it("announces and shows the Delete shortcut when the card is deletable", () => {
const html = render(
<DocumentCard
document={doc()}
selected={false}
onToggleSelected={noop}
onOpen={noop}
onDelete={noop}
highlighted={false}
/>,
);
// The keyboard contract used to live only in a code comment. It is now
// announced to assistive tech and drawn while the card holds focus.
expect(html).toContain('aria-keyshortcuts="Delete"');
expect(html).toContain('data-slot="document-delete-hint"');
expect(html).toContain("Del");
});
it("offers no delete shortcut when the card carries no delete handler", () => {
const html = render(
<DocumentCard
document={doc()}
selected={false}
onToggleSelected={noop}
onOpen={noop}
highlighted={false}
/>,
);
expect(html).not.toContain("aria-keyshortcuts");
expect(html).not.toContain('data-slot="document-delete-hint"');
});
it("shows the download-only badge for attachment-class documents", () => {
const html = render(
<DocumentCard
@@ -6,6 +6,7 @@ import {
buildVaultListApiSearch,
classifyUploadFailure,
countActiveFilters,
documentCardKeyAction,
documentDateKey,
expandRangeSelection,
formatBytes,
@@ -411,3 +412,22 @@ describe("isRecentlyUploaded / isDocumentProcessing / hasProcessingDocument", ()
expect(hasProcessingDocument([], NOW)).toBe(false);
});
});
describe("documentCardKeyAction", () => {
it("deletes on Delete and selects on Space", () => {
expect(documentCardKeyAction("Delete")).toBe("delete");
expect(documentCardKeyAction(" ")).toBe("select");
});
it("does NOT delete on Backspace", () => {
// The reflex "go back" key on a card that takes focus by default, with
// no other delete control and only a transient undo toast behind it.
expect(documentCardKeyAction("Backspace")).toBeNull();
});
it("ignores every other key", () => {
for (const key of ["Enter", "Escape", "a", "ArrowDown", "Del"]) {
expect(documentCardKeyAction(key)).toBeNull();
}
});
});