aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-19 09:09:10 +0200
committerDanilo M. <danix@danix.xyz>2026-10-19 09:09:10 +0200
commita8d3fe8752d6b889eb053b30ff6e986fbb2d8951 (patch)
treee3ef4db81983e450495856d5df236866e98cf4f1 /docs/superpowers
parent61d17788ff132fcd02d6831934cfe9beba1d49ce (diff)
downloadquickshell-a8d3fe8752d6b889eb053b30ff6e986fbb2d8951.tar.gz
quickshell-a8d3fe8752d6b889eb053b30ff6e986fbb2d8951.zip
docs: spec the notification settings page
One config file in ~/.config/notifyd read by the daemon (timeouts, caps, picked up by notifyctl reload) and by both shells (placement, mute, DND). Snooze stays out: the status tile already covers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/specs/2026-10-19-notification-settings-design.md104
1 files changed, 104 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-10-19-notification-settings-design.md b/docs/superpowers/specs/2026-10-19-notification-settings-design.md
new file mode 100644
index 0000000..9de79e5
--- /dev/null
+++ b/docs/superpowers/specs/2026-10-19-notification-settings-design.md
@@ -0,0 +1,104 @@
+# Notification settings page
+
+A settings window page for the notification daemon (`notifyd`, separate repo)
+and the balloon shell (`notifications/`). Today nothing is configurable: the
+daemon hardcodes its timeouts and caps, the balloon shell hardcodes DP-1
+bottom-right, and DND always passes critical only.
+
+Snooze is out of scope: the status tile already has a snooze switch with a
+remembered minutes field.
+
+## One file, three readers
+
+`~/.config/notifyd/config.json`. A missing file, a missing key, or a value of
+the wrong type reads as the default below, so the defaults reproduce today's
+behaviour exactly.
+
+```json
+{
+ "timeouts": { "low": 10, "normal": 10, "critical": 0 },
+ "liveCap": 20,
+ "historyCap": 20,
+ "balloons": { "screen": "DP-1", "corner": "bottom-right" },
+ "dndPassCritical": true,
+ "muted": []
+}
+```
+
+- `timeouts` are seconds, used only when the client sends `expire_timeout -1`.
+ `0` means never expires. A client's explicit timeout still wins, as the
+ freedesktop spec requires.
+- `liveCap`, `historyCap`: 1..100. Out of range clamps.
+- `balloons.screen`: a monitor name. An unknown name falls back to the first
+ screen, the existing pattern.
+- `balloons.corner`: `top-left`, `top-right`, `bottom-left`, `bottom-right`.
+- `dndPassCritical`: under DND, whether critical balloons still show. Low and
+ normal are always withheld under DND.
+- `muted`: app names, matched exactly against the queue entry's `app`. A muted
+ app gets no balloon; it still appears in the drawer's reserved space and in
+ history (suppression lives in the balloon shell, see `notifications/README.md`).
+
+Readers:
+
+| reader | keys | picks up a change |
+| --- | --- | --- |
+| notifyd | `timeouts`, `liveCap`, `historyCap` | `notifyctl reload` |
+| balloon shell | `balloons`, `dndPassCritical`, `muted` | watched `FileView` |
+| settings page | all | watched `FileView` |
+
+The daemon ignores keys it does not use, and the shells do too, so either side
+can grow keys alone. A write preserves unknown keys (same rule as `Prefs.qml`).
+
+## notifyd changes
+
+- `internal/notify/config.go`: `Config` struct, `DefaultConfig()`,
+ `LoadConfig(path) Config`. Malformed JSON logs and returns the defaults: a
+ hand typo must never stop the daemon. Path is
+ `$XDG_CONFIG_HOME/notifyd/config.json`, falling back to `~/.config`.
+- `DefaultTimeoutMS` takes the config's timeouts instead of constants.
+- `Store` reads `liveCap`/`historyCap` from fields set at construction and by
+ `SetCaps`. A lowered cap trims at the next add, not at reload: a reload must
+ not silently drop live notifications.
+- `control.Reload()` D-Bus method re-reads the file and applies it under the
+ store's lock; `notifyctl reload` drives it. A missing daemon makes
+ `notifyctl` exit non-zero, which the page reports.
+- `config_test.go`: defaults on missing file, defaults on malformed JSON,
+ partial file merges over defaults, cap clamping.
+
+## Quickshell changes
+
+- `shared/Notify.qml` gains `config`, parsed from a watched `FileView` with the
+ defaults applied, so both shells see the same resolved values.
+- `notifications/Balloons.qml`: screen and anchors from
+ `Notify.config.balloons`; the column grows away from the anchored edge
+ (top corners stack downward). The balloon visibility check adds the mute
+ list and `dndPassCritical`.
+- `desktop/modules/notifications/`: a `Module` in group "Desktop", settings
+ only, with a `SaveBar` (draft, save, discard), like HyprIdle. Rows:
+ - Timeouts: a seconds field and a "never" switch per urgency.
+ - Caps: two number fields.
+ - Placement: screen chooser from `Quickshell.screens`, a four-corner picker.
+ - Suppression: "Critical passes DND" switch; muted apps as removable chips,
+ added from the app names in the current queue and history, or typed.
+- Save writes the file through a `Process` running `cat > <file>` (the
+ `notes` pattern: the exit code is the outcome) after `mkdir -p`, then runs
+ `notifyctl reload`. If reload fails the page says the file was saved but the
+ daemon was not reached.
+- Search keywords cover timeout, history, mute, dnd, corner, monitor.
+
+Editing `shared/Notify.qml` needs a forced reload of both shells (hot reload
+does not follow the symlink), and adding the module directory needs
+`desktop/shell.qml` touched or a restart.
+
+## Docs
+
+- notifyd `README.md`: the config file and `notifyctl reload`.
+- `notifications/README.md`: placement, mute and DND now come from the file.
+- `AGENTS.md`: the settings list in "What this is" gains notifications.
+
+## Verification
+
+- `go test ./...` in notifyd.
+- `notify-send` with a 3s low timeout configured: balloon leaves at 3s.
+- Muted app: no balloon, present in drawer list.
+- Corner and screen changes: by eye, user's screen.