Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/repository-file-browser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"helmor": minor
---

Add repository file browser to Git panel with tab-based navigation. Users can now browse the full workspace file tree alongside Git changes, reducing context switching during development.

- Tab-based UI with "Changes" and "Files" tabs in Git panel header
- Files tab displays complete repository tree (respects .gitignore)
- Shared tree/list view toggle between both tabs
- Tab preference persisted to localStorage
- Files open in read-only mode for clean browsing experience
8 changes: 4 additions & 4 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe("App", () => {
screen.getByLabelText("Inspector section Actions"),
).toBeInTheDocument();
expect(screen.getByLabelText("Inspector section Tabs")).toBeInTheDocument();
expect(screen.getByLabelText("Changes panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Git panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Actions panel body")).toBeInTheDocument();
// Inspector tabs section starts collapsed; body only mounts when opened.
expect(
Expand Down Expand Up @@ -125,7 +125,7 @@ describe("App", () => {
await screen.findByRole("main", { name: "Application shell" });

// Default: tabs section collapsed; changes + actions bodies present.
expect(screen.getByLabelText("Changes panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Git panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Actions panel body")).toBeInTheDocument();
expect(
screen.queryByLabelText("Inspector tabs body"),
Expand All @@ -134,14 +134,14 @@ describe("App", () => {
// Clicking the toggle expands the tabs body.
await user.click(screen.getByLabelText("Toggle inspector tabs section"));

expect(screen.getByLabelText("Changes panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Git panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Actions panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Inspector tabs body")).toBeInTheDocument();

// Clicking again collapses it back.
await user.click(screen.getByLabelText("Toggle inspector tabs section"));

expect(screen.getByLabelText("Changes panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Git panel body")).toBeInTheDocument();
expect(screen.getByLabelText("Actions panel body")).toBeInTheDocument();
expect(
screen.queryByLabelText("Inspector tabs body"),
Expand Down
31 changes: 30 additions & 1 deletion src/features/inspector/hooks/use-inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ import {
} from "react";
import { loadRepoScripts, type RepoScripts } from "@/lib/api";
import type { InspectorFileItem } from "@/lib/editor-session";
import { workspaceChangesQueryOptions } from "@/lib/query-client";
import {
workspaceChangesQueryOptions,
workspaceFilesQueryOptions,
} from "@/lib/query-client";
import {
getInitialActionsOpen,
getInitialActiveTab,
getInitialChangesHeight,
getInitialGitActiveTab,
getInitialTabsHeight,
getInitialTabsOpen,
INSPECTOR_ACTIONS_OPEN_STORAGE_KEY,
INSPECTOR_ACTIVE_TAB_STORAGE_KEY,
INSPECTOR_CHANGES_HEIGHT_STORAGE_KEY,
INSPECTOR_GIT_ACTIVE_TAB_STORAGE_KEY,
INSPECTOR_SECTION_HEADER_HEIGHT,
INSPECTOR_TABS_HEIGHT_STORAGE_KEY,
INSPECTOR_TABS_OPEN_STORAGE_KEY,
Expand Down Expand Up @@ -126,6 +131,7 @@ export function useWorkspaceInspectorSidebar({
const [actionsOpen, setActionsOpen] = useState(getInitialActionsOpen);
const [tabsOpen, setTabsOpen] = useState(getInitialTabsOpen);
const [activeTab, setActiveTab] = useState(getInitialActiveTab);
const [gitActiveTab, setGitActiveTab] = useState(getInitialGitActiveTab);

const [containerHeight, setContainerHeight] = useState(0);
const [storedChangesBody, setStoredChangesBody] = useState(() =>
Expand Down Expand Up @@ -218,6 +224,20 @@ export function useWorkspaceInspectorSidebar({
}
}, [activeTab]);

useEffect(() => {
try {
window.localStorage.setItem(
INSPECTOR_GIT_ACTIVE_TAB_STORAGE_KEY,
gitActiveTab,
);
} catch (error) {
console.error(
`[helmor] git active tab save failed for "${INSPECTOR_GIT_ACTIVE_TAB_STORAGE_KEY}"`,
error,
);
}
}, [gitActiveTab]);

useEffect(() => {
try {
window.localStorage.setItem(
Expand Down Expand Up @@ -282,6 +302,12 @@ export function useWorkspaceInspectorSidebar({
});
const changes: InspectorFileItem[] = changesQuery.data?.items ?? [];

const filesQuery = useQuery({
...workspaceFilesQueryOptions(workspaceRootPath ?? ""),
enabled: !!workspaceRootPath && gitActiveTab === "files",
});
const allFiles: InspectorFileItem[] = filesQuery.data ?? [];

const prevChangesRef = useRef<Map<string, string> | null>(null);
const prevRootPathRef = useRef(workspaceRootPath);
if (prevRootPathRef.current !== workspaceRootPath) {
Expand Down Expand Up @@ -437,10 +463,12 @@ export function useWorkspaceInspectorSidebar({
actionsOpen,
actionsRef,
activeTab,
allFiles,
changes,
changesHeight: changesBody,
containerRef,
flashingPaths,
gitActiveTab,
handleResizeStart,
handleToggleActions,
handleToggleTabs,
Expand All @@ -450,6 +478,7 @@ export function useWorkspaceInspectorSidebar({
repoScripts,
scriptsLoaded,
setActiveTab,
setGitActiveTab,
tabsBodyHeight: tabsBody,
tabsOpen,
tabsWrapperRef,
Expand Down
10 changes: 8 additions & 2 deletions src/features/inspector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useSetupAutoRun } from "./hooks/use-setup-auto-run";
import { HorizontalResizeHandle, InspectorTabsSection } from "./layout";
import type { ScriptStatus } from "./script-store";
import { ActionsSection } from "./sections/actions";
import { ChangesSection } from "./sections/changes";
import { GitSection } from "./sections/git-section";
import { OpenDevServerButton, RunTab } from "./sections/run";
import { SetupTab } from "./sections/setup";
import { TerminalInstancePanel } from "./sections/terminal";
Expand Down Expand Up @@ -91,10 +91,12 @@ export function WorkspaceInspectorSidebar({
actionsOpen,
actionsRef,
activeTab,
allFiles,
changes,
changesHeight,
containerRef,
flashingPaths,
gitActiveTab,
handleResizeStart,
handleToggleActions,
handleToggleTabs,
Expand All @@ -104,6 +106,7 @@ export function WorkspaceInspectorSidebar({
repoScripts,
scriptsLoaded,
setActiveTab,
setGitActiveTab,
tabsBodyHeight,
tabsOpen,
tabsWrapperRef,
Expand Down Expand Up @@ -381,13 +384,16 @@ export function WorkspaceInspectorSidebar({
isResizing && "select-none",
)}
>
<ChangesSection
<GitSection
workspaceId={workspaceId ?? null}
workspaceRootPath={workspaceRootPath ?? null}
workspaceBranch={workspaceBranch ?? null}
workspaceRemoteUrl={workspaceRemoteUrl ?? null}
workspaceTargetBranch={workspaceTargetBranch ?? null}
changes={changes}
allFiles={allFiles}
gitActiveTab={gitActiveTab}
onGitTabChange={setGitActiveTab}
editorMode={editorMode}
activeEditorPath={activeEditorPath}
onOpenEditorFile={onOpenEditorFile}
Expand Down
16 changes: 16 additions & 0 deletions src/features/inspector/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const INSPECTOR_CHANGES_HEIGHT_STORAGE_KEY =
"helmor.workspaceInspectorChangesHeight";
export const INSPECTOR_TABS_HEIGHT_STORAGE_KEY =
"helmor.workspaceInspectorTabsHeight";
export const INSPECTOR_GIT_ACTIVE_TAB_STORAGE_KEY =
"helmor.workspaceInspectorGitActiveTab";

export function getInitialActionsOpen(): boolean {
if (typeof window === "undefined") {
Expand Down Expand Up @@ -141,6 +143,20 @@ export function getInitialTabsHeight(defaultHeight: number): number {
}
}

export function getInitialGitActiveTab(): "changes" | "files" {
if (typeof window === "undefined") {
return "changes";
}
try {
const stored = window.localStorage.getItem(
INSPECTOR_GIT_ACTIVE_TAB_STORAGE_KEY,
);
return stored === "files" ? "files" : "changes";
} catch {
return "changes";
}
}

export const INSPECTOR_SECTION_HEADER_CLASS =
"flex h-8 min-w-0 shrink-0 items-center justify-between border-b border-border/60 bg-muted/25 px-3";
export const INSPECTOR_SECTION_TITLE_CLASS =
Expand Down
Loading
Loading