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.md82
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.