diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-01 10:32:22 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-01 10:32:22 +0200 |
| commit | 107933409c9044a248ef483734176cbde03cc137 (patch) | |
| tree | a1d0fa9781cbd921e94e9af73f8c51e828e5f1a0 /docs/superpowers | |
| parent | 63c7a1fcaadb1e26c1597d5b86ce85066890ece3 (diff) | |
| download | qtmaildir-107933409c9044a248ef483734176cbde03cc137.tar.gz qtmaildir-107933409c9044a248ef483734176cbde03cc137.zip | |
docs: close item 200, the launch selectors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers')
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md | 57 | ||||
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 49 |
2 files changed, 58 insertions, 48 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 d6b199e..f5ae9f0 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 @@ -10583,6 +10583,63 @@ is coupled to `everySelectedRowIsInATrashFolder()`, which by design never answers for spam, so "Restore already covers what qtmaildir moved" was a capability claim that no surface offered. Read item 201 for the narrower, decision-free half. +## 200. qtmaildir cannot be launched at a given account, thread or message + +**Observed (user, from the notes):** "the program should accept cli parameters +like `--account` or `--thread`/`--message`, so that another app can launch +qtmaildir opening that account's inbox or a certain message/thread." + +**Cause.** Verified. `main.cpp:38-66` walks `argv` with `std::strcmp` and +recognises `--version`/`-v` and `--help`/`-h`, nothing else. Both answer and +return BEFORE `QApplication` is constructed, which is deliberate and documented +in the file: `--version` has to work on a machine where the GUI cannot open. +Anything else on the command line is ignored silently. + +**Parsing is the small half.** `QCommandLineParser` is stdlib for this and +replaces the `strcmp` loop, with the one constraint that the early-exit options +must keep answering without a `QApplication`. + +**Two things make this M rather than S, and both are the interesting part.** + +1. **There is no single-instance mechanism.** No `QLocalServer` or + `QLocalSocket` appears anywhere in `src/`. A second launch therefore opens a + second window against the same notmuch database, and notmuch permits only one + open handle per process, so two processes is two handles and the read-write + burst the write path depends on becomes a contention question. The note's own + framing, "another app can launch qtmaildir", is the case where the + application is usually ALREADY RUNNING, so the useful behaviour is to steer + the running window rather than to start a second one. +2. **A selector has to reach a query the startup path does not take.** + `--account` is close to free, since the account selector and the built-in + filters already compose that query and `Config::resolvedQuery()` exists for + exactly this. `--thread` and `--message` are not: they name a row that may + not be in the configured startup view at all, so the startup path has to + accept an arbitrary query and then select a row within its result, which is + a selection-after-load problem the window solves nowhere else. + +**One decision from the user before this can be planned.** Whether a second +launch should hand its arguments to the running window and focus it, or simply +start with a different query. The first is what makes the feature useful to an +external caller and is essentially the whole cost of the item; the second is +close to free for `--account` alone. They are different items sharing one line +in the notes. + +**Constraints.** Arguments are untrusted input in the ordinary sense: a +`--thread` value reaches a notmuch query, so it goes through `SearchTerm`'s +quoting like every other query this application builds, rather than being +concatenated at the call site. + +**Done 2026-10-01**, on branch `cli-selectors` and fast-forwarded to master, +for the release after 0.30.0. `LaunchSelectors` parses the three options and +carries them over a versioned socket payload; `SingleInstance` owns the +`QLocalServer` and degrades to a plain window when no socket can be made. +Decisions settled while hand-testing: `--thread` takes hex ids only, since it +reaches `thread:` unquoted; `--message` always targets the message, a +conversation's first included, and `--thread` is the way to ask for the +conversation; `--thread`/`--message` without `--account` switch to All +accounts; a miss names itself in the status bar and restores the previous +view. Hand-tested and confirmed, including a handoff to a running window and +bash completion. ## 201. A message in the Spam view cannot be un-spammed, even one qtmaildir put there **Observed (user, 2026-09-14, testing the `spam-view` branch).** "If a message 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 ed7afac..48c31af 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 @@ -279,7 +279,7 @@ taking that too literally. | 196 | Spam is never tagged automatically | workflow | ? | open, 2026-09-10, from the notes ("the app should be able to tag spam automatically leveraging intel from abusectl"). Depends on 194's sidecar existing: `~/Programming/GIT/abusectl` is a repo but nothing is on `PATH`, so the intel this would read does not yet have a shape to read. Also unspecified in direction: the natural home is the `post-new` hook rather than `src/`, since tagging at sync time is what `assets/hooks/mailrules.py` already does, and a rule sourced from an external database is a format question for both readers (see "Changing the rule format"). Ask the user what abusectl would expose before designing anything | | 198 | The unsynced-changes list never says which account a message belongs to | presentation | S | open, 2026-09-13, from the notes ("when clicking on the bottom right status bar, there's no way to discriminate what message belongs to what account"). The click opens `PendingChangesDialog` (item 119). Verified: `PendingChangeRow` (`pendingchangesdialog.h:32-50`) carries subject, action, `startsMessage` and `messageCount` and no account, so a list of five subjects across five accounts reads as one undifferentiated run. The data is reachable rather than missing: `accountForMessagePath()` (`mainwindow.cpp:6219`) resolves an account from a path, and `resolvePendingSubjects()` already walks every id in the worker and answers positionally, so the account is one more field on an existing round trip. One asymmetry to settle first: a held THREAD edit carries a thread id rather than a message id (`pendingChangeSnapshot()`, `mainwindow.cpp:5699`), and a thread can in principle span accounts, so the thread rows need a rule of their own rather than the message answer | | 199 | The window chrome uses the system icon theme, and the user wants a shipped set | presentation | M-L | open, 2026-09-13, from the notes ("we should ship our own icons, color themeable to be consistent in every theme a user may implement, since icons are a brand identity"). This deliberately REVERSES item 70, which drew the split as "panes are ours, chrome is the system's" and shipped `Marks` for the panes only; the note asks for the other half too, so it is a decision to revisit rather than a defect. Verified: the `themeIcons` table at `mainwindow.cpp:2211` and six `QIcon::fromTheme` sites in `composewindow.cpp` are every chrome icon, all resolved from the desktop theme. The mechanism already exists and is proven, `Marks::pixmap` compositing `fill="currentColor"` with `CompositionMode_SourceIn` so one asset serves a light and a dark palette, and `src/marks.h` records why it is compiled-in string literals rather than a `.qrc`. The size is the ARTWORK, not the code: item 70's six marks are shipped, this is roughly forty actions, each needing a drawing. Needs a decision from the user on scope before it can be sized honestly, and on whether the system theme stays as a fallback for an action with no shipped icon | -| 200 | qtmaildir cannot be launched at a given account, thread or message | workflow | M | open, **specified 2026-09-13** in `specs/2026-09-13-cli-selectors-design.md`; read that rather than this row. The user settled three things: a second launch STEERS the running window over a `QLocalServer` rather than opening a second one, the selectors are `--account`/`--thread`/`--message` (`--query` dropped as the one with no caller), and a selector matching nothing opens the window normally and says so in the status bar. The design shrank on one side and grew on the other: `recoverStaleThread()` already runs `thread:<id>` with a deferred selection and is reused as a third caller, so the selectors are the small half, while the socket (connect-first ordering, stale-socket recovery, a degrade path when no socket is possible) is the real work and adds `Qt6::Network` to the component list. Original entry: open, 2026-09-13, from the notes ("the program should accept cli parameters like `--account` or `--thread`/`--message`, so that another app can launch qtmaildir opening that account's inbox or a certain message/thread"). Verified: `main.cpp:38-66` hand-rolls a `strcmp` loop over `argv` for `--version` and `--help` only, both answering before `QApplication` exists, which is deliberate and documented. Parsing is the small half and `QCommandLineParser` covers it; the item is bigger than it looks for two reasons. There is NO single-instance mechanism (no `QLocalServer` anywhere in `src/`), so a second launch opens a second window against the same notmuch database rather than steering the running one, and notmuch permits only one open handle per process. And the selector has to reach a query the startup path does not currently take, since `--thread` names a row that may not be in the configured startup view at all. Needs a decision from the user first: whether a second launch should focus the running window (which is the useful behaviour for "another app launches qtmaildir" and is the whole cost of the item) or simply start with a different query | +| 200 | qtmaildir cannot be launched at a given account, thread or message | workflow | M | **done 2026-10-01**, on branch `cli-selectors`, fast-forwarded to master, unreleased. `--account`, `--thread`, `--message` plus a single-instance socket that hands a second launch to the running window. Hand-tested and confirmed. Section in the closed file | | 197 | No way to say a message is not spam | workflow | S | **done 2026-09-14**, released in 0.29.0. A `Not spam` action now exists (item 201): it returns each message to its `moved-from:` origin, strips `spam`, and for provider-caught mail with no origin falls back to the account's inbox, reported in the status bar (the destination question answered as the inbox guess). The provider-notification half stays out of scope as network work. Section in the closed file | | 201 | A message in the Spam view cannot be un-spammed, even one qtmaildir put there | defect | S | **done 2026-09-14**, released in 0.29.0. Built as the distinct `not_spam` action (the second option): shown on a spam-folder selection, hidden on a reply row and in the trash, worker-resolved origin, undoable. Section in the closed file | | 202 | Mail in a spam folder keeps `inbox`, so it appears in the Inbox view | defect | XS | **done 2026-09-14**, released in 0.29.0. Root cause was the `post-new` hook's non-arrival set, not the UI: `spam` added to `NOT_ARRIVALS`, helpers renamed `not_arrival_*`, tests added. Live one-time cleanup stripped `inbox` from the 49 affected messages (`tag:inbox` 5904 -> 5855). Section in the closed file | @@ -1536,53 +1536,6 @@ cannot be narrowed without the user. action must carry an icon both key on the current table and would need rereading against whichever answer is chosen. -## 200. qtmaildir cannot be launched at a given account, thread or message - -**Observed (user, from the notes):** "the program should accept cli parameters -like `--account` or `--thread`/`--message`, so that another app can launch -qtmaildir opening that account's inbox or a certain message/thread." - -**Cause.** Verified. `main.cpp:38-66` walks `argv` with `std::strcmp` and -recognises `--version`/`-v` and `--help`/`-h`, nothing else. Both answer and -return BEFORE `QApplication` is constructed, which is deliberate and documented -in the file: `--version` has to work on a machine where the GUI cannot open. -Anything else on the command line is ignored silently. - -**Parsing is the small half.** `QCommandLineParser` is stdlib for this and -replaces the `strcmp` loop, with the one constraint that the early-exit options -must keep answering without a `QApplication`. - -**Two things make this M rather than S, and both are the interesting part.** - -1. **There is no single-instance mechanism.** No `QLocalServer` or - `QLocalSocket` appears anywhere in `src/`. A second launch therefore opens a - second window against the same notmuch database, and notmuch permits only one - open handle per process, so two processes is two handles and the read-write - burst the write path depends on becomes a contention question. The note's own - framing, "another app can launch qtmaildir", is the case where the - application is usually ALREADY RUNNING, so the useful behaviour is to steer - the running window rather than to start a second one. -2. **A selector has to reach a query the startup path does not take.** - `--account` is close to free, since the account selector and the built-in - filters already compose that query and `Config::resolvedQuery()` exists for - exactly this. `--thread` and `--message` are not: they name a row that may - not be in the configured startup view at all, so the startup path has to - accept an arbitrary query and then select a row within its result, which is - a selection-after-load problem the window solves nowhere else. - -**One decision from the user before this can be planned.** Whether a second -launch should hand its arguments to the running window and focus it, or simply -start with a different query. The first is what makes the feature useful to an -external caller and is essentially the whole cost of the item; the second is -close to free for `--account` alone. They are different items sharing one line -in the notes. - -**Constraints.** Arguments are untrusted input in the ordinary sense: a -`--thread` value reaches a notmuch query, so it goes through `SearchTerm`'s -quoting like every other query this application builds, rather than being -concatenated at the call site. - - ## 203. Marking a message as spam crashed the application **Observed (user, from the notes):** while browsing unread, marking a message |
