| Age | Commit message (Collapse) | Author | Files | Lines |
|
Plan two of two: the quickshell side of the notification daemon. The Notify
singleton reads the daemon's published files, a new notifications/ component
draws balloons and owns suppression, the drawer's reserved Item becomes the
live queue with a history page, and the Status page gains a snooze row.
Suppression lives in the balloon shell so the drawer can list what DND held
back, and the drawer flag is the single no-double-show mechanism. Per-id
history removal is a tracked TODO in the notifyd repo rather than an
untracked spec gap.
|
|
The freedesktop spec says -1 means the server decides and 0 means never; the
spec and plan had them reversed, which the shipped code inherited and the
live handover test caught. Corrected to match the spec, libnotify's -1
default and dunst.
|
|
The check has five assertions, not four.
|
|
The daemon binary was built at $tmp/notifyd, the same name RuntimeDir() gives
under XDG_RUNTIME_DIR, so the two collided. The runtime dir moves to $tmp/run,
which is what the shipped script does.
|
|
The test snippets omitted the header the global constraints require on every
.go file, which a task review flagged against the first one shipped. The
snippets now carry it, so later tasks do not repeat the gap.
|
|
Public under the Linux cgit section, cloned to ~/Programming/GIT/notifyd, so
Task 1 Step 1 is done and the executor starts at Step 2.
|
|
Plan one of two for the notification daemon: the Go daemon and notifyctl in
their own repo. The renderer plan is separate because the daemon is working,
testable software on its own, exercised through notifyctl.
The policy is pure and tested without a bus; the store's expiry, dismissal
and eviction are separate lifetimes, which the store tests pin; and
test-notifyctl.sh runs the daemon and CLI on a private session bus against a
temporary runtime directory.
|
|
The freedesktop expire_timeout is the notification's lifetime, and --wait
and -b clients block until the daemon closes it. So the daemon emits
NotificationClosed at expiry and frees the client, but keeps the entry in
the live queue: the drawer lists it until it is dismissed or evicted, inert
because its client is gone. History holds only what was dismissed or
evicted.
The reserved space is therefore scrollable, since the live queue can hold
20 at once, and a row past its expiry is read-only.
|
|
The second of the two specs the status registry named. A Go daemon owns
org.freedesktop.Notifications and holds the state; quickshell renders it.
The daemon and notifyctl live in a separate repo, the balloon shell and the
drawer's notification centre live here.
Rendering is split because quickshell has no generic D-Bus module, so
something outside QML has to own the bus name. Files are the interface in
the direction the renderers read, the same convention the registry set, and
notifyctl is the one surface back.
DND suppression lives in the balloon shell, not the daemon: the drawer's
reserved space lists every live notification including the suppressed ones,
because a list the user opened is not an interruption. This supersedes the
registry spec's assumption that the daemon would read dnd.
|
|
F1: after a click the shared Switch writes checked directly, dropping
StatusRow's declarative binding, so an external mode change from waybar
or statusctl no longer moved the switch. Resync via onValueChanged.
F2: the spec claimed hyprctl clients counts idle inhibitors, which is
false for a layer-surface inhibitor; replace with the behavioral
hypridle marker check.
F3: README said the check covers both watch states; it exercises the
activated report and the absent-file down report.
F4: give breakProc an onExited check so a failed breaktimer.sh verb is
visible instead of silent.
F5: drop StatusTile's unused required st property and its injection.
F6: comment the startup-order limit on dndBeforePresentation.
|
|
A FileView fires its own fileChanged on setText, so a handler that writes
in response to a change loops unless it compares first. IdleInhibitor has
no window of its own and does nothing without one, so the singleton is
handed the keepalive window by shell.qml.
Both were read from the documentation while designing and confirmed while
implementing.
|
|
read_mode() failed on a missing mode file with a shell redirection
error, 'statusctl: line 56: .../status.dnd: No such file or directory',
printed to stderr on every get/toggle before the file existed and
polluting waybar's exec stderr.
The '2>/dev/null' sat on the 'tr' command, but the error is the parent
shell's input redirection failure, which that redirect cannot suppress.
Guard on '[[ -e "$file" ]]' before reading; a missing file still
reads as 0, which was always the contract.
Mirror the fix into the plan so plan and script agree.
|
|
Two corrections to the plan, both found by running it rather than reading
it.
The test script the plan specifies makes nine assertions; the prose around
it claimed eight, in both the task step and the final check. An implementer
running it would see a passing suite that disagrees with its own expected
output, which reads as a failure of either the test or the plan. My
arithmetic.
The plan's statusctl also swallowed a failed rename and read the mode file
through cat piped into tr, so a write that did not land still reported
success and an unreadable file fell back to off by accident of pipeline
semantics rather than by intent. Review of the implemented script caught
both. The plan now carries the same fixes, so a future run of it does not
reintroduce them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7ThHHh5iTYbVfp3rNAkw2
|
|
Review of the first task caught that the commit message credited
atomicWrites while the code never set it. The property is already the
documented default in 0.3.1, so the behaviour was right, but statusctl
watches close_write,moved_to specifically because an atomic write arrives
as a rename, and leaning on a default for something the CLI depends on is
weaker than declaring it.
Both ModeFile blocks in the plan now set it, so task four does not
reintroduce the gap when it adds the second mode.
Worth recording: the review asserted atomicWrites defaults to false in
0.3.1 and concluded the shipped writes were not atomic. The v0.3.1 and
v0.3.0 documentation both read "If true (default)", so that conclusion was
wrong, though the underlying observation about the unset property was
right.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7ThHHh5iTYbVfp3rNAkw2
|
|
Eight tasks against the registry spec: the singleton with the dnd mode,
the statusctl CLI and its check, naming the keepalive window, presentation
mode and its three effects, the module and tile, the page and registration,
installing the CLI and swapping the waybar module, then the README and
traps.
The plan carries the facts probed while designing so the implementer does
not re-probe them: the runtime directory is tmpfs, elogind runs here with
the pam caveat that stops it guaranteeing logout cleanup, the compositor
advertises zwp_idle_inhibit_manager_v1, inotifywait is present, and
Inconsolata Nerd Font lives under ~/.fonts rather than either share
directory. That last one cost a failed cmap search that read as a missing
glyph when it was a bad search path.
Two claims stay marked unverified because they come from documentation and
have not been observed running: that a FileView fires its own fileChanged
on setText, and that IdleInhibitor does nothing without a non-null window.
The plan handles both and tells the implementer to confirm them and record
what actually happens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7ThHHh5iTYbVfp3rNAkw2
|
|
Desktop modes as state: do not disturb and presentation mode, with one
place that owns them. First of two specs; the notification daemon that
replaces dunst is the second and consumes the registry's dnd mode rather
than owning it. The registry comes first because DND belongs to the
desktop rather than to the notification daemon, and because the daemon is
the larger and riskier component.
The design that needed deciding was not the toggle but the coupling.
Presentation mode sets DND, so DND has two writers, and turning
presentation mode off has to restore what the user had rather than
clearing it. That rule is why this is a registry and not two booleans.
State is one file per mode in XDG_RUNTIME_DIR, matching the convention
breaktimer already writes on this machine. That directory is /run/user/1000,
a tmpfs, so a reboot clears every mode with no cleanup code. Logout without
reboot is less certain and the design does not lean on it: elogind runs
here and its pam module documents removing the runtime directory at last
logout, but the same manual says the module does nothing when the system
was not booted with elogind as init, which on Slackware it is not. If
removal does not happen, a mode survives a logout, which is the same
behaviour as surviving a shell restart.
FileView covers both directions: atomicWrites for the write, watchChanges
for the watch, no shell-out. External watchers need close_write,moved_to
because an atomic write arrives as a rename, the same trap the mail watcher
hit with Xapian.
Confirmed while designing: the compositor advertises
zwp_idle_inhibit_manager_v1 and waybar's built-in idle_inhibitor already
drives it here, so the inhibitor is asserted over Wayland rather than D-Bus.
That module is replaced rather than demoted, because it owns its inhibitor
object and cannot display state owned by anything else; left alongside the
registry it would assert a second inhibitor and idle would resume only when
both released.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7ThHHh5iTYbVfp3rNAkw2
|
|
Absorbs the kdeconnect-indicator into the drawer: status, battery,
pairing, ping, clipboard send, file share, refresh and mount. State is
polled from a qdbus6 helper script while the drawer is open, since
quickshell 0.3.1 has no generic D-Bus module.
The spec notes the phone-to-PC clipboard is the daemon's plugin, not the
indicator's, so removing the indicator does not touch it.
|
|
The pairing flow needs isPairRequested, isPairRequestedByPeer and the
verification key, so the polled line carries them; the key is only queried
with a pairing in flight. A failed daemon query is a non-zero exit with no
output, which is how the module tells it apart from a valid empty list.
Pairing failure comes from the kdeconnect-cli exit code, not the
pairingFailed signal, since there is no generic D-Bus module to watch it.
|
|
Status plus basic actions, absorbed from the indicator. State comes from
qdbus6 over kdeconnectd, since quickshell 0.3.1 has no generic DBus module.
Polled while the drawer is open, driven by the tile lifecycle.
Notes the indicator is not what serves the phone-to-PC clipboard, and the
FileDialog focus risk under an Exclusive layer surface.
|
|
The tile no longer swaps a wired/wifi icon, and the local IP comes from
ip -j because Quickshell.Networking exposes only the MAC. Recorded in the
spec so it matches the code.
|
|
The Connections block needs a typed property on a QtObject, the bluetooth
README lines belong to Task 6 so every commit stays self-consistent, and
Pairing has no status property. Each was found by a task review and fixed
in the plan as well as the code.
|
|
Eight tasks, each leaving the tree working: network wired page, wifi
section, README, bluetooth module and page, pairing, README, then the
waybar removal and the trap notes.
The spec's failure section is corrected in the same commit: it claimed
forget and disconnect failures would surface inline, but neither backend
exposes a failure signal for them, so that is a ceiling rather than a
feature.
Verified by probe before writing any task: the singletons start empty for
about two seconds, the device lists are ObjectModels, signalStrength is
0..1, scanner and discovering are writable, and NMSettings is not in QML
scope, which drops the hidden-network join the spec had left conditional.
|
|
Two drawer modules covering wired, wifi and bluetooth, using the native
Quickshell.Networking and Quickshell.Bluetooth backends rather than nmcli
or bluetoothctl. The one exception is pairing: quickshell ships no BlueZ
agent and no generic D-Bus module, so a device needing passkey
confirmation is paired through bluetoothctl, whose own agent handles it.
The wifi module became Network so a connected eth0 does not mask the wifi
state; the tile shows both links when both are up because the API exposes
no route metric.
Replaces waybar's network and bluetooth indicators, per the drawer being
the single place they are managed.
|
|
Quickshell 0.3.1 IPC requires every declared argument, so the plan's
`ipc call drawer open` with no page fails; the page-less grid entry is
the zero-argument `toggle`. Also records that `... | head` races
timeout and kills the shell early, so verification uses a file redirect.
|
|
The plan claimed a pragma Singleton outside the config root is invisible
until a qmldir names it, and had Task 1 create one plus Tasks 6, 7 and 8
append a line each. That premise was wrong.
Tested: a pragma Singleton in modules/sub/, reached by a plain
import "modules/sub", resolves with no qmldir anywhere. The control was a
reference to a genuinely undefined type, which warns "ReferenceError:
<name> is not defined"; the singleton case produced no such warning and
the binding evaluated. This matches what AGENTS.md already documented
about Theme.qml, which the code review pointed out.
Four registration steps removed and the remaining steps renumbered. The
AGENTS.md note Task 9 adds now records the tested behaviour rather than
the invented one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We9zcHEH8ZWHPcQwMaJRNv
|
|
The paths are written with a tilde because this file is committed and the
repo forbids home paths in committed files. But neither Hyprland's
exec_cmd nor waybar's exec goes through a shell, so a tilde there never
expands and fails silently, which reads as a broken keybind rather than a
bad path. The two rules genuinely conflict here, so the plan now says
which form goes where instead of shipping lines that would not run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We9zcHEH8ZWHPcQwMaJRNv
|
|
Ten tasks: skeleton, contract, chrome, drawer, then the four modules in
increasing order of risk, then documentation, then the six config edits
outside this repo.
Appearance goes first among the modules despite being last in the grid,
because it is nine lines of substance and puts something in the grid
before the three migrations start. Sound, mail and vm follow in that
order: sound splits one 302-line file into three, mail is the gentlest
move and carries the only automated oracle in the project, and vm is the
largest and the one whose polling behaviour actually changes.
Each task leaves the repo working, because several delete a component and
a half-finished deletion is painful to unpick.
Paths in the config task are written with a tilde: the gitleaks hook
rejected the absolute form, correctly, since this file is committed and
the live Hyprland config it describes is not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We9zcHEH8ZWHPcQwMaJRNv
|
|
The vm module's service is lazy, so the tile had no state to show before its
page had been visited. One `virsh list` when the drawer opens fixes that for
the cost of a single call; the per-VM polling for guest-agent stats and
managed-save detection still waits for the page, which is where the expense
actually is.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We9zcHEH8ZWHPcQwMaJRNv
|
|
Five components mean five processes, five autostart lines, five blur rules,
and functionality scattered across surfaces with nothing in common. Nothing
is discoverable without remembering it exists.
The design is one left-side drawer on DP-1, reached from a launcher at the
left end of waybar, hosting modules as tiles that may open full-height
pages. Modules declare a tile, a page, both or neither, plus whether their
background service runs while the drawer is closed: sound and mail must
(the OSD reacts to keypresses, the unread count outlives the drawer), vm
must not, since polling virsh only paints a page nobody is looking at.
Three components merge in (volume-osd, mail-overview, vm-manager), two stay
separate because a 600px drawer is the wrong shape for them: window-switcher
is a fullscreen ALT+TAB grid, appearance needs room for a wallpaper picker.
Appearance still earns a tile that fires its existing IPC, which is the case
that proves a tile needs no page of its own.
The top of the drawer is reserved and empty. The notification engine is a
later project, but the space has to be claimed now: build the grid anywhere
else and it moves the day notifications land, having been tuned against a
position that does not survive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01We9zcHEH8ZWHPcQwMaJRNv
|
|
The lastmod range is inclusive at both ends and the stored revision
was reused as the next start, so the batch just notified matched
again. Start one revision later.
A subject can contain a newline, which broke the bullet rows and the
+N more count; flatten CR/LF to a space before escaping.
Also reconciles the state-write comment and spec with the actual
skip-and-advance failure behavior, and corrects two doc wordings.
|
|
Why a revision counter rather than a count delta or a date
watermark, why the state file carries a UUID, and why an unseeded
run stays quiet. Also records the final notification presentation:
the split heading, the absolute-path icon, and the show_indicators
setting that keeps the action indicator off.
|
|
Six tasks, TDD against one bash check rather than a framework: the
script is sourced as a library so the pure functions, the ones that
only move text around, are asserted without notmuch, dunstify or
inotify in the picture.
The two parsing traps that already cost this component a debugging
session each get a fixture apiece: a key containing dots, and a
folder value containing a bracket.
Verification does not wait for mail. Backdating the stored revision
by 2000 replays a real window on demand, and the seed run before it
proves the anti-storm rule with 101 unread messages on disk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ZwWrCEbdKzmisfg9bb1nS
|
|
One notification per account per arriving batch, from a new
mail-notify.sh sibling to waybar-mail.sh.
Arrival is detected with notmuch's lastmod revision counter rather
than a count delta or a date watermark: a commit is not the same as
new mail, since reading and tagging also commit, and date: is the
message's own Date header, so backdated mail would never notify and
future-dated mail would notify forever.
A separate process rather than extending waybar-mail.sh, which
already has the arrival edge: waybar owns that process, so a bar
restart would stop notifications with nothing reporting it.
State carries the database UUID beside the revision, because notmuch
revisions are only comparable within one database. Missing, corrupt
or mismatched state seeds silently: with no floor, lastmod:0..
matches all 101 unread messages here and startup becomes a wall of
popups.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ZwWrCEbdKzmisfg9bb1nS
|
|
Implementing the model found it. The property reads undefined, so the
planned sort compared NaN and silently did nothing, leaving the list in
arbitrary tracker order while looking entirely plausible. It is on
lastIpcObject instead.
Carried into the plan's later tasks so the grid does not reintroduce the
bare property, and into the notes that Task 6 puts in AGENTS.md, since a
sort that quietly does nothing is the kind of wrong answer this repo's
notes exist to prevent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYg4wYHq5XNbiVmMeKRb1S
|
|
The screen was found by a name written inline, falling back to null. The
other three panels all take a monitor property and fall back to
screens[0], so this now does too: a machine without a DP-1 gets its first
monitor rather than a null screen, and the name is in one place if it
ever needs changing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYg4wYHq5XNbiVmMeKRb1S
|
|
Six tasks, each ending in a commit: the skeleton and the keyboard grab,
the window model, one card, the grid and empty state, the Hyprland
wiring, and the docs.
Two things were checked against the running system while writing it
rather than left for the implementation to discover. The keyboard grab
works: Keys.onEscapePressed on a focused Item inside the layer surface
fires, so the AGENTS.md workaround holds and keyboard navigation is safe
to build on. And the dispatcher for sending a key is send_shortcut with
the underscore; sendshortcut does not exist, and a test written against
that name would have failed in a way that looks exactly like the key
never arriving.
No qmldir is added. There is none anywhere in this repo and Theme
resolves without one, since quickshell scans the config directory itself;
the AGENTS.md note about needing one is about singletons outside it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYg4wYHq5XNbiVmMeKRb1S
|
|
ALT + TAB with no windows would otherwise give a full-screen dim with
nothing in it, which reads as a hang. It is reachable from a fresh
session, and from closing the last window in the switcher itself, which
stays open by design. It now says so instead.
The empty state has no card to click, so the backdrop closes the overlay
on click as well as on Escape. That applies generally, not only when
there is nothing to show.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYg4wYHq5XNbiVmMeKRb1S
|
|
Two findings from testing against the running compositor, both of which
would have cost time during implementation.
Hyprland 0.56.2 evaluates dispatch arguments as Lua, so the form this
design previously specified, dispatch focuswindow address:0x..., is a
syntax error rather than a command. It fails silently unless stderr is
read: the window does not focus and nothing is reported. hypr-windows.sh
was written with hl.dsp.focus({ window = "..." }) for that reason, and
this design had misread that as legacy oddity.
The fullscreen batch sequence from that script is not carried over.
Workspaces 1 and 8 are monocle and 4 and 5 are scrolling, so a stacked
window is the normal case here rather than an edge case, and the worry
was that focus would land without the window becoming visible. Tested
with two stacked windows on workspace 1: focus alone raised the buried
one, confirmed on screen in both directions, so no alterzorder and no
batch sequence are needed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYg4wYHq5XNbiVmMeKRb1S
|
|
The card cannot take its shape from its preview. DP-3 reports transform=1,
so its windows are 1076x1916 portrait against 2556x1034 on DP-1, a 4.4x
span of aspect ratio in one grid, and a fullscreen app on that monitor is
portrait too. So the card is a fixed 16:10 box with the preview fitted
inside it, which keeps rows aligned; sizing each card to its own preview
would give ragged rows and break the alignment of the three text lines
that makes the grid scannable.
Cards now grow to fill their row up to 640px rather than sitting at a
fixed size, so a lone window is shown large without a preview becoming a
full-screen mirror of the window it stands for. Column count is the
window count capped at six, centred, so one card sits dead centre and two
straddle the middle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYg4wYHq5XNbiVmMeKRb1S
|
|
A full-screen grid of live window previews to replace the rofi list on
ALT + TAB. The script it replaces is not broken, so this is an upgrade
from a textual list to something that shows what is in each window.
Settled during design: toggle-style rather than hold-and-release, since
a missed ALT release would leave a stuck full-screen overlay; cards wrap
into a grid rather than shrinking, so a high window count degrades by
growing downwards; only special: workspaces are filtered, because
jumping to another desktop is the point of a switcher.
Feasibility was checked against quickshell 0.3.1 rather than assumed.
ScreencopyView is compiled in and captured all four open windows, the
scratchpad on special:special included, so hidden windows need no
icon-only fallback. Hyprland.toplevels reads 0 until refreshToplevels()
is called, and its address field omits the 0x that dispatches need.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYg4wYHq5XNbiVmMeKRb1S
|
|
The user works at this machine and runs the shell themselves. An agent
executing this plan writes code and runs non-GUI checks, then hands over the
command and the numbers to compare against; the four steps that need a running
qs are now marked as the user's.
Also warns against a bare pkill -x qs, which would take down the three
components the user already has running.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWL8JYHu7yhAdtx5pU9PMU
|
|
The previous commit, the spec and the plan all said notmuch exits 0 on a
malformed query while printing something that is not a count, and that
validating the output as an integer therefore catches it. Measured properly,
that is wrong in a way worth recording, because the truth is worse.
notmuch fails two different ways. A rejected query 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 (('` gives
41, and `'tag:unread and tag:'` gives 3. The second is undetectable by any
check on the output, which is why the original claim was not just imprecise
but inverted: the case it described as caught is the case nothing can catch.
The integer validation still earns its place, on the first failure mode, where
empty output would otherwise render as an empty inbox. The real defence
against the second is that QUERY is a fixed string and is never built from
anything, which the comment now says.
The earlier measurement that produced the wrong claim read 40 as mangled
output when it was a successful parse answering a different question.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWL8JYHu7yhAdtx5pU9PMU
|
|
Seven tasks, ordered so the thing most likely to be wrong is checked first.
The watcher script comes before any QML because it is the only piece with a
real runnable check and needs no compositor. The component skeleton gets its
own commit because the failure it guards against is invisible: a config whose
only window is hidden exits straight after logging Configuration Loaded and
reports no error.
Two decisions changed while reading the existing components. The drawer is a
fullscreen overlay with content anchored top right, which is the idiom both
other panels use and what gives click-outside and Escape for free, rather
than a small anchored window. And its exclusionMode is Normal rather than the
Ignore those two use, so waybar's exclusive zone places it below the bar
without this component carrying the bar's 42px height as a constant to drift.
Also records a trap measured this session: notmuch exits 0 even for a
malformed query, printing something that is not a count, so every count is
validated as an integer rather than trusting the exit status. A failure that
rendered as 0 would look exactly like an empty inbox.
The live-config task needs real absolute paths, because waybar's exec and
Hyprland's Lua strings do not expand a tilde. They are written as <repo> and
<home> placeholders with a note to substitute, since the gitleaks hook blocks
committed home paths and was right to reject the first attempt.
The spec was updated to match both changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWL8JYHu7yhAdtx5pU9PMU
|
|
qtmaildir scopes an account with a path glob, so the obvious move was to copy
that query. It is wrong for this panel. notmuch deduplicates by message id, so
a message that arrived at two configured addresses is one message with two
paths, and a path glob counts it under both accounts: the five accounts here
sum to 102 against a global total of 101.
The icon shows one global total and the panel header repeats it, so rows that
sum to a different number would look like a bug. Counting by the account tag,
which is a property of the message and therefore singular, makes the rows sum
to the header. The cost is that a cross posted message appears under only the
account the post-new hook attributed it to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWL8JYHu7yhAdtx5pU9PMU
|
|
Replaces three waybar modules that each poll the Gmail API through a python
script with its own credentials file, cover three of five accounts, and open
Thunderbird. Everything they fetch over the network is already in the local
notmuch index, tagged per account.
Two decisions carry most of the design. Counts are scoped to
"tag:unread and tag:inbox" rather than plain "tag:unread", which for one
account is the difference between 32 and 41, because the inbox number is the
one that means new mail worth looking at. And the waybar module runs in
continuous mode with an inotifywait loop, so waybar owns the watcher process
and there is no daemon to supervise on a machine with no systemd; the watch is
on the xapian directory rather than on named files because a commit replaces
them, which breaks a watch held on a file.
The account list is not in the component. qtmaildir.conf already has one
[account.<key>] section per account, where <key> is the notmuch tag suffix,
carrying a display label and a colour, so the panel parses that and a new
account appears without touching QML.
Thread level actions are out of scope because they are blocked rather than
deferred: qtmaildir takes no arguments, so nothing can tell it which thread
to open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWL8JYHu7yhAdtx5pU9PMU
|