diff options
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 48 |
1 files changed, 42 insertions, 6 deletions
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 43b241e..0029092 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 @@ -238,7 +238,7 @@ taking that too literally. | 162 | Delete fails while a sync is renaming the file underneath it | defect | S | **done, 2026-08-25.** mbsync renames an uploaded file to add its `,U=<uid>` infix and notmuch keeps the pre-`U=` name until that sync's `notmuch new` runs, so `moveMessages` renamed a path that no longer existed and Delete silently did nothing while blaming the destination folder. `moveMessages` now re-resolves by MESSAGE ID when the recorded path is gone: one reindex of that directory, then the filename that exists on disk. Bounded to one retry, so a file genuinely gone still reports. Holding the move during a sync was the other candidate and is NOT the fix: `sendMove` already refuses on notmuch's write lock, but this window sits between mbsync's rename and that sync's `notmuch new`, which touches no lock | | 163 | The message pane shows a stale path, and the composer forks the draft | defect | S | **done, 2026-08-25.** mbsync renames an uploaded file to add its `,U=<uid>` infix while the model still holds the name the query returned. `MaildirName::resolveRenamed()` returns the path unchanged when it exists, else finds the file in that one directory whose unique stem matches; it refuses an ambiguous match and yields nothing for a genuinely missing file. Wired into all THREE read sites: the pane, Reply/Forward, and the draft reopen. The reopen was the one that cost data, forking a draft into two files with two Message-IDs, both reaching the server | | 164 | A draft this application saved keeps `inbox` | defect | S | **dropped** 2026-08-27, NOT A DEFECT. The premise was a measurement artifact: its evidence was `notmuch search --output=tags`, which DISPLAYS the union over a thread, and a reply-draft under an arrived message reads `draft inbox unread` while no message carries both. Re-measured at message level: 0 of 12 drafts carry `inbox`, including nine written on or before 2026-08-25. The `unread` half was real and is item 172 | -| 165 | A draft gets a new Message-ID on every autosave | enhancement | ? | open, 2026-08-25, found while hand-testing 163 and 164. `MessageBuilder::build()` generates an id unconditionally and every autosave calls it, so each revision is a distinct MESSAGE to notmuch and to the server rather than a new version of one. Invisible while the file is replaced correctly, which item 163's fix restores; it is what turned that fork into two messages rather than one duplicated file. Needs a DECISION on what a draft's identity is before any code: a stable id reused at send, a stable id discarded at send, or the status quo. Neither `ComposeContext` nor `OutgoingMessage` has a field to carry an id, so it is not a changed call site | +| 165 | A draft gets a new Message-ID on every autosave | defect | S | open, 2026-08-25, **decided 2026-09-06**: a stable id while drafting, discarded at send, so the sent copy is a different item from the draft. Reclassified from enhancement after the user hit it: four revisions of one reply reached the server as four messages, because mbsync uploads each before the next save removes it locally, and they then thread into the conversation and put a `draft` tag on a Sent row. The local cleanup and the delete-on-send are already built and correct; the id is the whole remaining defect. Needs a field threaded from `forDraft()` through `ComposeContext` and `OutgoingMessage` into `build()` | | 166 | Mail you send to your own other account loses `inbox` | defect | S | **done 2026-08-25**, unreleased. `sent_only()` keeps a message only when EVERY file is inside a sent folder, which is what the carve-out's docstring already claimed. No query can express it, measured; the root comes from `database.mail_root`, with a split-index fixture the ordinary layout cannot provide. Verified read-only against the live index: 780 of 807 still stripped, 27 spared, no arrival affected | | 167 | No way to tell one build of an unreleased version from another | enhancement | XS | **done 2026-08-25**, unreleased. The user chose a counter over a git description: `QTMAILDIR_BUILD_NUMBER`, a cmake option ON by default, increments a counter in the BUILD directory on every build and writes `buildnumber.h`. `QTMAILDIR_VERSION_DISPLAY` carries it; `QTMAILDIR_VERSION` stays clean and is what the window title, `applicationVersion` and the release procedure use | | 168 | Delete is offered on mail already in the trash, and does nothing | defect | S | **done 2026-08-25**, unreleased. Delete is hidden when every selected row is already in its account's trash, Restore when none is, both keyed on the PATH rather than the `deleted` tag. Delete also drops `unread` now, in the same TagChange so one undo returns the folder and the tag together . **Its rule is now incomplete, see item 178** (2026-08-28): the check still judges a row on `firstMessagePath`, which item 177 stopped being a conversation row's identity, so a conversation whose messages disagree about the trash answers on one of them | @@ -1147,17 +1147,24 @@ previous revision only AFTER the new file is safely in place (deliberately, so a failed write cannot lose the draft), so a crash between the two leaves two files, and with two ids they are two drafts rather than one duplicated. -**Approach, and it needs a decision rather than an implementation.** The -question is what a draft's identity IS, and it is not obviously "the id it -will be sent under": +**Decided by the user on 2026-09-06: a stable id while drafting, DISCARDED at +send.** The second of the three positions below. A draft keeps one identity +across all its revisions, so each save replaces the previous message rather +than adding one; the sent copy is minted fresh and is a different item from +the draft, which is the user's own framing. The draft is DELETED on a +successful send, as it already is today, and the sent message carries no +`draft` tag. + +The three positions, kept because the reasoning is what makes the choice +reviewable: - A stable id reused at send time makes the draft and the sent message one message, which is what a user means by "my draft became this email". It also means the id was in a file mbsync uploaded to the drafts folder before - the message was ever sent, and the server has seen it. + the message was ever sent, and the server has seen it. **Not chosen.** - A stable id DISCARDED at send time keeps revisions collapsed while drafting and mints a fresh id for the sent copy. Two identities, and the sent one is - the one that threads. + the one that threads. **Chosen.** - The current behaviour is a third position, and its only virtue is that no id is ever reused for two different things. @@ -1165,6 +1172,35 @@ Whichever is chosen must be checked against `In-Reply-To`/`References` on the eventual send, since `referencesForReply()` builds those from the ORIGINAL's id and a draft of a reply carries both. +**What is already built, measured 2026-09-06 rather than assumed.** Two of the +three things the decision describes exist: + +- `DraftStore::write()` removes the previous revision after the new file is in + place (`draftstore.cpp:78`), so "discard what is left after a successful + save" is done LOCALLY. +- `ComposeWindow` removes the draft on a successful send + (`composewindow.cpp:1620`, through `resolveRenamed()` since item 163), so + "delete the draft on send" is done, and the user confirmed that is the + wanted behaviour rather than keeping the last revision. +- The sent copy is built fresh and filed to the Sent folder, so it never + carries `draft`. The tag the user saw on a Sent row came from the orphan + revisions threading into the conversation, not from the sent message. **No + work is needed for that clause**; it was already true. + +**So the whole remaining defect is the id, and the mechanism is the server.** +Measured on the user's own mail: four drafts of one reply, four distinct +Message-IDs, and every one of the four files carrying a `,U=` infix. mbsync +had uploaded each revision to the drafts folder BEFORE the next save removed +the local file, so the server holds four independent messages and syncs all +four back down. The local cleanup is correct and cannot help: the damage +happens between a save and the next sync, and deleting a local file does not +retract an uploaded one. + +This is what raises the item above "invisible while the file is replaced +correctly". The file IS replaced correctly and four messages still exist. +A stable id gives the server one message being revised rather than four +unrelated ones. + **Constraints.** - **A Message-ID reaches the server and every recipient**, so a reused id is |
