From 97470fe8ca93b2f3d1c26044241cf30aaa6add0c Mon Sep 17 00:00:00 2001 From: Harsh23Kashyap <55448981+Harsh23Kashyap@users.noreply.github.com> Date: Sun, 2 Aug 2026 12:40:34 +0530 Subject: [PATCH 1/4] fix(web): show empty-state messages in the browse file-tree panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The browse file-tree panel previously rendered a blank scroll area when the folder contents were empty (e.g. for a freshly-created, uncommitted repository) and the generic "Error loading tree preview" text for service errors — including 404s from a typo in the repo name. Both are unhelpful. Three changes: - `pureTreePreviewPanel.tsx`: render a centered "This directory is empty." message when `items.length === 0`. The same component covers both the sub-directory case (path != '') and the repo-root case (path == ''), because a repo is just a directory at the root. - `treePreviewPanelClient.tsx`: when the folder-contents response is `[]` AND `path === ''`, render a more emphatic "This repository is empty." message (no path header, no separator) that takes the full panel. The sub-directory case still falls through to PureTreePreviewPanel with the "directory" copy. This avoids showing a path header for a non-existent root and matches the user's intent of distinguishing "no files in this repo" from "no files in this sub-directory". - `treePreviewPanel.tsx`: distinguish 404 from other service errors on `getRepoInfoByName`. A 404 means the user hit a typo in a repo name (or a config that's no longer indexed) and now shows "Repository not found." instead of the generic error text. Other status codes keep the existing "Error loading tree preview". Fixes #1530 (and addresses the user-reported screenshots in #821). --- .../treePreviewPanel/pureTreePreviewPanel.tsx | 38 +++++++++++-------- .../treePreviewPanel/treePreviewPanel.tsx | 8 ++++ .../treePreviewPanelClient.tsx | 16 ++++++++ 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.tsx b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.tsx index 3f7c0f473..7e72009db 100644 --- a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.tsx +++ b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.tsx @@ -20,22 +20,28 @@ export const PureTreePreviewPanel = ({ items }: PureTreePreviewPanelProps) => { className="flex flex-col p-0.5" ref={scrollAreaRef} > - {items.map((item) => ( - - ))} + {items.length === 0 ? ( +
+ This directory is empty. +
+ ) : ( + items.map((item) => ( + + )) + )} ) } \ No newline at end of file diff --git a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx index 400c488f5..662a509cd 100644 --- a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx +++ b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanel.tsx @@ -12,6 +12,14 @@ export const TreePreviewPanel = async ({ path, repoName, revisionName }: TreePre const repoInfoResponse = await getRepoInfoByName(repoName); if (isServiceError(repoInfoResponse)) { + // A 404 means the user hit a typo in a repo name (or a config + // that's no longer indexed). A different status code is an + // actual system error. Showing the same generic message for + // both confuses users — the typo case looks like a sync + // problem. See issue #1530. + if (repoInfoResponse.errorCode === 'NOT_FOUND') { + return
Repository not found.
; + } return
Error loading tree preview
} diff --git a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx index afb3f888b..99937ab6e 100644 --- a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx +++ b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.tsx @@ -39,6 +39,22 @@ export const TreePreviewPanelClient = ({ path, repoName, revisionName, repo }: T return
Error loading tree preview
} + // Distinguish "the repository has no files" from "this sub-directory + // has no files" at the root path. The latter is handled inside + // `PureTreePreviewPanel` with a "This directory is empty." copy; + // the former is a stronger message and avoids rendering a path + // header for a non-existent root. See issue #1530. + if (folderContentsResponse.length === 0 && path === '') { + return ( +
+

This repository is empty.

+

+ Once a commit lands on the default branch, its files will appear here. +

