aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md90
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md67
2 files changed, 91 insertions, 66 deletions
diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
index b9eb8cf..a0337f1 100644
--- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
+++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
@@ -8112,3 +8112,93 @@ separate Release configure with the option OFF reported a clean `0.27.0`. The
suite is 37 of 38, the one failure being item 136 on an unrelated path.
**Size: XS**, as sized.
+
+## 166. Mail you send to your own other account loses `inbox`
+
+**Observed (agent, 2026-08-25, while setting up msmtp.)** Four test messages
+were sent to one of the user's own accounts, one from each configured sending
+account. All four were delivered and indexed. The two sent from accounts whose
+Sent folder is fetched locally arrived in the recipient account's Inbox
+**without the `inbox` tag**, so they were absent from that account's Inbox view.
+The two sent from an account whose Sent folder is not fetched kept `inbox`
+normally.
+
+**Cause: established, and it is the `post-new` hook, not this binary.**
+`strip_inbox_from_sent()` in `assets/hooks/post-new` removes `inbox` from any
+message matching a configured sent folder's PATH. Its docstring states the
+assumption exactly: "the provenance is the file's own path: a message inside a
+configured sent folder is one this system sent, and `inbox` was never true of
+it."
+
+That holds for one file. It fails for one MESSAGE, because **notmuch
+deduplicates by Message-ID and a message can have several files**. When the
+sender and the recipient are both the user's own accounts, mbsync fetches two
+copies: the sender's Sent copy and the recipient's Inbox copy. notmuch stores
+them as ONE message with two filenames. The carve-out's query matches via the
+Sent filename and strips `inbox` from the message object, which is the same
+object the recipient's Inbox copy belongs to.
+
+Measured: one message, two paths, one in the sender account's sent folder and
+one in the recipient account's `Inbox/cur`.
+
+The assumption is not merely incomplete, it is false in this case: the message
+was genuinely sent AND genuinely received. There is no single right answer for
+"was `inbox` ever true of this message", because it was true of one file and
+false of another.
+
+**Approach.** Not settled, and the choice matters more than the code:
+
+1. **Strip only when EVERY file is in a sent folder.** Closest to the existing
+ intent, and it makes the predicate match the docstring's claim. A
+ self-addressed message keeps `inbox`, which is right: it did arrive.
+2. **Strip only when the message has exactly one file.** Simpler to express,
+ but it silently stops protecting any sent message that happens to be
+ duplicated for an unrelated reason.
+3. **Leave it.** Self-addressed mail is rare outside testing. The cost is that
+ it is invisible when it happens, and it looks exactly like the sync defect
+ item 104 turned out to be, which is how this was found.
+
+Option 1 is the one that makes the code true to what it already says it does.
+
+**Constraints.**
+
+- **The hook is this repo's**, `assets/hooks/post-new`, which the live
+ `database.hook_dir` symlinks to. It has its own suites beside it; run
+ `./test_post_new.py` and `./test_mailrules.py` from `assets/hooks/`.
+- The hook **tags real mail unattended, every ten minutes, on the user's live
+ index.** A predicate that is wrong in the other direction would strip `inbox`
+ from arriving mail, which is the failure mode PROTECTED_REMOVALS exists to
+ prevent. Test against a throwaway database first.
+- `notmuch tag` matching zero messages SUCCEEDS, so a log line saying the
+ carve-out ran is not evidence it matched anything. The count added for item
+ 164 is what distinguishes them; use it.
+- Do not fix this by narrowing the query to exclude the recipient account. The
+ bug is in the per-file predicate, not in which folders are configured.
+
+**Fixed 2026-08-25, option 1**, the one the entry named: strip only when
+every file is in a sent folder.
+
+`sent_only()` in `assets/hooks/post-new` filters the matches and the tag is
+then applied per id. **It is a loop because no query can express it**, and both
+plausible query forms were measured against a real two-file message before the
+loop was written: `not path:"Inbox/**"` does NOT exclude the message, and
+`notmuch count --output=files` on a path query reports every file of every
+matching message rather than the files that matched. Both read as if they
+worked and are wrong for one reason, that a notmuch term is a predicate over a
+MESSAGE while the distinction here is between its FILES.
+
+The root comes from `database.mail_root`, not `database.path`, since this index
+is split and no message file sits under the index directory. The mutation
+putting `database.path` back passes every pre-existing test, because the
+ordinary fixture keeps the index inside the mail root and both keys return the
+same string; `setup_accounts(split_index=True)` is what catches it, and is the
+Python counterpart to `NotmuchFixture::splitIndex()`.
+
+Two mutations fail: `all` to `any` loses `inbox` on the self-addressed message,
+`mail_root` to `path` silently stops stripping anything.
+
+**Verified read-only against the live index**, tagging nothing: of 807 messages
+matching a sent path, 780 are still stripped and 27 are spared, every one of
+them two files with one in another account's Inbox. No arrival is affected.
+
+**Size: S.** Done.
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 3cdc71a..1f14e67 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
@@ -239,7 +239,7 @@ taking that too literally.
| 163 | The message pane shows a stale path, and the composer forks the draft | defect | S | **done, 2026-08-25.** mbsync renames an uploaded file to add its `,U=<uid>` infix while the model still holds the name the query returned. `MaildirName::resolveRenamed()` returns the path unchanged when it exists, else finds the file in that one directory whose unique stem matches; it refuses an ambiguous match and yields nothing for a genuinely missing file. Wired into all THREE read sites: the pane, Reply/Forward, and the draft reopen. The reopen was the one that cost data, forking a draft into two files with two Message-IDs, both reaching the server |
| 164 | A draft this application saved keeps `inbox` | defect | S | open, 2026-08-25, **cause corrected 2026-08-25**. The first diagnosis blamed a missing drafts helper and was WRONG: `NOT_ARRIVALS` in `qtmaildirconf.py` is `("sent", "drafts")`, the folder list includes every account's drafts folder, and `notmuch count` confirms the carve-out query MATCHES the affected draft. The carve-out is scoped to `tag:new`, and the draft carries `inbox` while `tag:new` is 0, so it was never in scope when the hook ran. Measured separately: an mbsync-style rename does NOT re-add `new.tags`, so the retag theory is out too. What remains unestablished is WHICH pass tagged it; establish that before writing code |
| 165 | A draft gets a new Message-ID on every autosave | enhancement | ? | open, 2026-08-25, found while hand-testing 163 and 164. `MessageBuilder::build()` generates an id unconditionally and every autosave calls it, so each revision is a distinct MESSAGE to notmuch and to the server rather than a new version of one. Invisible while the file is replaced correctly, which item 163's fix restores; it is what turned that fork into two messages rather than one duplicated file. Needs a DECISION on what a draft's identity is before any code: a stable id reused at send, a stable id discarded at send, or the status quo. Neither `ComposeContext` nor `OutgoingMessage` has a field to carry an id, so it is not a changed call site |
-| 166 | Mail you send to your own other account loses `inbox` | defect | S | open, found 2026-08-25. Wholly this repo's: the hooks live in `assets/hooks/` with their own suites, and the live `post-new` symlinks to them |
+| 166 | Mail you send to your own other account loses `inbox` | defect | S | **done 2026-08-25**, unreleased. `sent_only()` keeps a message only when EVERY file is inside a sent folder, which is what the carve-out's docstring already claimed. No query can express it, measured; the root comes from `database.mail_root`, with a split-index fixture the ordinary layout cannot provide. Verified read-only against the live index: 780 of 807 still stripped, 27 spared, no arrival affected |
| 167 | No way to tell one build of an unreleased version from another | enhancement | XS | **done 2026-08-25**, unreleased. The user chose a counter over a git description: `QTMAILDIR_BUILD_NUMBER`, a cmake option ON by default, increments a counter in the BUILD directory on every build and writes `buildnumber.h`. `QTMAILDIR_VERSION_DISPLAY` carries it; `QTMAILDIR_VERSION` stays clean and is what the window title, `applicationVersion` and the release procedure use |
Sizes are rough: XS under an hour, S a sitting, M a session.
@@ -537,71 +537,6 @@ reaches it (item 42), so most of this exists.
**Size: S** for the on-demand button, XS for the visibility half. Ask which.
-## 166. Mail you send to your own other account loses `inbox`
-
-**Observed (agent, 2026-08-25, while setting up msmtp.)** Four test messages
-were sent to one of the user's own accounts, one from each configured sending
-account. All four were delivered and indexed. The two sent from accounts whose
-Sent folder is fetched locally arrived in the recipient account's Inbox
-**without the `inbox` tag**, so they were absent from that account's Inbox view.
-The two sent from an account whose Sent folder is not fetched kept `inbox`
-normally.
-
-**Cause: established, and it is the `post-new` hook, not this binary.**
-`strip_inbox_from_sent()` in `assets/hooks/post-new` removes `inbox` from any
-message matching a configured sent folder's PATH. Its docstring states the
-assumption exactly: "the provenance is the file's own path: a message inside a
-configured sent folder is one this system sent, and `inbox` was never true of
-it."
-
-That holds for one file. It fails for one MESSAGE, because **notmuch
-deduplicates by Message-ID and a message can have several files**. When the
-sender and the recipient are both the user's own accounts, mbsync fetches two
-copies: the sender's Sent copy and the recipient's Inbox copy. notmuch stores
-them as ONE message with two filenames. The carve-out's query matches via the
-Sent filename and strips `inbox` from the message object, which is the same
-object the recipient's Inbox copy belongs to.
-
-Measured: one message, two paths, one in the sender account's sent folder and
-one in the recipient account's `Inbox/cur`.
-
-The assumption is not merely incomplete, it is false in this case: the message
-was genuinely sent AND genuinely received. There is no single right answer for
-"was `inbox` ever true of this message", because it was true of one file and
-false of another.
-
-**Approach.** Not settled, and the choice matters more than the code:
-
-1. **Strip only when EVERY file is in a sent folder.** Closest to the existing
- intent, and it makes the predicate match the docstring's claim. A
- self-addressed message keeps `inbox`, which is right: it did arrive.
-2. **Strip only when the message has exactly one file.** Simpler to express,
- but it silently stops protecting any sent message that happens to be
- duplicated for an unrelated reason.
-3. **Leave it.** Self-addressed mail is rare outside testing. The cost is that
- it is invisible when it happens, and it looks exactly like the sync defect
- item 104 turned out to be, which is how this was found.
-
-Option 1 is the one that makes the code true to what it already says it does.
-
-**Constraints.**
-
-- **The hook is this repo's**, `assets/hooks/post-new`, which the live
- `database.hook_dir` symlinks to. It has its own suites beside it; run
- `./test_post_new.py` and `./test_mailrules.py` from `assets/hooks/`.
-- The hook **tags real mail unattended, every ten minutes, on the user's live
- index.** A predicate that is wrong in the other direction would strip `inbox`
- from arriving mail, which is the failure mode PROTECTED_REMOVALS exists to
- prevent. Test against a throwaway database first.
-- `notmuch tag` matching zero messages SUCCEEDS, so a log line saying the
- carve-out ran is not evidence it matched anything. The count added for item
- 164 is what distinguishes them; use it.
-- Do not fix this by narrowing the query to exclude the recipient account. The
- bug is in the per-file predicate, not in which folders are configured.
-
-**Size: S.**
-
-
## 112. Toggle unread on a whole thread cannot reach "all unread" on a partly-read thread
**Observed (user, 2026-08-17):** clicking a thread root and asking to mark the