diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-14 20:12:59 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-14 20:12:59 +0200 |
| commit | 38b9466aea7daa0ddde91c1de7d8082ac000c238 (patch) | |
| tree | dde2ed88e6fb1ccecfc8fe7f096d26d1ba815c31 /docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md | |
| parent | 4a4849421ddac044212cd0e17f9aee8ff2606292 (diff) | |
| download | qtmaildir-38b9466aea7daa0ddde91c1de7d8082ac000c238.tar.gz qtmaildir-38b9466aea7daa0ddde91c1de7d8082ac000c238.zip | |
docs: close item 66, open 87 and 88, record the row-number trap
Item 66 turns out not to have been the defect it was filed as. The pane
was never blank: an unexpanded thread root rendered the CONVERSATION,
and the same click rendered one message once the thread had been opened,
because the model learned the root's message id only when the replies
arrived. The user's step-by-step account is what separated the two
halves; two probes against a real database had failed to reproduce the
blank pane because there was none.
Closed by carrying firstMessageId in the query and removing the
conversation view, which the user asked for after being told the stubs
not expanding was itself a defect and that the feature was being judged
in a broken state.
Two defects came out of it and are open. 87: auto mark-read still marks
a whole thread, coherent while a root rendered the conversation and not
any more. 88: threadAt(current.row()) answers about the wrong thread for
a reply row, because a tree numbers rows per parent. 87 is blocked on 88
and the entry says why: a fix for 87 was written, mutation-checked,
shipped and reverted the same evening after it marked an unrelated
message read.
CLAUDE.md gains the row-number trap as its own entry rather than leaving
it implied by the item 20 note, plus the rule that a test for a write
path must exercise the reply case: the reverted fix was green because it
asserted on a root selection, the one case where row() is correct. The
cid-prefixing note is corrected to say every caller now passes one
message while explaining why the prefixing stays.
The changelog carries a Removed entry and an Upgrading note, including
that mark_read_delay_ms accepts a negative value to disable auto
mark-read entirely, verified against config.h.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.md | 47 |
1 files changed, 47 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 ed88693..ff2bbba 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 @@ -4937,3 +4937,50 @@ means something. **Item 66 did not reproduce**, which is recorded in that item rather than worked around here. The test was not widened until it failed; the negative result and what it narrows to are in item 66's own entry. + +## 66. Selecting a thread root leaves the message pane blank until a reply has been selected + +**Done 2026-08-14, unreleased.** Closed by removing the conversation view, at +the user's request. Not the defect it was filed as. + +**What it actually was.** The original note described a blank pane. Two probes +against a real database failed to reproduce that, and the user's own step-by-step +account on 2026-08-14 showed the pane was never blank: clicking an unexpanded +thread root rendered the whole CONVERSATION, stubs plus the last two messages +expanded. Clicking a reply then rendered one message, and clicking back to the +root rendered one message from then on. + +**The cause was timing, not a race.** `onThreadSelected` already preferred to +load the root's own first message, but the model learned that message id only +when the replies arrived (`ThreadNode::first`, populated in the +`threadTreeLoaded` handler). A row that had never been expanded therefore had no +id and fell through to a whole-thread render. The identical click behaved +differently once the thread had been opened, which is what the user was +reporting as inconsistency. + +**Fixed by carrying the id in the query.** `ThreadSummary::firstMessageId` comes +from `notmuch_thread_get_toplevel_messages` during the query walk, so it is +known before any expansion and the fallback is unreachable. Free: measured +indistinguishable from not collecting it over a 36,615-thread database, because +it reads the index rather than the message files. The Sent view takes the first +message the query MATCHED instead, since a Sent row stands for what the user +sent and not for whoever opened the thread; libnotmuch has no matched-messages +iterator, only a count, so that branch walks to the first +`NOTMUCH_MESSAGE_FLAG_MATCH` and stops. + +**The conversation view is gone, on the user's decision.** Asked whether to keep +it reachable another way, the user declined: "I have no use for a view that +shows the first n messages as stubs I can't expand and lets me see only the last +two replies." Worth recording that the stubs not expanding was itself a defect, +so the feature was judged in a broken state; the user chose removal anyway when +told. `NotmuchWorker::loadThread` survives with no UI caller, documented as such, +because it is a tested way to read a thread's messages with the match set +resolved. + +**Two defects came out of this and are open as items 87 and 88.** Auto mark-read +still marks the whole thread, which was coherent while a root rendered the +conversation and is not any more; and `threadAt(current.row())` answers about +the wrong thread for a reply row. The second was found the hard way: a fix for +87 was written, mutation-checked, shipped and reverted the same evening after it +marked an unrelated message read. The test that passed had asserted on a ROOT +selection, the one case where `current.row()` is correct. |
