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.md59
1 files changed, 59 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 55c5ee7..27b4e28 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
@@ -9315,3 +9315,62 @@ before proceeding. A summary left at the default is a MESSAGE row, so a test
meaning to exercise a conversation would quietly exercise the other branch and
pass for the wrong reason, which is the fixture trap the top-level document
records.
+
+## 181. The thread dashboard does not follow a write to the conversation it shows
+
+**Done 2026-08-29**, unreleased, on `thread-row-identity`, from the user's
+notes: "the thread dashboard doesn't update live with the modifications applied
+to the list pane. If I mark the thread as read, the dash still reports N
+unread".
+
+**Cause, verified in the code.** `ThreadDashboard` draws a `ThreadDigest`,
+which the worker builds from the INDEX and which reached the pane in exactly
+two places: the selection handler, and `onThreadDigestLoaded()`. A tag write
+updated the model optimistically and repainted the card beside it, and nothing
+touched the digest, so the pane went on reporting the unread count, the
+progress bar and the "Waiting for you" list the conversation had when it was
+opened.
+
+**Reachable from the dashboard's own button.** It carries Mark all read,
+Archive and Delete in its bottom strip (item 177), so the user presses Mark all
+read and the number directly above it does not move. That is the worst version
+of the defect and the one the note reports.
+
+**Fix.** `MainWindow::refreshDashboardDigest()` re-asks the worker for the
+digest of the conversation the pane is showing, and returns at once when the
+pane is showing anything else, which is the ordinary case. It bumps
+`m_digestGeneration` like any other request, so the three guards in
+`onThreadDigestLoaded()` discard a reply that arrives after the user has moved
+on. No placeholder digest, unlike the selection path: the pane already holds
+this conversation, and blanking it to re-fill it would flicker the whole
+dashboard for a change to one number.
+
+**Called from `onTagsApplied()`, where a write is CONFIRMED, and this is the
+part worth keeping.** The first attempt called it from both write funnels,
+beside the optimistic model update, by analogy with every other optimistic
+repaint. That is wrong here and the test caught it: the digest is rebuilt from
+the index, so a refresh queued beside the write reaches the worker BEFORE the
+write does and answers from the state before it. The reasoning that made it
+look right is the reasoning that applies to a model update, which needs no
+round trip; anything read back from the index has to wait for the write to
+land.
+
+**Every write, not a chosen subset.** The user's decision, and the cheaper rule
+to keep: narrowing it to the writes that change what the dashboard happens to
+draw today is a list the dashboard can outgrow silently, and this costs a round
+trip only while a conversation is on screen. It sits beside the unsynced-edits
+counter in the same handler for the same reason.
+
+**Re-requested rather than edited in place.** The digest is a derived summary,
+senders and buckets and timestamps and a capped unread list, so updating it
+here would be a second place that has to agree with the worker about what a
+write did, which is item 180's complaint exactly.
+
+**Test.** `theDashboardFollowsAWriteToTheConversationItShows`, worker-backed
+over a real two-message conversation, driven through the `mark_all_read` ACTION
+rather than the private funnel: that is the path the dashboard's own button
+takes. It asserts the pane carries the unread state BEFORE the write, so the
+assertion after it means something, and mutation-checked by removing the call.
+Note the failing runs take 45s and the passing one 295ms: the `QTRY` timeouts
+expire when the refresh never comes, which reads as a slow test rather than a
+missing feature.