aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/plans/2026-09-15-notifyd.md24
-rw-r--r--docs/superpowers/specs/2026-09-15-notification-daemon-design.md23
2 files changed, 27 insertions, 20 deletions
diff --git a/docs/superpowers/plans/2026-09-15-notifyd.md b/docs/superpowers/plans/2026-09-15-notifyd.md
index 9a017d8..afb938f 100644
--- a/docs/superpowers/plans/2026-09-15-notifyd.md
+++ b/docs/superpowers/plans/2026-09-15-notifyd.md
@@ -140,10 +140,10 @@ func TestEffectiveTimeoutMS(t *testing.T) {
u Urgency
want int64
}{
- {"zero uses low default", 0, Low, 10_000},
- {"zero uses normal default", 0, Normal, 10_000},
- {"zero critical never", 0, Critical, 0},
- {"minus one never", -1, Normal, 0},
+ {"minus one uses low default", -1, Low, 10_000},
+ {"minus one uses normal default", -1, Normal, 10_000},
+ {"minus one critical never", -1, Critical, 0},
+ {"zero never", 0, Normal, 0},
{"explicit wins", 3_000, Normal, 3_000},
{"sub second exact", 1, Normal, 1},
}
@@ -228,8 +228,9 @@ const (
Critical Urgency = "critical"
)
-// DefaultTimeoutMS is the balloon lifetime for an urgency when the client sends
-// expire_timeout 0. Critical never expires, which is 0 here.
+// DefaultTimeoutMS is the balloon lifetime for an urgency when the client
+// leaves the choice to the server (expire_timeout -1). Critical never expires,
+// which is 0 here.
func DefaultTimeoutMS(u Urgency) int64 {
switch u {
case Low, Normal:
@@ -239,14 +240,17 @@ func DefaultTimeoutMS(u Urgency) int64 {
}
}
-// EffectiveTimeoutMS resolves the client's expire_timeout: 0 means the urgency
-// default, -1 means never, anything positive is milliseconds and wins.
+// EffectiveTimeoutMS resolves the client's expire_timeout per the freedesktop
+// spec: -1 means the server decides (the urgency default), 0 means never, and
+// anything positive is milliseconds and wins. The spec is precise about the
+// direction and libnotify's default is -1, so getting it backwards would make
+// every plain notify-send immortal.
func EffectiveTimeoutMS(expire int32, u Urgency) int64 {
switch {
case expire == -1:
- return 0
- case expire == 0:
return DefaultTimeoutMS(u)
+ case expire == 0:
+ return 0
default:
return int64(expire)
}
diff --git a/docs/superpowers/specs/2026-09-15-notification-daemon-design.md b/docs/superpowers/specs/2026-09-15-notification-daemon-design.md
index fa287e1..4e2806a 100644
--- a/docs/superpowers/specs/2026-09-15-notification-daemon-design.md
+++ b/docs/superpowers/specs/2026-09-15-notification-daemon-design.md
@@ -60,10 +60,12 @@ closed by a `CloseNotification` call.
All of this is in the daemon and is independent of any renderer.
**Timeout, and the two lifetimes.** `expire_timeout` from `Notify` is honoured
-exactly as the notification's balloon lifetime: `0` means use the urgency
-default, `-1` means never expire, any positive value is milliseconds. The
-urgency defaults are `10s` for low, `10s` for normal and never for critical,
-matching the running dunst. `ronema` relies on `-t 0` meaning never and `-t 1`
+exactly as the notification's balloon lifetime, in the spec's direction: `-1`
+means the server decides, so it takes the urgency default; `0` means never; any
+positive value is milliseconds and wins. The urgency defaults are `10s` for
+low, `10s` for normal and never for critical, matching the running dunst.
+libnotify sends `-1` by default, so a plain `notify-send` gets the urgency
+default, and `dunst` agrees. `ronema` relies on `-t 0` meaning never and `-t 1`
meaning effectively immediate; both work unchanged.
At expiry the daemon emits `NotificationClosed(id, 1)` and the balloon goes,
@@ -122,15 +124,16 @@ A live notification is one object:
"urgency": "normal",
"icon": "/home/you/.local/share/icons/.../mail-unread-multiple.svg",
"actions": [["default", "open"]],
- "created": 1758000000,
- "expires": 1758000010
+ "created": 1758000000000,
+ "expires": 1758000010000
}
`app`, `summary` and `body` are markup. `icon` is an absolute path or empty.
-`actions` is the spec's key and label pairs. `expires` is an epoch second, or
-`0` for a notification that never expires; a value in the past means the
-balloon has gone and the entry is inert. The history objects are the same
-shape.
+`actions` is the spec's key and label pairs. `created` and `expires` are epoch
+milliseconds, so a sub-second timeout (`ronema` sends `-t 1`) is exact rather
+than rounding to zero, which would read as never. `expires` is `0` for a
+notification that never expires; a value in the past means the balloon has gone
+and the entry is inert. The history objects are the same shape.
`queue.json` is capped at 20; an arrival beyond the cap pushes the oldest into
history.