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.md51
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md2
2 files changed, 45 insertions, 8 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 c37f250..6613059 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
@@ -10187,11 +10187,48 @@ 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.
+**The open question was answered by the user on 2026-09-07: the Sent view
+should refresh and show the message.** Indexing is not repainting, and the
+first build left it there: the message became findable the moment it was sent
+and a Sent view on screen still did not show it, because nothing re-ran the
+query. The model cannot insert the row optimistically either, since item 170's
+constraint applies, the query never returned that thread.
+
+**`NotmuchWorker::indexChanged()`, connected to
+`MainWindow::refreshCurrentQuery()`.** Emitted at the end of BOTH
+`indexDraftFile()` and `removeIndexedFile()`, which are the only two entry
+points that change what a path query would return without any query having
+run. One connection covers all three gestures a path view can miss: a sent
+copy indexed, a draft saved, a draft's entry dropped on send. Wiring only the
+indexing half would have left a ghost draft row visible in a Drafts view after
+a send, which is the same defect one signal over.
+
+Four decisions in it that are not incidental:
+
+- **The signal carries nothing.** A payload would invite an optimistic insert,
+ which item 170 rules out; the refresh re-runs the whole query, so which file
+ moved is not the UI's question.
+- **`refreshCurrentQuery()`, never `runCurrentQuery()`.** A send must not clear
+ the selection, the expansions, the undo stack or the message being read in
+ the window behind the composer. The refresh reconciles instead of replacing,
+ returns early when no query has run, and leaves a view of another folder
+ showing no change at all.
+- **Emitted AFTER the database is closed**, so a refresh reaching the database
+ on the next turn of the event loop cannot race the write handle. notmuch
+ permits one open handle per process, the same ordering `applyTags()` and
+ `moveMessages()` obey.
+- **A send fires it twice**, once for the sent copy and once for the draft
+ removal. Both are queued and serialize, and the generation counter makes the
+ second supersede the first, so the cost is one redundant query and the final
+ state is correct. Not worth a coalescing timer.
+
+**One test, end to end through a real send.**
+`aSentMessageAppearsInASentViewAlreadyOnScreen` builds a worker-backed window
+with a sending account whose `sent` is configured, runs the Sent view's own
+`path:` query, asserts it is EMPTY (a test starting from a non-empty view
+could not tell a refresh from a row that was already there), composes and
+sends, and asserts the row arrives with no further gesture: no second
+`returnPressed()`, no sync. A test that re-ran the query by hand would pass
+against the defect. Mutation-checked by disabling the connection, which fails
+on the row count.
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 da568c9..681200e 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,7 +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 |
+| 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. The open question, whether the view should also refresh, was answered yes by the user on 2026-09-07 and built: `indexChanged()` to `refreshCurrentQuery()`. Section in the closed file |
Sizes are rough: XS under an hour, S a sitting, M a session.