diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 93 |
1 files changed, 55 insertions, 38 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 ab11c4f..e3ede0b 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 @@ -233,8 +233,9 @@ taking that too literally. | 159 | The Drafts view lists threads, so a draft is unreachable by double-click | defect | S | **done** 2026-08-25, unreleased. Reverses item 138's own decision, confirmed with the user. `generatorIsFlat()` in `config.cpp` is now the single closed set of flat generators, replacing three hardcoded comparisons against `"sent"`: the built-in filter, the reader that reapplies the mode, and the writer that skips storing what the generator implies. Those three had to agree and nothing made them; a `drafts` entry saved and reloaded would otherwise have come back THREADED while the button was flat. `builtinFilter()` sets `flat` once from the helper rather than in a branch, so the set cannot drift from the labels | | 160 | The composer never says a draft was autosaved | feedback | S | **done** 2026-08-25, unreleased. A status bar on the composer: the age line left, the `○ unsaved content` cue beside it. **The fix is a funnel, not a label.** `m_dirty` had SEVEN writers and four of them clear it, only two of which are a save, so a cue hung off the save path silently missed the constructor and the send; `setDirty()` is the one writer now and refreshes both cues plus `setWindowModified()`. Presentation was **reworked after the user looked at it**: it first reused item 151's yellow ribbon treatment, which reads as a misplaced widget on a bare status label, and the cue sat in the permanent (right-hand) tray. Two defects found by probing rather than by reading, see the section | -| 161 | The composer has no menu bar | discoverability | S | open, 2026-08-25, from the notes. Save draft on `Ctrl+S` is asked for and does not exist at all: `saveDraftNow()` is reachable only from the timer, Send and `closeEvent`. The composer's actions are ad-hoc `QAction`s parented to the window (Send, Close, and the formatting toolbar's), none registered in `KeyMap`, so item 132's menu-reachability rule does not currently reach them. **Unblocked** 2026-08-25: 160 shipped the status bar, so a manual save now has somewhere to report. Still needs the user to say WHICH main-window actions belong on a composer menu, since most message actions are meaningless over a message being written | +| 161 | The composer has no menu bar | discoverability | S | **done** 2026-08-25, unreleased. File / Edit / Format, to the user's own chosen scope. **Save draft (`Ctrl+S`) is the only NEW action**; everything else is gathered, and the menus show the toolbar's own `QAction` objects rather than copies, per item 140's rule. Two needed hand-building: the HTML toggle is a `QToolButton` and cannot go in a menu, so a checkable twin mirrors it BOTH ways; and the signature entry takes the switch's own `QMenu` pointer, since that menu is rebuilt when the signatures change and copied entries would go stale. Edit's entries follow the editor's own `undoAvailable`/`copyAvailable`. `theMenuBarReachesEveryComposerAction()` is item 132's rule applied to the composer, walking the real menu bar and finding actions by `findChildren`, so a future action added to the toolbar and forgotten in the menus fails without touching the test | +| 162 | Delete fails while a sync is renaming the file underneath it | defect | S | open, 2026-08-25, found by hand. `Cannot move <file> to <folder>`. NOT a Delete defect and not item 158's: mbsync renames an uploaded file to add its `,U=<uid>` infix, and notmuch keeps the pre-`U=` name until that sync's `notmuch new` runs, so `moveMessages` renames a path that no longer exists. Truthful, harmless and SELF-HEALING, which is why it reads as intermittent: verified a ghost present mid-sync and gone after. The message names a folder as though the folder were the problem. Delete reaches the real mail server, so read `CLAUDE.md` before touching it | Sizes are rough: XS under an hour, S a sitting, M a session. --- @@ -1337,43 +1338,59 @@ The 70-second duration recorded above fits a `QTRY_*` waiting for a file that is never going to appear, which is consistent with a wrong destination rather than a slow one. -## 161. The composer has no menu bar - -**Observed (user, 2026-08-25):** "the compose window should have a menu bar on -top", with "save draft (ctrl+s)" and "duplicate the other actions like we do -on the main window". - -**Cause (verified in code):** the composer has no `QMenuBar`, and **Save draft -does not exist as an action at all.** `saveDraftNow()` is reachable only from -the autosave timer, from the send path, and from `closeEvent`; there is no way -for the user to ask for a save, and no `Ctrl+S` anywhere in the composer or in -`KeyMap`. - -The actions the composer does have are ad-hoc `QAction`s parented to the -window: `m_sendAction` and a Close action (`composewindow.cpp:583-608`), plus -the formatting toolbar's own. **None is registered in `KeyMap`**, which is -deliberate and documented for `Ctrl+W` in item 148: they are WindowShortcuts -dispatched to the active composer only, so they never touch the main window's -namespace. A consequence worth stating before this item is built: -`everyActionIsReachableFromAMenu()` walks the MAIN window's menu bar, so it -does not currently constrain these, and adding a composer menu bar does not -automatically bring them under item 132's rules. - -**Approach.** A `QMenuBar` on the composer, with Save draft (`Ctrl+S`) as the -new action and the existing ones gathered under it rather than duplicated. +## 162. Delete fails while a sync is renaming the file underneath it + +**Observed (user, 2026-08-25):** deleting a draft reported `Cannot move +<file> to <account>/Trash`. + +**Cause (verified against the live Maildir, not read):** a stale path, and +neither Delete nor item 158 is at fault. + +1. The composer autosaves a draft as `<name>:2,D` and item 158 indexes it + under exactly that filename. +2. **mbsync uploads it and RENAMES it** to `<name>,U=<uid>:2,D`, recording the + server UID in the filename. +3. notmuch still holds the pre-`U=` name until that sync's `notmuch new` runs. +4. `moveMessages()` reads the filename from notmuch and calls + `QFile::rename()` on a path that no longer exists. It fails, the error is + emitted, and the message is skipped. + +Measured, in this order: `notmuch search --output=files` named a file that was +not on disk while `Background sync running...` was up, and the same query was +clean once the sync finished, with the file present under its new `,U=4` name. +That is why it reads as intermittent, and why it heals itself. + +**It is truthful and it loses nothing.** The move is skipped, no wrong folder +is created, no file is destroyed, and the next sync reconciles. The defect is +that the message blames a folder for a timing problem, and that the action +silently does nothing when the user asked for something. + +**This is `CLAUDE.md`'s `,U=` trap from the other side.** `MaildirName::fresh()` +exists because CARRYING that infix across a folder boundary produced +`Maildir error: duplicate UID` on real mail. Here mbsync is ADDING it and the +index lags; the same infix, the opposite direction. + +**Approach, and it needs a decision.** Two candidates: + +- **Refuse the move while a sync holds the lock.** `SyncMonitor` already + reports this, and the held-edit machinery from items 97 and 106 already + exists for exactly this shape: a tag edit made during a sync is held and + flushed when it ends. Delete would join it rather than inventing anything. + This is the likelier right answer, since it matches what every other + mutation already does. +- **Re-resolve the filename** from notmuch immediately before the rename and + re-query the message if the path is gone. Smaller, but it races the same + window it is trying to close, and a second lookup can be stale by the time + it is used. **Constraints.** -- **"Duplicate the other actions" means the main window's MESSAGE actions, and - most of them are meaningless here.** A composer has no thread, no selection - and no tag surface. Ask which the user actually wants before building a menu - that offers Archive or Mark all read over a message being written. -- Keep the composer's actions out of `KeyMap`, per item 148's reasoning. A - composer menu bar is not a reason to move them. -- Save draft must go through `saveDraftNow()`, which already handles the - failure banner and emits `draftSaved` for the indexing item 158 added. A - second write path would reintroduce the ghost-file problem that fixed. -- **Item 160 unblocked this** on 2026-08-25: the status bar exists, so a - manual save reports through `refreshDraftStatus()` like an autosave. Route - `Ctrl+S` through `saveDraftNow()` and the reporting is already done. - +- **Delete reaches the real mail server.** Read `CLAUDE.md`'s item 103 notes + before touching `moveMessages()`: a wrong folder name is created, adopted by + mbsync, and propagated to every other client. +- Whatever is built, **the message must say a sync is running**, not name a + folder. The current wording sent the user looking for a broken folder + configuration, which was correct and configured. +- A test cannot see this in the ordinary fixture layout, where nothing renames + a file underneath the index. Driving it means renaming the file between the + index write and the move, which is what the reproducer has to do. |
