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 ? (
+
Repository not found.
;
+ }
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) => (
+
+
+
+);
+
+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();
+ });
+});