refactor(team): extract pending reply waits
This commit is contained in:
parent
69f7a21771
commit
4a561f2cd2
3 changed files with 120 additions and 42 deletions
|
|
@ -49,6 +49,11 @@ import {
|
|||
pruneOptimisticMessages,
|
||||
upsertOptimisticTeamMessage,
|
||||
} from '../team/teamMessagesCache';
|
||||
import {
|
||||
clearAllPendingReplyRefreshWaits,
|
||||
clearPendingReplyRefreshWaits,
|
||||
setPendingReplyRefreshEnabled,
|
||||
} from '../team/teamPendingReplyWaits';
|
||||
import { noteTeamRefreshFanout } from '../teamRefreshFanoutDiagnostics';
|
||||
import { getWorktreeNavigationState } from '../utils/stateResetHelpers';
|
||||
|
||||
|
|
@ -115,6 +120,10 @@ export type {
|
|||
TeamMessagesCacheEntry,
|
||||
} from '../team/teamMessagesCache';
|
||||
export { selectMemberMessagesForTeamMember, selectTeamMessages } from '../team/teamMessagesCache';
|
||||
export {
|
||||
getActiveTeamPendingReplyWaits,
|
||||
hasActiveTeamPendingReplyWait,
|
||||
} from '../team/teamPendingReplyWaits';
|
||||
|
||||
const GRAPH_STABLE_SLOT_LAYOUT_VERSION = 'stable-slots-v1' as const;
|
||||
const DISABLE_PERSISTED_TEAM_GRAPH_SLOT_ASSIGNMENTS = true;
|
||||
|
|
@ -148,7 +157,6 @@ const pendingFreshTeamMessagesHeadRefreshes = new Set<string>();
|
|||
const inFlightTeamMemberActivityMetaRequests = new Map<string, Promise<void>>();
|
||||
const pendingFreshTeamMemberActivityMetaRefreshes = new Set<string>();
|
||||
const pendingTeamPendingReplyRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
const activeTeamPendingReplyWaitSourceIdsByTeam = new Map<string, Set<string>>();
|
||||
const lastResolvedTeamDataRefreshAtByTeam = new Map<string, number>();
|
||||
const teamLocalStateEpochByTeam = new Map<string, number>();
|
||||
let inFlightGlobalTasksRefresh: Promise<void> | null = null;
|
||||
|
|
@ -203,18 +211,6 @@ export function getLastResolvedTeamDataRefreshAt(teamName: string): number | und
|
|||
return lastResolvedTeamDataRefreshAtByTeam.get(teamName);
|
||||
}
|
||||
|
||||
export function hasActiveTeamPendingReplyWait(teamName: string): boolean {
|
||||
return (activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName)?.size ?? 0) > 0;
|
||||
}
|
||||
|
||||
export function getActiveTeamPendingReplyWaits(): Set<string> {
|
||||
return new Set(
|
||||
Array.from(activeTeamPendingReplyWaitSourceIdsByTeam.entries())
|
||||
.filter(([, sourceIds]) => sourceIds.size > 0)
|
||||
.map(([teamName]) => teamName)
|
||||
);
|
||||
}
|
||||
|
||||
export function __resetTeamSliceModuleStateForTests(): void {
|
||||
inFlightTeamDataRequests.clear();
|
||||
inFlightRefreshTeamDataCalls.clear();
|
||||
|
|
@ -234,7 +230,7 @@ export function __resetTeamSliceModuleStateForTests(): void {
|
|||
clearTimeout(timer);
|
||||
}
|
||||
pendingTeamPendingReplyRefreshTimers.clear();
|
||||
activeTeamPendingReplyWaitSourceIdsByTeam.clear();
|
||||
clearAllPendingReplyRefreshWaits();
|
||||
lastResolvedTeamDataRefreshAtByTeam.clear();
|
||||
teamLocalStateEpochByTeam.clear();
|
||||
memberSpawnStatusesIpcBackoffUntilByTeam.clear();
|
||||
|
|
@ -1082,34 +1078,6 @@ function clearPendingReplyRefreshTimer(teamName: string): void {
|
|||
pendingTeamPendingReplyRefreshTimers.delete(teamName);
|
||||
}
|
||||
|
||||
function clearPendingReplyRefreshWaits(teamName: string): void {
|
||||
activeTeamPendingReplyWaitSourceIdsByTeam.delete(teamName);
|
||||
}
|
||||
|
||||
function setPendingReplyRefreshEnabled(
|
||||
teamName: string,
|
||||
sourceId: string,
|
||||
enabled: boolean
|
||||
): boolean {
|
||||
if (enabled) {
|
||||
const existing = activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName) ?? new Set<string>();
|
||||
existing.add(sourceId);
|
||||
activeTeamPendingReplyWaitSourceIdsByTeam.set(teamName, existing);
|
||||
return true;
|
||||
}
|
||||
|
||||
const existing = activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName);
|
||||
if (!existing) {
|
||||
return false;
|
||||
}
|
||||
existing.delete(sourceId);
|
||||
if (existing.size === 0) {
|
||||
activeTeamPendingReplyWaitSourceIdsByTeam.delete(teamName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function refreshTaskChangePresenceForUpdatedTask(
|
||||
getState: () => AppState,
|
||||
teamName: string,
|
||||
|
|
|
|||
45
src/renderer/store/team/teamPendingReplyWaits.ts
Normal file
45
src/renderer/store/team/teamPendingReplyWaits.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
const activeTeamPendingReplyWaitSourceIdsByTeam = new Map<string, Set<string>>();
|
||||
|
||||
export function hasActiveTeamPendingReplyWait(teamName: string): boolean {
|
||||
return (activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName)?.size ?? 0) > 0;
|
||||
}
|
||||
|
||||
export function getActiveTeamPendingReplyWaits(): Set<string> {
|
||||
return new Set(
|
||||
Array.from(activeTeamPendingReplyWaitSourceIdsByTeam.entries())
|
||||
.filter(([, sourceIds]) => sourceIds.size > 0)
|
||||
.map(([teamName]) => teamName)
|
||||
);
|
||||
}
|
||||
|
||||
export function clearAllPendingReplyRefreshWaits(): void {
|
||||
activeTeamPendingReplyWaitSourceIdsByTeam.clear();
|
||||
}
|
||||
|
||||
export function clearPendingReplyRefreshWaits(teamName: string): void {
|
||||
activeTeamPendingReplyWaitSourceIdsByTeam.delete(teamName);
|
||||
}
|
||||
|
||||
export function setPendingReplyRefreshEnabled(
|
||||
teamName: string,
|
||||
sourceId: string,
|
||||
enabled: boolean
|
||||
): boolean {
|
||||
if (enabled) {
|
||||
const existing = activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName) ?? new Set<string>();
|
||||
existing.add(sourceId);
|
||||
activeTeamPendingReplyWaitSourceIdsByTeam.set(teamName, existing);
|
||||
return true;
|
||||
}
|
||||
|
||||
const existing = activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName);
|
||||
if (!existing) {
|
||||
return false;
|
||||
}
|
||||
existing.delete(sourceId);
|
||||
if (existing.size === 0) {
|
||||
activeTeamPendingReplyWaitSourceIdsByTeam.delete(teamName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
65
test/renderer/store/teamPendingReplyWaits.test.ts
Normal file
65
test/renderer/store/teamPendingReplyWaits.test.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
clearAllPendingReplyRefreshWaits,
|
||||
clearPendingReplyRefreshWaits,
|
||||
getActiveTeamPendingReplyWaits,
|
||||
hasActiveTeamPendingReplyWait,
|
||||
setPendingReplyRefreshEnabled,
|
||||
} from '../../../src/renderer/store/team/teamPendingReplyWaits';
|
||||
|
||||
afterEach(() => {
|
||||
clearAllPendingReplyRefreshWaits();
|
||||
});
|
||||
|
||||
describe('teamPendingReplyWaits', () => {
|
||||
it('tracks active teams with at least one enabled source', () => {
|
||||
expect(setPendingReplyRefreshEnabled('my-team', 'tab-a', true)).toBe(true);
|
||||
expect(setPendingReplyRefreshEnabled('other-team', 'tab-b', true)).toBe(true);
|
||||
|
||||
expect(hasActiveTeamPendingReplyWait('my-team')).toBe(true);
|
||||
expect(hasActiveTeamPendingReplyWait('other-team')).toBe(true);
|
||||
expect(getActiveTeamPendingReplyWaits()).toEqual(new Set(['my-team', 'other-team']));
|
||||
});
|
||||
|
||||
it('keeps a team active until the last source is disabled', () => {
|
||||
setPendingReplyRefreshEnabled('my-team', 'tab-a', true);
|
||||
setPendingReplyRefreshEnabled('my-team', 'tab-b', true);
|
||||
|
||||
expect(setPendingReplyRefreshEnabled('my-team', 'tab-b', false)).toBe(true);
|
||||
expect(hasActiveTeamPendingReplyWait('my-team')).toBe(true);
|
||||
expect(getActiveTeamPendingReplyWaits()).toEqual(new Set(['my-team']));
|
||||
|
||||
expect(setPendingReplyRefreshEnabled('my-team', 'tab-a', false)).toBe(false);
|
||||
expect(hasActiveTeamPendingReplyWait('my-team')).toBe(false);
|
||||
expect(getActiveTeamPendingReplyWaits().size).toBe(0);
|
||||
});
|
||||
|
||||
it('is idempotent for repeated enables from the same source', () => {
|
||||
setPendingReplyRefreshEnabled('my-team', 'tab-a', true);
|
||||
setPendingReplyRefreshEnabled('my-team', 'tab-a', true);
|
||||
|
||||
expect(setPendingReplyRefreshEnabled('my-team', 'tab-a', false)).toBe(false);
|
||||
expect(hasActiveTeamPendingReplyWait('my-team')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when disabling a source that has no active wait', () => {
|
||||
expect(setPendingReplyRefreshEnabled('missing-team', 'tab-a', false)).toBe(false);
|
||||
expect(getActiveTeamPendingReplyWaits().size).toBe(0);
|
||||
});
|
||||
|
||||
it('clears waits by team or globally', () => {
|
||||
setPendingReplyRefreshEnabled('my-team', 'tab-a', true);
|
||||
setPendingReplyRefreshEnabled('other-team', 'tab-b', true);
|
||||
|
||||
clearPendingReplyRefreshWaits('my-team');
|
||||
|
||||
expect(hasActiveTeamPendingReplyWait('my-team')).toBe(false);
|
||||
expect(getActiveTeamPendingReplyWaits()).toEqual(new Set(['other-team']));
|
||||
|
||||
clearAllPendingReplyRefreshWaits();
|
||||
|
||||
expect(hasActiveTeamPendingReplyWait('other-team')).toBe(false);
|
||||
expect(getActiveTeamPendingReplyWaits().size).toBe(0);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue