| ... | @@ -1,10 +1,7 @@ | ... | @@ -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, |
| ... | @@ -61,7 +58,12 @@ import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../termina | ... | @@ -61,7 +58,12 @@ import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../termina |
| 61 | import { isMacPlatform } from "~/lib/utils"; | 58 | import { isMacPlatform } from "~/lib/utils"; |
| 62 | import { useOpenPrLink } from "../lib/openPullRequestLink"; | 59 | import { useOpenPrLink } from "../lib/openPullRequestLink"; |
| 63 | import { readLocalApi } from "../localApi"; | 60 | import { readLocalApi } from "../localApi"; |
| 64 | import { useUiStateStore } from "../uiStateStore"; | 61 | import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject"; |
| | 62 | import { |
| | 63 | buildSidebarProjectSnapshots, |
| | 64 | type SidebarProjectSnapshot, |
| | 65 | } from "../sidebarProjectGrouping"; |
| | 66 | import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; |
| 65 | import { useThreadSelectionStore } from "../threadSelectionStore"; | 67 | import { useThreadSelectionStore } from "../threadSelectionStore"; |
| 66 | import { useThreadActions } from "../hooks/useThreadActions"; | 68 | import { useThreadActions } from "../hooks/useThreadActions"; |
| 67 | import { useHandleNewThread } from "../hooks/useHandleNewThread"; | 69 | import { useHandleNewThread } from "../hooks/useHandleNewThread"; |
| ... | @@ -84,9 +86,11 @@ import { | ... | @@ -84,9 +86,11 @@ import { |
| 84 | firstValidTimestampMs, | 86 | firstValidTimestampMs, |
| 85 | hasUnseenCompletion, | 87 | hasUnseenCompletion, |
| 86 | isTrailingDoubleClick, | 88 | isTrailingDoubleClick, |
| | 89 | orderItemsByPreferredIds, |
| 87 | resolveAdjacentThreadId, | 90 | resolveAdjacentThreadId, |
| 88 | resolveSidebarV2Status, | 91 | resolveSidebarV2Status, |
| 89 | shouldNavigateAfterProjectRemoval, | 92 | shouldNavigateAfterProjectRemoval, |
| | 93 | sortLogicalProjectsForSidebar, |
| 90 | sortThreadsForSidebarV2, | 94 | sortThreadsForSidebarV2, |
| 91 | } from "./Sidebar.logic"; | 95 | } from "./Sidebar.logic"; |
| 92 | import { resolveLocalCheckoutBranchMismatch } from "./BranchToolbar.logic"; | 96 | import { resolveLocalCheckoutBranchMismatch } from "./BranchToolbar.logic"; |
| ... | @@ -716,12 +720,15 @@ function latestTurnDiff( | ... | @@ -716,12 +720,15 @@ function latestTurnDiff( |
| 716 | | 720 | |
| 717 | export default function SidebarV2() { | 721 | export default function SidebarV2() { |
| 718 | const projects = useProjects(); | 722 | const projects = useProjects(); |
| | 723 | const projectOrder = useUiStateStore((store) => store.projectOrder); |
| 719 | const threads = useThreadShells(); | 724 | const threads = useThreadShells(); |
| 720 | const router = useRouter(); | 725 | const router = useRouter(); |
| 721 | const { isMobile, setOpenMobile } = useSidebar(); | 726 | const { isMobile, setOpenMobile } = useSidebar(); |
| 722 | const keybindings = useAtomValue(primaryServerKeybindingsAtom); | 727 | const keybindings = useAtomValue(primaryServerKeybindingsAtom); |
| 723 | const autoSettleAfterDays = useClientSettings((s) => s.sidebarAutoSettleAfterDays); | 728 | const autoSettleAfterDays = useClientSettings((s) => s.sidebarAutoSettleAfterDays); |
| 724 | const confirmThreadDelete = useClientSettings((s) => s.confirmThreadDelete); | 729 | const confirmThreadDelete = useClientSettings((s) => s.confirmThreadDelete); |
| | 730 | const sidebarProjectSortOrder = useClientSettings((s) => s.sidebarProjectSortOrder); |
| | 731 | const projectGroupingSettings = useClientSettings(selectProjectGroupingSettings); |
| 725 | const { settleThread, unsettleThread, deleteThread } = useThreadActions(); | 732 | const { settleThread, unsettleThread, deleteThread } = useThreadActions(); |
| 726 | const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, { | 733 | const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, { |
| 727 | reportFailure: false, | 734 | reportFailure: false, |
| ... | @@ -762,6 +769,40 @@ export default function SidebarV2() { | ... | @@ -762,6 +769,40 @@ export default function SidebarV2() { |
| 762 | ), | 769 | ), |
| 763 | [environments], | 770 | [environments], |
| 764 | ); | 771 | ); |
| | 772 | const orderedProjects = useMemo( |
| | 773 | () => |
| | 774 | orderItemsByPreferredIds({ |
| | 775 | items: projects, |
| | 776 | preferredIds: projectOrder, |
| | 777 | getId: getProjectOrderKey, |
| | 778 | getPreferenceIds: (project) => [ |
| | 779 | getProjectOrderKey(project), |
| | 780 | legacyProjectCwdPreferenceKey(project.workspaceRoot), |
| | 781 | ], |
| | 782 | }), |
| | 783 | [projectOrder, projects], |
| | 784 | ); |
| | 785 | const unsortedProjectGroups = useMemo( |
| | 786 | () => |
| | 787 | buildSidebarProjectSnapshots({ |
| | 788 | projects: sidebarProjectSortOrder === "manual" ? orderedProjects : projects, |
| | 789 | settings: projectGroupingSettings, |
| | 790 | primaryEnvironmentId, |
| | 791 | resolveEnvironmentLabel: (environmentId) => environmentLabelById.get(environmentId) ?? null, |
| | 792 | }), |
| | 793 | [ |
| | 794 | environmentLabelById, |
| | 795 | orderedProjects, |
| | 796 | primaryEnvironmentId, |
| | 797 | projectGroupingSettings, |
| | 798 | projects, |
| | 799 | sidebarProjectSortOrder, |
| | 800 | ], |
| | 801 | ); |
| | 802 | const projectGroups = useMemo( |
| | 803 | () => sortLogicalProjectsForSidebar(unsortedProjectGroups, threads, sidebarProjectSortOrder), |
| | 804 | [sidebarProjectSortOrder, threads, unsortedProjectGroups], |
| | 805 | ); |
| 765 | const serverProviders = useAtomValue(primaryServerProvidersAtom); | 806 | const serverProviders = useAtomValue(primaryServerProvidersAtom); |
| 766 | const providerEntryByInstanceId = useMemo( | 807 | const providerEntryByInstanceId = useMemo( |
| 767 | () => | 808 | () => |
| ... | @@ -823,23 +864,29 @@ export default function SidebarV2() { | ... | @@ -823,23 +864,29 @@ export default function SidebarV2() { |
| 823 | // Project scope: one menu above the list. Scoping filters the list without | 864 | // Project scope: one menu above the list. Scoping filters the list without |
| 824 | // making the header width depend on the number or length of project names. | 865 | // making the header width depend on the number or length of project names. |
| 825 | const [projectScopeKey, setProjectScopeKey] = useState<string | null>(null); | 866 | const [projectScopeKey, setProjectScopeKey] = useState<string | null>(null); |
| 826 | const scopedProject = useMemo( | 867 | const scopedProjectGroup = useMemo( |
| 827 | () => | 868 | () => |
| 828 | projectScopeKey === null | 869 | projectScopeKey === null |
| 829 | ? null | 870 | ? null |
| 830 | : (projects.find( | 871 | : (projectGroups.find((project) => project.projectKey === projectScopeKey) ?? null), |
| 831 | (project) => `${project.environmentId}:${project.id}` === projectScopeKey, | 872 | [projectGroups, projectScopeKey], |
| 832 | ) ?? null), | 873 | ); |
| 833 | [projectScopeKey, projects], | 874 | const scopedProjectKeys = useMemo( |
| | 875 | () => |
| | 876 | scopedProjectGroup === null |
| | 877 | ? null |
| | 878 | : new Set( |
| | 879 | scopedProjectGroup.memberProjectRefs.map( |
| | 880 | (projectRef) => `${projectRef.environmentId}:${projectRef.projectId}`, |
| | 881 | ), |
| | 882 | ), |
| | 883 | [scopedProjectGroup], |
| 834 | ); | 884 | ); |
| 835 | useEffect(() => { | 885 | useEffect(() => { |
| 836 | if ( | 886 | if (projectScopeKey !== null && scopedProjectGroup === null) { |
| 837 | projectScopeKey !== null && | | |
| 838 | !projects.some((project) => `${project.environmentId}:${project.id}` === projectScopeKey) | | |
| 839 | ) { | | |
| 840 | setProjectScopeKey(null); | 887 | setProjectScopeKey(null); |
| 841 | } | 888 | } |
| 842 | }, [projectScopeKey, projects]); | 889 | }, [projectScopeKey, scopedProjectGroup]); |
| 843 | // Scope flips drop the selection: rows selected under the old scope may be | 890 | // Scope flips drop the selection: rows selected under the old scope may be |
| 844 | // hidden now, and bulk actions must never count or touch invisible rows. | 891 | // hidden now, and bulk actions must never count or touch invisible rows. |
| 845 | useEffect(() => { | 892 | useEffect(() => { |
| ... | @@ -847,66 +894,90 @@ export default function SidebarV2() { | ... | @@ -847,66 +894,90 @@ export default function SidebarV2() { |
| 847 | }, [clearSelection, projectScopeKey]); | 894 | }, [clearSelection, projectScopeKey]); |
| 848 | | 895 | |
| 849 | const handleRemoveProject = useCallback( | 896 | const handleRemoveProject = useCallback( |
| 850 | async (project: EnvironmentProject) => { | 897 | async (projectGroup: SidebarProjectSnapshot) => { |
| 851 | const api = readLocalApi(); | 898 | const api = readLocalApi(); |
| 852 | if (!api) return; | 899 | if (!api) return; |
| 853 | | 900 | |
| 854 | const projectThreads = threads.filter( | 901 | const memberKeys = new Set( |
| 855 | (thread) => | 902 | projectGroup.memberProjectRefs.map( |
| 856 | 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}`), |
| 857 | ); | 908 | ); |
| 858 | const confirmed = await settlePromise(() => | 909 | const confirmed = await settlePromise(() => |
| 859 | api.dialogs.confirm( | 910 | api.dialogs.confirm( |
| 860 | projectThreads.length > 0 | 911 | projectThreads.length > 0 |
| 861 | ? [ | 912 | ? [ |
| 862 | `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"}?`, |
| 863 | `Path: ${project.workspaceRoot}`, | 914 | ...(projectGroup.memberProjects.length === 1 |
| | 915 | ? [`Path: ${projectGroup.workspaceRoot}`] |
| | 916 | : [ |
| | 917 | `This removes ${projectGroup.memberProjects.length} grouped project entries.`, |
| | 918 | ]), |
| 864 | "This permanently clears conversation history for those threads.", | 919 | "This permanently clears conversation history for those threads.", |
| 865 | "This removes only the project entry, not the files on disk.", | 920 | "This removes only the project entries, not the files on disk.", |
| 866 | "This action cannot be undone.", | 921 | "This action cannot be undone.", |
| 867 | ].join("\n") | 922 | ].join("\n") |
| 868 | : [ | 923 | : [ |
| 869 | `Remove project "${project.title}"?`, | 924 | `Remove project "${projectGroup.displayName}"?`, |
| 870 | `Path: ${project.workspaceRoot}`, | 925 | ...(projectGroup.memberProjects.length === 1 |
| 871 | "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.", |
| 872 | ].join("\n"), | 931 | ].join("\n"), |
| 873 | ), | 932 | ), |
| 874 | ); | 933 | ); |
| 875 | if (confirmed._tag === "Failure" || !confirmed.value) return; | 934 | if (confirmed._tag === "Failure" || !confirmed.value) return; |
| 876 | | 935 | |
| 877 | const projectRef = scopeProjectRef(project.environmentId, project.id); | 936 | const draftStore = useComposerDraftStore.getState(); |
| 878 | const result = await deleteProject({ | 937 | let shouldNavigate = false; |
| 879 | environmentId: project.environmentId, | 938 | for (const project of projectGroup.memberProjects) { |
| 880 | input: { | 939 | const memberThreads = projectThreads.filter( |
| 881 | projectId: project.id, | 940 | (thread) => |
| 882 | ...(projectThreads.length > 0 ? { force: true } : {}), | 941 | thread.environmentId === project.environmentId && thread.projectId === project.id, |
| 883 | }, | 942 | ); |
| 884 | }); | 943 | const projectRef = scopeProjectRef(project.environmentId, project.id); |
| 885 | if (result._tag === "Failure") { | 944 | const projectDraftThread = draftStore.getDraftThreadByProjectRef(projectRef); |
| 886 | if (!isAtomCommandInterrupted(result)) { | 945 | const memberRemovalNeedsNavigation = shouldNavigateAfterProjectRemoval({ |
| 887 | const error = squashAtomCommandFailure(result); | 946 | routeTarget: routeTargetRef.current, |
| 888 | toastManager.add( | 947 | projectThreads: memberThreads, |
| 889 | stackedThreadToast({ | 948 | projectDraftId: projectDraftThread?.draftId ?? null, |
| 890 | type: "error", | 949 | }); |
| 891 | title: `Failed to remove "${project.title}"`, | 950 | |
| 892 | description: error instanceof Error ? error.message : "An error occurred.", | 951 | const result = await deleteProject({ |
| 893 | }), | 952 | environmentId: project.environmentId, |
| 894 | ); | 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 | if (shouldNavigate) { |
| | 970 | void router.navigate({ to: "/" }); |
| | 971 | } |
| | 972 | return; |
| 895 | } | 973 | } |
| 896 | return; | | |
| 897 | } | | |
| 898 | | 974 | |
| 899 | const draftStore = useComposerDraftStore.getState(); | 975 | shouldNavigate ||= memberRemovalNeedsNavigation; |
| 900 | const projectDraftThread = draftStore.getDraftThreadByProjectRef(projectRef); | 976 | if (projectDraftThread) { |
| 901 | const shouldNavigate = shouldNavigateAfterProjectRemoval({ | 977 | draftStore.clearDraftThread(projectDraftThread.draftId); |
| 902 | routeTarget: routeTargetRef.current, | 978 | } |
| 903 | projectThreads, | 979 | draftStore.clearProjectDraftThreadId(projectRef); |
| 904 | projectDraftId: projectDraftThread?.draftId ?? null, | | |
| 905 | }); | | |
| 906 | if (projectDraftThread) { | | |
| 907 | draftStore.clearDraftThread(projectDraftThread.draftId); | | |
| 908 | } | 980 | } |
| 909 | draftStore.clearProjectDraftThreadId(projectRef); | | |
| 910 | | 981 | |
| 911 | if (shouldNavigate) { | 982 | if (shouldNavigate) { |
| 912 | void router.navigate({ to: "/" }); | 983 | void router.navigate({ to: "/" }); |
| ... | @@ -925,9 +996,8 @@ export default function SidebarV2() { | ... | @@ -925,9 +996,8 @@ export default function SidebarV2() { |
| 925 | const visible = threads.filter( | 996 | const visible = threads.filter( |
| 926 | (thread) => | 997 | (thread) => |
| 927 | thread.archivedAt === null && | 998 | thread.archivedAt === null && |
| 928 | (scopedProject === null || | 999 | (scopedProjectKeys === null || |
| 929 | (thread.environmentId === scopedProject.environmentId && | 1000 | scopedProjectKeys.has(`${thread.environmentId}:${thread.projectId}`)), |
| 930 | thread.projectId === scopedProject.id)), | | |
| 931 | ); | 1001 | ); |
| 932 | const active: EnvironmentThreadShell[] = []; | 1002 | const active: EnvironmentThreadShell[] = []; |
| 933 | const settled: EnvironmentThreadShell[] = []; | 1003 | const settled: EnvironmentThreadShell[] = []; |
| ... | @@ -961,7 +1031,7 @@ export default function SidebarV2() { | ... | @@ -961,7 +1031,7 @@ export default function SidebarV2() { |
| 961 | autoSettleAfterDays, | 1031 | autoSettleAfterDays, |
| 962 | changeRequestStateByKey, | 1032 | changeRequestStateByKey, |
| 963 | nowMinute, | 1033 | nowMinute, |
| 964 | scopedProject, | 1034 | scopedProjectKeys, |
| 965 | serverConfigs, | 1035 | serverConfigs, |
| 966 | threads, | 1036 | threads, |
| 967 | ]); | 1037 | ]); |
| ... | @@ -1479,7 +1549,7 @@ export default function SidebarV2() { | ... | @@ -1479,7 +1549,7 @@ export default function SidebarV2() { |
| 1479 | // for multi-project setups. | 1549 | // for multi-project setups. |
| 1480 | const handleNewThreadClick = useCallback(() => { | 1550 | const handleNewThreadClick = useCallback(() => { |
| 1481 | // One project: nothing to pick, create immediately. | 1551 | // One project: nothing to pick, create immediately. |
| 1482 | if (projects.length <= 1) { | 1552 | if (projectGroups.length <= 1) { |
| 1483 | if (isMobile) setOpenMobile(false); | 1553 | if (isMobile) setOpenMobile(false); |
| 1484 | void startNewThreadFromContext({ | 1554 | void startNewThreadFromContext({ |
| 1485 | activeDraftThread: newThreadContext.activeDraftThread, | 1555 | activeDraftThread: newThreadContext.activeDraftThread, |
| ... | @@ -1491,7 +1561,7 @@ export default function SidebarV2() { | ... | @@ -1491,7 +1561,7 @@ export default function SidebarV2() { |
| 1491 | } | 1561 | } |
| 1492 | if (isMobile) setOpenMobile(false); | 1562 | if (isMobile) setOpenMobile(false); |
| 1493 | openCommandPalette({ open: "new-thread-in" }); | 1563 | openCommandPalette({ open: "new-thread-in" }); |
| 1494 | }, [isMobile, newThreadContext, projects.length, setOpenMobile]); | 1564 | }, [isMobile, newThreadContext, projectGroups.length, setOpenMobile]); |
| 1495 | | 1565 | |
| 1496 | const commandPaletteShortcutLabel = shortcutLabelForCommand(keybindings, "commandPalette.toggle"); | 1566 | const commandPaletteShortcutLabel = shortcutLabelForCommand(keybindings, "commandPalette.toggle"); |
| 1497 | // Same resolution as v1: prefer the local-thread binding, fall back to | 1567 | // Same resolution as v1: prefer the local-thread binding, fall back to |
| ... | @@ -1553,7 +1623,7 @@ export default function SidebarV2() { | ... | @@ -1553,7 +1623,7 @@ export default function SidebarV2() { |
| 1553 | </div> | 1623 | </div> |
| 1554 | </div> | 1624 | </div> |
| 1555 | </SidebarGroup> | 1625 | </SidebarGroup> |
| 1556 | {projects.length > 0 ? ( | 1626 | {projectGroups.length > 0 ? ( |
| 1557 | <SidebarGroup className="px-2 pb-2 pt-0"> | 1627 | <SidebarGroup className="px-2 pb-2 pt-0"> |
| 1558 | <div className="flex items-center gap-1"> | 1628 | <div className="flex items-center gap-1"> |
| 1559 | <Menu> | 1629 | <Menu> |
| ... | @@ -1561,17 +1631,17 @@ export default function SidebarV2() { | ... | @@ -1561,17 +1631,17 @@ export default function SidebarV2() { |
| 1561 | aria-label="Filter threads by project" | 1631 | aria-label="Filter threads by project" |
| 1562 | className="flex h-8 min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-md px-2 text-left text-sm font-medium text-sidebar-muted-foreground outline-none hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-sidebar" | 1632 | className="flex h-8 min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-md px-2 text-left text-sm font-medium text-sidebar-muted-foreground outline-none hover:bg-sidebar-row-hover hover:text-sidebar-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-sidebar" |
| 1563 | > | 1633 | > |
| 1564 | {scopedProject ? ( | 1634 | {scopedProjectGroup ? ( |
| 1565 | <ProjectFavicon | 1635 | <ProjectFavicon |
| 1566 | environmentId={scopedProject.environmentId} | 1636 | environmentId={scopedProjectGroup.environmentId} |
| 1567 | cwd={scopedProject.workspaceRoot} | 1637 | cwd={scopedProjectGroup.workspaceRoot} |
| 1568 | className="size-4 shrink-0" | 1638 | className="size-4 shrink-0" |
| 1569 | /> | 1639 | /> |
| 1570 | ) : ( | 1640 | ) : ( |
| 1571 | <FolderIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" /> | 1641 | <FolderIcon className="size-4 shrink-0 text-sidebar-muted-foreground/80" /> |
| 1572 | )} | 1642 | )} |
| 1573 | <span className="min-w-0 flex-1 truncate"> | 1643 | <span className="min-w-0 flex-1 truncate"> |
| 1574 | {scopedProject?.title ?? "All projects"} | 1644 | {scopedProjectGroup?.displayName ?? "All projects"} |
| 1575 | </span> | 1645 | </span> |
| 1576 | <ChevronDownIcon className="size-4 shrink-0 text-sidebar-muted-foreground/70" /> | 1646 | <ChevronDownIcon className="size-4 shrink-0 text-sidebar-muted-foreground/70" /> |
| 1577 | </MenuTrigger> | 1647 | </MenuTrigger> |
| ... | @@ -1590,8 +1660,8 @@ export default function SidebarV2() { | ... | @@ -1590,8 +1660,8 @@ export default function SidebarV2() { |
| 1590 | <FolderIcon className="size-4 shrink-0" /> | 1660 | <FolderIcon className="size-4 shrink-0" /> |
| 1591 | <span className="min-w-0 truncate text-sm">All projects</span> | 1661 | <span className="min-w-0 truncate text-sm">All projects</span> |
| 1592 | </MenuRadioItem> | 1662 | </MenuRadioItem> |
| 1593 | {projects.map((project) => { | 1663 | {projectGroups.map((project) => { |
| 1594 | const scopeKey = `${project.environmentId}:${project.id}`; | 1664 | const scopeKey = project.projectKey; |
| 1595 | return ( | 1665 | return ( |
| 1596 | <MenuRadioItem | 1666 | <MenuRadioItem |
| 1597 | key={scopeKey} | 1667 | key={scopeKey} |
| ... | @@ -1604,11 +1674,11 @@ export default function SidebarV2() { | ... | @@ -1604,11 +1674,11 @@ export default function SidebarV2() { |
| 1604 | cwd={project.workspaceRoot} | 1674 | cwd={project.workspaceRoot} |
| 1605 | className="size-4 shrink-0" | 1675 | className="size-4 shrink-0" |
| 1606 | /> | 1676 | /> |
| 1607 | <span className="min-w-0 truncate text-sm">{project.title}</span> | 1677 | <span className="min-w-0 truncate text-sm">{project.displayName}</span> |
| 1608 | <button | 1678 | <button |
| 1609 | type="button" | 1679 | type="button" |
| 1610 | aria-label={`Remove project ${project.title}`} | 1680 | aria-label={`Remove project ${project.displayName}`} |
| 1611 | title={`Remove ${project.title}`} | 1681 | title={`Remove ${project.displayName}`} |
| 1612 | 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" | 1682 | 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" |
| 1613 | onPointerDown={(event) => event.stopPropagation()} | 1683 | onPointerDown={(event) => event.stopPropagation()} |
| 1614 | onClick={(event) => { | 1684 | onClick={(event) => { |
| ... | @@ -1766,8 +1836,8 @@ export default function SidebarV2() { | ... | @@ -1766,8 +1836,8 @@ export default function SidebarV2() { |
| 1766 | Add project | 1836 | Add project |
| 1767 | </button> | 1837 | </button> |
| 1768 | </> | 1838 | </> |
| 1769 | ) : scopedProject ? ( | 1839 | ) : scopedProjectGroup ? ( |
| 1770 | `No threads in ${scopedProject.title} yet` | 1840 | `No threads in ${scopedProjectGroup.displayName} yet` |
| 1771 | ) : ( | 1841 | ) : ( |
| 1772 | "No threads yet" | 1842 | "No threads yet" |
| 1773 | )} | 1843 | )} |