Extend notification popup lifetime

This commit is contained in:
David Heinemeier Hansson
2026-05-27 10:50:31 +02:00
parent ee97329aee
commit 88816ab9be
3 changed files with 38 additions and 7 deletions
@@ -56,6 +56,8 @@ function glyphFromHints(hints) {
function snapshotOf(notification, timestamp) {
var n = notification || {}
var id = n.id || 0
var expireTimeout = Number(n.expireTimeout || 0)
if (!isFinite(expireTimeout) || expireTimeout < 0) expireTimeout = 0
return {
id: id,
originalId: id,
@@ -66,6 +68,7 @@ function snapshotOf(notification, timestamp) {
image: n.image || "",
glyph: glyphFromHints(n.hints),
urgency: n.urgency,
expireTimeout: expireTimeout,
timestamp: timestamp === undefined ? Date.now() : timestamp,
ref: notification
}
@@ -83,6 +86,7 @@ function historyEntry(value, normalUrgency) {
image: e.image || "",
glyph: e.glyph || "",
urgency: typeof e.urgency === "number" ? e.urgency : normalUrgency,
expireTimeout: 0,
timestamp: e.timestamp || 0,
ref: null
}
+30 -6
View File
@@ -92,17 +92,27 @@ Item {
readonly property int historyCap: 100
property var imageCacheQueue: []
function durationFor(urgency) {
readonly property int lowPopupDuration: 5000
readonly property int normalPopupDuration: 8000
readonly property int maxPopupDuration: 30000
function durationFor(urgency, expireTimeout) {
switch (urgency) {
case NotificationUrgency.Critical:
return 0
case NotificationUrgency.Low:
return 3000
return Math.min(maxPopupDuration, Math.max(lowPopupDuration, requestedDuration(expireTimeout)))
default:
return 5000
return Math.min(maxPopupDuration, Math.max(normalPopupDuration, requestedDuration(expireTimeout)))
}
}
function requestedDuration(expireTimeout) {
var seconds = Number(expireTimeout || 0)
if (!isFinite(seconds) || seconds <= 0) return 0
return Math.round(seconds * 1000)
}
// DND bypass: only let through notifications we trust to be intentional
// and rare.
// - omarchy-action: a user-action confirmation toast ("Theme changed",
@@ -237,6 +247,7 @@ Item {
image: row.image,
glyph: row.glyph || "",
urgency: row.urgency,
expireTimeout: row.expireTimeout || 0,
timestamp: row.timestamp
}
}
@@ -257,6 +268,14 @@ Item {
}
function dismissPopup(index) {
removePopup(index, "dismiss")
}
function expirePopup(index) {
removePopup(index, "expire")
}
function removePopup(index, reason) {
if (index < 0 || index >= popupModel.count) return
var entry = popupModel.get(index)
var ref = entry ? entry.ref : null
@@ -264,7 +283,10 @@ Item {
popupModel.remove(index)
if (ref) {
try {
if (ref.tracked) ref.dismiss()
if (ref.tracked) {
if (reason === "expire" && typeof ref.expire === "function") ref.expire()
else ref.dismiss()
}
} catch (e) {
// Object already torn down by the server — nothing to dismiss.
}
@@ -555,6 +577,7 @@ Item {
image: r.image,
glyph: r.glyph || "",
urgency: r.urgency,
expireTimeout: r.expireTimeout || 0,
timestamp: r.timestamp
})
}
@@ -753,6 +776,7 @@ Item {
required property string image
required property string glyph
required property int urgency
required property double expireTimeout
required property double timestamp
// Each card sizes itself based on mode (text vs media); the slot
@@ -761,7 +785,7 @@ Item {
Layout.alignment: Qt.AlignRight
implicitHeight: card.implicitHeight
readonly property real lifetime: service.durationFor(cardSlot.urgency)
readonly property real lifetime: service.durationFor(cardSlot.urgency, cardSlot.expireTimeout)
property real remainingLifetime: 1.0
readonly property bool ticking: cardSlot.lifetime > 0 && !card.hovered
@@ -774,7 +798,7 @@ Item {
cardSlot.remainingLifetime -= 50.0 / cardSlot.lifetime
if (cardSlot.remainingLifetime <= 0) {
cardSlot.remainingLifetime = 0
service.dismissPopup(cardSlot.index)
service.expirePopup(cardSlot.index)
}
}
}
+4 -1
View File
@@ -53,7 +53,8 @@ const notification = {
body: 'Body',
image: 'file:///tmp/mail.png',
hints: { 'omarchy-glyph': '!' },
urgency: 1
urgency: 1,
expireTimeout: 1.5
}
const snapshot = notifications.snapshotOf(notification, 12345)
assertDeepEqual(
@@ -67,6 +68,7 @@ assertDeepEqual(
image: snapshot.image,
glyph: snapshot.glyph,
urgency: snapshot.urgency,
expireTimeout: snapshot.expireTimeout,
timestamp: snapshot.timestamp
},
{
@@ -79,6 +81,7 @@ assertDeepEqual(
image: 'file:///tmp/mail.png',
glyph: '!',
urgency: 1,
expireTimeout: 1.5,
timestamp: 12345
},
'notifications create stable snapshots'