From 694adec6350fc6292751c64a6e31dfb76928c41e Mon Sep 17 00:00:00 2001 From: D'Angelo Rodriguez <70290504+dangelo352@users.noreply.github.com> Date: Sat, 6 Jun 2026 02:34:28 -0400 Subject: [PATCH] Smooth desktop sidebar drag sorting --- apps/desktop/src/app/chat/sidebar/index.tsx | 109 +++++++++++++++++--- 1 file changed, 95 insertions(+), 14 deletions(-) diff --git a/apps/desktop/src/app/chat/sidebar/index.tsx b/apps/desktop/src/app/chat/sidebar/index.tsx index 8c5f8bc71..c81483216 100644 --- a/apps/desktop/src/app/chat/sidebar/index.tsx +++ b/apps/desktop/src/app/chat/sidebar/index.tsx @@ -159,6 +159,33 @@ function orderByIds(items: T[], getId: (item: T) => string, orderIds: string[ return out } +function reconcileOrderIds(currentIds: string[], orderIds: string[]): string[] { + if (!currentIds.length) { + return [] + } + + if (!orderIds.length) { + return currentIds + } + + const current = new Set(currentIds) + const next = orderIds.filter(id => current.has(id)) + const known = new Set(next) + + for (const id of currentIds) { + if (!known.has(id)) { + next.push(id) + known.add(id) + } + } + + return next +} + +function sameIds(left: string[], right: string[]) { + return left.length === right.length && left.every((item, index) => item === right[index]) +} + const baseName = (path: string) => path .replace(/[/\\]+$/, '') @@ -266,7 +293,11 @@ function useSortableBindings(id: string) { dragHandleProps: { ...attributes, ...listeners }, ref: setNodeRef, reorderable: true as const, - style: { transform: CSS.Transform.toString(transform), transition } + style: { + transform: CSS.Transform.toString(transform), + transition: isDragging ? undefined : transition, + willChange: isDragging ? 'transform' : undefined + } } } @@ -479,6 +510,17 @@ export function ChatSidebar({ [sortedSessions, pinnedRealIdSet] ) + useEffect(() => { + const next = reconcileOrderIds( + unpinnedAgentSessions.map(s => s.id), + agentOrderIds + ) + + if (!sameIds(next, agentOrderIds)) { + setSidebarSessionOrderIds(next) + } + }, [agentOrderIds, unpinnedAgentSessions]) + const agentSessions = useMemo( () => orderByIds(unpinnedAgentSessions, s => s.id, agentOrderIds), [unpinnedAgentSessions, agentOrderIds] @@ -597,6 +639,21 @@ export function ChatSidebar({ workspaceOrderIds ]) + useEffect(() => { + if (!displayAgentGroups?.length || showAllProfiles) { + return + } + + const next = reconcileOrderIds( + displayAgentGroups.map(g => g.id), + workspaceOrderIds + ) + + if (!sameIds(next, workspaceOrderIds)) { + setSidebarWorkspaceOrderIds(next) + } + }, [displayAgentGroups, showAllProfiles, workspaceOrderIds]) + const showSessionSkeletons = sessionsLoading && sortedSessions.length === 0 const showSessionSections = showSessionSkeletons || sortedSessions.length > 0 @@ -1069,12 +1126,25 @@ function SidebarSessionsSection({ renderRows(items) ) + const renderNestedSessionList = (items: SessionInfo[]) => + dndActive ? ( + + s.id)} strategy={verticalListSortingStrategy}> + {renderRows(items)} + + + ) : ( + renderRows(items) + ) + const flatVirtualized = !showEmptyState && !groups?.length && sessions.length >= VIRTUALIZE_THRESHOLD let inner: React.ReactNode + let bodyOwnsDndContext = dndActive && !showEmptyState if (showEmptyState) { inner = emptyState + bodyOwnsDndContext = false } else if (groups?.length) { const groupNodes = groups.map(group => dndActive ? ( @@ -1082,7 +1152,7 @@ function SidebarSessionsSection({ group={group} key={group.id} onNewSession={onNewSessionInWorkspace} - renderRows={renderSessionList} + renderRows={renderNestedSessionList} /> ) : ( groupDndId(g.id))} strategy={verticalListSortingStrategy}> - {groupNodes} - + + groupDndId(g.id))} strategy={verticalListSortingStrategy}> + {groupNodes} + + ) : ( groupNodes ) + bodyOwnsDndContext = false } else if (flatVirtualized) { inner = ( - {inner} - - ) : ( - inner - ) + const body = bodyOwnsDndContext ? ( + + {inner} + + ) : ( + inner + ) // The virtualizer owns its own scroller, so suppress the wrapper's overflow // to avoid a double scroll container. @@ -1195,7 +1267,16 @@ function SidebarWorkspaceGroup({ } return ( -
+