refactor(team): extract pending reply waits

This commit is contained in:
777genius 2026-05-22 09:50:06 +03:00
parent 69f7a21771
commit 4a561f2cd2
3 changed files with 120 additions and 42 deletions

View file

@ -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,

View 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;
}

View 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);
});
});