diff --git a/src/main/workers/team-fs-worker.ts b/src/main/workers/team-fs-worker.ts index 60362205..e88290de 100644 --- a/src/main/workers/team-fs-worker.ts +++ b/src/main/workers/team-fs-worker.ts @@ -352,6 +352,14 @@ function cloneCached(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 { 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) diff --git a/src/renderer/components/sidebar/GlobalTaskList.tsx b/src/renderer/components/sidebar/GlobalTaskList.tsx index c125fae9..7f5b95e6 100644 --- a/src/renderer/components/sidebar/GlobalTaskList.tsx +++ b/src/renderer/components/sidebar/GlobalTaskList.tsx @@ -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, diff --git a/test/renderer/components/sidebar/GlobalTaskList.test.ts b/test/renderer/components/sidebar/GlobalTaskList.test.ts index 01294f1d..a84e54d3 100644 --- a/test/renderer/components/sidebar/GlobalTaskList.test.ts +++ b/test/renderer/components/sidebar/GlobalTaskList.test.ts @@ -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(); });