From e718ccf39ab5fdec2ec40568ed2b9dba4c2d3135 Mon Sep 17 00:00:00 2001 From: 777genius Date: Fri, 22 May 2026 10:00:54 +0300 Subject: [PATCH] refactor(team): extract refresh timestamps --- src/renderer/store/slices/teamSlice.ts | 22 +++---- .../store/team/teamDataRefreshTimestamps.ts | 21 +++++++ .../store/teamDataRefreshTimestamps.test.ts | 60 +++++++++++++++++++ 3 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 src/renderer/store/team/teamDataRefreshTimestamps.ts create mode 100644 test/renderer/store/teamDataRefreshTimestamps.test.ts diff --git a/src/renderer/store/slices/teamSlice.ts b/src/renderer/store/slices/teamSlice.ts index 56837103..3a10db66 100644 --- a/src/renderer/store/slices/teamSlice.ts +++ b/src/renderer/store/slices/teamSlice.ts @@ -29,6 +29,12 @@ import { isTeamTaskNeedsFixActionable, } from '@shared/utils/teamTaskState'; +import { + clearAllLastResolvedTeamDataRefreshes, + clearLastResolvedTeamDataRefreshAt, + hasLastResolvedTeamDataRefreshAt, + recordLastResolvedTeamDataRefresh, +} from '../team/teamDataRefreshTimestamps'; import { getFullTeamDataRequestKey, getTeamDataRequestKey, @@ -116,6 +122,7 @@ import type { } from '@shared/types'; import type { StateCreator } from 'zustand'; +export { getLastResolvedTeamDataRefreshAt } from '../team/teamDataRefreshTimestamps'; export { selectTeamDataForName, selectTeamIsAliveForName, @@ -164,7 +171,6 @@ const pendingFreshTeamMessagesHeadRefreshes = new Set(); const inFlightTeamMemberActivityMetaRequests = new Map>(); const pendingFreshTeamMemberActivityMetaRefreshes = new Set(); const pendingTeamPendingReplyRefreshTimers = new Map>(); -const lastResolvedTeamDataRefreshAtByTeam = new Map(); let inFlightGlobalTasksRefresh: Promise | null = null; let pendingFreshGlobalTasksRefresh = false; const memberSpawnStatusesIpcBackoffUntilByTeam = new Map(); @@ -213,10 +219,6 @@ export function isTeamDataRefreshPending(teamName: string): boolean { ); } -export function getLastResolvedTeamDataRefreshAt(teamName: string): number | undefined { - return lastResolvedTeamDataRefreshAtByTeam.get(teamName); -} - export function __resetTeamSliceModuleStateForTests(): void { inFlightTeamDataRequests.clear(); inFlightRefreshTeamDataCalls.clear(); @@ -237,7 +239,7 @@ export function __resetTeamSliceModuleStateForTests(): void { } pendingTeamPendingReplyRefreshTimers.clear(); clearAllPendingReplyRefreshWaits(); - lastResolvedTeamDataRefreshAtByTeam.clear(); + clearAllLastResolvedTeamDataRefreshes(); clearAllTeamLocalStateEpochs(); memberSpawnStatusesIpcBackoffUntilByTeam.clear(); teamRefreshBurstDiagnostics.clear(); @@ -271,7 +273,7 @@ function clearTeamScopedTransientState(teamName: string): void { pendingFreshTeamMessagesHeadRefreshes.delete(teamName); inFlightTeamMemberActivityMetaRequests.delete(teamName); pendingFreshTeamMemberActivityMetaRefreshes.delete(teamName); - lastResolvedTeamDataRefreshAtByTeam.delete(teamName); + clearLastResolvedTeamDataRefreshAt(teamName); memberSpawnStatusesIpcBackoffUntilByTeam.delete(teamName); teamRefreshBurstDiagnostics.delete(teamName); memberSpawnUiEqualLastWarnAtByTeam.delete(teamName); @@ -656,7 +658,7 @@ export function __getTeamScopedTransientStateForTests(teamName: string): { hasPendingFreshMessagesHeadRefresh: pendingFreshTeamMessagesHeadRefreshes.has(teamName), hasPendingFreshMemberActivityMetaRefresh: pendingFreshTeamMemberActivityMetaRefreshes.has(teamName), - hasLastResolvedTeamDataRefresh: lastResolvedTeamDataRefreshAtByTeam.has(teamName), + hasLastResolvedTeamDataRefresh: hasLastResolvedTeamDataRefreshAt(teamName), hasCurrentLocalStateEpoch: hasTeamLocalStateEpoch(teamName), hasMemberSpawnStatusesIpcBackoff: memberSpawnStatusesIpcBackoffUntilByTeam.has(teamName), hasTeamRefreshBurstDiagnostics: teamRefreshBurstDiagnostics.has(teamName), @@ -4014,7 +4016,7 @@ export const createTeamSlice: StateCreator = (set, selectedTeamError: null, }; }); - lastResolvedTeamDataRefreshAtByTeam.set(teamName, Date.now()); + recordLastResolvedTeamDataRefresh(teamName); try { const invalidationState = previousData @@ -4238,7 +4240,7 @@ export const createTeamSlice: StateCreator = (set, ...selectedState, }; }); - lastResolvedTeamDataRefreshAtByTeam.set(teamName, Date.now()); + recordLastResolvedTeamDataRefresh(teamName); const invalidationState = previousData ? collectTaskChangeInvalidationState(teamName, previousData.tasks, data.tasks) : { cacheKeys: [], taskIds: [] }; diff --git a/src/renderer/store/team/teamDataRefreshTimestamps.ts b/src/renderer/store/team/teamDataRefreshTimestamps.ts new file mode 100644 index 00000000..9330d545 --- /dev/null +++ b/src/renderer/store/team/teamDataRefreshTimestamps.ts @@ -0,0 +1,21 @@ +const lastResolvedTeamDataRefreshAtByTeam = new Map(); + +export function getLastResolvedTeamDataRefreshAt(teamName: string): number | undefined { + return lastResolvedTeamDataRefreshAtByTeam.get(teamName); +} + +export function recordLastResolvedTeamDataRefresh(teamName: string, resolvedAt = Date.now()): void { + lastResolvedTeamDataRefreshAtByTeam.set(teamName, resolvedAt); +} + +export function hasLastResolvedTeamDataRefreshAt(teamName: string): boolean { + return lastResolvedTeamDataRefreshAtByTeam.has(teamName); +} + +export function clearLastResolvedTeamDataRefreshAt(teamName: string): void { + lastResolvedTeamDataRefreshAtByTeam.delete(teamName); +} + +export function clearAllLastResolvedTeamDataRefreshes(): void { + lastResolvedTeamDataRefreshAtByTeam.clear(); +} diff --git a/test/renderer/store/teamDataRefreshTimestamps.test.ts b/test/renderer/store/teamDataRefreshTimestamps.test.ts new file mode 100644 index 00000000..5c8bf9de --- /dev/null +++ b/test/renderer/store/teamDataRefreshTimestamps.test.ts @@ -0,0 +1,60 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { + clearAllLastResolvedTeamDataRefreshes, + clearLastResolvedTeamDataRefreshAt, + getLastResolvedTeamDataRefreshAt, + hasLastResolvedTeamDataRefreshAt, + recordLastResolvedTeamDataRefresh, +} from '../../../src/renderer/store/team/teamDataRefreshTimestamps'; + +afterEach(() => { + vi.useRealTimers(); + clearAllLastResolvedTeamDataRefreshes(); +}); + +describe('teamDataRefreshTimestamps', () => { + it('returns undefined for teams without a recorded refresh', () => { + expect(getLastResolvedTeamDataRefreshAt('my-team')).toBeUndefined(); + expect(hasLastResolvedTeamDataRefreshAt('my-team')).toBe(false); + }); + + it('records explicit refresh timestamps by team', () => { + recordLastResolvedTeamDataRefresh('my-team', 100); + recordLastResolvedTeamDataRefresh('other-team', 200); + + expect(getLastResolvedTeamDataRefreshAt('my-team')).toBe(100); + expect(getLastResolvedTeamDataRefreshAt('other-team')).toBe(200); + expect(hasLastResolvedTeamDataRefreshAt('my-team')).toBe(true); + }); + + it('uses Date.now by default to preserve current call-site behavior', () => { + vi.setSystemTime(new Date('2026-05-22T06:30:00.000Z')); + + recordLastResolvedTeamDataRefresh('my-team'); + + expect(getLastResolvedTeamDataRefreshAt('my-team')).toBe( + new Date('2026-05-22T06:30:00.000Z').getTime() + ); + }); + + it('clears one team timestamp without touching other teams', () => { + recordLastResolvedTeamDataRefresh('my-team', 100); + recordLastResolvedTeamDataRefresh('other-team', 200); + + clearLastResolvedTeamDataRefreshAt('my-team'); + + expect(getLastResolvedTeamDataRefreshAt('my-team')).toBeUndefined(); + expect(getLastResolvedTeamDataRefreshAt('other-team')).toBe(200); + }); + + it('clears all recorded timestamps', () => { + recordLastResolvedTeamDataRefresh('my-team', 100); + recordLastResolvedTeamDataRefresh('other-team', 200); + + clearAllLastResolvedTeamDataRefreshes(); + + expect(hasLastResolvedTeamDataRefreshAt('my-team')).toBe(false); + expect(hasLastResolvedTeamDataRefreshAt('other-team')).toBe(false); + }); +});