aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-01 10:32:22 +0200
committerDanilo M. <danix@danix.xyz>2026-10-01 10:32:22 +0200
commit107933409c9044a248ef483734176cbde03cc137 (patch)
treea1d0fa9781cbd921e94e9af73f8c51e828e5f1a0 /docs
parent63c7a1fcaadb1e26c1597d5b86ce85066890ece3 (diff)
downloadqtmaildir-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')
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md57
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md49
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