aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md96
1 files changed, 96 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.
+