diff --git a/apps/code/src/main/services/folders/service.test.ts b/apps/code/src/main/services/folders/service.test.ts index 326000c90..9bc5d04c3 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 (defensive against legacy data)", async () => { + const repos = [ + { + id: "folder-1", + path: "/home/user/my-billing-fork", + 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("my-billing-fork (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..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; } @@ -80,6 +82,20 @@ export class FoldersService { } } + private getDisplayName( + repoPath: string, + remoteUrl: string | null | undefined, + ): string { + const localName = path.basename(repoPath); + if (remoteUrl) { + const repoName = normalizeRepoKey(remoteUrl).split("/").pop(); + 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 +103,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 +193,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..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,7 +379,9 @@ export function TaskListView({ const isExpanded = !collapsedSections.has(group.id); const folder = folders.find( (f) => - f.remoteUrl?.toLowerCase() === group.id.toLowerCase() || + (f.remoteUrl && + normalizeRepoKey(f.remoteUrl).toLowerCase() === + normalizeRepoKey(group.id).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) => (