Unified · Split
| @@ -26,6 +26,7 @@ import { · 25 unmodified lines | ||
| 26 | 26 | shouldNavigateAfterProjectRemoval, |
| 27 | 27 | shouldClearThreadSelectionOnMouseDown, |
| 28 | 28 | sortLogicalProjectsForSidebar, |
| 29 | + sortSidebarV2ProjectGroups, | |
| 29 | 30 | sortSettledThreadsForSidebarV2, |
| 30 | 31 | sortThreadsForSidebarV2, |
| 31 | 32 | sortScopedProjectsForSidebar, |
| @@ -1502,3 +1503,51 @@ describe("sortLogicalProjectsForSidebar", () => { · 1470 unmodified lines | ||
| 1502 | 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 | 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 | 104 | >(threads: readonly T[], scopedProjectKeys: ReadonlySet<string> | null): T[] { |
| 102 | 105 | return threads.filter( |
| 103 | 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 | 849 | * Sorts the cross-environment project collection used by landing surfaces. |
| 832 | 850 | * Project ids are only unique within an environment, and archived threads |
| @@ -112,7 +112,7 @@ import { · 111 unmodified lines | ||
| 112 | 112 | resolveSidebarV2Status, |
| 113 | 113 | resolveWorkingStartedAt, |
| 114 | 114 | shouldNavigateAfterProjectRemoval, |
| 115 | − sortLogicalProjectsForSidebar, | |
| 115 | + sortSidebarV2ProjectGroups, | |
| 116 | 116 | sortSettledThreadsForSidebarV2, |
| 117 | 117 | sortThreadsForSidebarV2, |
| 118 | 118 | } from "./Sidebar.logic"; |
| @@ -1104,7 +1104,7 @@ export default function SidebarV2() { · 985 unmodified lines | ||
| 1104 | 1104 | ], |
| 1105 | 1105 | ); |
| 1106 | 1106 | const projectGroups = useMemo( |
| 1107 | − () => sortLogicalProjectsForSidebar(unsortedProjectGroups, threads, sidebarProjectSortOrder), | |
| 1107 | + () => sortSidebarV2ProjectGroups(unsortedProjectGroups, threads, sidebarProjectSortOrder), | |
| 1108 | 1108 | [sidebarProjectSortOrder, threads, unsortedProjectGroups], |
| 1109 | 1109 | ); |
| 1110 | 1110 | const serverProviders = useAtomValue(primaryServerProvidersAtom); |