From 4f5eb3be4d973fdd89194f26caf685137496dbfb Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Feb 2026 22:52:04 -0800 Subject: [PATCH] Show worktree name in thread delete confirmation - Format orphaned worktree paths to display only the final segment - Apply formatting in Sidebar delete prompt for cleaner UX - Add tests for Unix, Windows, trailing slash, and fallback cases --- apps/web/src/components/Sidebar.tsx | 7 +++++-- apps/web/src/worktreeCleanup.test.ts | 28 +++++++++++++++++++++++++++- apps/web/src/worktreeCleanup.ts | 12 ++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 349034fdcf6c39a4a8a1de9ea26f519ae33a0885..ae2b6fb66ae0c16c8df64aad4f91fbe0479b67fe 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -12,7 +12,7 @@ import { type Thread, } from "../types"; import { useNativeApi } from "../hooks/useNativeApi"; -import { getOrphanedWorktreePathForThread } from "../worktreeCleanup"; +import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup"; const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const; function formatRelativeTime(iso: string): string { @@ -265,12 +265,15 @@ export default function Sidebar() { if (!thread) return; const threadProject = state.projects.find((project) => project.id === thread.projectId); const orphanedWorktreePath = getOrphanedWorktreePathForThread(state.threads, threadId); + const displayWorktreePath = orphanedWorktreePath + ? formatWorktreePathForDisplay(orphanedWorktreePath) + : null; const shouldDeleteWorktree = orphanedWorktreePath !== null && (await api.dialogs.confirm( [ "This thread is the only one linked to this worktree:", - orphanedWorktreePath, + displayWorktreePath ?? orphanedWorktreePath, "", "Delete the worktree too?", ].join("\n"), diff --git a/apps/web/src/worktreeCleanup.test.ts b/apps/web/src/worktreeCleanup.test.ts index cc86d7e8a30552faafd78151110be14dc4346eac..606717a028749061de75cd46e262a2ce489a26e3 100644 --- a/apps/web/src/worktreeCleanup.test.ts +++ b/apps/web/src/worktreeCleanup.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { DEFAULT_THREAD_TERMINAL_HEIGHT, DEFAULT_THREAD_TERMINAL_ID, type Thread } from "./types"; -import { getOrphanedWorktreePathForThread } from "./worktreeCleanup"; +import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "./worktreeCleanup"; function makeThread(overrides: Partial = {}): Thread { return { @@ -69,3 +69,29 @@ describe("getOrphanedWorktreePathForThread", () => { expect(result).toBe("/tmp/repo/worktrees/feature-a"); }); }); + +describe("formatWorktreePathForDisplay", () => { + it("shows only the last path segment for unix-like paths", () => { + const result = formatWorktreePathForDisplay( + "/Users/julius/.t3/worktrees/codething-mvp/codething-4e609bb8", + ); + expect(result).toBe("codething-4e609bb8"); + }); + + it("normalizes windows separators before selecting the final segment", () => { + const result = formatWorktreePathForDisplay( + "C:\\Users\\julius\\.t3\\worktrees\\codething-mvp\\codething-4e609bb8", + ); + expect(result).toBe("codething-4e609bb8"); + }); + + it("uses the final segment even when outside ~/.t3/worktrees", () => { + const result = formatWorktreePathForDisplay("/tmp/custom-worktrees/my-worktree"); + expect(result).toBe("my-worktree"); + }); + + it("ignores trailing slashes", () => { + const result = formatWorktreePathForDisplay("/tmp/custom-worktrees/my-worktree/"); + expect(result).toBe("my-worktree"); + }); +}); diff --git a/apps/web/src/worktreeCleanup.ts b/apps/web/src/worktreeCleanup.ts index b114814006e298443ab92f8dad9b161a651e6a4e..0bebd08355396425d3586cf2422e4bcc71540dd6 100644 --- a/apps/web/src/worktreeCleanup.ts +++ b/apps/web/src/worktreeCleanup.ts @@ -31,3 +31,15 @@ export function getOrphanedWorktreePathForThread( return isShared ? null : targetWorktreePath; } + +export function formatWorktreePathForDisplay(worktreePath: string): string { + const trimmed = worktreePath.trim(); + if (!trimmed) { + return worktreePath; + } + + const normalized = trimmed.replace(/\\/g, "/").replace(/\/+$/, ""); + const parts = normalized.split("/"); + const lastPart = parts[parts.length - 1]?.trim() ?? ""; + return lastPart.length > 0 ? lastPart : trimmed; +} -- 2.54.0