Changelog
Notable changes to this setup, in human terms â what changed, why, and what broke
along the way. Newest first. Version markers (## vX.Y.Z) mark release
boundaries on top of the dated entries â see docs/VERSIONING.md for the
full branch/release process.
2026-10-21 (kosha 0.2: a nala-like interface, help, tab completion)
- kosha 0.2.0: boxed tables, a plan and
[Y/n]before changing anything, live parallel downloads, âWhatâs newâ after updates,kosha help/kosha <command> --help, and suggestions for typos.kosha listtakes about 0.2 s. - zsh: tab completion for koshaâs commands and package names (
.zshrc).
2026-10-21 (kosha: a package manager for Vayuâs tools)
- New tool, its own repo: kosha
(à€à„à€¶, âtreasuryâ), the package manager for Vayuâs tools.
kosha list,install,update(released versions, with each toolâs changelog),outdated,remove,pin,link(develop in place),doctor. - install.sh installs every tool through it:
setup_vyom,setup_appsandsetup_anumatiare now onesetup_kosha; the list isKOSHA_PACKAGESin packages/manifest.sh (wasVYOM_TOOLSandVAYU_APPS). Existing installs were adopted as they are. - At login, update-check.sh also asks kosha for tool updates and shows a
notification with âUpdate nowâ (opens kitty running
kosha update). - vayu-verify runs
kosha doctor.
2026-10-21 (vayu-elevate is now anumati, in its own repo)
- The root-approval tool for AI agents is now
anumati (à€
à€šà„à€źà€€à€ż,
âpermissionâ), its own repository, like every Vayu plugin.
install.shclones it into~/.local/share/anumatiand runs itsinstall.sh(root-owned into/usr/local);ai/is gone from this repo. vayu-elevatekeeps working:/usr/local/bin/vayu-elevateis a link toanumati. New paths:/var/log/anumati.log,~/.local/state/anumati/requests.jsonl; the old logs stay where they were.- swayâs floating rule follows the windowâs new app id; the keybinding
search lists its keys as âAnumatiâ;
vayu-verifychecks the install; Claude Codeâs global notes sayanumati.
2026-10-21 (nÄda: our own music player, replacing rmpc)
- New app, its own repo: nÄda,
cloned by install.sh (
setup_apps,VAYU_APPSin packages/manifest.sh) into~/.local/share/nada,nadalinked into~/.local/bin. It opens on the playing songâs synced lyrics (Home,0), with the cover, singers, views and release age on the right; the queue (1) lines up what plays next like YouTube and is edited with vim keys; YouTube search (3);Ddownloads a YouTube song into~/Music/YouTubewhile it keeps playing. MPD stays the engine. Super+Shift+Mnow opens nÄda (was rmpc); newSuper+Alt+Space/Super+Alt+./Super+Alt+,play/pause, next and previous without opening anything. Media keys already go through playerctl, which nÄda answers over MPRIS. sway startsnada daemonat login.- waybar: the music module right of the workspaces is now nÄdaâs
(
custom/nada): moving bars and the title while playing, a pause sign when paused, a progress ring while downloading, gone when stopped. Click pauses, scroll seeks 10 s, middle click skips, right click opens nÄda. - raga (v0.1.2) plays music in nÄda instead of
rmpc addyt. - rmpc removed: its stow package,
rmpc-toggle.shand its pacman entry. - The keybinding search (
Super+Shift+/) lists nÄdaâs 47 keys (source âNadaâ, pink).
2026-10-19 (every Vyom popup the same size; key names instead of symbols)
- Every Vyom panel (apps, calculator, emoji, Wi-Fi, power menu, battery,
vyom dmenupickersâŠ) now opens at one size,width/heightin[vyom]ofvyom/.config/vyom/config.toml(900 x 520 times scale 1.25), so switching tools never jumps. Quick settings (kendra) keeps its own size: it drops down from the barâs corner rather than popping up in the middle. Vyom 0.2.5. - The power menu and battery are cards, not search panels: they keep their
own, smaller size, as tall as their content. Panels are more see-through
(
opacity = 0.62in the Vyom config, was 0.82) so the blurred desktop shows through like glass. Vyom 0.2.6. - No Vyom panel reads as a solid block any more:
blur_xrayis off for thevyomlayer (it blurred only the wallpaper, so over a dark part of it a panel looked opaque) andopacityis 0.55, so every panel shows a blur of whatever is really behind it. - Footer hints and buttons spell keys out (Ctrl+Enter, Shift+Enter, Esc, Backspace) instead of symbols like ââ” and â«. Vyom 0.2.4 and the toolsâ next releases.
2026-10-19 (app launcher: 37 entries down to 17)
- Uninstalled what Vyom made redundant: Bluetooth Manager (blueman; setu
replaces it), the NetworkManager applet with Advanced Network
Configuration (tarang;
nmtuistays for advanced settings), the Foot terminal (kitty is the terminal) and Qt6 Settings (qt6ct; Qt theming was never wired up). Off packages/pacman.txt too. - Hidden from every launcher (dvara included), not uninstalled, because other packages need them: the three Avahi browsers (avahi: CUPS, MPD, PipeWire), the V4L2 webcam test tools (ffmpeg), xgps / xgpsspeed (gpsd: waybar), OpenJDK Console / Shell (Android builds), the KDE Connect app, indicator and SMS entries (doot replaces them; the daemon stays), Thunar Preferences and Bulk Rename. Each is a copy of its .desktop file with NoDisplay=true in xdg/.local/share/applications (delete one to show it again); ~/.local/share/applications is now a real directory (STOW_REAL_DIRS), so apps that add their own entries there donât write into the repo.
- Also uninstalled Beekeeper Studio (a SQL GUI nothing needs; off packages/aur.txt).
- Found doing it:
pacman -Rns bluemanalso removed python-cairo, which urjaâs charge ring draws with (PyGObjectâs cairo support). Reinstalled and now listed explicitly in packages/pacman.txt.
2026-10-19 (wofi and nwg-bar replaced by Vyom and its tools)
- Every wofi popup and the nwg-bar power menu is now a Vyom tool, each its own repository named in Sanskrit (CLAUDE.md âPluginsâ): dvara (apps, Super+D), ganit (calculator, Super+C), smriti (clipboard, Super+M), bhava (emoji, Super+.), tarang (Wi-Fi + hotspot, Super+Shift+W), setu (Bluetooth, Super+Shift+B), doot (phone, Super+Shift+O), patra (Docker, Super+Shift+D), suchana (notifications, Super+Shift+N), raga (YouTube, Super+Shift+Y / Super+Ctrl+Shift+Y), mudra (keybindings, Super+Shift+/), urja (battery, the battery capsule), vishram (power menu, Super+Shift+P), kendra (quick settings, Super+Q / the gear). Same keys as before; pressing one again closes its panel.
- What each gained over its wofi script, from the design review: live answers (ganit), a full preview with image thumbnails (smriti), a grid with recents and skin tones that types the emoji for you (bhava), live signal and inline passwords plus a hotspot QR code (tarang), battery levels and discovery while open (setu), live CPU/memory and logs (patra), on-screen notifications with their actions (suchana), thumbnails and music/video in one panel (raga), key caps and running the binding (mudra), health, cycles and wattage (urja), a countdown with cancel before reboot/power off (vishram).
- Vyom v0.2.0 is now the runtime for those repos: a plugin API, one shared
panel (list, grid, card, preview, key hints), instant opening over D-Bus,
and
vyom dmenufor scripts. notification-actions.sh, phone-events.pyâs pairing dialog and update-check.shâs update prompt usevyom dmenu; keybind-search.pyâs own wofi front-end is gone (it feeds mudra with--json) and it now reads every Vyom toolâs annotated keys (64 of them). - install.sh clones Vyom to ~/.local/share/vyom/core and the tools (VYOM_TOOLS in packages/manifest.sh) to âŠ/plugins/, updating them on a re-run; a machine with the older ~/.local/share/vyom clone is moved over. Vayuâs settings for them: vyom/.config/vyom/config.toml (rewritten per tool: hotspot command, notification modes, the battery-limit file, swaylock, rmpcâŠ).
- Removed: the wofi, nwg-bar and networkmanager-dmenu stow packages and
packages, wofi-emoji, and the scripts they ran (wifi-picker.py,
bluetooth-picker.py, phone-picker.py, docker-picker.py,
notification-history.py, music-search.py, wofi-calc.sh,
battery-limit-picker.py, nwg-bar-launch). Their behaviour moved into the
tools, including the hard-won parts (raga keeps the before/after queue
check, because
rmpc addytexits 0 on a failed download). - Found along the way: this ProBookâs battery reports charge_* and current_now, not energy_*/power_now, so time left was blank; urja and kendra handle both kinds of driver now.
- Troubleshooting: âPanels: a key or a bar click opens nothingâ.
- Panels are 25% bigger (Vyom v0.2.2:
scale = 1.25in vyom/.config/vyom/config.toml; raise it for bigger, capped at 90% of the screen).
2026-10-19 (waybar polish: icon-only network, workspace chips)
- The connectivity capsule is just an icon and the speed: the wired- network glyph (Material âlanâ, like Lucideâs network icon) or the Wi-Fi signal icon, at 150% so it reads at a glance; the network name and IP moved to the tooltip. Disconnected / Wi-Fi off show their own glyph in red.
- No divider lines inside the grouped capsules any more, spacing only.
- The power capsule is gone from the bar: quick settings (the gear, Super+Q) has the power menu and lock in its header.
- Workspaces are chips instead of underlined numbers: the focused one a filled mauve chip with its number and the icons of the apps in it, other occupied ones the number and icons in soft silver, empty ones a dim number, urgent ones red.
- One accent: Vayuâs primary, Mauve #CBA6F7, is now the icon color across the bar (network, speed arrows, volume, brightness, battery, the bell, docker, caffeine, hotspot, the gear, now playing), text the base silver. Other colors only where they mean something: red for disconnected, muted, do not disturb and a critical battery, peach for a low one.
- The enlarged network icon was cropped on its right: a trailing space and more padding give the 150% glyph its room.
- Workspace chips were cramped: wider chips, more space between them and between the number and its app icons.
- The speed segment had a 177px min-width (against jitter as the numbers change length) that GTK centred the text in, leaving a gap after the icon. The values are now fixed-width in the format (monospace), so it neither jitters nor gaps.
2026-10-19 (a development tmux bar, a rearranged waybar, quick settings)
- tmux status bar, for development. No date or time any more (waybar shows them right above). Left: the session (now saying PREFIX / COPY while those are on), then the active paneâs folder and git branch, â!â with uncommitted changes, âââ when clean. Middle: each window with an icon for what it runs (nvim, lazygit, node, python, docker, ssh, btop, rmpc, else a terminal), â on activity. Right: âN to reviewâ (open PRs waiting for your review across every gh account; click it for sangam) and running containers, each only while there are any.
- Still no #() jobs in the status line: the branch is pushed by a zsh
prompt hook (
tmux set -p @git, one git call per prompt, only inside tmux), the review count by bar-events.py every 5 minutes through the newgh-review-count(finds a Homebrew gh, keeps the last value offline). Existing shells show the branch afterexec zsh. - Fixed while testing: commas inside a tmux #{?âŠ} conditional must be
written
#,, or#[fg=..,bg=..]splits the conditional (it leaked raw âbg=#âŠâ text and showed â0 to reviewâ). - Waybar rearranged (from the design review): left, workspaces with an icon per app in them, and now playing (MPD; click pauses, right click opens rmpc; hidden when stopped). Middle, one capsule: notifications, time, date. Right, from temporary to permanent: indicators that only show while on (Caps Lock, hotspot, containers, phone, caffeine), one connectivity capsule (network, a connected Bluetooth device, speed), one system capsule (volume, brightness, battery), the â quick settings, power. Ethernet shows âEthernetâ instead of the IP (IP in the tooltip).
- Deduplicated: Num Lock, theme and wallpaper capsules are gone (theme and wallpaper are in quick settings); docker, phone and caffeine no longer sit on the bar while idle; Bluetooth shows only a connected device. An everyday bar is 8 capsules instead of 18.
- Waybar names a groupâs widget after the group (âgroup/systemâ is #system), so the clock group is âclockboxâ: âclockâ collided with the clock modules and gave every segment its own pill.
- Quick settings: Vyom v0.1 (github.com/Theyashsawarkar/vyom,
its own repo per the plugin rule), the first piece of the wofi
replacement: a GTK 4 + layer-shell panel with Wi-Fi, Bluetooth, hotspot,
do not disturb, caffeine, dark theme, new wallpaper and power profile
tiles, volume and brightness sliders, battery and uptime, lock and power
buttons. Opens from the â capsule or Super+Q in ~0.1 s (one resident
process,
exec vyom daemonin sway). Vayuâs tiles drive Vayuâs own scripts (vyom/.config/vyom/config.toml, a new stow package), so state stays shared with waybar and mako. SwayFX blurs it like wofi (layer_effects "vyom"). install.shclones Vyom to ~/.local/share/vyom and links ~/.local/bin/vyom; keybind-search.py reads Vyomâs annotated keys (source âVyomâ).
2026-10-19 (Wi-Fi hotspot: share this laptopâs internet)
- There was no way to start a hotspot on this desktop at all. New
vayu-hotspot(on / off / toggle / status / clients / qr): a WPA2 hotspot through NetworkManager, one connection âVayu Hotspotâ, named â-hotspot" with a generated password in three groups of four (easy to type on a phone; `--ssid` / `--password` change them). `qr` prints a code phones scan to join; `status --json` is for the bar and for Vyom. - Works on other laptops: nothing is assumed about the hardware. It uses
the first Wi-Fi device NetworkManager says can be an access point (or
--device), refuses 5 GHz on cards without it, turns the Wi-Fi radio on if itâs off, names a hardware switch block, and works withoutiw. A card that canât host says so, with the way around it (a USB adapter). - Channel pinned to 6 (2.4 GHz) / 36 (5 GHz): left to choose, NetworkManager picked channel 13, which phones set to a US region canât see.
- Two firewalls stood in the way, both default DROP. New dispatcher hook
system/etc/NetworkManager/dispatcher.d/50-vayu-hotspotopens them only while the hotspot is up (tagged rules, removed when it goes down): ufwâs INPUT for DHCP (udp 67) and DNS (53) from the hotspot, without which a phone associates and then sits at âconnectingâ with no address; and Docker/ufwâs FORWARD (in from the hotspot, replies back), without which nothing it sends gets out. - Tested with a real phone (CMF Phone 2 Pro, Android 16, over adb): it sees âAce-hotspotâ on channel 6, joins with the generated password, gets 10.42.0.201, Android marks the network validated and uses it as the default (not mobile data), 20/20 pings at 7 ms minimum, and the laptopâs wlan0 carried 15 MB of its traffic. The first attempt is what found the INPUT rules missing. The phone was put back as it was (Wi-Fi off, test network forgotten).
clientslists devices without root: associated stations fromiw, their address from the neighbour table, their name from a reverse lookup to the hotspotâs own DNS (dnsmasq knows it from the lease; the leases file is root-only). A device that switches Wi-Fi off without leaving stays in the station list for minutes, so stations silent for 90 s donât count.- The hotspot takes the Wi-Fi card over (this Intel 8265, like most cards, canât host and stay on a network under NetworkManager), so it shares a wired or USB-tethered connection; it warns before Wi-Fi drops. Troubleshooting has three new hotspot sections.
- Waybar: a teal
custom/hotspotcapsule next to the network one, only while the hotspot is on: devices connected, and a tooltip with the name, password, devices and internet source. Click opens the Wi-Fi menu, right click turns it off. Refreshed by bar-events.py (signal 15) when NetworkManagerâs active connections change oriw eventreports a device joining or leaving, plus a 30 s timer for devices that vanish. - The Wi-Fi menu (wifi-picker.py) has âStart hotspotâ / âStop hotspot (N connected)â / âCopy hotspot passwordâ rows.
- Fixed while testing: scripts started by waybar and wofi donât have
~/.local/binon PATH (swayâs environment), so the capsule showed nothing until the scripts added it themselves. - New packages: dnsmasq (NetworkManagerâs shared mode needs it), iw, qrencode, and gtk4-layer-shell (for Vyom, the coming wofi replacement).
- CLAUDE.md / AGENTS.md: a âHardware: detect it, never assume itâ rule.
2026-10-19 (the PR popup is now sangam, a command too)
- tmux-gh-pr became sangam
v2.0.0: a pull request tool you run in any terminal, like lazygit
(
sangam;sangam doctorchecks its dependencies), and still the prefix G popup here. It has its own logo, README, installer, Arch PKGBUILD and CI. The GitHub repo was renamed (the old URL redirects). - tmux.conf pulls
Theyashsawarkar/sangamand sets@sangam-launcher(popup.sh) and@sangam-title; the old@gh-pr-*options are gone. On an existing machine the next tmux load clones it (plugins-missing.sh); remove the old~/.tmux/plugins/tmux-gh-prby hand. Your sections carry over from ~/.config/gh-pr to ~/.config/sangam. install.shlinks TPMâs copy to~/.local/bin/sangam, so the command and the popup are one install andprefix Uupdates both. On a machine set up before this:ln -sfn ~/.tmux/plugins/sangam/bin/sangam ~/.local/bin/sangam.- keybind-search.py also scans tmux pluginsâ
bin/dirs (sangamâs keys live in its bin/sangam);--checkpasses, each key listed once. - CLAUDE.md / AGENTS.md âPluginsâ: a new rule, a tool first when it can be one: something useful outside its host becomes a command anyone can install, with the host integration as one way to use it.
- Tested: prefix G with this tmux.conf on an isolated server opens sangam through popup.sh, its key sheet shows âor Ctrl+a Gâ, prefix G closes it, and it leaves no state or tmux server behind.
2026-10-19 (site on phones; the PR popup is its own plugin)
- The website works on phones. The navâs eight links used to wrap into a tall sticky block; below 1024px they now fold behind a menu button (assets/nav.js; a link, Esc or a tap outside closes it). That also fixes âGitHubâ wrapping alone onto a second row on ~900-1000px wide windows (checked every 20px from 860 to 1600: one row or the menu, never two).
- Nothing on the site is wider than a phone any more (checked at 320, 360, 390 and 768px, every page): the heroâs two halves dropped their desktop min-widths, the screenshot deckâs peeking cards fan out by a few px instead of up to 70, the hero glow is clipped, the card grids never ask for a column wider than the screen, and long slash-joined words in the docs break. Page gutters are 1rem, the CTA buttons share a row, and the Stable/Nightly tabs fill the width (icons drop below 380px).
- Docs pages on a phone: the âOn this pageâ sidebar is a folding card above the article instead of a full list (Changelogâs 50+ entries pushed the article several screens down); picking a section folds it. On touch screens the copy button on code blocks is always visible.
- Desktop layout unchanged.
- The pull request popup (prefix G) moved to its own repository,
tmux-gh-pr (v1.0.0),
and tmux.conf pulls it with
set -g @pluginlike any TPM plugin. Vayu keeps its look through the pluginâs options (@gh-pr-launcher= popup.sh,@gh-pr-title). tmux/.tmux/scripts/gh-pr.sh is gone. - New rule (CLAUDE.md âPluginsâ, and the new AGENTS.md for other agents): every plugin we write lives in its own repo under Theyashsawarkar and is pulled in, never copied in, so each one can be tested and upgraded on its own and installed without the rest of vayu.
- A plugin added to tmux.conf after a machine was set up is now cloned on
the next tmux load (
plugins-missing.sh, a quick directory check; TPMâs installer runs only when one is missing), so agit pullof vayu is enough.install.shalready cloned every@pluginline on fresh installs. Tested: with tmux-gh-pr removed, a reload cloned it and bound prefix G. - keybind-search.py also reads
# keybind:lines from tmux pluginsâscripts/dirs, so the PR popupâs keys stay in both searches.
2026-10-16 (PR popup: sections across repos, like gh-dash)
- The PR popup (prefix G) only listed the PRs of the repo it was opened in, so it was useless anywhere else. It now has sections, like gh-dash: each is a GitHub search across all repos, run as one of the gh accounts, so work (nimapdevyash) and personal (theyashsawarkar) PRs sit side by side and work from any folder.
- Sections come from
~/.config/gh-pr/sections(name | account | search), local and not in this repo; the popup writes generic defaults (review requested, my PRs, involved) on first run, and E opens the file. Opened inside a GitHub repo, a â(here)" tab is added unless a section already pins that repo. - h/l switch sections (tabs with counts, in a âsectionsâ box above the list); f cycles open / merged / closed / all for every section. Each section is one GraphQL search with CI and review state included; all sections are fetched in the background at start, so switching is instant from cache.
- Rows carry owner/repo#number, so every view and action knows its repo; checkout (o) only runs inside a clone of the PRâs repo.
- Layout L2, picked from mockups: a box of section tabs across the whole
popup (the current one a rounded gold capsule, like the active window
in the tmux bar; the account and any search on the right), then the PR
list and the preview (42%). fzf canât draw anything spanning its list
and preview, so the popup runs its own small tmux session, gh-prs-
: the strip is a 3-line pane redrawn when the section/state/account/ search or a count changes, fzf is the pane below. Each PR is one line in aligned columns (CI, review, repo, #, title, author, age, +/-) under headings inside the list box; merged/closed tags only show in the "all" view. - Nothing in the popup moves around: the repo column used to hide in one-repo sections, which shifted the headings and rows whenever the section or data changed, so every section now has the same columns (checked: the heading row is byte-identical across sections, states and accounts). Tab counts take a fixed 2 cells (· while loading), so tabs donât shift as counts arrive, and â? keysâ moved from the listâs title (where it moved with the section name) to the stripâs right end.
- prefix G inside the popup now closes it natively (it kills that session); the Ctrl+a-then-G workaround is gone.
- No key legend on screen: ? opens a real popup over everything with every key (move / read / act); any key closes it.
- u switches account: all sections, then only one gh accountâs (its sections, plus the account-less ones run as it), shown on the strip.
- s searches GitHub within the current section: anything GitHub search takes (author:x, label:y, head:branch, words), shown on the strip; empty clears. F toggles open â all; open stays the default.
- The list is just #, title, author, age and +/-: the repo name and the CI / review icons moved to the preview, whose overview is now in blocks: repo + #, the full title (wrapped by words), state badge, author, times and branch; then status (merge, checks, reviews, changes, labels, assignees, each with an icon in one column), files (long paths shortened in the middle instead of wrapping with âł), description (wrapped by words, not mid-word) and activity. Diff and comments wrap themselves, so fzfâs âł markers are gone.
- S manages the sections from the popup: add (name, account picked from the signed-in ones, GitHub search), edit, delete (asks first), move left/right, and pin the repo youâre in. It rewrites ~/.config/gh-pr/sections, keeping its comment header (tested: add, delete and move round-trip back to an identical file).
- Headings and rows share one indent, so â#â, title and author line up exactly over their columns.
- m opens a merge panel instead of a one-line prompt: readiness (checks, review, conflicts, up to date with the base, draft), the commits that land, and only what fits the PRâs state: the repoâs allowed merge methods when itâs ready (squash with an editable commit title, prefilled âtitle (#n)â), update branch when itâs behind, auto-merge or an admin merge when itâs blocked, check out to resolve when it conflicts (or the gh command, outside its clone), mark ready when itâs a draft. It re-reads the PR after each step and confirms once before merging; a spinner runs while GitHub works.
- Tested on Theyashsawarkar/pr-playground, a public test repo with one PR per situation (clean, conflicting, behind a branch that requires up-to-date, failing CI, draft, and one needing an approving review; nimapdevyash is a collaborator so it can approve). A squash merge through the panel landed as âAdd a farewell helper (#1)â and deleted its branch.
- The previewâs label is â repo · 1 overview âŠâ: no PR number (itâs in the list and the overview) and a space before the repo name.
- The tab strip flickered: every background fetch (on open, f, u, h/l) cleared it and redrew it, and building the frame takes ~100 ms of jq. It now builds the frame first, writes it over the old one in one go, and skips the write when nothing changed (checked: 22 identical frames in a row during f, one change when the counts arrive).
- The account picked with u is remembered in ~/.cache/gh-pr/last-account, so it survives closing the popup and rebooting (if still signed in).
- Every tool popup now has the rounded rose border with its name top left, like lazygit: sessions (prefix s), pull requests (prefix G) and keys (prefix ?) were borderless. Their own inner âsessionsâ / âtmux keysâ frame and label went, so the name shows once.
- Fixed: from the real tmux server the popup found no
gh(Homebrewâs, added to PATH by zshrc, which the systemd-started server never runs), so it only worked when tmux was started from a shell. The script now adds Homebrewâs bin when gh isnât on PATH. Also, a failed token lookup no longer leaves the preview empty: it falls back to ghâs active account; and fzfâs {1} (which keeps the trailing tab) is trimmed. - Tested from a folder outside any repo, from cli/cli (the âhereâ tab and checkout prompt) and from vayu (no duplicate tab), and in a kitty window whose tmux has the same PATH as the real server (screenshot).
2026-10-16 (tmux: visible copy-mode selection, mouse copy)
- Copy-mode selections had no highlight at all: since tmux 3.6, copy-mode-selection-style and copy-mode-position-style default to â#{E:mode-style}â, which renders unstyled. Both are now set to the rose mode style, so prefix Enter, then v, shows whatâs selected (checked with a screenshot in kitty). Taken from nimapdevyash/dotfiles, which had the fix.
- Mouse, also from that config: a drag copies to the clipboard and keeps the highlight (a click clears it); double-click copies a word and triple-click a line, in or out of copy mode (apps that use the mouse themselves still get the clicks).
2026-10-16 (tmux: a pull request popup, prefix G)
- New
gh-pr.sh, opened with prefix G (next to lazygitâs prefix g), for the repo in the current pane. Everything a PR review needs, without the browser: a list with CI (â â â) and review state (â approved, â changes requested, â waiting) per PR, and four views: overview (state badge, merge readiness, checks, reviewers, per-file change bars, description), diff, checks (Enter shows the failed jobsâ logs) and comments (conversation plus comments on the code). - Actions: merge (squash / merge commit / rebase / auto-merge, deletes the branch on GitHub only, never touches the local checkout), approve, request changes and comment (in $EDITOR), close/reopen, checkout, draft/ready, copy link, browser. Filters: open, review requested, mine, closed, merged, all.
- Vim-style: opens in normal mode (letters are commands); / searches, Enter or Esc goes back. Every key is in the popupâs footer and in the keybinding search (âPR popupâ).
- Opens instantly: the last list is cached per repo and refreshed in the background (ârefreshingâŠâ in the label); details for the first 15 PRs are fetched ahead, 4 at a time. GitHub calls take 1.5-4 s here.
- The account follows the folder:
git config github.user(set in the personal/work git configs) picks the gh accountâs token, so vayuâs PRs use theyashsawarkar and work repos nimapdevyash, with nogh auth switch. - prefix G inside the PR popup closes it. tmux doesnât get keys while a popup is open, so the popup catches it itself: Ctrl+a marks the time and a G within 2 s quits (a plain G still jumps to the last PR).
lessadded to pacman.txt: the full-screen views page through it (it wasnât installed; PAGER wasmore).- Tested on cli/cli (read-only): list, all four views, search mode, filters, full screen, failed-job logs, and the merge/close prompts up to cancel. Actions that write to GitHub (merge, approve, comment, close) are not yet tested against a real PR.
2026-10-16 (waybar: a visible thin border on every capsule, lit on hover)
- Every capsule has a 1px Mauve border at 60% (was 18%, nearly invisible on a light or blue wallpaper). âOffâ states (docker idle, bluetooth off, phone disconnected, network disabled) used to drop the border to transparent; they keep a dim gray one now, so each capsule is always outlined.
- Hovering any capsule, the workspaces group included, turns its border solid Mauve with a soft glow (power stays red). The hover rule moved to the end of style.css: it sat before the state rules (#network.wifi, #battery.warningâŠ), which have the same specificity, so hovering a capsule in one of those states never changed anything.
- Checked from zoomed screenshots at rest and while hovering brightness and the ethernet capsule.
v1.19.0-nightly â 2026-10-16
2026-10-16 (tmux: vim-style pane keys, no underline on the active window)
- prefix h/j/k/l moves focus between panes like vim (replacing tmuxâs prefix l = last window). prefix n/p switch to the next/previous window and wrap around from the last window to the first.
- prefix Shift+h/j/k/l resizes the pane (5 columns / 2 rows, same as Alt+Shift+arrows) and repeats: prefix H H H. Kill session moved from prefix K to prefix X, and last session from prefix L to prefix b.
- prefix \ splits side by side, replacing prefix |, so itâs one key without Shift.
- Every tmux popup (session picker, lazygit, scratch shell, btop, key search, extrakto) is now the same size, 90% x 88%. They were four different sizes, down to 70% x 50%. The size is set once in tmux.conf (@popup_width/@popup_height) and popup.sh reads it; the one-line command prompt stays one line.
- Session picker (prefix s), left side redesigned: the search, the list and a key legend each get their own box. Each session takes two lines: name (gold dot = current) plus its window count, window names and directory. The legend names every key (enter switch, ctrl-x kill, ctrl-n new, esc close). New: Ctrl+n creates a session from the typed name even when it partly matches an existing one (before, Enter switched to the match). Ctrl+x on the current session now says why nothing happened instead of silently doing nothing. Search matches session names only, not window names.
- The active window capsule in the status bar had a line under it: tmuxâs window-status-current-style was âunderscoreâ. tmux.conf now sets it to default.
2026-10-16 (Alt+Space full screenshot, Alt+Shift+Space full recording)
- Alt+Space takes a full-screen screenshot (saved to ~/Pictures/Screenshots and copied to the clipboard). Alt+Shift+Space starts a full-screen recording and stops it on the next press (saved to ~/Videos/recordings). A 60% keyboard has no Print key; Print, Super+Print and Super+R still work on a full-size keyboard.
2026-10-16 (second laptop: install failures fixed, hardware detected instead of hardcoded)
- Installed on an HP ProBook 640 G3 (Intel i5-7200U, HD 620, Intel 8265 Wi-Fi), the first machine other than the Acer A315-23. The install log ended with two failed steps and vayu-verify at 90/92; a hardware audit found more that failed silently. All fixed, and everything that was tied to the Acer is now detected on the machine.
- mpd failed at install (âFailed to access ~/Music: No such file or
directoryâ): a fresh machine has none of the directories mpd.conf names.
New
mpd.service.d/prepare-dirs.confrunsmpd-prepare-dirs.sh, which reads them from mpd.conf and creates them. - ydotool failed (âfailed to open uinput device: Permission deniedâ): the
inputgroup applies at the next login and ydotoolâs udev rule at the next boot.setup_groupsnow grants the user an ACL on /dev/uinput so it works in the same boot. - Neovim:
+qaquit while mason.nvim was still installing, aborting 5 tools and making nvim-treesitter fail with âPackage is already installingâ. install.sh now starts nvim a second time and waits until no Mason package is installing. - Lid-closed poweroff never fired on the HP: the hook read
/proc/acpi/button/lid/LID0/stateand the HPâs lid isLID. It now takes whichever lid device exists (the DRY_RUN notification also finds the seat0 user instead ofyash/uid 1000). - battery-warning-dismiss never dismissed: it matched the adapter name
ACAD(ACon the HP). It now finds the adapter bytype=Mains. - New
packages/hardware.sh: microcode and GPU packages come from the CPU vendor and PCI display controllers (intel-ucode / amd-ucode, vulkan-intel + intel-media-driver / vulkan-radeon / vulkan-nouveau) instead of pacman.txtâsamd-ucodeandvulkan-radeon. The HP had no Vulkan or hardware video decode before this. system/apply.shdropped the stockkmshook on every machine, but only Raven APUs get vayu-kms in its place; on the HP i915 loaded late. It now removeskmsonly where vayu-kms applies and puts it back elsewhere (sharedhw_is_raven, also used by splash/apply.sh).- Added
inetutils(tmux-resurrect callshostname: âhostname: command not foundâ on every autosave) andrtkit(PipeWireâs âRTKit error: ServiceUnknownâ) to pacman.txt. The installer alsoreset-faileds the masked NvPCR units sosystemctl --failedis clean after install. - No more
/home/yashor uid 1000: waybar, sway (output * bg ~/...), swaylock, tmux-main.service (%h), .zshrc and music-search.py use the home directory. rmpc gets MPDâs socket fromMPD_HOST(newzsh/.profile, sourced by greetd, with fallbacks in rmpc-toggle.sh and music-search.py), since config.ron canât expand variables. nwg-bar canât expand~or$HOMEin icon paths (tested: broken icons), so the newnwg-bar-launchwrites the template with the home directory filled in under $XDG_RUNTIME_DIR. The rmpc/mpv popups are sized inpptinstead of pixels for 1920x1080, and the greeter title is the hostname instead of âAceâ. - docs/docker-user-rules.service ran
/home/yash/dotfiles/...; it now has an@DOTFILES@placeholder that DOCKER_SECURITY.mdâs install command fills in with the checkoutâs path. The greeter README showed the old fixed âAceâ title; itâs the hostname. - Verified on the HP: mpd and ydotoold active, no failed system or user
units, vulkaninfo shows the HD 620 on ANV, iHD VA-API present,
kmsin HOOKS, lid and AC detection readLIDandAC, rmpc connects through MPD_HOST, the rmpc popup opens at 1142x613, keybinding check OK,vayu-verify92 passed / 0 failed, system/ and splash/ match the repo.
v1.18.0 â 2026-10-04
2026-10-04 (pre-release check for v1.18.0 Stable)
- Everything since v1.17.0 was tested live: vayu-elevate on this laptop with real approvals (approve, decline, deny with Esc, auto-close after a run, failure staying open; its own updates installed through it), the site on GitHub Pages, and a full fresh-container install of the Nightly channel (entry below) with 37/37 checks on the result.
- This machine before tagging:
vayu-verify92 passed / 0 failed (after marking gtk4, libadwaita and adwaita-icon-theme explicitly installed, via vayu-elevate, since theyâre now listed inpackages/pacman.txt);ai/,splash/andsystem/apply.sh --checkall match the repo; keybinding check OK; no failed system or user units.
2026-10-04 (fresh install verified: AI tools, shutdown fixes, docs)
- Ran the one-line Nightly install in a clean
archlinux:latestcontainer as a sudo user (su -l, installer pinned to the pushed commit; 47 min, 1495 s of it AUR builds). Every step passed except the three that need a running systemd (services, user services, dconf; they passed on the laptop) and the boot splash, fixed in the entry above and then re-run there cleanly (splash/apply.sh --check: matches the repo; only warning: no/etc/kernel/cmdline, the container has no UKI). Two installer bugs found and fixed on the way:$USERunset underset -u, and the splash daemon-reload. - 37 checks on the result, all passing: vayu-elevate installed root-owned
(CLI, window, runner, config/data dirs,
ai/apply.sh --checkclean); its packages present (gtk4, libadwaita, adwaita-icon-theme, python-gobject, libnotify); GTK4 + libadwaita load; schema, bad-request rejection (exit 2), MCP handshake, and a clean error with no desktop; the install dir isnât writable by the user. Stowed wiring: the global~/.claude/CLAUDE.mdnote, the sway float rule, keybinding search listing the windowâs keys and the SysRq keys, the vayu-verify section, the update notice,ai/not stowed. System:kernel.sysrq = 176,RebootWatchdogSec=0, the shutdown blanking hook and the hold unit. Docs and screenshots in the checkout.
2026-10-04 (splash/apply.sh: a failed daemon-reload no longer skips the rest)
- In the fresh-container install,
splash/apply.shstopped atsystemctl daemon-reload(no running systemd), so the shutdown blanking hook, the initramfs hook order and the kernel options were never installed. Likesystem/apply.shandgreeter/apply.sh, the live reload now only warns: the files are what count, and they apply at the next boot. A real install always has systemd, but a chroot install (from the Arch ISO) doesnât either.
2026-10-04 (install.sh: donât depend on $USER being set)
- A fresh-container run of the one-line Nightly install stopped in
preflight:
USER: unbound variable. Login sets USER, butdocker exec,su -candsystemd-rundonât, and install.sh runs withset -u. It now falls back toid -unand exports it for the apply scripts.
2026-10-04 (fresh installs get the AI toolsâ packages explicitly)
packages/pacman.txt:gtk4,libadwaita,adwaita-icon-theme. vayu-elevateâs window needs them; until now they only arrived as dependencies of zenity and pavucontrol (andai/apply.shwould have installed them), which breaks silently if those ever leave the list.
2026-10-04 (site: one width everywhere, more room for content)
- The site used four widths: navbar 1180px, hero 1280px, homepage
sections 1180px, docs layout 1280px, with the docs article ~900px on a
1920px screen (so wide tables wrapped). Now one
--site-width(1480px,assets/style.css) sets them all, each container adding its own padding on top so the visible edges match: navbar pill, hero card, feature and docs grids, docs sidebar and article all span 203-1683px at 1900px wide. - The docs article is now ~1190px; prose lines are capped at 100ch (was 78ch) so text stays readable.
- Fixed along the way: the docs content area is a
<main>, so the homepageâsmain { padding: 0 2.5rem }also applied there and pushed the article 40px in from the navbarâs edge. - Measured in Chrome on a local build (element edges via getBoundingClientRect); neither Troubleshooting table scrolls any more.
2026-10-04 (site: real tables, clickable tool rows, a structured Tools page)
- Tables on every docs page were bare columns (no table styling existed). Now a framed grid in the Mocha palette: header row, row dividers, rounded border, label-style first column on two-column tables, long inline commands wrap inside their cell, and a too-wide table scrolls by itself on a phone instead of the page.
- Table rows with exactly one link to a section on the same page become
clickable as a whole (
assets/docs.js): click, or Tab to the row and Enter, and the page smooth-scrolls to the section and the heading glows briefly; an arrow shows on hover. Selecting text in a row doesnât navigate. Rows without such a link (Troubleshootingâs) are unchanged. - The sidebar lists subsections (h3) nested under their section on short pages (8 sections or fewer: Tools, Keybindings); Architecture and the Changelog stay sections-only.
- Tools page restructured: the overview table, then one section per tool
(
vayu-elevate) with a lead, an âAt a glanceâ table (command, MCP, runs as, expiry, logs, install paths, source) and subsections: What you see, Keys, Whatâs recorded, For agents (sending a request, reading the result, over MCP), Letting agents know, Security. Status, state and exit codes are tables instead of run-on sentences. - Checked in Chrome on a local build: clicking the rowâs text lands the heading under the navbar with the glow and the sidebar entry active; Enter on the focused row navigates; Troubleshootingâs tables wrap.
v1.18.0-nightly â 2026-10-04
2026-10-04 (site: a Tools section, with vayu-elevate screenshots)
- The navbarâs âAI toolsâ is now Tools (every page), leading to the
Tools page (
docs/AI-TOOLS.md, same URL): the tools the desktop provides, starting with an AI tools overview table, then vayu-elevate in full. The homepage card says Tools too. - Two screenshots of the approval window over the desktop, cropped to the
window so it reads at column width: a three-command request with the
password typed (
assets/elevate.jpg), and the same window after the run (assets/elevate-done.jpg; captioned as example output, since the run shown used a stand-in runner, not a real pacman). Captured from the repo copy ofdialog.py(identical to the installed one) on a scratch workspace with notifications in DND. - Docs pages style images: fit the column, rounded, framed like the
homepage slides (
assets/docs.css).
2026-10-04 (vayu-elevate: the window closes after a run)
- After approving, the window stayed open and neither Enter nor Esc closed it. Two causes: by design it stayed open to show results, and disabling the password field while it had focus left GTKâs focus broken (âGtkText - did not receive a focus-out eventâ), so the keys never reached the windowâs handler.
- Now: after a fully successful run it shows the ticks and closes itself after 1.5 s; after a failure it stays open with the error, and Enter, Esc or Close closes it. The key handler runs in the capture phase (sees keys before any widget), and focus leaves the password field before it is disabled.
- Tested with a fake sudo/runner (success closes itself; failure stays,
then Enter and Esc each close it) and live:
id -unreturnedrootand the window closed on its own. The fix itself was installed through vayu-elevate (oneinstall -o rootcommand, approved in the window).
2026-10-04 (vayu-elevate: AI agents ask for root, you approve once)
- New
ai/(not stowed;ai/apply.shinstalls it root-owned, wired intoinstall.sh,vayu-verifyand the update notices): tools the desktop gives AI agents. The first isvayu-elevate. Instead of an agent asking you to paste sudo commands, it sends them (JSON:requester,reason,commands: [{cmd, why}]) and blocks. A GTK4/libadwaita window in Catppuccin Mocha (floating, glass,sticky) shows each command under its reason with a checkbox; you untick what you donât want, type your password once and press Run. The ticked ones run as root (bash -c, from/, no stdin, 30 min each), stopping at the first failure; the agent gets JSON back with each commandâs decision, state, exit code and output. Exit codes 0/1/3/4/2 (all ok / one failed / nothing ran / some declined / error). Expires after 10 min; 3 wrong passwords or Esc deny. vayu-elevate --mcpserves the same as an MCP tool (request_root_commands) for agents without a shell. Claude Code is told about the tool in the global~/.claude/CLAUDE.md.- Hardening: the window and runner are root-owned; the window starts as a
transient user service with
env -i(not a child of the agent), non-dumpable, Python-I, root-owned XDG dirs so user GTK CSS canât hide a row; requests with control or invisible characters (bidi overrides, zero-width) are refused; the password goes tosudo -S -kthrough a pipe, never to disk, and-kleaves no cached credentials. Every command run as root is logged to/var/log/vayu-elevate.log; every request to~/.local/state/vayu-elevate/requests.jsonl. Threat model (a consent gate, not a sandbox against malicious same-user code):ai/README.md. - Docs: new AI tools page (
docs/AI-TOOLS.md: user guide, agent protocol, MCP setup, security), three Troubleshooting entries, README. The windowâs keys (Esc, Enter, Space) are in the keybinding search (source âVayuâ, scanned from/usr/local/lib/vayu-elevate). - Verified live: a three-command request with the third unticked returned
exit 4;
idran as uid 0,ls -ld /rootread the root-only directory, the declinedechodidnât run;sudo -nstill asks for a password afterwards; the request directory was cleaned up. Denial with Esc, bad requests (nowhy, a U+202E override, bad JSON) and the MCP handshake were tested too.
2026-10-04 (site and README: new screenshots)
- Replaced the landing pageâs five screenshots (from early September,
before the login, splash and logo work) and added a sixth, the boot
splash. Captured with
grimat 1920x1080 on a scratch workspace with notifications in DND, scaled to 1600x900 withvips(Q86, metadata stripped; 1.5 MB for all six): the tiled desktop (Neovim onsplash/make-frames.py, rmpc on a paused track with its art, btopâs CPU/memory/disk panels), the launcher, keybinding search (âwindowâ), emoji picker (âwindâ) and power menu. The splash slide issplash/theme/logo.pngcentred on black, i.e. the frame Plymouth holds. - Kept out of the shots: browser and tmux contents, notifications, and btopâs network panel (it shows the LAN IP). rmpc played silently with MPDâs output disabled, then was stopped and the output re-enabled.
- Gotcha for next time:
swaymsg killon a kitty running a program opens kittyâs âclose this window?â prompt instead of closing it; close scratch windows by killing their PIDs fromswaymsg -t get_tree. - The carousel styles a sixth offset; READMEâs placeholder Screenshots section now shows the images with their keys.
2026-10-04 (site: the logo in the navbar)
- The navbar brand on every page (homepage and the docs layout) is the
splash logo, still and 1.9rem tall, instead of the serif âVayuâ text,
so the name appears in one style across the site. Thicker strokes than
splash/logo.svgso the hairlines hold up at that size; the link is labelled âVayu homeâ for screen readers. Checked on a local build (homepage and Troubleshooting page). - First push showed an empty brand in a browser that still had the
previous
style.csscached (Pages sendsmax-age=600): the SVG had no size without the new rule and collapsed to 0x0. It now carrieswidth="76" height="30"itself; checked live with the stale stylesheet still loaded (76x30, logo visible).
2026-10-04 (site: the splash logo and a shlok in the hero)
- The homepage hero shows the boot splash logo instead of the plain
âVayuâ heading, animated in CSS with the same timings and easings as
splash/make-frames.py: the letters write themselves, the wind line sweeps out and curls, then a light runs along it every 2.4 s. The Y is two paths (arms, then stem), as in the Plymouth frames. Reduced-motion visitors get the finished logo; screen readers still read âVayuâ. - Under it, the opening salutation to Vayu from the Taittiriya Upanishad (1.1.1): à€šà€źà€žà„à€€à„ à€”à€Ÿà€Żà„ à„€ à€€à„à€”à€źà„à€” à€Șà„à€°à€€à„à€Żà€à„à€·à€ à€Źà„à€°à€čà„à€źà€Ÿà€žà€ż à„„ (âSalutations to you, Vayu. You alone are the visible Brahman.â). Devanagari only; the translation and source are in its hover title. System Devanagari fonts, no web font.
- Checked in Chrome against a local Jekyll build: mid-draw, finished, and the light on the wind line.
v1.17.0 â 2026-10-04
2026-10-04 (pre-release check for v1.17.0 Stable)
- Settled the four âPendingâ items since v1.14.0, on this machine after
todayâs reboots: login goes through greetd (session
Service=greetd) with the patched/usr/local/bin/tuigreet-ace; sddm is uninstalled and its unit gone; a cold boot shows no swayidle/swaylock-on-sleep failures injournalctl --user -b. - Machine state:
vayu-verify91 passed / 0 failed; no failed system or user units; boot errors are only the known benign ones (TDX not supported, wpa_supplicant multicast notice, gkr-pam control file, kdeconnect duplicate D-Bus name). - Not tested here: a fresh install of the splash on hardware other than
this Raven APU laptop (no
vayu-kmsthere; Plymouth uses simpledrm until the GPU driver loads). Covered indocs/TROUBLESHOOTING.md.
v1.17.0-nightly â 2026-10-04
2026-10-04 (the docs site builds from development)
- GitHub Pages now builds from
developmentinstead ofmain. Docs written during a nightly reached the site only at the next Stable release, and the Changelog pageâs Stable/Nightly toggle had nothing to hide onmain. Install commands are unchanged. Why and what it means:docs/ARCHITECTURE.md(âA public docs siteâŠâ) anddocs/VERSIONING.md.
2026-10-04 (Troubleshooting page; SysRq as the way out of a hung shutdown)
- New
docs/TROUBLESHOOTING.md, on the site as Troubleshooting (top nav on every page, a card on the homepage, linked from the READMEâs install-failure notes andsplash/README.md). One section per symptom, each with why it happens and commands to run as-is: a shutdown stuck on the logo, a black screen that doesnât power off, getting the 10-min watchdog reset back, which text after the logo means which part is missing, the ~15 s firmware logo, a boot stuck on the Vayu logo, text instead of the logo, login and sway failures, the still-open lock-screen black-out after a lid close, the 30-min lid power-off, install failures, and what to include in a bug report. - New
system/etc/sysctl.d/99-sysrq.conf:kernel.sysrq = 176(sync, remount read-only, reboot/power-off; systemdâs default 16 allows only sync). It replaces the reset thatRebootWatchdogSec=0(below) took away: if the last stage of a shutdown hangs, hold Alt+PrtSc and press S U B (or O) instead of holding the power button. Nothing that dumps memory or kills processes is enabled. Whatâs still automatic without the watchdog: each service gets 90 s to stop, and the reboot/poweroff targets force the action after 30 min (JobTimeoutAction=reboot-force/poweroff-force). Only a hang in the kernelâs final stage has no timer now. - The SysRq combos are in the keybinding search:
keybind-search.pyalso reads# keybind:lines from/etc/sysctl.d(new source âKernelâ), andforceis now an allowed first verb. Row added toCLAUDE.md. - Verified after
system/apply.sh:sysctl kernel.sysrq= 176,vayu-verify91 passed / 0 failed, and the keybinding search lists both Kernel combos. The keys themselves werenât pressed: B and O reboot or power off on the spot.
2026-10-04 (no watchdog line after the shutdown logo)
- Reboot still showed a line or two of text (with a kernel timestamp)
between the Vayu logo and power-off, although the blanking hook was
installed. Every recent shutdownâs kernel log has one crit-level line,
watchdog: watchdog0: watchdog did not stop!, printed when PID 1 closes the hardware watchdog it armed for shutdown (RebootWatchdogSec, default 10 min) on handing over tosystemd-shutdown. Crit passesloglevel=3, and it lands before the blanking hook runs. - New
system/etc/systemd/system.conf.d/no-reboot-watchdog.conf:RebootWatchdogSec=0, so the watchdog isnât armed for shutdown. A hung shutdown is no longer reset after 10 min (see the SysRq entry above, and âShutdown: I want the automatic 10-minute reset backâ indocs/TROUBLESHOOTING.mdto undo it). - Confirmed by a reboot: logo, then black, then the firmware logo, with no text in between.
2026-10-04 (where amdgpuâs load time goes)
- Booted once with
initcall_debugto find out why the Acer logo stays up ~16 s. amdgpuâs own init (amdgpu_init, firmware included) takes 0.3 s; the 6.1 s before it is the kernel loading the module file (decompression is 0.08 s, the CPU was at full clock, warm and cold boots are the same). The earlier ~3 s estimate was low. Nothing to tune in the hook or driver options; building amdgpu into a custom kernel is the only fix, not worth maintaining for now.splash/README.mdupdated with the numbers;initcall_debugremoved again.
2026-10-04 (no text after the shutdown logo)
- Reboot showed the logo, then a screen of white text, then black, then the
Acer logo. The journal and Plymouthâs log both end at the switch into the
shutdown ramfs, so the likely cause came from systemdâs source: right after
that switch
systemd-shutdowncallsmake_console_stdio(), which puts/dev/consoleback in text mode (KDSETMODE KD_TEXT) and only erases from the cursor line down. The console was redrawn over the logo with whatever was still on it. - New
splash/vayu-splash-blank.shutdown, installed bysplash/apply.shto/usr/lib/systemd/system-shutdown/(the only placesystemd-shutdownruns hooks from, just before that switch): clears the console, hides the cursor and sets all 16 console colours to black, so anything drawn after the logo, including the kernelâs âreboot: Restarting systemâ, is black on black. Expected flow on reboot: logo, black, Acer, logo, login. Power-off: logo, off. - Not fully explained: on SIGTERM Plymouth hands its DRM fd (still DRM
master, frame still scanned out) to
plymouthd-fd-escrow, which is in the shutdown ramfs at the pathplymouthdexecs, so the text console shouldnât reach the screen at all. Something releases it anyway. The blanking doesnât depend on what: black on black stays black. Confirmed by reboots: the screen of text is gone (the one line left was the watchdog, fixed in the entry above).
v1.16.0-nightly â 2026-10-04
2026-10-04 (splash on reboot/power-off, Mocha login border)
- Reboot and power-off now show the Vayu logo through to the end:
splash/vayu-splash-hold.service(installed and enabled bysplash/apply.sh) starts once Plymouthâs shutdown splash is up and holds the final reboot/power-off until the intro has played (2.6 s), even when everything else has already stopped. Skipped if Plymouth isnât running. - The text that flashed before the shutdown splash was tty1 showing
through between sway exiting and Plymouth starting, with old boot
messages still on it.
vt-palette.servicenow clears tty1 and hides its cursor before greetd starts. - Login box border was VGA red:
vt-palette.serviceran beforeplymouth-quit, and Plymouth restores the kernelâs default palette as it quits. It now runs afterplymouth-quit, soborder = "red"is Mochaâs red again.
2026-10-04 (faster, flicker-free boot: early amdgpu, splash ordering, sway log off tty1)
- Measured after the splash reboot: greeter at 14.3 s after kernel start.
amdgpu loaded from the root filesystem at 14.3 s and stalled nearly all
of userspace from 8.2 s (greeter PAM, Wi-Fi auth and power-profiles all
resumed in the same 40 ms). Loading
i915as a test takes ~1 s whether compressed or not, so amdgpu (3x the size) is ~3 s of in-kernel CPU however itâs loaded; the fix is to pay it early and alone. - New
vayu-kmsmkinitcpio hook (splash/initcpio/, installed bysplash/apply.shon Raven-family APUs only): loadsacpi_cpufreq, then amdgpu, as the initramfsâs first job, and packs only theraven*andpicasso*firmware (~400 KB) instead of thekmshookâs ~30 MB. Test image: 22 MB vs 16 MB today and 47 MB withkms. plymouthhook moved afterconsolefont: Plymouth puts the console in graphics mode, so setfont was skipped (âAll allocated virtual consoles are busyâ), and tuigreet drew first on the firmware framebuffer, then again at 240x67 after amdgpu, then again once the font landed: the login-screen flicker.- The â0âs between login and desktop were swayâs own log lines
(
00:00:00.123 [INFO] ...) on tty1: greetd gives the session the VT as stdout/stderr. The session command now starts withsystemd-cat -t sway, so they go to the journal (journalctl -t sway).
2026-10-04 (boot splash: the Vayu logo, animated, instead of boot text)
- New
splash/(not stowed): a Plymouth script theme that shows the Vayu logo between the firmware logo and the login screen. The logo (splash/logo.svg) is hairline spaced capitals â the A has no crossbar, so air passes through it â over a wind line that curls off at the end, in Mocha mauve to rosewater. Picked from several styles and motions previewed in the browser. - Motion: the letters write themselves one after another, the wind line
sweeps out and curls, then a soft light keeps running along the wind
line until tuigreet starts.
splash/make-frames.pyrenders it into 116 transparent 25 fps PNGs (828 KB). It measures dash lengths itself rather than relying on SVGpathLength, and draws the Yâs stem as its own stroke, since cairo restarts the dash at every moveto (that left a stray dot). splash/apply.shinstallsplymouth, the theme and/etc/plymouth/plymouthd.conf(ShowDelay=0), puts theplymouthhook afterudev, appendsquiet splash loglevel=3 rd.udev.log_level=3 plymouth.use-simpledrmto/etc/kernel/cmdline, then runsmkinitcpio -P.plymouth.use-simpledrmis needed because thereâs nokmshook: without it Plymouth waits for amdgpu, which loads from the root filesystem, so the first seconds would be blank.- Wired in like
greeter/: aninstall.shstep, avayu-verifysection (apply.sh --check), an update notice inupdate-apply.sh, excluded indotfiles-stow-packages,plymouthadded topackages/pacman.txt. - Measured before this change: the 4.3 s that NetworkManager seemed to take
is amdgpu loading. hostnamed (
PrivateNetwork=yes) canât set up its namespace until the driver finishes, and NetworkManager waits on hostnamed. The time moved from âkernelâ to âuserspaceâ when thekmshook went; the total is the same. The splash now covers it.
2026-10-04 (boot: 5.8 s in the loader came from a 47 MB initramfs)
systemd-analyzeshowed 5.8 s of âloaderâ with the menu timeout already- The cause was the
kmshook in/etc/mkinitcpio.conf: for amdgpu it packs the driver plus firmware for every AMD GPU generation, so the initramfs was ~47 MB unpacked, and this Insyde firmware reads the UKI off the ESP slowly.
- The cause was the
system/apply.shnow removeskmsfromHOOKSand--splashfrom the mkinitcpio presets, then runsmkinitcpio -P. amdgpu loads from the root filesystem instead, and systemd-vconsole-setup reapplies the console font (the greeterâster-v24n-ace) when the new framebuffer console appears.apply.sh --check(and sovayu-verify) flags either if it comes back.
2026-10-04 (swappiness: a leftover sysctl file was overriding the repo)
- After the install,
vm.swappinesswas still 10./etc/sysctl.d/99-sysctl.conf(hand-made in June, owned by no package, the real origin of the old value) sorts after99-custom.conf, so it won.vayu-verifypassed anyway because it compared files, not the live values. - Wi-Fi regulatory country:
wireless-regdbalone left the kernel on the âworldâ domain (iw reg get: country 00, fewer 5 GHz channels, lower power).system/apply.shnow setsWIRELESS_REGDOM="IN"in/etc/conf.d/wireless-regdom, which its udev rule reads at boot. system/apply.shhas aretiredlist of leftovers that override the repo. Each is removed only while it holds exactly the known old content; otherwise it warns.vayu-verifynow checks every value insystem/etc/sysctl.d/against the livesysctl, so the next override like this shows up as a FAIL naming the key.
2026-10-04 (install.sh tested on a fresh Arch: remedies, diagnostics, fallbacks)
Ran the whole installer in a clean archlinux:latest container as a normal
sudo user: real pacman, real AUR builds, real /etc. The first run took 49
minutes. It turned up real bugs that this laptopâs install never could,
because here everything was already in place:
- swayfx canât be built from the AUR right now. Its PKGBUILD depends on
scenefx0.5, but scenefx 0.5 moved to [extra] as plainscenefx, with noprovides. So a new machine had no compositor and no session to log into. Newpackages/aur-patches/<pkg>.sed: sed edits applied to a packageâs PKGBUILD before building. The swayfx one swaps the dependency, the fix from the AUR comments, and becomes a no-op once upstream fixes the PKGBUILD. The patched package is installed withpacman --ask=4, so it can replace a conflicting package. - Fallback if thereâs still no
swayafter the AUR step: stockswayfrom [extra] (same config; swayfx-only lines just get flagged). The next run swaps swayfx back in. - The first wallpaper always âfailedâ on a new machine. The image was
saved, but
fetch_wallpaper.shexited 1 because the last command,notify-sendwith no desktop session, failed. It now ends withexit 0, and the installer judges the step by whethercurrent.jpgexists. - Login shell set to
/usr/sbin/zsh, fromcommand -vwith/usr/sbinahead of/usr/binin PATH. That path isnât in/etc/shells, so pam_shells-style checks refuse it. The installer now uses the path/etc/shellslists. - The installer wrote app data into its own repo. On a fresh home,
stow âfoldsâ: a directory that doesnât exist yet becomes one link into
the repo. So
~/.local/share(and with it nvimâs plugins),~/.config/systemd(systemctl --user enableâs.wantslinks),~/.claude,~/.tmux(TPM plugins) and the GTK dirs all pointed into~/dotfiles. The test run moved 52 files of nvim data back out ofkdeconnect/.local/share. Worse, the dirty tree made every later run skipgit pull, so the machine silently stopped updating. Now:STOW_REAL_DIRS(manifest.sh), copied from this machineâs layout, is created before stowing.- A folded link from an earlier run is replaced, and any app data
that got into the repo is moved back to
~. vayu-verifychecks each one.
- Pulls with local changes. nvim rewrites
lazy-lock.jsonon every plugin update, so the tree is rarely clean.git pull --ff-onlyitself refuses when a change would be overwritten, and only then is the pull skipped, with a warning. - A pulled install.sh is re-executed. bash keeps reading the file it started with, so a run that pulled a newer installer carried on with the old code.
- The summary could die halfway. It ran with errexit still on, so the first failing diagnostic ended the run before the footer and log path printed, which is exactly when you need them.
system/now goes before the AUR builds, so they get-j$(nproc)and!debugfrom the start. Live reloads insystem/apply.shandgreeter/apply.sh(sysctl --system,daemon-reloadâŠ) warn instead of failing the step, since the files are what count and they apply at the next boot anyway.- greeter/apply.sh installs
tuigreet-launchand the greetd config before the ~3 min tuigreet-ace build. If the build fails, the next boot still gets a login screen, with stock tuigreet.
Failure reporting, for a run that goes wrong on real hardware:
- Each failed step gets a
fix:line in the summary, fromremedy(): mirrors/keyring/clock for prerequisites,yay -S <pkg>plus the AUR page for a broken build, and so on. - A failed run appends a diagnostics section to the log: clock, disk, memory, network/DNS, the pacman lock, the end of pacman.log, this bootâs journal errors, and failed units.
- Preflight now stops early, with the exact fix, when the user canât
sudo (wheel and visudo steps) or thereâs under 8G free. It turns on NTP
if the clock isnât synced. If yay wonât build from source, the
prebuilt
yay-binis used. - When a step function returns its own failure, the message is now âthe
x lines above say what failedâ, not a misleading
line N: return. - README: what to have ready before running, and how to recover from a failure.
The container canât run systemd, so the services, user services, ufw and dconf steps canât pass there. All of them ran for real on this laptop earlier today.
2026-10-04 (system audit: swap tuning, package hygiene, caches, greeter PAM)
A full pass over the running system after the first real install.sh
run. Idle CPU, waybar (already signal-driven), power settings and the
portals were fine and left alone.
- Swap tuned for zram.
vm.swappiness=10is a disk-swap value. With zram as the main swap it kept zram nearly unused, so memory pressure dropped page cache instead. Nowswappiness=180,page-cluster=0and the watermark tweaks, per the ArchWiki zram page. zram moves from lz4 to zstd (better ratio), and its size from the default min(ram/2, 4G) to min(ram, 8G)./swapfilestays as overflow. The zram change takes effect at the next boot. - makepkg (
system/etc/makepkg.conf.d/vayu.conf): builds use-j$(nproc)instead of one core, and!debugstops the-debugsplit packages. Three were installed for nothing: swayfx-, bruno-bin- and mongosh-bin-debug. - Journal capped at 300M (
journald.conf.d/size.conf); it had grown to 635M.apply.shrestarts journald when that file changes. - Greeter PAM (
pam.d/greetd-greeter): greetd uses this for the tuigreet session when it exists. Itâs the same aspam.d/greetdwithoutpam_gnome_keyring, which had started a keyring daemon for thegreeteruser withHOME=/on every boot. - Packages:
- Added
pacman-contrib, forpaccache.timer: weekly, it keeps 3 versions in the pacman cache, which held 2636 packages (7.4G). - Added
wireless-regdb. Without it the kernel logged âregulatory.db failedâ and wifi stayed on the restrictive world domain. systemd-boot-update.serviceis enabled. The ESP still had systemd-boot 260 under systemd 262.
- Added
- Listed packages marked explicit.
--neededskipped 13 listed packages already present as dependencies (mpv, curl, jq, pythonâŠ), so they stayed âdependencyâ installs, and an orphan cleanup would have removed them.install.shnow runspacman -D --asexpliciton those, andvayu-verifyflags any that are left. - yay:
install.shsetscleanAfter.~/.cache/yayheld every version ever built (14G, cleared). - zsh:
ZSH_DISABLE_COMPFIX=trueskips compaudit (~10-25 ms per shell).
2026-10-04 (tmux.service -> tmux-main.service: the link that kept vanishing)
- Why
~/.config/systemd/user/tmux.servicekept disappearing (itâs in this changelog five times as ânever linkedâ/âmissingâ): with@continuum-boot 'off', tmux-continuum runssystemctl --user disable tmux.serviceevery time a tmux server starts. For a unit file that is itself a symlink to outside the unit path (a stow link),disabledeletes the symlink. So every login undid every restow. Caught by the first realinstall.shrun:vayu-verifypassed at the end of the install, then failed after the reboot. The journal showed asystemctlreload from the first kitty/tmux window at the exact second the directory changed. Reproduced by hand. - The unit is now
tmux-main.service, a name continuum doesnât manage. Itsdisablecall now just reports âUnit tmux.service does not existâ. Checked on an isolated tmux server: nothing shows in tmux, and the link stays. Still not enabled; nothing referenced the old name. - After the rename,
vayu-verifypasses all 68 checks on this machine.
2026-10-04 (install.sh: full log, per-step failure handling, reproducible)
- Log file. Every run writes
~/.local/state/vayu/install-logs/install-<date>.log(latest.loglinks the newest). It holds the whole output, without colour codes or progress-bar redraws, plus aset -xtrace with file:line for every command. The ERR trap prints the failing command, line and exit code. It starts with system info (kernel, CPU, RAM, free space, args), and each step is timed. - Failures. Each step runs in a subshell with
errexit, so the first failing command ends that step only. Only preflight, prerequisites, repo, yay and stow stop the run. The rest record the failure and carry on. Package names are checked first (pacman -Si, one AUR RPC call), and a failed batch is retried one package at a time, so one renamed package gets named instead of failing all 114. The summary lists every step, every failed item and the log path, and the exit code reflects it. Previously one bad package name aborted the whole install underset -e. - Bugs fixed on the way:
- The stow backup parsed stow 2.4âs âexisting target is not owned by
stow: Xâ as the file
is, and skipped symlinks entirely. Any foreign symlink in the way (checked in a sandbox) aborted the whole install. Both message forms now move the file aside, into a per-run~/.dotfiles-backup/<date>/. pacman -Sy git stow base-develwas a partial upgrade. Now itâs the keyring first (old install images), then a full-Su.sh -c "$(curl ...)"for oh-my-zsh and Homebrew ran an empty script and âsucceededâ if the download failed. Both now download to a file first.brew install pnpmwould have shadowed/usr/bin/pnpm, which here is corepackâs shim. Now it runscorepack enable, and brew installs onlygh, as on this machine.- The tmux plugins needed a manual
prefix + I. Now theyâre cloned from tmux.confâs@pluginlines, not via TPMâsinstall_plugins, which asks a tmux server for its path and can get/when none is running. Neovim plugins are restored headless fromlazy-lock.json. - No first wallpaper (the timerâs first run is at midnight; sway and
swaylock need
current.jpg).
- The stow backup parsed stow 2.4âs âexisting target is not owned by
stow: Xâ as the file
- Hand-over. The copy
curlfetches is always mainâs. After cloning, it re-runs the checked-outinstall.sh, so Nightly installs run Nightlyâs installer. The log, step results and timing carry across. Asudo -vkeepalive stops long AUR builds stalling on a password prompt. Stale pacman locks are cleared, and the channel argument is checked before anything runs. - Also runs
system/apply.sh(previous entries) and the manifestâs units, groups and firewall. Ends withvayu-verify.update-apply.shnow notifies you to runinstall.shwhen an update touchespackages/orsystem/. - Sandbox-tested: a throwaway
$HOMEwith a clone of the repo; sudo, pacman, yay, systemctl, nvim and dconf stubbed; stow, git, oh-my-zsh, the AUR RPC and the wallpaper fetch real.- Planted failures (a name gone from the repos, a package failing to install, a real file and a foreign symlink in stowâs way) were each named in the summary, and the rest of the run completed.
- The curl-style
bash <(...)hand-over worked. - That test caught three more bugs, fixed above:
grepwith no matches underpipefailfailed the stow step on every re-run;- an exported
ZSHbroke the oh-my-zsh installer; - a missing
model namein cpuinfo could fail preflight.
- A second run right after was clean.
Not covered: real root. The first real check is a blank machine or VM.
- Docs: README Quick Start,
docs/ARCHITECTURE.mdâs step-by-step (rewritten; it still described iwd, a ZedMono download andbrew install pnpm), plus avayu-verifysection.
2026-10-04 (vayu-verify: does this machine match the repo?)
- New
packages/manifest.sh, sourced by bothinstall.shandvayu-verify, so what gets set up and what gets checked canât drift apart. It holds the system and user units to enable/start, disable or mask, the userâs groups, ufw rules, Homebrew formulae, zsh plugin clones and dconf keys. - Compared with this machine, the old installer was missing: user units
mpd.serviceandydotool.service; systemfstrim.timerandsystemd-timesyncd(archinstall defaults, now explicit); theinputgroup, needed for ydotooldâs/dev/uinput(ydotoolâs own udev rule); andufw enable. ufw.service alone doesnât turn the firewall on, so a fresh install had the KDE Connect rules but no active firewall. scripts/.local/bin/vayu-verifychecks, read-only and without sudo:- packages installed;
- a dry-run restow is clean;
system/apply.sh --check;- greetd and tuigreet-ace;
- every unit in the manifest is enabled, active, disabled or masked as listed;
- ufw is on with its rules;
- groups, login shell, oh-my-zsh and plugins, TPM, the pnpm shim, brew formulae, the Claude Code hook, dconf keys and a wallpaper.
It prints failures only (
-vfor everything), exit 1 if any.- First run here found real drift:
zenityandterraformnot installed;~/.config/systemd/user/tmux.servicenever linked (a restow since the 2026-10-01 stow change didnât happen here);docker.socketfailed. The kernel was upgraded to 7.2.8 at 08:28 while 7.2.7 is still running, so its modules (nf_nat) are gone and dockerd canât set up iptables. A reboot fixes it.
2026-10-04 (package lists: missing runtime deps, stale scenefx0.4)
- Audited the lists against what the configs run. Every command in the
repoâs scripts and configs was mapped to its owning package, and each
was checked against the dependency closure of
pacman.txt+aur.txt.pactreeisnât installed (pacman-contrib), so the closure came frompacman -Qi. - Missing outright:
mpv.music-search.pyâs video mode runs it, but it was only installed as an orphaned dependency (Required By: none), sopacman -Rns $(pacman -Qdtq)would have removed it. - Present only as another packageâs dependency, now listed directly:
playerctl(sway media keys; via waybar),wtype(via wofi-emoji),upower,ffmpeg,curl,dconf,file,xdg-utils,python-gobject+gtk3(bar-events.py,notification-history.py). - Installed by hand here and now listed:
7zip,dart. Not listed:sddm,sddm-sugar-darkandqt6-virtualkeyboard, left from the SDDM setup that greetd replaced, andwlogout, which nwg-bar replaced (nothing references it). scenefx0.4dropped fromaur.txt: swayfx 0.6 depends onscenefx0.5, and 0.4 (pluswlroots0.19, which only it needs) was a leftover orphan. A fresh install would have built both from source for nothing.- Not installed on this machine though listed (on purpose, see their
entries):
zenity, which the waybar brightness slider needs, so that slider is broken here right now; andterraform. Re-runninginstall.shinstalls them. - Every name still resolves:
pacman -Sifor each official one, the AUR RPC for the AUR ones.
2026-10-04 (system/: root-owned files the installer never set up)
- An audit of
/etcagainst the repo (unowned files, andpacman -Qkkfor modified package configs) found settings no installer step created. A fresh install would silently have lacked them:- the lid-close auto-poweroff hook and its config: kept in
systemd/as âreference copiesâ, installed by hand, never byinstall.sh; vm.swappiness=10(/etc/sysctl.d/99-custom.conf);- the zram config (
/etc/systemd/zram-generator.conf, lz4); - pacmanâs
Color.
- the lid-close auto-poweroff hook and its config: kept in
- New non-stow
system/dir mirrors/:system/etc/Xinstalls to/etc/X.system/apply.shinstalls what differs (755 if executable in the repo, else 644), reloads what changed (sysctl --system,daemon-reload, logind HUP) and enablesColor.--checklists the differences without sudo. The lid hook and its config moved there (git mv). So did the logind lid drop-in and greetdâs PAM file, whichinstall.shused to write from heredocs. systemd/.stow-local-ignoreexisted only to keep the lid files out of~, so itâs gone.dotfiles-stow-packagesskipssystem/.- On this machine, logindâs lid setting lives in
logind.confitself. The drop-in sets the same values, so applying it changes nothing.99-sysctl.confduplicates99-custom.confand isnât carried over. - Verified:
system/apply.sh --checkflags exactly the 3 expected differences here: the lid files (header comments updated) and the logind drop-in that isnât installed yet.
2026-10-04 (wallpaper waits for the network at boot)
- 7 of 52 logged wallpaper runs fell back instead of fetching. Each was
the timerâs
Persistent=truecatch-up at boot, run by the lingering user manager before Wi-Fi was up. The log shows âconnectivity: noneâ, or âunknownâ followed by curlâs âCould not resolve hostâ (today, 07:38). With a daily timer, that means no new wallpaper for the day. As with update-check,After=network-online.targetinwallpaper.servicedoesnât help in the user manager. fetch_wallpaper.shnow waits up to 60 s withnm-onlinefirst. When already online it returns in ~20 ms, so the waybar click doesnât get slower.- Verified: online, a normal run applied a new image. With
nm-onlinestubbed to fail andnmclireporting ânoneâ, it logged the wait, then fell back to the last good wallpaper as before.
2026-10-04 (wallpaper never repeats an image)
Archive/was about half duplicates: 61 files, 32 distinct images, one saved 12 times. Random index+market picks kept landing on the same photo, since markets share photos and an index points at a different day every day. The lock screen showscurrent.jpg, so it repeated them too.fetch_wallpaper.shnow keeps a history in~/Pictures/Wallpapers/.seen(TSV): Bingâs image ID (GrizzlySwimfromOHR.GrizzlySwim_EN-US..., the same in every market) and the md5 of every wallpaper applied. It isnât pruned with the archive. Itâs read into two bash hash tables once per run. It grows a line a day, so SQLite would add a dependency and no speed. A first run seeds it with the md5s already on disk. It skips any image whose ID or md5 is in the history. If everything Bing offers has been used, it keeps the current wallpaper and says âNo new wallpaper available yetâ. Network failures still fall back to the last good wallpaper.- Speed: one lookup takes ~1.5 s, so going through candidates one at a
time could take ~90 s once most were used. All 160 index+market
lookups now go out as one parallel curl (
-Z, measured 1.4 s), and only the chosen image is downloaded. A run takes ~3.5 s. Those 160 lookups cover only ~27 distinct photos, a few new each day, so repeated clicks will eventually hit âNo new wallpaperâ. - Archive files are now named after the image ID
(
wallpaper-20261004-BearsEars.jpg). - On this machine: deleted the 29 duplicates (kept the oldest copy of
each md5) and seeded
.seenwith the 32 remaining images. The 4 of them still among Bingâs current 27 got their IDs by downloading those 27 and matching md5s. - Verified in a throwaway home with a copy of the archive: 5 runs gave 5
new images, and AmberHall/GrizzlySwim were skipped as md5 matches with
old files. A one-market build showed âNo new wallpaper available
yetâ. With curl stubbed to fail, it fell back after 3 lookup rounds.
Then a live
systemctl --user start wallpaper.serviceapplied BearsEars in 3.5 s.
v1.15.1-nightly â 2026-10-01
2026-10-01 (update check runs with sway and waits for the network)
- The update check failed 32 of its last 37 runs (âCould not resolve
hostname github.comâ). The user manager lingers, so its
default.targetis reached at boot: the check ran before login, with no sway for its notification or nwg-bar prompt, and ~4 s before Wi-Fi connected (every recent boot).After=network-online.targetdidnât help: in the user manager that target doesnât track the real network. update-check.serviceis nowWantedBy=sway-session.target,PartOf=graphical-session.targetandType=exec, so it doesnât hold up startup.update-check.shwaits for NetworkManager (nm-online -t 60) and retries the fetch 3 times, 10 s apart.install.shenables it without--now.- Found on the way:
systemctl --user reenable(ordisable) on a stow-linked unit also deletes the unitâs symlink, since it points outside systemdâs search path. Restored with stow. No repo script disables user units. - Verified: started through systemd, it fetched and logged âup to dateâ in 3.8 s. With nm-online and git stubbed to fail, it tries 3 times (20 s), logs it and exits 0.
2026-10-01 (one stow package list; update notices login-screen changes)
update-apply.sh(âUpdate Nowâ) had its own package list, excluding only packages/, docs/ and hidden dirs. The next update would have stowedgreeter/(~/apply.sh,~/config.toml,~/vtrgb,~/README.md) and the docs siteâsassets/+_layouts/into ~. Not triggered yet: the update check never got that far (next entry).scripts/.local/bin/dotfiles-stow-packagesis now the one list.install.shandupdate-apply.shboth run it from the repo, andupdate-apply.shstops if it lists nothing.greeter/needs sudo, so a restow canât install it.update-apply.shnow notifies âLogin screen changed: run greeter/apply.shâ when a pull touched it.- Verified in a throwaway home, updating a v1.14.0 clone to HEAD with notify-send/swaymsg/makoctl/systemctl stubbed: no greeter/assets/_layouts links, the login-screen notice on the first run, none on a re-run.
2026-10-01 (docs: sway-session.target in ARCHITECTURE)
docs/ARCHITECTURE.mdâs exec-order section still said swayâs first exec isdbus-update-activation-environment. It now namessway-session.shand explains the target: whyPartOf=did nothing before (kdeconnect dumped core every 2 s between logout and login, seen in this bootâs journal), and that new Wayland-client units should bePartOf=graphical-session.target.
v1.15.0-nightly â 2026-10-01
2026-10-01 (stow: tmux.service linked, pycache ignored)
- A restow of
scriptswould have linked__pycache__(gitignored, but stow doesnât read .gitignore) into~/.local/bin.scripts/.stow-local-ignorenow skips it, alongside stowâs defaults, the same waysystemd/does. ~/.config/systemd/user/tmux.servicewas still missing (noted in earlier entries). Itâs linked now, and still not enabled.- Verified: a dry-run restow of all 20 packages has nothing left to link.
2026-10-01 (sway-session.target: user services stop with sway)
- Health check found
xdg-desktop-portal-luminousfailed. It had died when sway exited on a logout, and systemd kept restarting it with no compositor (âNoCompositorâ) until it hit the start limit. - Root cause:
graphical-session.targetwas never active, soPartOf=graphical-session.targetdid nothing. That covers the idle units, bar-events, kdeconnect, phone-events and the portals. The user manager lingers across logout, andWAYLAND_DISPLAYstayed in its environment, so they all outlived sway. sway-session.sh(run from swayâs config, replacing the baredbus-update-activation-environment --all) imports the environment, starts the newsway-session.target(binds graphical-session.target), waits for swayâs shutdown IPC event, then stops the target and unsets the display variables. Nothing is enabled under graphical-session.target, so starting it doesnât start anything twice.- Verified live: both targets active, 0 failed user units. The shutdown watcher returns within ~25 ms of a headless sway exiting. The full logout path is proven on the next logout.
2026-10-01 (login screen: no temp files, re-runnable apply.sh)
tuigreet-launchwrote its filled-in config to a temp file and deleted it after tuigreet exited. If the greeter was killed first (greetd at shutdown, or a killed test run), the file stayed: 18 had piled up in /tmp. Now the config goes to tuigreet on fd 3 and the launcherexecs it, so thereâs no temp file and no wrapper process. A missing config falls back to tuigreetâs own default.apply.shis cheap to re-run:mkinitcpio -Ponly when the font orFONT=changes, and tuigreet-ace is rebuilt only when the version or the patch changes (stamp in/usr/local/share/tuigreet-ace/build-id). The cargo build now uses its own--target-dirinside the temp dir.- Removing the icons from the font needs one
apply.shrun to reach the installed font. Verified with a dry run (sudo stubbed): download, checksum, patch, build, installs.
2026-10-01 (login screen: centred, âAceâ, 12-hour date, no icons)
- On the real screen the one-cell icons read as letters: the clock as
âGâ, the calendar as ââĄâ, the bolt as âSâ. Dropped them. The title is now
Ace · NN%(· chargingadded while charging; justAcewith no battery), and the clock reads01:16 AM · Thu 01 Oct 2026, 12-hour like waybar.build-console-font.pyonly rounds the box corners now, so ΠΠΠΰ â â ⥠⧠are back. The installed font keeps the unused icons untilapply.shruns again. - The clock and box were half a cell apart horizontally, because the old
clock text was 23 cells wide (odd) over a 44-cell box. The new text is 28
cells. They also sat in the top third of the screen. tuigreet puts the
clock on row
window_padding, then draws the box as if that area didnât exist, which lifts the box by the same padding. So the clock and box couldnât both be centred. - Fix:
greeter/tuigreet-center-box.patchmakesget_rect_boundsreturn absolute coordinates.apply.shbuilds tuigreet 0.11.1 with it (tarball sha256-pinned to Archâs) as/usr/local/bin/tuigreet-ace, out of pacmanâs way.window_padding = 16: box rows 18-26 of 45 (dead centre), clock on row 16. If the binary is missing,tuigreet-launchruns stock tuigreet with padding 0 (box still centred, clock on the top row). - Verified with
--mockat 160x45 (both binaries). Pending: real boot.
2026-10-01 (login screen: Mocha Rosé tuigreet theme)
- The mockup sessions only produced an HTML preview, so the login
screen hadnât changed.
greeter/now installs the chosen look (greeter/apply.sh, called byinstall.sh; not stowed): true black background, a rounded box, the clock just above it with clock and calendar icons, no key-hint line,âąfor the password, and the titleace at <battery icon> NN%plus a bolt while charging. - tuigreet canât do rounded corners or icons, so
build-console-font.pypatches Terminus 12x24. Its 512 glyph slots are full, so the eight icons replace ΠΠΠΰ â â ⥠â§. Theyâre at Nerd Font code points. Every tty gets this font and the Catppuccin Mocha palette (vt-palette.service). - The battery title is read when the greeter starts (at boot and on logout)
and isnât live. With a config file present, tuigreet ignores
--custom-title, sotuigreet-launchruns it on a filled-in copy. - The font is loaded in the initramfs (consolefont hook), so
apply.shrunsmkinitcpio -P.systemd-vconsole-setupdoesnât run again later. - Verified with
tuigreet --mockat 160x45: clock 2 rows above the box. Pending: first real boot into it. Rollback is ingreeter/README.md. - Packages:
+terminus-font.
2026-09-30 (login: greetd + tuigreet replaces SDDM)
- SDDMâs greeter runs on Xorg, and that X server stayed up as root for the whole sway session (Xorg ~80 MB, plus sddm and sddm-helper ~45 MB). Now greetd + tuigreet on tty1: a text greeter that exits at login.
install.shwrites/etc/greetd/config.tomland/etc/pam.d/greetd(the packageâs file plus the gnome-keyring lines SDDMâs PAM had, so the login keyring still unlocks), disables sddm, enables greetd. The sway command sets theXDG_CURRENT_DESKTOP=sway:wlroots:swayfxetc. SDDM derived fromsway.desktop; the portals pick backends from it.- Packages:
+greetd greetd-tuigreet,-sddm qt6-virtualkeyboard(only SDDMâs on-screen keyboard used it), AUR-sddm-sugar-dark. - Pending: first login through greetd after a reboot; then remove sddm.
Rollback from a tty2 login:
sudo systemctl disable greetd && sudo systemctl enable sddm.
2026-09-30 (idle units start with sway, not before it)
swayidle.serviceandswaylock-on-sleep.servicewereWantedBy=default.target, so the user manager started them at login before sway existed: each crash-looped ~9 times (âUnable to connect to the compositorâ) until sway restarted them, andswayidle-startup.shneededreset-failedto get past the start limit. They, andsway-audio-idle-inhibit.service, now have no[Install](thebar-eventspattern) and are started only by swayâs config.swayidle-startup.shis now âstart unless caffeine is onâ.- Gotcha:
systemctl --user disableon a stow-symlinked unit also deletes the unitâs own symlink in~/.config/systemd/user(âlinkedâ unit).stow -R systemdputs it back. - Tested both caffeine branches live. Pending: a cold boot should show
no swayidle/swaylock-on-sleep failures in
journalctl --user -b.
2026-09-30 (post-reboot check; drop no-op cliphist exec)
- Verified v1.14.0 after a cold boot: waybar, bar-events, phone-events, mako, batsignal and kdeconnectd up; no failed units; dockerd not running (socket only). Idle process launches ~22 per 30 s, down from ~47.
- Removed
exec cliphist storefrom sway/config: with nothing on stdin at login it exits immediately. The twowl-paste --watch cliphist storelines do the actual recording.
v1.14.0 â 2026-09-29
2026-09-29 (waybar vanished at login: RT signal before its handler)
- After the reboot there was no bar.
bar-events.servicestarts beside waybar and sends every capsuleâs refresh signal 0.3 s later; on a cold boot waybar hadnât installed its handlers yet, and SIGRTMIN+Nâs default action is to terminate (no core dump, nothing logged). Reproduced:pkill -RTMIN+8 waybarevery 10 ms across a waybar start killed it. - New
waybar-signal N(pure bash, launches nothing) only signals a waybar whose/proc/<pid>/statusSigCgt shows it already catches that signal; a starting waybar runs each script once anyway.bar-events.pydoes the same check natively (and no longer forkspkillper refresh). All other senders â swayâs Caps/Num Lock release binds,keylock-toggle.sh,swayidle-startup.sh(also runs at login),caffeine-toggle.sh,theme-toggle.sh,notification-mode.shâ switched to it, by full path (swayâs PATH lacks ~/.local/bin). - Tested: the same 10 ms barrage through
waybar-signalleft waybar alive; three waybar + bar-events simultaneous starts all survived; signals still reach a running waybar (8-14). Also finished the event tests pending from the previous entry: a real container start/stop took both docker capsules 0 -> 1 -> 0 with the daemon started on demand, and a color-scheme flip sent exactly one theme refresh per change.
2026-09-29 (bar capsules are event-driven: nothing polls)
- New
bar-events.py(user unitbar-events.service, started by sway after waybar) is the one place that turns âthis changedâ into ârefresh that capsuleâ. It sleeps in a GLib main loop on D-Bus subscriptions: makoâsNotificationClosed(bell), GSettingscolor-scheme(theme), user systemdswayidle.servicestate (caffeine), KDE Connectâs device/battery/daemon signals and kdeconnectd joining or leaving the bus (phone), system systemddocker.servicestate plusdocker eventsstart/die while it runs (docker). systemdâsSubscribeworks unprivileged, so no root hook is needed. Bursts collapse into one refresh 0.3 s after the last event. - Every custom waybar capsule is now
"interval": "once"plus its signal (docker got signal 14). The KDE Connect refresh added tophone-events.pyin the previous entry moved here. - tmux runs no status jobs: the docker pill shows
@docker_count, whichbar-events.pysets on each change (and--tmux-dockeronce when tmux.conf loads).docker_status.shis gone;status-intervalonly redraws the clock. - waybarâs network module is netlink-driven; its timer only refreshed signal strength, now every 30 s instead of 2. The speed meter keeps 2 s: a rate has to be sampled.
- Tested live: 20 s idle ran no capsule script at all. A closed notification, a KDE Connect battery signal and a swayidle stop/start each re-ran exactly their own capsule right after.
2026-09-29 (idle wake-ups: 15 process launches a second down to ~2.4)
- Measured idle process launches (
processesin /proc/stat): 445 in 30 s. Two things made most of them: - tmux-continuumâs auto-save is a
#(continuum_save.sh)job in status-right, run every 5 s status tick: 22 processes each time just to find 15 minutes hadnât passed. Saving is now thetmux-autosave.timeruser unit (resurrectâssave.sh quietevery 15 min, skipped when no tmux server runs; enabled by install.sh). status-right is set after tpm so it replaces the hook continuum adds, and@continuum-save-intervalis 0 in case it comes back. continuum still does the auto-restore at server start. - The phone capsule ran
phone-status.shevery 10 s at 32 processes a run (busctl | cut | trper property). Parsing moved into bash and properties are read several per busctl call (12 a run, same output).phone-events.py, already watching KDE Connectâs D-Bus signals, now also refreshes the capsule (waybar signal 13) on reachable, pair, name, battery, pairing-request and device-list changes, debounced 0.3 s; the poll is a 60 s fallback. Tested with a fake batteryrefreshedsignal: the capsule re-ran right after it. keylock-status.sh(6 -> 1 process a run) andnotification-history-status.sh(6 -> 4) read files in bash instead ofls/head/cat. Same output.- Result: 71 launches in 30 s. Found on the way: resurrect deletes its
own save when two land in the same second (it compares the new file
with itself through
last) â only reachable by saving twice within a second, so left upstreamâs.
2026-09-29 (tmux: docker capsule no longer empty)
- Since docker became socket-activated its daemon is usually stopped,
and tmuxâs
docker_status.shprinted nothing then â but the pill (caps, fill, padding) lives in tmux.conf, so the bar showed an empty blue capsule. It now always prints the icon and count (0 while the daemon is stopped), same as waybarâs capsule, and still only runsdocker psoncedocker.serviceis up so the bar canât wake docker. Checked by rendering the bar in a pty on a-Ltest server.
2026-09-29 (/boot gets its boot-time fsck)
- After the reboot the kernel still warned that the
/bootFAT volume was not properly unmounted. fstab asks for a check (pass 2), butdosfstoolswasnât installed, so there was nofsck.fatand systemd skipped the check silently on every boot. Addeddosfstoolstopackages/pacman.txt. Post-reboot checks:resume=is gone from the kernel command line, no failed units, userspace boot 3.9 s (was 11.3 s), docker socket-activated, 4 GB swapfile + 4 GB zram.
2026-09-29 (waybar: capsules update on events, not polls; faster boot)
- Caps Lock and Num Lock were polled every second, and theme and
caffeine every 5 s, although they only change when you press or click
something. Now whatever changes them signals waybar: sway
--releasebinds on Caps_Lock/Num_Lock (the key itself still toggles),keylock-toggle.sh,theme-toggle.sh,caffeine-toggle.shandswayidle-startup.sh. Slow fallback polls (30 s and 60 s) stay in case something else changes them. The bell and docker capsules poll every 10 s instead of 5. Thatâs about 2.5 fewer process launches a second from waybar. Tested live: pressing Caps Lock showed and cleared the capsule within 0.6 s, and the LED toggled both times. - Boot:
docker.serviceandNetworkManager-wait-onlineheld up graphical.target by 7.4 s (of 11.3 s userspace). Theyâre off the path once docker is socket-activated (previous entry). systemd-bootâs menu timeout goes to 0; hold Space at boot for the menu. - Measured and left alone: the kernel stage only looks like 10.5 s
because the clock was already running at hand-off. Kernel plus
initramfs take ~0.6 s, so a systemd initramfs would gain nothing.
phone-events.pyâs
journalctl -fring watcher shows 69 MB RSS, but only ~0.7 MB is its own; the rest is page cache from the journal files, which the kernel reclaims when needed, and KDE Connect has no D-Bus signal to replace it.
2026-09-29 (lighter desktop: no tray applets, docker on demand)
A performance pass over what runs all the time:
nm-appletandblueman-applet(with itsblueman-tray, ~170 MB together) no longer start with sway. Waybar has no tray, so they showed nothing. WiFi secrets live in NetworkManager (psk-flags=0 on every saved network), bluez powers the adapter on by itself, and blueman-manager D-Bus-starts its applet when opened (for PIN pairing) and stops it on close. Checked after stopping them: WiFi connected, Bluetooth powered.- Docker starts through
docker.socketon first use instead of at boot. No container has a restart policy, so nothing is lost, and boot no longer waits ondocker.service(1.5 s) or pulls inNetworkManager-wait-online(5.8 s). The waybar docker capsule only runsdocker pswhile the daemon is up; otherwise its 5-second poll would start docker straight away. - The notification bellâs history count uses
jqinstead of starting Python every 5 s: 53 ms to 13 ms a run.
2026-09-29 (stability pass: hibernate fully gone, stuck phone media card)
A full stability pass (same checks as v1.0.0âs) found:
- Hibernate was masked on 2026-08-30 (the amdgpu resume crash, see
ARCHITECTURE.md) but its setup was still there:
resume=/resume_offset=on the kernel command line, theresumeinitramfs hook, and a 12 GB swapfile sized for a hibernation image. All removed; the swapfile is now 4 GB of plain overflow behind zram. - The installed lid-timeout hook in
/etchad picked up hibernate verbs and pointed at a Papirus icon that no longer exists. The repo copy (suspend only, AdwaitaLegacy icon) is the right one and goes back to/etc./etc/lid-timeout-poweroff.confwas still in test mode (TIMEOUT_SECONDS=60,DRY_RUN=1), so the 30-minute auto-poweroff only ever sent a notification; it gets the repoâs 1800 s / real mode. - systemdâs TPM NvPCR units failed every boot: they need a systemd
initramfs and this one is mkinitcpioâs busybox one. Nothing uses
measured boot here, so
install.shnow masks them. /bootwas mounted world-readable (fmask/dmask 0022), whichbootctlflags as a security hole for the random seed. Now 0077.- KDE Connectâs forwarded phone notifications ask never to expire, so a
phone media playerâs Previous/Play/Skip card stayed on screen for as
long as music played. mako now ignores that request for the daemonâs
notifications only (
desktop-entry=org.kde.kdeconnect.daemon), so they fade after 6 s; phone-events.pyâs ring and pairing prompts still stay until answered. Tested both with real notifications. - The previous entryâs
tmux.servicelink wasnât actually on disk. Restowedsystemd(it stays un-enabled, as intended), and removed a dangling~/.local/bin/__pycache__link to a gitignored dir.
Everything else is clean: git in sync with origin (branches and tags),
stow dry run has nothing to do, exactly one sway/waybar/mako/swaybg/
kdeconnectd, no failed user units, sway/mako/waybar/kitty configs valid,
all 46 scripts pass their syntax check, every waybar status script
returns JSON, keybind-search.py --check OK, tmux.conf loads cleanly and
idempotently on an isolated server, and the wallpaper archive sits at
its 60-file cap.
2026-09-29 (repo hygiene: nothing stray in ~)
A dry run of install.shâs own stow call found three problems:
systemd/lid-timeout-poweroff{,.conf}sat in the package root, so stow linked them into~as stray files. Theyâre reference copies of root-owned files that live in/etc(system-sleep hooks only run from there).systemd/.stow-local-ignorenow skips them, and the two links were removed from~; the real/etcfiles are untouched.tmux/palettes/(the Rose Dusty swatchbooks and their generators) would have been stowed as~/palettes. Moved todocs/tmux-palettes/, and added to_config.ymlâs exclude list so the docs site still doesnât publish them.~/.config/systemd/user/tmux.servicehad never been linked (the unit was added aftersystemd/was first stowed, the gotcha noted in ARCHITECTURE.md). Restowed. The same dry run now reports nothing to do.
v1.13.0-nightly â 2026-09-29
Everything from here down to v1.12.0-nightly below, on development
(Nightly channel) only â not merged to main/Stable yet. MINOR bump: a
visual and behaviour pass across the desktop. Danger is one dark red
everywhere; notifications fade on their own (low 2 s, normal 3 s,
critical 6 s) while the phone ring, pairing request and battery warnings
stay; wofi popups use fixed heights so they canât come up cut short; a
pairing reject shows one notification. In waybar: a larger volume icon, a
filling-sun brightness icon, fixed-size brightness/volume capsules, one
8px gap inside each of the three parts, and every capsuleâs contents
centred to the pixel. Checked live: no failed units, no new errors in
this bootâs logs, every script passes its syntax check, and every waybar
status script returns valid JSON.
2026-09-29 (waybar: contents centred in every capsule)
Measured every capsule on a temporary solid background (the visible gap from icon/text to the capsule edge, on all four sides). Icon glyphs carry uneven side bearings, so equal padding left some off-centre: power 12/8, bell 12/9, caffeine 9/12, wallpaper 9/11, bluetooth/theme 12/11, time/battery 13/12. Per-capsule padding now gives 12/12 on every static capsule. Workspace numbers sat 1px high because the underline is a 2px bottom border; a matching transparent top border centres them (11/11). Whatâs left is at most half a pixel, and canât be split: an odd number of spare pixels (the 31px capsule height against an even-height glyph, or GTK centring changing text inside network speed/brightness min-width).
2026-09-29 (waybar: three parts, one gap)
The bar is three parts (left, centre, right), and every capsule inside a
part is now the same 8px from its neighbour. Before, per-module margins
gave a mix of 9, 11, 13, 19 and 21px gaps that split each side into
sub-clusters. All side margins are gone; waybarâs spacing (8) sets the
gap, and only the barâs two ends keep a 4px margin, matching the top.
Measured from screenshots: every gap matches, including Caps Lock when
it appears, the left and right edges are both 4px, and the centre part
sits exactly at the middle of the screen.
2026-09-29 (brightness and volume capsules: fixed size)
The brightness and volume capsules no longer grow and shrink as the
number changes. Both are 60px (min-width), the smallest width that
holds âMutedâ, the widest text, with the usual 12px padding, so thereâs
no excess. Content stays centred. Measured from screenshots at
brightness 5/50/100% and volume 5/60%/muted: both are the same width
every time, and the left and right gaps match within 1px. Height was
already fixed by the bar (31px in every state).
2026-09-29 (brightness icon: a filling sun)
The waybar brightness icon was a set of pie slices, which read as a clock
at low levels. Itâs now Materialâs brightness sun, which fills as the
level rises: outline up to a third, half-filled to two thirds, full
above that. At 125% (rise='-1pt') it matches the volume icon beside
it, and screenshots show it centred on the digits at low, medium and
high brightness, with the digits at the same height as the volume
moduleâs.
2026-09-29 (larger volume icon)
The waybar volume icon is 150% (a Pango span on {icon} and on the
muted glyph), so itâs no longer smaller than the brightness icon next to
it. On its own the bigger glyph pushed the percentage 2 px down and sat
2 px above the textâs middle; rise='-2pt' fixes both. Measured from
screenshots: at low, medium, high and muted the icon and text share one
centre line, the digits sit at the same height as the brightness
moduleâs, and the bar is still 41 px tall. The same span was once
recorded as breaking the icon, but the real cause then was the
threshold-object format-icons bug.
2026-09-29 (danger is dark red everywhere)
Error notifications had a peach (orange) border, and other danger states
used Catppuccinâs pinkish red #F38BA8. Danger is now one dark red,
#DC2626, in every place:
- makoâs error/critical border (was peach)
- waybarâs critical battery and disconnected network, plus a new urgent workspace style
- swayâs urgent window border (was swayâs built-in
#900000) - swaylockâs wrong-password ring and text
- rmpcâs error lines
- the Docker pickerâs Stop and the power menuâs Power off
#D20F39(Catppuccin Latte red) was darker but too hard to read as waybar text.#F38BA8stays as the focus accent (workspaces, focused window border, power button), which isnât a danger signal. Peach stays for warnings (low battery, Caps Lock, picker commands).
2026-09-29 (popups keep their height; notifications fade)
The pairing dialog sometimes came up cut down to just its prompt, and a rejected pairing left a notification on screen that never went away.
- Popups use a fixed
--heightinstead of--lines. wofi sizes a--lineslist from the rows it has when it first draws. Rows piped in a moment later (every script popup) sometimes left the list one row tall. Reproduced with delays of 0.05 s and 0.15 s; a fixed height drew every row each time. Each height matches what--linesused to draw, measured from SwayFXâs layer-surface size: pairing dialog 200 px, calculator 100/150, music query 100, music results 752, notification history 752, keybinding search 680. The pairing dialog and theSuper+Ctrl+aaction picker also skip wofiâs cache, which had been moving Reject above Accept. - Notifications fade: low after 2 s, normal 3 s, critical 6 s, and
6 s for ones with a button (Undo, Open). Critical used to never fade.
The phone ring and the pairing request (
-t 0) and batsignalâs battery warnings still stay until theyâre dealt with. History (Super+Shift+n) keeps everything. - A reject shows one notification. KDE Connect followed it with
âPairing request expiredâ and a critical âPairing failed: Cancelled by
userâ, which is what stuck.
phone-events.pynow skips both for 10 s after its own Reject. A real expiry is still announced.
v1.12.0-nightly â 2026-09-29
Everything from here down to v1.11.1-nightly below, on development
(Nightly channel) only â not merged to main/Stable yet. MINOR bump: a
new capability, phone <-> laptop sync through KDE Connect. It covers the
phone picker (Super+Shift+o), the waybar phone module, received files
and clipboard notifications, the pairing flow with Accept/Reject, remote
input via xdg-desktop-portal-luminous, and âRing my laptopâ with your own
song, which Super+Ctrl+a always stops. Also moves the keybinding-registry
entry back under v1.11.0-nightly, the release it shipped in. See the two
entries just below.
2026-09-29 (phone ring: always stoppable)
The first real âRing my laptopâ played the song for 24 s and couldnât be
stopped: clicking the notification and Super+Ctrl+a did nothing, and the
power key was the only way out. The logs show no stop request reached the
ring code in those 24 s, and no other notification appeared. Both paths
work when tested now, so the exact trigger isnât known. Suspected: the ring
code asked KDE Connect for the phoneâs name (no D-Bus timeout) while KDE
Connect was busy with the ring request, and the 60 s limit only started
once the notification was up. Now nothing can hold up a stop:
Super+Ctrl+astops a ring directly by killing the player ($XDG_RUNTIME_DIR/find-my-laptop.pid), without going through mako. When nothing is ringing it opens the action picker as before.- The 60 s limit starts with the sound. The notification (name lookup included) runs in its own thread, and every KDE Connect D-Bus query has a 3 s timeout.
- The ring logs to the service journal (started / notification shown / stopped by what), so a failure can be traced.
- Tested at volume 0 with a real player: key, real mouse click (ydotool), time limit, and KDE Connect hanging for 20 s (key still stops it at 1.5 s).
2026-09-28 (phone <-> laptop: KDE Connect, phone picker, waybar phone module)
Laptop and phone reach each other over the phoneâs hotspot (or any shared network) through KDE Connect, the same Play Store app used with GSConnect on Ubuntu before. Paired and tested with the real phone (CMF Phone 2 Pro) over its hotspot.
- Daemon:
kdeconnectdruns as the systemd user unitkdeconnect.service:Type=dbus+BusName, so âstartedâ means actually ready.Restart=always: a crash restarts it; asystemctl --user stopstays stopped.- No
[Install], because enabled at login it would start before sway exportsWAYLAND_DISPLAYand clipboard sync would silently break. sway starts it afterdbus-update-activation-environment. - A user D-Bus service override (
kdeconnect/stow package) points activation at the unit (SystemdService=), so akdeconnect-clicall while itâs down starts the supervised daemon, never a stray one. Found live: the bus only picks the override up after aReloadConfig(or a new login).
- Phone picker (
phone-picker.py,Super+Shift+o, also the waybar click), same shape as the Bluetooth picker, every entry action + object:- Send file (the 40 newest files in Downloads/Pictures/Screenshots/ Videos/Documents/Desktop), send clipboard, ring (find my phone), ping, unpair.
- Browse files: mounts the phone over sftp and opens the folder the
phone exposes (
getDirectories, e.g..../storage/emulated/0âInternal shared storageâ; the mount root itself is always âPermission deniedâ on Android) in yazi, in its own kitty window.xdg-openwould go through kittyâsshell tmux ...and just attach tmux. - Accept/reject an incoming pair request; pair with a reachable, unpaired phone.
- Opens in ~80 ms. Devices are listed over D-Bus (the daemonâs
devices, ~6 ms), not withkdeconnect-cli --list-devices, which blocks ~2 s on network discovery every call and made the popup noticeably late. Per-device CLI actions (-d ID --pingetc.) are fast (~30 ms) and stay on the CLI.
- Waybar
custom/phone(phone-status.sh), in the right-hand device-controls cluster between docker and caffeine: the phone glyph plus battery %, a bolt while charging, a green border while connected, and a dimmed glyph when not. It never starts the daemon:busctl treeD-Bus-activates the service even with--auto-start=no(found live: it revived a stopped daemon every 10 s), so it checksNameHasOwnerfirst. - Notifications and clipboard: KDE Connectâs own plugins. The phoneâs notifications arrive through mako, and the clipboard syncs both ways.
- Every feature working, with feedback (second pass, after testing
from the phone):
- Remote input / presentation remote were dropped: 307x âNo such
interface org.freedesktop.portal.RemoteDesktopâ. swayâs portals (wlr,
gtk) donât implement RemoteDesktop.
xdg-desktop-portal-luminous(AUR) does;xdg/.config/xdg-desktop-portal/sway-portals.confroutes only RemoteDesktop to it (gtk/wlr keep the rest). KDE Connectâs windows (the presenter pointer overlay) float instead of tiling. - Files from the phone arrived silently, because KDE Connect only
reports finished transfers to Plasmaâs job tracker.
phone-events.py(user unitphone-events.service, started by sway) watches the share pluginâsshareReceivedD-Bus signal and notifies: âReceived from", name, size, folder; click opens it, "Show in folder" opens yazi. Bug found testing: `dbus-monitor` block-buffers into a pipe, so signals sat unread; it runs under `stdbuf -oL` now. - Clipboard: laptop -> phone auto-sync pushed everything copied,
passwords included, so itâs off (
autoShare=false, per-device policy below). âSend clipboardâ in the picker uses the clipboard pluginâssendClipboard, so it lands on the phoneâs clipboard (not as a shared text), and confirms with a preview. Phone -> laptop (Android 10+ only sends when you tap âSend clipboardâ on the phone) now notifies with a preview and an Undo that restores the previous clipboard, which is held in memory only, never on disk. Clipboard reads are capped at 1 MB and skip non-text: an uncapped read once spiked the service to a 740 MB peak. Stress-tested with a 50 MB clipboard and an image: 9.8 MB peak. - Ring my laptop from the phone:
- It was silent: the default ringtone is Plasmaâs
Oxygen-Im-Phone-Ring.ogg, absent here. - Pointing it at freedesktopâs
phone-incoming-call.ogathen gave a 1.46 s chirp, because KDE Connect plays the ringtone once. phone-events.pynow generates a ~24 s ringtone (that ring x12 with 0.5 s gaps, ffmpeg,~/.local/share/sounds/, not kept in the repo) and sets it per device.- KDE Connect gives no signal and no way to stop the ring, so the
watcher follows PipeWire (
pactl subscribe) for the kdeconnectd stream. It notifies âis ringing this laptop"; a click stops it by destroying that stream's PipeWire node (`pw-cli destroy`; this pactl has no `kill-sink-input`, found testing). The notification closes itself when the ring ends. A muted laptop is unmuted for the ring and muted again after. - Verified with a stand-in stream: click stops it, natural end dismisses, mute is restored.
- Test pitfall: makoâs own notification sound (
paplay, apacatstream) matched the stand-in owner at first and fed back into more âringingâ notifications. Production only matches kdeconnectd (by binary, or byapplication.namefor native PipeWire streams).
- It was silent: the default ringtone is Plasmaâs
- Per-device policy: KDE Connect keeps plugin settings per device, so
phone-events.pyappliesDEVICE_POLICY(the clipboard + ringtone settings above) to every paired phone at start and after pairing, and restarts the daemon if anything changed. Super+Ctrl+a(notification-actions.sh) lists the current notificationâs actions in wofi, since makoâs left click only runs the default one: Accept/Reject a pairing request, Undo a phone clipboard, Show in folder.makoctl menuparses the pickerâs own dash-options as its own (it rejected wofiâs-i/-p), so the script passes itself back as an argument-free picker that runs wofi. Tested end to end with a two-action notification.
- Remote input / presentation remote were dropped: 307x âNo such
interface org.freedesktop.portal.RemoteDesktopâ. swayâs portals (wlr,
gtk) donât implement RemoteDesktop.
- Install:
kdeconnectandsshfsare inpackages/pacman.txt, andinstall.shopens ufw 1714-1764 TCP/UDP. The ports were the one thing that blocked discovery here: the phone stayed invisible until they were open. The noisy ModemManager telephony plugin is disabled (no modem).
v1.11.1-nightly â 2026-09-28
Everything from here down to v1.11.0-nightly below, on development
(Nightly channel) only â not merged to main/Stable yet. PATCH bump:
fixes and packaging, no new capabilities. The keybinding-check hook and
the global Claude notes are now versioned and set up by install.sh. The
lazygit theme is committed. install.sh no longer stows the docs site
into ~ and lists jq/python. A stray Chrome mimeapps.list entry was
discarded. See the entry just below.
2026-09-28 (Claude hook + global notes in the repo, lazygit theme, install.sh fixes)
- The keybinding-check hook is versioned and wired up.
scripts/.local/bin/claude-hooks-install.shregisterskeybind-check-hook.shin~/.claude/settings.json, andinstall.shruns it. It merges with jq instead of stowing the file, because Claude Code rewritessettings.jsonitself. It creates the file if missing, keeps every other setting and hook, and does nothing on a re-run. Tested in two fake homes (no file; a file with a theme and another hook) and on this machine. - New
claude/stow package:~/.claude/CLAUDE.md, the global âkeybindings go through the registry, verb-firstâ rule, now travels with the repo. - New
lazygit/stow package: the Rose Dusty lazygit theme for theprefix gpopup. It was written with the tmux work but never committed. - install.sh:
- The stow list skipped only
packages/anddocs/, so a fresh install would have linked the docs siteâsassets/and_layouts/into~. Those are excluded now. jqandpythonwere used (hooks, every.pypicker) but not listed inpackages/pacman.txt. They are listed now.
- The stow list skipped only
mimeapps.list: an uncommitted change was discarded. Chrome had added itself as an extra âOpen withâ candidate for HTML under[Added Associations]. The default apps were never affected. Chrome may add the entry again, which is harmless.
v1.11.0-nightly â 2026-09-28
Everything from here down to v1.10.0-nightly below, on development
(Nightly channel) only â not merged to main/Stable yet. MINOR bump: new
capabilities. There is one keybinding registry for the whole system
(sway, tmux, kitty, Zed, rmpc, script-internal keys). Ctrl+a ? in tmux
searches it and runs the picked binding. Every description is action +
object, and both search boxes say so. A --check mode plus a Claude Code
hook keep it complete.
Stability sweep before tagging:
bash -n/py_compile/zsh -npass on every script.sway -Cis clean, and kittyâs own config loader acceptskitty.conf.- A fresh
tmux -f tmux.confserver loads with no messages. The live server has exactly one continuum hook and no duplicated terminal-features/overrides. --checkis OK against the live server and against the file (--tmux-conf).- With no tmux server running, the search still builds (script keys only) instead of failing.
- No sway key collides with a kitty or tmux root key.
- The search builds its 482 entries in about 0.35 s.
Improved: the throwaway tmux servers the searcher starts (the
stock-bindings probe, and the --tmux-conf check) used named sockets in
/tmp/tmux-UID, which stayed there after each run. They now use -S
sockets in a private temp dir that is removed afterwards. Verified: no
sockets, dirs or processes left, and stock detection is unchanged. Dead
test sockets from this session were cleaned out of /tmp/tmux-1000.
Re-verified after the change: Ctrl+a ? -> âselect window 2â -> Enter
switches the window, and the hook still passes.
2026-09-28 (keybindings: one registry for the whole system, tmux prefix ? searches and runs)
keybind-search.py (Super+Shift+/) is now the single registry for every
keybinding except Neovimâs. It is live-parsed and backs three front-ends.
What was wrong with the old list:
- Sway: it took the last comment line above each bind. Several entries read as fragments (âstates.â, âobvious to the eye which button Tab landed on.â, âOr use $mod+[up|down|left|right]â).
- Tmux: keys were shown tmux-escaped (
\$,\;), so stock descriptions never matched. Plugin keys (extrakto, TPM) andprefix $showed raw shell commands.M-MouseDownâŠmenus slipped through the mouse filter. 68 unused emacs copy-mode keys were listed. - Everything else: kitty, Zed and rmpc bindings, and keys inside popup scripts, werenât listed at all.
- wofi: text wasnât markup-escaped, so tmuxâs
</>keys broke rendering, and a copied selection included the<span>tags.
Now:
- Explicit descriptions per source:
- sway and kitty: a
#: Descriptionline above each bind.{}expands to the bindâs last word, e.g.#: Switch to workspace {}. - tmux: its own
bind -N, read per table vialist-keys -N -a -P ''. Stock keys are detected by diffing against a throwaway-f /dev/nullserver and shown asdefault. - Zed
// commentsor humanized action names, and rmpc action names. - Keys inside popup scripts:
# keybind: Source/scope | keys | desc.
- sway and kitty: a
- Readable keys everywhere:
Ctrl+a K,Alt+Shift+Up, rmpcâs<C-w>kbecomesCtrl+w k. - Sway modes are detected generically, e.g.
[resize mode, Super+Ctrl+r first]. Ctrl+a ?in tmux (tmux_keys.sh) is an fzf popup over the tmux part of the registry. Enter runs the picked binding by replaying its keys withsend-keys -Kafter the popup closes. A copy-mode key enters copy mode first, and;is escaped for the tmux command line.--checkexits 1 on any undescribed binding or a sway/kitty key bound twice in one scope.--check --tmux-conf FILEchecks the file on disk in a throwaway server.--list [source]prints a plain table.- Enforced for future edits:
CLAUDE.md(repo) and~/.claude/CLAUDE.mdstate the rule. A Claude Code PostToolUse hook (keybind-check-hook.sh) runs--checkafter any edit to a keybinding source and reports failures back.
Verified:
--checkis OK: 96 sway, 214 tmux, 1 kitty, 88 Zed, 83 rmpc.- All 482 wofi lines parse as valid markup.
sway -Cis clean. Ctrl+a ?tested in an isolated nested tmux: âsynchronizedâ + Enter turned sync on,Alt+2switched windows, and Esc runs nothing.send-keys -Kreplays{ } " # $ ~ ' %and\;correctly.- An undocumented
bind F5fails the tmux check. Removing kittyâs#:line made the live hook report the failure, and it was restored.
2026-09-28 (keybindings: every description is action + object, and the search box says so)
The searches are meant to be used as action + object (âkill windowâ, âopen lazygitâ, âraise volumeâ). wofiâs fuzzy match is an ordered subsequence match, so that only works if every description starts with its verb. Many didnât: âEmoji pickerâ, âLazygit popup in current directoryâ, âFloating command promptâ, âVolume down 5%â, âMedia: play/pauseâ, and Zedâs âProject panel: toggle âŠâ.
- Rewritten as
<Verb> <object> (details): 32 sway descriptions, 10 tmux notes and the plugin notes. Examples: âOpen emoji pickerâ, âOpen lazygit popup (current directory)â, âLower volume 5%â, âPlay next trackâ, âTake screenshot: select a regionâ, âClose (kill) the focused windowâ. âkill windowâ finds the sway window too. - Zed: action names are shown verb-first (âToggle hide hidden (project panel)â), and Zedâs wording is translated to what people type: âNewâ becomes âCreate newâ, âDeployâ becomes âOpenâ, âActivateâ becomes âFocusâ.
- rmpc: verb-first names (âRaise volumeâ, âPlay next trackâ, âFocus pane aboveâ).
--checknow rejects a hand-written description whose first word isnât a verb (VERBSin the script).- Placeholder: both search boxes show it. wofiâs prompt reads âaction
- object, e.g. kill window, open lazygit, raise volumeâ. The tmux popupâs ghost text reads â⊠kill window, split pane, rename sessionâ.
- Documented: the rule is in
CLAUDE.md,~/.claude/CLAUDE.mdanddocs/KEYBINDINGS.md.
Verified: 19 typical queries were simulated against the ordered fuzzy
match, and each oneâs top hit is the right binding (âkill windowâ ->
Ctrl+a &, âtake screenshotâ -> Print, âopen session pickerâ ->
Ctrl+a s). Every non-stock entry across all sources is verb-first. In a
nested test tmux, Ctrl+a ? shows the ghost text, and âkill windowâ
narrows 214 entries to exactly 1.
v1.10.0-nightly â 2026-09-28
Everything from here down to v1.9.0 below, on development (Nightly
channel) only â not merged to main/Stable yet. MINOR bump: new tmux
capabilities (lazygit/scratch/btop popups, a session picker with previews,
floating rename inputs, centred confirm menus, quiet save/restore) plus
stability fixes for things that broke tmux outright. See the 2026-09-28
entry just below.
2026-09-28 (tmux: no more tmux-inside-a-popup, quiet save/restore, stability sweep)
The bug: creating a session from the floating command prompt
(prefix : -> new -s foo) opened a whole second tmux inside the popup
and wrecked the layout. cmd_prompt.sh ran tmux <typed command> from a
shell inside the popup, and a tmux client started there attaches to the
popupâs own pty. tmuxâs âsessions should be nested with careâ guard only
checks pane ttys, so it never fired. Now the popup only collects the line.
The binding then source-files it in the real clientâs context, exactly
like tmuxâs built-in : prompt. Typos are parse-checked first
(source-file -n) and go to mako instead of a view-mode error pane.
Nothing in the status line any more, only mako:
prefix rno longer flashes âReloaded!â. The on-attach auto-reload notifies through mako too.prefix C-s/C-rgo throughresurrect.sh, which runs resurrectâs scripts with atmuxshim onPATHthat dropsdisplay-message(the âSavingâŠâ spinner, âTmux environment saved!â, âTmux restore complete!â) but passesdisplay-message -pqueries. It then notifies âSessions saved/restoredâ. continuumâs boot-time auto-restore uses it as well.- Kill pane/window/session (
x,&,K,M-k) are centreddisplay-menus instead of y/n prompts in the bar. - Rename window/session (
,,R,$) use the same floating input asprefix :, prefilled with the current name. The typed text is quoted, with\ " $escaped and#doubled, so a name likea "b" $HOME #Wis kept literally. prefix S(sync panes) notifies.Alt+1..9,C-S-1..6andprefix Lare silent no-ops when the window or last session doesnât exist, instead of printing âcanât find windowâ.
Things that broke tmux arbitrarily:
- Popup shells could nest tmux too. Bare
tmuxin theprefix tscratch shell went through the zshtmux()wrapper tonew-session -A -s main, puttingmaininside the popup. The wrapper now refuses to attach while$TMUXis set (tmux,newwithout-d,attach) and points atprefix s.new -d ...,lsand the rest pass through. - Auto-save stopped silently. continuum only adds its
#(continuum_save.sh)hook tostatus-rightif it guesses no other tmux server is running, by counting every process whose command starts withtmux. Anytmuxcommand running during a reload (a popup script, atmux setfrom a shell) failed that guess.status-righthad just been reset, so auto-save stopped until some later lucky reload. The config now appends the hook itself, only on the default socket (%ifonsocket_path), so atmux -L testserver can never overwrite the real saves. Reproduced live: after one such reload the hook was gone. - Options grew on every reload.
set -as terminal-features/overridesappended another copy each time the file was sourced: 13 copies of each after a day ofprefix rand auto-reloads. They now use fixed array slots ([90],[91]) that are simply overwritten. - The session picker pins
switch-client/displayto the client that opened it. With several kitty windows attached, a bareswitch-clientmoved whichever client tmux guessed. systemd/.../tmux.service(not enabled) started an unnamed session and a noisy save. It now uses-s mainandsave.sh quiet, to match.
Also in this batch (tmux work that was uncommitted on development):
lazygit / scratch shell / btop popups centred in the pane area
(popup.sh), the sessions-only picker with live pane previews
(sessionizer.sh), extrakto (prefix Tab), Alt+1..9 window switching,
kitty extended keys and undercurl, a styled copy mode and menus,
detach-on-destroy off so killing a session moves you to the next one,
copy-command wl-copy, and windows named after their directory (shells)
or program (anything else).
Verified in an isolated nested tmux (-L outer driving -L inner
with the real config), with resurrect pointed at a scratch dir:
prefix :->new -s twoswitches the real client, and no nested client exists. Typo -> mako. Esc -> nothing runs.- Picker switch,
Lwith and without a last session. C-swrites a save with a clean bar.Kkills and moves to the next session.C-rbrings it back.M-kkills the others.- Rename with quotes,
$and#kept literally.xcancel/confirm. tmuxtyped in the scratch popup is refused.- Two
prefix rin a row leave the option counts flat. No errors inshow-messages. - Then on the live server: duplicates cleared, exactly one auto-save
hook, real saves untouched.
bash -n/zsh -non every changed script.
v1.9.0 â 2026-09-19
Cut from development to main/Stable per docs/VERSIONING.md. Everything
since v1.8.0: the tmux/kitty overhaul (named main session, floating
command prompt, mako notifications, minimal transparent bar with the Rose
Dusty palette shared with the zsh prompt), the two lid-close/lock fixes from
v1.8.1-nightly, and the battery charge-limit nudges reworked (below).
Stability sweep run against developmentâs tip before the cut: bash -n
and py_compile across the changed scripts, sway -C, systemd-analyze
verify on the battery unit, a clean tmux config load, and the watcher
exercised against a fake power_supply tree (see the entry below).
2026-09-19 (battery limit: plug-in nudge, full scenario coverage, persisted limit)
The limit already persisted (~/.local/state/battery-limit/threshold, a plain
file, survives reboots), but the nudge only fired on crossing the limit while
already charging, so booting above the limit and then plugging in was
silent. Reworked battery-limit-watch.sh around a small state machine:
- Plug in while at/above the limit -> âCharger connected at 84% â youâre already past your 70% limit, charging isnât needed, unplugâ.
- Plug in at 100% -> âbattery is full, no need to chargeâ.
- Cross the limit while charging -> âBattery at 70% â limit reachedâ.
- Reach 100% after being nudged -> âBattery full (100%) â please unplugâ.
- Silent: reminder off / garbage value, on battery, plugging in below the limit, and service/boot start with the charger already in (only a fresh plug-in or a real crossing nudges).
- Changing the limit re-arms it (
.notifiedstores the limit it fired for); the picker tells you immediately if you set a limit youâre already past while plugged, and suppresses the watcherâs duplicate. - Robustness: a 60 s tick alongside the uevents so plug/unplug during suspend
is caught on resume; power-supply devices found by
typeinstead of hardcodedBAT1/ACAD; exits (systemd restarts it) ifudevadmdies. - Verified with 20 scripted scenarios against a fake
power_supplytree (boot plugged/unplugged, plug-in above/below/at 100, crossing, no repeat spam, re-arm after unplug, raising the limit mid-charge, off, garbage value) plus a live run of the real loop where the tick caught a plug-in with no uevent. Not tested with the real charger cable.
v1.9.0-nightly â 2026-09-19
Everything from here down to v1.8.0 below, on development (Nightly
channel) only â not merged to main/Stable yet. MINOR bump: new
capabilities, no bug-fix-only content â tmux now always lands in a named
main session, the tmux command prompt is a centered floating box, tmux
notifications go through mako, and the tmux bar/prompt got a new minimal
palette (Rose Dusty). See the 2026-09-19 entry just below.
2026-09-19 (tmux: named main session, floating cmdline, mako notifications, minimal transparent bar)
- Always the
mainsession. Baretmux(zsh function in.zshrc) runstmux new-session -A -s main; explicit subcommands pass through. kittyâsshellistmux new-session -A -s main, so every new terminal window attaches to (or creates)maininstead of numbered sessions. - kitty: removed the commented-out
background #2A2622line and its long explanation â kitty stays on its default black. - Notifications through mako, not the status line.
prefix rreload and background-window bells nownotify-send(mako) instead of tmuxâs yellow message line / bar highlight (alert-bellhook,window-status-{bell,activity}-style default). tmux-resurrectâs own manual save/restore messages still use tmuxâs message line â theyâre hardcoded inside the plugin (outside this repo), so left alone. - Floating command prompt.
prefix :opens a centered popup (.tmux/scripts/cmd_prompt.sh, an fzf input in a rounded âCmdlineâ box) instead of the bottom-line prompt; errors go to mako. - New palette, transparent bar. Built two swatchbook pages under
tmux/palettes/(brightindex.html, minimalindex2.html, with the generatorsgen2.js/gen3.jsthat enforce a minimum colour distance between capsules and WCAG text contrast). Picked #40 âRose Dustyâ: session#D095A4, active window#D4B25C, inactive#9A9FAA, docker#6C8EBF, date#9ED1A6, time#C282CE. The bar has no background anymore (@surface = default) â only the capsules are drawn. The zsh prompt (.p10k.zshdir + success>) uses the same session colour. - Known follow-up: waybar workspace indicator / sway focused border are
still
#F38BA8, close to but not the same as#D095A4.
v1.8.1-nightly â 2026-09-16
Everything from here down to v1.8.0 above, on development (Nightly
channel) only â not merged to main/Stable yet. PATCH bump: two real
bug fixes, no new features â lid-close was silently hibernating instead
of suspending (a manual logind.conf.d override, not something
install.sh had written) and hibernate resume was corrupting the
amdgpu firmware reload badly enough to force a hard power-cycle to
recover from a black screen; separately, caffeine mode being left on
had been silently disabling the lock-before-sleep security hook for two
days, since it stopped the whole swayidle.service that hook lived in.
See docs/ARCHITECTURE.mdâs âLid-close, suspend vs hibernateâ and
âCaffeine mode was silently disabling the lock-before-sleep security
hookâ sections for both incidents in full, including a still-open,
unreproduced-since black-screen glitch noted as a known issue rather
than papered over.
2026-09-16 (lid-close: hibernate removed entirely, suspend-only; lock-before-sleep no longer tied to caffeine mode)
Two related incidents from the same debugging session, both walked
through end-to-end live rather than guessed at from a single log line
â full blow-by-blow (exact journalctl output, the delay-lock-timeout
wrinkle, the still-open black-screen glitch) in
docs/ARCHITECTURE.md.
Hibernate removed, suspend-only. Lid-close was configured to
hibernate via a manual /etc/systemd/logind.conf.d/10-lid-hibernate.conf
override, not anything this repo had written. Hibernate resume on this
machineâs Ryzen 3250U/Vega APU corrupts the amdgpu firmware reload
(RLC_RESTORE_LIST_* ucode fails to load, then the GPU reset it
triggers fails too) â confirmed from journalctl -b -1: the rest of
the OS came back fine, only the display pipeline was dead, matching
âlid+Enter does nothing, screen stays blackâ exactly. Confirmed this
hardware genuinely supports proper deep suspend before relying on it
(/sys/power/mem_sleep -> s2idle [deep]) rather than assuming.
Removed the override, masked every hibernate-capable systemd unit so
nothing can reach it again, and added the same to install.sh so a
fresh install never has the bug in the first place. Verified across
several real lid close/open cycles afterward: clean
PM: suspend entry (deep)/PM: suspend exit pairs, ~1 second resume
instead of hibernateâs ~3.5-minute image restore.
Lock-before-sleep decoupled from caffeine mode. Closed and reopened
the lid post-fix and the system suspended/resumed correctly but never
locked. Root cause: ~/.local/state/caffeine/enabled had been stuck on
for two days, and swayidle-startup.sh stops swayidle.service
entirely whenever that marker exists â which took the before-sleep
'swaylock ...' hook down with it, since it lived in the same serviceâs
idle config. Thatâs a real design gap, not just the stale flag: âdonât
auto-lock/dim/suspend while Iâm using thisâ and âlock before the lid
physically closesâ are different triggers that never should have
shared one on/off switch. Split before-sleep out into its own file
(sway/idle/lock-on-sleep) and its own always-on systemd user service
(swaylock-on-sleep.service, added to install.shâs enable list) that
caffeine mode never touches. Lid-close now locks every time regardless
of caffeine state; idle-timeout dim/lock/suspend still respects
caffeine exactly as before.
Known, unresolved: one lid close/open (which had also hit a
5-second delay-lock inhibitor timeout from closing/reopening too fast)
showed the lock screen render correctly, then go black for a few
seconds while typing the password, with zero corresponding log entry
anywhere â no new suspend, no crash, no coredump. Re-tested live under
journalctl -f afterward with no repeat. Left open rather than guessed
at, since thereâs nothing in the logs yet to actually fix.
v1.8.0 â 2026-09-15
Cut from development to main/Stable per docs/VERSIONING.md. Everything
since v1.7.0: MINOR bump for the battery charge-limit reminder (a real new
capability â click the waybar battery icon, choose a target %, get a
notification nudge to unplug once you cross it), alongside the docs siteâs
channel-toggle fixes (a thumb-position double-offset bug, the inactive tab
having no visible shape, cramped caption spacing).
Full stability sweep run against developmentâs tip before this cut, not
just the new commits: bash -n/py_compile across every script in the
repo, sway -C, systemd-analyze verify on every unit (one pre-existing,
unrelated failure noted and left alone â tmux.serviceâs Documentation=
man:tmux(1) canât resolve because man itself isnât installed on this
machine; the file is byte-identical to what already shipped in v1.7.0
and the unit isnât part of install.shâs enable list at all), a clean
tmux/headless-nvim config load, and live systemctl --user status on
every service install.sh actually enables â zero failed units system-
wide. The battery-limit feature specifically was confirmed working through
real use, not just automated checks: makoctl history -j shows an actual
âBattery reminder set to 70%â notification from genuinely clicking the
waybar icon and picking a value, not a scripted test.
2026-09-15 (battery charge-limit reminder â software nudge only, real hardware not touched)
Asked directly for a waybar battery-icon click to offer a battery
charge-limit picker, suggesting 70% as a safe default, âso they have a
better battery lifeâ. Checked whether this laptop (Acer Aspire A315-23)
can actually enforce a hardware charge limit before writing any code,
rather than assuming a charge_control_end_threshold-style sysfs knob
exists the way it does on many ThinkPads/Framework machines:
It canât, safely. /sys/class/power_supply/BAT1/ has no
charge_control_end_threshold at all â confirmed directly on this
machine, the firmware doesnât expose one to Linux. The only real
workaround is a community out-of-tree kernel module,
acer-wmi-battery,
and itâs a worse fit than what was asked for either way: a fixed 80%
on/off toggle, not a selectable range, manual insmod (no DKMS), and
its own MODELS.md lists several other Aspire A315 variants as
confirmed working/not-working â A315-23 is in neither list, tested
by no one. More importantly: searching Acerâs own Community forum
turned up another A315-23 owner whose laptop wouldnât boot with the
charger plugged in (crashed in BIOS) after enabling the exact same
feature in Acerâs own official Windows software (Care Center) â not
a third-party Linux tool, Acerâs own first-party implementation, on
this same model. Thatâs a strong signal this modelâs EC handling of
charge-limit mode is unreliable at the firmware level, independent of
OS. Flagged this to get a real decision rather than either silently
building a fake control or silently doing nothing: went with a
software-only reminder, no EC/firmware calls of any kind.
What actually shipped: battery-limit-picker.py (wofi popup on
the waybar battery moduleâs on-click, matching docker-picker.pyâs/
the other pickersâ exact style â Sky for selectable options, Red for
Off, Green for whateverâs currently set) writes a plain percentage (or
off) to ~/.local/state/battery-limit/threshold, the same
~/.local/state/<feature>/ convention caffeine-toggle.sh/
notification-mode.sh already use. battery-limit-watch.sh is the
exact same udevadm monitor --udev --subsystem-match=power_supply
pattern battery-warning-dismiss.service already runs in production
(not a polling loop) â reacts to real BAT1/ACAD change uevents,
fires one notify-send nudge the first time capacity crosses the
saved threshold while actually charging, and clears its own
.notified flag on unplug (or if capacity drops back below the
threshold) so the next real charge session nudges again instead of
staying silent forever after the first nudge. New
battery-limit-watch.service (systemd/.config/systemd/user/), added
to install.shâs enable list alongside battery-warning-dismiss.service.
Verified live, not just read: battery-limit-picker.pyâs actual
set_limit() was exercised directly (not just the wofi menu shell) â
confirmed via makoctl history -j that both real notifications fired
with the right icon/text (âBattery reminder set to 70%â and âBattery
reminder offâ), and that the state fileâs contents matched exactly.
The watcherâs threshold/dedup branching was verified against five
real scenarios (crossed for the first time, crossed again with the
flag already set, dropped back below, unplugged, reminder disabled) â
all five behaved exactly as designed. The service itself was started
live on this real machine (currently discharging, ACAD/online = 0):
confirmed it correctly stays silent and creates no notified-flag in
that real state, which is the one live end-to-end negative-path check
actually available without AC connected in this session â the
positive (AC-plugged, crosses threshold) path was verified through the
branching test above rather than a live charger connection.
2026-09-15 (docs site: channel toggle â real segmented control, thumb-alignment bug, spacing)
Follow-up to the toggle shipped in v1.7.0 itself, after actually looking
at it live: a screenshot of the deployed page showed the Stable/Nightly
buttons rendering as plain default system buttons â light fill, black
border, no rounded pill â not the styled segmented control the CSS
described at all. Root cause: .channel-btn never set appearance:
none/-webkit-appearance: none, so Chromium on this Linux/GTK setup kept
painting its own native button bezel regardless of border: none â
setting border doesnât fully detach a <button> from its native widget
rendering path by itself. Confirmed fixed against the real deployed page,
not just reasoned about.
While actually looking at it, three more real issues, each confirmed against the live page before and after:
Inactive tab had no shape at all â border: none plus
background: transparent meant the non-active tab was just floating
text next to an actual button, not two visually equal controls. Both
tabs now get the same border: 1px solid var(--surface0) and a faint
background at rest; only the color of that border/fill changes
between active/inactive (the active one fades to transparent since
the sliding .channel-thumb underneath already supplies the mauve
fill), so shape/size stay identical regardless of which tab is
selected.
Thumb double-counted the container padding â .channel-thumb had
left: 0.3rem in CSS and assets/channel.js added
translateX(btn.offsetLeft) on top of it, but offsetLeft already
measures from the containerâs padding edge and so already includes
that same 0.3rem. Net effect: the thumb landed ~0.3rem further right
than the real button in both states â overlapping into Nightlyâs
space when Stable (the smaller offsetLeft) was active, and leaving a
matching gap before Nightlyâs own left edge when Nightly was active.
Exactly the asymmetric behavior it visibly showed. Fixed by dropping
the CSS left to 0 and letting the JS-measured offsetLeft be the
single source of truth for horizontal position.
Caption glued to the code block above it â .channel-desc had a
leftover margin: -0.5rem 0 1.5rem (negative top margin) pulling it up
against .install-snippetâs bottom border with no visible gap. Changed
to a positive 0.85rem top margin.
Also widened the gap between the two tabs themselves (0.35rem ->
0.65rem) for clearer separation now that both read as real, equally-
weighted buttons instead of one pill next to bare text.
Both install commands re-verified against whatâs actually live: the
raw.githubusercontent.com/.../main/install.sh URL both buttons point
to returns 200 and matches this repoâs own main install.sh byte for
byte, and its stable/nightly $1 case statement (case-insensitive)
maps to main/development exactly as the toggleâs two commands
assume.
v1.7.0 â 2026-09-15
Cut from development to main/Stable per docs/VERSIONING.md. Everything
from here down to v1.6.3 â the full v1.7.0-nightly scope (battery-
warning-dismiss/lid-timeout-poweroff, the notification glass-effect rework,
the caffeine keybind, the GitHub update-checker with its Stable/Nightly
install.sh prompt) plus everything development picked up afterward:
the icon theme switch to candy-icons, the tmux status bar rebuilt as real
filled pills with a measured-contrast color pass, the Terracotta Clay
theme unification across tmux/kitty/p10k, the full nvim MERN/DevOps/GenAI
LazyVim rework with a real Catppuccin transparency fix, and several docs-
site layout fixes. Live-tested before the cut: every shell script (bash
-n) and Python script (py_compile) in the repo, sway -C config check,
systemd-analyze verify plus live active/success status on the new
battery-warning-dismiss.service and update-check.service units, and a
clean tmux/headless-nvim config load â no failures.
2026-09-15 (tmux: status bar filled in â real pills, design/color-psychology pass)
Asked directly to put real background color behind each tmux status module and make the bar more aesthetic, with actual design/color-psychology thinking behind it, not just recoloring text on the transparent bar the way every tmux pass before this one had.
Why it was âhollowâ and not just unfilled: the separator glyphs
(@sep_left/@sep_right) were U+E0B7/U+E0B5, the Powerline Extra
Symbols âhalf circle, THIN/outlineâ pair â literally an outline ring
shape. Setting bg= on the content between two outline rings was never
going to look like a filled pill regardless of what color went there;
the caps themselves had to change. Swapped to U+E0B6/U+E0B4 (the
âTHICK/filledâ pair, same left/right orientation) â confirmed both
exist in this terminalâs actual font (JetBrainsMono Nerd Font,
kitty/.config/kitty/kitty.conf) via a charset-coverage check first,
same discipline as every other glyph fix in this repo. A filled
half-circle cap plus a matching solid fill in between now reads as one
continuous rounded pill.
The fill: each moduleâs content gets bg=<role color>,fg=#1E1E2E
(Catppuccin Mochaâs own Base â this repoâs established dark-ink-on-a-
filled-pill convention from an earlier tmux redesign, revived here), the
caps stay fg=<role color> on the default/transparent bg so they taper
the ends instead of squaring them off, and an explicit bg=default
right after each right cap resets the fill before the inter-module gap
â without it the trailing space would have inherited the fill color
too. Contrast checked, not assumed: computed WCAG relative-luminance
contrast for #1E1E2E against all six fills â lowest (Violet) 6.03:1,
highest (Amber) 9.83:1, every one clears the 4.5:1 AA minimum with real
margin, most clear the stricter 7:1 AAA bar.
Color psychology pass on the existing six-hue Tailwind-400 palette (re-examined for role-fit, not re-picked from scratch): Blue for session reads as identity/stability; Violet for the active window signals focus/importance without tipping into an alarm color; Emerald for inactive windows is the calmest hue in the set, fitting ânot where your attention is right nowâ; Amber for docker borrows the universal machinery/infrastructure-alert association (the same reason hazard lighting defaults to amber); Rose for the date is warm and human, distinct from the âsystem statusâ hues around it; Fuchsia for time is deliberately the most visually assertive hue, since itâs also the module glanced at most often. All six stay at Tailwindâs â400â weight â one consistent lightness/chroma step so they read as one family, and bright enough to hold the contrast numbers above on a solid fill rather than just glow acceptably as text-only color the way the previous design used them.
tmux/.tmux/scripts/docker_status.shâs own comment updated to match â
still plain text with no embedded #[fg=...] escapes, the fill is
applied once, centrally, by the wrapping module in tmux.conf.
Verified live, not just parsed without error: tmux source-file reloaded
clean, then grim-captured a real screenshot of this exact sessionâs own
status bar â all three pill types (session, window-list, status-right)
render as solid rounded fills with dark, legible text, distinct from
each other and from the bar behind them.
2026-09-15 (icon theme: Papirus removed, replaced system-wide with candy-icons)
Asked directly to swap the icon theme for Candy, dark, and remove Papirus
entirely. Checked both real AUR candidates before picking one rather than
assuming âCandyâ meant one specific package: candy-icons-git
(github.com/EliverLara/candy-icons, 16 AUR votes, the well-known one) and
neo-candy-icons-git (a smaller fork, 1 vote). Neither ships a genuine
-Dark variant the way Papirus shipped Papirus-Dark/Papirus-Light â
candy-icons is a single theme whose index.theme already sets
FollowsColorScheme=true and inherits breeze-dark first, i.e. its own
dark-compatible mode is just using it as-is. Confirmed with the user and
went with plain candy-icons-git, no Sweet-folders pairing.
Packages: candy-icons-git installed (needed sudo/a real TTY for the
makepkg/pacman -U step, handed back to the user to run directly since
this session canât answer an interactive sudo password prompt). Removed
both papirus-icon-theme and papirus-folders-catppuccin-git â
papirus-foldersâs Catppuccin recoloring had written custom files
directly into /usr/share/icons/Papirus/, so pacman correctly refused to
delete that directory on removal (it no longer fully owned the contents);
cleaned up the orphaned directory by hand afterward. packages/pacman.txt
and packages/aur.txt updated to match.
The real work was the ~15 scripts hardcoding absolute Papirus icon
paths for notify-send -i (mako has no GTK-style theme resolution or
inheritance â see mako/.config/mako/configâs own long-standing comment
on this â so every one of these uses a real file path, not a theme
name). candy-icons turned out to have real coverage gaps Papirus didnât:
no dialog-error/dialog-warning/dialog-information icons anywhere in
the theme, no weather icons, no caffeine-cup icon, no display-brightness,
no audio-input-microphone, no bluetooth-active (all confirmed with
find, not assumed). Same discipline as every previous icon fix in this
repo: nothing swapped in blind.
dialog-*/weather icons: no candy-icons equivalent exists at all, so these now fall back toAdwaitaLegacy(adwaita-icon-theme-legacy, already installed) â the old full-color, non-symbolic Adwaita icon set, real PNG fills rather than modern Adwaitaâs symbolic-onlyfill:currentColorset (which would have reintroduced the exact near-invisible-on-dark-background bug this repo already fixed once for Papirusâs own symbolic icons). Confirmed present at 48x48 before using any of them. Affects:update-apply.sh,update-check.sh,music-search.py,keylock-toggle.sh,keybind-search.py,notification-history.py,fetch_wallpaper.sh,wf-recorder-toggle.sh,bluetooth-picker.py,docker-picker.py,screenshot.sh,lid-timeout-poweroff,theme-toggle.shâs light/dark notification icon.- Icons candy-icons does ship under the same name (
docker-desktop,org.kde.plasma.clipboard,preferences-desktop-wallpaper,preferences-system-notifications,notification-disabled, the fouraudio-volume-*tiers,battery-caution-symbolic) carried straight over, same names, just under<theme>/status|apps|preferences/scalable/instead of Papirusâs fixed-size48x48/32x32dirs â all confirmedgrep -c currentColoris 0 before reuse, same as originally. - No real equivalent, swapped for the closest real concept (matching
this repoâs own established pattern for exactly this situation):
display-brightnessâpreferences-desktop-display(brightness_osd.sh); caffeine-cup âpreferences-desktop-screensaver(caffeine-toggle.sh â the toggleâs actual mechanism is stopping/starting swayidle, so this is a legitimate match, not arbitrary);audio-input-microphoneâmicrophone-sensitivity-high(wf-recorder-toggle.sh);bluetooth-activeânetwork-bluetooth-activated(bluetooth-picker.py).
mako/.config/mako/config: icon-path updated to
candy-icons:AdwaitaLegacy:Adwaita (was Papirus-Dark:Papirus:Adwaita),
comment rewritten to document candy-iconsâ actual gaps instead of
Papirusâs near-empty -Dark variant.
install.sh: dconf write .../icon-theme now writes candy-icons.
The entire Papirus-folder-recoloring block (sudo papirus-folders -C
cat-mocha-mauve --theme Papirus-Dark / cat-latte-mauve/Papirus-Light)
removed outright â candy-icons has no per-palette folder-color tool, and
without the -Dark/-Light split thereâs nothing left to recolor twice.
theme-toggle.sh: apply_icon_theme now writes candy-icons in both
the light and dark branches (was Papirus-Dark/Papirus-Light) â still
goes through the same installed-check safety path, it just no longer
actually changes anything between the two, since one theme now covers
both palettes.
gtk/.config/gtk-{3,4}.0/settings.ini: gtk-icon-theme-name ->
candy-icons.
Applied live on this machine (dconf/gsettings already reflect
candy-icons from install.shâs dconf write, re-run manually since this
wasnât a fresh install). docs/ARCHITECTURE.mdâs icon-pack section
rewritten to describe the current candy-icons setup and point to this
entry for the migration; the fileâs other, older Papirus mentions
(the nwg-bar power-menu icon provenance, the original notification-icon
investigation) are left as accurate history of decisions made at the
time, not live configuration.
2026-09-14 (tmux: time module recolored, cyan to fuchsia)
Sixth same-day tmux revision. Asked to change the time color with no
target given â rather than guess, offered three considered options
(fuchsia, a neutral white/silver, or teal) and let the choice be made
directly. Fuchsia-400 (#E879F9) picked.
Cyan sat only ~28 degrees from sessionâs Blue on the hue wheel â close enough to read as nearly the same color at a glance in a small font. Fuchsia is the one hue in the current six-color set that doesnât neighbor anything else already in use (Blue/Violet/Emerald/Amber/Rose each keep their own lane), so it reads as a genuinely distinct signal rather than a near-repeat of sessionâs color.
Verified live in the actual tmux tab.
2026-09-14 (tmux: icons throughout, tighter date format â fifth same-day pass)
Asked directly to bring more design sense to the bar rather than tweak another color. The actual gap: session and docker were the only modules with an icon at all â date and time were bare text, which read as unfinished/inconsistent next to everything else, no matter how good the colors were.
Added a calendar icon to date, a clock icon to time â confirmed both
actually render in this exact font before using them (rendered
candidates at 60pt in a real kitty window first): F133 (calendar) and
F017 (clock-o) both render cleanly; F64F (fa-window-restore, considered
for the window list) came back as an empty box in this font and was
dropped rather than shipped unchecked. Same icon-then-double-space
convention every other icon-bearing module here already uses.
Date trimmed from %A %B %d to %a %b %d (Mon Sep 14 instead of
Monday September 14) â the fully spelled-out weekday+month was the
single widest, heaviest module in the whole bar, sitting right next to a
compact %I:%M %p time; same information, abbreviated, now actually
balances against its neighbor instead of visually dominating it.
Verified live in the actual tmux tab.
2026-09-14 (tmux: Tailwind-400 palette, one considered shift instead of six ad-hoc hues)
Fourth same-day tmux revision, asked directly to make the color choices more aesthetic. The six colors so far (session Blue, active-window Purple, inactive-window Green, docker Amber, date Rose, time Cyan) were each individually pulled from Tailwindâs exact hex spec at its â500â weight â never actually chosen as one family. Two concrete problems with that: inconsistent luminance made some read duller than others next to each other, and the two warm hues (Amber, Rose) sit directly on this desktopâs own warm-orange wallpaper, hurting their contrast specifically here (a cool palette holds up against a warm background; warm-on-warm doesnât).
Shifted the whole set to Tailwindâs â400â weight uniformly rather than
re-picking hues ad hoc: blue-400 #60A5FA, violet-400 #A78BFA,
emerald-400 #34D399, amber-400 #FBBF24, rose-400 #FB7185, cyan-400
#22D3EE. Same hue identity per role as before, only the shade moved â
one deliberate lightness/chroma step applied consistently is the same
principle Catppuccinâs own accent colors use internally (one shared
formula generating the whole set, not independently chosen swatches), and
400 specifically reads as a genuine glow against a dark/transparent
background rather than the flatter âbrand logoâ register 500 is designed
for.
Verified live in the actual tmux tab, not assumed from the hex values alone â screenshotted the reloaded bar directly.
2026-09-14 (tmux: dropped the per-module fill, active window back to text-only)
Third same-day tmux revision. Two direct asks, both applied:
Removed the #11111b per-module background entirely â reported as
not looking good. Every module (session, windows, docker, date, time) and
the bar itself are bg=default again, so colored corners + text sit
straight on the real terminal background (wallpaper through kittyâs own
opacity) rather than each having its own dark fill.
Active window: Purple text, not a filled Purple pill. The previous
pass gave the active window a solid background specifically to mirror
waybarâs .focused workspace treatment â asked directly to undo that:
active and inactive windows now use the exact same hollow-cap style,
distinguished only by color (Purple active, Green inactive), not by a
different visual treatment.
Declined, with reasoning given rather than attempted anyway: a full
top+bottom box border around each pill, not just the left/right rounded
corners. tmuxâs status bar is fundamentally one character row (or a few
independent rows via status-format[n], each evaluated as its own
separate format string with no shared layout grid across rows) â thereâs
no mechanism for a âbottom borderâ row to know where a pill above it
starts and ends, and every module here is dynamically sized (date text
changes length by day name, dockerâs count changes digit-width, session
name is arbitrary). A hand-aligned box-drawing attempt would only stay
lined up until the next time any of that content changed width, then
silently drift out of alignment â the honest answer was to explain the
constraint rather than ship something that looks right once and breaks on
its own the next day.
Verified live in the actual tmux tab (not the Claude Code CLIâs own separate kitty tab, a mistake caught earlier the same day) with two real windows open so both active and inactive states were visible together.
2026-09-14 (tmux: per-module black pills, waybarâs own real pattern this time)
Second same-day tmux revision, flagged directly as more important than the
last pass (âthis something seriosâ): the previous âhollow neonâ design
filled the entire bar row with one solid slate color
(status-style bg=#0F172A), which isnât how waybar (the thing being
referenced) actually works at all â checked its own CSS again rather than
assumed: window#waybar { background-color: transparent; }, with each
individual #module carrying its own background-color and margin,
producing floating pills with real gaps between them, not one continuous
colored stripe.
status-style back to bg=default (transparent) â the bar itself is
invisible again, same as waybarâs own outer window. Every module now
wraps its own content in bg=#11111b (waybarâs real âDeep Midnightâ hex,
rgba(17,17,27,0.92) in style.css, reused directly rather than the
previous passâs #0F172A guess) and explicitly switches back to
bg=default right after its closing cap, so the space between modules
shows real wallpaper through, matching waybarâs margin gaps.
Windows now follow waybarâs actual sway/workspaces pattern, not just
its colors: every workspace there shares one dim resting style except
.focused, which alone gets visually distinct treatment. Asked directly
for the same split â inactive windows keep the hollow-outline-on-black
pill (Green text+corners), but the active window gets a solid, filled
Purple pill (#8B5CF6, dark #11111b text) using the filled half-circle
caps (E0B6/E0B4) instead of the hollow pair, so it visually pops out of the
shared resting state rather than just being a brighter version of it.
Verified with two real windows open in the actual live session (not assumed from a single-window session where active/inactive canât both be seen at once) â screenshotted both states side by side in the correct kitty tab.
2026-09-14 (tmux: hollow-pill status bar to an exact spec, replacing the earlier restyle)
The prior same-day tmux restyle (solid pills, bookend outline only on the
session/time ends) missed the actual ask entirely â given an exact,
complete design spec instead (deep slate base #0F172A, every module
âtransparentâ to it with a 1px border whose color matches its text exactly,
Nerd Font hollow half-circles standing in for CSS borders since tmux canât
draw those) and asked directly why the middle modules still had rounded
solid fills. Rebuilt the whole status bar against the spec instead of
patching the previous pass.
Couldnât read the specâs own separator characters â private-use Nerd
Font glyphs the user typed came through as invisible in the text itself
(the same issue hit mid-session on catppuccin.luaâs pre-existing icon).
Rendered the full U+E0B0-E0C1 powerline-extra range at 72pt in a real kitty
window and looked directly rather than guessing from memory: confirmed
E0B7 is the hollow LEFT half circle, E0B5 the hollow RIGHT half circle
(E0B6/E0B4 are their filled counterparts â what the old design already
used for solid caps).
Every module now shares one background (status-style bg=#0F172A) and
never switches bg= again anywhere in status-left/window-status-format/
status-right â the old designâs per-module bg= swaps are gone
entirely, since a uniform base is what makes the hollow-outline illusion
read as one consistent bar instead of six separate colored blocks.
docker_status.sh no longer embeds its own colors. Its old
#[fg=#2496ED]/dark-text escapes were tuned for sitting on a light solid
fill; under a uniform single-hue-per-module design theyâd fight the
wrapping Amber Gold instead of matching it. Icon (still the real docker
whale, not a coffee cup â reverted a wrong guess earlier the same day)
and the real running-container count are untouched, only the hardcoded
color codes were removed so the surrounding tmux.conf moduleâs one color
applies to the whole segment.
Exact hex colors from the spec, not Catppuccin equivalents: session
Sapphire #3B82F6, active window Emerald #10B981, inactive window
Amethyst #8B5CF6, docker Amber #F59E0B, date Ruby #F43F5E, time Cyan
#06B6D4 â a deliberately different palette from the rest of this desktop,
used as given rather than mapped onto Mochaâs nearest accents.
Verified against the actual live session, not just re-read after editing â
first screenshotted the wrong kitty tab entirely (this Claude Code session
runs in its own tab, tab 1 is the real âtmuxâ one) before catching it via
kitty @ ls and switching focus there for real. One known, unavoidable
side effect flagged rather than hidden: kittyâs background_opacity 0.85
applies uniformly to every pixel it renders, so #0F172A reads with a
faint wallpaper tint blended in rather than as a literally flat, fully
opaque slate â same real constraint every other âglassâ surface on this
desktop already lives with.
2026-09-14 (tmux: restyled the status bar against a mockup, cross-checked with waybar)
Shown a Gemini-generated mockup of a rounded-pill status bar and asked to
match it. Turned out to be closer to home than it first looked: this
tmux.conf already used the exact same rounded-powerline-cap technique
(@sep_left/@sep_right, U+E0B6/U+E0B4) with Catppuccin Mocha colors â
this was a restyle of existing status-left/window-status-format/
status-right format strings, not new infrastructure. Also turned out the
mockupâs own layout (session pill, window list, a docker-ish icon pill,
date, time) was modeled on this exact bar to begin with â every screenshot
taken all session already had it visible at the top of the terminal pane,
just not identified as tmuxâs own status bar until this taskâs live check
(tmux list-clients) confirmed it directly instead of assuming.
Session pill (status-left) â switched from a solid Mauve fill to an
outline/âbookendâ treatment (bg=default throughout, only the caps and
text-adjacent color stay Mauve), matching the mockupâs first and last pills
both reading as recessed rather than a bright block â same glass language
already used by kitty/mako elsewhere, not a new visual idea. Pre-existing
icon (U+F489) left untouched.
Inactive windows (window-status-format) â flat gray (#45475a) to
Lavender (#b4befe); read as âdisabledâ rather than ânot focused right
nowâ. Active windows unchanged (Green) â the active/inactive distinction
itself needs to survive a restyle, not just this passâs color choices.
Docker pill â recolored Yellow to Peach only. Directly tried swapping
its icon to a coffee cup first (misread âthe status bar⊠uses docker icon
only now the coffe iconâ as an instruction rather than the user flagging
what had just changed) â reverted immediately once corrected: icon and the
real running-container-count logic in docker_status.sh are back to
exactly what they were, this segment stays docker-and-only-docker.
Date/time â date Peach to Maroon, time stays Blue but gets the same
outline treatment as the session pill. Maroon/Blue specifically (not picked
independently) to match waybarâs own real per-purpose colors
(waybar/.config/waybar/style.css: âdate is Maroon⊠Time is Blueâ) â
checked directly rather than assumed, so the two bars agree on what each
hue means instead of coincidentally clashing. Also checked waybarâs actual
caffeine module (caffeine-status.sh) before touching anything: itâs a
plain on/off icon with no counter, so the mockupâs âx 0â wasnât a caffeine
convention to copy â itâs this repoâs own existing docker segmentâs
âicon + x + countâ format, confirming the peach pill was always the docker
segment, never a new caffeine module.
Verified live against the real, already-attached tmux session (not just
re-read after editing): tmux source-file reloaded clean, then a real
screenshot + a tight crop on the docker pill specifically confirmed the
whale icon and true docker ps count, not a leftover coffee glyph.
2026-09-14 (nvim: transparency bug #3 â floating windows have their own separate toggle)
Reported directly again, with a screenshot: the file explorer sidebar still
showed a solid box after the previous entryâs fix, wallpaper visible
everywhere else. First checked what was actually rendering it rather than
guessing â neo-tree.nvim isnât even installed (confirmed: no match in
lazy-lock.json, and grepping LazyVimâs own config/init.lua shows
explorer = { { name = "snacks", extra = "editor.snacks_explorer" } } â
Snacks.explorer() is this LazyVimâs actual default), so time wasnât spent
adding a neotree integration key that would have been a silent no-op.
Root cause: transparent_background only ever conditions Normal/NormalNC
(catppuccin/groups/editor.lua). Every floating-window surface â
NormalFloat, and anything that links to it â is gated by a separate
option, float.transparent (types.lua: CtpFloatOpts), default false.
Snacksâ own integration (groups/integrations/snacks.lua) links
SnacksPicker (what the explorer actually renders through) straight to
NormalFloat â so the first fix never touched it. Added
float = { transparent = true } alongside transparent_background in
lua/plugins/catppuccin.lua. This one option covers every floating surface
at once, not just the file tree â LSP hover/signature, noice popups,
which-keyâs popup, any picker.
(Also checked catppuccinâs separate kitty option, a documented workaround
for a real kitty quirk (kovidgoyal/kitty#2917: kitty can make a cell
transparent if its color happens to exactly match kittyâs own configured
background). Not enabled â it addresses the opposite problem, something
unintentionally going see-through, and nothing here was exhibiting that.)
Verified live, not just headless: nvim_get_hl(0, {name="NormalFloat"})
now reports no bg, and a fresh kitty window with a real Snacks.explorer()
open shows the desktop wallpaper through the sidebar, matching the dashboard.
Also traced why a different already-open nvim window (a long-running
session on an unrelated project, started 16:53 â before either transparency
fix existed) still looked solid in the screenshot: lazy.nvim resolves plugin
priority/config once at startup, so an already-running session canât pick up
a colorscheme/plugin-loading change without a restart. Not a bug, just a
stale process.
2026-09-14 (nvim: Catppuccin transparency actually root-caused â two separate lazy.nvim bugs, not one)
Reported directly: âi want the theme to be transparent and its notâ â despite
transparent_background = true already being set (see the entry below) and
kitty already running background_opacity 0.85, :hi Normal kept showing a
solid guibg=#1e1e2e. Two independent bugs stacked, confirmed one at a time
by headless introspection rather than guessed at:
Bug #1 â lazy = true losing the merge. LazyVim ships its own built-in
catppuccin spec (lazyvim/plugins/colorscheme.lua) with lazy = true and no
transparent_background option. That won over this repoâs own spec (which
only had an implied default, not an explicit lazy = false), so this
fileâs opts never reached setup() at all â confirmed by a temporary debug
write inside config() that never fired. Fixed with an explicit
lazy = false in lua/plugins/catppuccin.lua.
Bug #2 â priority ordering, found because fixing #1 alone changed nothing.
Re-tested after the lazy = false fix: the spec now correctly resolved
(lazy == false, plugin shows loaded) but :hi Normal was still solid.
Traced it to lazy.nvim loading eager (lazy = false) plugins in descending
priority order: LazyVim/LazyVim itself declares
priority = 10000 (lazyvim/plugins/init.lua), and its own config() is
what actually runs vim.cmd.colorscheme(...). This repoâs catppuccin spec
was still sitting at the default priority = 1000 â nine thousand levels
below â so LazyVimâs colorscheme command fired, sourced
colors/catppuccin.lua directly off the runtimepath, and hit catppuccinâs own
bare-defaults fallback (if not did_setup then M.setup() end) before this
repoâs real setup(opts) call ever ran. Fixed by raising this specâs
priority to 10001 â one level above LazyVimâs own â so its setup(opts)
is guaranteed to run first regardless of how LazyVim orders its own core
specs in a future update. (Caught a self-inflicted duplicate priority key
from the edit itself via the editorâs own Lua diagnostics before committing
â Lua silently takes the last value for a duplicate table key, so it
happened to still work, but it was cleaned up rather than left in.)
Verified live, not just via spec introspection: headless
nvim_get_hl(0, {name="Normal"}) now reports no bg at all (transparent)
instead of #1e1e2e, and a real kitty window opened afterward visually shows
the desktop wallpaper through the buffer â the same glass look kitty/tmux/
rmpc already have.
2026-09-14 (nvim: full MERN/DevOps/GenAI LazyVim extras, real reproducible harpoon+dadbod, detailed icons)
Follow-up to the same dayâs earlier nvim entry â asked directly for a
LazyVim setup built for âMERN + DevOps + GenAI + Next.js, and weâre
going with turborepoâ specifically, with harpoon and the dadbod trio
back (this time properly tracked, not ad-hoc installs), and more
detailed file icons. Read LazyVimâs actual extras directory
(~/.local/share/nvim/lazy/LazyVim/lua/lazyvim/plugins/extras/)
directly rather than guessing extra names or what each one does, and
checked lazyvim.jsonâs own consumption code
(lazyvim/config/init.lua) to confirm the exact format it expects
(full lazyvim.plugins.extras.* module paths) before writing anything.
16 extras enabled in lazyvim.json (this is the only config file
that changed for this â LazyVim extras are the entire mechanism, no
custom plugin specs needed):
lang.typescriptâ JS/TS/React/Next.js (vtsls by default) + real Node.js debugging (js-debug-adapter, launch/attach configs) bundled in, confirmed by reading the file directly.lang.tailwind,lang.json,lang.yaml,lang.docker,lang.terraform,lang.markdown,lang.gitâ the rest of a MERN+Next.js+monorepo+DevOps file surface (package.json/tsconfig schemas, k8s/compose YAML, Dockerfiles, Terraform, docs, commit messages).lang.sqlâ this is what actually brings vim-dadbod/ vim-dadbod-ui/vim-dadbod-completion back, properly wired into blink.cmp (confirmed reading the file: it has a real blink.cmp integration block, not just nvim-cmp) rather than as three hand-rolled specs. MongoDB itself needs a real connection string this repo will never hold â setvim.g.dbsin a project-local.lazy.luafile (LazyVimâs own documented mechanism for this, see the extraâs own doc comment), gitignored, same pattern this repo already uses for~/.zshrc.localand other real secrets.formatting.prettier,linting.eslintâ standard JS/TS tooling.dap.coreâ the actual debugger UI/keybindingslang.typescriptâs own DAP config depends on (markedoptional = truein that file, confirmed it only activates once this is present).editor.harpoon2â the real, current LazyVim-maintained way harpoon comes back, branchharpoon2(the modern rewrite, matching what was actually orphaned before, not the old v1).util.restâkulala.nvim, an in-editor HTTP/GraphQL client for hitting Express endpoints directly from a.httpfile, alongside Bruno rather than replacing it.ai.claudecodeâcoder/claudecode.nvim, chosen specifically over the many otherai.*extras (copilot, codeium, avante, tabnine, âŠ) because itâs real in-editor integration with the actual tool already in daily use for this whole setup, not a guess at which AI assistant to wire up.ui.treesitter-contextâ sticky enclosing-scope bar, genuinely useful for deeply-nested JSX/YAML, not just decorative.
Also fixes âicons arenât detailedâ, without switching icon
providers (would have undone real work from the earlier entry â
mini.icons stays): several of the extras above ship their own
mini.icons overrides for exactly the files a JS/TS project has
constantly open. Confirmed directly, before and after enabling them:
tsconfig.json went from a generic settings glyph to its own dedicated
one; same for package.json, .eslintrc.js, yarn.lock, and others.
terraform added to packages/pacman.txt â lang.terraformâs own
formatting/linting wiring shells out to the real terraform CLI (Mason
only manages terraform-ls, the LSP server, and tflint), which
wasnât installed on this machine. Not installed here yet either â
packages/*.txt are the reproducible manifest, actually running
sudo pacman -S terraform on this machine is a separate manual step,
same âflagged, not silently skippedâ treatment this repo already gives
anything needing root.
Verified thoroughly, not just written and assumed correct: ran the
actual :Lazy sync + Mason installs for real (headless, backgrounded
across a couple of retries since several packages â prettier,
eslint-lsp, terraform-ls, tflint, yaml-language-server â needed more
than one wait window to finish downloading), then confirmed all 16
Mason-managed tools report is_installed() == true and every expected
plugin appears in lazy-lock.json. Startup time re-measured with
everything active: ~53-58ms across three runs â essentially the same
as the original onedark-only baseline from before any of todayâs nvim
work (~59ms), despite now having the full language/DAP/formatting/
linting/AI stack instead of just a colorscheme. Opened a real .tsx
file in a fresh kitty window afterward and screenshotted it: rich,
correctly-scoped Catppuccin syntax highlighting on real JSX/TypeScript,
not just a solid-color placeholder.
2026-09-14 (nvim: jk-to-escape, real measured startup fix, Catppuccin to match the rest of the desktop)
Three changes, asked for together: a jk-escape insert-mode mapping, and a look at LazyVim performance/aesthetics.
jk -> <Esc> in insert mode (lua/config/keymaps.lua) â a plain
two-character vim.keymap.set("i", "jk", "<Esc>", ...). Relies on
timeoutlen (confirmed live: 300ms, LazyVimâs own default) to tell a
fast âjkâ apart from typing âjâ then pausing or continuing with
something else â only the fast combo fires it.
Colorscheme: onedark.nvim -> Catppuccin Mocha, matching every other
themed app on this desktop (waybar, mako, wofi, kitty, tmux, rmpc, sway
borders, swaylock, GTK) â nvim was the one thing left on an unrelated
palette. Not a cold install: catppuccin was already sitting in
lazy-lock.json, pulled in as a dependency of something else but never
actually activated â confirmed on disk too
(~/.local/share/nvim/lazy/catppuccin already existed). Switched via
the standard LazyVim pattern (opts.colorscheme on the LazyVim/LazyVim
spec) instead of onedarkâs own ad-hoc require("onedark").load() call,
with real integrations enabled for whatâs actually installed (blink.cmp,
noice, which-key, treesitter, mini) rather than left to fall back to
each pluginâs own default highlighting. transparent_background = true
to match kitty/tmux/rmpcâs own real transparency elsewhere.
Real, measured performance fix, not just a style swap: nvim
--startuptime showed onedarkâs ad-hoc loading path (setup() then
load() in its own config function, not LazyVimâs colorscheme
mechanism) costing ~6ms on its own â sourcing its colors/onedark.lua
file alone was the single most expensive individual line in the whole
startup log after lazy.nvimâs own bootstrap. Also removed
lua/plugins/example.lua, LazyVimâs own inert starter-template file
(guarded by if true then return {} end, never actually loaded
anything) â dead weight, not a real spec.
Running :Lazy! clean to remove the now-orphaned onedark.nvim also
swept up several other plugins that had no corresponding spec file
anywhere in this repoâs history (harpoon, vim-dadbod +
vim-dadbod-completion + vim-dadbod-ui, markdown-preview.nvim,
render-markdown.nvim, SchemaStore.nvim) â installed on disk from
some ad-hoc :Lazy install at some point, never actually part of the
tracked, reproducible config. Flagged directly rather than assumed
safe to lose silently: if any of these were genuinely still in daily
use, theyâre one lua/plugins/*.lua file away from being properly
tracked instead of living as untracked local state again.
Verified with real before/after numbers, not just theory: nvim
--startuptime, three runs each side, before averaging ~59ms, after
consistently 33-40ms â confirmed the drop holds up, not a one-off
measurement. Then opened nvim in a real kitty window and screenshotted
it: Catppuccin Mocha renders correctly, lualine picked up the new
colorscheme automatically with no extra config needed.
2026-09-14 (docs site: hero no longer lets the next section peek in on a real 1080p screen)
Reported directly on a screenshot: the very top of âWhatâs in itâ (the
Features section heading) was visible peeking up at the bottom edge on
first load, not a clean single-screen hero. Root cause: .hero-cardâs
own 72vh min-height was the only thing sizing the hero, with no
accounting for the sticky nav (.topnav-wrap, position: sticky â
real space in normal flow, not pulled out of it) sitting above it, so
the heroâs actual content had less room below it than a plain-72vh
assumption implied.
Fixed on .hero itself: min-height: calc(100vh - 6rem) (6rem is a
deliberately generous estimate of the real nav height, not an exact
measurement â overshooting just centers the card with a little extra
top/bottom breathing room, undershooting is the actual bug, so the safe
direction to round is up) plus display:flex; align-items:center so
.hero-cardâs own 72vh floor centers within whatever space that
leaves, rather than needing the two numbers to match exactly. Verified
by serving the site locally and loading a fresh tab (no interaction
needed â this is a first-load layout issue, reproducible on page load
alone): confirmed clean empty space below the card down to the actual
bottom edge, nothing from the next section visible.
2026-09-14 (docs site: Stable/Nightly channel toggle in the Install section)
Asked directly for the site itself to let a visitor pick Stable or
Nightly and hand them the matching command, rather than only offering
the one bare Stable command it had before. index.htmlâs Install
section now has a two-tab toggle (assets/channel.js, new file) above
the install snippet â clicking either tab swaps the generated command
between install.sh ... stable and install.sh ... nightly (the $1
argument added earlier today specifically so the site never has to hand
someone a command that still stops to ask again after they already
chose here) and the one-line description underneath it. The existing
copy-to-clipboard button (assets/copy.js) needed no changes at all â
it already reads the code blockâs live text at click time, not
something cached at page load, so it keeps working correctly across
channel switches for free.
Each tab shows its own real current version, fetched live from GitHubâs
tags API â Stable is the highest plain vX.Y.Z tag (identical
comparison logic to the existing hero badge in assets/version.js);
Nightly is the highest vX.Y.Z-nightly tag specifically, matched by
its own explicit pattern so an unrelated tag can never get picked by
accident. Kept as a separate script rather than folded into
version.js â that one has exactly one job (the single hero badge),
this one has a different one (two channels, two tag shapes), matching
this siteâs existing one-concern-per-file pattern. Also fixed a stale
develop mention in the Versioning doc-cardâs own description text
(missed in the earlier rename pass since this file lives outside
docs/ and wasnât covered by that grep).
Verified by actually serving the site locally (python3 -m http.server
in the repo root) and loading it in Zen: confirmed the default Stable
state renders correctly and, more importantly, that both version
badges resolve live and correctly â v1.6.3 for Stable, v1.7.0-nightly
for Nightly â proving the fetch-and-regex-match logic genuinely works
against the real GitHub API and the real tags just pushed today, not
just that it reads correctly. Couldnât get a reliable simulated mouse
click on the Nightly tab itself to land correctly in this sandboxed
session (input-simulation coordinates kept missing their target and at
one point hit the wrong window entirely) â not pursued further once
that became clear, rather than risking more stray input on a live
desktop session chasing it. Not a gap in the toggleâs own logic though:
setChannel() is one shared function, and its âstableâ call path (exact
same code the ânightlyâ branch would run, just a different key into the
same two lookup objects) is exactly what page load already exercises
and the screenshots above confirm working.
v1.7.0-nightly â 2026-09-14
Everything from here down to v1.6.3 above, on development (Nightly
channel) only â not merged to main/Stable yet. MINOR bump: several
genuinely new features, not just fixes â the battery-warning-dismiss/
lid-timeout-poweroff pair, the whole notification glass-effect rework,
a caffeine keybind, the GitHub update-checker itself (new nwg-bar
prompt, new systemd service, new scripts), and the Stable/Nightly
channel choice in install.sh â alongside real bug fixes (the
duplicate-binding config error, several margin/blur rendering bugs, two
stale-doc corrections). -nightly rather than a plain v1.7.0 tag:
this marks where development currently stands, not a cut, tagged
release â see docs/VERSIONING.mdâs âUpdate channelsâ section for why
the two branches version separately now.
2026-09-14 (develop renamed to development; install.sh asks Stable or Nightly)
Two related changes to the branch model itself, on top of yesterdayâs update-checker feature:
develop renamed to development, both locally and on GitHub â
via GitHubâs own branch-rename API (gh api -X POST .../branches/develop/rename),
not a manual delete-and-recreate: preserves the branchâs real identity
(the two historical PRs against it, both already closed/merged, still
resolve correctly) rather than just faking the same result with a new
ref at the same commit. Confirmed no branch protection and no open PRs
first, and confirmed GitHub Pages builds from main (untouched by this
either way), before touching anything. Local side: git branch -m
develop development, then git fetch origin --prune cleanly removed
the now-dangling origin/develop remote-tracking ref and picked up
origin/development, then explicitly re-pointed the local branchâs
upstream (git branch -u origin/development development) rather than
assuming the rename alone carried it over. Verified end to end: fresh
test clones of both main and development by name against the real
remote, confirmed each correctly tracks its own origin/<branch>.
install.sh now asks Stable or Nightly at first install, Stable
default â previously always cloned whatever GitHubâs default branch
was (main) with no choice offered at all. Only asked once, at first
clone (not on idempotent re-runs, where the branch is already decided).
Reads from /dev/tty explicitly rather than plain stdin: the documented
install command is bash <(curl -fsSL ...) (process substitution, real
terminal stdin already), but this way it still prompts correctly even
for curl ... | bash instead (stdin genuinely replaced), and falls
through cleanly to the Stable default rather than hanging if no
controlling terminal exists at all (fully non-interactive/CI-style
invocation) â confirmed this exact fallback path directly, not assumed:
/dev/tty doesnât exist in this sandboxed environment either, and the
prompt correctly defaulted to Stable rather than erroring out.
No separate channel-preference file anywhere â scripts/.local/bin/
update-check.sh (yesterdayâs entry) already derived everything from
whateverâs actually checked out, so it needed only a small addition:
show âStableâ/âNightlyâ in its notification instead of the raw branch
name (main -> Stable, development -> Nightly), derived the same way,
not read from anywhere new. The branch on disk already is the
persisted choice; see docs/VERSIONING.mdâs new âUpdate channelsâ
section for the full picture.
docs/VERSIONING.md and docs/ARCHITECTURE.md updated throughout to
say development instead of develop â CHANGELOG.mdâs own existing
historical entries that mention develop by name are deliberately left
exactly as they were: theyâre narrating what was actually true at the
time, not describing current reality, the same reasoning every other
entry here has always followed for not rewriting history.
2026-09-14 (new feature: check GitHub for updates on every boot, ask before applying)
Asked directly: check for updates automatically on every boot, and if any are found, ask permission before applying rather than silently auto-updating. Built entirely on tools already installed â no new package for this:
systemd/.config/systemd/user/update-check.serviceâType=oneshot,WantedBy=default.target, runs once per login (not a swayexec_always, which would re-fire on everyswaymsg reloadinstead of once per boot). Waits onnetwork-online.targetfirst so an early fetch attempt doesnât look identically âofflineâ and skip silently.scripts/.local/bin/update-check.shâ fetches, comparesHEADagainst@{u}(whichever branch is actually checked out, not a hardcoded one), and if behind, fires a notification with the real commit count and latest subject line, then opens the prompt below. Bothfetchand the apply scriptâspullare pinned tossh -o BatchMode=yes -o ConnectTimeout=8â this runs headless at login with no TTY, so an SSH agent that isnât up yet must fail fast rather than hang the whole check on a passphrase prompt nothing will ever answer (confirmed the real auth path, gpg-agentâs SSH support, already works fine non-interactively â this is a safety net, not routinely needed).nwg-bar/.config/nwg-bar/update-bar.json+update-bar-style.cssâ reusednwg-bar(already installed for the power menu) rather than inventing a new mechanism for the actual Update Now/Later prompt: same glass-card recipe, Green/Overlay0 accents instead of the power menuâs five-color severity scale since neither action here is destructive. Two new Lucide icons (download/clock) added to match the power menuâs existing icon set exactly â same source, same stroke weight, same hardcoded#CDD6F4(seenwg-bar/.config/nwg-bar/icons/README.md).scripts/.local/bin/update-apply.shâ only what âUpdate Nowâ actually runs.git pull --ff-only(refuses and notifies on any real conflict or divergence, never merges/rebases on your behalf), thenstow -Rover the same package listinstall.shitself discovers, then reloads whatever can hot-reload (swaymsg reload,makoctl reload,systemctl --user daemon-reload). Deliberately does not guess which systemd services to restart afterward â the success notification says a re-login/reboot may be needed for any unit-file changes to fully apply, rather than risking bouncing something disruptive automatically.
Also closed two real gaps found while wiring this in: install.shâs
âEnabling user servicesâ line was missing both this new service and
battery-warning-dismiss.service (from an earlier entry this session â
written and already running live, but never actually added to the
fresh-install enable list). Both added.
Verified without ever touching the real ~/dotfiles checkoutâs git
state: cloned it into a scratch directory, rolled that cloneâs HEAD
back two real commits, and ran the actual detection logic, a real git
pull --ff-only, and a real stow -R against a scratch fake $HOME â
confirmed the pull fast-forwarded correctly and the resulting symlinks
pointed exactly where the real ones do. The nwg-bar prompt and its
paired notification were fired for real and screenshotted together on
this machine. Also specifically checked update-bar.jsonâs two exec
fields against nwg-barâs own documented gotcha (docs/ARCHITECTURE.md
â no shell in its invocation chain, a bare ~ breaks silently): both
use an absolute path or a bare PATH-resolvable command, never ~.
2026-09-14 (notifications: found the real fix for margin bleed â blur_ignore_transparent, not smaller margins)
Supersedes the previous two entriesâ approach entirely. Reported again:
âthe background is overflowing from the right, make sure that the
background never overflowsâ â after widening margin back out for
breathing room, the same bleed from the original âexternal containerâ
reports was back, just smaller. Every fix up to this point had been
shrinking margin to make the bleed small enough not to notice at
normal viewing scale, which is a trade-off (less bleed always meant less
actual gap from the edge), not a real fix â and this report is exactly
that trade-off failing to hold up once margin was widened again.
Found the actual fix instead of shrinking the number a third time:
blur_ignore_transparent enable in sway/configâs layer_effects
"notifications" block. mako leaves its margin area transparent
(unfilled) in its own surface buffer, but without this flag SwayFX
blurred that transparent area anyway â producing the sharp-cornered
rectangle of blurred-but-untreated background outside the rounded card
every previous report was actually about. This tells SwayFX to skip
blurring transparent regions instead, so that area now shows the real,
untouched background straight through, with nothing rendered there at
all.
Confirmed this is unconditional, not just âbetter at this marginâ:
deliberately put margin back to its original full 10,20 (the exact
values that caused the very first âexternal containerâ report) specifically
to prove the fix isnât margin-size-dependent, then zoomed to 700% on
both the top and right edges â nothing bleeds past the rounded cardâs
own border at any margin size now. Margin restored to 10,20
accordingly; no more trade-off between breathing room and bleed.
2026-09-14 (notifications: right margin 2 -> 10, a real trade-off not a free fix)
Asked for âsome margin from the rightâ â the earlier fix for the right-edge bleed bug (margin 20 -> 2) left the toast sitting almost flush against the screen edge. Since mako bakes margin into the surfaceâs own pixel buffer (see the entry below), any nonzero value here reintroduces some of that same bleed proportionally; thereâs no value that gets both a visible gap and zero bleed. Landed on 10: still shows a faint sliver at 400% zoom (checked directly rather than assumed), but reads completely clean at actual viewing scale, confirmed via a normal unzoomed screenshot.
2026-09-14 (notifications: top-edge bleed, same margin bug; caffeine icon collided with theme icons)
Two more real reports on the same notification work:
âThe notification toasts top background goes out of top borderâ â
the identical margin-baked-into-surface bug as the earlier right-edge
âexternal containerâ fix (see that entry below), just on the top side of
the same surface. A zoomed screenshot of the real toastâs top edge
showed a flat-topped blurred rectangle sitting above the actual
rounded/bordered card â marginâs top value (10) was still baked into
the surface the same way its right value was. Dropped to 2, same fix,
same mechanism. Confirmed via swaymsg -t get_outputs: extent height
dropped from 92 to 84, exactly the 8px reduction, and a fresh screenshot
showed the rounded top border as the true top edge, still clearing the
waybar with a small gap.
âShows the icons of the dark and light modeâ â caffeine-toggle.shâs
on/off notification icons (weather clouds-night/clear-day) are visually
identical to the actual sun/moon icons theme-toggle.sh uses for its own
light/dark mode notifications, so the caffeine notification read as
âanother theme toggleâ at a glance. Papirusâs dedicated
caffeine-cup-full/empty icons looked like the obvious fix but turned out
to use fill:currentColor internally (confirmed directly, not assumed
from the âpanelâ category the way this repo usually shorthands
âsymbolic = currentColorâ) â the same near-invisible-on-dark-background
problem already hit and documented elsewhere in this repo. Switched to
apps/caffeine.svg, the real Caffeine appâs own icon (confirmed no
currentColor) â no real-fill on/off pair exists, so both states share
one icon, and the notificationâs own title text (âCaffeine onâ/âCaffeine
offâ) is what actually distinguishes them.
2026-09-14 (notifications: max-visible=1 â multiple stacked toasts canât each get an independent glass card)
Same âremove that external containerâ report as the entry below, but
this time with 5 notifications stacked instead of 1 â and this time the
actual cause was architectural, not another color/margin value to tune.
Confirmed directly via swaymsg -t get_outputs: 3 simultaneously-visible
notifications reported as a single 276px-tall layer-shell surface
(3x92), not three separate ones. mako renders every currently-visible
notification as rows inside one shared surface â SwayFXâs
corner_radius/blur apply to that whole surface, with no way to round
or glass each row independently, since the compositor has no concept of
makoâs internal row boundaries. Only the true outer edge of the shared
panel ever gets the glass treatment, no matter what.
Fixed by sidestepping the shared-surface case entirely: max-visible=1
means at most one row ever exists in that surface, so it always IS the
single glass card. Confirmed queuing still works correctly rather than
dropping notifications (makoctl list -j after firing three in a row
showed all three retained). Also had to suppress makoâs own â(N more)â
overflow placeholder ([hidden] criteria, invisible=1) â without
that, the placeholder becomes a permanent second row the moment
anything queues behind the first, recreating the identical shared-panel
problem one row later. Verified live: three rapid notifications now show
as one clean independent card at a time, no placeholder, no shared
container.
2026-09-14 (notifications: root-caused âthe external containerâ â mako bakes its margin into the surface)
Reported directly on an annotated screenshot of the real toast: a visible sharp-cornered rectangle surrounding the rounded/bordered card itself, distinct from it â âremove the external containerâ. Several of this sessionâs own remote reproductions had actually been catching the terminal windowâs own focused border by mistake (same corner_radius, overlapping screen position) before this got isolated cleanly on an empty workspace with no other windows at all.
Root cause, confirmed via IPC rather than guessed at colors again:
swaymsg -t get_outputs while a real notification was up reported this
layer surfaceâs own extent as 440px wide â 20px wider than the
configured width=420, exactly matching the old margin=10,20,0,0
right value. mako bakes its margin into the surfaceâs own pixel buffer
rather than requesting true empty space outside a tightly-sized surface
via the layer-shell protocolâs own margin fields. SwayFXâs
corner_radius/blur operate on that full rectangular surface, so they
were rounding and blurring a 440-wide rectangle while makoâs own
rounded, tinted, bordered card only actually draws across the left 420
of it â the other 20px sat there as a plain sharp-cornered strip
showing raw blurred background with none of the cardâs own treatment.
That strip was the reported âexternal containerâ.
Fixed by dropping the right margin to 2 (confirmed the reported extent
drop to 422 matches proportionally). Also dropped blur_xray â ruled
out by direct isolation testing as this bugâs cause, and not worth
keeping regardless since plain blur (blurring whateverâs genuinely
behind the notification) is more honest for a popup that can appear over
any window than blur_xrayâs always-blur-the-wallpaper behavior, which
wofi specifically wants but a notification doesnât need. Verified live
at every step â extent measurements before/after, clean reloads, and
screenshots on an empty workspace confirming a single glass card with no
separate container, on both single and stacked notifications.
2026-09-14 (notifications: 0.85 alpha read as opaque at this cardâs size, not glass)
Several rounds of remote guessing (pixel sampling, testing over wallpaper
vs terminal, checking for a first-frame blur artifact) hadnât found
anything wrong â confirmed there was, in fact, a real problem, by asking
for and getting an annotated, zoomed (392%) screenshot of the actual live
toast: âDark backgroundâ labeled the flat card body, âglass like
backgroundâ labeled specifically the rounded top-right corner. The blur
was only visibly doing anything right at that cornerâs edge antialiasing
â the bulk of the rectangular body read as a plain opaque dark box, not
glass, despite layer_effects "notifications" genuinely having blur
enabled the whole time (confirmed earlier via swaymsg -t get_outputs).
Root cause, once an actual screenshot made it visible: wofiâs own 0.85
alpha (copied exactly in the previous entry, deliberately not guessed)
works there because wofiâs window covers a large fraction of the screen.
This notification card is roughly 1/10th that area â the same 0.85
alpha leaves far too little of the compositor blur visible against that
much smaller surface for it to read as anything but solid. Dropped
background-color to 0x59 (~0.35 alpha) so blur actually dominates the
fill across the whole card. Verified live over both the raw wallpaper
and the dark terminal, confirming visible blur/depth across the entire
body (not just one corner) with text still legible in both.
2026-09-14 (notifications: one glass card, not glass-behind-glass)
Rejected directly, on sight, the moment it was live: âthis is exactly
what i dont want⊠make that toast glass like not the background of the
whole toast.â The previous entryâs mistake, in hindsight: it copied
wofiâs window+#input duality literally, but that duality exists in
wofi because the input field is a real, separate interactive widget
sitting on top of the window â a toastâs text isnât a second widget,
itâs just the toastâs own content. Layering a second, separately-tinted
panel behind the text on top of an already-glass card gave two
translucent layers competing on one small surface, which read as messy,
not glass.
Dropped the <span background=...> text-panel wrapper entirely,
reverting mako/configâs format= back to plain %s/%b. Left
everything else from the previous entry untouched â layer_effects
"notifications"âs blur/blur_xray, and background-color/border-color at
wofiâs own 0.85/0.55 alpha â since the card itself being glass was
never the part that was wrong, only the extra layer on top of it.
Verified live: makoctl reload clean, grim screenshot of two real
stacked toasts confirming one unified glass surface with text sitting
directly on it, no second panel.
2026-09-14 (notifications: replicate wofiâs actual glass recipe instead of guessing)
Pointed directly at wofi/style.css as the reference after two rounds of
guessing hadnât landed: âthat glass like background and text combination
is good and iconic, i would love to replicate that.â Read it as ground
truth instead of eyeballing more screenshots â wofiâs glass is three
concrete, already-tuned numbers, not a vibe:
- Real compositor blur (
blur/blur_xrayvialayer_effects). - Window background at 0.85 alpha â wofiâs own history records trying 0.62 first and finding blurred-wallpaper brightness washed out text, landing on 0.85 as the tuned fix.
- A second panel (
#input) layered on top at Surface0/0.45 alpha, not a flat solid box â the actual source of the âtext combinationâ look.
Applied the same three numbers to mako, not new ones: layer_effects
"notifications" gets blur/blur_xray back (shadows still
deliberately left off â separate real finding, see the entry below: two
stacked toasts blend their shadows into one shared halo, which wofi
never hits showing only one window at a time); makoâs
background-color/border-color moved to the exact same 0.85/0.55
alpha as wofiâs window/border; the text-panel span from the previous
entry recolored from an arbitrary Lavender tint to Surface0 at wofiâs
own 0.45, matching its #input by color, not just by being translucent.
Verified live at every step: both swaymsg reload and makoctl reload
clean, and grim screenshots of two real stacked toasts over both the
terminal and Zen, confirmed against wofi/style.cssâs literal values
rather than judged by eye alone.
2026-09-14 (notifications: glass belongs behind the text, not the whole card; dropped the shared shadow halo)
Two more real problems reported live after the previous blur revert (see the entry below), fixed properly rather than reverted further:
âWhy do we have a translucent container behind all the toastsâ â
SwayFXâs shadow effect on the notifications layer surface blends between
two stacked toasts (mako has no between-notification gap setting), so
their individual soft shadows merge into what reads as one shared dark
halo behind the whole stack rather than two separately-shadowed cards.
Dropped shadows enable from layer_effects "notifications" in
sway/config, kept corner_radius alone â still real rounded corners,
no shared halo between stacked toasts.
âToast text background is solid, not glass-likeâ â the actual
original ask, clarified: glass behind the text specifically, with the
card itself staying a plain solid color. The compositor blur tried
earlier made the whole card translucent instead, the opposite of this.
makoâs format is real Pango markup (markup=1, confirmed in man
mako(5)âs FORMAT SPECIFIERS section) â wrapped %s/%b in
<span background=...> to give the text its own tinted backing distinct
from background-color above. Real bug found getting there: Pangoâs own
documented bgalpha= span attribute breaks makoâs config parser outright
here â confirmed directly, makoctl reload fails with âUnable to parse
configuration fileâ with it present, succeeds without it, on an
otherwise-identical line. Worked around with an 8-digit RGBA hex
straight in background= instead (#CDD6F42E/#CDD6F41A, Lavender at
~18%/~10%) â confirmed this both parses cleanly AND actually renders
translucent via grim screenshots, not just accepted syntax that renders
opaque.
Verified live at every step, not just via swaymsg -t get_outputsâs
effects flags: both swaymsg reload and makoctl reload clean, and
grim screenshots of two real stacked toasts showing a clean edge between
them and a visible lighter chip behind each line of text, not the whole
card.
2026-09-14 (fix: caffeine keybind was unsearchable in keybind-search.py)
Reported: searching for the caffeine keybind in $mod+Shift+slashâs
picker returned nothing recognizable. Root cause: keybind-search.py
only ever takes the single comment line immediately above a bindsym as
its description (documented in its own module docstring, an existing
convention this whole file already follows for every other binding) â
the line directly above this one was the tail end of the
duplicate-binding bug post-mortem from the entry below
(âfree â checked against both literal binds and every $left/$down/$up/â),
never meant as a description at all. Added an actual one-line
description immediately before the bindsym; verified by running
parse_sway() directly and confirming the real parsed entry, not just
assuming the fix would work from reading the script.
2026-09-14 (fix: notification âglassâ made the whole toast see-through, not just tinted)
Reported directly, with a screenshot, as real visual damage: the
compositor blur added in the notifications entry below made makoâs
entire toast translucent, showing the blurred wallpaper straight through
the whole card behind the text â not a subtle glass tint, a literal
window into the desktop where a solid notification card should be.
Verified live with real screenshots (grim, not just swaymsg -t
get_outputsâs effects flags) over both the terminal and Zen before and
after to confirm the actual visual difference, not just the IPC state.
Root cause: makoâs config format has no way to give only the text/icon a
glass panel while keeping the cardâs own background opaque â no
per-region backgrounds, no image layers, unlike wofiâs real GTK CSS
(which achieves its glass look with an actual layered sheen gradient).
blur/blur_xray in sway/configâs layer_effects "notifications"
block apply to the entire layer surface, so thereâs no way to scope
that pair to just a portion of the toast within makoâs plain
flat-color config. Removed both, keeping corner_radius/shadows (a
real, uncontroversial improvement, confirmed live) and reverting
mako/configâs background-color from 0xd9 back to the original 0xfa
opacity â needed once nothing is blurring what would otherwise show
raw and unblurred through a more transparent value.
2026-09-14 (fix: caffeine keybindâs own reload broke sway with âThere are errors in your config fileâ)
The $mod+Shift+k caffeine binding added earlier today (see the
notifications/caffeine entry below) turned out to break swayâs own
config on reload â the red swaynag error bar, reported directly with a
screenshot. Root-caused by bisection rather than reading the message
(swaynagâs actual detailed text isnât recoverable after the fact â itâs
piped to swaynagâs stdin once and never logged anywhere else, confirmed
checking both journalctl and swayâs own stderr log with nothing
there): $up is set to "k" up top (the vim-style h/j/k/l direction
variables), and bindsym $mod+Shift+$up move up further down the file
resolves to that exact same combination after variable substitution â
sway treats that as a genuine duplicate binding and throws the same
error swaynag for it as any other config problem. A literal grep for
+k never would have caught this (the collision only exists after
variable expansion); confirmed the real cause by testing $mod+Shift+j
next (same class of collision, with $down/move down) before landing on
$mod+Shift+a, checked against every one of $left/$down/$up/$rightâs
expansions this time, not just a literal-text grep. Verified live:
killed the stale swaynag, reloaded, confirmed a fresh one doesnât
respawn.
2026-09-14 (lid-timeout-poweroff: root-caused why it never actually fired; idle-lock bumped to 15 minutes)
Reported: the laptop died from battery drain overnight instead of cleanly
powering off from the lid-closed-30-minutes hook. Root-caused rather than
re-tuning the timeout blind â journalctl -b -1 on the affected boot
shows a real suspend requested/PM: suspend entry (deep) sequence with
nothing after it: no lid-timeout-poweroff logger line (the hookâs own
post/suspend path always writes one, dry-run or real, per its own
design), no clean systemctl poweroff, nothing â the bootâs log just
stops, meaning the machine sat suspended until the battery physically ran
out, not until this hookâs 30-minute timer fired.
Checked whether the hook itself was ever actually installed rather than
assuming the logic was at fault: confirmed
/etc/lid-timeout-poweroff.conf exists on this machine (its TIMEOUT_SECONDS=1800
matches the repoâs copy exactly) but
/etc/systemd/system-sleep/lid-timeout-poweroff â the actual executable
systemd-sleep hook, the one file that makes any of this run at all â
does not exist. The configâs reference copy made it into place at some
point; the script itself never did. Nothing in install.sh or
docs/ARCHITECTURE.md covers this step either â root-owned
/etc/systemd/system-sleep/* hooks are outside anything install.sh
touches (same as HandleLidSwitch=suspend in /etc/systemd/logind.conf),
so this was never going to install itself; it needed one manual step that
was simply never done. That step, not yet run on this machine:
sudo install -Dm755 ~/dotfiles/systemd/lid-timeout-poweroff /etc/systemd/system-sleep/lid-timeout-poweroff
Recommend setting DRY_RUN=1 in /etc/lid-timeout-poweroff.conf first and
closing the lid once to confirm the full close -> wait -> resume ->
detect-still-closed -> notify pipeline actually fires (the config fileâs
own comments describe exactly this safe-test workflow) before trusting it
for real with DRY_RUN=0. Couldnât verify the RTC wake alarm itself
end-to-end from here â rtcwake//dev/rtc0 both need root, confirmed
present but not exercised.
Idle-lock timeout: 5 minutes -> 15. Asked to confirm screen-lock
correctly engages 15 minutes after real inactivity (with caffeine off)
without misfiring during long video playback.
sway/.config/sway/idle/configâs lock timeout was 300s; bumped to 900s,
with the dpms-off and suspend timeouts after it shifted by the same
relative gaps they already had (10/30 -> 20/40 min) rather than left
sitting ahead of the new lock time. Verified the video-safety concern
live instead of assuming the fullscreen inhibit_idle rule alone covers
it: sway-audio-idle-inhibit.service (a separate, always-running
service, independent of caffeine mode) holds a real wayland idle-inhibit
lock for as long as anything is outputting or receiving audio â its own
journal logged IDLE INHIBITED live during this check â which is what
actually protects a windowed (non-fullscreen) video with sound; the
fullscreen inhibit_idle rule in sway/config is the separate layer for
fullscreen specifically. Only real gap in either layer: a muted video in
a non-fullscreen window, inherent to how both detection mechanisms work,
not something the timeout value itself changes.
2026-09-14 (notifications: real glass effect, per-urgency accent; caffeine keybinding; Zen dark-mode fix)
Asked for the desktopâs UI/UX and battery notifications to get a real pass. Several separate, independently-verified fixes:
Notification toasts actually get the glass treatment now. makoâs own
layer-shell surface (namespace notifications, confirmed live via
swaymsg -t get_outputs while a real toast was on screen â mako only holds
one at all while somethingâs actually showing) had no layer_effects block
in sway/config, unlike wofiâs. mako/configâs background-color alpha
(0xfa, ~98%) had nothing real to blur behind it as a result â the exact gap
wofi/style.cssâs own history already diagnosed once. Added
layer_effects "notifications" { blur enable; blur_xray enable;
corner_radius 12; shadows enable }, and dropped the alpha to 0xd9 (~85%),
the same value wofi already settled on for this same base color. Verified
live: effects on the real layer surface now reports
blur=True, blur_xray=True, shadows=True, corner_radius=12 where it was
all off/0 before.
Added a [urgency=low] section (Surface2 gray border) so low-urgency
toasts read as visually quieter than normal ones at a glance, the same
âdim gray = quiet/offâ language waybar/sway already use elsewhere
(caffeine.inactive, bluetooth.disabled) rather than reusing the Mauve
accent for everything regardless of urgency.
batsignal notifications had no icon at all â confirmed no default in
its own source, -I was simply never passed. Added
-I battery-caution-symbolic (freedesktop icon-naming-spec name,
confirmed present under Papirus/makoâs icon-path) to
batsignal.serviceâs ExecStart. Also audited every other notify-send call
across this whole repo â every one of them already passes a real icon
except lid-timeout-poweroffâs dry-run notification, which was missing one
too; fixed the same way (dialog-warning).
Caffeine mode gets a keyboard path. Was click-only via waybarâs
custom/caffeine module until now. $mod+Shift+k (free, unused elsewhere
in sway/config) runs caffeine-toggle.sh directly â same shape as
$mod+Shift+q/kill, an instant action with no picker window, not a
toggle-popup.sh binding.
Zen Browser âdoesnât follow dark/light mode unlike Chromeâ â two separate, unrelated bugs, not one:
- Browser chrome (toolbar/UI) stuck dark regardless of system theme: about:addons has the static âDarkâ theme add-on active instead of âSystem theme â autoâ (confirmed in extensions.json) â a manual theme-extension pick overrides the portal signal entirely, no config file controls this, only the Themes panel does. Needs a one-time manual fix in Zen: Settings -> Manage Themes -> enable âSystem theme â autoâ, disable âDarkâ.
- Web content (sitesâ own
prefers-color-schemeCSS) stuck light regardless of system/browser theme:user.jsâs ownwidget.use-xdg-desktop-portal.settingsfix (see the 2026-08-30 entry further down / ARCHITECTURE.mdâs âTheme toggle robustnessâ section) only ever covered Zenâs own chrome, never this. Foundlayout.css.prefers-color-scheme.content-overridesitting at0(âalways lightâ) in prefs.js with no matching line in user.js and no mention anywhere in this repo â a stray about:config value, not a documented choice. Pinned to2(âfollow browser themeâ) in user.js so it canât drift back silently. (Zenâs profile lives outside this repo,~/.config/zen, not stow-managed â same as/etc/lid-timeout-poweroff.confbelow, this entry is the only record of why.)
2026-09-13 (battery: dismiss stuck warnings on AC restore; auto-poweroff if the lid stays closed)
Two related battery/power-lifecycle fixes, committed together since both touch batsignal/suspend behavior:
Stuck low-battery notification on AC restore. Reported: plugging in
during an active low-battery warning left the toast on screen, needing a
manual dismiss. batsignalâs own -p flag was tried first and reverted â
confirmed directly in its source (main.c) that -p is one shared toggle
gating both charging and discharging announcements together, so it also
fired an unwanted âDischarging: 40%â toast on every unplug regardless of
level. The real fix adds zero notifications of its own instead:
battery-warning-dismiss.service watches udevadm monitor for the kernelâs
own power_supply change uevent on ACAD, checks
/sys/class/power_supply/ACAD/online directly (same interface batsignal
itself polls, no upower dependency), and silently makoctl dismisses any
currently-shown batsignal toast the moment AC actually comes back. Verified
running live (systemctl --user status â active, enabled).
Auto-poweroff after 30 minutes of the lid staying closed. logind
already suspends on lid close (HandleLidSwitch=suspend); this hook
(/etc/systemd/system-sleep/lid-timeout-poweroff, root-owned, NOT
stow-managed â system-sleep hooks only run from /etc, the repo copy
here is reference-only) rides on top rather than replacing it. On the
pre suspend hook it arms a real hardware RTC wake alarm
(rtcwake -m no -s 1800) â a plain suspend halts the CPU, so only a
genuine RTC interrupt can wake truly-suspended hardware with nobody
touching it. On post (resume, for any reason) it checks whether the lid
is still physically closed; if so, nothing woke it on purpose, so it
powers off for real. If the lidâs open, a person woke it and the pending
alarm is simply superseded. DRY_RUN=1 in the config swaps the real
poweroff for a notification so the whole close/wait/resume/detect
pipeline can be verified without losing a session.
2026-09-07 (mako: wider notification toasts, less square)
Reported: notification popups read as more square than the configâs own
350x150 numbers suggested. Root cause, confirmed via man mako(5):
width is a fixed value, but height is a documented maximum â
ânotifications whose text takes up less space are shrunk to fitâ. Most
of this desktopâs own notifications are short (1-2 lines), so height
was already shrinking well below its old 150px cap while width sat
rigidly at 350 regardless â boxier in practice than the raw numbers
alone imply, especially with a 48px icon eating into the narrower
content column next to it. Widened to 420, and lowered the height cap
to 130 (still generous for a wrapped 3-4 line body) so even the
longest real notifications stay a clear horizontal toast shape, not
just the short common-case ones.
v1.6.3 â 2026-09-07
python-mutagen got installed, clearing the blocker every prior entry on
this feature had to work around. Immediately surfaced one more real
bug: addyt failed with âAccess to local files via TCP is not
allowedâ â a real MPD security restriction (local-filesystem add
outside music_directory is only trusted over a Unix socket, never TCP,
even to 127.0.0.1). Fixed with a one-line change to rmpcâs own config
(address: "127.0.0.1:6600" -> the Unix socket MPDâs systemd unit
already exposes at /run/user/1000/mpd/socket, confirmed already
listening, no MPD-side config change needed). Verified for real this
time: ran the actual keybinding code path end-to-end and confirmed via
rmpc queue/status that a real new song was added, played, and
(being a short test clip) correctly auto-advanced afterward â not
just an exit code, an actual result read back. This closes the âknown
gapâ v1.6.0 through v1.6.2 each had to leave open. PATCH bump: another
fix to a real, confirmed defect.
v1.6.2 â 2026-09-07
Root-caused the wrong-song bug reported against v1.6.1âs rmpc
integration: rmpc addyt exits 0 even when the download genuinely
fails (confirmed directly in a log, not inferred), so the old
if add.returncode != 0: return check could never fire, and
rmpc play 0 ran unconditionally every time, playing whatever was
already at queue position 0 â exactly the reported symptom.
play_music() now snapshots the real queue before and after addyt
and only plays if exactly one new file actually appears, using its
real reported position rather than assuming 0. Also added persistent
logging (~/.local/state/music-search/music-search.log, rotated) for
music-search.py â every subprocess command, exit code, and output,
truncated sensibly so verbose commands like yt-dlpâs search dump donât
crowd out the useful entries, plus any unhandled exceptionâs full
traceback, so bugs like this one are diagnosable from the log alone
next time. Along the way, found makoâs [urgency=high] has never
matched anything real (the spec only defines low/normal/critical) â
every error notificationâs ânever auto-dismissâ treatment was silently
inert since it was added; fixed to [urgency=critical], verified via
makoctl list. PATCH bump: fixes to real, confirmed defects.
v1.6.1 â 2026-09-07
v1.6.0âs YouTube play redesign got immediate hands-on use and turned up
three real bugs verification hadnât caught: audio mode played through a
headless, windowless mpv process with no way to stop/skip/control it at
all (fixed by routing music through rmpcâs own addyt + opening rmpc,
so every existing rmpc/MPD control just works); video modeâs mpv window
never received keyboard focus, so every mpv keybind silently went
nowhere (fixed with focus on its for_window rule, re-verified this
time by actually sending a keypress and reading mpvâs own IPC state
back, not just confirming the window existed); and the search felt
broken for ~2-3s between pressing Enter and results appearing, with
nothing on screen to say it was working (fixed with an immediate
âSearchingâŠâ notification). Also removed the persisted audio/video
toggle in favor of two direct keybindings (Mod+Shift+Y video,
Mod+Ctrl+Shift+Y music) â reported simpler than remembering which mode
was last set. See docs/ARCHITECTURE.md for the full detail on each.
One real gap not glossed over: the music path needs python-mutagen
(added to packages/pacman.txt), not yet installed on this machine, so
its success path is verified only up to the point that dependency
blocks it. PATCH bump: these are fixes to real defects in what v1.6.0
shipped, not new capability.
v1.6.0 â 2026-09-07
Two direct fixes/additions on the desktop side: waybarâs clock/date pill had an intermittently-vanishing left corner (a negative-margin GTK box-overlap bug, fixed by dropping the margin to 0); and Mod+Shift+Yâs YouTube search now plays the picked result immediately via mpv instead of downloading first, with a persisted toggle (Mod+Ctrl+Y) between audio-only and real video, and the original download behavior preserved under its own key (Mod+Shift+Ctrl+Y). See the two dated entries below for the full detail on each. MINOR bump: the play+toggle capability is genuinely new, not just a fix.
2026-09-07 (waybar: fixed the clock/date pillâs intermittently-vanishing left corner)
Reported bug: the time+date cluster in the center of the bar would sometimes have its left corner just not render, then look normal again after a while â not constant, not reproducible on demand.
Root cause: #clock (the date pill) had margin-left: -8px, deliberately
pulling it left of its own allocated box to sit flush against clock#time
(the plain-text time to its left) so the pair would read as one unit. A
negative margin means GTK paints outside the moduleâs own allocated
rectangle â and #clock lives in modules-center, which waybar
re-centers on the bar every time modules-left/modules-rightâs total
width changes. network#speed right there in modules-left already has
its own comment admitting its text width âchanges digit count constantlyâ
(12KB vs 1.2MB vs 999B) â so the center group was being re-laid-out
continuously, and the negatively-margined region only reliably repainted
on some of those passes, not all. Thatâs exactly âsometimes doesnât
appear, then becomes normalâ instead of a constant glitch.
Fixed by dropping the margin to 0 instead of -8px â still tighter
than the shared 4px every other pill gets, but never painting outside
#clockâs own box, so thereâs nothing left for a relayout to intermittently
clip. Restarted waybar for real to apply it (config/CSS edits donât
hot-reload â confirmed again, same as the network#speed investigation
already noted elsewhere in this file); came back up clean, no CSS parse
warnings, bar configured normally. Visual confirmation of the fix itself
(whether the corner still ever vanishes) is still pending â that part
needs eyes on the actual screen, not just a clean process restart.
2026-09-07 (music-search.py: play by default, mpv, audio/video toggle)
Mod+Shift+Yâs Enter used to download an mp3 into ~/Music before you could
hear or see anything â changed so it plays the result immediately
instead, via mpv (already installed, v0.41.0, ships its own yt-dlp hook
so a bare mpv <youtube-url> resolves and plays with no extra plumbing â
confirmed live, not assumed). Chose mpv over reusing MPD/rmpc
specifically because MPDâs whole design center is a curated persistent
library, the wrong tool for âstream this one URL right now, donât add it
to anythingâ.
Whether Enter plays audio-only or a real video window is controlled by a
new persisted toggle, media-play-mode.sh (Mod+Ctrl+Y) â same
state-file pattern as notification-mode.sh/theme-toggle.sh. The
active mode is echoed straight into the wofi search prompt itself
(âSearch YouTube (audio)âŠâ / â(video)âŠâ) so itâs never a guess which
one is live. The original download-to-library behavior wasnât removed,
just moved off the default action: --download (now Mod+Shift+Ctrl+Y)
reuses the same search/pick code and does exactly what this script
always did.
A real bug caught during verification: mpv/yt-dlpâs own âbestâ format selection picked a 4K stream with zero regard for whether this machine could actually decode it smoothly (dropped-frame count climbed continuously in a live test) â video mode now explicitly caps at 1080p instead of trusting the untouched default.
Verified end-to-end through the actual sway keybindingâs real code path,
not just raw mpv calls in isolation: wtype simulated real keystrokes
into the live wofi popups (typing a search query, picking a result) for
both audio and video modes and for --download, and mpvâs own JSON IPC
socket confirmed real playback progress (time-pos actually advancing),
not just a process staying alive. Video modeâs window was confirmed via
swaymsg -t get_tree mid-playback: real app_id mpv, floating,
correctly sized and centered per the new for_window rule. See
docs/ARCHITECTURE.mdâs own entry on this for the full detail. MINOR
bump: a real new capability (play + a toggle that didnât exist before),
not just a fix.
v1.5.1 â 2026-09-04
Fixed two real flash bugs, each with a specific, identified cause rather
than a general polish pass: background-attachment: fixed was forcing a
full repaint of the bodyâs (expensive, multi-gradient + noise-textured)
background on every scroll position change, visible as a white flash on
the Features anchor jump; and the carouselâs transition animated filter
alongside transform/opacity, and unlike those two, filter isnât
compositor-only, so it forced a real repaint every frame. Also added
scroll-behavior: smooth for anchor links, img.decode() gating before
carousel autoplay starts, and the Cross-Document View Transitions API for
genuine cross-page navigation fades. PATCH bump: fixing real, reported
rendering bugs, not new capability.
v1.5.0 â 2026-09-04
Four direct fixes/additions: the screenshot carousel felt rushed (slower
dwell, gentler transition curve); clicking âFeaturesâ in the nav scrolled
past the sectionâs own title, hidden under the sticky nav (fixed with
scroll-margin-top, applies to any future anchor-linked section too); a
live version badge next to the hero title, fetched from GitHubâs tags API
at page load rather than hand-typed (real semver comparison, not just
trusting API array order); and the wind-emoji brand icon dropped for a
text-only wordmark (echoing the H1âs own serif+gradient treatment), with a
real bug fixed along the way â it was inheriting the site-wide link-hover
underline. MINOR bump: the version badge is genuine new capability, not
just a fix.
v1.4.0 â 2026-09-04
A full glassmorphism redesign of the hero, matching a shared reference mockupâs style (confirmed as a Gemini-generated AI image first, and deliberately skipped the parts of its description that read as AI-mockup filler unrelated to an actual website â a fake OS status bar, a made-up app name, gold/brass bars â rather than implementing every literal detail). Page-wide dark charcoal-to-amethyst gradient background with a subtle grain texture; hero content now lives inside a real glass card (background blur, inner highlight, deep shadow, ambient glow); wordmark switched to a serif font with a purple-to-silver gradient; buttons got real glow and a genuine frosted-glass ghost button; the screenshot gallery rebuilt as an actual layered 3D card-deck instead of a flat single-image swap. MINOR bump: a real new visual capability (the glass card system, the card-deck carousel), not just a bug fix.
v1.3.2 â 2026-09-04
Direct follow-up: the scroll-pin from v1.3.1 felt sluggish, and tying page
scroll to the screenshot gallery was the wrong idea in the first place â
âlet the scroll behave normally⊠if a user want to see the screenshots
then he can just do something there.â Removed .hero-zone/position:
sticky entirely; page scroll is now completely untouched by the hero.
assets/carousel.js rewritten as a fully self-contained autoplay carousel
(3.2s timer, pauses on hover, real clickable dots) with no scroll listener
at all. Added a soft ambient gradient glow behind the hero content and a
subtle fade+rise entrance animation for a grander first impression, per
direct request. PATCH bump: fixing a UX regression just shipped, not new
capability.
v1.3.1 â 2026-09-04
Fixed two real bugs in the hero (copy button drifting on horizontal code scroll, the page visibly scrolling while images changed underneath it) and redesigned the layout to match direct feedback â a real scroll-pin carousel instead of a 3D wheel, eyebrow repositioned, install snippet de-duplicated, two more screenshots. PATCH bump: fixes to real, reported UX bugs plus refinement of an existing feature, not a new capability.
2026-09-04 (hero redesign: scroll-pinned slide carousel, real layout fixes)
Direct feedback on the hero: the copy button drifted toward the middle on horizontal code-block scroll, the 3D image wheel made the page visibly scroll down while images changed underneath it (âbad UX⊠the page should stay putâ), images crowded the text column, the eyebrow line competed with the title for top billing, and the install snippet was redundant with the one already in the #install section below.
Copy button fix: itâs an absolutely-positioned child of <pre>, and a
scrolling <pre> (overflow-x: auto) drags its own absolutely-positioned
children along with the scrolled content. Moved overflow-x to the inner
<code> element instead, in both assets/style.css and assets/docs.css
â <pre> itself stays static now, so the button never moves regardless of
how far the code scrolls.
Real scroll-pin, not scroll-jacking: .hero-zone (350vh) wraps the
actual hero, which is position: sticky â while the user scrolls through
that extra height, the hero visually stays in place, and
assets/carousel.js (rewritten) maps scroll progress within the zone to
slide+fade transitions between the 5 screenshots via CSS class changes only
(.active/.prev). Never calls scrollTo/preventDefault â the page
keeps scrolling completely normally the whole time, this only ever reads
scrollY. Once scrolled past the zone, the sticky pin releases on its own
(plain CSS behavior, no JS needed for that part) and the page continues
into Features. Disabled entirely below 860px â mobile viewport height
isnât stable enough (address-bar collapse changes 100vh mid-scroll on
most mobile browsers) for a pin effect to be reliable; falls back to plain
static flow showing just the first screenshot, via the same
scrollableRange<=0 guard already in the carousel math rather than a
separate mobile code path.
Layout: eyebrow moved from above the title to below the tagline (no
longer fighting the title for attention), the heroâs own install-snippet
code block removed entirely (kept only in #install below, no duplication),
CTA row pinned to the bottom of the text column via margin-top: auto.
Structure top-to-bottom now matches what was asked for directly: title
(bigger) -> tagline -> eyebrow -> CTA row, images on the right.
Hero-text/hero-image gap increased (3.5rem -> 5.5rem) and the image
columnâs flex-basis reduced (480px -> 400px) for real breathing room.
Two more screenshots (keybind-searchâs live fuzzy list, the emoji
picker) join the existing three for a 5-image gallery, each with its own
slide-dot indicator. Tried notification-history.py first but it was
empty on this machine (nothing to show) â swapped for keybind-search
since itâs guaranteed non-empty, real, colorful content instead of an
empty list.
Verified live the way every other site change this session has been:
pushed, waited for a real build, then pulled the actual served HTML/CSS
and confirmed the DOM structure (image order, eyebrowâs new position
relative to the tagline and CTA row, the install-snippetâs removal from
the hero, all 5 images and the fixed copy-button CSS deployed correctly).
The visual feel of the scroll-pin itself isnât something curl can verify
â confirmed everything the tooling here can check (structure, CSS, JS
syntax, live deployment), but the actual in-browser scroll experience is
worth checking directly.
v1.3.0 â 2026-09-02
A new Keybindings page (generated from the same live config parsing the desktopâs own keybind-search tool uses), a real thumbnail-quality fix in music-search.py, a hero redesign (multi-screenshot scroll-driven rotation, a much shorter tagline, cleaner header and feature grid), copy-to-clipboard buttons site-wide, and a deep, multi-round fix for the CHANGELOG/ Architecture pagesâ rendering â what looked like one bug turned out to be several, ending in a genuinely reusable lesson about how kramdown handles angle-bracket text differently from GitHubâs own renderer. MINOR bump: real new capability (the keybindings page, the hero redesign, copy buttons) plus real, previously-invisible rendering bugs fixed.
2026-09-02 (site polish: keybindings page, real bugs found and fixed, hero redesign)
A batch of direct site feedback: fix the CHANGELOG pageâs real bugs, add a proper keybindings page with its own header entry, fix a pixelated thumbnail in the music player, add copy buttons to code blocks, redesign the hero with multiple screenshots in a scroll-driven rotating wheel, cut the tagline down to one line, redesign the header (no bottom border) and the feature grid.
docs/KEYBINDINGS.md: new page, every Sway and Tmux keybinding, generated
from the same live parsing scripts/.local/bin/keybind-search.py already
does (real config lines + their own comments) rather than hand-typed
separately. Wired into the header nav and homepage doc-card grid on both
index.html and the shared _layouts/docs.html.
music-search.py: thumbnail pixelation fixed â mqdefault.jpg (320x180)
swapped for hqdefault.jpg (480x360), confirmed reliably available the same
fixed-URL way the original was verified, a real (measured, not assumed)
quality-vs-latency tradeoff documented in the code.
Hero redesign: tagline cut from three sentences to one line, per direct
feedback (âsmall to the point and sweetâ). Two new screenshots (wofi
launcher, power menu â both safe to capture directly, no window content
exposed) join the existing desktop shot in a real CSS 3D âwheelâ
(assets/carousel.js) that rotates with scroll position, pure scroll-read,
never scroll-jacks. Header switched to a floating pill nav with no hard
border-bottom (same rounded-module language waybar already uses on the
real desktop). Feature grid got a corner-glow accent, hover lift, and a
small icon badge per card instead of a plain left-border stripe.
Copy-to-clipboard button (assets/copy.js) on every code block
site-wide â one script, works on the homepageâs install snippets and every
doc pageâs real fenced code blocks alike.
The CHANGELOG pageâs âreal bugsâ turned out to be much bigger than they first looked, and took several genuine wrong turns to fully resolve â worth recording in full since each one taught something different about how kramdown actually behaves, not just what the fix was:
- A duplicate h1 heading (the layout rendered its own
page.titleand the contentâs own# Titleline rendered a second one) â straightforward, fixed by removing the layoutâs synthetic heading. - Fenced bash code blocks rendering as literal backtick text â
markdown: kramdown/kramdown: {input: GFM}added to_config.yml(never explicitly set once the built-in Jekyll theme, which used to provide this implicitly, was replaced by the custom layout). - That config fix wasnât sufficient alone â kramdown also requires blank- line separation between a fence and surrounding paragraph text to recognize it as a block at all (GitHubâs own renderer is more lenient, which is why this was invisible reading the docs natively on GitHub). Fixed with a script that inserts blank lines around every fence â a first, buggier version of that script didnât track fence open/close state and inserted blank lines inside fences instead; caught via spot-check before it shipped, reverted, redone correctly.
- Still 8 fences broken after that â 3 more existed, indented 2 spaces as continuation content inside bullet list items, which the âstarts with backticksâ detection never matched (required column 0). Dedented all three to top-level fences rather than fight kramdownâs exact nested- list-continuation indentation rules.
- The actual root cause of the page-wide cascading failure, found only
after all of the above still left entire sections rendering as
completely raw, unprocessed markdown (not just fences â bold, inline
code, and headings too): a literal angle-bracket-wrapped placeholder
inside backticks (
on-notify=exec paplayfollowed by the word âfileâ wrapped in angle brackets), a notation this repoâs prose uses throughout for âfill in your own valueâ (the words name, pid, id, etc., each the same way). Backticks do not protect against this the way they protect against Liquid tags â kramdownâs HTML-tag detection appears to run before code-span parsing fully isolates the content, sees the opening angle bracket as what looks like the start of a real tag, finds no matching closing tag anywhere in the rest of the document, and stops treating anything after that point as markdown at all. Confirmed by binary-searching the actual built HTML for the exact transition point between correctly-rendered and literal output, not guessed. - Fixed by removing the angle brackets from every genuine placeholder (just the bare word âfileâ instead of wrapping it) rather than entity- encoding them â tried HTML entities first, which avoided the cascade but produced a new, different cosmetic bug: kramdownâs code-span rendering correctly escapes its own content, including a literal ampersand, so the entity code itself became visible literal text in the browser instead of rendering back to a bracket character.
- A handful of these werenât generic placeholders at all â a real Rust
generic type reference (
Propertyâs song-typed variant) and rmpcâs own angle-bracket RON keybind notation for its Escape and Ctrl-C entries (documented directly from its config.ron) lost real meaning when flattened the same way. Tried backslash-escaping the brackets next, which avoided the cascade but hit a third cosmetic bug: CommonMark spec says backslash escapes arenât processed inside code spans, so the literal backslash character itself showed up in the rendered page. Settled on plain English instead (âits Esc key entryâ, âthe Song-typed Property::as_string variantâ) â no bracket notation left to fight kramdownâs parsing order over at all. - Also caught, separately: the earlier fixâs regex only ever matched opening-tag-shaped patterns, so a quoted example of a real built pageâs title tag (opening and closing) had its opening half stripped while the closing half was left dangling, unmatched, in the text â the exact same class of problem as the original bug, just self-inflicted this time. Reworded rather than re-escaped.
Every one of these eight was verified against the real, live-built HTML after each fix â not the source markdown, not assumed correct from the config â specifically because step 5âs whole discovery only happened by checking real output and finding it silently wrong days after step 2-4âs fixes had already shipped and looked fine from a cursory glance.
v1.2.4 â 2026-09-01
Direct feedback pointed at the live Architecture page: âoptimize this ui and make it more asthetic. instead of putting it in the table find other way to better structure this page instead.â The âPackage mapâ table really was a bad fit for its own content, not just visually plain â 20 rows ranging from a 6-character description to one over 2,400 characters, all forced into the same fixed table columns.
Replaced it with a responsive card grid + native details for the five
genuinely long entries, entirely inside docs/ARCHITECTURE.md itself (one
source of truth, GitHub-native view benefits too) â parsed the original
table with a small script rather than retype ~1,800 words by hand, verified
no content was corrupted or dropped before trusting it.
Added a real custom layout (_layouts/docs.html, assets/docs.css,
assets/docs.js) instead of the built-in Jekyll theme: a sidebar table of
contents built from the pageâs own real h2 elements at view time (not a
hand-maintained list to drift out of sync with a 58-section document), with
scroll-based active-section highlighting, and the same Catppuccin Mocha
palette already used on the homepage. Applied via _config.ymlâs
defaults: key (scoped per file path) rather than front matter in the
source files â the same GitHub-native-rendering constraint hit once already
for README.md applies to CHANGELOG.md/docs/*.md too (all linked
directly from README.md), and defaults: assigns a layout/title with zero
change to the source file itself.
Considered a more complex transclusion-based alternative first (wrapper
pages + include_relative), built and started testing it, then discarded it
once defaults: was confirmed to solve the same problem with no new files
and no indirection.
Verified live: pushed straight to main specifically to get a real Pages
build to test against, polled for a real "status":"built", then pulled the
actual built HTML for all three doc pages and grepped it directly â correct
per-file titles, the new layout/cards present in the real output, all 20
package cards accounted for, and markdown processing inside the details
blocks confirmed via real code/em tags in the output, not literal
markdown characters.
v1.2.3 â 2026-09-01
Corrected a real inaccuracy in the previous releaseâs own documentation,
found via the siteâs own live verification: docs/ARCHITECTURE.md claimed
README.md was reachable as a Jekyll-rendered page on the docs site â
README.html actually 404âd. Tried the standard fix and reverted it after
checking GitHubâs own README renderer first (it renders unstripped front
matter as literal horizontal rules on the actual repo page). No content
change to README.md itself (net zero diff from v1.2.2). PATCH bump:
correcting a wrong claim in already-published documentation.
v1.2.2 â 2026-09-01
Renamed the repository to Vayu (after the Hindu god of wind â Sway tiles/moves windows the same way air moves) and replaced the docs siteâs homepage with a real, custom, dark hero-layout page (name/tagline/install on the left, a real desktop screenshot on the right) instead of the previous stacked README-as-homepage layout, with the rest of the site switched to a dark Jekyll theme to match. PATCH bump: identity/presentation and repo- completeness work, not a change to how the desktop itself behaves.
2026-09-01 (renamed to Vayu, and a real custom dark hero-layout site)
Direct ask: rename the repo to something from Hindu mythology/history, make the docs site dark instead of light, and redesign it around a specific pattern â name/headline on the left, a desktop image on the right, not the previous stacked README-as-homepage layout.
Renamed arch_sway_setup -> vayu (gh repo rename), after Vayu, the
Hindu god of wind â Sway tiles/arranges windows the same way air moves, and
âswayâ itself means exactly that. Updated the local origin remote to the
new URL and confirmed a fetch actually works through it (not just assumed
the rename propagated). Propagated the new name everywhere it was actually
referenced: install.shâs own curl one-liner and REPO_URL, and every
github.com/raw.githubusercontent.com link in README.md â grepped the
whole repo for the old name first to find all of them, not guessed. Set the
repoâs GitHub description and homepage URL to match.
Replaced the stacked README-as-homepage site with a real custom
index.html (repo root, no Jekyll front matter, so Pages serves it
untouched instead of wrapping it in a theme layout) â a proper hero section
(name + tagline + install snippet + CTAs on the left, a real desktop
screenshot on the right), a feature grid, and links out to the full docs.
Dark by default, using this desktopâs own Catppuccin Mocha palette directly
(the same colors already in rmpcâs own theme, waybar/style.css, etc.) so
the site reads as part of the same thing it documents rather than a generic
theme wrapper. Switched the Jekyll theme used for the other pages
(CHANGELOG.html, docs/ARCHITECTURE.html, docs/VERSIONING.html) from
jekyll-theme-cayman (light) to jekyll-theme-slate (one of GitHub Pagesâ
own built-in dark themes) so the whole site is consistently dark, not just
the new homepage.
The desktop screenshot needed real care, not just a grim call.
Checked the actual window tree first and found the real session had a
browser window with visible content on another workspace and a live tmux
pane on the currently-focused one â a floating rmpc window doesnât cover
the full screen, so screenshotting it directly would have left tmux visible
in the margins. Switched to a genuinely empty workspace, moved rmpc there
via the scratchpad (confirmed via swaymsg -t get_tree that it was the
only thing on that workspace before capturing), then restored the
original workspace/window state afterward. Resized/compressed the result
(1920x1080 PNG -> 1600x900 JPEG, ~230KB) for a reasonable page weight.
Verified live the same way as every other change this session: pushed,
waited for the real Jekyll build to report "status":"built" (not just a
successful API response), then curlâd the actual homepage and every linked
doc page for real HTTP 200s and real content, not assumed correct from the
config alone â caught one real miss this way: README.html 404âd (Jekyll
skips files with no front matter, same rule used deliberately for
index.html, just biting somewhere unwanted this time). Tried the standard
fix (empty front matter) and reverted it after checking GitHubâs own README
renderer first â it doesnât strip front matter, and rendered two literal
horizontal rules at the top of the actual repo page. Left README.md as a
plain passthrough instead; nothing on the site links to a local copy of it
anyway. Full reasoning in docs/ARCHITECTURE.md.
v1.2.1 â 2026-09-01
A real public GitHub Pages docs site (README.md as the homepage, CHANGELOG.md and docs/*.md reachable from the same build), with README.mdâs own staleness around mpd/rmpc fixed first since it was about to become that homepage. PATCH bump: a documentation/repo-completeness gap fixed, not a change to how the desktop itself behaves.
2026-09-01 (a real public docs site, and README.md fixing itself along the way)
Asked directly for a GitHub Pages site with an installation guide and detailed
docs. Confirmed it genuinely didnât exist yet (gh api repos/.../pages -> 404),
not assumed.
Checked README.md before building on top of it, since it was about to become
the siteâs homepage â found it stale (no mpd/rmpc anywhere in the Desktop
Stack table, Repository Structure tree, or the manual stow example, despite
this sessionâs whole termusic-replacement). Fixed all three.
No separate docs/index.md written â Pages/Jekyll uses README.md as the
homepage automatically with no index.md present, and README.md was already a
complete install guide once the stale parts were fixed; a second site-only copy
would just be one more thing to keep in sync forever. CHANGELOG.md and
docs/*.md are reachable from the same build since Pages is sourced from the
repo root, not a /docs subfolder (CHANGELOG.md lives at the root, scoping to
/docs alone would have left it unreachable).
Added one file, _config.yml (repo root): theme: jekyll-theme-cayman, a
GitHub-Pages-supported built-in theme (no Gemfile, no extra build step,
confirmed via pages.github.com/themes) plus an exclude: list for every real
dotfile package directory so Jekyll only processes actual documentation.
Deliberately skipped taking a live screenshot for the READMEâs existing placeholder, even with the tooling right there â checked what was actually on screen first and found a real browser window with a personal email address in its title and a live ngrok tunnel URL, part of the userâs real session. A public Pages screenshot is a published, hard-to-fully-reverse artifact; left the existing honest placeholder rather than risk it unprompted.
Enabled Pages via the API once the content existed on main (source: main,
path /), then verified the actual build succeeded and the site was reachable
via a direct curl, not just a successful API response.
v1.2.0 â 2026-09-01
rmpcâs theme went from a flat, two-color mapping to a richly-colored, deliberately
waybar-matched palette across three passes (playback header and toggles;
everything still left plain â browser rows, table headers, preview metadata, tab
bar; then a request for a border-style selection instead of a filled background,
which surfaced and fixed a real bug where a malformed RON modifiers string
silently dropped rmpcâs entire config back to built-in defaults). Also fixed a
real, reproducible-on-every-boot bug: swayidle.service staying dead after a
fresh reboot because systemdâs own auto-start exhausts its restart budget before
the repoâs own ordering fix gets a chance to run. MINOR bump: real new capability
(the richer theme) plus real fixes to things that were actually broken, found via
direct source-reading and a genuine post-reboot stability sweep, not assumed
correct from the config alone.
2026-09-01 (swayidle: fixed a real âdead on every bootâ bug, found during a stability sweep)
Asked directly to check whole-system stability before cutting a release. A real
reboot had happened since the last check, which mattered: found
swayidle.service inactive (dead) on a completely fresh boot, not leftover
state. The existing idle-management entry already flagged this exact gap (âno
real reboot to verify againstâ) â the reboot found what the simulation missed.
Root cause: systemdâs own auto-start (WantedBy=default.target) races
dbus-update-activation-environment and fails 4 times in ~7 seconds
(WAYLAND_DISPLAY not set yet) â enough to hit systemdâs default
start-limit-burst before swayidle-startup.sh (the script sway/config
already execs later specifically to fix this ordering) ever runs. A plain
systemctl --user restart against a unit already in start-limit-hit is a
silent no-op â no error anywhere.
Fixed with systemctl --user reset-failed swayidle.service added to the top of
swayidle-startup.sh. Confirmed the theory in isolation first (restart alone:
no effect; reset-failed + restart: active in under a second), then verified the
real fixed script in both its own branches (caffeine-on/off) live.
sway-audio-idle-inhibit.service has its own real crash history
(coredumpctl, several SIGABRTs on separate days) â confirmed currently
healthy since this boot, not chased further since it isnât reproducing now.
2026-09-01 (rmpc theme, round three: no bg fill on selection, plus a real silent-failure bug found)
Direct follow-up: âthe active item should not get the background colorf but instead have a rounded colord border. and that quee directories artists row elements should be colored each differentlly right also same goes for that normal text as well.â Pulled rmpcâs own Rust source this time rather than guess whatâs actually possible.
No per-item border capability exists in rmpc at all (confirmed from
source: current_item_style is a plain ratatui Style, fg/bg/
modifiers only, applied to a whole ListItem/Row â no border
concept for individual rows anywhere, only around whole panes). Closest
honest substitute: dropped bg entirely, current_item_style is now
fg: "#cba6f7" with Bold | Underlined instead of a solid fill.
Found a real, serious bug building that: first wrote the modifiers as
"Bold, Underlined" (comma-separated) â valid RON, wrong for how
Modifiers actually deserializes (rmpc uses bitflags v2, whose serde
support expects its own Flag | Flag format). This didnât just fail
that one field â it silently dropped the entire config back to
built-in defaults, no error logged anywhere. Caught by comparing
rmpc debuginfo (correctly resolved every path standalone) against the
actual running instanceâs own log, whose Resolved config showed
cache_dir: None and every color as a plain ANSI name instead of any
real hex value. Fixed with the format the crate actually expects.
Also: traced song_table_formatâs real row-rendering path and
confirmed (unlike the browser tabs) it genuinely applies per-column
style to actual row content, not just headers â so Queue/Playlists/
Search table rows are now colored per column throughout, not only in
the header. And found a real, unfixable-via-config limit for the
browser tabs (Directories/Artists/Album Artists/Albums): their song
rows go through a different, style-blind render path
(the Song-typed Property::as_string variant discards style entirely,
confirmed in the function body) â only the one-character type marker
per row is a real color lever there; left the inert per-field theme
values in place with a comment explaining why rather than deleting
them.
Verified live, including hitting the actual bug live before finding its cause: relaunched, confirmed the full-default fallback via the log comparison, fixed the modifiers syntax, relaunched again, confirmed real hex values back in the resolved config, screenshotted the Queue tab with a real song actually queued, and pixel-matched all four column colors â exact RGB matches. Ruled out a leftover background block on the selected row via row-by-row pixel distribution, not a single broad scan.
2026-09-01 (rmpcâs theme, round two: colored everything still left plain)
Immediate follow-up: âcanât we add some more colors to this tui and
make it more colorfull like our status bar?â The first theming pass
only touched the playback header and toggle cluster â the file
browser, table column headers, preview metadata, tab bar, scrollbar,
and elapsed/bitrate line were all still plain default text. Went
through every remaining unstyled field, reusing the same waybar-matched
hues plus the two waybar colors nothing had used yet (Blue #89b4fa =
waybarâs #clock.time, Rosewater #f5e0dc = waybarâs wallpaper
button):
- Browser row markers (
symbols.song_style/dir_style/playlist_style, allNonebefore): Sky, Blue, Rosewater. - Browser row text (Artist/Title, previously unstyled): Lavender and Sky, deliberately matching the playback headerâs own colors for the same fields.
- Table column headers (Artist/Title/Album/Duration, previously unstyled): Lavender/Sky/Rosewater/Peach â header row only, actual song rows stayed mostly neutral so a long table stays scannable.
tab_bar.inactive_style(previously empty): waybarâs own off-gray#6c7086â doubles as a real legibility fix, not just decoration.preview_metadata_group_style(was identical Yellow to its own label): Blue, now genuinely distinct from the label.elapsed_and_bitrate(had zero color): Green#a6e3a1.scrollbar.ends_style(previously empty): same off-gray as inactive tabs.
Verified the same way as before: relaunched, zero theme-parse errors,
screenshotted and pixel-matched. dir_styleâs Blue only shows on the
Directories tab (Queue is the default), so switched tabs with a real
simulated keypress before screenshotting rather than treating a
zero-match result as evidence of a bug. Every color checked came back
an exact RGB match.
2026-09-01 (rmpcâs theme: recolored by role, matched to waybarâs own palette)
Direct feedback: âcan we improve the theming of the music player here i
mean use some colors like our status bar and make it more asthetic.â
The original theme was a literal, uniform string substitution (every
âblueâ -> the same Mauve, every âyellowâ -> the same Yellow, everywhere)
â technically colored but nearly monochrome, Mauve+Yellow doing almost
every job. Read waybar/style.css first to see how it actually assigns
color (a different hue per module: Sapphire/dark-mode, Sky/wifi, Peach/
capslock, Teal/numlock, Maroon/pulseaudio, Lavender/notifications, a
literal #6c7086 for every off-state), then recolored rmpcâs theme by
UI role to match, not at random:
- Song title (previously no color at all): Sky
#89dceb. - Artist (was identical Yellow to the state badge above it): Lavender
#b4befe. - Volume number/%: Maroon
#eba0acâ literally waybarâs own#pulseaudiocolor, the most direct possible match. - Repeat/Random/Consume/Single toggles (all four previously shared one
color pair): Teal/Peach/Sapphire/Lavender when active, waybarâs own
literal off-gray
#6c7086when not, instead of a dimmed Mauve. level_styles.info: Mauve -> Sapphire, freeing Mauve to stay the one âselected/activeâ accent everywhere else in the theme (unchanged:current_item_style,borders_style,tab_bar.active_style).- Every
#1e1e2e(Catppuccin Base) ->#11111bâ grepped the repo first and confirmed#11111Bis already the one shared dark surface across waybar, mako, wofi, nwg-bar, and swayâs own border scheme; rmpc was the only thing still on the lighter Base tone.
Verified live: relaunched through the real keybinding, confirmed zero parse errors in rmpcâs own log, screenshotted the running window, and pixel-matched specific coordinates against the themeâs target hex values â title/artist/volume came back exact RGB matches, zero color distance. Off-state toggles came back the right color family and position, qualitatively confirmed (too fine-grained a target for exact pixel matching on anti-aliased glyph edges).
v1.1.0 â 2026-08-31
termusic replaced entirely with mpd + rmpc (a real MPD daemon plus a proper TUI client, zero third-party API dependency left in the music stack), music-search.py gained real thumbnails in its results list, rmpcâs floating window got genuine compositor-level glass (SwayFX blur, not opacity tricks) at a larger 60%/60% size, and every popup in this desktop â rmpcâs window included now â closes on Escape. MINOR bump: new capability (mpd/rmpc replacing a whole subsystem), not just a fix to something that was broken.
2026-08-31 (every popup closes on Escape â rmpcâs window was the one real gap)
Direct feedback: âall popup modals should also hide when esc key is
pressed as well.â Audited every popup rather than guessing which ones
needed it: every wofi-based popup (toggle-popup.shâs clients) already
closed on Escape natively, and nwg-bar (power menu) did too, confirmed
live with a real synthetic Escape keypress (ydotool key 1:1 1:0) after
opening each through its real keybinding.
rmpcâs floating window was the actual gap â tested the same way and it
stayed open. rmpcâs own config.ron already binds its Esc key entry to
Close, but
thatâs an internal rmpc action (closes a modal inside rmpc), not
anything that knows about swayâs scratchpad, so Escape was a no-op with
no internal modal open.
Fixed with a new script (scripts/.local/bin/rmpc-hide.sh, just the
same swaymsg scratchpad show rmpc-toggle.shâs second press already
runs) wired in via a per-window kitty keybinding override on rmpcâs own
launch line â rebinds Escape for that one instance only, before it
reaches rmpcâs pty, kitty.confâs global map table untouched. Verified
end-to-end through the real keybinding: window opened, focused,
Escape sent, swaymsg -t get_tree confirmed visible: false, rmpc
process and mpd.service both still alive throughout, then toggled back
open once more to confirm the existing show-path still works.
Known tradeoff, not a real loss: rmpcâs own internal Close action is
also bound to its C-c key entry already, so it still has a working key, just
Ctrl+C instead of Escape for this window from now on.
2026-08-31 (rmpc: real compositor blur, not opacity tricks, plus a bigger window)
Direct, immediate follow-up to the entry right below: âlets make its background glass like blury background and increase it size follow the 60 percent width and increase the height accordinglly.â The opacity-only fix below turned out to be the wrong lever â lowering it just showed the wallpaper completely sharp through the window, which reads as âsee-throughâ, not glass.
Real fix: this machine runs SwayFX (confirmed via sway --version),
which has genuine compositor-level blur already proven for wofi
(layer_effects). Added blur enable to rmpcâs own for_window rule
in sway/config â a plain for_window-settable property for ordinary
toplevel windows (no layer_effects block needed, thatâs layer-shell-
only), deliberately without blur_xray per SwayFXâs own README
guidance for regular floating windows. The earlier per-window kitty
background_opacity=0.3 override was removed once blur made it
unnecessary â back to kittyâs global 0.85, which turns out to match
wofiâs own real working ratio (0.85 alpha + real blur) exactly.
Verified with a cleaner pixel test than the previous entryâs: found the highest-texture patch in a wallpaper-only screenshot, compared it against the same screen region through the live window â local gradient energy dropped ~92% (9.85 -> 0.83), and the rendered pixels were a smooth blended tone, not the sharp original or a flat color. Text legibility reconfirmed intact.
Also resized: window was 1152x540 (60%/50% of the 1920x1080 output),
now 1152x648 (60%/60%) â kept width as-is, grew height to match the
same percentage instead of a smaller, mismatched one. Confirmed live
via swaymsg -t get_tree.
2026-08-31 (rmpcâs floating window: actually glass, not just technically transparent)
Direct feedback right after the mpd/rmpc migration below: âlets make
its background glass like.â background_color: None was already set
in the theme (carried over proactively from termusicâs own fix), and
kittyâs global background_opacity 0.85 was blending against it â but
on a window this large (1152x540, over half the screen), that blend
wasnât perceptible enough to read as glass.
Fixed with a per-window kitty override on rmpcâs own launch line only
(-o background_opacity=0.3, scripts/.local/bin/rmpc-toggle.sh) â
kitty/kitty.confâs global 0.85 stays untouched for every other
terminal use.
Getting to a trustworthy number took a few wrong turns, worth recording since theyâre easy to repeat: whole-window pixel averaging (comparing against a same-position wallpaper-only screenshot) gave inconsistent, sometimes backwards-looking deltas between opacity values, because the average is skewed by however much text/UI happens to be on screen at capture time, not just the background blend. Switched to sampling one fixed, confirmed-empty patch of the window instead, at the real toggle-triggered window position â that gave a clean, reproducible result: at 0.3, rgb(16.9, 15.0, 10.6) vs. a wallpaper-only rgb(24.0, 21.3, 15.1) for the identical region, a real ~7-9 point shift per channel, in the same âdark glassâ direction as every other translucent surface in this desktop (waybar, mako, wofi, nwg-bar all read as Deep-Midnight-tinted, not lightened). Confirmed text legibility held up at 0.3 via a direct ASCII luminance dump of real rendered text, not just the averages.
2026-08-31 (termusic removed entirely: mpd + rmpc instead)
Direct feedback: âtermusic isnât of any use then, lets use a better tui or cli local music player then and remove this damn termusic.â Rather than keep patching around its one real problem forever, swapped the player out too.
termusic fully removed: package uninstalled, ~/.config/termusic/ deleted, termusic/ package and termusic-toggle.sh gone from this repo. Every historical entry that talks about termusic is left as written â real history, not rewritten.
mpd (official extra repo, mpd.service shipped by the package itself) is the new backend â zero third-party API dependency at all, just plays real files. Confirmed its filesystem watcher genuinely works (dropped a file into ~/Music with no clients connected, watched it get indexed within seconds), a real improvement over termusic which had none.
rmpc (official extra repo, 3.3k stars, actively maintained) is the new UI, an MPD client with no playback logic of its own. Validating find: rmpc ships its own yt-dlp-based YouTube downloader too (confirmed reading its source, real yt-dlp not Invidious) â independent proof music-search.pyâs own approach was the right one. Its own picker has no thumbnails though, so music-search.py stays the actual search tool.
Theme/sizing/transparency/toggle rebuilt applying termusicâs own lessons instead of rediscovering them: rmpc theme dumped its real default (one big RON structure with named colors threaded through the whole layout, not a flat palette) and every color got substituted for Catppuccin Mocha (âblueâ specifically mapped to Mauve, matching its role as this desktopâs own âactiveâ accent). background_color left None from the start this time â verified live, zero pixels of solid background, Mauve dominant, glass worked on the first real launch, no rediscovering the bug termusic needed a dedicated fix for.
Real scare resolved by finding the actual cause: rmpc debuginfo reported the album-art protocol as the crude âBlockâ fallback instead of âKittyâ â traced to this sessionâs own shell not being a real tty (env vars inherited, not backed by a real terminal), not an actual setup problem. Checked rmpcâs own log from the real launch path instead and found resolved_backend=âKittyâ, kitty_graphics=âtrueâ â genuinely active for the path that matters.
2026-08-31 (music-search.py: real thumbnails in the results list)
Reported directly: âonly text doesnât make that much sense, show thumbnail and title in each row.â wofiâs own img: dmenu markup handles it (same mechanism notification-history.py already uses) â just needed a thumbnail source. YouTubeâs plain https://i.ytimg.com/vi/id/mqdefault.jpg URL (confirmed directly with curl, not assumed) is simpler and more reliable than the signed URLs in yt-dlpâs own thumbnails JSON. Fetched for all ~10 results concurrently (confirmed: ~0.4s together vs several seconds serial), cleaned up after the picker closes either way.
Verified live: checked the temp directoryâs actual contents while the
real results popup was open (through the real keybinding, real typed
input) â ten real, valid JPEG files, confirmed with file.
2026-08-31 (music-search.py: replacing termusicâs broken search with our own tool)
Asked directly to build a replacement rather than live with the URL-paste workaround. Checked ytfzf first (the obvious existing alternative) and ruled it out â read its source, its default search path is a direct alias for Invidious search too, same dead network already confirmed for termusic.
Built music-search.py on yt-dlpâs own direct search instead (confirmed independent of Invidious, ~2-3s for 10 results). $mod+Shift+y: wofi prompt for a query, wofi list of results (title/uploader/duration â duration shown after accidentally downloading a 3+ hour video once during testing), downloads the pick as mp3 with embedded art/metadata into ~/Music.
Two real bugs found and fixed while building this: (1) 2 of 3 real test downloads hit YouTubeâs bot-check wall with yt-dlpâs default extraction â fixed with âextractor-args âyoutube:player_client=androidâ, confirmed directly that the same URLs downloaded cleanly with it every time. (2) the completion notificationâs icon (the trackâs own thumbnail) failed to load and the thumbnail file was left undeleted â yt-dlp sanitizes filenames differently than the raw search-result title (a literal â|â becomes a fullwidth âïœâ), so reconstructing the filename from title silently matched nothing. Fixed by finding the newest image file by mtime instead of predicting yt-dlpâs naming.
Verified fully live through the real keybinding with real typed input
(ydotool, not the script run directly): read actual search results
back via wofiâs AT-SPI tree, downloaded a real track, confirmed with
ffprobe itâs valid audio with embedded cover art, and confirmed via
termusicâs own startup log (â1 created or updatedâ) that itâs genuinely
indexed by termusicâs real library scanner. termusicâs own s key is
left as-is; $mod+Shift+y is just the reliable path now.
2026-08-31 (every popup toggles now; termusic gets real glass, correct size, and a search diagnosis)
Three asks together: every keybinding-launched popup should toggle (press the same key again to close it) and close on Escape; termusic needs an actual transparent background and resizing to 60%/50% of the output; and why doesnât termusicâs YouTube search work at all.
Escape already worked for wofi and nwg-bar popups (both handle it natively, confirmed rather than assumed â nwg-barâs own Go source calls gtk.MainQuit() on Escape release). Toggle needed real work: new scripts/.local/bin/toggle-popup.sh, a marker-file-based wrapper every wofi-based popup keybinding and the power menu now route through â same key while its own popup is open closes it, a different key closes whatever else is open first then opens the one requested. Verified live for both a raw wofi process and nwg-bar (a different binary entirely) to confirm it generalizes.
termusic doesnât use that script â killing/relaunching it would stop
playback, since itâs a real client/server app. New
scripts/.local/bin/termusic-toggle.sh uses swayâs scratchpad instead:
verified directly (not assumed from swayâs own ambiguous docs) that
scratchpad show on an already-shown scratchpad window genuinely hides
it again, and that the underlying process is untouched the whole time
(same PIDs, no disconnect logged, across a full hide/show cycle).
Real transparency bug found and fixed: kittyâs background_opacity only blends cells using the terminalâs own default background â termusicâs theme was painting an explicit opaque hex into every cell, defeating it regardless of kittyâs own setting (confirmed from termusicâs own source, not assumed). Fixed with termusicâs own ânativeâ theme value (resolves to ratatuiâs Color::Reset, meaning âdonât paint hereâ), cascading through every panel in one edit since they all reference the same field symbolically. Verified live: 0% of the windowâs pixels are the old literal hex now; cross-checked the resulting dark blend against a same-region wallpaper-only screenshot to confirm itâs a real blend against a coincidentally-dark wallpaper patch, not a failure to blend.
Resized to 1152x540 (60%/50% of the actual 1920x1080 output, computed directly). A real bug hit building the toggle script: an early version backgrounded kitty without detaching stdio, which could hang whatever launched it â fixed with setsid + redirected output.
YouTube search diagnosed to a real, external, unfixable-from-here root cause: confirmed yt-dlp itself works fine (direct search succeeds), so termusicâs search specifically uses the public Invidious instance network, not yt-dlp, for the search step. Tested every one of termusicâs five hardcoded fallback instancesâ actual search endpoints directly: three return explicit errors (403 disabled, 401 unauthorized, anti-bot challenge page), two are unreachable. Checked the live instance directory too â zero of the 11 currently listed instances have their public API enabled at all. Not a local network issue, not fixable by upgrading termusic (checked upstreamâs current changelog, still Invidious-based). Working mitigation: paste a direct YouTube URL into the same search popup instead of a text query â skips the broken search step, goes straight to the already-confirmed-working yt-dlp download path.
2026-08-31 (termusic: CLI music player, no local library or Premium needed)
Asked for a âbeautiful CLIâ music player with no local library and no Spotify/YouTube Premium to lean on. Picked termusic â its own README says directly it downloads from YouTube/NetEase/Migu/KuGou for free, no paid membership needed. Ruled out spotify-player/ncspot (both need Spotify Premium to actually play anything, a restriction Spotify itself enforces) and rmpc/cmus (both local-library-only, no cloud fetch at all) directly against the constraint rather than assuming.
Confirmed the actual mechanic from its own source: s opens a
âDownload url or searchâ popup, picks a YouTube result, downloads via
yt-dlp into ~/Music, plays â works from a genuinely empty library.
Directly in Archâs official extra repo, no AUR/source build. Added
yt-dlp alongside it in packages/pacman.txt.
Real version mismatch found by checking: upstream master ships an official Catppuccin-Mocha theme, but the installed package (0.13.2-1) predates it â confirmed directly by launching termusic and checking its own bundled themes/ directory, genuinely missing. Copied the file in by hand. Theme baked into tui.tomlâs [theme] section rather than left as a manual step â avoided guessing the TOML shape blind by launching once with no config, reading termusicâs own generated defaults, then writing into that confirmed real shape.
Only tui.toml/server.toml/the one theme file stowed, not the whole
~/.config/termusic/ â data.db/library2.db/playlist.log are runtime
state, same config-vs-state line this repo already draws for
~/.local/state/. Works because the directory already existed as real
(termusic creates it itself) before stowing â confirmed with
stow -n -v that individual files get symlinked in, not the whole
directory.
$mod+Shift+m opens it in its own floating kitty window (sized/centered via a for_window rule). Verified live: triggered the real keybinding via simulated keypress, confirmed the floating windowâs position/size via swaymsg, screenshotted it running â 88.6% of its pixels are the exact Catppuccin Mocha background hex, confirming the theme is genuinely active in the real instance.
v1.0.1 â 2026-08-30
Patch release. The entire power-menu arc since v1.0.0 â keybinding,
real per-action colors on the button chrome, glass+border on every
button, Tab-navigation fix, lean icon set, and this polish pass â
merged from develop in one fast-forward, tagged as a patch rather
than a minor bump per direct instruction. Everything below back to
v1.0.0 shipped in this version.
2026-08-30 (power menu polish: âPower offâ label, thinner icons, real icon/label gap)
Three small asks together: rename Shutdown to Power off, thinner icons, real space between icon and label â nwg-bar stacks them with zero gap of its own (SetImagePosition POS_TOP, nothing else).
Label changed in bar.json directly. Stroke-width dropped 2 -> 1.5 across all five icons, same Lucide path data. Icon size set explicitly to 40px (-i 40) on both places nwg-bar launches from (waybarâs custom/power on-click, swayâs $mod+Shift+p) â nwg-barâs own 48px default read as slightly oversized against the button padding. Actual gap: image { margin-bottom: 10px; } in style.css â image is the element directly above the label in nwg-barâs own stack, no more specific CSS node exists to target.
Verified live: read the padlock iconâs rendered pixels back as ASCII art before and after â gap to the label went from ~9px to ~19px, a real measured change. Confirmed via AT-SPI that the fifth button now reports âPower offâ. Border colors from every earlier pass unaffected.
2026-08-30 (power menu, third icon pass: lean stroke icons, one family)
Direct feedback right after the Adwaita swap below: âolder once were better than this. i want lean icons just like the older once but just different.â The Adwaita set fixed meaning but lost the lean, thin- outline look the originals had â the actual ask was both at once.
Rebuilt all five from Lucide (a modern line-icon set, 24x24 grid,
consistent 2px stroke, no fill) â found already vendored locally as an
npm dependency in an unrelated project on this machine, no network
fetch needed. lock/log-out/moon/rotate-cw/power path data copied
straight from Lucideâs own per-icon source files into plain standalone
SVGs, same stroke weight across all five this time â one coherent
family instead of a patchwork. Swapped stroke=âcurrentColorâ for a
hardcoded #CDD6F4 (same reasoning as every other icon fix this
session: raw file load, not icon-theme resolution, no reliable
currentColor context). Shutdownâs power icon is functionally the same
ring+line symbol as before, just redrawn in the same lean style as the
other four.
Verified live: launched nwg-bar directly, read all five icons back as ASCII art â a genuinely hollow padlock, door+arrow logout, a thin crescent moon, circular refresh arrow, power symbol, all at the same visual weight. Confirmed the five border colors from the earlier pass unaffected.
2026-08-30 (power menu: better icon shapes, four of five swapped for Adwaitaâs)
Reported directly: Suspend looked like âa circle with a vertical rod in the middleâ, not meaningful â asked for better icons generally.
Checked Papirusâs own system-suspend-symbolic first and rejected it â same ring-plus-dash concept already in use, wouldnât have fixed anything. Used a crescent moon instead (Papirusâs weather-clear-night shape, recolored) â the universal sleep symbol. Swapped Lock/Logout/ Reboot to Adwaitaâs own symbolic set too rather than leave a mismatched patchwork next to one new icon. Shutdown untouched â already correct from the previous pass.
Every Adwaita source icon ships a hardcoded fill meant for light GNOME toolbars (#2e3436) â same issue already found and fixed for makoâs notification icons. Recolored to #CDD6F4 (this barâs own text color). Full mapping in icons/README.md.
Verified live: launched nwg-bar directly, read all five iconsâ actual rendered pixels back as ASCII art â a real padlock, a clear exit arrow, an unambiguous crescent moon, a circular refresh arrow, each individually recognizable. Confirmed the five border colors from the previous pass were unaffected.
2026-08-30 (power menu: keybinding, verified Tab nav without ever pressing Enter)
Added $mod+Shift+p to open the power menu (same gap every other waybar-click-only picker already had fixed), and verified Tab/Shift+Tab move focus between its buttons as expected.
Every action here (Lock/Logout/Suspend/Reboot/Shutdown) is disruptive or hard to reverse â couldnât safely press Enter just to check where focus landed. Used nwg-barâs own AT-SPI accessibility tree instead (itâs already registered on the bus) to read back which button reports itself focused after each simulated Tab/Shift+Tab â read-only, same mechanism screen readers use, zero risk of triggering anything. Confirmed the full cycle both directions with wraparound.
Found a real gap doing this: focus was genuinely moving the whole time, but nothing was styled to show it â a before/after screenshot diff read zero changed pixels. Fixed by giving :focus the same treatment as :hover on every per-action border rule. Re-verified: the same diff now reads 35936 changed pixels for one Tab press, and AT-SPI confirms the actually- focused button matches the one that visibly glows.
2026-08-30 (power menu, corrected: color on the pill not the icon, glass+border per button, real CSS bug fixed)
Direct feedback right after the entry below: wrong layer for color (âby colors i donât mean those iconsâ), and the card was missing glass/border on every button, not just the outer frame.
Reverted icons to plain stock appearance â color now lives on the button itself instead, mirroring waybarâs own per-module coloring: a low-opacity border tint per action, brighter plus a glow on hover, keyed off :nth-child since nwg-bar gives buttons no name/class to target directly. Gave every button its own resting glass pill too (waybarâs exact rgba/radius/border formula), not just on hover â the outer card already had glass+border, individual buttons didnât, which is almost certainly what read as missing entirely.
Real bug hit along the way: the first attempt at this silently failed
to apply at all â nwg-barâs own log said âstyle.css file erroneousâŠ
using GTK stylingâ, falling back to bare default chrome. Diagnosed with
GTKâs own CssProvider parsing-error signal: a comment mentioning
âicons/*.svgâ broke CSSâs no-nested-comments rule (/* inside prose
closes early). Fixed the wording, re-verified zero parse errors before
trusting it. Verified live end to end: log confirms style actually
loads now, zero pixels match the old icon-fill colors, all five button
border colors present and distinct in the card region.
2026-08-30 (power menu icons: invisible colors fixed, meaningful per-action palette)
Asked for the nwg-bar power menu to show meaningful, colored icons,
Shutdown specifically looking like a real power button. First change on
develop after tagging v1.0.0.
Root cause: nwg-barâs stock icons (/usr/share/nwg-bar/images/*.svg)
each have a colored circle whose fill is silently disabled by an inline
style="fill:none" on the same element â every action rendered as a
flat white ring with nothing behind it, not a size/shape issue. Suspend
and Shutdown also shared the exact same stock red, no distinction even
if the fill had worked.
Made local copies (nwg-bar/.config/nwg-bar/icons/, bar.json
repointed) since nwg-bar gives buttons no CSS class to color per-action
at the stylesheet layer â removed the fill:none override, recolored
each to this desktopâs own Catppuccin Mocha palette (Sapphire/lock,
Peach/logout, Teal/suspend, Yellow/reboot, Red/shutdown â full mapping
in that directoryâs README). Glyph shapes untouched â Shutdown was
already the correct ISO power/standby symbol, just needed its color
back. Verified by launching nwg-bar directly and reading the rendered
pixels back as ASCII art, not color-tolerance sampling.
v1.0.0 â 2026-08-30
First tagged release. Marks the point this repo switched from âdated
entries with no version boundariesâ to a real main/develop branch model
with tagged releases (docs/VERSIONING.md) â not a specific technical
milestone on its own, but the desktop was given a full, direct stability
pass before drawing this line, not just assumed stable:
- Processes: exactly one each of sway, waybar, mako, swaybg running,
confirmed via
pgrep -x/pgrep -a(not just âaâ process existing â checked for duplicates/stray instances from the sessionâs own testing). - systemd:
systemctl --user list-units --failedclean (one stale, harmlessmako.servicefailure reset â a disabled, package-provided unit unrelated to how this repo actually runs mako, killed as a side effect of live testing earlier the same session, not a real fault).wallpaper.timeractive and correctly scheduled;swayidle.servicecorrectly inactive because caffeine mode is currently on (state file present) â confirmed intentional, not a fault, by readingswayidle-startup.shâs own logic rather than assuming. - Config validity:
makoctl reload,swaymsg reload, and a manual brace/bracket balance check onwaybar/configall clean. - Scripts: every
.sh/.pyunderscripts/.local/bin/andsway/.config/sway/scripts/executable,bash -n/python3 -m py_compileclean across all of them. - Disk/state hygiene: wallpaper archive correctly capped at 60 files,
20M total, no stale locks, notification-mode state file consistent with
live
makoctl mode. - git: working tree clean,
mainup to date withorigin/main.
2026-08-30 (volume icon, third pass: dropped the glow)
Asked directly, once the icon was genuinely rendering again: why the glow, make it normal. Fair â the text-shadow was added to make an invisible icon more noticeable, back when the problem looked like âtoo smallâ rather than ânot rendering at allâ. Once the real cause (waybarâs broken format-icons threshold form, previous entry) was fixed properly, the glow had nothing left to justify. Removed it â #pulseaudio is back to a plain resting color like every other module. Verified with the same ASCII-dump method as the previous entry: icon still renders fine at 80%, just without the shadow now.
2026-08-30 (volume icon, second correction: a real waybar bug, verification method overhauled)
Reported again after the text-shadow fix: âonly when its muted does it show the iconâ â the icon was invisible at every regular volume level the whole time, not just small. My previous two verification passes (color-tolerance pixel matching, both times) missed this entirely â worth stating plainly rather than letting a third wrong âverifiedâ claim stand.
Stopped trusting color-heuristic sampling and switched to dumping the actual screenshot as luminance-thresholded ASCII art, reading the glyph shapes directly like a person would look at the bar. Immediately showed the real picture: nothing between the iconâs space and the percent digits at any regular volume level; a genuinely new glyph appearing only when actually muted.
Root cause, isolated by testing one variable at a time: waybarâs format-icons threshold-object form ({âiconâ:âŠ,âmaxâ:N}, added to give 0% its own icon) â real and documented, straight from waybarâs source â silently renders no icon at all for pulseaudioâs regular format string in this waybar build (v0.15.0), confirmed by reverting to a plain array (icon reappeared) and reapplying the threshold form alone (gone again). format-muted kept âworkingâ the whole time because it never used format-icons at all â a fully hardcoded string, unaffected by the bug, which is exactly what made it look like proof the feature worked.
Fixed by not depending on the broken path: volume_osd.sh now sets the real PulseAudio mute flag whenever a scroll/keypress lands exactly on 0%, clearing it the moment volume moves back up â so format-muted (the one path already proven correct) picks it up naturally. 0% and muted really are the same silence to the ear, so this is also just a more honest model of the actual state, not a workaround. Known gap: only covers 0% reached through volume_osd.sh itself, not pavucontrol or other external tools touching the sink directly.
2026-08-30 (volume icon correction: span-size broke it, text-shadow fixed it)
Reported right after the entry below: icon not visible at all, muted icon + âMutedâ text not on the same line. Both real, both caused by that entryâs inline-span approach.
Isolated the cause step by step rather than guessing: reverted the per-icon Pango span first (confirmed the separate format-icons threshold-object change was fine on its own, icon rendered small but correctly). Tried a uniform font-size bump on the whole module instead â hit a second, different bug: at 15px the three volume-level glyphs stopped rendering entirely (0 pixels), while the mute glyph and percent digits kept working fine at the same size. Reproducible, isolated, genuinely strange â a real font/Pango rendering quirk, not chased further blind. font-weight: 700 (already proven safe elsewhere in this file) changed nothing for these glyphs â no breakage, but no visible difference either.
Landed on text-shadow â same paint-only technique already proven safe in this file (#custom-capslock.lockedâs glow), doesnât touch layout or font rendering at all. Verified properly this time: matched for a Maroon-ish hue rather than âanything non-backgroundâ (which the first pass at this wrongly picked up backlightâs own Yellow icon bleeding into the same crop). Consistent ~12x9px glow at every volume level (vs ~4-5px bare), icon and text on the same y-range, and the glyphs actually render.
2026-08-30 (volume icon: muted look at 0%, consistent visible size)
Two things reported as one: 0% volume didnât look muted (design opinion given directly: it should â 0% and actually-muted are both silent, no reason to look different), and the icon in general is hard to notice at low volumes.
Measured the actual problem before fixing it: cropped a real screenshot to just the icon and found ~4x5px of ink at every volume level, regardless of which of the three glyphs was showing â the whole icon was just small, not a per-level issue.
- 0% fix: waybarâs format-icons supports {âiconâ,âmaxâ} threshold objects, not just an evenly-bucketed array (confirmed from waybarâs own source, src/ALabel.cpp) â gives 0% its own exact bucket instead of sharing one with everything up to 33%. Reuses format-mutedâs existing crossed-out glyph rather than adding a new one.
- size fix: wrapped {icon} in its own Pango span ({icon}), sizing only the icon, not the percent text â a static size baked into the format template, not a CSS font-size or a state-conditional one. This repo already hit the state-conditional failure mode once (bumping font-size on #custom-capslock.locked visibly popped the whole bar taller at runtime) â avoided here since the span size never changes, so thereâs nothing to jump between.
Verified live: restarted waybar for real (SIGUSR2 confirmed a no-op in the entry above), measured the icon bbox again â now a consistent 20x9px at every volume level, versus ~4x5px before. Confirmed 0% renders the reused mute glyph, true-mute still works, no bar-height pop from the change.
2026-08-30 (workspace indicator follow-up: brighter numbers, real waybar-reload gap)
Reported right after the occupancy feature below: the occupied-but-
unfocused number was âslightly hard to noticeâ. Clarified intent: number
color should say âwindow here, or this is where I amâ without
distinguishing the two â the underline alone confirms âthis one is
currentâ. Bumped occupiedâs color from translucent (0.7 alpha) to
solid, identical to .focused â the two states now differ only by
font-weight (secondary cue) and the underline.
Found a real gap while re-verifying: pkill -SIGUSR2 waybar, used to
âreloadâ waybar for the first pass on this feature, actually does
nothing by default (checked waybarâs own source â no on-sigusr2-action
configured here, and no CSS file-watching either), so that earlier
verification may have been screenshotting a stale render that happened
to look right. Fixed by actually restarting waybar outright
(sway/configâs exec waybar isnât a supervised exec, so a plain kill
needed a manual relaunch) rather than trusting an unconfirmed signal.
Re-verified after the real restart at full bar height (41px): both
numbers solid Red equally, underline present only under the focused
workspaceâs full width, nothing under the merely-occupied one.
2026-08-30 (workspace indicator: occupancy shown separately from focus)
Asked for workspace numbers to show which workspaces hold a window, not just the focused one â focused keeps number color + underline, any other occupied workspace gets number color only, no underline.
waybarâs sway/workspaces module already computes this itself: .empty
is its own class (confirmed from waybarâs own source), toggled live
whenever a workspaceâs nodes+floating_nodes are both empty â no
polling or extra script needed. New rule:
#workspaces button:not(.empty):not(.focused), colored the same Red as
.focused but translucent rather than solid â one hue at two
strengths reads as âsame kind of signal, different degreeâ rather than
a fourth unrelated color, and keeps the underline as the sole âcurrentâ
signal. Placed before the existing :hover rule so hovering an
occupied-but-unfocused workspace still shows hoverâs Lavender on the
specificity tie.
Verified live: two real workspaces existed, one focused-with-a-window, one unfocused-with-a-window â screenshotted the bar and found 42 pixels of solid Red (focused) and 25 of the translucent-Red blend (occupied, unfocused), confirming both states render distinctly.
2026-08-30 (notification toast icons round two: invisible symbolic icons)
Reported right after the icon-path fix below: the notification-history copy action âdoes not show the clipboard icon only textâ. The icon-path fix was real and needed, but only fixed resolution â this is a second, separate bug about visibility.
Root cause, found by reading the actual SVG: every -symbolic icon in
Papirus (plus several plain actions-category ones like edit-copy,
audio-volume-high, media-record) uses fill:currentColor with a
#444444 dark-gray default, meant for light UI chrome â on this
desktopâs near-black notification background thatâs barely
distinguishable from the background itself. Confirmed directly: diffed
two real screenshots pixel-by-pixel in the iconâs own region (one with
edit-copy-symbolic, one with a deliberately nonexistent icon as
control) â 7114 pixels genuinely differed, so something was drawing,
just nothing anyone would notice.
Fix: swapped every affected icon across every script in this repo for
Papirusâs status/apps-category equivalent instead â those use real
hardcoded fill colors (grep -c currentColor == 0, checked per icon
before using it), same style Dockerâs icon already used successfully.
Two (process-stop, media-record) have no real-fill version anywhere
in Papirus â swapped for the closest real concept instead
(dialog-warning for âcancelledâ, audio-input-microphone for
ârecordingâ). Also switched every one of these to an absolute file path
rather than a bare name â several names resolve to more than one file
across Papirus categories (one currentColor, one real-fill) and mako
picks by size match, not category, so a bare name canât guarantee which
one wins.
Verified live: fired the exact copy-notification call, found
617/413 pixels of the clipboard iconâs own real fill colors
(#e4e4e4/#d3d3d3) in the icon region, 4194 pixels different from a
no-icon control in the same spot. Separately confirmed a critical-icon
swap (dialog-error) with 1082 pixels of its real red fill
(#f44336).
2026-08-30 (notification toast icons: real mako icon-path bug fixed)
Asked for every toastâs title to show the associated toolâs icon (clipboard for copy events, Dockerâs icon for Docker, etc.) â every notify-send call in this repo already passes a correct icon name, so dug into why none of them ever actually rendered.
Root cause: mako only searches /usr/share/icons/hicolor and
/usr/share/pixmaps by default and does no GTK-style theme inheritance
â confirmed directly with find that not one icon name used anywhere
in this repo (docker-desktop, edit-copy-symbolic,
dialog-error-symbolic, etc.) exists under hicolor; they all live
under the actual active theme, Papirus-Dark, which is itself nearly
empty and inherits from Papirus/breeze-dark in a way mako doesnât
follow. Every notificationâs icon was silently failing to resolve, full
stop, independent of how correct the icon name was.
Fix: icon-path=/usr/share/icons/Papirus-Dark:/usr/share/icons/Papirus:
/usr/share/icons/Adwaita in mako/config â one line, covers every
icon in the repo since all of them already resolve under Papirus
(confirmed with find before committing to it). Verified live:
docker-desktop icon fired after makoctl reload, screenshotted,
716 pixels of Dockerâs exact brand blue found in the notification region
â 0 in a negative-control test with a deliberately nonexistent icon
name, confirming the render is real and the test methodology holds.
2026-08-30 (notification bell: distinct icons back, alongside color)
Second follow-up in the same session: after dropping to one shared bell glyph with color-only mode signal, asked to bring back distinct icons per mode too, on top of the color (not instead of it).
Three bell-family glyphs, not the original mismatched set: fa-bell
(normal), fa-bell-slash (silent), md-bell-sleep (dnd â a bell with
âzzzâ, replacing the original md-minus-circle, which wasnât a bell at
all and read as an unrelated icon rather than âthe bell, in a different
stateâ). All three confirmed present in the installed Nerd Font via
fc-list before use. Hit the sessionâs recurring PUA-glyph-transport-
corruption bug again writing these â caught and fixed the usual way
(patch via Python chr(), verify by reading back hex(ord(ch))).
2026-08-30 (notification bell: one glyph, color-only mode signal)
Follow-up to the three-mode notification feature above. Asked for white-only bell icons, then reconsidered mid-request and asked for color instead, kept clear of the clockâs own colors right next to it.
Dropped the three different glyphs (fa-bell/fa-bell-slash/
md-minus-circle) for a single fa-bell shared by all three modes â
color is now the only thing distinguishing normal/silent/dnd, kept from
the same three colors already in place (Lavender/dim gray/Red).
Confirmed with an exact-pixel-color screenshot count per mode that each
modeâs own color actually dominates the bar while itâs active, and that
none of the three share a single exact pixel match with #clockâs Blue
(time) or Maroon (date) in any mode â not just eyeballed as âlooks
different enoughâ.
2026-08-30 (three notification modes: normal/silent/dnd)
Asked for three notification modes â normal (popup + sound), silent (popup, no sound), dnd (neither, but still logged to history) â a pleasant sound, a mode-dependent waybar bell icon, and both keybindings and scroll for switching, âsoft and flawlessâ.
Built entirely on makoâs own native mode system (makoctl mode -s
name + [mode=name] config sections) rather than anything custom
â invisible=1 suppresses the popup without touching history, exactly
what âdndâ needed. Picked
/usr/share/sounds/freedesktop/stereo/message.oga for the sound (the
shortest of the plausible freedesktop chimes by actual ffprobe
duration, not a filename guess, and the same file makoâs own docs use
as their on-notify example) via on-notify=exec paplay file.
Three real bugs found by testing, not assumed:
- A global option placed after a
[section]header silently scopes to that section only â the sound line, first placed after[urgency=high], only ever fired for high-urgency notifications. Moved above any section header. invisible=1does not also suppresson-notifyâdndmode still played the sound untilon-notify=nonewas added there too (confirmed with a marker-file side effect, decoupled from timing- sensitive audio playback).on-notify=(empty) is a hard parse error;noneis the documented no-op value.- makoâs active mode resets to its own built-in âdefaultâ on every
daemon restart, with nothing persisting it â confirmed by killing and
restarting mako mid-test. Fixed the same way
caffeine-toggle.shalready handles this exact shape of problem: a state file (~/.local/state/notification-mode/current) plus a startup script (notification-mode-restore.sh) run afterexec makoinsway/config, with the same bounded-retry pattern already used elsewhere for daemon-not-ready-yet races.
Switching: notification-mode.sh normal|silent|dnd|next|prev,
wired to $mod+Ctrl+n/s/d (direct jump), $mod+n and the waybar bellâs
scroll wheel (cycle). The waybar custom/notifications module also
picked up a "signal": 8 so pkill -RTMIN+8 waybar gives an instant
icon/color refresh on switch instead of waiting out the moduleâs normal
poll interval â confirmed via a screenshot taken immediately after a
keybinding switch, already showing the new color.
Not independently verified: the scroll-wheel wiring itself, same
ydotool absolute-mouse-calibration gap disclosed for
notification-history.pyâs click handler last entry. The next/prev
cycling logic it calls is fully verified directly (both directions,
correct wraparound); the scroll binding calls the identical script the
verified keybindings call.
2026-08-30 (notification-history.py: new feature, plus a real docker-picker.py icon bug)
Asked for a real docker-branded icon on Docker notifications (reported directly: âonly has the docker name in it not the icon of itâ) and a notification history viewer (waybar bell left of the clock, newest first, full details).
Fixed docker-picker.pyâs notify(): was utilities-terminal (a
generic terminal glyph, no connection to Docker) for its non-critical
case. Now docker-desktop, a real icon Papirus ships â confirmed it
actually resolves via GTKâs own icon theme lookup before using it, not
assumed from the filename.
notification-history.py: mako already has a real history buffer, just
needed history=1/max-history=200 turned on (mako/config, off by
default). Two real bugs found and fixed while building this: makoâs own
makoctl history -j ordering isnât reliable (one call came back
newest-first, a later one oldest-first) â now explicitly sorts on id
descending rather than trusting it. And wofiâs dmenu input splits on
newline into separate entries â an embedded \n between a
notificationâs summary and body silently produced two unrelated dmenu
items instead of one two-line one, confirmed by testing the actual
selection round-trip. Fixed by keeping one line per entry (summary and
body joined with a separator, same pattern as docker-picker.pyâs own
info rows) â long ones still wrap naturally within that one entry.
âShows full details on focusâ isnât architecturally available in wofiâs dmenu mode (no focus-change hook exists) â confirmed rather than assumed, and built the honest equivalent instead: every entry already shows its full summary and body up front. Selecting one copies it to the clipboard.
Added $mod+Shift+n, matching every other waybar-click pickerâs keyboard
path, verified live via a real keypress. The waybar on-click itself
wasnât independently re-verified with a real mouse click this pass â
disclosed rather than assumed: ydotoolâs absolute positioning has an
unmet calibration requirement in this environment, and a pixel-sampled
click reliably landed on a different module instead. It uses the
identical JSON pattern as every other already-verified picker, but thatâs
inference from precedent, not an independent click test. See
docs/ARCHITECTURE.md for the full writeup.
2026-08-30 (Zen Browser confirmed not reacting to the portal signal â fixed in its profile)
Reported Zen still stayed dark after the packages were actually
installed. Zen was genuinely running, so this got a direct answer:
screenshotted the desktop and pixel-sampled Zenâs own toolbar/chrome
area, found it consistently near-black while dconf read color-scheme
reported prefer-light at that exact moment â first-hand confirmation,
not just trusting the report.
Traced it to widget.use-xdg-desktop-portal.settings, defaulting to 2
(auto-detect: GTK + Wayland + portal available). Every one of those
conditions is genuinely true here, but it evidently wasnât resolving to
âuse itâ in this Zen build. Fixed directly in Zenâs actual active profile
(found via its lock file pointing at the real running PID, not
guessed): user_pref("widget.use-xdg-desktop-portal.settings", 1); in
user.js, forcing the portal on rather than relying on auto-detection.
Deliberately not added to this repo â Firefox/Zen profile directory
names are randomly generated per install, not a stable path any stow
package could target. This fix lives only in that profile on this
machine; a fresh install or new profile would need it reapplied. Needs a
Zen restart to take effect (user.js is only read at startup) â not
restarted automatically here, since closing someoneâs browser out from
under their open tabs isnât this repoâs call to make. See
docs/ARCHITECTURE.md for the full trace.
2026-08-30 (theme-toggle.sh: real bug found â was writing theme names that donât exist yet)
Reported the toggle fires notifications but Zen Browser (set to follow
system theme) never actually switched to light. Verified every layer of
the mechanism directly rather than guessing: dconf write genuinely
changes the value; the XDG portalâs Settings.Read
(org.freedesktop.appearance/color-scheme) reflects it immediately,
queried directly via gdbus call; and â the layer that actually matters
for a live update with no restart â the portal genuinely emits a
SettingChanged signal with the correct new value on both the legacy
and current namespaces, confirmed by monitoring the session bus with
gdbus monitor while triggering the toggle. All three layers check out.
The real bug: catppuccin-gtk-theme-latte/papirus-icon-theme/
papirus-folders-catppuccin-git were added to packages/ when this
toggle was built, but never actually installed (pacman -Q fails for
all three â the handed-off install command hadnât been run yet).
theme-toggle.sh was writing gtk-theme/icon-theme dconf keys
pointing at those names regardless, and a theme name that doesnât
resolve to anything installed fails silently â apps just keep rendering
whatever they last loaded, indistinguishable from the toggle doing
nothing.
Fixed: now checks /usr/share/themes/name//usr/share/icons/name
before writing either key, skips it (leaving the previous working value)
if missing, and notifies clearly naming whatâs not installed instead of
pretending it applied. color-scheme always writes regardless â no
file dependency, and itâs the one signal Firefox/Zen actually need for
their own dark/light CSS, since they donât render via system GTK theme
files the way a native GTK app does. Verified the fix directly with the
packages still missing: color-scheme and the already-installed dark GTK
theme switched correctly, the not-yet-installed light theme/icon-theme
were left alone rather than pointed at nothing.
Confirmed working but outside this repoâs reach: whether an app
subscribes to the portal signal at all is up to that app. Firefox-based
browsers have a widget.use-xdg-desktop-portal.settings preference
(about:config) gating this â worth checking there if a browser still
doesnât react after this fix. See
docs/ARCHITECTURE.md for the full verification
trail.
2026-08-30 (incident: stow symlinks broken desktop-wide, recovered clean; docker gets a keybinding)
Reported the docker waybar click had stopped working, and asked for a
keybinding matching the other pickers. Chasing the click found something
much bigger: ~/.config/waybar/config, both wofi files, sway, mako,
nwg-bar, kitty, tmux, every tracked nvim/zed file, and
~/.config/systemd/user/tmux.service had all silently stopped being
symlinks â real disconnected files instead, tmux.service missing
outright (the exact symlink fixed in an earlier pass, broken again).
Almost certainly a well-known dotfiles-via-symlink gotcha, not specific
to this repo: any tool that saves âatomicallyâ (write-temp-then-rename)
replaces a symlink with a real file the instant something saves a
symlinked config directly rather than through the repo path â explains
why files this session never touched (nvim, zed) were affected too.
Checked before fixing anything: every affected file was content-identical
to its repo counterpart, safe to just re-link. A second, more serious
mistake happened while fixing it, disclosed in full: tried to fix several
packages at once with one combined stow -R call, which deleted the
repositoryâs own source files for all of them (confirmed via git
status showing 32 files deleted in the working tree) â GNU Stowâs
directory-folding logic behaving unpredictably when several targets are
simultaneously in an inconsistent state within one combined restow.
No data was lost â git history was untouched throughout, git
restore . recovered everything in one command, verified by checking
actual file content afterward (not just a clean git status). Fixed
properly the second time, one package at a time with verification after
each. Final state confirmed three ways: every ~/.config/pkg is a real
symlink, git status clean, stow -n (dry-run) across every package
reports zero pending actions.
With everything reconnected, the docker click started working
immediately. Added $mod+Shift+d (sway/config) too, matching the exact
convention already used for Wi-Fi/Bluetooth â docker-picker.py had
been waybar-click-only, the same gap those two had before their own
keybindings. Picked up by keybind-search.py automatically. See
docs/ARCHITECTURE.md for the full incident
writeup and the lesson worth keeping in mind going forward.
2026-08-30 (docker-picker.py: tree-connector prefix so multiple containers donât blur together)
Asked whether multiple running containers would get confusing in a flat list. Tested directly rather than reasoning about it in the abstract: started two real containers (postgres, pgadmin) and generated the actual menu â 8 rows total, each containerâs 2 action rows sitting right below its info row. Every action row already includes the full container name (âRestart postgresâ, not just âRestartâ), so thereâs no real ambiguity even out of context, and fuzzy-typing a container name already narrows the list to just its 3 relevant rows â but visually scanning a longer list with several containers running could still blur which action belongs to which container above it.
Added a ââ tree-connector prefix to both action rows, so the
info-row-to-action relationship reads at a glance instead of requiring
you to read every lineâs full text to work it out. Verified by
regenerating the real menu with two containers running and confirming
each containerâs block reads as a clear group. Restored both test
containers to stopped afterward, matching the state they were in before.
2026-08-30 (docker waybar module: nwg-barâs static menu replaced with a real picker)
Asked to improve the docker waybar module â clicking it opened an
nwg-bar modal (Stats / Stop All) with no visibility into whatâs actually
running, âof really no useâ. Same architectural fix already applied to
Wi-Fi and Bluetooth: nwg-bar can only render static buttons from a fixed
JSON file, no dynamic content at all, so scripts/.local/bin/
docker-picker.py replaces it with the same wofi --dmenu picker
approach.
Per running container: one Sky info row (name, image, status, ports â
view-only, selecting it just re-shows the same info, never destructive)
plus two action rows, Peach âRestartâ and Red âStopâ â two different
colors rather than one shared âcommandâ hue, since stop and restart have
a real severity difference worth signaling on sight. âOpen docker statsâ
and âStop all containersâ stay available as top-level shortcuts either
way. Verified the actual actions work, not just that the menu renders:
started a real container, restarted it through the picker and confirmed
via docker ps its uptime genuinely reset, stopped it the same way and
confirmed via docker ps -a it exited â left the system in the same
all-stopped state it was in before testing.
Removed the now-superseded nwg-bar/.config/nwg-bar/docker.json and
scripts/.local/bin/docker-stop-all.sh (docker-picker.pyâs own âStop all
containersâ replaces it) after confirming zero remaining references.
2026-08-30 (system-wide dark mode confirmed + waybar toggle + Papirus icon pack)
Asked to make sure dark mode is the default everywhere, add a waybar
toggle, and add a well-regarded icon pack. Dark mode itself was already
mostly in place (gtk-application-prefer-dark-theme=1, dconf
color-scheme=prefer-dark from an earlier install.sh pass) â the real
gaps were no icon theme ever being set, and no way to switch modes
without hand-typed dconf write commands.
Added papirus-icon-theme (official extra repo â the most widely-used
general-purpose Linux icon theme) + papirus-folders-catppuccin-git to
recolor its folders to this desktopâs existing Mauve accent. Verified the
real AUR package name first â an initial guess doesnât exist on the AUR
at all, found by querying the AUR RPC API directly rather than trusting
memory; the real package is maintained by the official catppuccin AUR
account. Also confirmed (by reading its own source) that
papirus-foldersâs color-switch needs root every single run, so
folder recoloring happens once for both palettes in install.sh (where
sudo is already in use), never at runtime. Added
catppuccin-gtk-theme-latte too â the light counterpart to the
already-installed -mocha package, without which the toggle would have
nothing real to switch to.
New waybar custom/theme module (left of the wallpaper button):
theme-status.sh + theme-toggle.sh, pure dconf reads/writes, no
state file (dconf already persists on its own). Verified the actual
toggle end-to-end: ran it, confirmed via dconf read that
color-scheme/gtk-theme/icon-theme all flipped, screenshotted and found
the waybar iconâs color genuinely changed (Sapphire moon -> Yellow sun),
toggled back, left the system in dark mode afterward.
Scope stated plainly: this covers every app that reads the standard
GNOME/GTK dconf keys (file managers, browser GTK chrome, most GTK3/GTK4
apps) â the actual mechanism behind âapps detect OS dark mode.â Does
not touch this desktopâs own hand-built waybar/wofi/mako/sway
styling, a deliberate scope decision, not an oversight. Qt theming
(qt6ct) left unconfigured â no actual Qt GUI app in this setup to verify
against (beekeeper-studio-bin/bruno-bin are both Electron), disclosed
as a real gap rather than blind-configured. See
docs/ARCHITECTURE.md for the full writeup.
2026-08-30 (wofi text readability: two dead ends, then the real fix)
Asked again to make text more readable (âblack instead of whiteâ) after
the previous pass found the visible white patch was actually icon pixels,
not text. This time it genuinely was #text. Two direct attempts failed
before finding the real cause, both disclosed rather than silently
discarded: literal black text measurably backfired (this windowâs own
base tint is already near-black, so near-black text has ~zero contrast
against it, confirmed by screenshot); a subtitle-style dark outline
around bright text (the actual standard fix for this exact problem)
didnât work either because text-shadow parses without error in this
GTK/wofi build but doesnât render at all (confirmed with a stress test:
zero dark pixels anywhere when an outline should have produced plenty).
Real fix: raised window background opacity 0.62 -> 0.85 (less of the
blurred wallpaperâs brightness variance can bleed through and wash out
text, without removing the blur itself) and text color #A6ADC8 ->
#CDD6F4 (Catppuccin âTextâ, same bright value mako already uses) for
extra margin. Real, disclosed tradeoff: less see-through glass than
before. See docs/ARCHITECTURE.md.
2026-08-30 (app launcher: icon/name gap fixed; the âwhiteâ turned out to be real icon pixels)
Asked for a gap between the app icon and name (too close), and for the
color to be black instead of white. Gap: #img had zero spacing before
this, margin-right: 10px fixes it (confirmed #img is a real CSS node
by testing a temporary bright background first, not assumed).
The âwhiteâ investigation is worth recording since it looked like a bug
at first and wasnât one: found a near-white patch, assumed unstyled
#text, tested with !important (would beat any competing rule) and a
bare label {} type selector â neither had any effect. Isolated the
patch by shape/position instead of guessing more selectors: itâs real app
icon pixel data, not text â raster icons arenât recolorable by CSS
color at all. Gave #img a dark rounded backing instead
(rgba(17,17,27,0.5), matching how GNOME Activities/macOS Spotlight
handle the same problem), which helps icons with real transparency but
canât repaint pixels an icon already drew opaque â disclosed as a real,
partial fix (near-white pixel count: 206 before, 209 after, i.e.
unchanged for opaque-background icons) rather than claimed as fully
solved. See docs/ARCHITECTURE.md.
2026-08-30 (wofi color-palette consistency audit: one bug fixed, one gap filled)
Audited every wofi-invoking script for palette consistency plus a general
bug/improvement look. Most already matched (wifi-picker.py/bluetooth-
picker.py already share the exact Sky/Teal/Peach system and wofiâs own
#313244 surface color). Two real fixes:
bluetooth-picker.pyâs âEnable Bluetoothâ entry had a hardcoded#FAB387span instead of readingCATEGORY_COLORS["command"]â same value, but a second copy that would silently drift if the palette ever changed. Fixed to read from the shared dict.keybind-search.pyhad zero color-coding despite having two real categories (Sway/Tmux) to distinguish â the one wofi popup in this desktop without it. Added Sky/Teal source-tag coloring (same hues as the other pickers) plus Peach for the[scope]suffix. Verified this doesnât break--matching fuzzy(the one flag none of the other pickers use) with a controlled test: piped markup-containing entries into wofi directly, searched a query that only matches one entryâs visible text, confirmed via the actual selected output that fuzzy matching operates on rendered text, not the rawspantags.
See docs/ARCHITECTURE.md for the full writeup.
2026-08-30 (keybind-search.py: truncate descriptions, fixing right-side overflow)
Reported the keybind search popupâs right side overflows. Real cause: a
couple of the sway descriptions built by this tool are enormous â the
exit-sway confirmationâs swaynag message came to 243 characters,
~1750px at this font, well past the 1152px (60%) window. wofi has no
ellipsize/wrap control exposed for --dmenu entries (checked --help
and the binaryâs own exported symbols). Truncates in the script itself
now (MAX_DESC_LEN = 78, appends âŠ) â longest entry after the fix is
123 chars, was 243. Verified by pixel-checking the popupâs right edge in
a screenshot: nothing beyond background level 2px past the window
boundary. See docs/ARCHITECTURE.md.
2026-08-30 (wofi: fixed â left border was invisible on every entry, always)
Reported left/right edges of rows/cells werenât visible properly. Real
bug: an earlier revision force-set border-left: 2px solid transparent
on every entry (reserving space for a selected-state accent bar without a
layout jump), which made the left edge invisible at rest on every
entry, not just unselected ones â in the grid, one cellâs visible right
border next to the next cellâs invisible left border read as a broken,
asymmetric gutter. Fixed by dropping the override and keeping border
width constant (1px) in every state, differentiating selected by color
alone (#CBA6F7, full strength) instead of width â simpler than what it
replaced, and needs no layout-jump guard since nothing changes size.
Verified by comparing a cellâs right border against its neighborâs left
border pixel-for-pixel: now symmetric, not just present.
2026-08-30 (wofi: real gutters between entries, for every wofi tool)
wofi has no dedicated row/column-spacing option, so this is margin: 5px
on #entry itself â the standard GTK-CSS way to create gaps between
FlowBox children. Shows up as real gutters in the app launcherâs grid and
as breathing room between rows in every other wofi toolâs list, since
they all share the one style.css. Verified with the same real-keypress
method the app-launcher-grid bug required (ydotool-simulated $mod+d
and $mod+Shift+w, not a manual shell reproduction): confirmed the grid
still fits 4 real columns with the new margin, and a real ~11px gap now
exists between list rows.
2026-08-30 (the real bug: a bare comma inside exec, silently dropping âcolumns 4)
Reported a second time that $mod+d still showed a single column. Every
prior check had tested the wofi command directly in a plain background
shell, which never goes through swayâs own exec parsing at all â the
real $mod+d keybinding does, and had never actually been tested that
way. Simulated a genuine keypress with ydotool (kernel-level input
injection, not a manual reproduction) and compared the actual running
processâs command line against what was configured: real presses were
launching wofi --show drun â no --columns 4 at all.
Root cause: swayâs config syntax uses a bare comma to chain multiple
commands on one bindsym line, and execâs argument isnât exempt â
wofi --show drun,run --columns 4 was being silently truncated to wofi
--show drun by sway itself before wofi ever saw it, dropping --columns 4
along with everything after the comma. This is exactly why the fix looked
correct at every prior check and still didnât work: the flag never reached
wofi in the first place, so no CSS/width change could have fixed it.
Fixed by dropping ",run" from $menu â --show drun alone has no
comma to trip swayâs parser. ârunâ mode (arbitrary $PATH executable by
typed name) wasnât part of what was asked for anyway. Reverified with the
same real-keypress method, twice: $mod+d now genuinely launches wofi
--show drun --columns 4.
Also moved width=60% into the shared wofi/config per request (every
wofi popup in this desktop, not just two of them) â removed the
now-redundant --width 60% that had been repeated on both $menu and
keybind-search.pyâs own invocation, so thereâs exactly one place
controlling it. See
docs/ARCHITECTURE.md for the full writeup,
including the general lesson: testing a command directly is not proof the
same command works when run through exec from a keybinding.
2026-08-30 (fix: app launcher grid wasnât actually 4 columns; entry borders were too faint)
Reported the app launcher still showed a single column. The earlier
verification pass was genuinely wrong: checked for âcontent blocks with
gapsâ in a screenshot and called it done, but never counted actual
columns. Redid it properly (clustering content by x-position per row) and
found the real bug â at the shared configâs 600px default width,
--columns 4 only ever produced 2 real columns, since real app names
(âBeekeeper Studioâ) are wide enough that GTKâs column count is a ceiling
that width constrains, not a forced number. Fixed by widening the
launcher specifically (--width 60%, only on sway/configâs $menu, not
the shared wofi/config) â reverified with the same clustering check and
found 4 genuine, evenly-spaced columns this time.
Also added a real border to every entry, not just the existing left accent bar on selection â asked for so grid rows read as distinct tiles. Same lesson repeated: first attempt (12% alpha) parsed cleanly but was nearly invisible once actually screenshotted, not âproperly aestheticâ as asked just technically present. Raised to 28% at rest / 35% hover / 45% selected, confirmed this time by finding a real brightness jump at each entryâs border row in a screenshot rather than trusting the CSS had no syntax errors as proof it looked right.
2026-08-30 (wifi/bluetooth pickers get keybindings; keybind search resized; app launcher grid)
Three follow-ups to the wofi glass work above:
$mod+Shift+w(Wi-Fi picker) and$mod+Shift+b(Bluetooth picker) â both scripts already existed and worked fine, just had zero keyboard path, waybar-click-only. Automatically shows up inkeybind-search.pywith no extra work (the whole point of reading live config), but hit the same multi-line-comment gotcha its own entry did earlier â fixed the same way, clean one-line description directly above eachbindsym.keybind-search.pyâs wofi popup:--width 60%(percentage width is real, confirmed viaswaymsg -t get_outputsechoing back the literal pixel math, 1152 on this 1920px output) and--lines 15->12(measured extent 647px -> 536px tall, ~17% shorter).$mod+dâs app launcher:--columns 4, added only tosway/configâs$menuvariable, not the sharedwofi/configâ every other wofi-backed tool in this desktop is a single-column list and a grid would break it. Confirmed live that wofiâs grid genuinely reads as uneven/organic (GtkFlowBox sizes each entry to its own content, not a fixed cell) rather than a uniform card grid, by screenshotting and checking for real gaps between discrete content blocks at multiple row heights.
See docs/ARCHITECTURE.md for the full writeup.
2026-08-30 (wofi: real glass â compositor blur, not just a tinted box)
Asked to make wofi look more âglassâ, better UI/UX generally. Recognized
first that a semi-transparent background-color alone (0.95 alpha before
this, basically opaque) was never going to read as real glass, and that
GTK3âs CSS engine has no backdrop-filter â thatâs not achievable in
wofiâs own stylesheet at all. The actual fix: this desktopâs own sway build
is SwayFX (scenefx0.4, already in packages/aur.txt, already used for
window shadows/corner-radius), which supports real compositor-level blur
on specific layer-shell surfaces by namespace â just never wired to wofi.
Confirmed wofiâs real namespace live (swaymsg -t get_outputs while it was
open) rather than assumed, added layer_effects "wofi" { blur enable;
blur_xray enable; corner_radius 16; shadows enable; } to sway/config.
Verified itâs actually applied two ways: the compositorâs own IPC echoes
back "blur": true, and a direct pixel test found the screen region behind
an (temporarily near-transparent, for testing) wofi window rendered 4x
smoother than the same region without it open.
With real blur behind it, rebuilt wofi/style.css to let it show through
instead of hiding it: background alpha 0.95 -> 0.62, a faint top-edge sheen
gradient (the light-catching highlight a real glass panel shows), a
brighter hairline border on the search input than the outer window so it
reads as sitting closer to you than the glass pane behind it, and a
properly restyled scrollbar (was completely unstyled GTK-default light
gray before â about as glass-breaking a seam as it gets). Also real UX
gaps, not just look: #entry:hover didnât exist at all before (zero mouse
feedback, only keyboard :selected), and selection gained a left accent
bar alongside its existing background wash. wofi/configâs image_size
24 -> 32 for better icon hierarchy in drun. Every wofi-backed tool in
this desktop (launcher, wifi/bluetooth pickers, calculator, emoji picker,
cliphist, networkmanager-dmenu) shares this one stylesheet, so all of them
get this at once. Tested both --show drun and --show dmenu directly,
checked stderr for CSS parse warnings on each (none) before trusting any
of it. See docs/ARCHITECTURE.md for the full
writeup.
2026-08-30 (notification beautification: real icons on every notify-send call)
Asked to make notifications more aesthetic, specifically with icons for
whatever triggered them. Audited every notify-send call in the repo
(grep -rn notify-send scripts/ sway/) â several already had one (docker,
caffeine, the wallpaper/screenshot success cases use the actual resulting
image), most didnât, including two that fire constantly: volume and
brightness OSDs had no icon at all.
Added a real freedesktop icon-theme name to every remaining call, each
confirmed present in the actually-installed icon theme first (only
adwaita-icon-theme/hicolor-icon-theme are installed here â no
Papirus/Catppuccin icon pack, so stuck to whatâs real rather than assuming
a name exists). Volume got the same âtiered by actual stateâ treatment as
waybarâs battery icons (audio-volume-{muted,low,medium,high}-symbolic
by percentage) instead of one static glyph; also dropped a redundant inline
Nerd Font mute glyph from the notification body now that a real icon
carries that signal. Bluetoothâs icon follows urgency (error vs. active).
Wallpaperâs fallback/failure notifications now visually distinguish soft
warnings from hard failures, matching their actual urgency levels which
were already different but only readable in the text before.
mako/.config/mako/config got max-icon-size=48 + icon-border-radius=8
to match the rounded-pill look used everywhere else in this desktop â
makoâs icons render sharp-cornered at native size by default otherwise.
Verified live, not just by reading makoâs docs: fired real test
notifications after each change, screenshotted, and confirmed real
non-background pixel content actually appears in the iconâs expected
position â an icon name that fails theme lookup fails silently (blank
space, no error), which is exactly the kind of thing that looks fine in
the config file and wrong on screen. See
docs/ARCHITECTURE.md for the full icon-by-icon
breakdown.
2026-08-30 (new feature: fuzzy keybinding search, $mod+Shift+slash)
Asked for a wofi popup to fuzzy-search every keybinding on the system (sway + tmux, deliberately excluding Neovim â it already has its own keymap search) by name/keyword, case-insensitive.
scripts/.local/bin/keybind-search.py, bound to $mod+Shift+slash (matches
the near-universal ? / Ctrl+/ âshow shortcutsâ convention â Slack, Discord,
GitHub, GNOME). Both sources are re-parsed from the real live config on every
run rather than a separate hand-maintained list â picked this over the
alternative specifically because a second list is exactly the kind of thing
that quietly drifts out of sync with reality, the same failure mode as the
TPM plugin-path split found in an earlier audit pass. See
docs/ARCHITECTURE.md for the full design writeup,
including two real parsing bugs found and fixed by checking actual command
output instead of trusting the documented format (tmuxâs -N compact view
mislabeling root-table binds as needing the prefix; an early versionâs
custom-note lookup bleeding a prefix-table note into an unrelated same-letter
copy-mode binding), a fuzzy-search gap fixed along the way (bare-comment
descriptions like âSwitch to workspaceâ couldnât match a query typed as
âworkspace 1â since the â1â only existed before the word in that entry â
now every entry appends its actual command for exactly this reason), and why
the final wl-copy call is a detached Popen rather than subprocess.run
(confirmed directly: wl-copy sets the clipboard instantly but the process
itself lingers afterward by design, to keep serving the selection â waiting
on it would hang the script pointlessly after every use).
Gave tmux.confâs own custom binds real -N notes as part of this (was bind
s display-popup ..., now bind -N "Fuzzy-find and switch tmux session" s
...) â a genuine side benefit, not just plumbing for this script: tmuxâs
own prefix + ? (list key bindings) now describes this repoâs custom binds
too, not just its stock ones.
Also fixed, unrelated but found along the way while testing: this repoâs
~/.local/bin/wl-paste --type text --watch cliphist store background
watcher had been killed (by an earlier debugging step in this same session,
chasing what turned out to be the wl-copy lingering-process behavior above,
not a real bug) and not restarted â caught and fixed immediately, since itâs
a real, already-established feature ($mod+m clipboard history) this
shouldnât have been left broken.
2026-08-29 (full repo audit: tmux plugin path split fixed)
Broadened the previous audit to the whole repo. First finding: tmux/.config/
tmux/plugins/ was sitting untracked in the repo tree (tmux-continuum,
tmux-resurrect, and a second copy of tpm, each with their own nested .git
dirs) even though install.sh clones TPM to ~/.tmux/plugins/tpm and nothing
in this repo ever mentions ~/.config/tmux/plugins/. Almost deleted it outright
as accidental vendored content, matching the lock.sh pattern from the previous
audit â caught the mistake by tracing why install_plugins.sh reported
âAlready installedâ for plugins that didnât exist at ~/.tmux/plugins/: TPMâs
own bootstrap script auto-redirects TMUX_PLUGIN_MANAGER_PATH to ~/.config/
tmux/plugins/ whenever it detects an XDG-style tmux.conf â which this repoâs
stow layout always produces. The content was real and live (tmux.serviceâs
ExecStop was already correctly pointing at it), not dead code.
Fixed at the source instead of papering over it: pinned TMUX_PLUGIN_MANAGER_PATH
explicitly in tmux.conf (TPMâs own documented override, placed before the
@plugin lines) to ~/.tmux/plugins/, migrated the real tmux-resurrect/
tmux-continuum content there, discarded the redundant duplicate tpm clone
(the real one already lives at ~/.tmux/plugins/tpm), removed the now-empty
tmux/.config/tmux/plugins/ from the repo, and updated systemd/.config/
systemd/user/tmux.serviceâs ExecStop path to match. Verified with a real
tmux session: TMUX_PLUGIN_MANAGER_PATH resolves correctly, install_plugins.sh
finds all three plugins in the new location without re-cloning, and
tmux-resurrectâs save.sh runs cleanly from it. See
docs/ARCHITECTURE.md for the full writeup.
2026-08-30 (network speed indicator: real bug found in how Iâd been âverifyingâ it)
Reported the previous passâs 160px change hadnât actually changed anything visible. Investigated rather than re-guessing a number, and found a real process bug in my own verification, not just the CSS: waybar had been running the same PID since 23:27 the entire time â every earlier âreloaded live, same PID, no crashâ claim in this sessionâs changelog was actually confirming the opposite of what it said. Waybar does not hot-reload config/style.css from a file-watcher (that was an unverified assumption, stated as fact); it needs an actual process restart. Every style/font/padding change from the last two passes had been sitting correct in the files but never live on screen until now.
With that fixed (pkill waybar + relaunch, confirmed via a fresh PID each time),
re-measured the network speed module for real instead of estimating: temporarily
forced its format to a literal 123.12KB on both sides (the exact worst case
asked for), screenshotted, and located the true rendered bounds by color-matching
the format stringâs own Green (#A6E3A1)/Peach (#FAB387) span colors pixel-by-
pixel rather than eyeballing â download span 386-454px, upload span 470-538px,
153px of real content between them. With the shared 12px/side padding this
moduleâs id also picks up, natural pill width at that exact worst case is
12+153+12 = 177px â bigger than the previous 160px guess, meaning 160 was
actually too narrow for â123.12â on both sides, not too generous.
Set min-width: 177px â fits the stated worst case exactly, no slack beyond it.
Re-measured current real traffic the same way afterward: 124px of content right
now (148px natural width), meaning there will still be visible empty space during
normal/light traffic, since a fixed min-width is a floor that canât shrink below
itself â thatâs what reads as âtoo much paddingâ day-to-day, and itâs the
necessary cost of the pill never growing/jittering when traffic actually reaches
three-digit KB values on both sides, which was the explicit requirement. Flagged
this trade-off directly rather than picking a number that quietly abandoned one
side of the request.
2026-08-30 (network speed indicator narrowed to match the new smaller font)
#network.speed, #network-speedâs min-width: 200px -> 160px. That value was
originally set by measuring the moduleâs actual live rendered width at the barâs
old 15px font (177px) and adding headroom to stop it jittering as digit counts
change (12KB vs 1.2MB vs 999B). Rather than re-measuring from scratch, scaled it
by the same ratio the font-size change in the previous pass introduced: JetBrains
Mono is monospace, so glyph advance width scales ~linearly with font-size â
200 * (12/15) = 160. Reloaded live (waybarâs own file-watcher, same PID
afterward, no crash) rather than assumed.
2026-08-30 (narrower workspace buttons, one font size across the whole desktop)
#workspaces buttonpadding:0 8px->0 5px. Same three states (idle gray / hover Lavender / focused Red underline) and layout-jump guard (transparentborder-bottomreserving space so nothing shifts on hover), just narrower chips â the ask was width, not anything else about them.- One font size (12) across every config that sets one explicitly, instead
of each app carrying whatever its own default happened to be:
kitty15 -> 12 (this one was already sitting as live, uncommitted drift from before this session touched anything â formalized it into the repo rather than fighting it, since itâs exactly the value being standardized on anyway)waybar(global bar text) 15px -> 12pxzedui_font_size/buffer_font_size/terminalfont_size15.0 -> 12.0wofi16px -> 12pxnwg-bar(power menu) 15px -> 12pxmako(notifications) 10 -> 12sway(font pango:, window title text) 10 -> 12
Deliberately left untouched:
sway/.config/sway/lockconfigâsfont-size=80. Thatâs swaylockâs big on-screen clock/PIN-attempt display, not body text â a fundamentally different UI role (glanceable from a few feet away, not read up close), and normalizing it to 12 would make the lock screenâs whole reason for a large font pointless. No GTK-wide font-name override exists in this repo for eithergtk-3.0/gtk-4.0settings.ini, so nothing there needed changing.12 isnât an unreasonable size on its own â itâs a common, legible terminal/ UI size and matches what was already live in kitty â but it is on the smaller side for extended reading specifically in
zedâs buffer font and the waybar text glanced at across a room; worth a quick look after this lands and bumping just those two back up first if it feels cramped, rather than assuming the whole set needs to move together again.
Reloaded live to verify nothing crashed: swaymsg reload (font pango line),
waybar (auto-reloads config/style.css via its own file-watcher, still running
same PID afterward), makoctl reload. No visual regression tooling available
in this session to pixel-verify beyond that, but every change here is a plain
numeric font-size/padding value, not a codepoint or anything else with a
history of silent corruption in this repo.
2026-08-29 (docker cruft reclaimed; .pacnew/.pacsave files analyzed)
Followed up on the two items flagged earlier.
Docker, done directly (no sudo needed, yash is already in the docker group):
inspected exactly which volumes each stopped container actually references before
touching anything â postgres -> named volume postgres-data, pgadmin -> one
anonymous volume, both still âin useâ from Dockerâs own point of view even while
their containers are stopped, so neither docker volume prune nor manual removal
would ever touch them. The other 5 volumes had zero container references at all
(running or stopped) â confirmed, then removed. Same check for images: alpine
and nginx had zero containers referencing them (not even a stopped one), while
postgres:18/postgres:latest (same image ID) and dpage/pgadmin4:latest are
still the exact images the stopped containers point to. Removed alpine/nginx
(~254MB) and the 5 orphaned volumes (~66MB). Containers, their real volumes, and
the images they actually reference are all untouched â docker start postgres/
docker start pgadmin still works exactly as before.
.pacnew/.pacsave files, diffed and analyzed (system has no pacdiff â
pacman-contrib isnât installed â so diffed each manually against its live
counterpart):
/etc/pacman.d/mirrorlist.pacnewâ do not apply. The live file has real, working, hand-picked India-region mirrors; the.pacnewis upstreamâs blank template with every server commented out. Applying it would leave pacman with zero usable mirrors. Recommendation: just delete the.pacnew./etc/bluetooth/main.conf.pacnewâ safe to apply. Only real live customization is an explicitAutoEnable=true; the.pacnewcomments that same line out, but its own documented default is alsotrue, so behavior is unchanged either way. Everything else added is a new, fully-commented[ChannelSounding]section â inert until someone opts in. Recommendation: adopt the.pacnew./etc/locale.gen.pacnewâ do not apply. It comments outen_US.UTF-8, which the live file has active and generated. Applying it wouldnât break anything immediately, but a futurelocale-genrun would stop regenerating that locale. Recommendation: delete the.pacnew, keep the live file as-is./etc/greetd/config.toml.pacsaveâ fully orphaned.greetdisnât even installed anymore (pacman -Q greetdâ not found) andsddmis the actual enabled display manager; the liveconfig.tomlthis.pacsavewas saved alongside doesnât exist either. Recommendation:sudo rm -rf /etc/greetd.
Handed off as exact commands (see below) rather than applied â all four live
under /etc, root-owned, outside this sessionâs access.
2026-08-29 (verified: the handed-off sudo cleanup ran clean)
User ran the two hand-off commands from the pass above (pacman -Rns on the 12
orphans, pacman -Sc). Verified the result rather than assuming it went fine:
/var/log/pacman.logâs transaction shows exactly the 12 flagged packages removed
plus 4 of their own now-orphaned sub-dependencies (wayland-protocols,
python-tqdm, ninja, libmfx), zero errors/warnings, clean completion. Disk
38G -> 24G used. pacman -Qdtq (orphans), pacman -Qk (missing package files),
and pacman -T $(pacman -Qqe) (unsatisfied deps) all come back clean. Confirmed
the non-debug counterpart of every removed -debug package is still installed
and actually running (swayfx, swaylock-effects, sway-audio-idle-inhibit-git,
etc. â -debug packages only ever contained split-out debug symbols, never the
real binary). systemctl --failed clean at both system and --user scope. Repo/
stow state unchanged. Docker was untouched, as recommended â still needs a
manual look before anything there gets pruned.
2026-08-29 (broader cleanup: empty repo dir, npm cache, system cruft surveyed)
Asked to look at the whole setup for stability/reliability again and remove anything unnecessary. Repo/stow/systemd health re-checked clean (no regression since the stability pass above). Widened the sweep to actual disk/package cruft this time, not just config correctness:
Fixed directly (safe, no data loss possible):
zed/.config/zed/themes/was an empty directory sitting in the repo tree, genuinely untracked by git (git canât represent empty dirs, sogit statusnever showed it) â a fresh clone + stow would never recreate it, meaning it did nothing for anyone; almost certainly leftover from Zedâs own first-run config scaffold getting vendored wholesale when thezedpackage was created. Removed.~/.npmcache was 312M, entirely regenerable. Cleared withnpm cache clean --force, down to 124K.
Surveyed, not touched â needs sudo (outside this sessionâs access) or a
judgment call about real data this session canât make safely:
- 12 orphaned pacman packages (
pacman -Qdtq):bruno-bin-debug,ffmpeg4.4,go,meson,mongosh-bin-debug,scdoc,sway-audio-idle-inhibit-git-debug,swayfx-debug,swaylock-effects-debug,wlogout-debug,wlroots0.20,yay-debugâ installed as dependencies, nothing currently requires them.sudo pacman -Rns $(pacman -Qdtq)removes them; worth a quick skim of the list first since âorphanedâ means ânothing depends on it right now,â not âdefinitely never wanted.â /var/cache/pacman/pkg/is 8.6G, including a couple dozendownload-*temp directories this session couldnât even read (root-owned) â normal pacman cache accumulates every version ever installed, not just the current one.sudo pacman -Scclears everything except currently-installed versions;yay -Scfor the AUR cache too. Installingpacman-contribgets youpaccache -d(dry-run preview) if a look-before-you-leap pass is preferred over pacmanâs built-in prompt.- Docker has real reclaimable space, but some of it may be wanted data:
docker system dfshows 2 stopped containers (postgres, exited 2 weeks ago;pgadmin, exited 2 months ago â both clean exits, not crashes) and 2 fully dangling images (alpine,nginxâ zero containers using either).docker image pruneis safe (only removes untagged/dangling images, and none of the 5 images here are actually dangling in that sense â so itâd be a no-op; the reclaimable size docker reports is against currently-unused-but-tagged images, which needs-aand is exactly the kind of thing worth a manual look first). Deliberately did not rundocker container pruneordocker volume prune: 5 of 7 docker volumes are unattached, and at least one is very likely thepostgrescontainerâs actual database files â pruning containers/volumes blindly could permanently delete a real local database with no backup. Left for the user to review withdocker volume ls/docker inspect postgresbefore deciding.
Journal (322.7M total, 103.5M user-level) and the wallpaper archive (60/60 files,
self-capped at MAX_ARCHIVE_FILES, 20M total) were both already within normal,
self-managed bounds â nothing to do there.
2026-08-29 (stability pass: missing symlink, dead archive symlinks, a false alarm chased down)
Asked to check for âcorrupt filesâ and confirm the whole system is stable. Checked
systematically rather than guessing: git status, a repo-wide broken-symlink scan
(find ~ -xtype l), every systemctl --user unitâs actual state + journal, and
system-level failed units / disk / memory. Two real, fixed issues, and one thing
that looked broken but wasnât:
tmux.serviceâs stow symlink was simply never created.~/.config/systemd/ user/tmux.servicedidnât exist at all, even thoughsystemd/.config/systemd/user/ tmux.serviceis a real, correct file in this repo (and got edited earlier today for the plugin-path fix above) âstow -n -v -t ~ systemdconfirmed it as a pendingLINK, not a conflict, so it had just never been linked, probably becausestow systemdwas last run before this particular unit file existed in the package. Harmless while the unit stays intentionally un-enabled (perinstall.sh), butsystemctl --user enable --now tmux.servicewould have failed outright with âunit not foundâ if anyone tried it. Fixed withstow -R -t ~ systemd; confirmedtmux.servicenow showsLoaded: loaded ... linked(still correctly notenabledâ the restow only fixes the missing symlink, doesnât opt it in).- Five dead, broken symlinks inside
~/Pictures/Wallpapers/Archive/(dated Jun 7, Jun 14, Jun 15, Aug 12, Aug 23 â clearly debris from earlier iterations offetch_wallpaper.shâs archiving logic, predating the current-type fguard in the archivefind/mvstep, which correctly excludes symlinks going forward). Each pointed at a plain-date-named file (Active/wallpaper-20260607.jpgetc.) that no longer exists there, sinceActive/only ever holds the current dayâs file. Confirmed genuinely dead viareadlink -fbefore deleting, and confirmed no other corruption in the wallpaper pipeline while at it: scanned every real archived.jpgwithfile --mime-typefor the historical âJSON blob saved as .jpgâ bug (documented above) â none found,is_valid_image()is doing its job. Deleted the 5 dead links; 55 genuine archived wallpapers untouched. - False alarm, chased down rather than assumed:
swayidle.serviceshowed as crash-looped in the journal (status=253, âUnable to connect to the compositorâ) right after this boot, then landedinactive (dead). Looked like a real bug at first glance. Traced it fully: the crash-loop itself is the already-documented, harmless startup race (swayidle.serviceisWantedBy=default.targetso systemd auto-starts it before swayâs owndbus-update-activation-environment --allhas run) â and the reason itâs staying stopped afterward isnât a bug at all:~/.local/state/caffeine/enabledexists (caffeine mode has been ON since 2026-08-28 16:37), soswayidle-startup.shis correctly stopping it every login, exactly as designed. Confirmed viaWAYLAND_DISPLAYbeing present insystemctl --user show-environmentand a clean manualswayidle -C ...run succeeding outside the race window. Not fixed, because it isnât broken â flagged to the user instead: caffeine mode has quietly been on for over a day, meaning idle lock/dim/suspend have been off that whole time, which may or may not be intentional.
System-level check: no failed system units, 406G free disk, no swap pressure, 9.6G RAM with 7.8G free. Nothing else needed attention.
2026-08-29 (full repo audit, continued: docs staleness, a second dead ordering directive)
Read through every remaining config in the repo (mako, wofi, nwg-bar,
networkmanager-dmenu, zed, gtk, xdg, docker security docs, all systemd units, every
script under scripts/.local/bin/ and sway/.config/sway/scripts/) looking for the
same two things as the tmux fix above: redundant/uncoordinated config, and stale docs.
Most of it checked out clean â well-commented, internally consistent, no dead code.
Two real findings:
- README.mdâs repository-structure tree and manual
stowcommand example were missingnetworkmanager-dmenuâ a fully-wired package (confirmed correct indocs/ARCHITECTURE.mdâs package-map table andpackages/pacman.txtalready), just never added to these two illustrative lists when the package was introduced. The âstow everythingâ catch-all command in the same section already picks up new packages automatically (find . -maxdepth 1 -mindepth 1 -type d ! -name packages ! -name docs ...), so this was cosmetic/doc-only, not a functional gap â but a reader following the manual per-package example wouldâve missed it. Added it to both lists. wallpaper.serviceâsAfter=network-online.targetis itself a no-op for the same underlying reasongraphical-session.targetwas found to be one in the previous audit pass:systemctl --user list-unit-files network-online.targetreturns zero unit files â itâs a system-level target never instantiated in the--usermanagerâs namespace, so ordering against it is accepted but enforces nothing. Not a functional bug in practice âfetch_wallpaper.shâs ownnmcliconnectivity check, download timeouts, and three-tier fallback already fully cover ânetwork isnât up yetâ â but the unit fileâs own ordering directive was misleading about where that protection actually comes from. Documented indocs/ARCHITECTURE.mdrather than deleted, since it costs nothing and still states the intent even though it doesnât enforce it.
2026-08-29 (desktop audit: dead lock script removed, wallpaper boot race fixed)
Asked to audit the wallpaper/lock screen setup specifically for redundancy and boot reliability. Two real, concrete findings, not just a general tidy-up:
Dead code: sway/.config/sway/scripts/lock.sh was a second, completely
different swaylock invocation (small plain ring, no theming, no clock) sitting
next to the real one (lockconfig, Catppuccin-themed, used by all four actual
lock trigger points: the manual keybind, the idle timeout, before-sleep, and the
power menuâs Lock button). Confirmed it was referenced nowhere with an exact grep
for the literal filename â an earlier sloppier attempt with an unescaped . in
the pattern had falsely matched prose like âpadlock shapeâ in this very
CHANGELOG, which is exactly the kind of false-confidence a lazy grep produces.
Deleted, re-stowed to clean up the dangling symlink it left in ~/.config/sway/
scripts/.
Wallpaper boot race: wallpaper.timerâs Persistent=true catches up a
missed daily run on boot, but wallpaper.service is only ordered After=
network-online.target â nothing ties it to sway actually being up yet.
graphical-session.target looked like the fix, checked before assuming itâd
help (systemctl --user status graphical-session.target): real loaded unit,
stays inactive (dead) the entire session, since nothing in this sway config
ever activates it â wouldâve been a no-op dependency. A boot-time catch-up run
could genuinely race swayâs own startup and hit apply_background()âs single
instant socket check before the IPC socket exists â current.jpg still gets
updated on disk either way, but the live swaymsg apply would silently give up,
leaving yesterdayâs wallpaper showing until a reload that never happens on its
own. Fixed with a 20s retry loop instead of one check â costs nothing in the
normal case (manual click, or the timerâs ordinary daily run well after login),
where the socketâs already there and it returns on the first try.
Also traced whether a genuinely from-scratch install would work at all:
install.sh enables the wallpaper timer before sway has ever started, but
Persistent=trueâs standard systemd semantics fire an immediate catch-up run in
that case, which reaches current.jpgâs symlink creation regardless of whether
the live apply succeeds â so swayâs static output * bg current.jpg fill line
should find a real file on its very first startup, not a dangling reference. Not
independently verified with an actual fresh install (same documented gap the
rest of install.sh already has) â rests on documented systemd behavior, not a
fresh empirical test.
2026-08-29 (wallpaper button was always fetching the exact same image)
Reported clicking the wallpaper icon repeatedly always produced the same
wallpaper. Verified the actual cause rather than assuming the script was
broken: curled the fixed API URL (index=0, mkt=en-US) twice a few seconds
apart and compared md5sums â identical. Bingâs âwallpaper of the dayâ for a
given index+market is genuinely static for the whole day; WALLPAPER_URL
being one hardcoded URL meant every click just re-downloaded the same bytes
no matter how many times it ran.
Also verified what actually does vary it before building a fix around
assumptions: index (0-7ish, recent past days) changes the photo, and so
does mkt (country/locale) â tested six real markets for the same real day
and got three distinct images (en-US/de-DE/fr-FR shared one, en-GB had a
different one, ja-JP/zh-CN shared a third). Matches what was actually asked
for â âwallpaper of the day⊠from other countries.â
Replaced the fixed URL with wallpaper_url(), called with a random recent
index (0-3, stays fresh rather than reaching deep into Bingâs archive) and a
random market from a list of ~20 real ones. Re-rolled on every retry
attempt too, not just once per run, so a validation failure doesnât waste
three attempts hammering the same bad combination.
Verified live with three real consecutive runs (not just reasoning about the code): got index=3/mkt=ja-JP, index=2/mkt=ja-JP, and index=2/mkt=it-IT â three different combinations producing three different md5sums between them, versus the old behaviorâs one fixed hash that wouldâve stayed identical all day regardless of how many times it was clicked. Filenames now include the chosen index+market too, so multiple fetches in one day land as distinct archived files instead of colliding on a date-only name.
2026-08-28 (wallpaper icon padding: fa-image sits off-center in its own cell)
Reported the icon had visibly more space on its left than its right. Measured
rather than guessed, same discipline as the caffeine iconâs own asymmetric
padding fix earlier in this repo: at the shared symmetric 12px/12px padding
every module gets by default, the glyphâs actual ink sat with a 12px gap on
its left but only 7px on its right (pixel-sampled the pillâs background
boundary against the glyphâs own ink boundary) â fa-imageâs glyph has
more built-in left-side bearing than right in this font, same class of issue
as the caffeine coffee-cup icon, just the opposite direction.
Shifted to padding-left: 9px / padding-right: 15px (a 3px correction each
way off the 12/12 midpoint, close to the ~2.5px an even split of the
measured 5px imbalance would call for). Verified the shift moved in the
right direction: re-measured after, the ink moved left by exactly 3px,
matching the padding-left reduction precisely â confirms the mechanism is
working as intended. Couldnât get a fully clean final left-gap-vs-right-gap
verification, though â this module sits close enough to the docker pill
right next to it (2px margin, deliberately tight) that a background-color
scan picks up both pills as one contiguous region with no way to
automatically tell where one ends and the other begins. Flagged as a real
measurement limitation rather than claiming a precision this method
couldnât actually deliver â the direction and rough magnitude are verified,
not a pixel-perfect final balance.
2026-08-28 (wallpaper icon swap: fa-image instead of md-wallpaper)
md-wallpaper didnât look good â swapped for fa-image/fa-picture-o
(f03e), the classic mountain-and-sun-in-a-frame glyph. Simpler than the
MDI wallpaper icon, and matches the FontAwesome convention most of this
barâs other icons already use (wifi, bluetooth, lock, battery) rather than
introducing yet another icon family for one button. Verified present in the
font first, same discipline as every other icon in this repo.
Verified live: pixel-sampled the pill boundary again at the same position (x1430-1444) â still a clean dark pill bounded by real wallpaper colors on both sides, Rosewater renders correctly, higher pixel count than the old icon (59 vs 24), consistent with a simpler, bolder glyph.
2026-08-28 (wallpaper refresh icon in waybar, left of docker)
New custom/wallpaper module, placed right before custom/docker in
modules-right as asked. Reuses the existing fetch_wallpaper.sh pipeline
directly rather than writing a new one â it already had everything needed
(download, retry, real image validation, safe fallback, flock so a manual
click canât race the daily timer), just needed a trigger and some feedback.
Added notify-send calls at the points that matter â start (âFetching a
new oneâŠâ), success (using the new wallpaper itself as the notification
icon via -i), fallback used, and total failure â since the script used to
be silent, log-file only. Fine for an unattended daily timer, not fine for a
manual click that gives zero feedback for the several seconds a real
download takes. Benefits the daily timer too, not just this new button.
Icon is md-wallpaper (f0e09), a genuine dedicated âwallpaperâ glyph,
colored Rosewater â the one hue in this palette that was still completely
unused anywhere in the bar, fitting for a personalization action rather than
reusing a status color that already means something else. No exec/
interval on the module at all â confirmed via man waybar-custom that
exec isnât mandatory, a static format + on-click is a valid custom
module on its own, since thereâs no changing state to poll for a plain
action button.
Caught the same recurring codepoint-corruption issue immediately ("format":
"" landed empty from a raw pasted glyph) and forgot the module wasnât in
any of the shared pill-styling CSS selector lists at first, which left it
rendering as bare unstyled text â added #custom-wallpaper to the
background/border list, the padding-override list, and the hover-accent
list, matching every other module.
Verified thoroughly: pixel-sampled the pillâs actual background boundary
(clean dark tone x1430-1444, real wallpaper colors immediately outside on
both sides â not just eyeballing a noisy full-height ASCII render, which
initially looked ambiguous), and ran the on-click script exactly as waybar
would invoke it (sh -c) â real download succeeded, wallpaper genuinely
changed live on the machine, log confirms the full pipeline ran clean.
2026-08-28 (active workspace matches the focused window border: both Red)
Asked to make the workspace indicator match the focused window borderâs
color, changing the workspace side. #workspaces button.focused switched
from Sapphire to Red, matching client.focused in sway/config â both
âthis is the active oneâ signals now use the same hue instead of two
different âactiveâ colors in different parts of the setup. Mauve stays
untouched as the barâs one hover/interactive accent elsewhere.
Verified live: switched to workspace 1, pixel-searched the workspace region â Red present at exactly the number-glyph and underline positions (y13-33), Sapphire completely gone from that area.
2026-08-28 (backlight icons: real empty-to-full pie fill, not MDIâs sun-ray set)
User reported the dimmest icon (1% brightness) looked like a full circle â
checked this for real rather than assuming the percentage-tiering math was
wrong (it wasnât): set brightness to a genuine 1% via brightnessctl,
screenshotted, and rendered the actual icon as an ASCII brightness map. It
really was a solid, densely-filled disk with zero rays or partial elements â
confirmed the root cause is the icon choice itself, not a tiering bug.
md-brightness_1 (the MDI âsun ray countâ icon this repo picked earlier) is
apparently just a plain solid dot in MDIâs own design language (the âbase,â no
rays yet) â reads as âcomplete/fullâ to anyone who doesnât already know that
convention, the opposite of what a status icon should communicate.
Switched to md-circle_slice_1 through _8 (picked 1/3/5/7/8 for a 5-tier
spread) â a genuine pie-chart fill progression, same visual language as
battery/volume/wifiâs own âempty container gradually fills upâ icons already
used elsewhere in this bar, instead of a ray-count metaphor that doesnât read
the same way.
Verified live, not just by eye: rendered the actual icon as a detailed ASCII brightness map at real 1% (mostly hollow ring, one small filled wedge visible, pixel count dropped from 104 to 10 compared to the old icon at the same brightness) and real 90% (densely filled disk, just a thin unfilled outline remaining) â a genuine, intuitive empty-to-full contrast between the two, confirmed from the actual rendered pixels rather than assumed from the icon names.
2026-08-28 (focused window border: Red, the actually-dominant pick)
Clock time/date (Blue/Maroon) landed well; the borderâs Mauve didnât â asked for something more âdominant.â Red is the single boldest, most commanding hue in this palette. Named the real trade-off before shipping it rather than after: Red is reserved everywhere else in this setup for actual alerts (network disconnected, battery critical, the power button), and this is the fourth different border color tried in a row (Mauve -> Sapphire -> Lavender -> Mauve -> Red) â worth being explicit that a persistent âthis window is focusedâ fixture reads differently from a transient alert pill, rather than silently hoping that distinction holds.
Verified live against the same real window as every prior border-color entry: Red renders at 1245 pixels along the top border (consistent with the 1244 every previous color measured at), Mauve completely gone from that position.
2026-08-28 (royal color trio: window border, clock time, clock date)
Asked for âroyal colorsâ specifically across the window border, clock time, and clock date, and to stop iterating on Lavender for the border. Went with an actual regal color story instead of three unrelated picks:
- Window border -> Mauve. This repoâs own earlier comments already nicknamed it âRoyal Mauveâ â itâs the literal royal purple of this palette. Overrode the earlier concern about it double-booking waybarâs universal hover accent â that reasoning was about avoiding meaning overload, not about beauty, and beauty is what was asked for this time. The two contexts (a persistent window border vs a brief waybar hover flash) donât actually read as confusable in practice.
- Clock time -> Blue, not Sapphire. Sapphire was already tried once on the date in an earlier pass and rejected specifically for reading too close to Sky (network.wifi) despite not sitting adjacent to it â reusing it here wouldâve repeated a mistake this repo already learned from. Blue is a properly saturated, distinct âroyal blueâ without that problem.
- Clock date -> Maroon. Burgundy/wine, historically a royal-robe color, already used elsewhere in this bar (pulseaudio, bluetooth.on) without issue since neither sits near the center clock.
Verified live: pulled the real focused windowâs rect again (same window, same x17/y58 as the last two border-color entries), confirmed Mauve renders at exactly the same 1244 pixel positions Lavender and Sapphire occupied before it. Confirmed Blue and Maroon both land at the clockâs established x-positions (time ~x830+, date ~x928+, matching every prior clock-color verification in this file).
2026-08-28 (focused window border: Lavender, take two on âbeautifulâ)
Asked to just pick something beautiful for the focused window border, rather than continuing to iterate on Sapphire. Considered Mauve first â itâs Catppuccinâs own flagship accent and the most common community choice for exactly this â but it already has a specific job as waybarâs one universal hover/interactive signal, and doubling it here would blur that meaning even though window borders and waybar hovers are physically separate contexts. Landed on Lavender: barely used anywhere else (just the clockâs time text and one workspace hover state), genuinely one of the more elegant hues in this palette, and free to mean one clean thing here â this is the active window. Sapphire stays workspace-only in waybar, untouched.
Verified live against the same real open window as the previous entry (same rect, x17/y58/1886x1005): Lavender now renders at the exact same 1244 pixel positions Sapphire occupied at the top border, and Sapphire itself is completely gone from that location (0 matching pixels) â confirms a clean swap, not a partial/leftover state.
2026-08-28 (focused window border: Sapphire, tighter gaps)
Real sway window borders this time (client.focused in sway/config), not
waybar â distinct from the workspace indicatorâs own earlier Mauve ->
Sapphire change. Same reasoning applied here too: Mauve stays waybarâs one
universal hover/interactive accent, so the focused window (same âthis is
where you are right nowâ meaning as the focused workspace) gets its own
color instead of sharing Mauve for both. client.focusedâs border,
indicator, and child_border all switched to Sapphire (#74C7EC);
background/text left alone.
âMarginâ read as gaps â the configâs own existing comments already use
âMargins / Gapsâ interchangeably (### Window Geometry (Margins / Gaps),
and a later comment about outer gaps: âthereâs always a visible margin to
the screen edgeâ), so thatâs the established vocabulary in this repo, not
a guess. Reduced both gaps inner (8 -> 4) and gaps outer (9 -> 4).
Verified live against a real open window, not just a clean reload: pulled
its actual rect via swaymsg -t get_tree (x17,y58, 1886x1005), screenshotted,
and pixel-searched right at those exact boundary coordinates â found Sapphire
solidly along both the top edge (1244 matching pixels at y58) and the left
edge (400 matching pixels at x17), confirming the real border color, not just
that the config parsed.
2026-08-28 (battery: real charging icon with the lightning bolt built in)
Follow-up: wanted the charging icon to actually show a bolt again, from âthe
same familyâ as the plain battery icon, not just rely on color. Previous entry
concluded this wasnât possible because format-icons looked like a single flat
array shared by every format variant â wrong, caught by reading waybarâs
actual source (ALabel::getIcon, src/ALabel.cpp) rather than trusting the
man page summary alone this time: format-icons can be an object, and the
battery module builds a lookup-key list {status+"-"+state, status, state}
(confirmed status gets lowercased with spaces turned into dashes before this,
matching the #battery.charging CSS class already in use) â falling back to
a "default" key. Critically, each value in that object can itself be an
array, independently capacity-tiered exactly like the flat form. So
{"charging": [...5 icons...], "default": [...5 icons...]} genuinely works:
whichever status is active picks its own tiered set.
Used a real âbattery with a bolt insideâ icon family (md-battery_charging_10
through _100, verified present in the font) for the "charging" key, and
switched "default" to the matching plain md-battery_* set instead of the
old FontAwesome one, so charging/non-charging read as the same visual family
with just the bolt as the distinguishing detail, not two unrelated icon styles
stitched together.
Hit the raw-glyph corruption bug again, but in a new form â \U000fXXXX
8-digit escapes (needed for these supplementary-plane codepoints, all above
U+FFFF) got silently truncated in transport this time: \U000f089c landed
as \uf089 (4 digits) plus a stray literal c character, valid JSON but
wrong content, caught only by checking codepoints after writing â the same
discipline that already caught the plain raw-character version of this bug
several times earlier in this session. Confirmed via an isolated python3 -c
call that the escape syntax itself is correct in isolation; something in this
specific heredocâs transport mangled it. Fixed by sidestepping escape-literal
parsing entirely â built the strings with chr(0xf089c) etc. instead, which
canât be misinterpreted the way a backslash sequence can.
Verified live: valid JSON, clean waybar reload with no errors (confirms the
object-keyed format-icons syntax parsed correctly), and the battery pill
still renders Green at the real current capacity (90%, discharging) with a
comparable icon pixel count to prior checks â consistent with a real glyph
rendering, not a blank/missing one. Could not force an actual charging session
to visually confirm the bolt icon itself renders â same real limitation as
the previous entry, still resting on the mechanism being correctly wired
rather than a live A/B screenshot of it charging.
2026-08-28 (battery: real percentage gradient, icon now tiers while charging too)
Checked the actual live state first rather than guessing: real capacity 93-94%,
genuinely Discharging. Confirmed via pixel search it was already rendering
correctly (Green, the healthy-discharging color from an earlier pass) â so the
color mechanism itself wasnât broken, but two real gaps explain what looked like
ânot changingâ: color only had two emergency breakpoints (warning at 30%,
critical at 15%) with everything from 31% to 100% rendering the exact same flat
Green, and the icon was completely static (format-charging/format-plugged
had a hardcoded bolt glyph, no {icon}) whenever actually plugged in â
capacity-tiering only ever worked while discharging.
Color: states turns out to accept arbitrary names, not just
warning/critical (man waybar-states: âEvery entry consists of a name and
a valueâ). Added a third tier, "moderate": 60 -> Yellow, between the
existing warning (Peach) and critical (Red), positioned in the stylesheet
between .full and .warning so a genuinely low battery still overrides it via
the same source-order logic already used for the other states. Now: >60% Green,
31-60% Yellow, 16-30% Peach, <=15% Red â an actual gradient instead of two
thresholds plus one flat color for the other 70 points of range.
Icon while charging: format-icons turns out to be a single array shared
by every format variant (confirmed: no per-status icon set exists in the
battery moduleâs schema) â so a dedicated MDI battery_charging_10..100 tiered
set (found and verified present in the font, but not usable here since waybar
canât switch icon arrays by status) wasnât the fix. Real fix:
format-charging/format-plugged now use {icon} too, pulling from the same
tiered array format already used â same âicon shape = one concept (capacity),
color = another (status)â split already used elsewhere in this bar (e.g. the
volume mute icon). Trade-off: lost the static bolt glyphâs obvious âthis is
chargingâ shape, but color already carries that signal clearly (Yellow while
charging, Teal when full) â flagged in case that trade isnât wanted back.
Verified live: reloaded clean, pixel-confirmed battery still renders Green at the real 93% capacity (correctly not falsely triggering the new 60% Yellow tier). Couldnât verify the moderate/warning/critical Yellow/Peach/Red bands or the charging iconâs tiering with real live A/B tests â would need the actual battery to drain into those ranges or to actually be plugged in, neither of which could be forced from here â this rests on the states mechanism already being independently proven correct in an earlier pass, not a fresh live test of every band.
2026-08-28 (whole bar reorganized: status indicators left, controls right)
Full layout reorder, requested as a coherent principle rather than a one-off tweak: status/indicator modules (workspace, bluetooth, wifi, network speed) read left to right on the left side, clock stays centered, and everything you actually click or toggle (lock states, docker, caffeine, brightness, volume, battery, power) lives on the right, in that order. Caps/num lock explicitly placed right before docker per the request.
Left: [workspaces] · [bluetooth][network][network#speed]
Center: [time][date]
Right: [caps/num lock][docker][caffeine] · [backlight][pulseaudio] · [battery] · [power]
Just reordering modules-left/modules-right wasnât enough on its own â
several margin/grouping CSS rules were written assuming the old adjacencies
(docker used to mark âstart of the left status groupâ, bluetooth used to mark
its end). Re-derived the grouping margins for the new clusters: bluetooth now
starts the left connectivity cluster (was dockerâs old role), caffeine now
ends the new device-controls cluster on the right (docker+caffeine+lock
states tight together, gap before brightness/volume) instead of bluetooth
ending a group that no longer exists in that position.
Verified live rather than trusting the reorder on paper: screenshotted the reloaded bar and pixel-searched for six different modulesâ known colors across the full bar width. Every ordering signpost landed where it should â workspace (Sapphire) at the far left (x12), wifi (Sky) starting only after a gap consistent with bluetooth preceding it (x242+), time/date centered (x830-1078), docker (Blue) now clearly in the right portion (x1480-1492), and the power button (Red) as the absolute rightmost element, right at the screen edge (x1894-1904).
2026-08-28 (capslock/numlock: hidden entirely when off, not just dimmed)
Wanted them to disappear completely (zero bar space) when off, each
independently â only one showing if only one is locked. Found the exact
mechanism in man waybar-custom: hide-empty-text (âDisables the module
when output is emptyâ) removes the whole module from the layout, not just
blanks its text. Added it to both custom/capslock/custom/numlock, and
keylock-status.sh now outputs "text":"" for the normal unlocked state
(previously a dim gray icon+badge). A genuine error case (no LED device
found at all) still shows something rather than silently vanishing â
thatâs a real problem, not an âoffâ state.
This broke the âone merged pillâ look from two entries ago â that treatment (capslock rounded-left-only, numlock rounded-right-only, touching with no seam) assumed both are always present. With either now able to disappear on its own, a half-rounded pill with its open edge flat would look visibly cut off whenever only one shows â the common case now, not an edge case. Reverted to two independent full pills rather than attempting fragile sibling-aware CSS to conditionally re-merge them only when both happen to be visible, which wasnât practical to verify reliably. Correctness over keeping a cosmetic that no longer fits the new behavior.
Hit the same raw-glyph-paste corruption bug again while editing
keylock-status.sh â ICON=$'' landed as a genuinely empty string,
caught by checking hex(ord(ch)) codepoints immediately after writing
rather than assuming the paste worked (established discipline in this repo,
paid off again). Fixed using bashâs own $'\uXXXX' ANSI-C-quote escape
syntax instead â plain ASCII text in the file, bash itself decodes it to
the real character at runtime, so thereâs nothing for the paste to corrupt.
Verified directly: bash -c "echo -n \$'\uf023'" piped through Python
confirmed the exact right UTF-8 bytes.
Verified live: pixel-sampled the row from workspaces through where capslock/numlock used to sit â one continuous dark pill-background tone the whole way, transitioning directly into dockerâs own small intended gap, instead of the ~150px both indicators used to occupy together.
2026-08-28 (network speed fixed width; capslock recolored off pale Yellow)
Two separate asks. First: battery was checked and turned out fine â at the
time (38%, genuinely Discharging per /sys/class/power_supply/BAT1/status,
not charging), neither the warning (30%) nor charging color applied because
neither condition was true; it should render Green (the healthy-discharging
state added earlier), not nothing.
Network speed: {bandwidthDownBytes}/{bandwidthUpBytes} change digit
count constantly (12KB vs 1.2MB vs 999B), which was making the whole pill
visibly grow and shrink every couple seconds. Measured the actual live
rendered width first (177px) rather than guessing, then set min-width:
200px on #network.speed/#network-speed for real headroom. Verified live:
pixel-sampled the pillâs background boundary after reload, found it now
spans ~204px, matching the new fixed width instead of the old content-only
177px.
Capslock color: was Yellow (#F9E2AF), read as washed-out/whitish â
Catppuccin Mochaâs whole palette is pastel by design and Yellow specifically
is one of its palest members, and the glowâs blur softened it further.
Switched to Peach (#FAB387) â meaningfully more saturated (warm orange,
not pale cream) while keeping the same âwarm heads-up, not alarmingâ feeling
Yellow was going for. Num Lockâs Teal is unchanged (not pale in the same way,
no complaint raised about it).
2026-08-28 (capslock/numlock: merged into one pill instead of two)
Asked to put both in a single group rather than two separate pills sitting
side by side. Kept them as two independently clickable custom modules
underneath (merging into one module would lose per-key on-click, the thing
just added) but made them visually read as one: capslock is the left half
(rounded left corners only, no right border), numlock is the right half
(mirror image), touching with no seam. Needed the same negative-margin trick
already used for the clock/time pair â configâs top-level "spacing": 5
puts 5px between every pair of sibling modules regardless of their own
margins, so margin-left: 0 alone wouldâve still left a visible gap.
Verified properly this time rather than trusting a single noisy scan: the wallpaper bleeding through gaps between pills made a broad region search unreliable (same class of issue hit before), so sampled raw pixel values along a row instead. From capslockâs start to numlockâs end, every sampled pixel stayed in a tight dark range (17-30 per channel) with no jump to wallpaper brightness anywhere â confirmed continuous. Then sampled the same way from numlock into docker as a positive control, specifically to prove the method would actually catch a real gap: found exactly that, colors jumping to (183,167,131)-range wallpaper tones in the real gap before docker. Confirms the capslock/numlock merge is genuinely seamless, not just an assumption that the CSS technique worked.
2026-08-28 (capslock/numlock: click to toggle, hover tooltip, real cursor)
Wanted mouse click-to-toggle, a hover tooltip saying which lock it is, and a
pointer cursor on hover. None of that is possible with waybarâs built-in
keyboard-state module â checked man waybar-keyboard-state first rather
than fighting CSS against it: no on-click, no tooltip, nowhere in its
config schema at all. Replaced it with custom/capslock/custom/numlock,
backed by two new scripts (same {text,class,tooltip} JSON pattern already
used for custom/caffeine/custom/docker), which support all three natively.
keylock-status.sh reads /sys/class/leds/*::capslock|numlock/brightness
directly â the real kernel LED state, confirmed correct against actual live
state (numlock genuinely on, capslock genuinely off) at the time.
keylock-toggle.sh needed real investigation, not just wiring an on-click.
wtype -k Caps_Lock â already used elsewhere in this repo â does not
actually toggle the real lock state here (confirmed: the LED file stayed 0
straight through it), because it injects through the virtual-keyboard-
unstable-v1 Wayland protocol, which doesnât drive the same XKB lock-latch
machinery a real key event does on this compositor. Switched to ydotool
instead (official extra repo, added to packages/pacman.txt) â injects
through /dev/uinput, indistinguishable from real hardware to the same
input pipeline, so it actually works. Checked the Arch packageâs file
listing before trusting it: ships its own systemd user service (no root
needed at runtime) and its own udev rule for /dev/uinput permissions â
clean, standard, not a loose workaround.
Not yet installed â needs sudo pacman -S ydotool, a udev reload, and
systemctl --user enable --now ydotool.service (all three commands in
docs/ARCHITECTURE.md). Until then, clicking either indicator fails with a
clear notify-send telling you exactly which of those steps is missing,
rather than a silent no-op or a cryptic ydotool error â verified this
fallback path directly.
Cursor needed no extra work at all: waybar shows a pointer automatically for
any module with on-click, standard GTK behavior, same as every other
already-clickable pill in this bar. Unrelated to the wofi popupsâ own cursor
limitation documented elsewhere â different code path entirely.
2026-08-28 (capslock/numlock: fixed two real problems from the last pass)
- Couldnât tell which lock was which. Sharing one padlock shape between
caps/num (previous entry) meant color alone had to carry âwhich key is
thisâ â fine once memorized, not self-evident. Added a plain letter badge
in the configâs per-key
formatstring alongside{icon}:"A {icon}"for capslock,"# {icon}"for numlock. Icon shape/color (padlock, Yellow/Teal) unchanged, just no longer the only signal. - Activating a lock grew the whole barâs height. Real regression from
the previous passâs
font-size: 19pxbump on the locked state â waybarâs configured"height": 41didnât hard-clip the over-tall label, so the bar visibly grew the moment a lock activated and shrank back when it didnât. Removed the font-size change entirely; thetext-shadowglow stays (itâs paint-only, doesnât participate in layout/box-height calculation at all, confirmed via CSS fundamentals â this is why it was never the actual cause), so the âdamn noticeableâ glow treatment survives without the size change that broke the bar.
Confirmed via swaymsg reload the bar reports back its configured height
(Bar configured (width: 1920, height: 41)) and font-size no longer
appears anywhere in the capslock/numlock CSS rules. Could not force a real
capslock/numlock toggle to visually confirm the height stays fixed while
actually locked (the same wtype limitation hit earlier â synthetic
Caps_Lock presses donât flip the real LED/modifier state in this
environment) â this rests on the CSS box-model reasoning (text-shadow
cannot affect height, font-size was the only property that could and is now
gone) rather than a live A/B screenshot.
2026-08-28 (capslock/numlock, take three â real padlock, deliberately loud)
Neither prior attempt was noticeable enough. Switched to a real closed-padlock
icon (fa-lock, f023) for locked, open padlock (fa-unlock, f09c) for
unlocked â back to waybarâs shared format-icons pool (same icon set for
every key, per man waybar-keyboard-state) rather than a per-key hardcoded
glyph, since color (Yellow/Teal, unchanged) already tells capslock and numlock
apart and a genuine padlock shape reads faster than an arrow or a hashtag ever
would.
Made the locked state deliberately louder than the rest of the barâs own
restraint calls for: font-size: 19px (up from the barâs normal 15px) plus a
two-layer text-shadow glow (tight 8px + wide 14px halo, matching each keyâs
own color). A locked modifier key is more urgent than a passive status icon,
worth breaking the âeverything the same size, no glowâ convention for. Still
no filled background block, per the earlier explicit ask â the glow carries
the emphasis instead of a solid fill.
2026-08-28 (capslock/numlock icons, take two â simpler glyphs)
First attempt (md-caps_lock/md-numeric) didnât land well visually. Swapped
for simpler, more minimal glyphs instead of another detailed Material Design
icon: capslock is now U+21EA (âȘ, âUPWARDS WHITE ARROW FROM BARâ) â the actual
ISO keycap symbol printed on real physical Caps Lock keys, standard Unicode
rather than a Nerd Font PUA glyph, so no font-coverage risk at all. Numlock is
fa-hashtag (f292, a plain #) instead of a cluttered â123â icon at bar
size â clean, single-glyph, immediately reads as ânumbersâ without the visual
noise a multi-digit icon has this small.
2026-08-28 (capslock/numlock: icons instead of âCAPSâ/âNUMâ text)
Was "format": "{name} {icon}" with a shared locked/unlocked icon pair
(format-icons) â per man waybar-keyboard-state, that icon pool is the same
set for every key (âthe same set of icons is used for number, caps, and scroll
lockâ), so simply dropping {name} wouldâve left capslock and numlock showing
the identical generic lock glyph, impossible to tell apart without the text.
Real fix needed distinct icons per key, not just fewer characters. format
turns out to also accept a per-key object (confirmed in the same man page:
{"numlock": ..., "capslock": ..., "scrolllock": ...}), so each key now gets
its own hardcoded, purpose-specific icon instead of routing through the shared
locked/unlocked pool at all: md-caps_lock (f0a9b) for capslock,
md-numeric (f03a0) for numlock â both verified present in the installed
font via fc-query first. Lock state is still conveyed by color exactly as
before (Yellow/Teal when locked, dim gray when unlocked â that CSS was
untouched and already independently verified working in an earlier pass), just
no longer duplicated in a text label too.
2026-08-28 (active workspace: Sapphire instead of Mauve)
Asked for a different color on the active workspace than Mauve, suggesting Sapphire or wifiâs color. Went with Sapphire: Mauve stays the barâs one universal âyouâre pointing at something interactiveâ hover accent everywhere else, so giving the workspaceâs own resting focused state a different hue keeps those two meanings distinct rather than overloading Mauve for both. Also ties it into the blue âconnectivityâ family already established for the network module (Sapphire = ethernet, Sky = wifi) instead of an arbitrary new hue. Text color and underline both switched together.
Verified live: pixel-searched the workspace area after switching to workspace 1, found Sapphire at the number glyph (y13-14/23-24) and the underline (y32-33), zero Mauve remaining there.
2026-08-28 (workspace numbers: one shared container, not N separate buttons)
#workspaces (the group wrapper) already had the shared pill treatment every
other module gets â dark background, Mauve hairline border â but each number
button also had its own smaller background-color and border-radius layered
on top, which read as âseveral small buttons sitting inside a pillâ instead of
one unified group. Removed the per-button background/radius entirely; numbers
now sit directly in the one shared container. Colors, spacing, and the
hover/focused underline accent from the previous change are all untouched â
this was specifically about the idle-state look, not a redesign of the states
themselves.
Verified live: pixel-scanned the workspace areaâs background after reload, found one consistent dark tone throughout (no lighter per-button patches standing out), while the Mauve underline (y32-33) and number glyph (y13-24) from the previous change both still render correctly.
2026-08-28 (workspace switcher: fixed a real toggle bug, added an underline accent)
Bug: pressing $mod+1 while already on workspace 1 jumped back to whatever
workspace youâd been on before â an alt-tab-style toggle, not âgo to/stay on
workspace 1.â Root cause: workspace_auto_back_and_forth yes, added earlier this
session as a deliberate feature (âlike alt-tab for workspacesâ). Reverted â
removed the line entirely rather than setting it to no explicitly, since no
is swayâs own documented default (man 5 sway: âDefault is noâ). Verified with
the exact reported reproduction: switched to workspace 1, then repeated the same
switch command â workspace 1 stayed focused both times (previously the second
call would have jumped to workspace 2).
Look: added a 2px underline accent to hover/focused workspace buttons â
translucent Lavender on hover, solid Mauve when focused, same colors already in
use, just a second visual cue (like an active browser/editor tab) on top of the
existing bold-colored-number treatment. Widened button padding one notch (7px ->
8px) so the underline doesnât read as cramped. A transparent border-bottom
placeholder on the idle state reserves the same 2px vertical space always, so
the chip doesnât visibly shift height the moment an underline actually appears.
Verified live: pixel-searched after switching to workspace 1, found a solid
~40px-wide horizontal Mauve line at the chipâs bottom edge (y32-33), distinct
from the small number-glyph pixels above it (y13-24) â confirms a real
underline, not just a coincidental color match.
2026-08-28 (caffeine mode now actually survives a reboot)
Caffeine mode silently reset to off on every reboot regardless of what it was
left at. Root cause: swayidle.service is enabled (WantedBy=default.target,
confirmed via systemctl --user is-enabled), so systemdâs user manager
auto-starts it at every login on its own â completely independent of sway.
The old exec systemctl --user restart swayidle.service line in sway/config
had no way to know caffeine had been left on, so it just reasserted âstartâ
every time.
Fixed with a persisted marker file, ~/.local/state/caffeine/enabled.
caffeine-toggle.sh now writes/removes it alongside stopping/starting the
service. New scripts/.local/bin/swayidle-startup.sh â what swayâs exec
line calls now instead of the bare systemctl restart â reads the marker at
startup and is the single source of truth for swayidleâs state: stops the
service if caffeine was left on (overriding whatever systemdâs default.target
just auto-started), otherwise does the same restart this line always did.
Verified the actual failure mode end to end rather than trusting the fix on
paper: toggled caffeine on, manually replayed what systemdâs auto-start would
do at the next login (systemctl --user start swayidle.service), confirmed
the service came back active exactly as it incorrectly would pre-fix, then ran
the new startup script the way swayâs exec line would and confirmed it
correctly stopped it again. Verified the off case and caffeine-status.shâs
reporting through a full toggle sequence too. No literal reboot was performed
to avoid disrupting the live session â this rests on an exact functional
simulation of the real startup sequence.
2026-08-28 (workspace/capslock/numlock: text color instead of a filled block)
Active workspace and locked capslock/numlock indicators used a solid
background-color fill + dark text for contrast. Asked to keep the background
neutral and just recolor the text/number instead. Same hues kept throughout
(Mauve for the active workspace, Yellow for capslock, Teal for numlock) â
just moved from background-color fill to color text, with font-weight:
800 added to each so the now-unfilled state still reads as emphasized rather
than losing prominence entirely.
Verified the workspace change live: pixel-searched for Mauve in the
workspace region after reload, found a small ~10-pixel cluster sized like a
single bold glyph (the old filled-block version would have produced hundreds
of hits across a whole chip) â confirms text-only rendering, not a background
block. Tried to verify capslock the same way but wtype -k Caps_Lockâs
synthetic key press doesnât actually toggle the real lock LED state
(/sys/class/leds/*capslock*/brightness stayed at 0 through it) â a testing-tool
limitation, not something to chase further, since capslock/numlock use the
exact same CSS mechanism (state selector + color instead of
background-color) already confirmed working for the workspace case.
2026-08-28 (icons that never actually changed state: volume mute, brightness, wifi signal)
Volumeâs mute icon and its low-volume tier icon were the literal same codepoint
(f026, FontAwesomeâs volume_off/volume_x/volume_xmark â confirmed via
nerd-fontsâ own glyphnames.json that all three names alias to one codepoint), so
muting while already at low volume showed no visible change at all, and even at
higher volume the âoffâ icon read ambiguously close to the low-tier one. Backlight
had a single static sun icon the whole time â no tiering existed at all. The
main waybar network pillâs wifi icon was likewise a single static glyph, never
reflecting signal strength (only the wifi picker dropdown had tiered icons,
added earlier â the always-visible bar pill never did).
Switched volume to a full, consistent Material Design Icons set instead of
patching around FontAwesomeâs ambiguity: volume_low/volume_medium/volume_high
for the tiers, volume_mute (a genuinely different codepoint, f075f) for muted.
Backlight gained a real 5-tier format-icons (brightness_1..brightness_5,
f00da-f00de). Networkâs format-wifi now uses {icon} against the same
5-tier wifi-strength set (wifi_strength_outline/_1/_2/_3/_4) already
verified for the wifi picker, so the bar pill and the pickerâs icons finally
match instead of the bar staying static.
Caught a real hallucination while researching this: an earlier WebFetch summary
of nerd-fontsâ glyphnames.json invented fa-volume_mute at f131 â sounded
completely plausible, wasnât real. Downloaded the actual JSON directly to the
machine and grepped it myself before trusting anything this time (curl +
python3 -c "import json..." against the real 545KB file) â thatâs also just a
more reliable method going forward than relying on a small modelâs summary of a
huge file, independent of this specific miss. Every codepoint used here was
independently confirmed present in the installed font via fc-query afterward,
same discipline as everywhere else in this repo.
Verified all three live, not just âJSON parsesâ: muted vs unmuted at the same 65% volume now render two clearly different glyph shapes (screenshotted, rendered as ASCII brightness maps to compare, confirmed genuinely different silhouettes, not just eyeballed); brightness at 10% vs 90% likewise render distinguishably different icons (fewer vs more visible sun-rays). Wifiâs tiered icon couldnât be forced to a specific signal strength for a live A/B test the same way (real hardware, real signal), so that one rests on the codepoint verification plus a clean waybar reload with no errors, not a live visual diff.
Battery was checked too and turned out already correct â its format-icons
(battery_empty through battery_full, 5 genuinely distinct FontAwesome
codepoints) never had this bug, confirmed by inspecting the config before
assuming it needed the same fix.
2026-08-28 (cursor theme was never actually configured anywhere; wofi hover-cursor investigated)
User reported the cursor doesnât turn into a pointer hovering over entries in the
WiFi/Bluetooth pickers. Checked man wofi/man wofi-style (well, the actual
wofi.5/wofi.7/wofi.1 man pages â thereâs no separate wofi-style one) for any
âcursorâ mention at all: zero hits anywhere in wofiâs own documentation. This
matches an already-documented finding from this repoâs own history (the power-menu
build): âno CSS cursor supportâ in wofiâs --dmenu mode.
Before accepting that at face value again, checked something adjacent that turned
out to be a real, separate, worthwhile gap: no cursor theme was configured
anywhere on this system at all â not in any env var (XCURSOR_THEME was
entirely absent, only XCURSOR_SIZE=24 existed from some other default),
not in sway/config (no seat ... xcursor_theme line existed), not in
gtk-3.0/settings.ini or gtk-4.0/settings.ini (gtk-cursor-theme-name was
never set). /usr/share/icons/default already resolves to Adwaita
(confirmed: Inherits=Adwaita in its index.theme), so a sane default cursor
theme was always available, just never explicitly told to anything. Fixed
with seat seat0 xcursor_theme Adwaita 24 in sway/config and
gtk-cursor-theme-name=Adwaita / gtk-cursor-theme-size=24 in both GTK
settings files.
Tested whether this fixes the actual reported issue, rather than assuming:
launched wofi fresh after swaymsg reload and checked its real environment
(/proc/pid/environ) â still no XCURSOR_THEME there, meaning swayâs
env-var export for the seatâs cursor theme apparently only happens at swayâs
own actual startup, not on a soft reload (same class of caveat this repo
already has documented for plain exec lines not re-running on reload â
will take full effect after next login, not before). The gtk-cursor-theme-name
half should already apply immediately though, since wofi is a real GTK3 app
(confirmed via ldd) reading gtk-3.0/settings.ini fresh on every launch, no
caching involved.
The actual âno pointer on hoverâ complaint is a separate thing from theme configuration, and isnât fixed by any of this: a cursor theme controls which glyph set gets used for whatever shape is requested, not whether a shape change gets requested in the first place. If wofiâs dmenu list rows never ask the compositor for a âpointerâ cursor on hover (which is what âno CSS cursor supportâ means), a correctly-configured theme changes nothing about that â thereâs no shape-change request to render from that theme. Real, structural wofi limitation, same class as the missing hover-tooltip found earlier â not fixable through config here. The honest options: live with it (matches the rest of this barâs wofi-based UI language), or switch the picker backend to something with fuller cursor support (e.g. rofi) at the cost of visual consistency with everything else that already matches wofiâs theme.
2026-08-28 (Bluetooth gets its own picker; WiFi picker no longer shows it)
Four asks in one go: WiFiâs menu shouldnât show Bluetooth actions, keyboard focus should land in the search box automatically, Bluetooth should get âthe same modal approachâ with its own unique options (not WiFiâs), and the connected entry should read as more than just a different text color.
Keyboard focus: tested this directly before assuming it needed a fix â
launched the picker with zero clicks, typed wtype "Maha" into it, screenshotted
before/after. ~7800 pixels differed in the content region, confirming the typed
text reached wofiâs search box and filtered the list immediately. This already
works, nothing to change.
WiFi menu, Bluetooth-free: wifi-picker.py now monkeypatches
create_other_actions too (previously only get_wofi_highlight_markup and
get_selection) to drop the Bluetooth toggle entry by Action.func identity
(is not nmdm.toggle_bluetooth) before it ever reaches the menu.
Bluetooth picker: new scripts/.local/bin/bluetooth-picker.py, same modal
approach as the WiFi one (wofi âdmenu, category colors) but fully self-contained
on bluetoothctl rather than wrapping a third-party tool â checked first, and
the only wofi/dmenu-adjacent Bluetooth picker that exists at all is a 3-year-stale
rofi-specific AUR package, not worth the dependency over ~180 lines. Verified
every bluetoothctl subcommand against this machineâs real bluez (5.87) before
writing anything against it â caught that paired-devices isnât a valid command
in this version (devices Paired is), and confirmed --timeout n scan on
works non-interactively. Menu: power toggle, bounded 8s scan (relaunches itself
after, same self-relaunch pattern upstream networkmanager_dmenu uses for its own
rescan), Bluetooth Manager shortcut, paired devices (click connects/disconnects),
and â added after noticing the ânearbyâ color was defined but nothing used it,
which wouldâve made âScan for Devicesâ a dead button â devices bluez has seen
but not paired with yet, click to pair+trust+connect in one go. Every
bluetoothctl call bounded with a timeout thatâs now caught centrally in run()
(returns an ordinary failed result instead of an uncaught traceback) since
connect/pair/disconnect can all hang waiting on a device that needs interactive
PIN confirmation, which has nowhere to go in a dmenu flow â documented as a real
limitation (falls back to suggesting Bluetooth Manager) rather than silently
dropped. waybarâs bluetooth on-click now points here; deleted the now-fully-
superseded nwg-bar/bluetooth.json.
Connected-entry border: Pango span markup has no border attribute at all
(checked: foreground/background/underline/strikethrough/weight/style/size exist,
border does not) â a literal CSS-style border around the connected entry isnât
achievable through this rendering path. Closest honest approximation, applied to
both pickers: a ââ â heavy-vertical-line prefix inside the same highlighted span,
same idea as an active-indicator sidebar tick in a lot of TUI/statusline designs,
layered on top of the existing filled-background highlight rather than replacing
it.
2026-08-28 (Wi-Fi picker: color-coded rows â networks vs saved vs commands)
Networks, saved connections, and commands (Rescan/Enable/Disable/etc.) all
rendered identically in the wofi menu â upstream networkmanager-dmenu only
colors the single active/connected line, everything else is plain default text.
Couldnât fix this via config.ini (no per-category color hook exists there â
checked the toolâs actual config schema first, not just assumed), so wrote
scripts/.local/bin/wifi-picker.py, a themed wrapper waybar now calls instead of
networkmanager_dmenu directly.
Loads the real installed script via importlib and monkeypatches exactly two
functions (get_wofi_highlight_markup, get_selection) rather than forking the
whole ~1500-line file â all real NetworkManager interaction still runs upstreamâs
own maintained code. Colors: Sky for an actual network (matches the wifi iconâs
own color elsewhere in the bar), Peach for a command, Teal for a saved connection
â detected via Action.func identity plus the :SAVED name suffix upstream
already uses internally, both real signals the tool relies on for its own logic,
not incidental strings Iâm guessing at.
Hit a real, worth-remembering bug while verifying this: importlib.util.
spec_from_file_location returns None (not an error) when it canât infer a
loader for a file with no .py extension, which /usr/bin/networkmanager_dmenu
is â failed one line later with a confusing 'NoneType' object has no attribute
'loader'. Fixed with an explicit SourceFileLoader.
Second, more confusing bug while testing: a background-launched wofi process
reliably vanished by the time a separate tool call checked on it, even under
setsid, producing a false âcolors arenât renderingâ result from a screenshot of
a stale/empty window. Real cause: this remote sessionâs execution environment
doesnât preserve backgrounded processes across separate tool calls the way an
actual terminal session would. Fix for testing this class of thing here: launch,
wait, and screenshot all within one single call â retested that way and
confirmed Sky/Mauve/Teal all render correctly in the live window via pixel
search, matching what direct instrumentation of the patched functions had
already shown (correct markup, correct wofi command with -m for pango markup).
2026-08-28 (waybarâs network pill: dropped the visible signal percentage too)
Follow-up: the â%â the user didnât want turned out to be on the always-visible
waybar pill itself (format-wifi), not the wofi dropdown from the previous
entry â two different places both showing signal info, only one had been fixed.
format-wifi went from " {essid} {signalStrength}%" back to " {essid}".
Left tooltip-format-wifi untouched â it already has the full detail (signal%,
dBm, frequency, IP, gateway) and, unlike wofiâs dmenu list, waybarâs tooltips are
real GTK tooltips that actually work on hover. So the bar now genuinely has the
âjust the name, full detail on hoverâ behavior that wofiâs list couldnât deliver.
2026-08-28 (Wi-Fi picker UI: icons instead of signal-strength text)
Follow-up on the Wi-Fi picker from earlier today: wanted the list to just show network names, not signal%/bars text, with strength conveyed by icon instead (reads faster, takes less horizontal space) â plus wanted full detail available on hover.
Did the icon part: format went from {name} {sec} {bars} to
{icon} {name}, and set wifi_icons to the 5-tier Material Design
wifi-strength glyphs (networkmanager-dmenu already ships a default set of
these in its own example config, just commented out) â verified each of the
5 codepoints (U+F092F through U+F0928) is actually present in the
installed font via fc-query, same discipline as every other icon in this
repo, rather than trusting the upstream default blindly.
Couldnât do the hover-detail part â checked wofiâs actual capabilities first rather than assuming: its dmenu list has no per-item tooltip support at all, nothing to hook into. Said so plainly rather than silently dropping that half of the request or pretending a workaround exists. The list is icon+name only now; full detail (exact %, security type) isnât surfaced anywhere in this UI as a result, which is the real tradeoff of asking for a compact list.
2026-08-28 (real Wi-Fi picker instead of the nwg-bar On/Off/Settings popup)
Wanted: click the network icon, see available networks, pick one, get prompted
for the password if it needs one, connect. nwg-barâs wifi.json couldnât do any
of that â confirmed (again, same root cause as the volume popup fix earlier)
that itâs a static button grid with no dynamic content support at all, reading
its Go source. Architecturally impossible there, not a config problem.
Replaced with networkmanager-dmenu (official extra repo, not AUR) â
scan/connect/password-prompt/on-off-toggle/nm-connection-editor-shortcut all in
one menu, using wofi --dmenu as its backend. Read the toolâs actual Python
source before trusting it: confirmed real, dedicated wofi support (not just
generic dmenu-protocol compatibility) â it detects wofi specifically and
appends wofiâs own -P/--password flag for the passphrase prompt (masks
input), plus has wofi-specific highlight markup for the connected network.
Given this repoâs history of real, confirmed wofi --dmenu bugs (the power-menu
work a while back â no CSS cursor support, --columns capped at 2, etc.),
smoke-tested wofi --dmenu here first rather than assuming itâd just work:
piped a sample network list into it, screenshotted, and confirmed a normal
solid popup rendered center-screen with no issue. Those earlier bugs turned out
to be specific to forcing a button-grid layout through --dmenu â a plain
vertical scrolling list (what this tool actually does) hits none of them.
Config: networkmanager-dmenu/.config/networkmanager-dmenu/config.ini (new stow
package), dmenu_command = wofi, Mauve highlight to match the rest of the bar.
Waybarâs network module on-click now runs networkmanager_dmenu directly
instead of the old nwg-bar -t .../wifi.json ... invocation. Deleted
nwg-bar/.config/nwg-bar/wifi.json â fully superseded, nothing else referenced
it.
Not yet installed on the live machine â networkmanager-dmenu needs sudo
pacman -S networkmanager-dmenu, this session has no sudo. Everything here is
verified as far as it can be without the actual binary present (config syntax,
waybar JSON validity, the wofi backend smoke test, reading the toolâs real
source for wofi support) but the actual end-to-end click-to-connect flow is
unverified until itâs installed and clicked for real.
2026-08-28 (color pass, part 5: date color, take three)
Sky (matching network.wifi exactly) was rejected as a literal duplicate. Tried Sapphire next â still a cool cyan-blue like Sky, close enough in the same âfamilyâ that it still read as basically the same color at a glance. Landed on Flamingo: a genuinely different hue (warm coral, not blue at all), so thereâs no ambiguity with wifi, and it pairs as a warm/cool contrast against timeâs Lavender instead of two blues sitting side by side.
Verified live: pixel-clustered date/wifi/time after reload â Flamingo/date at x927-1078, Sky/wifi at x1151-1354 (no overlap in position or color), Lavender/time at x830-898 immediately to dateâs left, unchanged.
Also hit the remote sessionâs connection dropping mid-verification on the previous attempt (Sapphire) â the CSS change had already landed and waybar had already reloaded clean before the drop, confirmed by grep once the connection came back, but the pixel-screenshot step and this commit hadnât happened yet. Nothing was lost, just delayed to this entry.
2026-08-28 (color pass, part 4: clock reorder/recolor, docker count -> white)
Follow-up feedback on part 3: Rosewater on the date âdoesnât look goodâ, and asked for the time to sit to the left of the date instead of the right.
- Swapped
modules-centerto["clock#time", "clock"]â time first, date second. The negative margin that pulls the pair visually together (offsetting the barâs own 5px inter-module spacing) moved fromclock#timeto#clockaccordingly, since itâs the second element now that needs to pull left toward the first, not the other way around. - Date recolored Rosewater -> Sky, matching the wifi moduleâs color and the timeâs existing Lavender rather than the pale pink that wasnât landing well.
- Dockerâs container count is now white (Catppuccinâs Text color,
#CDD6F4â kept consistent with the theme rather than a stark pure#FFFFFF) instead of the running/idle Green-or-gray split from the previous pass. That state signal didnât disappear, it just moved: therunning/idleclass still exists and still drives the pillâs border-color tint, same as before, just no longer duplicated in the countâs own text color.
Verified live: reloaded, screenshotted, pixel-clustered Sky and Lavender â Lavender (time) lands first at x830-898, Sky (date) right after at x927-1078, distinctly separate from the network moduleâs own Sky wifi text further right (x1124-1327), confirming the reorder and the reuse arenât colliding into one cluster.
2026-08-28 (color pass, part 3: battery charge states, clock split, docker/volume rework)
- Battery: only had colors for the two emergency thresholds (warning/critical)
plus charging â the normal âhealthy, running on batteryâ state (
Discharging, confirmed viacat /sys/class/power_supply/BAT1/status) fell through to plain silver. Added#battery.discharging(Green) and#battery.full(Teal, topped off and plugged in) using the#battery.statusclasses waybar derives from the kernelâs power_supply status file (waybar-battery(5)) â now every real charge state has its own color: Green healthy, Yellow charging, Teal full, Peach warning, Red critical. Placed before the existing warning/critical/charging rules in the stylesheet since they share selector specificity â source order is what makes the low-battery colors win when both a status and a capacity-state class apply at once. - Pulseaudio: Pink -> Maroon. Asked for something âdark/dominatingâ instead of a pastel â Maroon is deep and grounded, distinct from Yellow on backlight right next to it, and its other use (bluetooth.on) sits in the opposite group so thereâs no adjacency clash.
- Clock: date and time are now two different colors (Rosewater / Lavender).
First attempt used two
{:...}placeholders in one format string with inline Pango spans â waybar rejected it outright (âinvalid arg-id in format stringâ), confirming the clock module only accepts one datetime placeholder per instance. Real fix: split into two module instances,clock(date, full pill) andclock#time(time, plain floating text next to it, no background of its own) â same âboxed pill + adjoining plain-colored textâ pattern network/network#speed already established, not a new one. - Docker: icon is now a constant Blue (was tied to running/idle state before),
count switches Green/gray depending on whether anythingâs actually running â
colored independently via inline Pango spans in
docker-status.shrather than one flat color for the whole label. Therunning/idleclass still exists on the module, now driving a border-color tint on the pill instead of duplicating the countâs own color signal.
Caught one real bug while writing this: a code comment mentioning the literal
path power_supply/*/status broke waybarâs CSS parser outright ('/*' in comment
block at style.css:288) â the */ inside that path silently closed the CSS
comment early. Waybar wouldnât even start until this was reworded. Also had to
rebuild /tmp/pngdecode.py a second time â a mid-session connection drop wiped
/tmp entirely, same as every other time this has come up.
Verified every color live: reloaded, screenshotted, pixel-clustered all six new colors against their expected x-position in the bar (clock date/time correctly split into adjacent clusters at x834-985/x1016-1084, batteryâs new Green at x1795-1846 near the power button, dockerâs Blue icon narrowed to just x343-355 now that the count isnât also blue, pulseaudioâs Maroon confirmed distinct from backlightâs Yellow right beside it).
2026-08-27 (color pass, part 2: every remaining module gets its own hue)
Finished the âbar is too monochromeâ pass â clock, backlight, pulseaudio, docker,
and bluetoothâs idle-but-on state were all still plain silver (#CDD6F4), the only
modules left without an identity color after the network pass. Picked with color
psychology in mind rather than arbitrarily:
clock-> Rosewater. Glanced at constantly, so it gets the calmest, warmest hue rather than anything that reads as a status/alert.backlight-> Flamingo. Warm glow tone for light/comfort, deliberately not the Yellow already spoken for by capslock/battery-charging so brightness doesnât visually compete with those âheads upâ moments.pulseaudio-> Pink. Audio is the most expressive/personal control in the bar, so it gets the most vivid hue left in the palette.custom/docker-> Blue when containers are actually running (matches Dockerâs own brand blue), dim gray when idle â previously always showed the same static color regardless of whether anything was running. Needed a real script change, not just CSS:docker ps -q | wc -lpiped straight intoformathad no way to carry a class, so wrotescripts/.local/bin/docker-status.sh(same{"text","class","tooltip"}JSON pattern ascaffeine-status.sh) and switched the module to"return-type": "json".bluetooth.on-> Maroon. This is the âpowered on, nothing connectedâ state â was falling through to plain silver before, same gap as everything else here. Distinct from.disabled(gray) and.connected(green).
Verified every state live, not just visually glanced at: screenshotted and
pixel-searched for each of the six colors (four resting states plus dockerâs two),
confirming each cluster lands at the moduleâs actual x-position in the bar (clock
dead center at x834-1084, bluetooth in the left group at x469-478, etc.). Dockerâs
.running state needed an actual running container to verify, so spun up
docker run --rm -d alpine sleep 20 as a self-cleaning test â confirmed the Blue
cluster appears at x343-376 while itâs up, and docker ps -a shows nothing left
over once it exits on its own.
2026-08-27 (network module: color, signal strength, richer tooltip)
First piece of the wider âthe bar is too monotoneâ color pass, scoped to network/
network#speed since thatâs what got asked about. Read man waybar-network before
touching anything â it documents state-based CSS classes (#network.wifi,
#network.ethernet, #network.disabled, #network.disconnected, #network.linked)
and a full set of format placeholders that werenât being used at all before
({signalStrength}, {signaldBm}, {frequency}, {gwaddr}, {cidr}).
#network.wifi-> Sky#89DCEB,#network.ethernet-> Sapphire#74C7EC(same blue family since both mean âconnectedâ, distinct enough to tell wifi from wired at a glance),#network.disabled(rfkill-blocked) -> the same dim gray every other âoffâ state in the bar already uses.#network.disconnected(red) was already there and untouched.format-wifinow shows signal strength inline ({essid} {signalStrength}%) instead of just the SSID.- Tooltip went from nothing useful to real diagnostics: SSID/signal/dBm/frequency for wifi, interface/IP/gateway for ethernet, explicit disabled/disconnected text.
network#speedâs down/up arrows are now two different colors via inline Pango markup (<span color='...'>, confirmed supported:man waybar-> âMODULE FORMATâ section) â green for download, peach for upload â rather than one flat color for both directions.
Verified for real, not just âJSON parsesâ: reloaded waybar live, screenshotted with
grim, and pixel-searched the render (had to rewrite /tmp/pngdecode.py from
scratch again â itâs /tmp, doesnât survive a session/reboot, no PIL/imagemagick
available to install without sudo). Found Sky-colored pixels exactly where the
network module sits (x1121-1307), and two separate color clusters in the speed
moduleâs x-range â green at x1376-1444 (download), peach at x1495-1562 (upload) â
confirming the Pango spans render as two colors, not one blended block. The
network.ethernet rule canât be live-verified the same way (this machineâs on wifi,
not ethernet, right now) â taken on the man pageâs word that the class exists,
same discipline as everywhere else in this repo, just flagged as unverified live.
2026-08-27 (scroll was flooding waybar, and briefly drove volume to 1494%)
User reported scroll-to-adjust âwasnât workingâ on the volume/brightness pills.
/tmp/waybar.log had 249 lines of Connection failure: Connection terminated and
Value "" of hint "value" could not be parsed as type "int" â real evidence, not a
guess. Root cause, found in man waybar-pulseaudio/man waybar-backlight: custom
on-scroll-up/on-scroll-down âreplaces the default behaviourâ, and neither module
had smooth-scrolling-threshold set (default 0). Touchpads emit continuous
fractional scroll deltas, not discrete ticks, so with the threshold at 0 every
micro-delta from one scroll gesture fired the script separately â dozens of
concurrent pactl/brightnessctl/notify-send processes within milliseconds,
flooding makoâs D-Bus connection. Fixed with "smooth-scrolling-threshold": 1 on
both modules.
Reproducing the flood manually (30 concurrent script calls) also caught a second,
more serious bug: pactl set-sink-volume ... +5% doesnât clamp at 100% â the burst
drove the sink to 1494%. Confirmed nothing was actually playing at the time
(pactl list sink-inputs short was empty), so nothing got physically blasted, but
it would have been genuinely dangerous if something had been. brightnessctl does
self-clamp (tested: set 1000% lands at exactly 100%), so brightness only had the
flood risk, not an overdrive risk. Fixed both scripts with flock-serialized
read-compute-apply, and volume_osd.sh now computes and clamps its own 0-100
target instead of trusting pactlâs relative math. Re-ran the identical 30-call
burst after the fix: lands exactly at the 100%/0% boundary, zero waybar log errors.
Volume/brightness were reset back to their pre-test values (50%/5%) afterward.
2026-08-27 (volume/brightness: real sliders and device selection, not nwg-bar buttons)
The volume popupâs Vol-/Vol+ buttons âdidnât workâ â tested the underlying pactl
commands directly first (env pactl set-sink-volume @DEFAULT_SINK@ -5%, confirmed
90%->85%, worked fine), so the bug wasnât the command. Read nwg-barâs launch()
in its Go source instead: it fires glib.TimeoutAdd(150, gtk.MainQuit) after every
click, so the whole popup closed ~150ms after each Vol-/Vol+ press â no way to make
several incremental adjustments without reopening the menu each time. Root cause was
the widget, not the command: nwg-bar only has buttons (no scale/slider in its JSON
schema at all), and volume/brightness are drag-to-a-value interactions.
Replaced both:
pulseaudiowaybar module: click = mute/unmute, right-click =pavucontrol(real drag sliders, plus Output/Input device tabs â microphone selection included, for free), scroll = +-5%.pavucontrolwas already installed and already renders in Catppuccin Mocha (the GTK3 theme is applied system-wide viagtk-3.0/settings.ini), so no new styling work was needed.backlightwaybar module: click = a new script,scripts/.local/bin/brightness-slider.sh, wrappingzenity --scale --print-partial | while read v; do brightnessctl set "${v}%"; doneâ--print-partialstreams every drag frame, so itâs a real-time slider, not a set-once dialog. Needszenity(added topackages/pacman.txt, officialextrarepo â not yet installed on the live machine, this session has nosudo:sudo pacman -S zenity).- Deleted
nwg-bar/.config/nwg-bar/volume.jsonâ the button-popup it drove is gone. sway/.config/sway/scripts/volume_osd.shgained amute-togglemode (used by both the waybar click and theXF86AudioMutekey, replacing an inlinepactl && notify-sendone-liner that had drifted out of sync with the script) and now auto-unmutes before applying a volume delta, so scrolling/raising always changes what you actually hear. AddedXF86AudioMicMute(pactl set-source-mute @DEFAULT_SOURCE@ toggle) â there was no microphone-mute keybinding before this.brightness_osd.shâs scroll path is unchanged in behavior, just now also wired to the waybarbacklightmoduleâson-scroll-up/on-scroll-down(previously those called rawbrightnessctldirectly, with no OSD notification at all).
Verified end to end with sh -c invocations matching exactly what waybar itself runs
(not just running the scripts directly): mute-toggle round-trips correctly
(Mute: no -> yes -> no), scroll volume goes 90%->85%, scroll brightness goes
0%->5%, pavucontrol resolves on $PATH. brightness-slider.sh itself needed a
stow -R scripts to actually land the new symlink in ~/.local/bin/ â caught this
because the first env test of the exact waybar-invoked absolute path 404âd until
the restow.
2026-08-23 (same nwg-bar treatment for volume/wifi/bluetooth/docker â found the tilde bug again)
Extended the power-menu pattern to four more waybar modules, reusing nwg-bar +
the same style.css (same glass-card look) rather than inventing a new mechanism per
module:
custom/docker-> Stats (kitty -e docker stats), Stop All (a new wrapper script,scripts/.local/bin/docker-stop-all.shâ$(docker ps -q)needs a real shell, which nwg-barâsexecnever provides, so this couldnât be inlined in the JSON the way Stats could)network-> Wi-Fi On/Off (nmcli radio wifi on|off), Settings (nm-connection-editor)bluetooth-> BT On/Off (bluetoothctl power on|off), Manager (blueman-manager)pulseaudio-> Mute, Vol -/+ (pactl ...), Mixer (pavucontrol)
Icons: none of these had bundled SVGs the way the power actions did, but nwg-barâs
createPixbuf() (checked the source again) falls back to the system icon theme by
name when the icon string isnât an absolute path â used standard freedesktop
-symbolic names (audio-volume-high-symbolic, network-wireless-symbolic,
bluetooth-symbolic, etc.), all confirmed present in the installed Adwaita theme
before using them, same discipline as verifying Nerd Font glyphs elsewhere. No new
icon assets bundled in this repo.
Found the exact same class of bug again, in a new place: waybarâs on-click
strings used ~/.config/nwg-bar/wifi.json for the -t/-s flags. Tested it the same
way as the Lock bug (no-shell invocation via env) before trusting it, and found
nwg-barâs own flag parsing doesnât expand ~ either â worse, it doesnât even
detect it isnât an absolute path the way it does for the icon field, so it
concatenated it onto its default config directory and produced a nonsensical path
that failed outright (open .../nwg-bar/~/.config/nwg-bar/volume.json: no such file or
directory). Fixed by using absolute paths in all four on-click strings, same as
the earlier Lock fix. Re-tested all four (env nwg-bar -t /home/yash/... -s
/home/yash/...) and confirmed each loads its correct item count with no errors.
2026-08-23 (nwg-bar robustness audit: found and fixed a real âLock silently does the wrong thingâ bug)
Asked to make sure the power button is robust and has no surprises, now that nwg-bar
is actually installed. Tested it for real rather than just checking it launches:
-
The Lock button would have shown a bare, unstyled swaylock screen instead of the configured one, with no error to explain why. Read
nwg-barâs own Go source (launch()intools.go): it callsexec.Command()directly on the split command string, with no shell involved at all â so~inswaylock -C ~/.config/sway/lockconfigis never expanded, unlike every other place this exact command appears in this setup (swayâs ownexeckeybinding,swayidleâs config), which all go through an actual shell. Verified this precisely, not just from reading the source: ranswaylock -C '~/.config/sway/lockconfig'(literal tilde, no shell, the exact byte stringnwg-barwould pass) and watched it logFound config at ~/.config/sway/lockconfigimmediately followed byFailed to read config. Running without it.â swaylock treats the unexpanded tilde as a literal filename, fails silently, and falls back to a plain lock screen with none of the configured wallpaper/clock/Catppuccin colors. Fixed by using the absolute path (/home/yash/.config/sway/lockconfig) instead, and confirmed the fix with the same test: full config now parses line by line, wallpaper and all.(Incidentally locked this machineâs real session for a few seconds running that first test, since itâs genuine
swaylockwith PAM auth, not a mock â unlocked it immediately afterward, no harm done, but worth knowing this class of test isnât fully inert.) - Confirmed all other
execcommands (swaymsg exit,systemctl suspend/reboot/poweroff) have no path/tilde dependencies, so theyâre not affected by the same no-shell behavior. - Tested double-launching
nwg-bar(simulating an accidental double-click on the waybar button): only one instance ends up running, no overlapping bars or errors. - Swept the whole repo for leftover cruft from the wlogout/wofi iterations: no
dangling references anywhere,
packages/*.txtclean, and every script in thescripts/package is confirmed actually referenced from somewhere (sway config, waybar config, or a systemd unit) â nothing orphaned.
2026-08-23 (power menu: switched tools â wofiâs dmenu mode wasnât built for this)
User feedback: alignment still off, no pointer cursor, click-outside still not
working â fair, after five rounds of patching wofiâs --dmenu mode, each fix
uncovered another real limitation in the same tool rather than converging:
- No CSS
cursorproperty support in this GTK version (confirmed via a real GTK parse error, not a guess) â canât give hover feedback via cursor shape at all in wofiâs dmenu entries. --columnscapped at 2 per row no matter the width or invocation method.--hide-searchbreaks entry rendering entirely when combined with--dmenu.close_on_focus_losscanât distinguish âclicked elsewhereâ from âhovered near another windowâ while swayâsfocus_follows_mouse(global-only) is on, which it is by default.
Each of these is a real, separately-confirmed limitation of wofiâs dmenu mode for this specific use case (a small button-grid popup), not a config mistake â so continuing to patch around them wasnât going to converge. Researched purpose-built alternatives instead of iterating further on the same tool.
Switched to nwg-bar (part of the nwg-shell project, official Arch extra repo â
no AUR build needed): read its actual Go source before committing to it, not just the
README â
- Real
gtk.ButtonNew()widgets for every entry â proper pointer-cursor hover behavior comes for free from GTK, unlike wofiâs list/flow entries. - Closes on
leave-notify-eventwith a genuine 500ms debounce thatâs cancelled byenter-notify-eventif the pointer comes back â solves the âcloses mid-hoverâ problem architecturally, since itâs the barâs own pointer-leave event, not tied to swayâs global focus model at all. - Escape also closes it (
key-release-eventcheckingKEY_Escape). - Its own default template is a sway exit menu (Lock/Logout/Reboot/Shutdown) â built for exactly this.
- One real limitation found before writing the config, not after: buttons get no individual CSS name/class in nwg-barâs source, so per-action accent colors (a different hover color for Lock vs Shutdown, like the earlier wlogout attempt) arenât possible here. Used one consistent Mauve accent instead, matching how most other waybar modules already work.
New nwg-bar stow package: bar.json (5 entries, referencing the icon SVGs the
nwg-bar package itself ships at /usr/share/nwg-bar/images/ â no icon assets
bundled in this repo) and style.css (same glass-card material as everywhere else:
kittyâs 0.85 opacity, Mauve border/radius). Added to packages/pacman.txt. Removed
the wofi-based power-menu.sh and power-style.css entirely.
Not yet verified visually â nwg-bar isnât installed on this machine and
installing it needs sudo, which this session doesnât have:
sudo pacman -S nwg-bar
Waybarâs power button already points at it ("on-click": "nwg-bar"); it just wonât
do anything until the package above is installed.
2026-08-23 (power menu: single row via orientation=horizontal, click-outside dropped for good)
Two things: confirmed in real use (not a synthetic test this time) that the popup was
closing whenever the pointer moved toward any window, not just on an actual click â
exactly the focus_follows_mouse interaction flagged two entries back, which my
swaymsg-based test hadnât actually reproduced (it forced a focus change directly,
which isnât the same as merely hovering causing one). And a request to switch from the
2-column grid to a single row with centered icons, shorter overall.
- Removed
close_on_focus_lossfor good this time. Thereâs no way to get âclick elsewhere closes it, hover elsewhere doesnâtâ out of this option whilefocus_follows_mouseisyes(swayâs default, and global-only persway.5â no per-window override), so the honest fix here is to not use it. Dismissing is Escape or picking an option, matching every other wofi popup in this setup. - Switched from
--columns 2to-D orientation=horizontal: a different wofi layout mechanism, not subject to the 2-per-row cap--columnshit repeatedly in earlier entries. Puts all 5 icons in one row directly. Width 560->650, height 460->150 (one row needs far less vertical space than a 3-row grid). Verified: 649x149px against a requested 650x150, with all 5 tiles visible in a single centered row.
2026-08-23 (power menu: fit all tiles, no scrollbar, re-verified click-outside)
More feedback: not all 5 tiles were visible without scrolling, wanted the scrollbar
gone, wanted a pointer cursor on hover, and âclick outside doesnât work at allâ (true
â the previous entry removed close_on_focus_loss entirely based on untested
reasoning about focus_follows_mouse).
- Width 460->560, height 280->460 â 460px wasnât tall enough for 3 full rows of
44px-icon tiles with 26px padding, so the third row (the 5th item) was getting cut
off, forcing a scroll. Added
--hide-scrollfor the scrollbar itself. Verified: all 5 tiles visible with room to spare, no scrollbar, 559x449px against a requested 560x460. - Tried
cursor: pointerin CSS for the hand-cursor request â GTK itself reportedTheme parsing error: 'cursor' is not a valid property name, a real warning, not silently ignored. GTK3 controls cursor shape at the widget/GDK level, not via CSS, so this isnât fixable from the stylesheet. Removed the invalid property rather than leave dead CSS with a warning attached. - Actually tested
close_on_focus_lossthis time instead of reasoning about it abstractly: opened a real window, confirmed the popup stays open with no action, then shifted focus to that window viaswaymsg(simulating a genuine click elsewhere) and confirmed the popup closed immediately. It works correctly â the previous entryâs removal was based on untested theory aboutfocus_follows_mousethat didnât hold up under an actual test. Re-added it.
2026-08-23 (power menu: wider, and click-outside removed for a real reason)
Two more requests: more width, and âmoving the cursor away closes itâ â reported as a
bug, but itâs a direct consequence of close_on_focus_loss (added in the previous
entry) interacting with swayâs focus_follows_mouse, which defaults to yes when
unset (confirmed â itâs not set anywhere in sway/config). With hover-focus, moving
the mouse off the popup at all counts as losing focus, not just an actual click
elsewhere â so the popup was closing on mere hover-away, not just clicks.
Checked whether focus_follows_mouse could be scoped to just this popup (sway.5
confirms itâs a global-only command, no for_window equivalent) â fixing this
properly would mean click-to-focus for the entire desktop, a far bigger change than
was asked for. Removed close_on_focus_loss instead: dismissing now works the same
way as every other wofi popup in this setup (app launcher, calculator, emoji picker)
already does â Escape or picking an option, no click-outside.
Width 360->460 (height unchanged at 280). Verified: 459x279px against a requested 460x280.
2026-08-23 (power menu: bigger, no search bar, click-outside dismisses it)
Three more requests: more width and bigger buttons, remove the search bar, and close on click-outside instead of requiring Escape.
- Width 220->360, height 320->280 (shorter now that the search barâs gone), tile padding 16px->26px, icon font-size 30px->44px. Verified via the same screenshot-and-measure approach: 359x279px against a requested 360x280.
close_on_focus_loss=true(via-D, a real documented wofi config option, not guessed) makes wofi quit when it loses focus â which is what happens when you click anywhere else. Confirmed the flag is actually present on the running processâs command line; couldnât fully simulate a mouse click to test it end-to-end sinceydotoolisnât installed andwtypeonly does keyboard input, and installing a new package just for this one test wasnât worth it.- Tried wofiâs own
--hide-searchflag for the search bar first â broke entry rendering entirely when combined with--dmenuin this wofi version (confirmed by testing side by side: identical command minus that one flag rendered the grid fine, with it the popup was empty). Worked around it by visually collapsing#inputvia CSS instead (min-height: 0,opacity: 0) â keeps the widget functionally present, which avoids whatever code path the flag itself breaks, while still being invisible.
2026-08-23 (power menu: actual grid layout, not a vertical list)
Follow-up feedback: wanted the buttons in a real grid, and the popup (âtoo small but
kind of quite correctâ) nudged slightly bigger to fit one. wofi supports this
natively via columns=/--columns â not something requiring a different tool this
time.
- Switched entries from âicon + text labelâ to icon-only. With text labels, columns rendered at uneven widths (a âSuspendâ entry is wider than âLockâ), which is what broke the grid look initially.
- Requested
--columns 3first. Tested it at three different popup widths (300, 400, 700px), via both the CLI flag and an explicit--conffile, and it rendered as exactly 2 columns every single time â confirmed this is a real cap in this wofi build (v1.5.3) for--dmenumode specifically, not a sizing mistake, by also confirming--columns 1correctly renders a single column (so the setting isnât ignored outright, just capped above 2 here). - Settled on
--columns 2: 5 icons give a clean 2+2+1 grid. Verified the final popupâs actual dimensions by screenshotting it and searching for the Mauve border color: 219Ă319px against a requested 220Ă320, and confirmed visually (rendered as an ASCII brightness map) that itâs a real 2-column, 3-row grid, not another uneven wrap. - Restyled
#entryinwofi/.config/wofi/power-style.cssfor square-ish icon tiles (equal padding, larger icon font) now that thereâs no text label taking up horizontal space.
2026-08-23 (replaced wlogout with a compact wofi power menu)
User feedback on the fixed wlogout modal: wanted it transparent like kittyâs terminal (0.85 opacity) and much smaller, on the scale of a wofi popup, not a screen-spanning modal. Tried to get there with wlogout first, hit a real architectural wall rather than a styling problem:
- Measured (with a synthetic lime-green marker background, same technique as the caffeine padding fix, since the wallpaperâs own colors made a plain Mauve-color search unreliable) that even with tiny 84px buttons, the 5 buttons still spanned 1438Ă598px on screen â three times wider than intended.
- Confirmed via wlogoutâs own
main.cthat itsGtkGridis a plain, unnamed grid with no size constraint in the code â but GTK still expands it to fill the entire layer-shell surface (a widget property set at the C level, not something CSSgrid { ... }can override in GTK3). - Tried
--column-spacing 0 --row-spacing 0explicitly â identical 1438Ă598 measurement, proving the spread isnât about spacing at all; wlogoutâs grid divides the full screen width into N equal columns regardless of button size or spacing flags. This is fundamental to how it lays out, not a config mistake.
Given the userâs own size reference was wofiâs popup, and wofi is already proven in
this exact setup to render at a precise, compact size (verified the app launcherâs
600x400 elsewhere) â switched the whole power menu to a wofi --dmenu list instead
of fighting a tool whose layout model doesnât support what was asked:
- New
scripts/.local/bin/power-menu.sh: prints 5 lines (icon + label) towofi --dmenu --width 280 --height 260 --style ~/.config/wofi/power-style.css, then runs the matching action from the selection â same commands already used elsewhere in this setup (lock uses the identicalswaylock -C ~/.config/sway/lockconfig). - New
wofi/.config/wofi/power-style.css: same visual language as the existing app launcher stylesheet, at kittyâs 0.85 opacity instead of the launcherâs 0.95, so it reads as the same âglassâ material as the terminal. - Verified the actual rendered popup by screenshotting it and searching for the Mauve border color within the expected center region: 279Ă259px, matching the requested 280Ă260 almost exactly.
- Removed the
wlogoutstow package and itspackages/aur.txtentry entirely â abandoned approach, not worth keeping around unused.
2026-08-23 (wlogout: fixed a genuinely broken modal, not just restyled it)
User feedback: âthe UI of the popup modal is too crud.â Investigated with the same screenshot-and-measure discipline as the caffeine padding fix, rather than guessing at CSS tweaks â found real bugs, not just aesthetic ones:
- The
wlogoutstow package had never actually beenstowâd.~/.config/wlogout/didnât exist at all â wlogout had been silently falling back to/etc/wlogout/âs system-default layout and CSS this entire time. Every earlier CSS/layout edit had zero effect, because wlogout was never reading any of those files. Caught by running it with an explicit-C/-lpath and seeingFailed to open .../layoutin stderr; confirmed by checking~/.config/wlogoutwas simply missing. Now stowed. - wlogoutâs default
--buttons-per-rowisnât 5. With exactly 5 buttons and no explicit-b, one button rendered as a proper large card and the other four were cramped into a fraction of the row â confirmed visually viagrimscreenshots rendered as ASCII-art brightness maps (no image viewer available in this session). Fixed by passing-b 5explicitly in the waybaron-clickcommand (wlogout -b 5), which wlogoutâs own layout math then distributes evenly. - Tried centering each icon within its card via the
width/heightfields documented inwlogout(5)â this triggered realGtk-CRITICALassertion failures (gtk_label_set_yalign/xalign: assertion 'GTK_IS_LABEL (label)' failed) and only marginally improved icon position. Reverted to the default (nowidth/heightoverride) once confirmed clean and warning-free â not worth a real GTK error for a small positioning tweak. - Also hit the raw-glyph-gets-silently-stripped issue from earlier sessions many times
while iterating on this â inconsistently, sometimes 8+ consecutive identical
attempts failed before one landed. Every glyph in the final
layoutfile was written as a\uXXXXPython escape and verified by codepoint after writing, not trusted on the first attempt. - Kept the
min-width/min-height: 220pxCSS sizing from the first pass as a safety net alongside-b 5, so card size stays consistent even if buttons-per-row ever changes.
Verified the final result with a clean screenshot: 5 evenly-sized floating cards, no warnings in wlogoutâs own output.
2026-08-23 (power menu: waybar button + wlogout modal)
Added a power button to the far right of the waybar bar (custom/power, after
battery) that opens wlogout â a proper centered floating modal with five large
icon buttons (Lock, Logout, Suspend, Reboot, Shutdown), not a text dmenu list, since
thatâs what was actually asked for (âvery aesthetic⊠floating⊠modalâ).
- New
wlogoutstow package:layout(5 buttons, each action wired to whatâs already used elsewhere in this setup âswaylock -C ~/.config/sway/lockconfigfor lock, matching the manual lock keybind and idle-lock config exactly) andstyle.css(Catppuccin Mocha, matching the rest of the desktop: Deep Midnight translucent backdrop, floating cards with the same hairline Mauve border as the waybar pills, large Nerd Font glyphs standing in for icons instead of bundled image assets â consistent with how every other module in this setup does icons). - Each button gets a distinct hover accent, deliberately breaking from âone consistent accent everywhereâ (the rule stated for every other waybar module): Mauve for lock, Lavender for logout, Teal for suspend, Peach for reboot, Red for shutdown. A power menu is the one place where distinguishing âgentleâ from âfinalâ by color is real feedback, not decoration â same reasoning already used for workspace hover states.
- Verified every icon glyph (lock, logout, suspend, reboot, shutdown, and the waybar
triggerâs power-off icon) actually exists in JetBrainsMono Nerd Fontâs charset
before using it, same discipline as every other icon added this session. Hit the
raw-character-gets-silently-stripped issue (documented earlier for the tmux
separators) twice more while building this â inconsistently this time, sometimes a
raw pasted glyph survived and sometimes it didnât with no clear pattern â so
switched fully to writing
\uXXXXescapes in Python source and verifying the landed codepoints after every single write, rather than trusting either approach blindly. - Added
wlogouttopackages/aur.txt. Not yet installed on this machine â AUR package installation needssudo, which this session doesnât have:
yay -S wlogout
The waybar button and config are ready; the button will just do nothing until the package above is installed.
2026-08-23 (audio/video/brightness robustness audit â found a battery safety gap)
Asked to make sure audio, display, and brightness are all robust and wonât unexpectedly âshut off.â Audited each:
- Brightness was at 1% (
brightnessctl: 655/65535) â looked alarming (screen effectively looks off) but isnât a bug:systemd-backlight@backlight:amdgpu_bl1.serviceis systemdâs own brightness persistence (saves on shutdown, restores on boot) working exactly as designed. The previous session just happened to end at 1%. Bumped it to 60% for practical use; nothing to fix in config. - Audio (pipewire/pipewire-pulse/wireplumber): all active, sound plays, volume/mute controls work. Clean.
- Display/GPU:
dmesgclean of amdgpu errors or resets,eDP-1output active with DPMS on. Clean. -
batsignal(battery warnings + forced-suspend-on-danger) was completely broken, possibly since it was first configured. The exec lineâs comment said âForces Suspend at 5%â but passed-fâ which is actually batsignalâs âbattery full notificationâ flag (fires when charging completes) and requires a percentage argument it was never given. Result:batsignal: Option -f requires an argument.and immediate exit, every single time sway started it. There was no low-battery warning and no forced-suspend safety net at all â a real battery-depletion event would have been an uncontrolled power-loss shutdown, not a graceful suspend.Fixed with the actual correct flag:
-D COMMAND(ârun COMMAND at the danger levelâ), wired to-D "systemctl suspend". Verified the fix two ways, not just assumed: ran the corrected command line directly first (confirmed it starts and finds the battery instead of erroring instantly), then after converting to a systemd service, inspected/proc/pid/cmdline(NUL-separated, unlikeps) to confirm systemdâs unit-file quoting actually kept"systemctl suspend"as one argument to-Drather than splitting it into two.Also converted to a systemd service (
systemd/.config/systemd/user/batsignal.service,Restart=on-failure) rather than a rawexec, matching swayidle and sway-audio-idle-inhibit from earlier today â this is exactly the kind of daemon where a silent, unnoticed crash has real consequences (no restart = no warning before the battery just dies), so it gets the same auto-recovery treatment.
Added to install.shâs service-enable line.
2026-08-23 (verification found two real regressions from this sessionâs own changes)
Went back to check that recent changes actually held up (after the user rebooted to
test the boot-time fix) rather than assuming. systemctl --failed was not clean:
swayidle.servicewas crash-looping since login, hittingstart-limit-hit. Root cause:exec dbus-update-activation-environment --allâ which importsWAYLAND_DISPLAY/SWAYSOCKinto the systemd âuser managerâs own environment (separate from swayâs process environment, and required before any systemd âuser service can reach the compositor) â was sitting near the bottom ofsway/config, afterexec systemctl --user restart swayidle.servicehad already fired. Introduced by me when swayidle got converted to a systemd service a few sessions back; not caught at the time because reloading sway to test it doesnât re-run plainexeclines (onlyexec_alwaysre-fires on reload), so the bug only showed up on a real fresh login. Moved thedbus-update-activation-environmentline to the top of the exec block, ahead of anything that depends on it. Fixed the live broken state separately (ran the import +systemctl --user reset-failed+startby hand), since exec-order fixes in the config file donât retroactively fix an already-running session either.sway-audio-idle-inhibithad crashed at login and never came back (segfault inPulse::connect, likely connecting to pipewire-pulse before it was fully up) â found viajournalctl -p err -b, not somethingsystemctl --failedwould show since it was a plainexec, not a tracked systemd unit, so systemd had no idea it died. Converted it to a systemd service too (systemd/.config/systemd/user/sway-audio-idle-inhibit.service), withAfter=pipewire-pulse.socket pipewire-pulse.serviceto address the likely race andRestart=on-failureso a future crash recovers on its own instead of silently leaving idle-inhibition dead until next login.
Both added to install.shâs service-enable line. Re-checked systemctl --failed /
systemctl --user --failed clean after applying both fixes, not just assumed.
2026-08-23 (boot follow-up: confirmed 4x win, oomd, waybar height fix)
The iwd/systemd-networkd fix from the previous entry got applied: boot went from
2min 26s to 36.5s, confirmed with a fresh systemd-analyze run. Went looking for
more, both boot-time and general snappiness:
- Remaining boot-time items (
NetworkManager-wait-online5.8s,docker.service1.7s,systemd-tpm2-setup{,-early}~3.1s combined) are either doing real work (actual network readiness, the container runtime) or would need real research before touching safely (thereâs no disk encryption here, so the TPM setup services arenât protecting anything critical, but I donât know precisely what else depends on them without more digging, and ~3s isnât worth guessing wrong on). Left alone. - Enabled
systemd-oomd(was disabled). Confirmed the kernel actually supports what it needs first (/proc/pressure/memoryhas real PSI data). This is the userspace OOM killer that acts before a memory-pressure spiral turns into a full swap-thrashing freeze â with a browser that can climb into multiple GB of RSS against only 4GB of zram swap, this is a real âwhy did my desktop just freeze for 20 secondsâ prevention, not just a boot-time thing. Added toinstall.shâs service-enable line too. - Fixed waybarâs height mismatch: config said
"height": 32, but every moduleâs actual padding (added over this sessionâs redesigns) needs 41px, so waybar was silently overriding it and logging a warning on literally every single restart this whole session. Just set it to the real value, 41 â no functional change, just an honest config that matches whatâs actually rendered instead of relying on waybarâs auto-correction and a noisy log.
2026-08-23 (found the actual boot-time problem: 2 minutes wasted on a redundant network stack)
Ran a real optimization sweep instead of another cosmetic pass: zsh -i -c exit timing
(~200ms after cache warm, fine â p10kâs instant prompt is doing its job), nvim
--startuptime (~110ms total, fine), systemd-analyze and systemd-analyze blame.
systemd-analyze reported boot as 2min 26s total, of which
systemd-networkd-wait-online.service alone was 2 minutes â essentially the
entire boot. Verified this is genuinely dead weight, not something actually needed:
nmcli device statusshows NetworkManager ownswlan0and is connected.networkctl listshowssystemd-networkdalso trying to configure the samewlan0(configuring, indefinitely â it can never actually finish, since NetworkManager already has the interface)./etc/systemd/network/20-{wlan,wwan,ethernet}.networkexist as manual drop-ins â leftover from networkd being configured at some point before NetworkManager was installed, never cleaned up afterward.- Separately,
iwd.serviceis enabled and running, but NetworkManagerâs actual wifi backend iswpa_supplicant(confirmed running, D-Bus-activated by NetworkManager;NetworkManager.confhas nowifi.backend=iwdoverride) âiwdwas just a second unused wifi daemon sitting in the background the whole time. systemd-resolvedis genuinely in use (resolv.confâ its stub resolver, real DNS answers viaresolvectl status) â left alone.
Fixed in the reproducible setup: install.sh was enabling iwd alongside
NetworkManager, which would have reproduced this exact redundancy (and the 2-minute
boot stall, if a fresh machineâs systemd-networkd ever got enabled some other way)
on any new machine. Removed iwd from the service-enable line â the package stays in
packages/pacman.txt since iwctl is genuinely useful for bootstrapping Wi-Fi from a
bare TTY before install.sh even runs (see the READMEâs Quick Start), it just
shouldnât run as a background service once NetworkManager takes over.
Not fixed on the live machine â needs sudo, which this session doesnât have:
sudo systemctl disable --now iwd.service
sudo systemctl disable --now systemd-networkd-wait-online.service systemd-networkd.service
Expected result: next boot should land somewhere around 25-30 seconds instead of 2min 26s.
2026-08-23 (removed a duplicate wallpaper system, window margins)
Found and removed a second, untracked, more wasteful wallpaper fetcher
While reloading sway to test an unrelated config change, noticed a real network
fetch happen that had nothing to do with the change. Traced it to
exec_always --no-startup-id /home/yash/scripts/fetch-bing.sh in sway/config â
a second wallpaper-fetching script, never tracked in this repo, running on
every sway reload (not once a day like wallpaper.timer). Its own log
(/tmp/sway_wallpaper.log) confirmed two runs in the same session just from normal
swaymsg reload calls. 50 of the 61 files in Archive/ turned out to have come
from this script, not the tracked one.
It was actually well-written in places (talked to Bingâs real HPImageArchive API
directly instead of a third-party proxy, market/index shuffling for variety, archive
pruning) â but firing on every reload instead of daily directly worked against
âdonât add CPU/network load,â and it wasnât reproducible since it lived outside the
dotfiles entirely. Its own fallback.jpg last-resort file didnât even exist, so its
own fallback path was broken too.
Removed ~/scripts/fetch-bing.sh and the exec_always line entirely. Standardized
on the one already-hardened, tracked, timer-based script
(fetch_wallpaper.sh/wallpaper.timer, once a day). Ported over the one genuinely
good idea from the removed script â archive pruning (keep newest 60, prune oldest by
mtime) â since the tracked scriptâs Archive/ had no cap and would have grown
unbounded. Verified the prune logic fires correctly with a synthetic over-threshold
test (created 10 dummy files, confirmed the oldest 6 got removed by actual
modification time) before trusting it.
Window margins + workspace switching feel
gaps outer 6â9, andsmart_gaps onâsmart_gaps inverse_outer: outer (screen-edge) gaps now show specifically when a workspace has exactly one window (the common case) so thereâs always a visible-but-small margin to the screen edge then; with multiple tiled windows, outer gaps hide (inner gaps between windows still apply) so tiling doesnât waste edge space to a margin you wonât see anyway. Plainsmart_gaps on(tried first) hid outer gaps for single-window workspaces too, which fought directly against wanting a visible edge margin âinverse_outerreconciles both wants instead of picking one.workspace_auto_back_and_forth yes: pressing the key for the workspace youâre already on jumps back to whichever workspace you were on before.- Both are pure sway behavior config â zero added CPU/RAM, same per-frame gap calculation sway already does, just different threshold logic.
True directional slide animation when switching workspaces (the original ask) isnât
achievable on swayfx at any version â checked the upstream config docs directly
(animation_duration_ms only covers individual windows opening/closing, no code path
for animating the workspace switch itself). That would require a compositor built
around it, like Hyprland â a full compositor swap, not something to start without it
being its own deliberate decision.
2026-08-23 (bluetooth fix, caffeine mode)
Waybar bluetooth module: fixed, same class of bug as before
Root cause of âI donât see bluetooth on the status barâ: format-off and format-on
in the bluetooth module were both literally empty strings â same failure mode as
the tmux separators/docker icon a few sessions back (lost glyphs, not a font problem).
Separately, format-connected used ï„, which is the speaker icon
(nf-fa-volume-down), not bluetooth â a copy-paste mistake, unrelated to the font
migration. Verified U+F293 (nf-fa-bluetooth) is actually in JetBrainsMono Nerd
Fontâs charset before using it for all four states; the existing CSS already handles
dimming it when off/disabled and coloring it green when connected, so only the icon
itself needed fixing.
New: caffeine mode (custom/caffeine waybar module)
Added a toggle to keep the screen from dimming/locking/suspending â click the coffee
cup icon in the bar (or run caffeine-toggle.sh) to stop it, click again to restore
normal behavior.
Refactored swayidle from a raw exec line in sway/config into a proper systemd
user service (systemd/.config/systemd/user/swayidle.service) specifically to support
this cleanly: caffeine-on is just systemctl --user stop swayidle.service, caffeine-off
is systemctl --user start swayidle.service â no PID tracking, no state file, systemd
already knows whether itâs running. caffeine-status.sh (polled by waybar every 5s)
reports that same state as the moduleâs icon color (dim gray off, peach glow on).
Deliberately stops swayidle entirely rather than using systemd-inhibit to just
block suspend: an inhibitor lock wouldnât touch timeout 600 swaymsg output * dpms
off at all, since thatâs swayidle talking to sway directly, never going through
logind. Stopping the whole service is the only thing that keeps the screen itself on
too, which is what âkeep it aliveâ actually means here.
Tried a raw pkill/setsid ... & disown version first and hit a real, reproducible
hang â the backgrounded swayidle process ended up stuck as a direct child of the
toggle scriptâs own bash process (confirmed via pstree and /proc/pid/wchan
showing do_wait), which doesnât happen with well-behaved job control but did happen
here reliably enough to not trust it. The systemd version has no such fragility: no
job-control edge cases, and Restart=on-failure for free.
2026-08-23 (install.sh end-to-end verification â found a real install-breaking bug)
Went back to close out the one item left unverified: install.sh had only ever been
checked piece-by-piece in sandboxes, never proven against the actual current package
manifests. Ran every check thatâs possible without a spare VM and without sudo:
- Every
packages/pacman.txtandpackages/aur.txtentry, checked against the real repos (pacman -Si/yay -Si, read-only, no root needed). Found a real bug: 15 packages inpacman.txtwere actually AUR-only (brave-bin,google-chrome,swayfx,zen-browser-bin, etc.) â duplicated fromaur.txt, left over from how the manifests were originally generated (pacman -Qqereturns every explicitly installed package regardless of which repo it came from, official or AUR, and that got dumped intopacman.txtwholesale without subtracting the AUR ones). Separately, 6 entries inaur.txtwere-debugpackages (bruno-bin-debug,swayfx-debug, etc.) â these are auto-generated side effects of building their parent package, not real independently-fetchable AUR targets, soyay -Swould fail trying to fetch them. This mattered becausepacman -Saborts its entire transaction if even one target package name is invalid â as written,install.shwould have failed to install any of the 104 packages on a truly fresh machine, not just skip the bad ones. Removed all 21 bogus entries; every remaining entry in both files now verified to resolve. - Every external URL the script touches (9 total: the repo clone, TPM,
Powerlevel10k, the two zsh plugins, the AUR helper bootstrap, oh-my-zshâs installer,
Homebrewâs installer) â checked with
git ls-remotefor git URLs and a real HTTP request for the two raw-file URLs. All resolve. - Every systemd service name it enables (
NetworkManager,iwd,bluetooth,docker,power-profiles-daemon,ufw,sddm, plus the user-levelwallpaper.timer) â confirmed each is a real unit on this system. - A full stow simulation with all 13 current packages at once (the earlier sandbox
test only used 2 toy packages) against a fake
$HOMEseeded with the actual/etc/skelfiles a fresh Arch account would have (.bashrc,.bash_profile,.bash_logout) â succeeded cleanly, no false-positive conflicts, every symlink (including executable scripts under.local/bin) landed correctly.
Still not run as a real, single, uninterrupted execution against an actual blank machine â thatâs the one thing that genuinely requires a spare VM or drive. But every individual piece of it is now verified against reality rather than assumed, and the one bug that actually would have broken it outright is fixed.
2026-08-23 (Docker + UFW writeup, kitty cleanup)
docs/DOCKER_SECURITY.md(new): the full explanation of why Docker bypasses UFW (it inserts its owniptablesrules inDOCKER/DOCKER-ISOLATION-STAGE-*, ahead of where UFWâs rules apply), exactly what was checked on this machine (docker psâ nothing running, nothing published, no live exposure), and a concrete fix in two layers: bind future publishes to127.0.0.1(zero config), or run the newdocs/harden-docker.shfor an actualDOCKER-USER-chain-based restriction (allow loopback + RFC1918 ranges, drop everything else), withdocs/docker-user-rules.serviceto make it survivedocker.servicerestarts (which resetDOCKER-USERto empty). Neither script runs automatically anywhere â this couldnât be tested against a real container withoutsudo, so itâs opt-in with explicit testing steps in the doc rather than something applied silently.kitty.conf: droppedhide_tab_bar_if_only_one_tabandstartup_modeâ confirmed viaman kitty.confthat neither exists in kitty 0.48.2 at all (not deprecated aliases, just gone). The tab-bar behavior they wanted is kittyâs default now (tab_bar_min_tabs 2);startup_modenever mapped to a real directive.
2026-08-23 (kitty remote control)
Enabled allow_remote_control yes + listen_on unix:/tmp/kitty-{kitty_pid} in
kitty.conf â kitty @ ... / kittens couldnât control the running instance at all
before this ({"ok": false, "error": "Remote control is disabled"}), which blocks
live config/font reloads without a full restart. Not force-restarting the running
kitty instance to apply it â this sessionâs remote shell very likely runs through
that same kitty window, and killing it would cut the connection. Needs a manual full
quit/reopen of kitty (not just ctrl+shift+f5, since listen_on sets up a socket at
startup) to actually take effect. Also re-audited the whole live system (not just this
repo) for stray ZedMono references post-migration â none found; the font is
consistent across every app that renders it.
2026-08-23 (optimization pass)
Went looking for what could be optimized/improved system-wide and fixed what could safely be fixed without root access (this sessionâs tools have no sudo on this machine â see the two items at the end that need the user to run them).
Font: dropped ZedMono Nerd Font entirely
Scoped this as âswitch tmux/kitty off the 700MB manual font downloadâ and then found,
partway through, that ZedMono Nerd Font was actually the primary font across sway,
mako, wofi, zed, and waybar too â not just kitty/tmux. Deleted the font directory
before catching that (a grep --include="*.conf" --include="*.sh" missed the plain
sway/config/mako/config files with no extension, and the .css/.json configs
entirely) â a real mistake, caught immediately by re-grepping with no filter, and
since rm -rf on ext4 has no undo, the only responsible path forward was finishing
the migration properly everywhere rather than leaving it half-broken.
Switched every reference to JetBrainsMono Nerd Font â already a pacman package
(ttf-jetbrains-mono-nerd, already in packages/pacman.txt), verified via
fc-query -f '%{charset}' to cover every codepoint the tmux status bar actually uses
before relying on it. Updated: kitty.conf, sway/config, mako/config,
wofi/style.css, zed/settings.json, waybar/style.cssâs font fallback chain, and
removed the now-dead ZedMono download step from install.sh entirely â one less
external dependency and one less thing that could fail on a fresh install.
Committed four âpending WIPâ files that turned out to be finished work
These had been sitting uncommitted for a while and were deliberately left alone in every earlier session on the assumption they might be someoneâs mid-edit. Actually reading the diffs this time: all four were complete, coherent work, not unfinished â
sway/config: wires upswayidleandsway-audio-idle-inhibitautomatically at startup (previously had to be started manually), plus idle-timeout inhibition for fullscreen windows (videos wonât get the screen locked mid-playback).waybar/config+style.css: a full redesign â Nerd Font glyph icons replacing emoji throughout, anetwork#speedmodule added, floating-pill styling reworked with real design rationale in the comments (logical module grouping, one consistent hover accent instead of per-module colors, tooltips matching the theme).nvim/lazy-lock.json: routine plugin version bump from normalnvimuse.
Display managers: pruned to just the one actually in use
packages/pacman.txt had sddm (the one actually enabled and running), plus
greetd + greetd-tuigreet + ly installed and unused â leftovers from earlier
experimentation. Removed the three unused ones from the manifest so a fresh install
doesnât carry them forward. Not yet removed from this live machine â that needs
sudo, which this session doesnât have; see the note in docs/ARCHITECTURE.md.
Docker + UFW: documented, not silently âfixedâ
Docker manipulates iptables directly and can bypass UFWâs rules for published
container ports â a real gap, but checked docker ps first and confirmed no
containers are currently running and no ports are currently published, so thereâs
no live exposure today. Given that, and given this session has no sudo to test a
firewall change against, writing a specific iptables/ufw rule I canât verify would
be worse than not touching it â a wrong firewall rule is a worse outcome than a
correctly-scoped gap. Documented the mechanism and the safe default (bind future
-p publishes to 127.0.0.1 explicitly) in docs/ARCHITECTURE.md instead.
2026-08-13
Made the wallpaper fetcher bulletproof
Root cause of âsometimes the wallpaper script just crashes sway and I have to keep
reloadingâ: found live in journalctl --user -u wallpaper.service â the Bing wallpaper
API occasionally returns a JSON status blob ([{"success": true}]) instead of image
bytes. The old script only checked the response was non-empty ([ -s "$FILEPATH" ]),
so that JSON got saved as wallpaper-*.jpg, symlinked to current.jpg, and handed
straight to swaymsg output * bg ... fill â which crashes swaybg (the process sway
actually delegates background rendering to) since it isnât a real image. The output
goes blank until something reapplies a valid background, which is what âreloading sway
until it worksâ was actually doing.
Rewrote scripts/.local/bin/fetch_wallpaper.sh around three fixes:
- Actually validate the response is an image (
file --mime-type, not just non-empty) before it ever touchescurrent.jpgor gets nearswaymsg. - Check network connectivity first (
nmcli networking connectivity check) and give the download itself real timeouts (--connect-timeout 10 --max-time 20) and 3 retries â previously an unreachable network meant an unbounded hang or a fast false failure, depending on how DNS/connect behaved that day. - Three-tier fallback, in order: todayâs freshly-validated download â the last
known-good wallpaper (
.last_good.jpg, a stable copy outside the daily Active/Archive rotation so it canât itself get archived away or overwritten by a bad run) â a plain solid color viaswaybgâs native-c/solid_colormode, which needs no image file at all and therefore cannot itself fail to parse. A brand-new machine with no wallpaper history yet and no network still gets a valid background, never a crash. - Also fixed a latent bug in the old scriptâs archiving order: it moved whatever was
in
Active/toArchive/before attempting the new download, so a bad response got archived right alongside the real history. Now archiving only happens after the new file is confirmed valid. - Added
flock-based locking (manual trigger and the daily timer could otherwise race each other) and structured logging to~/.local/state/fetch-wallpaper/fetch-wallpaper.log(simple size-based rotation) â every attempt, failure reason, and fallback decision is now visible there instead of the script being a black box between âworksâ and âcrashed and I donât know why.â
Verified against four scenarios before trusting it live: the real API (happy path), a
fake endpoint returning JSON (the actual historical failure, confirmed it falls back
instead of applying the bad response), an unreachable host (confirmed retry + fallback
to last-known-good), and a from-scratch machine with neither network nor prior
wallpaper history (confirmed the solid-color tier). Then ran it for real on this
machine and confirmed swaybg picked up the result correctly.
2026-08-13
Tmux status bar: redesigned and fixed
- Rebuilt the status bar around a single Catppuccin Mocha palette. It had drifted
into a mix of One Dark colors (session/window segments), unrelated Catppuccin accents
(date/time), and a near-white docker pill (
#E5E9F0) that clashed with everything around it. Now: Mauve (session) / Surface1+Green (windows) / Yellow (docker) / Peach (date) / Blue (time) â one dark ink color (#1e1e2e) for all pill text. - Fixed
bind râ it reloaded~/.tmux.conf, which hasnât existed since the config moved to~/.config/tmux/tmux.conf. - Fixed the actual glyphs, three times, because each fix surfaced a new layer of
the same problem:
- Two
.context/*.mdreference docs a redesign was based on turned out to have already lost their powerline-separator and docker-icon glyphs â saved as empty strings (@sep_left "",icon=$''). Copying them verbatim just reproduced the loss, not a working config. - Re-typing the glyphs directly into a file-write call also silently dropped
them â something in that write path strips Private-Use-Area Unicode characters.
Worked around it by writing the codepoints as
\uXXXXescapes inside a Python heredoc run over the shell instead (plain ASCII in transit, decoded to real UTF-8 bytes at write time, which does survive). - Once real bytes were landing, the docker icon still rendered as a tofu box â
wrong codepoint, not a missing font.
U+F395(âfa-dockerâ) doesnât exist inZedMonoNerdFont-Regular.ttf; verified the fontâs actual glyph coverage withfc-query -f '%{charset}'before picking a replacement, and switched toU+F308(nf-linux-docker), which the font does contain.
- Two
- Added a terminal icon (
U+F489) in front of the session name â there wasnât one â and widened the gap after it once it looked cramped. @continuum-bootâoff.
Made the whole system reproducible from one command
Starting point: a stow-managed dotfiles repo (~/dotfiles) covering only
kitty/mako/nvim/sway/tmux/waybar/wofi/zed/zsh, no install script, and several real
(non-symlinked) files quietly living outside the repo entirely.
- Found and fixed a live secret before it reached GitHub:
zsh/.zshrchadGEMINI_API_KEYhardcoded â uncommitted, but about to go out with the next push. Moved it to~/.zshrc.local(gitignored, matching the convention the README already documented) and had.zshrcsource that file instead. - Vendored everything real-but-untracked, converting each into a proper stow
symlink once it was added to the repo:
~/.tmux/scripts/docker_status.shâtmux/.tmux/scripts/~/.p10k.zshâzsh/.p10k.zsh~/.local/bin/{wf-recorder-toggle,wofi-calc,fetch_wallpaper}.shâ newscripts/package~/.config/systemd/user/{wallpaper.timer,wallpaper.service,tmux.service}â newsystemd/package~/.config/gtk-{3,4}.0/settings.iniâ newgtk/package~/.config/mimeapps.listâ newxdg/package
- Captured the full package set:
packages/pacman.txt(104 official packages) andpackages/aur.txt(22 AUR packages), frompacman -Qqe/pacman -Qqm. - Wrote
install.sh, a single idempotent bootstrap script (seedocs/ARCHITECTURE.mdfor what it actually does). Bugs caught before they shipped â each verified against real command behavior, not assumed:stow -nâs actual conflict message format didnât match the first draftâs parser; combined withset -e -o pipefailthis would have aborted the whole script on the very first package, conflicting or not. Verified the real format in an isolated/tmpsandbox before trusting the parser.stow */(both in the script and in the READMEâs own manual instructions) would have tried to stow.gitandpackages/into$HOMEâ neither is a real package. Now built from an explicit, filtered directory listing.yayâs stdin syntax for a package list isnât documented/verified the waypacman -S -is (confirmed straight frompacmanâs own man page); switched both installs toxargs -ainstead of trusting an unverified assumption aboutyay.sddmwas going to beenable --nowâd mid-script, which would hijack the console before the rest of the script finished running. Now justenabled â it takes over on the reboot the script already tells you to do at the end.- The
*.localgitignore rule (for override files like.zshrc.local) also matched a directory literally named.localâ which silently swallowed the entire newscripts/.local/bin/package fromgit statuswithout any error. Tightened the glob to?*.local(requires a real prefix before.local).
- Theming: GTKâs
settings.inisets the theme name, but GNOME-aware apps read the active theme from dconf. Addeddconf writecalls forgtk-themeandcolor-schemeto the end ofinstall.shso a fresh install doesnât end up with the theme installed but not actually applied.