Unified · Split
| @@ -26,6 +26,7 @@ import { · 25 unmodified lines | |||
| 26 | shouldNavigateAfterProjectRemoval, | 26 | shouldNavigateAfterProjectRemoval, |
| 27 | shouldClearThreadSelectionOnMouseDown, | 27 | shouldClearThreadSelectionOnMouseDown, |
| 28 | sortLogicalProjectsForSidebar, | 28 | sortLogicalProjectsForSidebar, |
| 29 | sortSidebarV2ProjectGroups, | ||
| 29 | sortSettledThreadsForSidebarV2, | 30 | sortSettledThreadsForSidebarV2, |
| 30 | sortThreadsForSidebarV2, | 31 | sortThreadsForSidebarV2, |
| 31 | sortScopedProjectsForSidebar, | 32 | sortScopedProjectsForSidebar, |
| @@ -1502,3 +1503,51 @@ describe("sortLogicalProjectsForSidebar", () => { · 1470 unmodified lines | |||
| 1502 | ).toEqual(["logical-newer", "logical-older"]); | 1503 | ).toEqual(["logical-newer", "logical-older"]); |
| 1503 | }); | 1504 | }); |
| 1504 | }); | 1505 | }); |
| 1506 | |||
| 1507 | describe("sortSidebarV2ProjectGroups", () => { | ||
| 1508 | it("does not let a hidden subagent thread reorder projects", () => { | ||
| 1509 | const olderProjectId = ProjectId.make("project-older"); | ||
| 1510 | const newerProjectId = ProjectId.make("project-newer"); | ||
| 1511 | const olderRootThreadId = ThreadId.make("thread-older-root"); | ||
| 1512 | const projects = [ | ||
| 1513 | { | ||
| 1514 | ...makeProject({ id: olderProjectId, title: "A older project" }), | ||
| 1515 | projectKey: "logical-older", | ||
| 1516 | memberProjectRefs: [{ environmentId: localEnvironmentId, projectId: olderProjectId }], | ||
| 1517 | }, | ||
| 1518 | { | ||
| 1519 | ...makeProject({ id: newerProjectId, title: "Z newer project" }), | ||
| 1520 | projectKey: "logical-newer", | ||
| 1521 | memberProjectRefs: [{ environmentId: localEnvironmentId, projectId: newerProjectId }], | ||
| 1522 | }, | ||
| 1523 | ]; | ||
| 1524 | const threads = [ | ||
| 1525 | makeThread({ | ||
| 1526 | id: olderRootThreadId, | ||
| 1527 | projectId: olderProjectId, | ||
| 1528 | updatedAt: "2026-03-09T10:01:00.000Z", | ||
| 1529 | }), | ||
| 1530 | makeThread({ | ||
| 1531 | id: ThreadId.make("thread-newer-root"), | ||
| 1532 | projectId: newerProjectId, | ||
| 1533 | updatedAt: "2026-03-09T10:05:00.000Z", | ||
| 1534 | }), | ||
| 1535 | makeThread({ | ||
| 1536 | id: ThreadId.make("thread-hidden-subagent"), | ||
| 1537 | projectId: olderProjectId, | ||
| 1538 | updatedAt: "2026-03-09T10:10:00.000Z", | ||
| 1539 | lineage: { | ||
| 1540 | rootThreadId: olderRootThreadId, | ||
| 1541 | parentThreadId: olderRootThreadId, | ||
| 1542 | relationshipToParent: "subagent", | ||
| 1543 | }, | ||
| 1544 | }), | ||
| 1545 | ]; | ||
| 1546 | |||
| 1547 | expect( | ||
| 1548 | sortSidebarV2ProjectGroups(projects, threads, "updated_at").map( | ||
| 1549 | (project) => project.projectKey, | ||
| 1550 | ), | ||
| 1551 | ).toEqual(["logical-newer", "logical-older"]); | ||
| 1552 | }); | ||
| 1553 | }); | ||
| @@ -97,7 +97,10 @@ export function isSidebarSubagentThread(thread: Pick<SidebarThreadSummary, "line · 96 unmodified lines | |||
| 97 | } | 97 | } |
| 98 | 98 | ||
| 99 | export function filterSidebarV2VisibleThreads< | 99 | export function filterSidebarV2VisibleThreads< |
| 100 | T extends Pick<SidebarThreadSummary, "archivedAt" | "environmentId" | "lineage" | "projectId">, | 100 | T extends Pick<SidebarThreadSummary, "archivedAt" | "lineage"> & { |
| 101 | environmentId: string; | ||
| 102 | projectId: string; | ||
| 103 | }, | ||
| 101 | >(threads: readonly T[], scopedProjectKeys: ReadonlySet<string> | null): T[] { | 104 | >(threads: readonly T[], scopedProjectKeys: ReadonlySet<string> | null): T[] { |
| 102 | return threads.filter( | 105 | return threads.filter( |
| 103 | (thread) => | 106 | (thread) => |
| @@ -827,6 +830,21 @@ export function sortLogicalProjectsForSidebar< · 723 unmodified lines | |||
| 827 | ); | 830 | ); |
| 828 | } | 831 | } |
| 829 | 832 | ||
| 833 | export function sortSidebarV2ProjectGroups< | ||
| 834 | TProject extends LogicalSidebarProject, | ||
| 835 | TThread extends ScopedSidebarThread & Pick<SidebarThreadSummary, "lineage">, | ||
| 836 | >( | ||
| 837 | projects: readonly TProject[], | ||
| 838 | threads: readonly TThread[], | ||
| 839 | sortOrder: SidebarProjectSortOrder, | ||
| 840 | ): TProject[] { | ||
| 841 | return sortLogicalProjectsForSidebar( | ||
| 842 | projects, | ||
| 843 | filterSidebarV2VisibleThreads(threads, null), | ||
| 844 | sortOrder, | ||
| 845 | ); | ||
| 846 | } | ||
| 847 | |||
| 830 | /** | 848 | /** |
| 831 | * Sorts the cross-environment project collection used by landing surfaces. | 849 | * Sorts the cross-environment project collection used by landing surfaces. |
| 832 | * Project ids are only unique within an environment, and archived threads | 850 | * Project ids are only unique within an environment, and archived threads |
| @@ -112,7 +112,7 @@ import { · 111 unmodified lines | |||
| 112 | resolveSidebarV2Status, | 112 | resolveSidebarV2Status, |
| 113 | resolveWorkingStartedAt, | 113 | resolveWorkingStartedAt, |
| 114 | shouldNavigateAfterProjectRemoval, | 114 | shouldNavigateAfterProjectRemoval, |
| 115 | sortLogicalProjectsForSidebar, | 115 | sortSidebarV2ProjectGroups, |
| 116 | sortSettledThreadsForSidebarV2, | 116 | sortSettledThreadsForSidebarV2, |
| 117 | sortThreadsForSidebarV2, | 117 | sortThreadsForSidebarV2, |
| 118 | } from "./Sidebar.logic"; | 118 | } from "./Sidebar.logic"; |
| @@ -1104,7 +1104,7 @@ export default function SidebarV2() { · 985 unmodified lines | |||
| 1104 | ], | 1104 | ], |
| 1105 | ); | 1105 | ); |
| 1106 | const projectGroups = useMemo( | 1106 | const projectGroups = useMemo( |
| 1107 | () => sortLogicalProjectsForSidebar(unsortedProjectGroups, threads, sidebarProjectSortOrder), | 1107 | () => sortSidebarV2ProjectGroups(unsortedProjectGroups, threads, sidebarProjectSortOrder), |
| 1108 | [sidebarProjectSortOrder, threads, unsortedProjectGroups], | 1108 | [sidebarProjectSortOrder, threads, unsortedProjectGroups], |
| 1109 | ); | 1109 | ); |
| 1110 | const serverProviders = useAtomValue(primaryServerProvidersAtom); | 1110 | const serverProviders = useAtomValue(primaryServerProvidersAtom); |