From 6de81918dc7a9342fb532d6a5542c868ff65826c Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Wed, 19 Aug 2026 10:09:09 +0200 Subject: docs: close item 103 and tick the delete-to-trash plan Every step of the plan is done. Item 103's section moves to the closed file on the same commit, per this repo's own rule, with its outcome recorded: what was built, the ten defects hand testing found that the suite did not, and the two process gaps closed alongside them. The fact worth carrying forward is the one that damaged real mail. Under mbsync's Create Both, a wrongly named origin folder propagates to the mail server, so any code composing a folder name reaches the server whether it means to or not. Item 118, emptying the trash, remains deferred at the user's request. Co-Authored-By: Claude Opus 5 --- .../2026-08-03-post-0.1.0-usability-closed.md | 87 ++++++++++++++++++++ .../plans/2026-08-03-post-0.1.0-usability.md | 53 +----------- .../plans/2026-08-17-delete-to-trash.md | 94 +++++++++++----------- 3 files changed, 135 insertions(+), 99 deletions(-) diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md index f46fec5..ef51b5c 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md @@ -6612,3 +6612,90 @@ Three lessons, in the order they were paid for: `image/png` that is demonstrably on the clipboard: a Wayland clipboard-manager interaction, or GIMP's own paste path. Neither is this repository's, and neither needs an item here until it is shown to be. + +## 103. What Delete does to mail on the server is undocumented and unverified + +**Observed (user, from the notes):** "verify how 'delete' works", with two +sub-questions of their own: "trash bin (?)" and "delete from server (?)". + +**This is a question first.** The user is not reporting a defect; they are saying +they do not know what the button does to their mail, which for a destructive +action is its own problem regardless of the answer. + +**What the code does (verified).** `src/mainwindow.cpp:825` adds and removes the +`deleted` tag, and nothing else. It is a toggle, it goes through the undo stack, +and it writes a notmuch tag. + +**What that means downstream is what needs verifying, and it is NOT in this +repo.** `maildir.synchronize_flags` is true, so notmuch maps certain tags to +Maildir filename flags, and mbsync carries filename flags to the server. Whether +`deleted` is one of those, whether the user's `~/.mbsyncrc` has `Expunge Both` +(it does, on every channel), and what each provider does with a message flagged +deleted, together decide whether this button is reversible. The undo stack makes +the TAG reversible; it says nothing about what a sync did with it in between. + +**Measured 2026-08-17, and the answer is that Delete does not delete.** notmuch's +tag-to-flag table has no row for `deleted` and no `T` flag, confirmed by a probe +on a throwaway database: `+deleted` left the filename untouched while the +control `+flagged` immediately produced `:2,F`. mbsync carries filename flags, so +`Expunge Both` never sees anything to expunge, and `assets/mailsync.sh` contains +no delete path. Both sub-questions answer no: no trash bin, no deletion from the +server, and the mail stays in the Maildir and the index forever. + +**Specified in +`specs/2026-08-17-delete-to-trash-design.md`. Read that before writing code.** +Delete becomes a real move into the account's trash folder, with a `Trash` +filter beside the other built-ins and a Restore action. Three constraints decide +whether the spec is worth opening: + +- It needs a **mandatory per-account `trash` key**, so an existing config warns + until five keys are added. User-visible: minor bump and an `### Upgrading` + note. +- The worker gains its **first non-tag mutation**, a rename plus a reindex, + deliberately shaped as `moveMessages(ids, destFolder)` so Send in v2 reuses it + for Drafts and Sent. +- **848 messages carry the old tag** while sitting in an inbox, and would be + invisibly half-deleted after the change. A repeatable menu entry queries them + into the list for review; it is not a startup migration. + +The no-confirmation rule survives, with its justification amended: the mail +lands in a browsable folder, but reversibility is now bounded by the provider +where the trash is purged on a timer. + +**Size: S** for the investigation, which is done. The build is **M**. + +**Built 2026-08-19, over eight commits on `delete-to-trash`.** Delete moves the +file into the account's `trash` folder and records where it came from in a +`deleted-from:` tag, because the file has moved and nothing else still +knows. A `Trash` filter sits beside the other four, `Restore from trash` +(`Ctrl+R`) is the inverse, and `Find stranded deleted mail` (`Ctrl+Alt+T`, a +menu entry only at the user's request) lists the 848 messages the old behaviour +left tagged but unmoved. It reports and moves nothing: acting on its own would +be a bulk delete with no selection behind it. + +**Ten defects were found by hand testing, not by the suite**, each reproduced +with a probe before being fixed. One had already damaged real mail: a folder +name containing a space was truncated by the origin tag, creating a bogus folder +beside the real one and stranding four messages of a thread. mbsync had adopted +it and written state files for it. That is the fact worth carrying out of this +item: **under `Create Both` a wrongly named origin folder propagates to the mail +server**, so any code composing a folder name is reaching the server whether it +means to or not. Tags are joined by a TAB across the thread boundary for the +same reason, since a notmuch tag may contain a space. + +**Two process gaps closed alongside it.** Adding an action is FIVE places and +this repository's CLAUDE.md said four; the missing one is a menu, and nothing +enforced it, so `restore` shipped reachable by a chord and by nothing a user +could see. `everyActionIsReachableFromAMenu()` now asserts it and found three +more of the same (`open_thread`, `clear_pane`, `clear_selection`). And a guard +in `deletingTwiceLeavesNoOriginTagBehind()` queried through the query bar in the +gap between the file rename and the tag writes, where a run returns zero rows +forever because `QTRY_VERIFY` re-reads `rowCount()` and never re-runs the query: +3 failures in 12 runs, each burning a full 15s timeout, 0 in 8 after asking the +database directly. + +**Emptying the trash is item 118 and remains deferred**, at the user's request. + +**Verified:** clean build with no warnings from any changed file, 24 of 24 +suites over three consecutive runs, 211 tests in `test_mainwindow`. Every one of +the spec's six testing bullets has a test, each mutation-checked. diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md index 6a5e64d..1f71006 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md @@ -167,7 +167,7 @@ taking that too literally. | 100 | The message pane offers Back, Forward, Reload and Save page, none of which mean anything | defect | XS | **done** 2026-08-17, unreleased. `MessageView::removeBrowserActions()` filters the standard menu by `pageAction()` POINTER, never by text; `ViewSource` went with them, and stranded separators are swept | | 101 | Sync is account-aware for edits but not for the account the user is looking at | workflow | S | open; item 49 built the edit half deliberately. Needs a decision, see the entry | | 102 | The rules table shows no note, so the field explaining a rule is invisible until it is opened | workflow | XS | **done** 2026-08-17, unreleased. A Note column before `ColumnCount`, so the appended Matches column stays last. Found a second defect on the way: `restoreState` REFUSES a header state with a different column count, and the sized flags were being set regardless | -| 103 | What Delete does to mail on the server is undocumented and unverified | clarification | S | open; a question first, possibly no code at all | +| 103 | What Delete does to mail on the server is undocumented and unverified | clarification | S+M | done; Delete moves to the account trash, with Restore and a stranded-mail cleanup. Section in the closed file | | 104 | Mail visible in Thunderbird never reaches qtmaildir | defect | ? | open, reported 2026-08-16, cause NOT established. Most likely outside this repo; see the entry before writing code | | 109 | A root card's own message is invisible to a message-scoped write | defect | S | **done** 2026-08-16, unreleased. Found by hand-testing 108. `applyMessageTagChange` and `messageById` searched only the loaded replies, and a root's message is never among them, so the ORDINARY gesture repainted nothing and wiped the pane's chip row | | 110 | A card and the message pane show tags belonging to a message's siblings | defect | S | **done** 2026-08-16, unreleased. Found by hand-testing 109 against a real 4-message thread. `ThreadSummary::tags` is notmuch's UNION; a card standing for one message drew it. Also the reason a root card could not repaint at all | @@ -485,57 +485,6 @@ reaches it (item 42), so most of this exists. **Size: S** for the on-demand button, XS for the visibility half. Ask which. -## 103. What Delete does to mail on the server is undocumented and unverified - -**Observed (user, from the notes):** "verify how 'delete' works", with two -sub-questions of their own: "trash bin (?)" and "delete from server (?)". - -**This is a question first.** The user is not reporting a defect; they are saying -they do not know what the button does to their mail, which for a destructive -action is its own problem regardless of the answer. - -**What the code does (verified).** `src/mainwindow.cpp:825` adds and removes the -`deleted` tag, and nothing else. It is a toggle, it goes through the undo stack, -and it writes a notmuch tag. - -**What that means downstream is what needs verifying, and it is NOT in this -repo.** `maildir.synchronize_flags` is true, so notmuch maps certain tags to -Maildir filename flags, and mbsync carries filename flags to the server. Whether -`deleted` is one of those, whether the user's `~/.mbsyncrc` has `Expunge Both` -(it does, on every channel), and what each provider does with a message flagged -deleted, together decide whether this button is reversible. The undo stack makes -the TAG reversible; it says nothing about what a sync did with it in between. - -**Measured 2026-08-17, and the answer is that Delete does not delete.** notmuch's -tag-to-flag table has no row for `deleted` and no `T` flag, confirmed by a probe -on a throwaway database: `+deleted` left the filename untouched while the -control `+flagged` immediately produced `:2,F`. mbsync carries filename flags, so -`Expunge Both` never sees anything to expunge, and `assets/mailsync.sh` contains -no delete path. Both sub-questions answer no: no trash bin, no deletion from the -server, and the mail stays in the Maildir and the index forever. - -**Specified in -`specs/2026-08-17-delete-to-trash-design.md`. Read that before writing code.** -Delete becomes a real move into the account's trash folder, with a `Trash` -filter beside the other built-ins and a Restore action. Three constraints decide -whether the spec is worth opening: - -- It needs a **mandatory per-account `trash` key**, so an existing config warns - until five keys are added. User-visible: minor bump and an `### Upgrading` - note. -- The worker gains its **first non-tag mutation**, a rename plus a reindex, - deliberately shaped as `moveMessages(ids, destFolder)` so Send in v2 reuses it - for Drafts and Sent. -- **848 messages carry the old tag** while sitting in an inbox, and would be - invisibly half-deleted after the change. A repeatable menu entry queries them - into the list for review; it is not a startup migration. - -The no-confirmation rule survives, with its justification amended: the mail -lands in a browsable folder, but reversibility is now bounded by the provider -where the trash is purged on a timer. - -**Size: S** for the investigation, which is done. The build is **M**. - ## 104. Mail visible in Thunderbird never reaches qtmaildir **Observed (user, from the notes):** "sync doesn't work compared to thunderbird. diff --git a/docs/superpowers/plans/2026-08-17-delete-to-trash.md b/docs/superpowers/plans/2026-08-17-delete-to-trash.md index 4e52789..70742b5 100644 --- a/docs/superpowers/plans/2026-08-17-delete-to-trash.md +++ b/docs/superpowers/plans/2026-08-17-delete-to-trash.md @@ -52,7 +52,7 @@ No new files. Every change lands in a file that already owns that responsibility - Modify: `src/config.cpp` (`Account::trashQuery()` beside `sentQuery()` at line 130, and the account parser) - Test: `tests/test_config.cpp` -- [ ] **Step 1: Write the failing test** +- [x] **Step 1: Write the failing test** Add to `tests/test_config.cpp`, and declare both slots in the `private slots:` block: @@ -94,7 +94,7 @@ void TestConfig::aBracketedTrashFolderIsQuoted() } ``` -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** ```bash cmake --build build 2>&1 | tail -5 @@ -102,7 +102,7 @@ cmake --build build 2>&1 | tail -5 Expected: FAIL to compile, `'trash' is not a member of 'Account'`. -- [ ] **Step 3: Write minimal implementation** +- [x] **Step 3: Write minimal implementation** In `src/config.h`, add to `Account` immediately after the `sent` member: @@ -143,7 +143,7 @@ Then find where the account parser reads `sent` (search for `QStringLiteral("sen account.trash = settings.value(QStringLiteral("trash")).toString(); ``` -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config @@ -151,7 +151,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config Expected: PASS, all tests. -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add src/config.h src/config.cpp tests/test_config.cpp @@ -170,7 +170,7 @@ First read how warnings are currently raised: search `src/config.cpp` for `m_warnings` and copy the surrounding form exactly. Do not invent a new mechanism. -- [ ] **Step 1: Write the failing test** +- [x] **Step 1: Write the failing test** ```cpp void TestConfig::anAccountWithoutATrashFolderWarns() @@ -204,7 +204,7 @@ void TestConfig::anAccountWithoutATrashFolderWarns() } ``` -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config -functions | grep -i trash @@ -213,7 +213,7 @@ QT_QPA_PLATFORM=offscreen ./build/tests/test_config anAccountWithoutATrashFolder Expected: FAIL, `warnings` is empty. -- [ ] **Step 3: Write minimal implementation** +- [x] **Step 3: Write minimal implementation** In the account-loading loop in `src/config.cpp`, after the account is parsed and before it is appended: @@ -232,7 +232,7 @@ before it is appended: } ``` -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config @@ -240,7 +240,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config Expected: PASS. -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add src/config.cpp tests/test_config.cpp @@ -256,7 +256,7 @@ git commit -m "feat(config): warn when an account configures no trash folder" - Modify: `src/config.cpp` (`kQueryGenerators` line 60, `builtinFilter()` line 757, `resolvedQuery()` line 790, `allTrashQuery()` beside line 140) - Test: `tests/test_config.cpp` -- [ ] **Step 1: Write the failing test** +- [x] **Step 1: Write the failing test** ```cpp void TestConfig::theTrashFilterComposesPerAccount() @@ -307,7 +307,7 @@ void TestConfig::theTrashFilterMatchesNothingWithoutAFolder() } ``` -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config theTrashFilterComposesPerAccount @@ -315,7 +315,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config theTr Expected: FAIL, `trash.isGenerated()` is false because the generator is unknown. -- [ ] **Step 3: Write minimal implementation** +- [x] **Step 3: Write minimal implementation** In `src/config.cpp`, add to `kQueryGenerators` at line 60, last so it sits rightmost on the query row: @@ -377,7 +377,7 @@ And in the per-account branch, beside the `sent` case: } ``` -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config @@ -385,13 +385,13 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_config Expected: PASS. -- [ ] **Step 5: Verify the mutation fails** +- [x] **Step 5: Verify the mutation fails** Temporarily change the per-account branch to `return allTrashQuery();` and rebuild. `theTrashFilterComposesPerAccount` must FAIL on the `QCOMPARE`. Revert the mutation. This proves the test asserts on the string rather than on rows. -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add src/config.h src/config.cpp tests/test_config.cpp @@ -412,7 +412,7 @@ This is the first mutation in the project that is not a notmuch tag. Read uses is required, not stylistic, because notmuch permits one open handle per process. -- [ ] **Step 1: Write the failing test** +- [x] **Step 1: Write the failing test** Add to `tests/test_notmuchworker.cpp`, declaring each slot in `private slots:`: @@ -536,7 +536,7 @@ void TestNotmuchWorker::moveMessagesReportsOnlyWhatMoved() } ``` -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** ```bash cmake --build build 2>&1 | tail -5 @@ -544,7 +544,7 @@ cmake --build build 2>&1 | tail -5 Expected: FAIL to compile, `'moveMessages' is not a member of 'NotmuchWorker'`. -- [ ] **Step 3: Write minimal implementation** +- [x] **Step 3: Write minimal implementation** In `src/notmuchworker.h`, beside `applyTags()`: @@ -675,7 +675,7 @@ void NotmuchWorker::moveMessages(const QStringList &messageIds, } ``` -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_notmuchworker @@ -683,7 +683,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_notmuchworke Expected: PASS, all tests. -- [ ] **Step 5: Verify the ordering mutation fails** +- [x] **Step 5: Verify the ordering mutation fails** Move the `notmuch_database_remove_message` call to immediately BEFORE the `notmuch_database_index_file` call and rebuild. @@ -691,7 +691,7 @@ Move the `notmuch_database_remove_message` call to immediately BEFORE the the mutation. This is the single most important check in the task: the wrong order silently destroys user tags and every other test still passes. -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add src/notmuchworker.h src/notmuchworker.cpp tests/test_notmuchworker.cpp @@ -716,7 +716,7 @@ selection, resolved through `everySelectedRowHasTag()`, and `CLAUDE.md` records two separate bugs that lived in those three lines. Preserve the toggle: Delete twice still means "put it back". -- [ ] **Step 1: Write the failing test** +- [x] **Step 1: Write the failing test** ```cpp void TestMainWindow::deleteMovesTheMessageToTrash() @@ -793,7 +793,7 @@ whichever helpers are missing, following the form of the ones already there. observable state with `QTRY_VERIFY_WITH_TIMEOUT` and never on worker signals or a fixed `qWait(n)`. -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow deleteMovesTheMessageToTrash @@ -801,7 +801,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow d Expected: FAIL, the message is still in Inbox because Delete only tags. -- [ ] **Step 3: Write minimal implementation** +- [x] **Step 3: Write minimal implementation** Add a `MoveCommand` in `src/mainwindow.h`, beside `MessageTagCommand` around line 1176, following its shape exactly: @@ -888,7 +888,7 @@ the current path, and pushes one `MoveCommand` carrying `deleted` and `deleted-from:`. An account with no `trash` key contributes nothing and reports through `statusMessage`, since Task 2 already warned at load. -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow @@ -896,7 +896,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow Expected: PASS, all tests. -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add src/mainwindow.h src/mainwindow.cpp tests/test_mainwindow.cpp @@ -918,7 +918,7 @@ are enforced by tests that fail confusingly: `KeyMap::knownActions()` (a `defaultBindings()` (every action must be keyboard-reachable), and the icon table (every action must carry one). -- [ ] **Step 1: Write the failing test** +- [x] **Step 1: Write the failing test** ```cpp void TestMainWindow::restoreIsOnlyEnabledInTheTrashView() @@ -985,7 +985,7 @@ void TestMainWindow::restoreFallsBackToInboxWithoutAnOriginTag() } ``` -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow restoreIsOnlyEnabledInTheTrashView @@ -993,7 +993,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow r Expected: FAIL, there is no `restore` action. -- [ ] **Step 3: Write minimal implementation** +- [x] **Step 3: Write minimal implementation** Add `"restore"` to `KeyMap::knownActions()` and give it a binding in `defaultBindings()`. Check what is free first: @@ -1030,7 +1030,7 @@ generator's, for the current account selection. `restoreSelected()` reads each row's `deleted-from:` tag for its destination and falls back to `Inbox`, reporting which through `statusMessage`. -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow @@ -1040,7 +1040,7 @@ ctest --test-dir build --output-on-failure Expected: PASS, 24 of 24. The keymap and icon-table tests fail loudly if a place was missed. -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add src/mainwindow.h src/mainwindow.cpp src/keymap.cpp tests/test_mainwindow.cpp @@ -1059,7 +1059,7 @@ git commit -m "feat: restore mail from the trash view" The user's constraint, verbatim: "the cleanup should be a menu entry only, not to be confused with the filter Trash". Do not add a sixth button. -- [ ] **Step 1: Write the failing test** +- [x] **Step 1: Write the failing test** ```cpp void TestMainWindow::theCleanupQueryFindsStrandedMail() @@ -1109,7 +1109,7 @@ void TestMainWindow::theCleanupQueryExcludesMailAlreadyInTrash() } ``` -- [ ] **Step 2: Run test to verify it fails** +- [x] **Step 2: Run test to verify it fails** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow theCleanupQueryFindsStrandedMail @@ -1117,7 +1117,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow t Expected: FAIL, there is no `cleanup_stranded` action. -- [ ] **Step 3: Write minimal implementation** +- [x] **Step 3: Write minimal implementation** Register the action in `knownActions()`, `defaultBindings()` and the icon table as in Task 6, then: @@ -1141,7 +1141,7 @@ as in Task 6, then: Add it to a menu, not to the query row. Find where the other menu entries are built and follow that form. -- [ ] **Step 4: Run test to verify it passes** +- [x] **Step 4: Run test to verify it passes** ```bash cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow @@ -1149,7 +1149,7 @@ cmake --build build && QT_QPA_PLATFORM=offscreen ./build/tests/test_mainwindow Expected: PASS. -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add src/mainwindow.h src/mainwindow.cpp src/keymap.cpp tests/test_mainwindow.cpp @@ -1168,7 +1168,7 @@ Every user-facing string added above needs a translation, and `lrelease` silently DROPS an unfinished string and ships it as English inside an otherwise Italian UI. Item 108 shipped fifteen strings that way. -- [ ] **Step 1: Refresh the translation source** +- [x] **Step 1: Refresh the translation source** ```bash lupdate-qt6 src/ -ts translations/qtmaildir_it_IT.ts -no-obsolete -locations none @@ -1178,7 +1178,7 @@ Expected: a clean run reporting zero context warnings. A "tr() cannot be called without context" warning means a literal needs `QT_TRANSLATE_NOOP("TheClass", "Text")` rather than `tr()`. -- [ ] **Step 2: Translate every new string** +- [x] **Step 2: Translate every new string** Open `translations/qtmaildir_it_IT.ts` and fill in each ``. The new strings are the Trash filter name, the Restore and @@ -1188,7 +1188,7 @@ messages. Do NOT translate notmuch query syntax. `tag:deleted`, `path:` and `deleted-from:` are wire format. -- [ ] **Step 3: Verify nothing is unfinished** +- [x] **Step 3: Verify nothing is unfinished** ```bash lrelease-qt6 translations/qtmaildir_it_IT.ts @@ -1197,7 +1197,7 @@ lrelease-qt6 translations/qtmaildir_it_IT.ts Expected: "Generated N translation(s) (N finished, 0 unfinished)". A nonzero unfinished count means a string will ship as English. -- [ ] **Step 4: Run the translations test** +- [x] **Step 4: Run the translations test** ```bash ctest --test-dir build -R translations --output-on-failure @@ -1205,7 +1205,7 @@ ctest --test-dir build -R translations --output-on-failure Expected: PASS. -- [ ] **Step 5: Write the changelog entry** +- [x] **Step 5: Write the changelog entry** Under `## [Unreleased]` in `CHANGELOG.md`, with an `### Upgrading` section, since a working config now warns until five keys are added: @@ -1243,7 +1243,7 @@ Note that Delete's reversibility depends on your provider: a trash folder that the provider purges on a timer will eventually remove the mail for good. ``` -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add translations/qtmaildir_it_IT.ts CHANGELOG.md @@ -1254,7 +1254,7 @@ git commit -m "i18n: translate the trash strings, and document the trash key" ## Task 9: Full verification -- [ ] **Step 1: Clean build** +- [x] **Step 1: Clean build** ```bash rm -rf build @@ -1264,7 +1264,7 @@ cmake --build build Expected: no warnings from the changed files. -- [ ] **Step 2: Full suite** +- [x] **Step 2: Full suite** ```bash ctest --test-dir build --output-on-failure @@ -1272,13 +1272,13 @@ ctest --test-dir build --output-on-failure Expected: 24 of 24 passing. -- [ ] **Step 3: Confirm the spec's claims hold** +- [x] **Step 3: Confirm the spec's claims hold** Re-read `docs/superpowers/specs/2026-08-17-delete-to-trash-design.md` and check each testing bullet has a test. The spec lists six; all six are covered by Tasks 1, 3, 4, 5, 6 and 7. -- [ ] **Step 4: Hand the build to the user** +- [x] **Step 4: Hand the build to the user** Do NOT run `./build/src/qtmaildir`. Report what to look at: -- cgit v1.2.3