Files
dotfiles/docs/superpowers/plans/2026-05-11-volume-notification.md
T
funman300 cc17e7bab4 docs: implementation plan for volume notification
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:09:40 -07:00

228 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Volume Notification Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace the three `pamixer`-direct media-key bindings with calls to a wrapper script that also fires a mako notification showing the new volume level (or "Muted") with a progress bar.
**Architecture:** New `scripts/volume.sh` wrapper accepting `up|down|mute`. Adjusts master sink via `pamixer`, then calls `notify-send` with mako's `x-canonical-private-synchronous` and `int:value` hints so successive notifications replace each other and render as a progress bar. niri keybinds call the wrapper through its `~/.local/bin/volume` symlink. install.sh symlinks the script alongside the existing entries.
**Tech Stack:** bash, pamixer, notify-send, mako (notification daemon), niri.
**Spec:** `docs/superpowers/specs/2026-05-11-volume-notification-design.md`
**Verification model:** Bash syntax check + dry-run script invocation with each subcommand. niri config validity via `niri validate`. End-to-end (actually pressing the volume key and seeing a mako bubble) requires the live desktop — the controller verifies after both task commits.
---
## File Structure
| File | Role |
|---|---|
| `scripts/volume.sh` | New wrapper: pamixer adjust + notify-send. Three subcommands: `up`, `down`, `mute`. |
| `install.sh` | One new `ln -sf` line in the `==> Installing scripts` block. |
| `niri/config.kdl` | Three lines (9799) modified to call `volume up/down/mute` instead of `pamixer` directly. |
---
## Task 1: Wrapper script + install.sh symlink
After this task, `~/.local/bin/volume up|down|mute` works from any shell and produces both the volume change and the mako notification. niri keybinds still call `pamixer` directly — that swap happens in Task 2.
**Files:**
- Create: `scripts/volume.sh`
- Modify: `install.sh` (append one line in the scripts symlink block)
- [ ] **Step 1: Create `scripts/volume.sh`**
Write `scripts/volume.sh` with this exact content:
```bash
#!/bin/bash
case "${1:-}" in
up) pamixer -i 5 ;;
down) pamixer -d 5 ;;
mute) pamixer -t ;;
*) echo "usage: $0 up|down|mute" >&2; exit 1 ;;
esac
if [ "$(pamixer --get-mute)" = "true" ]; then
notify-send -h string:x-canonical-private-synchronous:volume \
-h int:value:0 \
-t 1500 \
"Muted"
else
LEVEL=$(pamixer --get-volume)
notify-send -h string:x-canonical-private-synchronous:volume \
-h int:value:"$LEVEL" \
-t 1500 \
"Volume: ${LEVEL}%"
fi
```
- [ ] **Step 2: Make it executable**
```bash
chmod +x scripts/volume.sh
ls -la scripts/volume.sh
```
Expected: `-rwxr-xr-x` mode.
- [ ] **Step 3: Syntax-check**
```bash
bash -n scripts/volume.sh && echo "syntax ok"
```
Expected: `syntax ok`, exit 0.
- [ ] **Step 4: Dry-run the usage-error branch**
The unrecognised-arg branch is safe to exercise without side effects (pamixer never called):
```bash
bash scripts/volume.sh 2>&1
echo "exit=$?"
```
Expected:
```
usage: scripts/volume.sh up|down|mute
exit=1
```
- [ ] **Step 5: Symlink it into `~/.local/bin`**
```bash
ln -sf "$PWD/scripts/volume.sh" ~/.local/bin/volume
ls -la ~/.local/bin/volume
```
Expected: symlink pointing to `$PWD/scripts/volume.sh` (absolute path of repo's script).
- [ ] **Step 6: Add the symlink line to `install.sh`**
Find the `==> Installing scripts` block in `install.sh`. It currently ends like this:
```bash
ln -sf "$(pwd)/scripts/waybar-restart.sh" ~/.local/bin/waybar-restart
ln -sf "$(pwd)/scripts/screenshot.sh" ~/.local/bin/screenshot
```
Append one line after `screenshot.sh`:
```bash
ln -sf "$(pwd)/scripts/volume.sh" ~/.local/bin/volume
```
The block then ends with three `ln -sf` lines (waybar-restart, screenshot, volume).
- [ ] **Step 7: Sanity-check install.sh parses**
```bash
bash -n install.sh && echo "syntax ok"
```
Expected: `syntax ok`, exit 0.
- [ ] **Step 8: Functional smoke test (controller verifies; subagent skip if no audio session)**
If you have an audio session, run:
```bash
~/.local/bin/volume up
```
Expected: volume rises 5%, a mako bubble appears with `Volume: NN%` and a progress bar, the bubble auto-dismisses in 1.5 s. Then run `~/.local/bin/volume down` and `~/.local/bin/volume mute` and confirm each behaves.
If your sandbox has no audio session or no notification daemon, skip; the controller will verify after the commit.
- [ ] **Step 9: Commit**
```bash
git add scripts/volume.sh install.sh
git commit -m "volume: add notification wrapper script"
```
---
## Task 2: Point niri keybinds at the wrapper
After this task, pressing the three XF86Audio keys runs the wrapper instead of `pamixer` directly. Volume changes now produce notifications.
**Files:**
- Modify: `niri/config.kdl` (lines 9799)
- [ ] **Step 1: Replace the three keybind lines**
Find lines 9799 in `niri/config.kdl`:
```kdl
XF86AudioRaiseVolume allow-when-locked=true { spawn "pamixer" "-i" "5"; }
XF86AudioLowerVolume allow-when-locked=true { spawn "pamixer" "-d" "5"; }
XF86AudioMute allow-when-locked=true { spawn "pamixer" "-t"; }
```
Replace with:
```kdl
XF86AudioRaiseVolume allow-when-locked=true { spawn "volume" "up"; }
XF86AudioLowerVolume allow-when-locked=true { spawn "volume" "down"; }
XF86AudioMute allow-when-locked=true { spawn "volume" "mute"; }
```
`allow-when-locked=true` stays — same behaviour, just routed through the wrapper.
- [ ] **Step 2: Validate niri config**
```bash
niri validate 2>&1 | tail -2
```
Expected: a final line `INFO niri: config is valid`. Non-zero exit or `ERROR` means a syntax issue.
- [ ] **Step 3: Confirm pamixer is no longer referenced in keybinds**
```bash
grep -n "pamixer" niri/config.kdl
```
Expected: zero matches (the wrapper is invoked via `volume`, pamixer is implementation detail).
- [ ] **Step 4: Confirm the three new lines are in place**
```bash
grep -n 'spawn "volume"' niri/config.kdl
```
Expected: three lines, one each for `"up"`, `"down"`, `"mute"`:
```
97: XF86AudioRaiseVolume allow-when-locked=true { spawn "volume" "up"; }
98: XF86AudioLowerVolume allow-when-locked=true { spawn "volume" "down"; }
99: XF86AudioMute allow-when-locked=true { spawn "volume" "mute"; }
```
- [ ] **Step 5: Functional smoke test (controller verifies)**
niri picks up `config.kdl` changes live; no reload needed. Press the volume up key — a mako notification should appear within ~100 ms showing the new level + progress bar. Press mute — notification shows `Muted` with empty bar. Press volume up again — notification returns to `Volume: NN%`.
Each key press should replace the previous notification (no stacking), thanks to the `x-canonical-private-synchronous` hint.
- [ ] **Step 6: Commit**
```bash
git add niri/config.kdl
git commit -m "niri: route volume keys through notification wrapper"
```
---
## Final verification (controller, post-merge)
- [ ] Pressing XF86AudioRaiseVolume / XF86AudioLowerVolume produces a mako notification with the new percentage and a progress bar.
- [ ] Pressing XF86AudioMute toggles the mute state; the notification shows `Muted` (with empty bar) when muting and `Volume: NN%` when unmuting.
- [ ] Holding the key (rapid repeat) produces a single updating bubble, not a stack.
- [ ] Volume changes still work on the lock screen (gtklock). The bubble may not be visible while locked depending on mako's layer ordering relative to gtklock; the volume change itself fires regardless.
- [ ] `~/.local/bin/volume bogus` prints the usage message and exits 1.