Unified · Split
| @@ -1,10 +1,7 @@ | |||
| 1 | import { autoAnimate } from "@formkit/auto-animate"; | 1 | import { autoAnimate } from "@formkit/auto-animate"; |
| 2 | import { useAtomValue } from "@effect/atom-react"; | 2 | import { useAtomValue } from "@effect/atom-react"; |
| 3 | import { effectiveSettled } from "@t3tools/client-runtime/state/thread-settled"; | 3 | import { effectiveSettled } from "@t3tools/client-runtime/state/thread-settled"; |
| 4 | import type { | 4 | import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/models"; |
| 5 | EnvironmentProject, | ||
| 6 | EnvironmentThreadShell, | ||
| 7 | } from "@t3tools/client-runtime/state/models"; | ||
| 8 | import { | 5 | import { |
| 9 | scopeProjectRef, | 6 | scopeProjectRef, |
| 10 | scopeThreadRef, | 7 | scopeThreadRef, |
| @@ -62,7 +59,10 @@ import { isMacPlatform } from "~/lib/utils"; · 51 unmodified lines | |||
| 62 | import { useOpenPrLink } from "../lib/openPullRequestLink"; | 59 | import { useOpenPrLink } from "../lib/openPullRequestLink"; |
| 63 | import { readLocalApi } from "../localApi"; | 60 | import { readLocalApi } from "../localApi"; |
| 64 | import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject"; | 61 | import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject"; |
| 65 | import { buildSidebarProjectSnapshots } from "../sidebarProjectGrouping"; | 62 | import { |
| 63 | buildSidebarProjectSnapshots, | ||
| 64 | type SidebarProjectSnapshot, | ||
| 65 | } from "../sidebarProjectGrouping"; | ||
| 66 | import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; | 66 | import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; |
| 67 | import { useThreadSelectionStore } from "../threadSelectionStore"; | 67 | import { useThreadSelectionStore } from "../threadSelectionStore"; |
| 68 | import { useThreadActions } from "../hooks/useThreadActions"; | 68 | import { useThreadActions } from "../hooks/useThreadActions"; |
| @@ -894,66 +894,86 @@ export default function SidebarV2() { · 825 unmodified lines | |||
| 894 | }, [clearSelection, projectScopeKey]); | 894 | }, [clearSelection, projectScopeKey]); |
| 895 | 895 | ||
| 896 | const handleRemoveProject = useCallback( | 896 | const handleRemoveProject = useCallback( |
| 897 | async (project: EnvironmentProject) => { | 897 | async (projectGroup: SidebarProjectSnapshot) => { |
| 898 | const api = readLocalApi(); | 898 | const api = readLocalApi(); |
| 899 | if (!api) return; | 899 | if (!api) return; |
| 900 | 900 | ||
| 901 | const projectThreads = threads.filter( | 901 | const memberKeys = new Set( |
| 902 | (thread) => | 902 | projectGroup.memberProjectRefs.map( |
| 903 | thread.environmentId === project.environmentId && thread.projectId === project.id, | 903 | (projectRef) => `${projectRef.environmentId}:${projectRef.projectId}`, |
| 904 | ), | ||
| 905 | ); | ||
| 906 | const projectThreads = threads.filter((thread) => | ||
| 907 | memberKeys.has(`${thread.environmentId}:${thread.projectId}`), | ||
| 904 | ); | 908 | ); |
| 905 | const confirmed = await settlePromise(() => | 909 | const confirmed = await settlePromise(() => |
| 906 | api.dialogs.confirm( | 910 | api.dialogs.confirm( |
| 907 | projectThreads.length > 0 | 911 | projectThreads.length > 0 |
| 908 | ? [ | 912 | ? [ |
| 909 | `Remove project "${project.title}" and delete its ${projectThreads.length} thread${projectThreads.length === 1 ? "" : "s"}?`, | 913 | `Remove project "${projectGroup.displayName}" and delete its ${projectThreads.length} thread${projectThreads.length === 1 ? "" : "s"}?`, |
| 910 | `Path: ${project.workspaceRoot}`, | 914 | ...(projectGroup.memberProjects.length === 1 |
| 915 | ? [`Path: ${projectGroup.workspaceRoot}`] | ||
| 916 | : [ | ||
| 917 | `This removes ${projectGroup.memberProjects.length} grouped project entries.`, | ||
| 918 | ]), | ||
| 911 | "This permanently clears conversation history for those threads.", | 919 | "This permanently clears conversation history for those threads.", |
| 912 | "This removes only the project entry, not the files on disk.", | 920 | "This removes only the project entries, not the files on disk.", |
| 913 | "This action cannot be undone.", | 921 | "This action cannot be undone.", |
| 914 | ].join("\n") | 922 | ].join("\n") |
| 915 | : [ | 923 | : [ |
| 916 | `Remove project "${project.title}"?`, | 924 | `Remove project "${projectGroup.displayName}"?`, |
| 917 | `Path: ${project.workspaceRoot}`, | 925 | ...(projectGroup.memberProjects.length === 1 |
| 918 | "This removes only the project entry, not the files on disk.", | 926 | ? [`Path: ${projectGroup.workspaceRoot}`] |
| 927 | : [ | ||
| 928 | `This removes ${projectGroup.memberProjects.length} grouped project entries.`, | ||
| 929 | ]), | ||
| 930 | "This removes only the project entries, not the files on disk.", | ||
| 919 | ].join("\n"), | 931 | ].join("\n"), |
| 920 | ), | 932 | ), |
| 921 | ); | 933 | ); |
| 922 | if (confirmed._tag === "Failure" || !confirmed.value) return; | 934 | if (confirmed._tag === "Failure" || !confirmed.value) return; |
| 923 | 935 | ||
| 924 | const projectRef = scopeProjectRef(project.environmentId, project.id); | 936 | const draftStore = useComposerDraftStore.getState(); |
| 925 | const result = await deleteProject({ | 937 | let shouldNavigate = false; |
| 926 | environmentId: project.environmentId, | 938 | for (const project of projectGroup.memberProjects) { |
| 927 | input: { | 939 | const memberThreads = projectThreads.filter( |
| 928 | projectId: project.id, | 940 | (thread) => |
| 929 | ...(projectThreads.length > 0 ? { force: true } : {}), | 941 | thread.environmentId === project.environmentId && thread.projectId === project.id, |
| 930 | }, | 942 | ); |
| 931 | }); | 943 | const projectRef = scopeProjectRef(project.environmentId, project.id); |
| 932 | if (result._tag === "Failure") { | 944 | const projectDraftThread = draftStore.getDraftThreadByProjectRef(projectRef); |
| 933 | if (!isAtomCommandInterrupted(result)) { | 945 | shouldNavigate ||= shouldNavigateAfterProjectRemoval({ |
| 934 | const error = squashAtomCommandFailure(result); | 946 | routeTarget: routeTargetRef.current, |
| 935 | toastManager.add( | 947 | projectThreads: memberThreads, |
| 936 | stackedThreadToast({ | 948 | projectDraftId: projectDraftThread?.draftId ?? null, |
| 937 | type: "error", | 949 | }); |
| 938 | title: `Failed to remove "${project.title}"`, | 950 | |
| 939 | description: error instanceof Error ? error.message : "An error occurred.", | 951 | const result = await deleteProject({ |
| 940 | }), | 952 | environmentId: project.environmentId, |
| 941 | ); | 953 | input: { |
| 954 | projectId: project.id, | ||
| 955 | ...(memberThreads.length > 0 ? { force: true } : {}), | ||
| 956 | }, | ||
| 957 | }); | ||
| 958 | if (result._tag === "Failure") { | ||
| 959 | if (!isAtomCommandInterrupted(result)) { | ||
| 960 | const error = squashAtomCommandFailure(result); | ||
| 961 | toastManager.add( | ||
| 962 | stackedThreadToast({ | ||
| 963 | type: "error", | ||
| 964 | title: `Failed to remove "${project.title}"`, | ||
| 965 | description: error instanceof Error ? error.message : "An error occurred.", | ||
| 966 | }), | ||
| 967 | ); | ||
| 968 | } | ||
| 969 | return; | ||
| 942 | } | 970 | } |
| 943 | return; | ||
| 944 | } | ||
| 945 | 971 | ||
| 946 | const draftStore = useComposerDraftStore.getState(); | 972 | if (projectDraftThread) { |
| 947 | const projectDraftThread = draftStore.getDraftThreadByProjectRef(projectRef); | 973 | draftStore.clearDraftThread(projectDraftThread.draftId); |
| 948 | const shouldNavigate = shouldNavigateAfterProjectRemoval({ | 974 | } |
| 949 | routeTarget: routeTargetRef.current, | 975 | draftStore.clearProjectDraftThreadId(projectRef); |
| 950 | projectThreads, | ||
| 951 | projectDraftId: projectDraftThread?.draftId ?? null, | ||
| 952 | }); | ||
| 953 | if (projectDraftThread) { | ||
| 954 | draftStore.clearDraftThread(projectDraftThread.draftId); | ||
| 955 | } | 976 | } |
| 956 | draftStore.clearProjectDraftThreadId(projectRef); | ||
| 957 | 977 | ||
| 958 | if (shouldNavigate) { | 978 | if (shouldNavigate) { |
| 959 | void router.navigate({ to: "/" }); | 979 | void router.navigate({ to: "/" }); |
| @@ -1650,11 +1670,11 @@ export default function SidebarV2() { · 690 unmodified lines | |||
| 1650 | cwd={project.workspaceRoot} | 1670 | cwd={project.workspaceRoot} |
| 1651 | className="size-4 shrink-0" | 1671 | className="size-4 shrink-0" |
| 1652 | /> | 1672 | /> |
| 1653 | <span className="min-w-0 truncate text-sm">{project.title}</span> | 1673 | <span className="min-w-0 truncate text-sm">{project.displayName}</span> |
| 1654 | <button | 1674 | <button |
| 1655 | type="button" | 1675 | type="button" |
| 1656 | aria-label={`Remove project ${project.title}`} | 1676 | aria-label={`Remove project ${project.displayName}`} |
| 1657 | title={`Remove ${project.title}`} | 1677 | title={`Remove ${project.displayName}`} |
| 1658 | className="ml-auto inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/55 outline-none transition-colors hover:bg-destructive/12 hover:text-destructive focus-visible:bg-destructive/12 focus-visible:text-destructive focus-visible:ring-2 focus-visible:ring-destructive/40" | 1678 | className="ml-auto inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/55 outline-none transition-colors hover:bg-destructive/12 hover:text-destructive focus-visible:bg-destructive/12 focus-visible:text-destructive focus-visible:ring-2 focus-visible:ring-destructive/40" |
| 1659 | onPointerDown={(event) => event.stopPropagation()} | 1679 | onPointerDown={(event) => event.stopPropagation()} |
| 1660 | onClick={(event) => { | 1680 | onClick={(event) => { |