diff --git a/apps/desktop/src/lib/trpc/routers/changes/status.ts b/apps/desktop/src/lib/trpc/routers/changes/status.ts index a059c764684..748201236c1 100644 --- a/apps/desktop/src/lib/trpc/routers/changes/status.ts +++ b/apps/desktop/src/lib/trpc/routers/changes/status.ts @@ -11,6 +11,13 @@ import { parseNameStatus, } from "./utils/parse-status"; +/** Server-side cache to avoid re-running git commands when polled frequently */ +const STATUS_CACHE_TTL_MS = 2_000; +const statusCache = new Map< + string, + { result: GitChangesStatus; timestamp: number } +>(); + export const createStatusRouter = () => { return router({ getStatus: publicProcedure @@ -23,8 +30,14 @@ export const createStatusRouter = () => { .query(async ({ input }): Promise => { assertRegisteredWorktree(input.worktreePath); - const git = simpleGit(input.worktreePath); const defaultBranch = input.defaultBranch || "main"; + const cacheKey = `${input.worktreePath}:${defaultBranch}`; + const cached = statusCache.get(cacheKey); + if (cached && Date.now() - cached.timestamp < STATUS_CACHE_TTL_MS) { + return cached.result; + } + + const git = simpleGit(input.worktreePath); const status = await getStatusNoLock(input.worktreePath); const parsed = parseGitStatus(status); @@ -41,7 +54,7 @@ export const createStatusRouter = () => { applyUntrackedLineCount(input.worktreePath, parsed.untracked), ]); - return { + const result: GitChangesStatus = { branch: parsed.branch, defaultBranch, againstBase: branchComparison.againstBase, @@ -55,6 +68,9 @@ export const createStatusRouter = () => { pullCount: trackingStatus.pullCount, hasUpstream: trackingStatus.hasUpstream, }; + + statusCache.set(cacheKey, { result, timestamp: Date.now() }); + return result; }), getCommitFiles: publicProcedure diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InfiniteScrollView/components/CommitSection/CommitSection.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InfiniteScrollView/components/CommitSection/CommitSection.tsx index 884b9e7fb07..584a14cad7d 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InfiniteScrollView/components/CommitSection/CommitSection.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InfiniteScrollView/components/CommitSection/CommitSection.tsx @@ -19,7 +19,7 @@ export function CommitSection({ onToggleFile, scrollElementRef, }: CommitSectionProps) { - const [isCommitExpanded, setIsCommitExpanded] = useState(true); + const [isCommitExpanded, setIsCommitExpanded] = useState(false); const { data: commitFiles } = electronTrpc.changes.getCommitFiles.useQuery( { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/VirtualizedFileList/VirtualizedFileList.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/VirtualizedFileList/VirtualizedFileList.tsx index 1c949f39510..5c1b495b7bb 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/VirtualizedFileList/VirtualizedFileList.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/VirtualizedFileList/VirtualizedFileList.tsx @@ -1,5 +1,5 @@ -import { useVirtualizer } from "@tanstack/react-virtual"; -import { type RefObject, useCallback, useRef } from "react"; +import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual"; +import { type RefObject, useRef } from "react"; import type { ChangeCategory, ChangedFile } from "shared/changes-types"; import { FileDiffSection } from "../FileDiffSection"; @@ -36,35 +36,13 @@ export function VirtualizedFileList({ isActioning = false, }: VirtualizedFileListProps) { const listRef = useRef(null); - const renderedIndicesRef = useRef>(new Set()); - - const rangeExtractor = useCallback( - (range: { startIndex: number; endIndex: number }) => { - const indices = new Set(renderedIndicesRef.current); - - const start = Math.max(0, range.startIndex - OVERSCAN); - const end = Math.min(files.length - 1, range.endIndex + OVERSCAN); - for (let i = start; i <= end; i++) { - indices.add(i); - } - - for (const idx of indices) { - if (idx >= files.length) { - indices.delete(idx); - } - } - - renderedIndicesRef.current = indices; - return Array.from(indices).sort((a, b) => a - b); - }, - [files.length], - ); const virtualizer = useVirtualizer({ count: files.length, getScrollElement: () => scrollElementRef.current, estimateSize: () => ESTIMATED_COLLAPSED_HEIGHT, - rangeExtractor, + rangeExtractor: defaultRangeExtractor, + overscan: OVERSCAN, scrollMargin: listRef.current?.offsetTop ?? 0, });