diff --git a/packages/livekit/src/livestore/task-queries.ts b/packages/livekit/src/livestore/task-queries.ts index 7f6a15ee07..fdf6789a04 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,35 @@ export const makeTasksCountQuery = (cwd: 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 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, + }), + ), + }, + { + label: "tasks.activePaths.worktrees", + deps: [origin, activePathsStr], + }, + ); +}; diff --git a/packages/vscode-webui/src/components/worktree-list.tsx b/packages/vscode-webui/src/components/worktree-list.tsx index 5700c787f7..c375731eaa 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 { usePochiTabs } from "@/lib/hooks/use-pochi-tabs"; import { useWorktrees } from "@/lib/hooks/use-worktrees"; import { cn } from "@/lib/utils"; @@ -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 ? ( <> 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..ebe01e3748 --- /dev/null +++ b/packages/vscode-webui/src/lib/hooks/use-deleted-worktrees.ts @@ -0,0 +1,26 @@ +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, + activeWorktrees, + origin ?? "", + ); + }, [cwd, activeWorktrees, origin]); + const deletedWorktrees = store.useQuery(worktreeQuery); + return { deletedWorktrees }; +}