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
5 changes: 5 additions & 0 deletions .changeset/show-repo-paths-filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"helmor": patch
---

Show repository paths in the sidebar filter when multiple repositories share the same name.
2 changes: 2 additions & 0 deletions src-tauri/src/models/repos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct RepositoryCreateOption {
/// account had access at add-repo time; UI surfaces a "Connect"
/// affordance.
pub forge_login: Option<String>,
pub root_path: Option<String>,
pub repo_icon_src: Option<String>,
pub repo_initials: String,
}
Expand Down Expand Up @@ -138,6 +139,7 @@ pub fn list_repositories() -> Result<Vec<RepositoryCreateOption>> {
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)?,
Expand Down
43 changes: 43 additions & 0 deletions src/features/navigation/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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(
<TooltipProvider delayDuration={0}>
<WorkspacesSidebar
groups={repoWorkspaceGroups}
archivedRows={[]}
availableRepositories={duplicateNameRepositoryOptions}
sidebarRepoFilterIds={["repo-alpha-primary"]}
onSidebarRepoFilterChange={onSidebarRepoFilterChange}
/>
</TooltipProvider>,
);

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(
<TooltipProvider delayDuration={0}>
Expand Down
49 changes: 44 additions & 5 deletions src/features/navigation/sidebar-view-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,45 @@ 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,
onRepoFilterChange,
}: 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(
Expand Down Expand Up @@ -143,10 +172,13 @@ function SidebarRepoFilterPicker({
</CommandItem>
{sortedRepositories.map((repo) => {
const checked = selectedRepoIdSet.has(repo.id);
const showRootPath = Boolean(
repo.rootPath && hasDuplicateRepoName(sortedRepositories, repo),
);
return (
<CommandItem
key={repo.id}
value={repo.name}
value={repoCommandValue(repo)}
data-checked={checked}
onSelect={() => toggleRepository(repo.id)}
>
Expand All @@ -156,7 +188,14 @@ function SidebarRepoFilterPicker({
repoName={repo.name}
title={repo.name}
/>
<span className="truncate">{repo.name}</span>
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate">{repo.name}</span>
{showRootPath ? (
<span className="break-all text-[11px] leading-4 text-muted-foreground">
{repo.rootPath}
</span>
) : null}
</span>
</CommandItem>
);
})}
Expand Down
1 change: 1 addition & 0 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
Loading