aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/plans')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md42
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md1
2 files changed, 43 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 f5ae9f0..c83e41c 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
@@ -10870,3 +10870,45 @@ not a backlog item picked up on a Tuesday.
to, since there are three and nothing in the files names a preference.
**Shares the write-during-sync question with 205.** Answer it once.
+
+## 211. Archiving thousands of selected rows freezes the window
+
+**Observed.** From the user's notes: a `tag:promo` search returned about 10k
+rows; Select all, then Archive, froze the window with one CPU pinned and no
+sign of progress.
+
+**Cause.** Measured on the live process, not read: three gdb samples of the
+UI thread, all identical, worker thread idle. Nothing had reached the worker,
+because the optimistic model update runs before the write is sent. The loop in
+`sendMessageTagChange` calls `applyMessageTagChange` once per message, which
+walked every thread to find it and emitted `dataChanged`. `MainWindow`'s
+`dataChanged` handler ran `refreshUnreadAction`, whose `selectionTagPresence`
+walked the whole selection calling `messageById`, itself another walk of
+every thread. N writes times N selected rows times N threads is about 10^12
+string compares at 10k. `QStringList::contains` in the membership code added
+smaller quadratic terms on top.
+
+**Approach.** `ThreadListModel` answers id lookups from a lazily rebuilt hash
+(`rowForThread`, `rowForMessage`). The model's own structural signals mark it
+stale, and so do the two in-place id changes (`reconcile` replacing a summary,
+`setThreadMessages` setting `first`). The first row wins, which matches the
+walks it replaces. `MainWindow::batchModelWrites()` holds the
+`dataChanged`-driven label refresh while a loop of optimistic writes runs and
+refreshes once afterwards. All nine loops go through it. Thread-id membership
+tests use a `QSet`. The whole path is now linear.
+
+**Constraints.** Correctness now depends on the index being invalidated.
+`idLookupsFollowEveryChangeToTheRows` asks a question whose answer moved after
+each kind of change, and it was mutation-checked against dropping the
+`reconcile` mark and the `rowsRemoved` hook. Both failed it.
+
+**Feedback.** First skipped as unneeded, then asked for after the hand test,
+where the archive worked (0 promo messages left in the inbox) and the user
+could not tell. The announcement used to vanish on a timer. It now reads
+"<action>, applying…" until the worker has run the write, then a transient
+"<action>, done". The trigger is a queued round trip through the worker's
+queue, not `tagsApplied()`, which stays silent for a write that moved nothing,
+while moves report through another signal. An error bumps the same ticket, so
+a late "done" cannot cover the error message. Tested by
+`aWriteSaysItIsApplyingUntilTheWorkerHasRunIt`, which fails when the reply is
+dropped.
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 48c31af..a3df5be 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
@@ -148,6 +148,7 @@ taking that too literally.
| 208 | No way to add a new correspondent to the contacts | workflow | ? | open, 2026-09-18, **asked for by the user** while settling 204: an "add to contacts" button on mail from someone not in the store. Writes a vcard, so it lands on 205's writer rather than 204's reader. **Needs a brainstorm when picked up** |
| 209 | The calendar has no Week view | presentation | M | open, from 206's brainstorm. The occurrence list is the seam: a new painter over CalendarItem, no store change |
| 210 | A repeating event cannot be edited "this and following" | workflow | S-M | open, from 206's brainstorm. Splits a series into two files, divides EXDATEs and overrides, shifts moved RECURRENCE-IDs, converts COUNT; see the spec's Out of scope |
+| 211 | Archiving thousands of selected rows freezes the window | defect | S | **done** 2026-10-31, unreleased. Cubic optimistic-update path made linear: a lazily rebuilt id index in `ThreadListModel` and `MainWindow::batchModelWrites()` refreshing the action labels once per loop. A write now reads "applying…" until the worker has run it, then "done". Hand-tested on a 10158-message archive |
| 73 | This backlog is past four thousand lines | maintenance | S | **done** 2026-08-13; 5056 lines to 578, closed sections moved to `2026-08-03-post-0.1.0-usability-closed.md` |
| 74 | "Searching..." keeps claiming a query is running while rows are already arriving | feedback | XS | **done** 2026-08-15, unreleased. The status-bar half only: the bar now counts threads per batch. The cold-cache delay itself was measured in 2026-08-11 and is not fixable here |
| 75 | The tagging rules window forgets its size and its column widths | persistence | S | **done** 2026-08-13, shipped in 0.17.0. The window-kind question is left open, see the closed-items file |