From 7819d4cc381f482ee4fe9285f7fe65f354423559 Mon Sep 17 00:00:00 2001 From: Aidan Date: Sat, 9 May 2026 20:48:08 +0800 Subject: [PATCH 1/3] fix: show repository paths in picker --- .changeset/calm-repos-name.md | 5 + src-tauri/src/models/repos.rs | 2 + src/features/workspace-start/index.tsx | 127 ++++++++++++++++--------- src/lib/api.ts | 1 + 4 files changed, 91 insertions(+), 44 deletions(-) create mode 100644 .changeset/calm-repos-name.md diff --git a/.changeset/calm-repos-name.md b/.changeset/calm-repos-name.md new file mode 100644 index 000000000..e04c6e114 --- /dev/null +++ b/.changeset/calm-repos-name.md @@ -0,0 +1,5 @@ +--- +"helmor": patch +--- + +Show the selected or hovered repository directory in the workspace-start repository picker. diff --git a/src-tauri/src/models/repos.rs b/src-tauri/src/models/repos.rs index 22c758fd3..3c2a962d7 100644 --- a/src-tauri/src/models/repos.rs +++ b/src-tauri/src/models/repos.rs @@ -16,6 +16,7 @@ use super::db; pub struct RepositoryCreateOption { pub id: String, pub name: String, + pub root_path: Option, pub remote: Option, pub remote_url: Option, pub default_branch: Option, @@ -134,6 +135,7 @@ pub fn list_repositories() -> Result> { Ok(RepositoryCreateOption { id: row.get(0)?, name, + root_path, remote: row.get(4)?, remote_url: row.get(5)?, forge_provider: row.get(6)?, diff --git a/src/features/workspace-start/index.tsx b/src/features/workspace-start/index.tsx index 684ebc135..704d720b3 100644 --- a/src/features/workspace-start/index.tsx +++ b/src/features/workspace-start/index.tsx @@ -1,5 +1,13 @@ -import { ChevronDown, GitBranch, Laptop, Plus, Split, X } from "lucide-react"; -import { useCallback, useEffect, useState } from "react"; +import { + ChevronDown, + FolderOpen, + GitBranch, + Laptop, + Plus, + Split, + X, +} from "lucide-react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { BranchPickerPopover } from "@/components/branch-picker"; import { TrafficLightSpacer } from "@/components/chrome/traffic-light-spacer"; import { Button } from "@/components/ui/button"; @@ -43,6 +51,67 @@ function defaultBranchPrefix(repo: RepositoryCreateOption | null): string { } } +function RepositoryMenuContent({ + align, + repositories, + selectedRepository, + onSelectRepository, +}: { + align: "center" | "start"; + repositories: RepositoryCreateOption[]; + selectedRepository: RepositoryCreateOption | null; + onSelectRepository: (repository: RepositoryCreateOption) => void; +}) { + const [focusedRepoId, setFocusedRepoId] = useState(null); + const locationRepo = useMemo( + () => + repositories.find((repository) => repository.id === focusedRepoId) ?? + selectedRepository ?? + repositories[0] ?? + null, + [focusedRepoId, repositories, selectedRepository], + ); + const location = locationRepo?.rootPath?.trim() ?? null; + + return ( + + {repositories.map((repository) => ( + onSelectRepository(repository)} + onFocus={() => setFocusedRepoId(repository.id)} + onPointerEnter={() => setFocusedRepoId(repository.id)} + className="gap-2" + > + + {repository.name} + + ))} + {location ? ( +
+ + + {location} + +
+ ) : null} +
+ ); +} + type WorkspaceStartPageProps = { repositories: RepositoryCreateOption[]; selectedRepository: RepositoryCreateOption | null; @@ -297,27 +366,12 @@ export function WorkspaceStartPage({ /> - - {repositories.map((repository) => ( - onSelectRepository(repository)} - className="gap-2" - > - - - {repository.name} - - - ))} - + - - {repositories.map((repository) => ( - onSelectRepository(repository)} - className="gap-2" - > - - - {repository.name} - - - ))} - + ) : null} diff --git a/src/lib/api.ts b/src/lib/api.ts index a0d9ea3c3..43a2a023c 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -199,6 +199,7 @@ export type BranchPrefixType = "username" | "custom" | "none"; export type RepositoryCreateOption = { id: string; name: string; + rootPath?: string | null; remote?: string | null; remoteUrl?: string | null; defaultBranch?: string | null; From 678d4044db6ee4f367861b6e9ce4072c2892b0f0 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 15 May 2026 14:05:16 +0800 Subject: [PATCH 2/3] feat(workspaces): show repo paths in sidebar groups --- .changeset/calm-repos-name.md | 4 ++- src-tauri/src/workspace/workspaces.rs | 2 ++ .../hooks/controller/optimistic-rows.ts | 1 + src/features/navigation/index.test.tsx | 34 +++++++++++++++++++ src/features/navigation/index.tsx | 27 ++++++++++++--- .../navigation/sidebar-projection.test.ts | 2 ++ src/features/navigation/sidebar-projection.ts | 12 +++++-- src/lib/api.ts | 2 ++ 8 files changed, 77 insertions(+), 7 deletions(-) diff --git a/.changeset/calm-repos-name.md b/.changeset/calm-repos-name.md index e04c6e114..0ab85cba6 100644 --- a/.changeset/calm-repos-name.md +++ b/.changeset/calm-repos-name.md @@ -2,4 +2,6 @@ "helmor": patch --- -Show the selected or hovered repository directory in the workspace-start repository picker. +Show repository directories in more repo selection surfaces: +- Show the selected or hovered repository path in the workspace-start repository picker. +- Show repository paths on repo-grouped workspace headers. diff --git a/src-tauri/src/workspace/workspaces.rs b/src-tauri/src/workspace/workspaces.rs index c59777f30..c1f7f58df 100644 --- a/src-tauri/src/workspace/workspaces.rs +++ b/src-tauri/src/workspace/workspaces.rs @@ -47,6 +47,7 @@ pub struct WorkspaceSidebarRow { pub directory_name: String, pub repo_id: String, pub repo_name: String, + pub repo_root_path: Option, pub repo_icon_src: Option, pub repo_initials: String, pub state: WorkspaceState, @@ -1124,6 +1125,7 @@ pub fn record_to_sidebar_row(record: WorkspaceRecord) -> WorkspaceSidebarRow { directory_name: record.directory_name, repo_id: record.repo_id, repo_name: record.repo_name, + repo_root_path: record.root_path.clone(), repo_icon_src: helpers::repo_icon_src_for_root_path(record.root_path.as_deref()), repo_initials, state: record.state, diff --git a/src/features/navigation/hooks/controller/optimistic-rows.ts b/src/features/navigation/hooks/controller/optimistic-rows.ts index 8d466e76e..88f3f33b3 100644 --- a/src/features/navigation/hooks/controller/optimistic-rows.ts +++ b/src/features/navigation/hooks/controller/optimistic-rows.ts @@ -27,6 +27,7 @@ export function createPreparedWorkspaceRow( directoryName: prepared.directoryName, repoId: repository.id, repoName: repository.name, + repoRootPath: repository.rootPath ?? null, repoIconSrc: repository.repoIconSrc ?? null, repoInitials: repository.repoInitials ?? null, state: prepared.state, diff --git a/src/features/navigation/index.test.tsx b/src/features/navigation/index.test.tsx index 5f9cc9727..778cc2719 100644 --- a/src/features/navigation/index.test.tsx +++ b/src/features/navigation/index.test.tsx @@ -540,6 +540,7 @@ describe("WorkspacesSidebar", () => { { id: "repo:repo-1", label: "helmor", + repoRootPath: "/Users/aidan/dev/projects/helmor/.git/worktrees/isonoe", tone: "pinned", rows: [ { @@ -552,6 +553,39 @@ describe("WorkspacesSidebar", () => { }, ]; + it("shows the repository path in a capped-width wrapping tooltip on repo headers", async () => { + const user = userEvent.setup(); + + render( + + + , + ); + + const header = screen + .getAllByRole("button", { name: /helmor/i }) + .find((el) => el.tagName === "DIV"); + expect(header).toBeDefined(); + + await user.hover(within(header as HTMLElement).getByText("helmor")); + + const tooltip = ( + await screen.findAllByText( + "/Users/aidan/dev/projects/helmor/.git/worktrees/isonoe", + ) + ).find( + (element) => element.getAttribute("data-slot") === "tooltip-content", + ); + expect(tooltip).toBeDefined(); + expect(tooltip).toHaveClass("max-w-64"); + expect(tooltip).toHaveClass("whitespace-normal"); + expect(tooltip).toHaveClass("break-all"); + }); + it("renders a `+` button on a repo group header that fires onCreateWorkspaceForRepo with the repo id", async () => { const user = userEvent.setup(); const onCreateWorkspaceForRepo = vi.fn(); diff --git a/src/features/navigation/index.tsx b/src/features/navigation/index.tsx index fe0d57cd7..100006e8e 100644 --- a/src/features/navigation/index.tsx +++ b/src/features/navigation/index.tsx @@ -697,9 +697,12 @@ export const WorkspacesSidebar = memo(function WorkspacesSidebar({ const repoSampleRow: WorkspaceRow | undefined = isRepoGroup ? item.group.rows[0] : undefined; + const repoRootPath = isRepoGroup + ? (item.group.repoRootPath?.trim() ?? null) + : null; const headerLabel = ( - + {isArchived ? ( )} - {item.group.label} + {item.group.label} ); + const visibleHeaderLabel = + repoRootPath && isRepoGroup ? ( + + {headerLabel} + + {repoRootPath} + + + ) : ( + headerLabel + ); const headerClassName = cn( "group/trigger flex w-full select-none items-center justify-between rounded-lg px-2 text-[13px] font-semibold tracking-[-0.01em] text-foreground hover:bg-accent/60 py-1", @@ -761,7 +780,7 @@ export const WorkspacesSidebar = memo(function WorkspacesSidebar({ } }} > - {headerLabel} + {visibleHeaderLabel} {onCreateWorkspaceForRepo ? ( @@ -804,7 +823,7 @@ export const WorkspacesSidebar = memo(function WorkspacesSidebar({ disabled={!item.canCollapse} onClick={() => toggleSection(item.groupId)} > - {headerLabel} + {visibleHeaderLabel} {item.group.rows.length > 0 ? ( diff --git a/src/features/navigation/sidebar-projection.test.ts b/src/features/navigation/sidebar-projection.test.ts index 1a57baffd..392d91a90 100644 --- a/src/features/navigation/sidebar-projection.test.ts +++ b/src/features/navigation/sidebar-projection.test.ts @@ -256,6 +256,7 @@ describe("regroupByRepo", () => { status: "in-progress", repoId: "repo-A", repoName: "alpha", + repoRootPath: "/Users/me/repos/alpha", }, ], }, @@ -330,6 +331,7 @@ describe("regroupByRepo", () => { const result = regroupByRepo(fixture); const repoGroups = result.filter((g) => g.id.startsWith(REPO_GROUP_PREFIX)); expect(repoGroups.map((g) => g.label)).toEqual(["alpha", "beta"]); + expect(repoGroups[0]?.repoRootPath).toBe("/Users/me/repos/alpha"); // progress (pendingCreation) + done + review rows for repo-A // collapse into the alpha bucket. Pinned and backlog rows do NOT // land here — they kept their own groups. diff --git a/src/features/navigation/sidebar-projection.ts b/src/features/navigation/sidebar-projection.ts index dd0107eda..33d5091ed 100644 --- a/src/features/navigation/sidebar-projection.ts +++ b/src/features/navigation/sidebar-projection.ts @@ -152,7 +152,7 @@ export function regroupByRepo(groups: WorkspaceGroup[]): WorkspaceGroup[] { const bucketOrder = new Map(); const repoBuckets = new Map< string, - { label: string; rows: WorkspaceRow[] } + { label: string; repoRootPath: string | null; rows: WorkspaceRow[] } >(); let seen = 0; @@ -171,10 +171,17 @@ export function regroupByRepo(groups: WorkspaceGroup[]): WorkspaceGroup[] { : UNKNOWN_REPO_GROUP_ID; let bucket = repoBuckets.get(bucketId); if (!bucket) { - bucket = { label: row.repoName ?? "Unknown", rows: [] }; + bucket = { + label: row.repoName ?? "Unknown", + repoRootPath: row.repoRootPath?.trim() || null, + rows: [], + }; repoBuckets.set(bucketId, bucket); firstSeen.set(bucketId, seen++); } + if (!bucket.repoRootPath) { + bucket.repoRootPath = row.repoRootPath?.trim() || null; + } bucket.rows.push(row); // Lowest non-zero `repoSidebarOrder` across the bucket's rows is // the canonical bucket order. They should all agree (a single @@ -209,6 +216,7 @@ export function regroupByRepo(groups: WorkspaceGroup[]): WorkspaceGroup[] { return { id: bucketId, label: bucket.label, + repoRootPath: bucket.repoRootPath, // Repo groups don't carry status semantics; reuse "pinned" as a // neutral tone that won't render a status icon (the header will // branch on group.id and render an avatar instead). diff --git a/src/lib/api.ts b/src/lib/api.ts index 56a6e2d43..475efeee3 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -67,6 +67,7 @@ export type WorkspaceRow = { directoryName?: string; repoId?: string; repoName?: string; + repoRootPath?: string | null; repoIconSrc?: string | null; repoInitials?: string | null; state?: WorkspaceState; @@ -112,6 +113,7 @@ export type WorkspaceRow = { export type WorkspaceGroup = { id: string; label: string; + repoRootPath?: string | null; tone: GroupTone; rows: WorkspaceRow[]; }; From 603cce20171bab9dd02f1c447878e066c01925d2 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 15 May 2026 14:10:04 +0800 Subject: [PATCH 3/3] fix(workspaces): reposition repo path tooltip --- src/features/navigation/index.test.tsx | 2 ++ src/features/navigation/index.tsx | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/features/navigation/index.test.tsx b/src/features/navigation/index.test.tsx index 778cc2719..0a2dd9855 100644 --- a/src/features/navigation/index.test.tsx +++ b/src/features/navigation/index.test.tsx @@ -581,6 +581,8 @@ describe("WorkspacesSidebar", () => { (element) => element.getAttribute("data-slot") === "tooltip-content", ); expect(tooltip).toBeDefined(); + expect(tooltip).toHaveAttribute("data-side", "top"); + expect(tooltip).toHaveAttribute("data-align", "start"); expect(tooltip).toHaveClass("max-w-64"); expect(tooltip).toHaveClass("whitespace-normal"); expect(tooltip).toHaveClass("break-all"); diff --git a/src/features/navigation/index.tsx b/src/features/navigation/index.tsx index 100006e8e..988380de3 100644 --- a/src/features/navigation/index.tsx +++ b/src/features/navigation/index.tsx @@ -726,8 +726,8 @@ export const WorkspacesSidebar = memo(function WorkspacesSidebar({ {headerLabel}