From f0065f6cdcaf423e8c09b93f63a7d362b415e9c4 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 12 Sep 2026 10:24:12 +0200 Subject: docs(mail-overview): component notes Records the decisions a later reader would otherwise reverse, each with the measurement behind it: counting by the account tag rather than the path glob qtmaildir itself uses, because notmuch deduplicates by message id and a glob double-counts a message that arrived at two addresses; validating notmuch output as an integer, because a rejected query prints nothing while a misparsed one returns a plausible wrong number; and parsing the config by walking lines, because account folders named like [Gmail]/Bozze end a bracket-delimited section body before its label. Also notes the one property that differs from the other two panels, exclusionMode Normal, with the measured geometry showing the overlay starting exactly where waybar ends. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WWL8JYHu7yhAdtx5pU9PMU --- mail-overview/README.md | 167 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 mail-overview/README.md diff --git a/mail-overview/README.md b/mail-overview/README.md new file mode 100644 index 0000000..daf2bf9 --- /dev/null +++ b/mail-overview/README.md @@ -0,0 +1,167 @@ +# mail-overview + +Unread mail across every account, as one waybar number and a drawer behind it. +Clicking the icon opens the drawer; Escape or a click outside closes it. + + ┌──────────────────────────────────────────────────┐ + │ Mail 33 unread │ + ├──────────────────────────────────────────────────┤ + │ ● Account A 2 │ + │ Some Sender Today 06:18 │ + │ [a-list] a subject line that is elided... │ + ├──────────────────────────────────────────────────┤ + │ ● Account B 0 │ + │ ● Account C 31 │ + │ ...three newest threads... │ + │ ● Account D 0 │ + │ ● Account E 0 │ + ├──────────────────────────────────────────────────┤ + │ [Sync now] [Open qtmaildir] │ + └──────────────────────────────────────────────────┘ + +## Running it + + qs -p . + +It is started from `autostart.lua` and reached over IPC, so the shell has to be +running for the waybar click to do anything: + + qs -p ~/Programming/GIT/quickshell/mail-overview ipc call mail toggle + +Write that path out in full in the real config. Waybar's `exec` and `on-click` +have no shell to expand `~`, and neither do Hyprland's Lua strings. + +## The accounts are not listed here + +`qtmaildir.conf` already has one `[account.]` section per account, and +`` is exactly the suffix of the notmuch tag `account-`, with a short +`label` and a `color` alongside. The panel parses that file, in file order, so +adding an account to qtmaildir makes it appear here with no edit to any QML. +The file is watched, so that happens without a restart. + +The `color` is the account's own, from its config section. It marks the dot +beside each row and nothing else: per-account identity is not a palette, and +`Theme` still owns every other colour. + +**Parsing walks lines rather than matching one regex over the file.** The +obvious pattern for a section body, everything up to the next `[`, is wrong +here: several accounts have folders named like `[Gmail]/Bozze`, so the body +ended at that bracket, before the `label` line, and three of five accounts +quietly fell back to displaying their raw key. Nothing errored, because falling +back is a legitimate path for an account with no label. + +## Counting + +Every count is `tag:unread and tag:inbox`. Plain `tag:unread` also counts +archived-but-unread mail and mailing list traffic that was never in the inbox, +which for one account here was the difference between 32 and 41; the +inbox-scoped number is the one that means new mail worth looking at. + +**By the `account-*` tag, never by a `path:` glob.** qtmaildir itself scopes an +account with `path:"/**"`, and copying that query would be wrong for +this panel. notmuch deduplicates by message id, so a message that arrived at +two of the configured addresses is one message with two file paths: a glob +counts it under both accounts, and the rows then sum to more than the total the +waybar icon shows. Measured here, five accounts summed to 102 against a global +total of 101. The tag is a property of the message, so it is singular and the +rows always sum to the header. The cost is that such a message appears under +only the account the `post-new` hook attributed it to, which is the right trade +for an overview whose headline figure is a single number. + +## notmuch fails two ways, and only one is detectable + +A query notmuch rejects prints nothing and exits 1: + + notmuch count 'tag:unread and (' + +A query Xapian merely misparses returns a plausible wrong number and exits 0: + + notmuch count 'tag:unread and ((' # 41, exit 0 + notmuch count 'tag:unread and tag:' # 3, exit 0 + +So the exit status is not the test, and neither is any check that a wrong +number could pass. Counts are validated as non-negative integers, which catches +the first case, where empty output would otherwise render as an empty inbox. +The second is not catchable; the defence against it is that the queries are +fixed strings and are never built from anything. + +An unknown count shows a dash, never a zero, for the same reason the VM panel +refuses to substitute libvirt's host-side figures: a number that is actually a +failure reads as a fact. + +## The waybar module + +A `custom` module in **continuous mode**: the script never exits, prints one +JSON line per database commit, and waybar redraws on each line. There is no +`interval`, and no daemon to supervise, because waybar owns the process. + + "custom/mail": { + "format": "󰇮 {}", + "return-type": "json", + "exec": "~/Programming/GIT/quickshell/mail-overview/waybar-mail.sh", + "on-click": "qs -p ~/Programming/GIT/quickshell/mail-overview ipc call mail toggle", + "on-click-right": "~/bin/mailsync.sh", + "tooltip": false + } + +The count drops the moment mail is read in qtmaildir and rises the moment +mbsync commits, because both write the database and the watcher sees either. + +Two details in that loop are load-bearing. The watch is on the **xapian +directory**, not on a file inside it: a commit replaces files, and a watch held +on a filename dies with it. And a short sleep coalesces the several writes of +one commit, without which the module redraws three or four times per sync with +intermediate counts. + +The old setup this replaces ran three copies of a python script polling the +Gmail API with stored credentials, covered three of the five accounts, and +opened Thunderbird. Everything it fetched over the network was already in the +local index. + +## Geometry, and the one property that differs from the other panels + +The window is a fullscreen overlay with a dimmed backdrop and the drawer itself +anchored to its top right, which is the idiom `vm-manager` and `appearance` both +use and what gives click-outside-to-close and a focusable item for Escape. + +It differs from those two in one property: `exclusionMode` is `Normal`, not +`Ignore`. Waybar claims an exclusive zone at the top of this screen, so +respecting it places the overlay below the bar with no height constant here to +drift. Measured: waybar at `y=-540 h=42`, this overlay at `y=-498 h=1038`, +starting exactly where the bar ends. The backdrop therefore never dims waybar. + +**Key events reach a focused item, not a window.** Setting +`WlrLayershell.keyboardFocus` is necessary but not sufficient, and +`Keys.onEscapePressed` on a `PanelWindow` never fires, so the focus sits on an +inner `Item`. + +## Thread rows are read-only + +Each account shows its three newest unread threads as author, relative date and +subject. Clicking one launches qtmaildir and nothing more, because `qtmaildir` +accepts no command line arguments: there is no way to tell it which thread to +open. `startup_account` in its config is a static setting, not a flag, so +"open on this account" is unavailable for the same reason. Marking read from +here was rejected rather than deferred: it would write the database behind a +possibly running client. + +"Sync now" runs `~/bin/mailsync.sh`, which is already lock-protected against a +concurrent cron run, and the watcher picks up whatever it commits. + +## Theme and blur + +`Theme.qml` is the shared one: the palette comes from +`~/.cache/wal/udt-palette.qml` and is watched. It is a fourth identical copy, +which is a known loose end, not a palette to grow. + +Frosting is Hyprland's, matched on this window's namespace: + + hl.layer_rule({ + name = "blur-mail", + match = { namespace = "^(quickshell-mail)$" }, + blur = true, + xray = false, + ignore_alpha = 0.1, + }) + +Without the rule it still works, rendering flat translucent. -- cgit v1.2.3