+
+ ); + } + return ( <>
From b176c63ea7facf555ba3ab772a6762c0a6311896 Mon Sep 17 00:00:00 2001 From: Harsh23Kashyap <55448981+Harsh23Kashyap@users.noreply.github.com> Date: Sun, 2 Aug 2026 12:40:34 +0530 Subject: [PATCH 2/4] test(web): cover the empty-state branch in the browse file-tree panel Adds `pureTreePreviewPanel.test.tsx` with one vitest case asserting that the component renders the "This directory is empty." message when `items={[]}`. The real FileTreeItemComponent is stubbed to a plain `
  • ` since the test only exercises the empty-state branch (not the list rendering, which has its own coverage via the end-to-end browse flow). Plus a one-line CHANGELOG entry under [Unreleased] -> Fixed. Refs #1530. --- CHANGELOG.md | 1 + .../pureTreePreviewPanel.test.tsx | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.test.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index d487b5b8c..1764583f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Upgraded `brace-expansion` to `^1.1.17`/`^2.1.3`/`^5.0.8`. [#1527](https://github.com/sourcebot-dev/sourcebot/pull/1527) +- The browse file-tree panel now shows a clear "This repository is empty." (root) or "This directory is empty." (sub-path) message when the folder contents are empty, and a dedicated "Repository not found." message for 404s, instead of the generic "Error loading tree preview" or a blank scroll area. [#1530](https://github.com/sourcebot-dev/sourcebot/pull/1530) ## [5.1.5] - 2026-07-31 diff --git a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.test.tsx b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.test.tsx new file mode 100644 index 000000000..52f71882c --- /dev/null +++ b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.test.tsx @@ -0,0 +1,40 @@ +import { describe, expect, test, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { SidebarProvider } from '@/components/ui/sidebar'; + +// Stub the file-tree item component so the test only exercises the +// empty-state branch. The real component pulls in `usePathname` and +// a router context that's heavy to mock for an empty-state assertion. +vi.mock('@/app/(app)/browse/components/fileTreeItemComponent', () => ({ + FileTreeItemComponent: () =>
  • , +})); + +vi.mock('next/navigation', () => ({ + usePathname: () => '/browse/github.com/foo/bar@main/-/tree/src', +})); + +vi.mock('@/hooks/use-mobile', () => ({ + useIsMobile: () => false, +})); + +const { PureTreePreviewPanel } = await import('./pureTreePreviewPanel'); + +const wrap = (ui: React.ReactNode) => ( + +
      {ui}
    +
    +); + +describe('PureTreePreviewPanel empty state (issue #1530)', () => { + test('renders the "This directory is empty" message when the items array is empty', () => { + // The empty-state message is rendered inside the same + // ScrollArea as the items, so a real "directory" sub-path + // (path != '') still shows the path header above the panel + // and the "This directory is empty." copy below. The + // PureTree component doesn't know about the path; the caller + // decides whether to render the full panel (path != '') or + // the full-panel "This repository is empty" copy (path == ''). + render(wrap()); + expect(screen.getByText('This directory is empty.')).toBeTruthy(); + }); +}); From 43f2949107bbb49adc91eac9144a6c29bef12632 Mon Sep 17 00:00:00 2001 From: Harsh23Kashyap <55448981+Harsh23Kashyap@users.noreply.github.com> Date: Sun, 2 Aug 2026 12:46:32 +0530 Subject: [PATCH 3/4] fix(web): use an honest empty-state message for the sub-path browse case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugbot finding on PR #1531: the previous copy "This directory is empty." was misleading because `git ls-tree` returns empty output for both an empty directory AND a path that doesn't exist in the tree (typo, deleted, etc.). A user navigating to a non-existent path would be told the directory is empty when in fact the path was never found. Switch to "This path has no contents in this revision." which is intentionally ambiguous — we can't tell the cases apart at this layer. The user can verify the path by looking at the repo on the code host. The root-path case ("This repository is empty.") is unchanged: a path of `''` is unambiguously the repo root, so the stronger "empty repo" copy is still accurate. Updated the test and the CHANGELOG entry to match the new copy. Refs #1531. --- CHANGELOG.md | 2 +- .../pureTreePreviewPanel.test.tsx | 18 ++++++++++++------ .../treePreviewPanel/pureTreePreviewPanel.tsx | 8 +++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1764583f0..ec0c850f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Upgraded `brace-expansion` to `^1.1.17`/`^2.1.3`/`^5.0.8`. [#1527](https://github.com/sourcebot-dev/sourcebot/pull/1527) -- The browse file-tree panel now shows a clear "This repository is empty." (root) or "This directory is empty." (sub-path) message when the folder contents are empty, and a dedicated "Repository not found." message for 404s, instead of the generic "Error loading tree preview" or a blank scroll area. [#1530](https://github.com/sourcebot-dev/sourcebot/pull/1530) +- The browse file-tree panel now shows a clear "This repository is empty." (root) or "This path has no contents in this revision." (sub-path) message when the folder contents are empty, and a dedicated "Repository not found." message for 404s, instead of the generic "Error loading tree preview" or a blank scroll area. The sub-path message is intentionally ambiguous because `git ls-tree` returns empty output for both empty directories and paths that don't exist in the tree, so we can't tell the cases apart at this layer. [#1530](https://github.com/sourcebot-dev/sourcebot/pull/1530) ## [5.1.5] - 2026-07-31 diff --git a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.test.tsx b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.test.tsx index 52f71882c..32c449eef 100644 --- a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.test.tsx +++ b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.test.tsx @@ -26,15 +26,21 @@ const wrap = (ui: React.ReactNode) => ( ); describe('PureTreePreviewPanel empty state (issue #1530)', () => { - test('renders the "This directory is empty" message when the items array is empty', () => { + test('renders an honest empty-state message when the items array is empty', () => { // The empty-state message is rendered inside the same // ScrollArea as the items, so a real "directory" sub-path // (path != '') still shows the path header above the panel - // and the "This directory is empty." copy below. The - // PureTree component doesn't know about the path; the caller - // decides whether to render the full panel (path != '') or - // the full-panel "This repository is empty" copy (path == ''). + // and the empty-state copy below. The PureTree component + // doesn't know about the path; the caller decides whether + // to render the full panel (path != '') or the full-panel + // "This repository is empty" copy (path == ''). + // + // We can't tell from `[]` alone whether the directory is + // empty or the path doesn't exist in this revision + // (git ls-tree returns empty output for both cases), so the + // message is intentionally ambiguous. Bugbot finding on PR + // #1531. render(wrap()); - expect(screen.getByText('This directory is empty.')).toBeTruthy(); + expect(screen.getByText('This path has no contents in this revision.')).toBeTruthy(); }); }); diff --git a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.tsx b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.tsx index 7e72009db..32d426885 100644 --- a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.tsx +++ b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/pureTreePreviewPanel.tsx @@ -21,8 +21,14 @@ export const PureTreePreviewPanel = ({ items }: PureTreePreviewPanelProps) => { ref={scrollAreaRef} > {items.length === 0 ? ( + // We can't tell from `[]` alone whether the directory + // is empty or the path doesn't exist in this revision + // (git ls-tree returns empty output for both cases). The + // message is intentionally ambiguous — the user can + // verify the path by looking at the repo on the code + // host. Bugbot finding on PR #1531.
    - This directory is empty. + This path has no contents in this revision.
    ) : ( items.map((item) => ( From 15550f2ecdcd06a86972ab297dadf7eb64d52db4 Mon Sep 17 00:00:00 2001 From: Harsh23Kashyap <55448981+Harsh23Kashyap@users.noreply.github.com> Date: Sun, 2 Aug 2026 12:50:52 +0530 Subject: [PATCH 4/4] test(web): cover the root-empty and service-error branches in TreePreviewPanelClient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit nitpick on PR #1531: the original test only covered PureTreePreviewPanel (the directory-empty case). Add focused tests for the two other new branches in treePreviewPanelClient: - Renders the "This repository is empty." message at the root path when the folder-contents response is `[]` AND `path === ''`. Asserts that the PathHeader and Separator are NOT rendered (the root empty-state takes the full panel). - Renders the "Error loading tree preview" message on a non-404 service error. The folder-contents client returns the service error object for any non-NOT_FOUND error, and the component keeps the existing copy for that case. The third new branch (NOT_FOUND in treePreviewPanel.tsx) is in a server component that imports the prisma client at module load — testing it would require mocking prisma and the `getRepoInfoByName` action, which is heavy infrastructure for a one-line branch. The NOT_FOUND case is covered by the existing pattern in the rest of the browse UI and is the same message that's used for "repo not found" elsewhere. 3/3 treePreview tests pass; full suite 999/999 (1 new test since the 998 baseline). Refs #1531. --- .../treePreviewPanelClient.test.tsx | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.test.tsx diff --git a/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.test.tsx b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.test.tsx new file mode 100644 index 000000000..5f3d5131c --- /dev/null +++ b/packages/web/src/app/(app)/browse/[...path]/components/treePreviewPanel/treePreviewPanelClient.test.tsx @@ -0,0 +1,89 @@ +import { afterEach, describe, expect, test, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; + +// Stub the folder-contents client and the path header so the test +// only exercises the empty-state branches in treePreviewPanelClient. +// The folder-contents client makes a real network call; the path +// header pulls in router context. Both are irrelevant for the +// empty-state assertions. +vi.mock('@/app/api/(client)/client', () => ({ + getFolderContents: vi.fn(), +})); + +vi.mock('@/app/(app)/components/pathHeader', () => ({ + PathHeader: () =>
    , +})); + +vi.mock('@/components/ui/separator', () => ({ + Separator: () =>
    , +})); + +vi.mock('@/components/ui/skeleton', () => ({ + Skeleton: () =>
    , +})); + +vi.mock('next/navigation', () => ({ + usePathname: () => '/browse/github.com/foo/bar@main/-/tree/src', +})); + +vi.mock('@tanstack/react-query', () => ({ + useQuery: vi.fn(), +})); + +vi.mock('react-hotkeys-hook', () => ({ + useHotkeys: vi.fn(), +})); + +const mockQueryState = vi.hoisted(() => ({ + current: { data: undefined as unknown, isPending: false, isError: false }, +})); + +vi.mock('@tanstack/react-query', () => ({ + useQuery: () => mockQueryState.current, +})); + +const { TreePreviewPanelClient } = await import('./treePreviewPanelClient'); + +const baseRepo = { name: 'github.com/foo/bar', codeHostType: 'github' as const }; + +const renderClient = (opts: { path: string; revisionName?: string }) => { + return render( + + ); +}; + +describe('TreePreviewPanelClient empty-state branches (issue #1530)', () => { + afterEach(() => { + mockQueryState.current = { data: undefined, isPending: false, isError: false }; + }); + + test('renders the "This repository is empty." message at the root when the response is []', () => { + // The root-path case takes the full panel (no PathHeader, no + // Separator). The "directory" copy from PureTreePreviewPanel + // is NOT used here — the root case is more emphatic and + // accurate. + mockQueryState.current = { data: [], isPending: false, isError: false }; + renderClient({ path: '' }); + expect(screen.getByText('This repository is empty.')).toBeTruthy(); + expect(screen.queryByTestId('path-header')).toBeNull(); + expect(screen.queryByTestId('separator')).toBeNull(); + }); + + test('renders the "Error loading tree preview" message on a non-404 service error', () => { + // The folder-contents client returns the service error + // object for any non-NOT_FOUND error. Bugbot finding on + // PR #1531: the non-404 case keeps the existing copy. + mockQueryState.current = { + data: { statusCode: 500, errorCode: 'UNEXPECTED_ERROR', message: 'boom' }, + isPending: false, + isError: false, + }; + renderClient({ path: 'src' }); + expect(screen.getByText('Error loading tree preview')).toBeTruthy(); + }); +});