From 09c62688fea89a7ae937d9c1e1b083a4d3da6a65 Mon Sep 17 00:00:00 2001 From: onevcat Date: Thu, 29 Jan 2026 22:33:55 +0900 Subject: [PATCH 1/2] fix(desktop): persist sidebar reorder on drag end --- .../ProjectSection/ProjectSection.tsx | 15 ++++- .../WorkspaceListItem/WorkspaceListItem.tsx | 61 +++++++++++++++++-- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx index 3a28eac2a80..aac82671676 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx @@ -64,11 +64,24 @@ export function ProjectSection({ () => ({ type: PROJECT_TYPE, item: { projectId, index, originalIndex: index }, + end: (item, monitor) => { + if (!item) return; + if (monitor.didDrop()) return; + if (item.originalIndex !== item.index) { + reorderProjects.mutate( + { fromIndex: item.originalIndex, toIndex: item.index }, + { + onError: (error) => + toast.error(`Failed to reorder: ${error.message}`), + }, + ); + } + }, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }), - [projectId, index], + [projectId, index, reorderProjects], ); const [, drop] = useDrop({ diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx index 0ffbff2c9f0..57e5976748b 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx @@ -206,30 +206,79 @@ export function WorkspaceListItem({ const [{ isDragging }, drag] = useDrag( () => ({ type: WORKSPACE_TYPE, - item: { id, projectId, index }, + item: { id, projectId, index, originalIndex: index }, + end: (_item, monitor) => { + const item = monitor.getItem() as + | { + id: string; + projectId: string; + index: number; + originalIndex: number; + } + | undefined; + if (!item || monitor.didDrop()) return; + if (item.projectId !== projectId) return; + if (item.originalIndex === item.index) return; + reorderWorkspaces.mutate( + { + projectId: item.projectId, + fromIndex: item.originalIndex, + toIndex: item.index, + }, + { + onError: (error) => + toast.error(`Failed to reorder workspace: ${error.message}`), + }, + ); + }, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }), - [id, projectId, index], + [id, projectId, index, reorderWorkspaces], ); const [, drop] = useDrop({ accept: WORKSPACE_TYPE, - hover: (item: { id: string; projectId: string; index: number }) => { + hover: (item: { + id: string; + projectId: string; + index: number; + originalIndex: number; + }) => { if (item.projectId === projectId && item.index !== index) { + utils.workspaces.getAllGrouped.setData(undefined, (oldData) => { + if (!oldData) return oldData; + return oldData.map((group) => { + if (group.project.id !== projectId) return group; + const workspaces = [...group.workspaces]; + const [moved] = workspaces.splice(item.index, 1); + workspaces.splice(index, 0, moved); + return { ...group, workspaces }; + }); + }); + item.index = index; + } + }, + drop: (item: { + id: string; + projectId: string; + index: number; + originalIndex: number; + }) => { + if (item.projectId !== projectId) return; + if (item.originalIndex !== item.index) { reorderWorkspaces.mutate( { projectId, - fromIndex: item.index, - toIndex: index, + fromIndex: item.originalIndex, + toIndex: item.index, }, { onError: (error) => toast.error(`Failed to reorder workspace: ${error.message}`), }, ); - item.index = index; } }, }); From 31091a5714496d55ecd8649c78be33a7e898e694 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Mon, 2 Feb 2026 00:41:08 -0800 Subject: [PATCH 2/2] fix(desktop): prevent double mutation and add rollback for sidebar reorder Return value from drop handlers so didDrop() returns true, preventing both drop and end handlers from firing mutate(). Add onSettled callback to invalidate query cache, ensuring UI syncs with server state on mutation failure. --- .../ProjectSection/ProjectSection.tsx | 3 ++ .../WorkspaceListItem/WorkspaceListItem.tsx | 39 ++++++++----------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx index aac82671676..308d1625130 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx @@ -73,6 +73,7 @@ export function ProjectSection({ { onError: (error) => toast.error(`Failed to reorder: ${error.message}`), + onSettled: () => utils.workspaces.getAllGrouped.invalidate(), }, ); } @@ -113,8 +114,10 @@ export function ProjectSection({ { onError: (error) => toast.error(`Failed to reorder: ${error.message}`), + onSettled: () => utils.workspaces.getAllGrouped.invalidate(), }, ); + return { reordered: true }; } }, }); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx index 57e5976748b..9afe861ca98 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx @@ -207,29 +207,22 @@ export function WorkspaceListItem({ () => ({ type: WORKSPACE_TYPE, item: { id, projectId, index, originalIndex: index }, - end: (_item, monitor) => { - const item = monitor.getItem() as - | { - id: string; - projectId: string; - index: number; - originalIndex: number; - } - | undefined; + end: (item, monitor) => { if (!item || monitor.didDrop()) return; - if (item.projectId !== projectId) return; - if (item.originalIndex === item.index) return; - reorderWorkspaces.mutate( - { - projectId: item.projectId, - fromIndex: item.originalIndex, - toIndex: item.index, - }, - { - onError: (error) => - toast.error(`Failed to reorder workspace: ${error.message}`), - }, - ); + if (item.originalIndex !== item.index) { + reorderWorkspaces.mutate( + { + projectId: item.projectId, + fromIndex: item.originalIndex, + toIndex: item.index, + }, + { + onError: (error) => + toast.error(`Failed to reorder workspace: ${error.message}`), + onSettled: () => utils.workspaces.getAllGrouped.invalidate(), + }, + ); + } }, collect: (monitor) => ({ isDragging: monitor.isDragging(), @@ -277,8 +270,10 @@ export function WorkspaceListItem({ { onError: (error) => toast.error(`Failed to reorder workspace: ${error.message}`), + onSettled: () => utils.workspaces.getAllGrouped.invalidate(), }, ); + return { reordered: true }; } }, });