perf(renderer): coalesce initial global task refreshes

This commit is contained in:
777genius 2026-05-31 11:38:23 +03:00
parent 47cea728b0
commit 174ad83b47
2 changed files with 40 additions and 1 deletions

View file

@ -247,6 +247,7 @@ const pendingFreshTeamMemberActivityMetaRefreshes = new Set<string>();
const pendingTeamPendingReplyRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
let latestTeamsFetchRequestId = 0;
let inFlightGlobalTasksRefresh: Promise<void> | null = null;
let inFlightGlobalTasksRefreshScope: ContextRequestScope | null = null;
let pendingFreshGlobalTasksRefresh = false;
interface RefreshTeamDataOptions {
withDedup?: boolean;
@ -1642,7 +1643,13 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
fetchAllTasks: async () => {
if (inFlightGlobalTasksRefresh) {
pendingFreshGlobalTasksRefresh = true;
const inFlightScope = inFlightGlobalTasksRefreshScope;
if (
get().globalTasksInitialized ||
(inFlightScope && !isContextRequestScopeCurrent(get, inFlightScope))
) {
pendingFreshGlobalTasksRefresh = true;
}
await inFlightGlobalTasksRefresh;
return;
}
@ -1658,6 +1665,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
set({ globalTasksLoading: true, globalTasksError: null });
}
const requestScope = captureContextRequestScope(get);
inFlightGlobalTasksRefreshScope = requestScope;
const oldTasks = get().globalTasks;
try {
const tasks = await withTimeout(
@ -1706,6 +1714,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
const request = runRefresh().finally(() => {
if (inFlightGlobalTasksRefresh === request) {
inFlightGlobalTasksRefresh = null;
inFlightGlobalTasksRefreshScope = null;
}
});
inFlightGlobalTasksRefresh = request;

View file

@ -326,6 +326,36 @@ describe('team slice context races', () => {
expect(store.getState().globalTasksLoading).toBe(false);
});
it('coalesces concurrent initial global task refreshes for the same context', async () => {
const store = createSliceStore();
const initialTasks = deferred<GlobalTaskLike[]>();
apiMock.teams.getAllTasks.mockReturnValueOnce(initialTasks.promise);
const firstFetch = store.getState().fetchAllTasks();
const secondFetch = store.getState().fetchAllTasks();
initialTasks.resolve([
{
id: 'initial-task',
subject: 'Initial task',
status: 'todo',
teamName: 'initial-team',
teamDisplayName: 'Initial Team',
projectPath: '/initial/project',
comments: [],
},
]);
await Promise.all([firstFetch, secondFetch]);
expect(apiMock.teams.getAllTasks).toHaveBeenCalledTimes(1);
expect(store.getState().globalTasks).toEqual([
expect.objectContaining({ id: 'initial-task', teamName: 'initial-team' }),
]);
expect(store.getState().globalTasksInitialized).toBe(true);
expect(store.getState().globalTasksLoading).toBe(false);
});
it('ignores global tasks loaded before a context epoch reset with the same context id', async () => {
const store = createSliceStore();
const localTasks = deferred<GlobalTaskLike[]>();