diff options
Diffstat (limited to 'mail-overview/README.md')
| -rw-r--r-- | mail-overview/README.md | 254 |
1 files changed, 0 insertions, 254 deletions
diff --git a/mail-overview/README.md b/mail-overview/README.md deleted file mode 100644 index 34eaa53..0000000 --- a/mail-overview/README.md +++ /dev/null @@ -1,254 +0,0 @@ -# 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 │ - ├──────────────────────────────────────────────────┤ - │ ● watcher ok · 25 folders │ - ├──────────────────────────────────────────────────┤ - │ [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.<key>]` section per account, and -`<key>` is exactly the suffix of the notmuch tag `account-<key>`, 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:"<maildir>/**"`, 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": "<span font='18px'></span> {}", - "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. - -## Notifications on arrival - -`mail-notify.sh` sends one notification per account when mail lands, with the -newest three threads' sender and subject and a `+N more` line when the batch is -bigger. Each message is a bullet on its own line with a blank line between, -under a bold `New Mail` heading with the account and count as the second line. -Started from `autostart.lua`, it runs for the whole session. - - /home/you/Programming/GIT/quickshell/mail-overview/mail-notify.sh - -It watches the same xapian directory the waybar module does, for the same -reason and with the same 0.3s debounce. It is a **separate process rather -than part of `waybar-mail.sh`**, which already has that edge: waybar owns -that script's process, so a bar restart would stop notifications with nothing -reporting it. - -**A commit is not the same as new mail.** Reading a message in qtmaildir drops -its `unread` tag and commits; so does tagging. Arrival is found with notmuch's -revision counter instead: - - notmuch count --lastmod 'tag:unread and tag:inbox' - -which prints count, database UUID and revision, tab separated. Each account is -then asked what it gained in `lastmod:<prev+1>..<cur>`, the lower bound one -revision after the stored one so it is exclusive. A count delta was -rejected because it cannot name senders, and a `date:` watermark because -`date:` is the message's own Date header: backdated mail would never notify -and future-dated mail would notify forever. - -Position is kept in `~/.local/state/mail-notify.lastmod`, written by atomic -replace, holding the **UUID as well as the revision**. Revisions only compare -within one database, so a rebuild restarts the counter and a stored revision -from the old one means nothing. - -**Missing, corrupt or mismatched state seeds silently**, recording the current -revision and notifying nothing. Without that floor, `lastmod:0..` matches every -unread message ever: 101 of them here, which is a wall of popups at every -login. The same applies to a restart, so mail that arrived while it was down is -never notified. The waybar count is still right and the drawer still shows it, -so nothing is lost but a stale popup. - -The notification is sent with dunstify. The icon is passed as an **absolute -path** to the active icon theme's `mail-unread-multiple`, because this dunst -resolves an icon *name* only through the `icon_path` in `dunstrc`, and that -path holds no mail icon, so a name renders no icon at all. The click action -(`-A default,open`) makes dunst prepend an `(A)` action indicator when -`show_indicators` is on, so `dunstrc` sets `show_indicators = no`. A middle -click (dunst's `do_action`) opens qtmaildir, but not the account it belongs -to: `qtmaildir` takes no arguments and `startup_account` is a static setting -rather than a flag, the same limitation the thread rows have. - -`test-mail-notify.sh` is the check. It sources the script as a library and -asserts on the functions that only move text around, so it needs no mail and no -notmuch: - - ./test-mail-notify.sh - -## 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. - -"Open qtmaildir" launches the client. There is no "Sync now" button: the -watcher triggers a sync the moment mail lands, the cron tick is the backstop, -and `on-click-right` on the waybar module runs `~/bin/mailsync.sh` for a manual -pull. - -## The watcher status dot - -Below the accounts, above the button, a coloured dot reports whether -`mail-watcher` is alive and sane, read from its heartbeat at -`~/.local/state/mail-watcher.heartbeat`: - -- **green** (`watcher ok · N folders`) heartbeat fresh, no dead threads. -- **yellow** (`N folder(s) dead, check the log`) heartbeat fresh, but a folder - gave up permanently. -- **red** (`watcher not running`) heartbeat missing, unparseable, or older than - 300s. - -Backoff never turns the dot: it is normal recovery from a dropped IDLE -connection, and the watcher's own health check treats it as healthy. The -staleness rule is the same one `mail-watcher` uses (`heartbeat_is_healthy`), -reimplemented here in a few lines rather than shelling out to -`mail-watcher.py --status` on every open. - -The heartbeat is read once per open. The drawer is a `LazyLoader`, so closing -and reopening rebuilds the `FileView` and reads the file current. A file watch -is deliberately not used: the heartbeat is written by atomic replace (tmpfile -then rename), so an inotify watch held on the old inode dies with it, which is -the same trap as watching a file inside the Xapian directory. The cost is that -a drawer left open does not update until reopened, which for 60s heartbeat data -is not worth a timer. - -## 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. |
