Delete all spam, and call folders what the server calls them #65

Closed
opened 2026-08-26 19:01:01 +00:00 by jcoffey-dev · 0 comments
Owner

Delete all spam

Junk Mail can be emptied in one action, from three places: a banner across the top of the folder, the folder's right-click menu, and the list's own menu.

Junk Mail  1
┌──────────────────────────────────────────────────────┐
│ Deleting spam is permanent — it does not go to        │
│ Deleted Items first.            Delete all spam now   │
└──────────────────────────────────────────────────────┘

The messages are destroyed, not moved to Deleted Items — routing spam through the bin leaves you with the same problem in another folder, and "delete all spam" means gone everywhere else. The dialog says so before you commit, and there's no undo.

emptyMailbox already did the hard part: walking a folder a page at a time so it survives maxObjectsInSet, which a Deleted Items of 5192 once didn't. All that changed is which folders it accepts. The guard stays in the store, not only in the menus, so a future caller can't empty the Inbox by asking nicely.

The three entry points share one helper. Three dialogs warning about a permanent deletion in three slightly different ways is how one of them ends up not warning at all. An empty folder shows the item greyed out rather than hidden, so it's where you expect it next time.

Folder naming — two bugs, one cause

This is in the same commit because it touches the same file and because testing the above is what surfaced it.

The mock called its folders Trash and Sent. Stalwart's defaults follow the Exchange convention — Deleted Items, Sent Items. So anything built from a folder's name read differently against the mock than against a real server, and every screenshot in the README shows a folder list no user has.

Worse, in shipping code: the undo toast preferred a hardcoded label over the folder's actual name.

-const name = opts.label ?? mailboxes[toMailboxId]?.name ?? "folder";
+const name = mailboxes[toMailboxId]?.name ?? opts.label ?? "folder";

So on a real server, deleting mail announced "moved to Trash" when the folder is Deleted Items, and reporting spam said "moved to Spam" when it's Junk Mail. The one message whose entire job is saying where mail went was naming somewhere that doesn't exist.

Verified against the mock, by hand

check result
banner shows in Junk with mail in it yes
banner hidden once the folder is empty yes
dialog counts and pluralises "All 1 message will be deleted permanently"
messages destroyed, not moved Junk → 0 rows, Deleted Items still empty
right-click menu on Junk "Delete all spam", danger-styled, greyed out when empty
right-click menu on Archive offers neither
right-click menu on Trash still "Empty Deleted Items" — no regression

Plus 249 web + 75 server tests, typecheck and build clean. The store test that used Junk as its example of a disallowed folder now uses Archive, and a new test asserts Junk empties via destroy with no update call anywhere — i.e. nothing was moved.

Merged 2026-08-26 as coffey-labs/ihasmail@e45c43100f

Rebuilt from: git history, session transcript.

## Delete all spam Junk Mail can be emptied in one action, from three places: a banner across the top of the folder, the folder's right-click menu, and the list's own menu. ``` Junk Mail 1 ┌──────────────────────────────────────────────────────┐ │ Deleting spam is permanent — it does not go to │ │ Deleted Items first. Delete all spam now │ └──────────────────────────────────────────────────────┘ ``` The messages are **destroyed**, not moved to Deleted Items — routing spam through the bin leaves you with the same problem in another folder, and "delete all spam" means gone everywhere else. The dialog says so before you commit, and there's no undo. `emptyMailbox` already did the hard part: walking a folder a page at a time so it survives `maxObjectsInSet`, which a Deleted Items of 5192 once didn't. All that changed is which folders it accepts. **The guard stays in the store**, not only in the menus, so a future caller can't empty the Inbox by asking nicely. The three entry points share one helper. Three dialogs warning about a permanent deletion in three slightly different ways is how one of them ends up not warning at all. An empty folder shows the item greyed out rather than hidden, so it's where you expect it next time. ## Folder naming — two bugs, one cause This is in the same commit because it touches the same file and because testing the above is what surfaced it. **The mock called its folders `Trash` and `Sent`.** Stalwart's defaults follow the Exchange convention — `Deleted Items`, `Sent Items`. So anything built from a folder's name read differently against the mock than against a real server, and every screenshot in the README shows a folder list no user has. **Worse, in shipping code:** the undo toast preferred a hardcoded label over the folder's actual name. ```diff -const name = opts.label ?? mailboxes[toMailboxId]?.name ?? "folder"; +const name = mailboxes[toMailboxId]?.name ?? opts.label ?? "folder"; ``` So on a real server, deleting mail announced *"moved to Trash"* when the folder is *Deleted Items*, and reporting spam said *"moved to Spam"* when it's *Junk Mail*. The one message whose entire job is saying where mail went was naming somewhere that doesn't exist. ## Verified against the mock, by hand | check | result | |---|---| | banner shows in Junk with mail in it | yes | | banner hidden once the folder is empty | yes | | dialog counts and pluralises | "All **1 message** will be deleted permanently" | | messages destroyed, not moved | Junk → 0 rows, **Deleted Items still empty** | | right-click menu on Junk | "Delete all spam", danger-styled, greyed out when empty | | right-click menu on Archive | offers neither | | right-click menu on Trash | still "Empty Deleted Items" — no regression | Plus 249 web + 75 server tests, typecheck and build clean. The store test that used Junk as its example of a *disallowed* folder now uses Archive, and a new test asserts Junk empties via `destroy` with no `update` call anywhere — i.e. nothing was moved. **Merged** 2026-08-26 as coffey-labs/ihasmail@e45c43100f68 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.