History

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

Julius Marminge committed 9 months ago

Commit 4f5eb3b1 parent b6523a13 files · +44 −3Browse files

Unified · Split

3 files
  1. apps/web/src/components/Sidebar.tsx +5 −2
  2. apps/web/src/worktreeCleanup.test.ts +27 −1
  3. apps/web/src/worktreeCleanup.ts +12 −0
apps/web/src/components/Sidebar.tsx+5−2
@@ -12,7 +12,7 @@ import { · 11 unmodified lines
12 type Thread,12 type Thread,
13} from "../types";13} from "../types";
14import { useNativeApi } from "../hooks/useNativeApi";14import { useNativeApi } from "../hooks/useNativeApi";
15import { getOrphanedWorktreePathForThread } from "../worktreeCleanup";15import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup";
16 16
17const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const;17const THEME_CYCLE = { system: "light", light: "dark", dark: "system" } as const;
18function formatRelativeTime(iso: string): string {18function formatRelativeTime(iso: string): string {
@@ -265,12 +265,15 @@ export default function Sidebar() { · 246 unmodified lines
265 if (!thread) return;265 if (!thread) return;
266 const threadProject = state.projects.find((project) => project.id === thread.projectId);266 const threadProject = state.projects.find((project) => project.id === thread.projectId);
267 const orphanedWorktreePath = getOrphanedWorktreePathForThread(state.threads, threadId);267 const orphanedWorktreePath = getOrphanedWorktreePathForThread(state.threads, threadId);
268 const displayWorktreePath = orphanedWorktreePath
269 ? formatWorktreePathForDisplay(orphanedWorktreePath)
270 : null;
268 const shouldDeleteWorktree =271 const shouldDeleteWorktree =
269 orphanedWorktreePath !== null &&272 orphanedWorktreePath !== null &&
270 (await api.dialogs.confirm(273 (await api.dialogs.confirm(
271 [274 [
272 "This thread is the only one linked to this worktree:",275 "This thread is the only one linked to this worktree:",
273 orphanedWorktreePath,276 displayWorktreePath ?? orphanedWorktreePath,
274 "",277 "",
275 "Delete the worktree too?",278 "Delete the worktree too?",
276 ].join("\n"),279 ].join("\n"),
apps/web/src/worktreeCleanup.test.ts+27−1
@@ -1,7 +1,7 @@
1import { describe, expect, it } from "vitest";1import { describe, expect, it } from "vitest";
2 2
3import { DEFAULT_THREAD_TERMINAL_HEIGHT, DEFAULT_THREAD_TERMINAL_ID, type Thread } from "./types";3import { DEFAULT_THREAD_TERMINAL_HEIGHT, DEFAULT_THREAD_TERMINAL_ID, type Thread } from "./types";
4import { getOrphanedWorktreePathForThread } from "./worktreeCleanup";4import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "./worktreeCleanup";
5 5
6function makeThread(overrides: Partial<Thread> = {}): Thread {6function makeThread(overrides: Partial<Thread> = {}): Thread {
7 return {7 return {
@@ -69,3 +69,29 @@ describe("getOrphanedWorktreePathForThread", () => { · 61 unmodified lines
69 expect(result).toBe("/tmp/repo/worktrees/feature-a");69 expect(result).toBe("/tmp/repo/worktrees/feature-a");
70 });70 });
71});71});
72
73describe("formatWorktreePathForDisplay", () => {
74 it("shows only the last path segment for unix-like paths", () => {
75 const result = formatWorktreePathForDisplay(
76 "/Users/julius/.t3/worktrees/codething-mvp/codething-4e609bb8",
77 );
78 expect(result).toBe("codething-4e609bb8");
79 });
80
81 it("normalizes windows separators before selecting the final segment", () => {
82 const result = formatWorktreePathForDisplay(
83 "C:\\Users\\julius\\.t3\\worktrees\\codething-mvp\\codething-4e609bb8",
84 );
85 expect(result).toBe("codething-4e609bb8");
86 });
87
88 it("uses the final segment even when outside ~/.t3/worktrees", () => {
89 const result = formatWorktreePathForDisplay("/tmp/custom-worktrees/my-worktree");
90 expect(result).toBe("my-worktree");
91 });
92
93 it("ignores trailing slashes", () => {
94 const result = formatWorktreePathForDisplay("/tmp/custom-worktrees/my-worktree/");
95 expect(result).toBe("my-worktree");
96 });
97});
apps/web/src/worktreeCleanup.ts+12−0
@@ -31,3 +31,15 @@ export function getOrphanedWorktreePathForThread( · 30 unmodified lines
31 31
32 return isShared ? null : targetWorktreePath;32 return isShared ? null : targetWorktreePath;
33}33}
34
35export function formatWorktreePathForDisplay(worktreePath: string): string {
36 const trimmed = worktreePath.trim();
37 if (!trimmed) {
38 return worktreePath;
39 }
40
41 const normalized = trimmed.replace(/\\/g, "/").replace(/\/+$/, "");
42 const parts = normalized.split("/");
43 const lastPart = parts[parts.length - 1]?.trim() ?? "";
44 return lastPart.length > 0 ? lastPart : trimmed;
45}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t