Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 40 additions & 15 deletions src/renderer/src/moss-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import {
} from './electron-api.js';
import {
devModeToolLibraryFromDevConfig,
encodeAndStringify,
findAppForDnaHash,
getNotificationState,
sortVersionsDescending,
Expand Down Expand Up @@ -167,6 +168,13 @@ export type DevModeToolLibrary = {
devCollective: DeveloperCollective;
};

function getNotificationFeedEntryId({ source, notification }: MossNotification): string {
const sourceKey =
source.type === 'applet' ? `applet:${source.appletId}` : `group:${source.groupDnaHash}`;

return `${sourceKey}:${encodeAndStringify(notification)}`;
}

export class MossStore {
constructor(
public adminWebsocket: AdminWebsocket,
Expand Down Expand Up @@ -698,12 +706,25 @@ export class MossStore {
): Promise<void> {
const mainWindowFocused = await window.electronAPI.isMainWindowFocused();

for (const notification of notifications) {
// 1. Persist if requested (tools do this, foyer doesn't)
if (options.persist && source.type === 'applet') {
storeAppletNotifications([notification], source.appletId, true, this.persistedStore);
}
// 1. Persist if requested (tools do this, foyer doesn't)
if (options.persist && source.type === 'applet') {
storeAppletNotifications(notifications, source.appletId, true, this.persistedStore);
}

// Deduplicate incoming notifications against what's already in the feed so that
// OS notifications, sounds, and unread counts are not triggered for duplicates.
const existingFeedIds = new Set(
get(this._notificationFeed).map((entry) => getNotificationFeedEntryId(entry)),
);
const seenInBatch = new Set<string>();
const newNotifications = notifications.filter((notification) => {
const id = getNotificationFeedEntryId({ source, notification, sourceName: options.sourceName });
if (existingFeedIds.has(id) || seenInBatch.has(id)) return false;
seenInBatch.add(id);
return true;
});

for (const notification of newNotifications) {
// 2. Update unread count for sidebar dot
if (options.updateUnreadCount) {
if (source.type === 'applet') {
Expand All @@ -727,16 +748,6 @@ export class MossStore {
}
}

// 3. Add to activity feed
if (options.showInFeed) {
this._notificationFeed.update((feed) => {
const newNotification: MossNotification = { source, notification, sourceName: options.sourceName };
return [newNotification, ...feed].sort(
(a, b) => b.notification.timestamp - a.notification.timestamp,
);
});
}

// 4. Send OS notification if conditions are met
if (
options.sendOSNotification &&
Expand Down Expand Up @@ -764,6 +775,20 @@ export class MossStore {
}
}

// 3. Add to activity feed (done once after the loop to avoid O(k*n²))
if (options.showInFeed && newNotifications.length > 0) {
this._notificationFeed.update((feed) => {
const newEntries: MossNotification[] = newNotifications.map((notification) => ({
source,
notification,
sourceName: options.sourceName,
}));
return [...newEntries, ...feed].sort(
(a, b) => b.notification.timestamp - a.notification.timestamp,
);
});
}

}

/**
Expand Down
Loading