From 8eada276caaeef5b1fdca7ae280c300f031c120b Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 06:45:42 +0300 Subject: [PATCH] perf(renderer): reuse global task notification indexes --- .../store/team/teamGlobalTaskNotifications.ts | 105 ++++++++++++------ 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/src/renderer/store/team/teamGlobalTaskNotifications.ts b/src/renderer/store/team/teamGlobalTaskNotifications.ts index 5d1ba503..5e8d8851 100644 --- a/src/renderer/store/team/teamGlobalTaskNotifications.ts +++ b/src/renderer/store/team/teamGlobalTaskNotifications.ts @@ -8,7 +8,12 @@ import { } from '@shared/utils/teamTaskState'; import type { AppConfig } from '@renderer/types/data'; -import type { GlobalTask, TaskComment, TeamMessageNotificationData, TeamSummary } from '@shared/types'; +import type { + GlobalTask, + TaskComment, + TeamMessageNotificationData, + TeamSummary, +} from '@shared/types'; const notifiedClarificationTaskKeys = new Set(); const notifiedStatusChangeKeys = new Set(); @@ -19,6 +24,13 @@ const notifiedBlockedTaskKeys = new Set(); let isFirstFetchAllTasks = true; +interface TaskNotificationIndexes { + readonly firstOldTaskByKey: ReadonlyMap; + readonly lastOldTaskByKey: ReadonlyMap; + readonly oldTaskKeys: ReadonlySet; + readonly oldTasksByTeam: ReadonlyMap; +} + export interface ProcessGlobalTaskNotificationsParams { oldTasks: GlobalTask[]; newTasks: GlobalTask[]; @@ -43,9 +55,7 @@ export function consumeFirstGlobalTasksFetchFlag(): boolean { return wasFirst; } -export function processGlobalTaskNotifications( - params: ProcessGlobalTaskNotificationsParams -): void { +export function processGlobalTaskNotifications(params: ProcessGlobalTaskNotificationsParams): void { const { oldTasks, newTasks, appConfig, teamByName, isInitialFetch } = params; if (isInitialFetch) { @@ -54,18 +64,20 @@ export function processGlobalTaskNotifications( } const notifyOnClarifications = appConfig?.notifications?.notifyOnClarifications ?? true; - detectClarificationNotifications(oldTasks, newTasks, notifyOnClarifications); - detectBlockedTaskNotifications(oldTasks, newTasks, notifyOnClarifications); - detectStatusChangeNotifications(oldTasks, newTasks, appConfig, teamByName); + const oldTaskIndexes = buildTaskNotificationIndexes(oldTasks); + + detectClarificationNotifications(oldTaskIndexes, newTasks, notifyOnClarifications); + detectBlockedTaskNotifications(oldTaskIndexes, newTasks, notifyOnClarifications); + detectStatusChangeNotifications(oldTaskIndexes, newTasks, appConfig, teamByName); const notifyOnTaskComments = appConfig?.notifications?.notifyOnTaskComments ?? true; - detectTaskCommentNotifications(oldTasks, newTasks, notifyOnTaskComments); + detectTaskCommentNotifications(oldTaskIndexes, newTasks, notifyOnTaskComments); const notifyOnTaskCreated = appConfig?.notifications?.notifyOnTaskCreated ?? true; - detectTaskCreatedNotifications(oldTasks, newTasks, notifyOnTaskCreated); + detectTaskCreatedNotifications(oldTaskIndexes, newTasks, notifyOnTaskCreated); const notifyOnAllCompleted = appConfig?.notifications?.notifyOnAllTasksCompleted ?? true; - detectAllTasksCompletedNotification(oldTasks, newTasks, notifyOnAllCompleted); + detectAllTasksCompletedNotification(oldTaskIndexes, newTasks, notifyOnAllCompleted); } function seedGlobalTaskNotificationState(tasks: readonly GlobalTask[]): void { @@ -74,7 +86,9 @@ function seedGlobalTaskNotificationState(tasks: readonly GlobalTask[]): void { notifiedClarificationTaskKeys.add(`${task.teamName}:${task.id}`); } if ((task.blockedBy?.length ?? 0) > 0) { - notifiedBlockedTaskKeys.add(`${task.teamName}:${task.id}:${(task.blockedBy ?? []).join(',')}`); + notifiedBlockedTaskKeys.add( + `${task.teamName}:${task.id}:${(task.blockedBy ?? []).join(',')}` + ); } notifiedStatusChangeKeys.add(`${task.teamName}:${task.id}:${task.status}`); if (isTeamTaskNeedsFixActionable(task)) { @@ -105,19 +119,53 @@ function seedGlobalTaskNotificationState(tasks: readonly GlobalTask[]): void { } } +function getTaskNotificationKey(task: Pick): string { + return `${task.teamName}:${task.id}`; +} + +function buildTaskNotificationIndexes(tasks: readonly GlobalTask[]): TaskNotificationIndexes { + const firstOldTaskByKey = new Map(); + const lastOldTaskByKey = new Map(); + const oldTaskKeys = new Set(); + const oldTasksByTeam = new Map(); + + for (const task of tasks) { + const key = getTaskNotificationKey(task); + if (!firstOldTaskByKey.has(key)) { + firstOldTaskByKey.set(key, task); + } + lastOldTaskByKey.set(key, task); + oldTaskKeys.add(key); + + const teamTasks = oldTasksByTeam.get(task.teamName); + if (teamTasks) { + teamTasks.push(task); + } else { + oldTasksByTeam.set(task.teamName, [task]); + } + } + + return { + firstOldTaskByKey, + lastOldTaskByKey, + oldTaskKeys, + oldTasksByTeam, + }; +} + function showTeamNotification(data: TeamMessageNotificationData): void { void api.teams?.showMessageNotification(data).catch(() => undefined); } function detectClarificationNotifications( - oldTasks: GlobalTask[], + oldTaskIndexes: TaskNotificationIndexes, newTasks: GlobalTask[], notifyEnabled: boolean ): void { for (const task of newTasks) { - const key = `${task.teamName}:${task.id}`; + const key = getTaskNotificationKey(task); if (task.needsClarification === 'user') { - const oldTask = oldTasks.find((t) => t.teamName === task.teamName && t.id === task.id); + const oldTask = oldTaskIndexes.firstOldTaskByKey.get(key); if (oldTask?.needsClarification !== 'user' && !notifiedClarificationTaskKeys.has(key)) { notifiedClarificationTaskKeys.add(key); showClarificationNotification(task, !notifyEnabled); @@ -155,7 +203,7 @@ function showClarificationNotification(task: GlobalTask, suppressToast: boolean) } function detectStatusChangeNotifications( - oldTasks: GlobalTask[], + oldTaskIndexes: TaskNotificationIndexes, newTasks: GlobalTask[], config: AppConfig | null, teamByName: Record @@ -168,7 +216,7 @@ function detectStatusChangeNotifications( const onlySolo = config?.notifications?.statusChangeOnlySolo ?? true; for (const task of newTasks) { - const oldTask = oldTasks.find((t) => t.teamName === task.teamName && t.id === task.id); + const oldTask = oldTaskIndexes.firstOldTaskByKey.get(getTaskNotificationKey(task)); if (!oldTask) continue; const taskKanbanColumn = getTeamTaskWorkflowColumn(task); @@ -254,15 +302,12 @@ function showStatusChangeNotification( } function detectTaskCommentNotifications( - oldTasks: GlobalTask[], + oldTaskIndexes: TaskNotificationIndexes, newTasks: GlobalTask[], notifyEnabled: boolean ): void { - const oldTaskMap = new Map(oldTasks.map((t) => [`${t.teamName}:${t.id}`, t])); - for (const task of newTasks) { - const mapKey = `${task.teamName}:${task.id}`; - const oldTask = oldTaskMap.get(mapKey); + const oldTask = oldTaskIndexes.lastOldTaskByKey.get(getTaskNotificationKey(task)); const oldCommentCount = oldTask?.comments?.length ?? 0; const newCommentCount = task.comments?.length ?? 0; @@ -346,14 +391,12 @@ function showTaskReviewRequestedNotification( } function detectBlockedTaskNotifications( - oldTasks: GlobalTask[], + oldTaskIndexes: TaskNotificationIndexes, newTasks: GlobalTask[], notifyEnabled: boolean ): void { - const oldTaskMap = new Map(oldTasks.map((task) => [`${task.teamName}:${task.id}`, task])); - for (const task of newTasks) { - const oldTask = oldTaskMap.get(`${task.teamName}:${task.id}`); + const oldTask = oldTaskIndexes.lastOldTaskByKey.get(getTaskNotificationKey(task)); const oldBlockedBy = new Set(oldTask?.blockedBy?.filter(Boolean) ?? []); const newBlockedBy = Array.from(new Set(task.blockedBy?.filter(Boolean) ?? [])); const taskKeyPrefix = `${task.teamName}:${task.id}:`; @@ -407,15 +450,13 @@ function showTaskBlockedNotification( } function detectTaskCreatedNotifications( - oldTasks: GlobalTask[], + oldTaskIndexes: TaskNotificationIndexes, newTasks: GlobalTask[], notifyEnabled: boolean ): void { - const oldTaskKeys = new Set(oldTasks.map((t) => `${t.teamName}:${t.id}`)); - for (const task of newTasks) { - const key = `${task.teamName}:${task.id}`; - if (oldTaskKeys.has(key)) continue; + const key = getTaskNotificationKey(task); + if (oldTaskIndexes.oldTaskKeys.has(key)) continue; if (notifiedCreatedTaskKeys.has(key)) continue; notifiedCreatedTaskKeys.add(key); @@ -444,7 +485,7 @@ function showTaskCreatedNotification(task: GlobalTask, suppressToast: boolean): } function detectAllTasksCompletedNotification( - oldTasks: GlobalTask[], + oldTaskIndexes: TaskNotificationIndexes, newTasks: GlobalTask[], notifyEnabled: boolean ): void { @@ -464,7 +505,7 @@ function detectAllTasksCompletedNotification( } if (notifiedAllCompletedTeams.has(teamName)) continue; - const oldTeamTasks = oldTasks.filter((t) => t.teamName === teamName); + const oldTeamTasks = oldTaskIndexes.oldTasksByTeam.get(teamName) ?? []; const wasAlreadyAllCompleted = oldTeamTasks.length > 0 && oldTeamTasks.every(isTeamTaskFinalForCompletionNotification); if (wasAlreadyAllCompleted) {