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

View file

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

View file

@ -248,7 +248,7 @@ describe('GlobalTaskList project grouping', () => {
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);
storeState.viewMode = 'grouped';
@ -261,6 +261,19 @@ describe('GlobalTaskList project grouping', () => {
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.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);
const host = document.createElement('div');
@ -282,6 +295,19 @@ describe('GlobalTaskList project grouping', () => {
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.fetchRepositoryGroups).not.toHaveBeenCalled();
@ -301,7 +327,12 @@ describe('GlobalTaskList project grouping', () => {
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(GlobalTaskList));
root.render(
React.createElement(GlobalTaskList, {
filtersPopoverOpen: true,
onFiltersPopoverOpenChange: vi.fn(),
})
);
await flushMicrotasks();
});
@ -323,7 +354,12 @@ describe('GlobalTaskList project grouping', () => {
const root = createRoot(host);
await act(async () => {
root.render(React.createElement(GlobalTaskList));
root.render(
React.createElement(GlobalTaskList, {
filtersPopoverOpen: true,
onFiltersPopoverOpenChange: vi.fn(),
})
);
await flushMicrotasks();
});