From 0dee84687f65cb200e46d2f612c9c94887a126eb Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 25 Aug 2026 10:16:38 +0200 Subject: docs(backlog): record items 163 and 164, both found by hand 163: the message pane names a path that no longer exists and reports the message unreadable. Same mechanism as 162, different site and different fix. mbsync renames the file to add ,U=; by the time the pane fails, notmuch is already CORRECT and the stale path is the MODEL's, cached when the row was loaded. Measured: the index named the ,U=4 file while the pane named the pre-U= one. 162's likely fix, refusing to write while a sync runs, does not touch the read path. Points at recovering by re-resolving the id, the way recoverStaleThread() already does, with a bounded retry so a genuinely unparseable message still reports. 164: every newly synced draft carries inbox. Measured "draft inbox unread" on a draft this application wrote. strip_inbox_from_sent() reads qtmaildirconf.sent_folders() only, and qtmaildirconf.py has no drafts equivalent, so the carve-out never covers a drafts folder. Item 158's measurement was right and did not reach this: index_file assigns no tags, but mbsync's upload and the next notmuch new re-tag the file. 164 also contradicts the shipped 0.27.0 changelog, which claims sent mail and drafts both stay out of the inbox. The drafts half has never been true, so correcting the entry is part of that item. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UUQS6n3cmsFrsjCNmwNtf8 --- .../plans/2026-08-03-post-0.1.0-usability.md | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) (limited to 'docs/superpowers') 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 to `. NOT a Delete defect and not item 158's: mbsync renames an uploaded file to add its `,U=` 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=`, 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. -- cgit v1.2.3