diff options
Diffstat (limited to 'docs/superpowers/plans')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 82 |
1 files changed, 82 insertions, 0 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 e3ede0b..6041e28 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 @@ -236,6 +236,9 @@ taking that too literally. | 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 | +| 163 | The message pane shows a stale path and reports the message unreadable | defect | S | open, 2026-08-25, found by hand. Same root as 162 and a DIFFERENT site: the model keeps the filename a row was loaded with, mbsync renames the file to add `,U=<uid>`, and `MimeParser` then opens a path that no longer exists and honestly reports "could not be parsed". notmuch is CORRECT by then; the UI is behind, so 162's fix (refuse the write while a sync runs) does not touch this. The read path needs to recover rather than refuse | +| 164 | Every newly synced draft carries `inbox` | defect | S | open, 2026-08-25, found by hand. Measured `draft inbox unread` on a draft this application wrote. `strip_inbox_from_sent()` in `assets/hooks/post-new` reads `qtmaildirconf.sent_folders()` only, and `qtmaildirconf.py` has no drafts equivalent, so the carve-out never covers a drafts folder. **Contradicts a shipped 0.27.0 changelog entry** claiming both are kept out of the inbox, so it is a documentation defect as well. Item 158's measurement was correct and did not cover this: `index_file` assigns no tags, but mbsync's upload and the next `notmuch new` re-tag the file | + Sizes are rough: XS under an hour, S a sitting, M a session. --- @@ -1394,3 +1397,82 @@ index lags; the same infix, the opposite direction. - 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. + +## 163. The message pane shows a stale path and reports the message unreadable + +**Observed (user, 2026-08-25):** selecting a draft filled the pane with +`(unreadable message)` and `This message could not be parsed.`, naming a file +under the account's drafts folder. + +**Cause (verified against the live Maildir):** the path in the pane ended +`.dnx:2,D`, and the only file on disk ended `.dnx,U=4:2,D`. Same mechanism as +item 162: mbsync renames an uploaded file to record its server UID, and the +name the application is holding stops existing. + +**The site is different, and so is the fix.** Item 162 is the WRITE path, +`moveMessages()` reading a filename from notmuch. This is the READ path, and +by the time it fires notmuch is already CORRECT: measured, the index named the +`,U=4` file while the pane still named the pre-`U=` one. The stale path is the +MODEL's, cached when the row was loaded, so refusing to act while a sync runs +(162's likely fix) would not help here at all. + +**The report is honest, which is why it is confusing.** `MimeParser` opened a +path that did not exist and said so. Nothing is lost and the next query +repairs it. + +**Approach.** The read path should RECOVER rather than refuse: on a failed +parse, re-resolve the message id through notmuch and retry once before +reporting. `recoverStaleThread()` already exists for the neighbouring problem +(item 91 reuses it) and is the shape to follow. + +**Constraints.** + +- **A retry must be bounded.** A message that genuinely cannot be parsed + (item 41's territory) must still report, or a real defect becomes an + infinite loop. +- Re-resolving by id is what makes this safe; re-scanning the folder is not, + since two files can carry the same id. +- The placeholder wording is correct and should stay for the genuine case. + +## 164. Every newly synced draft carries `inbox` + +**Observed (developer, 2026-08-25):** `notmuch search --output=tags` on a draft +this application had just written reported `draft inbox unread`. + +**Cause (verified in the hook):** `strip_inbox_from_sent()` in +`assets/hooks/post-new` builds its query from +`qtmaildirconf.sent_folders()`, and `assets/hooks/qtmaildirconf.py` exposes +`sent_folders()` and `sent_query()` and **no drafts equivalent**. The +carve-out therefore never covers a drafts folder, and every draft that +completes a sync round trip is tagged `inbox` by `notmuch new` like any other +newly indexed file. + +**Item 158's measurement was right and did not cover this.** That item +measured `notmuch_database_index_file` assigning NO tags, which is true and is +why a freshly autosaved draft is clean. The tagging happens later: mbsync +uploads the file, renames it, and the next `notmuch new` indexes it as new +mail. + +**This contradicts shipped documentation.** The 0.27.0 changelog says "Sent +mail and drafts no longer appear in the inbox", and the drafts half has never +been true. Fixing the hook and correcting the entry are one item. + +**Approach.** A `drafts_folders()` / `drafts_query()` pair beside the sent +ones, and one more carve-out call, or a single function taking the folder kind +so the two cannot drift. + +**Constraints.** + +- **This is a two-repo change in spirit but not in fact.** The hooks moved + into this repository in 0.27.0, so `mailrules.py` is not involved and the + shared-format procedure does not apply. Check that before assuming + otherwise: `CLAUDE.md` still describes the hook as shipping from `mailctl` + in places. +- The hook must keep refusing to consume `tag:new` when a carve-out fails. + Clearing the marker while the rules did not run orphans that mail + permanently, which the sent half already gets right. +- Only `inbox` may be touched. A draft legitimately carries `draft` and + `unread`, and `maildir.synchronize_flags` means removing `unread` rewrites + the filename and reaches the server. +- `test_post_new.py` and `test_qtmaildirconf.py` are in the same directory and + must both be extended; the sent carve-out has tests to copy. |
