aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mail-overview/README.md167
1 files changed, 167 insertions, 0 deletions
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.<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.
+
+## 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.