From a8d3fe8752d6b889eb053b30ff6e986fbb2d8951 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 19 Oct 2026 09:09:10 +0200 Subject: 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 --- .../2026-10-19-notification-settings-design.md | 104 +++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-19-notification-settings-design.md (limited to 'docs/superpowers') 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 > ` (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. -- cgit v1.2.3