From 56b4ef7f2b08187eb98c75ca062924602369ec4f Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Thu, 27 Aug 2026 15:59:25 -0700 Subject: [PATCH] Leave worker notifications out of the dashboard entirely --- .../UI/Dashboard/UIDashboardNotifications.js | 10 ++----- .../src/UI/Dashboard/notificationCenter.js | 28 ++++++++----------- .../UI/Dashboard/notificationCenter.test.js | 22 +++++---------- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/src/gui/src/UI/Dashboard/UIDashboardNotifications.js b/src/gui/src/UI/Dashboard/UIDashboardNotifications.js index d42470ac9..14075cf3f 100644 --- a/src/gui/src/UI/Dashboard/UIDashboardNotifications.js +++ b/src/gui/src/UI/Dashboard/UIDashboardNotifications.js @@ -31,7 +31,6 @@ import { planBurstToasts, reconcileWithServer, setReadAt, - shouldToast, titleWithBadge, toEntry, unreadCount, @@ -71,7 +70,6 @@ const checkAllIcon = ``, - worker: ``, default: bellIcon, }; @@ -515,15 +513,11 @@ export default function UIDashboardNotifications ({ $el_window, socket }) { for ( const entry of fresh ) surfaced.add(entry.uid); for ( const entry of result.added ) justAdded.add(entry.uid); render(); - - // Quiet arrivals only update the badge and the list; the toast and - // its spoken counterpart stay silent alike. - const loud = fresh.filter((entry) => shouldToast(entry.notification)); - announce(loud); + announce(fresh); // Open, the panel is already showing them. if ( isOpen ) return; - const { shown, folded } = planBurstToasts(loud); + const { shown, folded } = planBurstToasts(fresh); for ( const entry of [...shown].reverse() ) toast(entry); if ( folded > 0 ) toastSummary(folded); }; diff --git a/src/gui/src/UI/Dashboard/notificationCenter.js b/src/gui/src/UI/Dashboard/notificationCenter.js index 2c8584e8b..9921ca2f5 100644 --- a/src/gui/src/UI/Dashboard/notificationCenter.js +++ b/src/gui/src/UI/Dashboard/notificationCenter.js @@ -45,6 +45,12 @@ /** Notifications announcing a share; the two templates the backend writes. */ const SHARE_TEMPLATES = new Set(['file-shared-with-you', 'file-shared-before-you-joined']); +/** + * Senders the notification center leaves out entirely — a worker's deploy + * confirmations are noise next to things that need the user. + */ +const HIDDEN_SOURCES = new Set(['worker']); + // SQLite's CURRENT_TIMESTAMP: UTC with no zone marker, which `Date.parse` // would otherwise read as local time. const SQL_TIMESTAMP = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?$/; @@ -81,7 +87,8 @@ export const parseCreatedAt = (value) => { * (`{ uid, value, created_at, acknowledged }`) or a socket item (`{ uid, * notification, created_at? }`). A push carries no `acknowledged` and is * unread — the server only pushes what the user hasn't dismissed. `null` - * for anything without a uid — there is nothing to acknowledge it by. + * for anything without a uid — there is nothing to acknowledge it by — and + * for senders the center doesn't show. * * @param {Object} raw * @param {number} now - Epoch ms; stands in for `created_at` when absent @@ -92,6 +99,7 @@ export const toEntry = (raw, now) => { if ( typeof uid !== 'string' || uid === '' ) return null; const payload = raw.notification ?? raw.value; const notification = payload && typeof payload === 'object' ? payload : {}; + if ( HIDDEN_SOURCES.has(notification.source) ) return null; const createdAt = parseCreatedAt(raw.created_at); return { uid, @@ -202,28 +210,14 @@ export const isUnread = (entry) => entry.readAt === null; export const unreadCount = (entries) => entries.filter(isUnread).length; /** The senders that have a glyph of their own; anything else gets the bell. */ -const GLYPH_SOURCES = new Set(['sharing', 'worker']); - -/** - * Senders whose notifications go straight to the panel and badge without a - * toast — a worker finishing a deploy is routine, not an interruption. - */ -const QUIET_SOURCES = new Set(['worker']); - -/** - * Whether an arrival should interrupt with a toast, or just wait in the panel. - * - * @param {NotificationPayload} notification - * @returns {boolean} - */ -export const shouldToast = (notification) => ! QUIET_SOURCES.has(notification?.source); +const GLYPH_SOURCES = new Set(['sharing']); /** * Which glyph an entry shows, by who sent it. A plain property lookup * would let a `source` like `constructor` reach into the prototype. * * @param {NotificationPayload} notification - * @returns {'sharing'|'worker'|'default'} + * @returns {'sharing'|'default'} */ export const glyphKey = (notification) => ( GLYPH_SOURCES.has(notification?.source) ? notification.source : 'default' diff --git a/src/gui/src/UI/Dashboard/notificationCenter.test.js b/src/gui/src/UI/Dashboard/notificationCenter.test.js index 30a5f3cb4..60bad7eb0 100644 --- a/src/gui/src/UI/Dashboard/notificationCenter.test.js +++ b/src/gui/src/UI/Dashboard/notificationCenter.test.js @@ -30,7 +30,6 @@ import { planBurstToasts, reconcileWithServer, setReadAt, - shouldToast, sortEntries, titleWithBadge, toEntry, @@ -123,6 +122,13 @@ describe('toEntry', () => { expect(toEntry(null, NOW)).toBeNull(); expect(toEntry(undefined, NOW)).toBeNull(); }); + + it('leaves out worker notifications, listed or pushed', () => { + const worker = { title: 'Successfully deployed https://x.puter.work', source: 'worker', template: 'user-requesting-share' }; + expect(toEntry({ uid: 'a', value: worker, created_at: '2026-08-27 12:00:00' }, NOW)).toBeNull(); + expect(toEntry({ uid: 'b', notification: worker }, NOW)).toBeNull(); + expect(toEntry({ uid: 'c', notification: { ...worker, source: 'sharing' } }, NOW)).not.toBeNull(); + }); }); describe('sortEntries', () => { @@ -289,7 +295,6 @@ describe('notificationTarget', () => { describe('glyphKey', () => { it('names the glyph for known senders', () => { expect(glyphKey({ source: 'sharing' })).toBe('sharing'); - expect(glyphKey({ source: 'worker' })).toBe('worker'); }); it('falls back to the bell for anything else', () => { @@ -402,16 +407,3 @@ describe('planBurstToasts', () => { expect(folded).toBe(9); }); }); - -describe('shouldToast', () => { - it('keeps worker notifications out of the toasts', () => { - expect(shouldToast({ source: 'worker', title: 'Successfully deployed https://x.puter.work' })).toBe(false); - }); - - it('toasts everything else', () => { - expect(shouldToast({ source: 'sharing' })).toBe(true); - expect(shouldToast({ source: 'billing' })).toBe(true); - expect(shouldToast({})).toBe(true); - expect(shouldToast(null)).toBe(true); - }); -});