diff options
Diffstat (limited to 'docs/superpowers/specs')
| -rw-r--r-- | docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md | 61 |
1 files changed, 36 insertions, 25 deletions
diff --git a/docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md b/docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md index 0b4db9b..1423fe9 100644 --- a/docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md +++ b/docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md @@ -59,12 +59,14 @@ revision is field 3. Verified on notmuch 0.39. Per tick, if the revision has not advanced, there is nothing to do. Otherwise, per account: - notmuch count "tag:unread and tag:inbox and tag:account-<key> and lastmod:<prev>..<cur>" + notmuch count "tag:unread and tag:inbox and tag:account-<key> and lastmod:<prev+1>..<cur>" notmuch search --format=json --limit=3 --sort=newest-first "<same query>" Two calls, the same count-plus-preview pair `Accounts.qml` already makes, and only for accounts whose query is non-empty. The count gives the true N for -"+N more"; the search gives the rows. +"+N more"; the search gives the rows. The lower bound is exclusive (`prev+1`) +so the revision just written, which is inclusive at the top end, is not +re-matched on the next tick. `search --format=json` returns `authors` and `subject` directly, which is all the body needs. @@ -95,9 +97,12 @@ was down is never notified. This is the right trade: the waybar count is still correct and the drawer still shows the mail, so nothing is lost except a popup that would have been stale anyway. -The new revision is written **after** every account has been processed, so a -failure mid-loop leaves `prev` unchanged and the next tick retries rather than -dropping a batch silently. +The new revision is written **after** every account has been processed. A +single account whose count does not validate is skipped for that tick, and the +revision still advances: holding it back would make every later tick re-notify +the successful accounts' whole range. Only a failure to write the state file +itself is non-fatal and leaves the old revision, so the next tick re-notifies +rather than dropping mail. ## Accounts @@ -120,39 +125,45 @@ The `label` is what the notification summary shows. One per account with new mail: - dunstify -a mail-overview -u normal -t 10000 \ + dunstify -a "New Mail" -u normal -t 10000 -b \ + -i "$MAIL_ICON" \ -h string:x-dunst-stack-tag:mail-<key> \ -A default,open \ - "<label> · N new" "<body>" + "<label> (<count>)" "<body>" -Body: up to 3 rows of author and subject, then `+N more` when N > 3. Three -matches the drawer's own `--limit=3`, and bounds the height so a 20-message -mailing list burst does not become a wall. +The heading is split to match the running dunst `format`, which renders `%a` +(the app name) bold on the top line and `%s` (the summary) italic below it: +the app name is `New Mail`, the summary is `<label> (<count>)`. + +Body: up to 3 bullet rows of author and subject, each on its own line with a +blank line between, then `+N more` when N > 3. Three matches the drawer's own +`--limit=3`, and bounds the height so a 20-message mailing list burst does not +become a wall. + +`MAIL_ICON` is an absolute path, not an icon name. The running dunst resolves +a themed icon name only through the `icon_path` in `dunstrc`, which holds no +mail icon, so a name renders nothing; an absolute path under the active theme +resolves, as the machine's other notifiers already do. It points at +`${XDG_DATA_HOME:-$HOME/.local/share}/icons/MB-Blueberry-Suru-GLOW/actions/24/mail-unread-multiple.svg`. + +Because the notification carries `-A`, dunst prepends an `(A)` action +indicator when `show_indicators` is on, so `dunstrc` sets +`show_indicators = no`. Normal urgency and an explicit 10s timeout. Deliberately not `-u critical`: -on most dunst configurations critical notifications never expire, which would -leave mail popups stuck on screen. +on most dunst configurations critical notifications never expire, which +would leave mail popups stuck on screen. **Stack tag per account** means a second batch for the same account replaces the first rather than stacking, which is what "one notification per account" has to mean when mail keeps arriving. **Click opens qtmaildir.** `-A default,open` plus `-b` makes dunstify block -until the notification is dismissed or clicked and print the action key, so +until the notification is dismissed or actioned and print the action key, so each notification is launched in a backgrounded subshell that waits and runs `~/bin/qtmaildir` on `default`. Without backgrounding, the loop would stall -for the full timeout on every account. - -The click cannot open the *account* that was clicked: `qtmaildir` accepts no -command line arguments, and `startup_account` in its config is a static -setting rather than a flag. This is the same limitation the README already -records for the drawer's thread rows, and it is accepted for the same reason. - -`dunstify` rather than `notify-send` because actions need it. Verified -present, and dunst 1.12.2 lists both `actions` and `x-dunst-stack-tag` in -`--capabilities`. If `dunstify` is absent the script falls back to -`notify-send` with no click action, rather than failing: a notification -without a click is still worth having. +for the full timeout on every account. With the running dunst's +`mouse_middle_click = do_action`, it is a middle click that invokes it. ## Failure handling |
