perf(renderer): defer task project scans

This commit is contained in:
777genius 2026-05-30 22:33:17 +03:00
parent 1f46a14a79
commit 7ea57cb012
3 changed files with 57 additions and 9 deletions

View file

@ -352,6 +352,14 @@ function cloneCached<T>(value: T): T {
: (JSON.parse(JSON.stringify(value)) as T); : (JSON.parse(JSON.stringify(value)) as T);
} }
function dateFromFingerprintMs(ms: unknown): Date | null {
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms <= 0) {
return null;
}
const date = new Date(ms);
return Number.isFinite(date.getTime()) ? date : null;
}
async function statPathFingerprint(filePath: string): Promise<PathFingerprint> { async function statPathFingerprint(filePath: string): Promise<PathFingerprint> {
try { try {
const stat = await fs.promises.stat(filePath, { bigint: true }); const stat = await fs.promises.stat(filePath, { bigint: true });
@ -1460,7 +1468,6 @@ async function readTasksDirForTeam(
} }
taskDiag.cacheMisses++; taskDiag.cacheMisses++;
const stat = await fs.promises.stat(taskPath);
const raw = await readFileUtf8WithTimeout(taskPath, payload.maxTaskReadMs); const raw = await readFileUtf8WithTimeout(taskPath, payload.maxTaskReadMs);
const parsed = JSON.parse(raw) as ParsedTask; const parsed = JSON.parse(raw) as ParsedTask;
const metadata = parsed.metadata; const metadata = parsed.metadata;
@ -1504,11 +1511,12 @@ async function readTasksDirForTeam(
typeof parsed.createdAt === 'string' ? parsed.createdAt : undefined; typeof parsed.createdAt === 'string' ? parsed.createdAt : undefined;
let updatedAt: string | undefined; let updatedAt: string | undefined;
try { try {
const birthtime = dateFromFingerprintMs(pathFingerprint.birthtimeMs);
const mtime = dateFromFingerprintMs(pathFingerprint.mtimeMs);
if (!createdAt) { if (!createdAt) {
const bt = stat.birthtime.getTime(); createdAt = (birthtime ?? mtime)?.toISOString();
createdAt = (bt > 0 ? stat.birthtime : stat.mtime).toISOString();
} }
updatedAt = stat.mtime.toISOString(); updatedAt = mtime?.toISOString();
} catch { } catch {
/* ignore */ /* ignore */
} }
@ -1559,7 +1567,7 @@ async function readTasksDirForTeam(
status, status,
workIntervals: normalizeWorkIntervals(parsed), workIntervals: normalizeWorkIntervals(parsed),
reviewIntervals: normalizeReviewIntervals(parsed), reviewIntervals: normalizeReviewIntervals(parsed),
historyEvents: normalizeHistoryEvents(parsed), historyEvents,
blocks: Array.isArray(parsed.blocks) ? (parsed.blocks as unknown[]) : undefined, blocks: Array.isArray(parsed.blocks) ? (parsed.blocks as unknown[]) : undefined,
blockedBy: Array.isArray(parsed.blockedBy) ? (parsed.blockedBy as unknown[]) : undefined, blockedBy: Array.isArray(parsed.blockedBy) ? (parsed.blockedBy as unknown[]) : undefined,
related: Array.isArray(parsed.related) related: Array.isArray(parsed.related)

View file

@ -462,6 +462,9 @@ export const GlobalTaskList = memo(function GlobalTaskList({
}, [fetchAllTasks, globalTasksLoading]); }, [fetchAllTasks, globalTasksLoading]);
useEffect(() => { useEffect(() => {
if (!filtersPopoverOpen) {
return;
}
if ( if (
viewMode === 'grouped' && viewMode === 'grouped' &&
!repositoryGroupsInitialized && !repositoryGroupsInitialized &&
@ -475,6 +478,7 @@ export const GlobalTaskList = memo(function GlobalTaskList({
}, [ }, [
fetchProjects, fetchProjects,
fetchRepositoryGroups, fetchRepositoryGroups,
filtersPopoverOpen,
projectsError, projectsError,
projectsInitialized, projectsInitialized,
projectsLoading, projectsLoading,

View file

@ -248,7 +248,7 @@ describe('GlobalTaskList project grouping', () => {
storeListeners.clear(); storeListeners.clear();
}); });
it('fetches repository groups when grouped project filter data is missing', async () => { it('fetches repository groups when grouped project filter data is needed', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
storeState.viewMode = 'grouped'; storeState.viewMode = 'grouped';
@ -261,6 +261,19 @@ describe('GlobalTaskList project grouping', () => {
await flushMicrotasks(); await flushMicrotasks();
}); });
expect(storeState.fetchRepositoryGroups).not.toHaveBeenCalled();
expect(storeState.fetchProjects).not.toHaveBeenCalled();
await act(async () => {
root.render(
React.createElement(GlobalTaskList, {
filtersPopoverOpen: true,
onFiltersPopoverOpenChange: vi.fn(),
})
);
await flushMicrotasks();
});
expect(storeState.fetchRepositoryGroups).toHaveBeenCalledTimes(1); expect(storeState.fetchRepositoryGroups).toHaveBeenCalledTimes(1);
expect(storeState.fetchProjects).not.toHaveBeenCalled(); expect(storeState.fetchProjects).not.toHaveBeenCalled();
@ -270,7 +283,7 @@ describe('GlobalTaskList project grouping', () => {
}); });
}); });
it('fetches flat projects when flat project filter data is missing', async () => { it('fetches flat projects when flat project filter data is needed', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
const host = document.createElement('div'); const host = document.createElement('div');
@ -282,6 +295,19 @@ describe('GlobalTaskList project grouping', () => {
await flushMicrotasks(); await flushMicrotasks();
}); });
expect(storeState.fetchProjects).not.toHaveBeenCalled();
expect(storeState.fetchRepositoryGroups).not.toHaveBeenCalled();
await act(async () => {
root.render(
React.createElement(GlobalTaskList, {
filtersPopoverOpen: true,
onFiltersPopoverOpenChange: vi.fn(),
})
);
await flushMicrotasks();
});
expect(storeState.fetchProjects).toHaveBeenCalledTimes(1); expect(storeState.fetchProjects).toHaveBeenCalledTimes(1);
expect(storeState.fetchRepositoryGroups).not.toHaveBeenCalled(); expect(storeState.fetchRepositoryGroups).not.toHaveBeenCalled();
@ -301,7 +327,12 @@ describe('GlobalTaskList project grouping', () => {
const root = createRoot(host); const root = createRoot(host);
await act(async () => { await act(async () => {
root.render(React.createElement(GlobalTaskList)); root.render(
React.createElement(GlobalTaskList, {
filtersPopoverOpen: true,
onFiltersPopoverOpenChange: vi.fn(),
})
);
await flushMicrotasks(); await flushMicrotasks();
}); });
@ -323,7 +354,12 @@ describe('GlobalTaskList project grouping', () => {
const root = createRoot(host); const root = createRoot(host);
await act(async () => { await act(async () => {
root.render(React.createElement(GlobalTaskList)); root.render(
React.createElement(GlobalTaskList, {
filtersPopoverOpen: true,
onFiltersPopoverOpenChange: vi.fn(),
})
);
await flushMicrotasks(); await flushMicrotasks();
}); });