From a18dad58e0cdce217144896c2eb61a75ec80142b Mon Sep 17 00:00:00 2001 From: swellee Date: Mon, 29 Dec 2025 21:23:45 +0800 Subject: [PATCH 1/3] show deleted worktrees --- .../livekit/src/livestore/task-queries.ts | 30 ++++++++- .../src/components/worktree-list.tsx | 65 +++++++++++++++++-- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/packages/livekit/src/livestore/task-queries.ts b/packages/livekit/src/livestore/task-queries.ts index 7f6a15ee07..c7dbc940b7 100644 --- a/packages/livekit/src/livestore/task-queries.ts +++ b/packages/livekit/src/livestore/task-queries.ts @@ -1,10 +1,11 @@ +import { parseGitOriginUrl } from "@getpochi/common/git-utils"; import { Schema, queryDb, sql } from "@livestore/livestore"; import { tables } from "./default-schema"; export const makeTasksQuery = (cwd: string) => queryDb( { - query: sql`select * from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' like '${cwd}/.git/worktrees%') order by updatedAt desc`, + query: sql`select * from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' = '${cwd}') order by updatedAt desc`, schema: Schema.Array(tables.tasks.rowSchema), }, { @@ -21,7 +22,7 @@ export const makeTasksQuery = (cwd: string) => export const makeTasksWithLimitQuery = (cwd: string, limit: number) => { return queryDb( { - query: sql`select * from tasks where parentId is null and cwd = '${cwd}' order by updatedAt desc limit ${limit}`, + query: sql`select * from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' = '${cwd}') order by updatedAt desc limit ${limit}`, schema: Schema.Array(tables.tasks.rowSchema), }, { @@ -38,7 +39,7 @@ export const makeTasksWithLimitQuery = (cwd: string, limit: number) => { export const makeTasksCountQuery = (cwd: string) => { return queryDb( { - query: sql`select COUNT(*) as count from tasks where parentId is null and cwd = '${cwd}'`, + query: sql`select COUNT(*) as count from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' = '${cwd}')`, schema: Schema.Array(Schema.Struct({ count: Schema.Number })), }, { @@ -47,3 +48,26 @@ export const makeTasksCountQuery = (cwd: string) => { }, ); }; + +export const makeNonCwdWorktreesQuery = (cwd: string, origin?: string) => { + const originFilter = origin + ? `and git->>'$.origin' = '${parseGitOriginUrl(origin)?.webUrl}'` + : ""; + + return queryDb( + { + query: sql`select distinct git->>'$.worktree.gitdir' as path, git->>'$.branch' as branch, cwd from tasks where parentId is null and cwd != '${cwd}' ${originFilter} and git->>'$.worktree.gitdir' is not null`, + schema: Schema.Array( + Schema.Struct({ + path: Schema.String, + branch: Schema.String, + cwd: Schema.String, + }), + ), + }, + { + label: "tasks.cwd.worktrees", + deps: [cwd, origin], + }, + ); +}; diff --git a/packages/vscode-webui/src/components/worktree-list.tsx b/packages/vscode-webui/src/components/worktree-list.tsx index e44811c175..31d8ef254b 100644 --- a/packages/vscode-webui/src/components/worktree-list.tsx +++ b/packages/vscode-webui/src/components/worktree-list.tsx @@ -23,6 +23,7 @@ import { } from "@/components/ui/tooltip"; import { useSelectedModels } from "@/features/settings"; import { useCurrentWorkspace } from "@/lib/hooks/use-current-workspace"; +import { useDeletedWorktrees } from "@/lib/hooks/use-deleted-worktrees"; import { usePaginatedTasks } from "@/lib/hooks/use-paginated-tasks"; import { usePochiTabs } from "@/lib/hooks/use-pochi-tabs"; import { useWorktrees } from "@/lib/hooks/use-worktrees"; @@ -65,7 +66,7 @@ interface WorktreeGroup { isMain: boolean; createdAt?: number; branch?: string; - data: GitWorktree["data"]; + data?: GitWorktree["data"]; } export function WorktreeList({ @@ -77,6 +78,7 @@ export function WorktreeList({ deletingWorktreePaths: Set; onDeleteWorktree: (worktreePath: string) => void; }) { + const { t } = useTranslation(); const { data: currentWorkspace, isLoading: isLoadingCurrentWorkspace } = useCurrentWorkspace(); const { @@ -85,6 +87,7 @@ export function WorktreeList({ gitOriginUrl, isLoading: isLoadingWorktrees, } = useWorktrees(); + const [showDeleted, setShowDeleted] = useState(false); const groups = useMemo(() => { if (isLoadingWorktrees || isLoadingCurrentWorkspace) { @@ -147,11 +150,31 @@ export function WorktreeList({ return groups.filter((x) => !deletingWorktreePaths.has(x.path)); }, [groups, deletingWorktreePaths]); + const { deletedWorktrees } = useDeletedWorktrees({ + cwd, + origin: gitOriginUrl, + activeWorktrees: worktrees, + }); + const deletedGroups = useMemo(() => { + return R.pipe( + deletedWorktrees, + R.map((wt): WorktreeGroup => { + const name = getWorktreeNameFromWorktreePath(wt.path) || "unknown"; + + return { + path: wt.path, + createdAt: 0, + name, + isMain: false, + branch: wt.branch, + }; + }), + ); + }, [deletedWorktrees]); return (
{optimisticGroups.map((group) => ( ))} + {deletedGroups.length > 0 && ( + <> +
+
+ +
+
+ + {showDeleted && + deletedGroups.map((group) => ( + + ))} + + )}
); } @@ -168,9 +223,10 @@ function WorktreeSection({ onDeleteGroup, gh, gitOriginUrl, + isDeleted, }: { group: WorktreeGroup; - isLoadingWorktrees: boolean; + isDeleted?: boolean; onDeleteGroup?: (worktreePath: string) => void; gh?: { installed: boolean; authorized: boolean }; gitOriginUrl?: string | null; @@ -222,7 +278,7 @@ function WorktreeSection({ {prefixWorktreeName(group.name)} -
+
{pullRequest ? ( <> From 9288d3a96f32509506f74fafa2cafb6bdec802be Mon Sep 17 00:00:00 2001 From: swellee Date: Mon, 29 Dec 2025 21:24:53 +0800 Subject: [PATCH 2/3] add use-deleted-worktree hook that enable group deleted worktrees from task table --- .../src/lib/hooks/use-deleted-worktrees.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts diff --git a/packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts b/packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts new file mode 100644 index 0000000000..be5763855f --- /dev/null +++ b/packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts @@ -0,0 +1,29 @@ +import { taskCatalog } from "@getpochi/livekit"; +import { useStore } from "@livestore/react"; +import { useMemo } from "react"; + +interface DeletedWorktreesOptions { + cwd: string; + origin?: string | null; + activeWorktrees?: { path: string }[]; +} +export function useDeletedWorktrees({ + cwd, + origin, + activeWorktrees, +}: DeletedWorktreesOptions) { + const { store } = useStore(); + + const worktreeQuery = useMemo(() => { + return taskCatalog.queries.makeNonCwdWorktreesQuery(cwd, origin ?? ""); + }, [cwd, origin]); + const worktrees = store.useQuery(worktreeQuery); + const deletedWorktrees = useMemo( + () => + worktrees.filter( + (wt) => !activeWorktrees?.some((active) => active.path === wt.path), + ), + [worktrees, activeWorktrees], + ); + return { deletedWorktrees }; +} From c7b30f403bba7ed163650f70d6a3a15b7bcf4e83 Mon Sep 17 00:00:00 2001 From: swellee Date: Sat, 3 Jan 2026 17:14:28 +0800 Subject: [PATCH 3/3] filter non-cwd-worktrees in db level --- .../livekit/src/livestore/task-queries.ts | 19 ++++++++++++++----- .../src/lib/hooks/use-deleted-worktrees.ts | 17 +++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/livekit/src/livestore/task-queries.ts b/packages/livekit/src/livestore/task-queries.ts index c7dbc940b7..fdf6789a04 100644 --- a/packages/livekit/src/livestore/task-queries.ts +++ b/packages/livekit/src/livestore/task-queries.ts @@ -49,25 +49,34 @@ export const makeTasksCountQuery = (cwd: string) => { ); }; -export const makeNonCwdWorktreesQuery = (cwd: string, origin?: string) => { +export const makeNonCwdWorktreesQuery = ( + cwd: string, + activeWorktrees?: { path: string }[], + origin?: string, +) => { + const activePathsStr = activeWorktrees?.length + ? activeWorktrees.map((p) => `'${p.path}'`).join(", ") + : ""; + const worktreeFilter = activePathsStr + ? `and cwd not in (${activePathsStr})` + : ""; const originFilter = origin ? `and git->>'$.origin' = '${parseGitOriginUrl(origin)?.webUrl}'` : ""; return queryDb( { - query: sql`select distinct git->>'$.worktree.gitdir' as path, git->>'$.branch' as branch, cwd from tasks where parentId is null and cwd != '${cwd}' ${originFilter} and git->>'$.worktree.gitdir' is not null`, + query: sql`select distinct git->>'$.worktree.gitdir' as path, git->>'$.branch' as branch from tasks where parentId is null and cwd != '${cwd}' ${worktreeFilter} ${originFilter} and git->>'$.worktree.gitdir' is not null`, schema: Schema.Array( Schema.Struct({ path: Schema.String, branch: Schema.String, - cwd: Schema.String, }), ), }, { - label: "tasks.cwd.worktrees", - deps: [cwd, origin], + label: "tasks.activePaths.worktrees", + deps: [origin, activePathsStr], }, ); }; diff --git a/packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts b/packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts index be5763855f..ebe01e3748 100644 --- a/packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts +++ b/packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts @@ -15,15 +15,12 @@ export function useDeletedWorktrees({ const { store } = useStore(); const worktreeQuery = useMemo(() => { - return taskCatalog.queries.makeNonCwdWorktreesQuery(cwd, origin ?? ""); - }, [cwd, origin]); - const worktrees = store.useQuery(worktreeQuery); - const deletedWorktrees = useMemo( - () => - worktrees.filter( - (wt) => !activeWorktrees?.some((active) => active.path === wt.path), - ), - [worktrees, activeWorktrees], - ); + return taskCatalog.queries.makeNonCwdWorktreesQuery( + cwd, + activeWorktrees, + origin ?? "", + ); + }, [cwd, activeWorktrees, origin]); + const deletedWorktrees = store.useQuery(worktreeQuery); return { deletedWorktrees }; }