From e9de8ecad129bb6d03c37f164c9c96fee5de8f10 Mon Sep 17 00:00:00 2001 From: Evan Chen Date: Thu, 6 Aug 2026 09:25:35 -0700 Subject: [PATCH] fix(desktop): paginate repository entries Signed-off-by: Evan Chen Co-authored-by: Codex Ai-assisted: true --- .../projects/lib/projectsViewHelpers.test.mjs | 11 +++- .../projects/lib/projectsViewHelpers.ts | 6 +++ .../projects/ui/ProjectRepositoryPanel.tsx | 52 ++++++++++++++----- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/desktop/src/features/projects/lib/projectsViewHelpers.test.mjs b/desktop/src/features/projects/lib/projectsViewHelpers.test.mjs index cd58f06a5..085feb3e3 100644 --- a/desktop/src/features/projects/lib/projectsViewHelpers.test.mjs +++ b/desktop/src/features/projects/lib/projectsViewHelpers.test.mjs @@ -1,7 +1,10 @@ import assert from "node:assert/strict"; import { test } from "node:test"; -import { relativeTime } from "./projectsViewHelpers.ts"; +import { + nextRepositoryEntryLimit, + relativeTime, +} from "./projectsViewHelpers.ts"; const DAY_SECONDS = 24 * 60 * 60; @@ -43,3 +46,9 @@ test("relativeTime includes the year only across a year boundary", () => { crossYearExpected, ); }); + +test("repository entry pagination advances and clamps to the total", () => { + assert.equal(nextRepositoryEntryLimit(200, 450), 400); + assert.equal(nextRepositoryEntryLimit(400, 450), 450); + assert.equal(nextRepositoryEntryLimit(450, 450), 450); +}); diff --git a/desktop/src/features/projects/lib/projectsViewHelpers.ts b/desktop/src/features/projects/lib/projectsViewHelpers.ts index 6084c7275..e1afcacf9 100644 --- a/desktop/src/features/projects/lib/projectsViewHelpers.ts +++ b/desktop/src/features/projects/lib/projectsViewHelpers.ts @@ -26,6 +26,12 @@ export type ProjectsFilter = | "users"; export type ProjectsSort = "updated" | "created" | "name"; +export const REPOSITORY_ENTRY_PAGE_SIZE = 200; + +export function nextRepositoryEntryLimit(current: number, total: number) { + return Math.min(current + REPOSITORY_ENTRY_PAGE_SIZE, total); +} + const PROJECTS_VIEW_MODE_STORAGE_KEY = "buzz.projects.viewMode"; const PROJECTS_FILTER_STORAGE_KEY = "buzz.projects.filter"; const PROJECTS_REPOSITORY_SCOPE_STORAGE_KEY = "buzz.projects.repositoryScope"; diff --git a/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx b/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx index c4becd905..8c69613d8 100644 --- a/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx +++ b/desktop/src/features/projects/ui/ProjectRepositoryPanel.tsx @@ -26,7 +26,11 @@ import type { ProjectRepoFile, ProjectRepoSnapshot, } from "@/features/projects/hooks"; -import { relativeTime } from "@/features/projects/lib/projectsViewHelpers"; +import { + nextRepositoryEntryLimit, + relativeTime, + REPOSITORY_ENTRY_PAGE_SIZE, +} from "@/features/projects/lib/projectsViewHelpers"; import { useUserSearchQuery } from "@/features/profile/hooks"; import type { UserProfileLookup } from "@/features/profile/lib/identity"; import type { UserSearchResult } from "@/shared/api/types"; @@ -633,11 +637,18 @@ export function RepositoryFilesPanel({ const [currentPath, setCurrentPath] = React.useState(""); const [selectedFile, setSelectedFile] = React.useState(null); + const [visibleEntryCount, setVisibleEntryCount] = React.useState( + REPOSITORY_ENTRY_PAGE_SIZE, + ); + const openPath = React.useCallback((path: string) => { + setCurrentPath(path); + setVisibleEntryCount(REPOSITORY_ENTRY_PAGE_SIZE); + }, []); const entries = React.useMemo( () => repositoryEntries(files, currentPath), [currentPath, files], ); - const visibleEntries = entries.slice(0, 200); + const visibleEntries = entries.slice(0, visibleEntryCount); const latestCommit = snapshot?.latestCommit ?? null; const knownLatestCommitProfile = React.useMemo( () => profileForCommitAuthor(latestCommit, profiles), @@ -679,6 +690,7 @@ export function RepositoryFilesPanel({ if (!filesKey) return; setCurrentPath(""); setSelectedFile(null); + setVisibleEntryCount(REPOSITORY_ENTRY_PAGE_SIZE); }, [filesKey]); // Loading/error/empty states keep the header controls visible — the @@ -737,7 +749,7 @@ export function RepositoryFilesPanel({ file={selectedFile} onOpenPath={(path) => { setSelectedFile(null); - setCurrentPath(path); + openPath(path); }} /> ); @@ -766,14 +778,14 @@ export function RepositoryFilesPanel({ /> ) : ( - setCurrentPath("")}> + openPath("")}> Files )} {sourceControls && pathSegments.length > 0 ? ( <> - setCurrentPath("")}> + openPath("")}> Files @@ -783,7 +795,7 @@ export function RepositoryFilesPanel({ return ( - setCurrentPath(nextPath)}> + openPath(nextPath)}> {segment} @@ -855,7 +867,7 @@ export function RepositoryFilesPanel({ const latestCommit = entry.latestCommit; const rowIsLast = index === visibleEntries.length - 1; const openEntry = () => - openRepositoryEntry(entry, setCurrentPath, setSelectedFile); + openRepositoryEntry(entry, openPath, setSelectedFile); return ( - {entries.length > 200 ? ( -

- Showing the first 200 entries in this folder. Open a folder to narrow - the list. -

+ {entries.length > visibleEntries.length ? ( +
+ + Showing {visibleEntries.length} of {entries.length} entries. + + +
) : null} );