- {workspaces.map((workspace) => (
- onWorkspaceHover(workspace.id)}
- shortcutLabel={workspaceShortcutLabels.get(workspace.id)}
- isCollapsed
- />
- ))}
+
+
+ {workspaceItems.map((id) => {
+ const parsed = parseId(id);
+ if (!parsed) return null;
+ const workspace = workspacesById.get(parsed.realId);
+ if (!workspace) return null;
+ return (
+
+ onWorkspaceHover(parsed.realId)
+ }
+ shortcutLabel={workspaceShortcutLabels.get(
+ parsed.realId,
+ )}
+ disabled={
+ workspace.type === "main" &&
+ workspace.hostType === "local-device"
+ }
+ />
+ );
+ })}
+
+
)}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarCollapsedProjectContent/components/SortableCollapsedWorkspaceItem/SortableCollapsedWorkspaceItem.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarCollapsedProjectContent/components/SortableCollapsedWorkspaceItem/SortableCollapsedWorkspaceItem.tsx
new file mode 100644
index 00000000000..9fc6a955b6e
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarCollapsedProjectContent/components/SortableCollapsedWorkspaceItem/SortableCollapsedWorkspaceItem.tsx
@@ -0,0 +1,52 @@
+import { useSortable } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import type { DashboardSidebarWorkspace } from "../../../../../../types";
+import { DashboardSidebarWorkspaceItem } from "../../../../../DashboardSidebarWorkspaceItem";
+
+interface SortableCollapsedWorkspaceItemProps {
+ sortableId: string;
+ workspace: DashboardSidebarWorkspace;
+ onHoverCardOpen?: () => void;
+ shortcutLabel?: string;
+ disabled?: boolean;
+}
+
+export function SortableCollapsedWorkspaceItem({
+ sortableId,
+ workspace,
+ onHoverCardOpen,
+ shortcutLabel,
+ disabled,
+}: SortableCollapsedWorkspaceItemProps) {
+ const {
+ setNodeRef,
+ attributes,
+ listeners,
+ isDragging,
+ transform,
+ transition,
+ } = useSortable({ id: sortableId, disabled });
+
+ return (
+
+
+
+ );
+}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarCollapsedProjectContent/components/SortableCollapsedWorkspaceItem/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarCollapsedProjectContent/components/SortableCollapsedWorkspaceItem/index.ts
new file mode 100644
index 00000000000..085b64132b8
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarCollapsedProjectContent/components/SortableCollapsedWorkspaceItem/index.ts
@@ -0,0 +1 @@
+export { SortableCollapsedWorkspaceItem } from "./SortableCollapsedWorkspaceItem";
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useSidebarDnd/useSidebarDnd.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useSidebarDnd/useSidebarDnd.ts
index df654f453a7..0c67794acc4 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useSidebarDnd/useSidebarDnd.ts
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useSidebarDnd/useSidebarDnd.ts
@@ -242,6 +242,31 @@ export function useSidebarDnd({
return null; // ungrouped — no section above
}, [activeId, overId, activeType, flatItems, sectionsById]);
+ // The sidebar data builder always pins local main workspaces first,
+ // so any drop that lands an item above one would silently revert on
+ // the next rebuild (e.g. when the sidebar collapses and remounts).
+ // Normalize drop results to match what actually persists.
+ const normalizePinnedFirst = useCallback(
+ (items: UniqueIdentifier[]) => {
+ const pinned: UniqueIdentifier[] = [];
+ const rest: UniqueIdentifier[] = [];
+ for (const id of items) {
+ const parsed = parseId(id);
+ const ws =
+ parsed?.type === "workspace"
+ ? workspacesById.get(parsed.realId)
+ : null;
+ if (ws?.type === "main" && ws.hostType === "local-device") {
+ pinned.push(id);
+ } else {
+ rest.push(id);
+ }
+ }
+ return pinned.length > 0 ? [...pinned, ...rest] : items;
+ },
+ [workspacesById],
+ );
+
// ── Persistence ──────────────────────────────────────────────────
const commitToDb = useCallback(
@@ -310,13 +335,14 @@ export function useSidebarDnd({
}
}
- const newItems: UniqueIdentifier[] = [...ungrouped];
+ const rebuilt: UniqueIdentifier[] = [...ungrouped];
for (const secSortId of reorderedSections) {
- newItems.push(secSortId);
+ rebuilt.push(secSortId);
const wsInSec = sectionGroups.get(String(secSortId)) ?? [];
- newItems.push(...wsInSec);
+ rebuilt.push(...wsInSec);
}
+ const newItems = normalizePinnedFirst(rebuilt);
setFlatItems(newItems);
commitToDb(newItems);
} else {
@@ -326,12 +352,14 @@ export function useSidebarDnd({
if (oldIndex === -1 || overIndex === -1 || oldIndex === overIndex)
return;
- const newItems = arrayMove(flatItems, oldIndex, overIndex);
+ const newItems = normalizePinnedFirst(
+ arrayMove(flatItems, oldIndex, overIndex),
+ );
setFlatItems(newItems);
commitToDb(newItems);
}
},
- [flatItems, commitToDb],
+ [flatItems, commitToDb, normalizePinnedFirst],
);
const onDragCancel = useCallback(() => {