diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-07 12:03:14 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-07 12:03:14 +0200 |
| commit | 5a4d8f5f021dc98b2a7cc471125aa3040c02675c (patch) | |
| tree | 181eccf726124f39ae6a1c14734cfa18ac42ed9f /docs | |
| parent | 0a9ef3c77c7c593f3568f25761aad5d1f55e0e33 (diff) | |
| download | qtmaildir-5a4d8f5f021dc98b2a7cc471125aa3040c02675c.tar.gz qtmaildir-5a4d8f5f021dc98b2a7cc471125aa3040c02675c.zip | |
feat(tags): mark every thread in the view read, in one undoable step
An action removing "unread" from every thread in the current view,
on the toolbar, the Message menu and Ctrl+Shift+U. It deliberately
ignores the selection, which makes it the one action in the window
that does, and it routes through the same funnel as every other tag
change, so it is one write rather than one per thread.
Disabled until the query reports its total. Threads arrive in batches,
so before then the model holds only what has landed, and an action
saying "all" must not silently skip the rest. A greyed control says
"not yet" without needing a dialog or a stall the user cannot see.
The state is also set at registration, since QAction starts enabled
and a window that has not run a query has nothing to act on.
Two things came out differently from the plan, both forced by existing
code. It carries a default binding, because everyActionHasAShortcut
requires every registered action to have one: an unbound action is
unreachable from the keyboard, and that invariant is deliberate, so the
action was given Ctrl+Shift+U rather than the invariant relaxed. And
only the threads that are actually unread are sent, because sending the
rest would inflate the pending-edit count with writes that change
nothing, and the quit prompt reads that count. A view with nothing
unread does nothing, pushes no command and says so: an undo entry that
restores nothing is worse than none, since it absorbs a Ctrl+Z meant
for the previous action.
undoDepthForTesting() is new and exists for a reason worth recording:
undo->isEnabled() cannot answer "was a command pushed", because the
undo QAction is always enabled and tests canUndo() when triggered. The
first version of the no-op test asserted on it and passed against a
mutant with the unread filter removed.
Closes item 43.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 30 |
1 files changed, 29 insertions, 1 deletions
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 9424ea4..2bcdd0a 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 @@ -90,7 +90,7 @@ taking that too literally. | 40 | No live filter over the current view | workflow | M | open | | 41 | A message whose HTML body carries a `Content-Id` renders blank | correctness | S | **done** | | 42 | "Syncing..." says nothing about what is being synced | feedback | S | **done** | -| 43 | No "Mark all read" for the current view | workflow | S | open | +| 43 | No "Mark all read" for the current view | workflow | S | **done** | | 44 | No way to manage the filters applied at sync time | workflow | ? | open, unspecified | | 45 | Two Sync buttons, and only one of them works properly | correctness | S | **done** | | 46 | `uiStateSurvivesARestart` fails under the offscreen platform | testing | XS | **done** | @@ -2192,6 +2192,34 @@ query, per `CLAUDE.md`, so marking many threads read is one write, not N. - The pending-edit count must move by the real number of threads changed, or the quit prompt understates the work at risk. +**Resolved (2026-08-07).** The scope question above was decided by the user: +the action is **disabled until the query reports its total**, rather than +acting on a partial set or stalling on a wait. `m_queryComplete` gates it, +cleared in `runCurrentQuery()` and set in `onQueryFinished()`. A greyed control +says "not yet" without a dialog, and the honesty constraint is satisfied by +construction rather than by wording. + +Two things came out differently from the plan, both forced by existing code. + +- **It carries a default binding, `Ctrl+Shift+U`**, shifted against `Ctrl+U` + for `toggle_unread`. The intent was toolbar and menu only, but + `everyActionHasAShortcut` requires every registered action to have one: an + unbound action is unreachable from the keyboard, and an empty shortcut means + the action list and the default table have drifted apart. The invariant is + deliberate, so the action was given a binding rather than the invariant being + relaxed. +- **Only the threads that are actually unread are sent.** Sending every row + would inflate the pending-edit count with writes that change nothing, and the + quit prompt reads that count. A view with nothing unread does nothing at all, + pushes no command, and says so: an undo entry that restores nothing is worse + than none, since it absorbs a Ctrl+Z meant for the previous action. + +**A test-seam note worth keeping.** `undo->isEnabled()` cannot answer "was a +command pushed": the undo `QAction` is always enabled and tests `canUndo()` +when triggered. A first version of the no-op test asserted on it and passed +against a mutant with the unread filter removed. `undoDepthForTesting()` exists +because of that, and the mutation is caught now. + ## 44. No way to manage the filters applied at sync time **Observed (user, 2026-08-05):** "manage filters to be applied when syncing (view |
