Collapse identical notifications from the same sender into one toast (#13522)

* Collapse identical notifications from the same sender into one toast

A web app open in several tabs fires one notification per tab for a single
reminder, stacking identical toasts. A new notification matching an on-screen
toast's app, summary, and body now replaces it like a replaces_id update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Only collapse live duplicates that share image and click target

Screen recording toasts share text but preview and open different files, and
replayed history rows share images with entries still in history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Compare a notification's current content when collapsing duplicates

A replaces_id update can land while the insert is deferred, so the initial
snapshot may no longer describe what the notification says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier HanssonandClaude Opus 5.5 authored and GitHub committed 2026-09-28 12:04:15 +02:00
1 parent f814639556
commit de63134b88
3 files changed
+85

No files matched your search

+32
View File
@@ -360,6 +360,30 @@ assert(
'notifications ignore identity fields when deciding whether a refresh has work'
)
const heyReminder = { originalId: 20, app: 'Chromium', summary: 'Interview', body: 'Today, 11:00 AM' }
assert(
notifications.isDuplicatePopup(heyReminder, Object.assign({}, heyReminder, { originalId: 21 })),
'notifications treat the same message from the same sender under a new id as a duplicate'
)
assert(
!notifications.isDuplicatePopup(heyReminder, heyReminder),
'notifications leave a same-id update to the replaces_id path'
)
assert(
!notifications.isDuplicatePopup(heyReminder, Object.assign({}, heyReminder, { originalId: 21, body: 'Today, 2:00 PM' })),
'notifications keep toasts whose body differs'
)
assert(
!notifications.isDuplicatePopup(heyReminder, Object.assign({}, heyReminder, { originalId: 21, app: 'Slack' })),
'notifications keep identical text from a different sender'
)
assert(
!notifications.isDuplicatePopup(
{ originalId: 30, app: 'omarchy-action', summary: 'Screen recording saved', body: '', image: '/tmp/a.png', execArgv: '["mpv","--","/tmp/a.mp4"]' },
{ originalId: 31, app: 'omarchy-action', summary: 'Screen recording saved', body: '', image: '/tmp/b.png', execArgv: '["mpv","--","/tmp/b.mp4"]' }),
'notifications keep same-text toasts that preview and open different files'
)
const settings = notifications.parseSettings(JSON.stringify({ version: 3, dnd: true }))
assertEqual(settings.dnd, true, 'notifications parse the persisted DND state')
assertEqual(settings.legacy, false, 'notifications do not flag a current settings file as legacy')
@@ -647,6 +671,14 @@ assert(
/watchForUpdates\(notification, snapshot\)/.test(serviceQml),
'notifications service watches a shown notification for in-place updates'
)
assert(
/removePopupsByOriginalId\(snapshot\.originalId, [^\n]*\)\n\s*removeDuplicatePopups\(service\.currentContent\(notification, snapshot\)\)\n\s*popupModel\.insert\(0, snapshot\)/.test(serviceQml),
'notifications service replaces an on-screen duplicate before showing the new copy'
)
assert(
/isDuplicatePopup\(row, snapshot\) \|\| isRestoredRow\(row\)\) continue\n\s*var ref = liveRefs\[row\.originalId\]\n\s*if \(!ref\) continue/.test(serviceQml),
'notifications service only collapses duplicates of toasts still backed by a live notification'
)
assert(
/if \(signal && typeof signal\.connect === "function"\) signal\.connect\(refresh\)/.test(serviceQml),
'notifications service refreshes the popup from every property the card draws'