From 19fbe4aa1f64eb458b9b0b600b4400239781b557 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Tue, 7 Apr 2026 21:16:35 -0700 Subject: [PATCH 1/2] Show both local and remote repo name when they differ --- .../src/main/services/folders/service.test.ts | 76 +++++++++++++++++++ .../code/src/main/services/folders/service.ts | 23 +++++- .../sidebar/components/TaskListView.tsx | 9 ++- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/apps/code/src/main/services/folders/service.test.ts b/apps/code/src/main/services/folders/service.test.ts index 326000c90..538123e06 100644 --- a/apps/code/src/main/services/folders/service.test.ts +++ b/apps/code/src/main/services/folders/service.test.ts @@ -296,6 +296,82 @@ describe("FoldersService", () => { ]); }); + it("strips .git suffix from remote repo name in display name", async () => { + const repos = [ + { + id: "folder-1", + path: "/home/user/MURZINI", + remoteUrl: "PostHog/billing.git", + lastAccessedAt: "2024-01-01T00:00:00.000Z", + createdAt: "2024-01-01T00:00:00.000Z", + updatedAt: "2024-01-01T00:00:00.000Z", + }, + ]; + mockRepositoryRepo.findAll.mockReturnValue(repos); + mockExistsSync.mockReturnValue(true); + + const result = await service.getFolders(); + + expect(result[0].name).toBe("MURZINI (billing)"); + }); + + it("uses remote repo name in display name when it differs from local dir", async () => { + const repos = [ + { + id: "folder-1", + path: "/home/user/ph-tour-demo", + remoteUrl: "PostHog/hogotchi", + lastAccessedAt: "2024-01-01T00:00:00.000Z", + createdAt: "2024-01-01T00:00:00.000Z", + updatedAt: "2024-01-01T00:00:00.000Z", + }, + ]; + mockRepositoryRepo.findAll.mockReturnValue(repos); + mockExistsSync.mockReturnValue(true); + + const result = await service.getFolders(); + + expect(result[0].name).toBe("ph-tour-demo (hogotchi)"); + }); + + it("uses local dir name when it matches remote repo name", async () => { + const repos = [ + { + id: "folder-1", + path: "/home/user/hogotchi", + remoteUrl: "PostHog/hogotchi", + lastAccessedAt: "2024-01-01T00:00:00.000Z", + createdAt: "2024-01-01T00:00:00.000Z", + updatedAt: "2024-01-01T00:00:00.000Z", + }, + ]; + mockRepositoryRepo.findAll.mockReturnValue(repos); + mockExistsSync.mockReturnValue(true); + + const result = await service.getFolders(); + + expect(result[0].name).toBe("hogotchi"); + }); + + it("uses local dir name when it matches remote repo name case-insensitively", async () => { + const repos = [ + { + id: "folder-1", + path: "/home/user/Hogotchi", + remoteUrl: "PostHog/hogotchi", + lastAccessedAt: "2024-01-01T00:00:00.000Z", + createdAt: "2024-01-01T00:00:00.000Z", + updatedAt: "2024-01-01T00:00:00.000Z", + }, + ]; + mockRepositoryRepo.findAll.mockReturnValue(repos); + mockExistsSync.mockReturnValue(true); + + const result = await service.getFolders(); + + expect(result[0].name).toBe("Hogotchi"); + }); + it("marks non-existent folders", async () => { const repos = [ { diff --git a/apps/code/src/main/services/folders/service.ts b/apps/code/src/main/services/folders/service.ts index 43f64adc2..562ab7a6e 100644 --- a/apps/code/src/main/services/folders/service.ts +++ b/apps/code/src/main/services/folders/service.ts @@ -80,6 +80,25 @@ export class FoldersService { } } + private getDisplayName( + repoPath: string, + remoteUrl: string | null | undefined, + ): string { + const localName = path.basename(repoPath); + if (remoteUrl) { + const repoName = remoteUrl + .trim() + .split("/") + .pop() + ?.replace(/\.git$/, "") + ?.trim(); + if (repoName && repoName.toLowerCase() !== localName.toLowerCase()) { + return `${localName} (${repoName})`; + } + } + return localName; + } + async getFolders(): Promise<(RegisteredFolder & { exists: boolean })[]> { const repos = this.repositoryRepo.findAll(); return repos @@ -87,7 +106,7 @@ export class FoldersService { .map((r) => ({ id: r.id, path: r.path, - name: path.basename(r.path), + name: this.getDisplayName(r.path, r.remoteUrl), remoteUrl: r.remoteUrl ?? null, lastAccessed: r.lastAccessedAt ?? r.createdAt, createdAt: r.createdAt, @@ -177,7 +196,7 @@ export class FoldersService { return { id: repo.id, path: repo.path, - name: path.basename(repo.path), + name: this.getDisplayName(repo.path, repo.remoteUrl), remoteUrl: repo.remoteUrl ?? null, lastAccessed: repo.lastAccessedAt ?? repo.createdAt, createdAt: repo.createdAt, diff --git a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx index fd63c5d73..65d3529e8 100644 --- a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx +++ b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx @@ -378,7 +378,10 @@ export function TaskListView({ const isExpanded = !collapsedSections.has(group.id); const folder = folders.find( (f) => - f.remoteUrl?.toLowerCase() === group.id.toLowerCase() || + f.remoteUrl + ?.trim() + .replace(/\.git$/, "") + .toLowerCase() === group.id.trim().toLowerCase() || f.path === group.id, ); const groupFolderId = @@ -387,7 +390,7 @@ export function TaskListView({ @@ -406,7 +409,7 @@ export function TaskListView({ navigateToTaskInput(); } }} - newTaskTooltip={`Start new task in ${group.name}`} + newTaskTooltip={`Start new task in ${folder?.name ?? group.name}`} > {group.tasks.map((task) => ( Date: Tue, 7 Apr 2026 21:23:14 -0700 Subject: [PATCH 2/2] Extract normalizeRepoKey helper for .git suffix stripping --- apps/code/src/main/services/folders/service.test.ts | 6 +++--- apps/code/src/main/services/folders/service.ts | 13 +++++-------- .../features/sidebar/components/TaskListView.tsx | 8 ++++---- apps/code/src/shared/utils/repo.ts | 3 +++ 4 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 apps/code/src/shared/utils/repo.ts diff --git a/apps/code/src/main/services/folders/service.test.ts b/apps/code/src/main/services/folders/service.test.ts index 538123e06..9bc5d04c3 100644 --- a/apps/code/src/main/services/folders/service.test.ts +++ b/apps/code/src/main/services/folders/service.test.ts @@ -296,11 +296,11 @@ describe("FoldersService", () => { ]); }); - it("strips .git suffix from remote repo name in display name", async () => { + it("strips .git suffix from remote repo name in display name (defensive against legacy data)", async () => { const repos = [ { id: "folder-1", - path: "/home/user/MURZINI", + path: "/home/user/my-billing-fork", remoteUrl: "PostHog/billing.git", lastAccessedAt: "2024-01-01T00:00:00.000Z", createdAt: "2024-01-01T00:00:00.000Z", @@ -312,7 +312,7 @@ describe("FoldersService", () => { const result = await service.getFolders(); - expect(result[0].name).toBe("MURZINI (billing)"); + expect(result[0].name).toBe("my-billing-fork (billing)"); }); it("uses remote repo name in display name when it differs from local dir", async () => { diff --git a/apps/code/src/main/services/folders/service.ts b/apps/code/src/main/services/folders/service.ts index 562ab7a6e..7f33c8fcd 100644 --- a/apps/code/src/main/services/folders/service.ts +++ b/apps/code/src/main/services/folders/service.ts @@ -3,12 +3,14 @@ import path from "node:path"; import { getRemoteUrl, isGitRepository } from "@posthog/git/queries"; import { InitRepositorySaga } from "@posthog/git/sagas/init"; +import { normalizeRepoKey } from "@shared/utils/repo"; + function extractRepoKey(url: string): string | null { const httpsMatch = url.match(/github\.com\/([^/]+\/[^/]+)/); - if (httpsMatch) return httpsMatch[1].replace(/\.git$/, ""); + if (httpsMatch) return normalizeRepoKey(httpsMatch[1]); const sshMatch = url.match(/github\.com:([^/]+\/[^/]+)/); - if (sshMatch) return sshMatch[1].replace(/\.git$/, ""); + if (sshMatch) return normalizeRepoKey(sshMatch[1]); return null; } @@ -86,12 +88,7 @@ export class FoldersService { ): string { const localName = path.basename(repoPath); if (remoteUrl) { - const repoName = remoteUrl - .trim() - .split("/") - .pop() - ?.replace(/\.git$/, "") - ?.trim(); + const repoName = normalizeRepoKey(remoteUrl).split("/").pop(); if (repoName && repoName.toLowerCase() !== localName.toLowerCase()) { return `${localName} (${repoName})`; } diff --git a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx index 65d3529e8..6d5f29456 100644 --- a/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx +++ b/apps/code/src/renderer/features/sidebar/components/TaskListView.tsx @@ -15,6 +15,7 @@ import { } from "@phosphor-icons/react"; import { Box, Flex, Popover, Text } from "@radix-ui/themes"; import { useWorkspace } from "@renderer/features/workspace/hooks/useWorkspace"; +import { normalizeRepoKey } from "@shared/utils/repo"; import { useNavigationStore } from "@stores/navigationStore"; import { useCallback, useEffect } from "react"; import type { TaskData, TaskGroup } from "../hooks/useSidebarData"; @@ -378,10 +379,9 @@ export function TaskListView({ const isExpanded = !collapsedSections.has(group.id); const folder = folders.find( (f) => - f.remoteUrl - ?.trim() - .replace(/\.git$/, "") - .toLowerCase() === group.id.trim().toLowerCase() || + (f.remoteUrl && + normalizeRepoKey(f.remoteUrl).toLowerCase() === + normalizeRepoKey(group.id).toLowerCase()) || f.path === group.id, ); const groupFolderId = diff --git a/apps/code/src/shared/utils/repo.ts b/apps/code/src/shared/utils/repo.ts new file mode 100644 index 000000000..5480c3105 --- /dev/null +++ b/apps/code/src/shared/utils/repo.ts @@ -0,0 +1,3 @@ +export function normalizeRepoKey(key: string): string { + return key.trim().replace(/\.git$/, ""); +}