aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md96
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md3
2 files changed, 99 insertions, 0 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 8de26f2..c37f250 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
@@ -10099,3 +10099,99 @@ on the server. Those are existing messages in the drafts folder, and nothing
here retracts them; the four found on the user's own mail were deleted by hand
as a separate act.
+---
+
+## 192. A sent message does not appear in the Sent view until the next sync
+
+**Observed (user, 2026-09-06, by hand):** "after sending, the sent message
+doesn't appear immediately in the sent view."
+
+**Cause, verified in the code and measured on the index.** `ComposeWindow`
+files the sent copy into the account's sent folder with
+`DraftStore::write(folder, built.bytes, "S")` (`composewindow.cpp:1614`) and
+uses the result only to test `filed.ok()`. **`filed.path` is discarded.** No
+signal carries it, so nothing tells notmuch the file exists, and the Sent
+view is a `path:` query over the INDEX rather than a directory listing. The
+message is on disk and invisible until the next `notmuch new`, which on this
+setup is a cron tick up to ten minutes away (item 184).
+
+Measured immediately after a send: 65 files in the account's `Sent/cur`, 64
+messages in the index for the same path. The one missing was the message just
+sent.
+
+**This is item 158's defect, one path over, and the asymmetry is the whole
+story.** That item established the rule for drafts: a saved draft is indexed
+immediately because the Drafts view is a path query and a sync is too far
+away. `MainWindow` wires both halves of it (`mainwindow.cpp:1273` and
+`:1277`): `draftSaved` to `indexDraftFile`, and `draftRemoved` to
+`removeIndexedFile`. The send path therefore already tells the worker to
+DROP the draft's entry, while never telling it to ADD the sent copy's. Half
+a chain, and the half that was missing is the one the user sees.
+
+**Approach.** A `sentCopyFiled(const QString &path)` signal from
+`ComposeWindow`, emitted where the write succeeds, connected to the worker's
+existing `indexDraftFile` slot exactly as `draftSaved` is.
+
+`indexDraftFile` is generic despite its name: it calls
+`notmuch_database_index_file` and, only when a previous path is given,
+removes that entry. It applies no draft-specific tags or flags, and its
+`previousPath` parameter already defaults to empty, so a sent copy passes
+through it correctly with no second slot and no change to the worker. The
+name is worth a note rather than a rename, since renaming it touches every
+existing caller and test for no behavioural gain.
+
+**Constraints.**
+
+- **Only on the success branch.** `filed.ok()` is already tested and a
+ failure already raises "Sent, but not filed"; indexing a path that was
+ never written would put a ghost in the index, which is the defect
+ `removeIndexedFile` exists to prevent.
+- **An account with no `sent` folder configured files nothing**, so there is
+ no path to emit and the signal must not fire. The existing
+ `!account.sent.isEmpty()` guard already scopes this.
+- **Indexing is not repainting.** Neither this nor the draft path re-runs the
+ query, so a view already on screen does not gain the row by itself. That is
+ a separate question from the one this item answers, and the model cannot
+ insert a row optimistically for a thread its query never returned (the
+ constraint item 170 records). Whether the Sent view should refresh itself
+ after a send is left open here rather than assumed.
+
+**Verification.** The signal and its connection are measurable: a test drives
+a send and asserts the path is emitted. Asserting the message is then FINDABLE
+needs a real index, which `test_notmuchworker` already has for
+`indexDraftFileMakesAFileFindable`; the same property for a sent copy is the
+same call and does not need a second test of libnotmuch's behaviour.
+
+**Built 2026-09-06, and it is the three lines the entry predicted.**
+`ComposeWindow::sentCopyFiled(const QString &path)` is emitted on the success
+branch of the filing write, and `MainWindow` connects it to the worker's
+existing `indexDraftFile`.
+
+**The connection is a lambda with `m_worker` as its CONTEXT OBJECT, and that
+is load-bearing rather than incidental.** `indexDraftFile` takes two arguments
+while the signal carries one, and a default argument does not fill that gap
+across a `connect()`, so a direct slot connection will not compile. The
+context object is what decides the thread: with `m_worker` given, Qt queues
+the lambda onto the worker's thread and notmuch never touches the GUI thread.
+A first attempt at simplifying this to a plain call was reverted for exactly
+that reason, and the comment records it, because the simplification LOOKS
+harmless and reads as tidier.
+
+**One test, asserting on the SIGNAL rather than on a notmuch query.**
+`aFiledSentCopyIsAnnouncedForIndexing` drives a real send through
+`ComposeFixture` and asserts the path is emitted, that the file at that path
+exists, and that it is inside the account's Sent folder. Indexing itself is
+already covered against a real database by
+`indexDraftFileMakesAFileFindable` in `test_notmuchworker`; what was unproven
+was that anything ever CALLS it for a sent copy. Asserting the path exists is
+not decoration: announcing a path that was never written is the ghost entry
+`removeIndexedFile()` exists to undo. Mutation-checked by removing the emit.
+
+**Left open deliberately, and it is a real question rather than an
+omission.** Indexing is not repainting. A Sent view already on screen does not
+gain the row from this, because neither this nor the draft path re-runs the
+query, and the model cannot insert a row optimistically for a thread its query
+never returned (item 170's constraint). The message is now findable the moment
+it is sent, which is what the user reported; whether the view should also
+refresh itself is a separate decision.
+
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 7267ea8..da568c9 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
@@ -265,6 +265,7 @@ taking that too literally.
| 189 | The message bar carries only Reply, Forward and Delete | presentation | S | **done 2026-08-29**, unreleased. Star and Archive joined the bar's ordinary branch, Archive leaving the main toolbar as Delete did. `mark_all_read` deliberately did NOT move, at the user's decision: it is the one action that ignores the selection. Item 140's toolbar test listed `archive` as a list-wide action and had to be corrected, which is the classification this item changed. Section in the closed file. Original entry: Asks for Star (`flag`) and Archive on the bar, and raises Mark all read as a question. Two of the three are selection-scoped and fit the bar's rule as it stands; **`mark_all_read` does not**, since it deliberately ignores the selection and acts on every row in the view, which is the one action in the window that does. Needs a decision from the user on that one and on whether Archive LEAVES the main toolbar the way Delete did |
| 190 | Mark spam is not on the message bar, and its icon was never chosen for one | presentation | XS | open, 2026-09-06, from the notes. The bar's ordinary branch carries Reply, Forward, Star, Archive, Delete after item 189 and `spam` is not among them, though it meets the bar's rule (selection-scoped, undoable). Two halves: put it on the bar, and settle the icon, which the note asks to be "a bug, or a skull, or something that signifies bad/evil" and which is `mail-mark-junk` today, chosen for a menu where the label carries the meaning. **Paired with 187**, which changes what the action DOES (moves the file); ordering is the user's call |
| 191 | The Sent view collapses two messages you sent in one conversation into one row | defect | S | **done 2026-09-06**, unreleased, from a hand test. The Sent and Drafts views are flat, but the worker emitted one summary per THREAD and picked a single matched message to stand for it, oldest-first. A conversation replied to twice showed one row, dated by the thread and opening the OLDER message, and the newer one was reachable nowhere. Also a data-safety defect: `firstMessagePath` named the wrong file, so Delete would have moved it. A second half, found by hand once the rows appeared: the sort notmuch applies is a THREAD sort, so both rows took their thread's position and an older reply drew above a newer one. Flat rows are now sorted as one list. Section in the closed file |
+| 192 | A sent message does not appear in the Sent view until the next sync | defect | XS | **done 2026-09-06**, unreleased. The sent copy was filed correctly and never announced, so the index did not know it and the Sent view, a path query, could not show it. Measured as 65 files against 64 indexed. One signal to the worker, mirroring what drafts have had since item 158. Section in the closed file |
Sizes are rough: XS under an hour, S a sitting, M a session.
@@ -972,6 +973,8 @@ on it is measurable and belongs in the same test item 189 corrected. The icon is
a visual judgement and belongs to the user, per the rule in `CLAUDE.md`: hand it
over and let them look.
+---
+
## Deferred, unsized, or split out
Items noted while triaging but not part of the original list. Same numbering