From 156acfac7a07b149a640d60e92b3cdd3805a07f9 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 15 May 2026 18:06:21 +0800 Subject: [PATCH] feat(navigation): distinguish duplicate repo filters --- .changeset/show-repo-paths-filter.md | 5 ++ src-tauri/src/models/repos.rs | 2 + src/features/navigation/index.test.tsx | 43 ++++++++++++++++ .../navigation/sidebar-view-popover.tsx | 49 +++++++++++++++++-- src/lib/api.ts | 1 + 5 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 .changeset/show-repo-paths-filter.md diff --git a/.changeset/show-repo-paths-filter.md b/.changeset/show-repo-paths-filter.md new file mode 100644 index 000000000..f09924d30 --- /dev/null +++ b/.changeset/show-repo-paths-filter.md @@ -0,0 +1,5 @@ +--- +"helmor": patch +--- + +Show repository paths in the sidebar filter when multiple repositories share the same name. diff --git a/src-tauri/src/models/repos.rs b/src-tauri/src/models/repos.rs index 39d2cc331..388792061 100644 --- a/src-tauri/src/models/repos.rs +++ b/src-tauri/src/models/repos.rs @@ -29,6 +29,7 @@ pub struct RepositoryCreateOption { /// account had access at add-repo time; UI surfaces a "Connect" /// affordance. pub forge_login: Option, + pub root_path: Option, pub repo_icon_src: Option, pub repo_initials: String, } @@ -138,6 +139,7 @@ pub fn list_repositories() -> Result> { remote_url: row.get(5)?, forge_provider: row.get(6)?, forge_login: row.get(7)?, + root_path, branch_prefix_type, branch_prefix_custom: row.get(9)?, default_branch: row.get(2)?, diff --git a/src/features/navigation/index.test.tsx b/src/features/navigation/index.test.tsx index 5d72f7bfd..3b2aa37c9 100644 --- a/src/features/navigation/index.test.tsx +++ b/src/features/navigation/index.test.tsx @@ -35,6 +35,12 @@ const repositoryOptions = [ { id: "repo-gamma", name: "Gamma" }, ]; +const duplicateNameRepositoryOptions = [ + { id: "repo-alpha-primary", name: "Alpha", rootPath: "/tmp/one/Alpha" }, + { id: "repo-alpha-secondary", name: "Alpha", rootPath: "/tmp/two/Alpha" }, + { id: "repo-beta", name: "Beta", rootPath: "/tmp/Beta" }, +]; + const repoWorkspaceGroups: WorkspaceGroup[] = [ { id: "progress", @@ -206,6 +212,43 @@ describe("WorkspacesSidebar", () => { expect(screen.queryByRole("button", { name: "Beta workspace" })).toBeNull(); }); + it("distinguishes same-name repositories in the sidebar filter", async () => { + const user = userEvent.setup(); + const onSidebarRepoFilterChange = vi.fn(); + + render( + + + , + ); + + await user.click( + screen.getByRole("button", { name: "Filter and sort sidebar" }), + ); + await user.click(screen.getByRole("button", { name: "Alpha" })); + + expect(screen.getByText("/tmp/one/Alpha")).toBeInTheDocument(); + expect(screen.getByText("/tmp/two/Alpha")).toBeInTheDocument(); + + const alphaItems = screen.getAllByRole("option", { name: /Alpha/ }); + expect(alphaItems).toHaveLength(2); + expect(alphaItems[0]).toHaveAttribute("data-checked", "true"); + expect(alphaItems[1]).toHaveAttribute("data-checked", "false"); + + await user.click(alphaItems[1]!); + + expect(onSidebarRepoFilterChange).toHaveBeenCalledWith([ + "repo-alpha-primary", + "repo-alpha-secondary", + ]); + }); + it("opens sidebar filter controls from the app shortcut event", () => { render( diff --git a/src/features/navigation/sidebar-view-popover.tsx b/src/features/navigation/sidebar-view-popover.tsx index 89dbec822..c7dabf994 100644 --- a/src/features/navigation/sidebar-view-popover.tsx +++ b/src/features/navigation/sidebar-view-popover.tsx @@ -81,6 +81,25 @@ function repoFilterLabel( return `${selectedRepoIds.length} selected`; } +function repoCommandValue(repo: RepositoryCreateOption) { + return [repo.name, repo.rootPath, repo.id].filter(Boolean).join(" "); +} + +function hasDuplicateRepoName( + repositories: RepositoryCreateOption[], + repo: RepositoryCreateOption, +) { + const repoName = repo.name.trim().toLocaleLowerCase(); + return ( + repoName.length > 0 && + repositories.some( + (other) => + other.id !== repo.id && + other.name.trim().toLocaleLowerCase() === repoName, + ) + ); +} + function SidebarRepoFilterPicker({ repositories, selectedRepoIds, @@ -88,9 +107,19 @@ function SidebarRepoFilterPicker({ }: SidebarRepoFilterPickerProps) { const sortedRepositories = useMemo( () => - [...repositories].sort((left, right) => - left.name.localeCompare(right.name, undefined, { sensitivity: "base" }), - ), + [...repositories].sort((left, right) => { + const byName = left.name.localeCompare(right.name, undefined, { + sensitivity: "base", + }); + if (byName !== 0) return byName; + const byRootPath = (left.rootPath ?? "").localeCompare( + right.rootPath ?? "", + undefined, + { sensitivity: "base" }, + ); + if (byRootPath !== 0) return byRootPath; + return left.id.localeCompare(right.id); + }), [repositories], ); const selectedRepoIdSet = useMemo( @@ -143,10 +172,13 @@ function SidebarRepoFilterPicker({ {sortedRepositories.map((repo) => { const checked = selectedRepoIdSet.has(repo.id); + const showRootPath = Boolean( + repo.rootPath && hasDuplicateRepoName(sortedRepositories, repo), + ); return ( toggleRepository(repo.id)} > @@ -156,7 +188,14 @@ function SidebarRepoFilterPicker({ repoName={repo.name} title={repo.name} /> - {repo.name} + + {repo.name} + {showRootPath ? ( + + {repo.rootPath} + + ) : null} + ); })} diff --git a/src/lib/api.ts b/src/lib/api.ts index b4016369e..148dc5741 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -223,6 +223,7 @@ export type RepositoryCreateOption = { /** gh/glab account login bound to this repo, or null when none had * access at add-time. UI shows a "Connect" prompt when null. */ forgeLogin?: string | null; + rootPath?: string | null; repoIconSrc?: string | null; repoInitials?: string | null; };