From dc1aa51645acf99c604ee911e00bc3e1e04a4559 Mon Sep 17 00:00:00 2001 From: Mike Olson Date: Fri, 10 Apr 2026 18:24:22 -0400 Subject: [PATCH 1/6] fix(web): resolve logical-to-physical key mismatch in project drag reorder Fixes #1902 --- apps/web/src/components/Sidebar.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index bb63db6fc0..765396034e 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -2696,7 +2696,16 @@ export default function Sidebar() { const activeProject = sidebarProjects.find((project) => project.projectKey === active.id); const overProject = sidebarProjects.find((project) => project.projectKey === over.id); if (!activeProject || !overProject) return; - reorderProjects(activeProject.projectKey, overProject.projectKey); + // projectOrder stores physical keys, but projectKey is a logical key + // that may differ (e.g. canonicalKey from repositoryIdentity). Resolve + // back to the representative's physical key for the reorder. + const activePhysicalKey = scopedProjectKey( + scopeProjectRef(activeProject.environmentId, activeProject.id), + ); + const overPhysicalKey = scopedProjectKey( + scopeProjectRef(overProject.environmentId, overProject.id), + ); + reorderProjects(activePhysicalKey, overPhysicalKey); }, [sidebarProjectSortOrder, reorderProjects, sidebarProjects], ); From 900e3eb272b0cfd8b1abd566eddeefe95c5c92fa Mon Sep 17 00:00:00 2001 From: Mike Olson Date: Fri, 10 Apr 2026 19:15:40 -0400 Subject: [PATCH 2/6] fix(web): move all group member keys together during drag reorder For multi-environment grouped projects, reordering only the representative's physical key could leave non-representative members anchored at the old position. Add reorderProjectGroup that removes and re-inserts all member keys as a unit, and use it from the drag handler via memberProjectRefs. --- apps/web/src/components/Sidebar.tsx | 17 ++----- apps/web/src/uiStateStore.test.ts | 76 +++++++++++++++++++++++++++++ apps/web/src/uiStateStore.ts | 44 +++++++++++++++++ 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 765396034e..0704b17d9c 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -2330,7 +2330,7 @@ export default function Sidebar() { const activeEnvironmentId = useStore((store) => store.activeEnvironmentId); const projectExpandedById = useUiStateStore((store) => store.projectExpandedById); const projectOrder = useUiStateStore((store) => store.projectOrder); - const reorderProjects = useUiStateStore((store) => store.reorderProjects); + const reorderProjectGroup = useUiStateStore((store) => store.reorderProjectGroup); const navigate = useNavigate(); const pathname = useLocation({ select: (loc) => loc.pathname }); const isOnSettings = pathname.startsWith("/settings"); @@ -2696,18 +2696,11 @@ export default function Sidebar() { const activeProject = sidebarProjects.find((project) => project.projectKey === active.id); const overProject = sidebarProjects.find((project) => project.projectKey === over.id); if (!activeProject || !overProject) return; - // projectOrder stores physical keys, but projectKey is a logical key - // that may differ (e.g. canonicalKey from repositoryIdentity). Resolve - // back to the representative's physical key for the reorder. - const activePhysicalKey = scopedProjectKey( - scopeProjectRef(activeProject.environmentId, activeProject.id), - ); - const overPhysicalKey = scopedProjectKey( - scopeProjectRef(overProject.environmentId, overProject.id), - ); - reorderProjects(activePhysicalKey, overPhysicalKey); + const activeMemberKeys = activeProject.memberProjectRefs.map(scopedProjectKey); + const overMemberKeys = overProject.memberProjectRefs.map(scopedProjectKey); + reorderProjectGroup(activeMemberKeys, overMemberKeys); }, - [sidebarProjectSortOrder, reorderProjects, sidebarProjects], + [sidebarProjectSortOrder, reorderProjectGroup, sidebarProjects], ); const handleProjectDragStart = useCallback( diff --git a/apps/web/src/uiStateStore.test.ts b/apps/web/src/uiStateStore.test.ts index b6d31b57e9..656a657e23 100644 --- a/apps/web/src/uiStateStore.test.ts +++ b/apps/web/src/uiStateStore.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it } from "vitest"; import { clearThreadUi, markThreadUnread, + reorderProjectGroup, reorderProjects, setProjectExpanded, setThreadChangedFilesExpanded, @@ -63,6 +64,81 @@ describe("uiStateStore pure functions", () => { expect(next.projectOrder).toEqual([project2, project3, project1]); }); + it("reorderProjects is a no-op when dragged key is not in projectOrder", () => { + const project1 = ProjectId.make("project-1"); + const project2 = ProjectId.make("project-2"); + const initialState = makeUiState({ + projectOrder: [project1, project2], + }); + + const next = reorderProjects(initialState, ProjectId.make("missing"), project2); + + expect(next).toBe(initialState); + }); + + it("reorderProjectGroup moves a single-member group to a target position", () => { + const project1 = ProjectId.make("project-1"); + const project2 = ProjectId.make("project-2"); + const project3 = ProjectId.make("project-3"); + const initialState = makeUiState({ + projectOrder: [project1, project2, project3], + }); + + const next = reorderProjectGroup(initialState, [project1], [project3]); + + expect(next.projectOrder).toEqual([project2, project3, project1]); + }); + + it("reorderProjectGroup moves all member keys of a multi-member group together", () => { + const keyALocal = "env-local:proj-a"; + const keyARemote = "env-remote:proj-a"; + const keyB = "env-local:proj-b"; + const keyC = "env-local:proj-c"; + const initialState = makeUiState({ + projectOrder: [keyALocal, keyARemote, keyB, keyC], + }); + + const next = reorderProjectGroup(initialState, [keyALocal, keyARemote], [keyC]); + + expect(next.projectOrder).toEqual([keyB, keyC, keyALocal, keyARemote]); + }); + + it("reorderProjectGroup handles member keys scattered across projectOrder", () => { + const keyALocal = "env-local:proj-a"; + const keyB = "env-local:proj-b"; + const keyARemote = "env-remote:proj-a"; + const keyC = "env-local:proj-c"; + const initialState = makeUiState({ + projectOrder: [keyALocal, keyB, keyARemote, keyC], + }); + + const next = reorderProjectGroup(initialState, [keyALocal, keyARemote], [keyC]); + + expect(next.projectOrder).toEqual([keyB, keyC, keyALocal, keyARemote]); + }); + + it("reorderProjectGroup is a no-op when dragged group equals target group", () => { + const key1 = "env-local:proj-a"; + const key2 = "env-remote:proj-a"; + const initialState = makeUiState({ + projectOrder: [key1, key2, "env-local:proj-b"], + }); + + const next = reorderProjectGroup(initialState, [key1, key2], [key1, key2]); + + expect(next).toBe(initialState); + }); + + it("reorderProjectGroup is a no-op when dragged keys are not in projectOrder", () => { + const initialState = makeUiState({ + projectOrder: ["env-local:proj-a", "env-local:proj-b"], + }); + + const next = reorderProjectGroup(initialState, ["env-local:missing"], ["env-local:proj-b"]); + + expect(next).toBe(initialState); + }); + it("syncProjects preserves current project order during snapshot recovery", () => { const project1 = ProjectId.make("project-1"); const project2 = ProjectId.make("project-2"); diff --git a/apps/web/src/uiStateStore.ts b/apps/web/src/uiStateStore.ts index 5f75b60281..4e7a4112a6 100644 --- a/apps/web/src/uiStateStore.ts +++ b/apps/web/src/uiStateStore.ts @@ -505,6 +505,44 @@ export function reorderProjects( }; } +export function reorderProjectGroup( + state: UiState, + draggedProjectIds: readonly string[], + targetProjectIds: readonly string[], +): UiState { + if (draggedProjectIds.length === 0) { + return state; + } + const draggedSet = new Set(draggedProjectIds); + const targetSet = new Set(targetProjectIds); + if (draggedProjectIds.every((id) => targetSet.has(id))) { + return state; + } + + const originalTargetIndex = state.projectOrder.findIndex((id) => targetSet.has(id)); + if (originalTargetIndex < 0) { + return state; + } + + const projectOrder = [...state.projectOrder]; + + const removed: string[] = []; + for (let i = projectOrder.length - 1; i >= 0; i--) { + if (draggedSet.has(projectOrder[i]!)) { + removed.unshift(projectOrder.splice(i, 1)[0]!); + } + } + if (removed.length === 0) { + return state; + } + + projectOrder.splice(originalTargetIndex, 0, ...removed); + return { + ...state, + projectOrder, + }; +} + interface UiStateStore extends UiState { syncProjects: (projects: readonly SyncProjectInput[]) => void; syncThreads: (threads: readonly SyncThreadInput[]) => void; @@ -515,6 +553,10 @@ interface UiStateStore extends UiState { toggleProject: (projectId: string) => void; setProjectExpanded: (projectId: string, expanded: boolean) => void; reorderProjects: (draggedProjectId: string, targetProjectId: string) => void; + reorderProjectGroup: ( + draggedProjectIds: readonly string[], + targetProjectIds: readonly string[], + ) => void; } export const useUiStateStore = create((set) => ({ @@ -533,6 +575,8 @@ export const useUiStateStore = create((set) => ({ set((state) => setProjectExpanded(state, projectId, expanded)), reorderProjects: (draggedProjectId, targetProjectId) => set((state) => reorderProjects(state, draggedProjectId, targetProjectId)), + reorderProjectGroup: (draggedProjectIds, targetProjectIds) => + set((state) => reorderProjectGroup(state, draggedProjectIds, targetProjectIds)), })); useUiStateStore.subscribe((state) => debouncedPersistState.maybeExecute(state)); From c18b87be1d2f2dc952926b58fca650821850516b Mon Sep 17 00:00:00 2001 From: Mike Olson Date: Fri, 10 Apr 2026 19:34:24 -0400 Subject: [PATCH 3/6] fix(web): correct group reorder overshoot when multiple keys precede target Adjust insertion index to compensate for dragged keys removed before the target position, fixing an off-by-(N-1) overshoot when N > 1 group members sit before the drop target. --- apps/web/src/uiStateStore.test.ts | 29 +++++++++++++++++++++++++++++ apps/web/src/uiStateStore.ts | 7 ++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/web/src/uiStateStore.test.ts b/apps/web/src/uiStateStore.test.ts index 656a657e23..cb07b974fd 100644 --- a/apps/web/src/uiStateStore.test.ts +++ b/apps/web/src/uiStateStore.test.ts @@ -117,6 +117,35 @@ describe("uiStateStore pure functions", () => { expect(next.projectOrder).toEqual([keyB, keyC, keyALocal, keyARemote]); }); + it("reorderProjectGroup places group after target when dragged from before a non-last target", () => { + const keyALocal = "env-local:proj-a"; + const keyARemote = "env-remote:proj-a"; + const keyB = "env-local:proj-b"; + const keyC = "env-local:proj-c"; + const keyD = "env-local:proj-d"; + const initialState = makeUiState({ + projectOrder: [keyALocal, keyARemote, keyB, keyC, keyD], + }); + + const next = reorderProjectGroup(initialState, [keyALocal, keyARemote], [keyC]); + + expect(next.projectOrder).toEqual([keyB, keyC, keyALocal, keyARemote, keyD]); + }); + + it("reorderProjectGroup places group before target when dragged from after", () => { + const keyB = "env-local:proj-b"; + const keyC = "env-local:proj-c"; + const keyALocal = "env-local:proj-a"; + const keyARemote = "env-remote:proj-a"; + const initialState = makeUiState({ + projectOrder: [keyB, keyC, keyALocal, keyARemote], + }); + + const next = reorderProjectGroup(initialState, [keyALocal, keyARemote], [keyB]); + + expect(next.projectOrder).toEqual([keyALocal, keyARemote, keyB, keyC]); + }); + it("reorderProjectGroup is a no-op when dragged group equals target group", () => { const key1 = "env-local:proj-a"; const key2 = "env-remote:proj-a"; diff --git a/apps/web/src/uiStateStore.ts b/apps/web/src/uiStateStore.ts index 4e7a4112a6..5fdbd8e602 100644 --- a/apps/web/src/uiStateStore.ts +++ b/apps/web/src/uiStateStore.ts @@ -527,16 +527,21 @@ export function reorderProjectGroup( const projectOrder = [...state.projectOrder]; const removed: string[] = []; + let draggedBeforeTarget = 0; for (let i = projectOrder.length - 1; i >= 0; i--) { if (draggedSet.has(projectOrder[i]!)) { removed.unshift(projectOrder.splice(i, 1)[0]!); + if (i < originalTargetIndex) { + draggedBeforeTarget++; + } } } if (removed.length === 0) { return state; } - projectOrder.splice(originalTargetIndex, 0, ...removed); + const insertIndex = originalTargetIndex - Math.max(0, draggedBeforeTarget - 1); + projectOrder.splice(insertIndex, 0, ...removed); return { ...state, projectOrder, From 2a76d1b3c20d473981091ef93854922b84a86859 Mon Sep 17 00:00:00 2001 From: Mike Olson Date: Fri, 10 Apr 2026 19:51:54 -0400 Subject: [PATCH 4/6] test: document multi-member target group behavior in reorderProjectGroup --- apps/web/src/uiStateStore.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/web/src/uiStateStore.test.ts b/apps/web/src/uiStateStore.test.ts index cb07b974fd..0b3dd4ed3b 100644 --- a/apps/web/src/uiStateStore.test.ts +++ b/apps/web/src/uiStateStore.test.ts @@ -146,6 +146,26 @@ describe("uiStateStore pure functions", () => { expect(next.projectOrder).toEqual([keyALocal, keyARemote, keyB, keyC]); }); + it("reorderProjectGroup with multi-member target inserts after first target occurrence", () => { + const keyALocal = "env-local:proj-a"; + const keyARemote = "env-remote:proj-a"; + const keyBLocal = "env-local:proj-b"; + const keyBRemote = "env-remote:proj-b"; + const initialState = makeUiState({ + projectOrder: [keyALocal, keyARemote, keyBLocal, keyBRemote], + }); + + const next = reorderProjectGroup( + initialState, + [keyALocal, keyARemote], + [keyBLocal, keyBRemote], + ); + + // Target members may become non-contiguous; this is fine because the + // sidebar groups by logical key using first-occurrence positioning. + expect(next.projectOrder).toEqual([keyBLocal, keyALocal, keyARemote, keyBRemote]); + }); + it("reorderProjectGroup is a no-op when dragged group equals target group", () => { const key1 = "env-local:proj-a"; const key2 = "env-remote:proj-a"; From cb03cf83f53a690816b239c426d38eb873966d1d Mon Sep 17 00:00:00 2001 From: Mike Olson Date: Fri, 10 Apr 2026 20:28:15 -0400 Subject: [PATCH 5/6] refactor: consolidate reorderProjectGroup into reorderProjects --- apps/web/src/components/Sidebar.tsx | 6 ++-- apps/web/src/uiStateStore.test.ts | 46 ++++++++++------------------- apps/web/src/uiStateStore.ts | 34 ++------------------- 3 files changed, 22 insertions(+), 64 deletions(-) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 0704b17d9c..c5725c6d0d 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -2330,7 +2330,7 @@ export default function Sidebar() { const activeEnvironmentId = useStore((store) => store.activeEnvironmentId); const projectExpandedById = useUiStateStore((store) => store.projectExpandedById); const projectOrder = useUiStateStore((store) => store.projectOrder); - const reorderProjectGroup = useUiStateStore((store) => store.reorderProjectGroup); + const reorderProjects = useUiStateStore((store) => store.reorderProjects); const navigate = useNavigate(); const pathname = useLocation({ select: (loc) => loc.pathname }); const isOnSettings = pathname.startsWith("/settings"); @@ -2698,9 +2698,9 @@ export default function Sidebar() { if (!activeProject || !overProject) return; const activeMemberKeys = activeProject.memberProjectRefs.map(scopedProjectKey); const overMemberKeys = overProject.memberProjectRefs.map(scopedProjectKey); - reorderProjectGroup(activeMemberKeys, overMemberKeys); + reorderProjects(activeMemberKeys, overMemberKeys); }, - [sidebarProjectSortOrder, reorderProjectGroup, sidebarProjects], + [sidebarProjectSortOrder, reorderProjects, sidebarProjects], ); const handleProjectDragStart = useCallback( diff --git a/apps/web/src/uiStateStore.test.ts b/apps/web/src/uiStateStore.test.ts index 0b3dd4ed3b..6a65b4d1b3 100644 --- a/apps/web/src/uiStateStore.test.ts +++ b/apps/web/src/uiStateStore.test.ts @@ -4,7 +4,6 @@ import { describe, expect, it } from "vitest"; import { clearThreadUi, markThreadUnread, - reorderProjectGroup, reorderProjects, setProjectExpanded, setThreadChangedFilesExpanded, @@ -59,7 +58,7 @@ describe("uiStateStore pure functions", () => { projectOrder: [project1, project2, project3], }); - const next = reorderProjects(initialState, project1, project3); + const next = reorderProjects(initialState, [project1], [project3]); expect(next.projectOrder).toEqual([project2, project3, project1]); }); @@ -71,25 +70,12 @@ describe("uiStateStore pure functions", () => { projectOrder: [project1, project2], }); - const next = reorderProjects(initialState, ProjectId.make("missing"), project2); + const next = reorderProjects(initialState, [ProjectId.make("missing")], [project2]); expect(next).toBe(initialState); }); - it("reorderProjectGroup moves a single-member group to a target position", () => { - const project1 = ProjectId.make("project-1"); - const project2 = ProjectId.make("project-2"); - const project3 = ProjectId.make("project-3"); - const initialState = makeUiState({ - projectOrder: [project1, project2, project3], - }); - - const next = reorderProjectGroup(initialState, [project1], [project3]); - - expect(next.projectOrder).toEqual([project2, project3, project1]); - }); - - it("reorderProjectGroup moves all member keys of a multi-member group together", () => { + it("reorderProjects moves all member keys of a multi-member group together", () => { const keyALocal = "env-local:proj-a"; const keyARemote = "env-remote:proj-a"; const keyB = "env-local:proj-b"; @@ -98,12 +84,12 @@ describe("uiStateStore pure functions", () => { projectOrder: [keyALocal, keyARemote, keyB, keyC], }); - const next = reorderProjectGroup(initialState, [keyALocal, keyARemote], [keyC]); + const next = reorderProjects(initialState, [keyALocal, keyARemote], [keyC]); expect(next.projectOrder).toEqual([keyB, keyC, keyALocal, keyARemote]); }); - it("reorderProjectGroup handles member keys scattered across projectOrder", () => { + it("reorderProjects handles member keys scattered across projectOrder", () => { const keyALocal = "env-local:proj-a"; const keyB = "env-local:proj-b"; const keyARemote = "env-remote:proj-a"; @@ -112,12 +98,12 @@ describe("uiStateStore pure functions", () => { projectOrder: [keyALocal, keyB, keyARemote, keyC], }); - const next = reorderProjectGroup(initialState, [keyALocal, keyARemote], [keyC]); + const next = reorderProjects(initialState, [keyALocal, keyARemote], [keyC]); expect(next.projectOrder).toEqual([keyB, keyC, keyALocal, keyARemote]); }); - it("reorderProjectGroup places group after target when dragged from before a non-last target", () => { + it("reorderProjects places group after target when dragged from before a non-last target", () => { const keyALocal = "env-local:proj-a"; const keyARemote = "env-remote:proj-a"; const keyB = "env-local:proj-b"; @@ -127,12 +113,12 @@ describe("uiStateStore pure functions", () => { projectOrder: [keyALocal, keyARemote, keyB, keyC, keyD], }); - const next = reorderProjectGroup(initialState, [keyALocal, keyARemote], [keyC]); + const next = reorderProjects(initialState, [keyALocal, keyARemote], [keyC]); expect(next.projectOrder).toEqual([keyB, keyC, keyALocal, keyARemote, keyD]); }); - it("reorderProjectGroup places group before target when dragged from after", () => { + it("reorderProjects places group before target when dragged from after", () => { const keyB = "env-local:proj-b"; const keyC = "env-local:proj-c"; const keyALocal = "env-local:proj-a"; @@ -141,12 +127,12 @@ describe("uiStateStore pure functions", () => { projectOrder: [keyB, keyC, keyALocal, keyARemote], }); - const next = reorderProjectGroup(initialState, [keyALocal, keyARemote], [keyB]); + const next = reorderProjects(initialState, [keyALocal, keyARemote], [keyB]); expect(next.projectOrder).toEqual([keyALocal, keyARemote, keyB, keyC]); }); - it("reorderProjectGroup with multi-member target inserts after first target occurrence", () => { + it("reorderProjects with multi-member target inserts after first target occurrence", () => { const keyALocal = "env-local:proj-a"; const keyARemote = "env-remote:proj-a"; const keyBLocal = "env-local:proj-b"; @@ -155,7 +141,7 @@ describe("uiStateStore pure functions", () => { projectOrder: [keyALocal, keyARemote, keyBLocal, keyBRemote], }); - const next = reorderProjectGroup( + const next = reorderProjects( initialState, [keyALocal, keyARemote], [keyBLocal, keyBRemote], @@ -166,24 +152,24 @@ describe("uiStateStore pure functions", () => { expect(next.projectOrder).toEqual([keyBLocal, keyALocal, keyARemote, keyBRemote]); }); - it("reorderProjectGroup is a no-op when dragged group equals target group", () => { + it("reorderProjects is a no-op when dragged group equals target group", () => { const key1 = "env-local:proj-a"; const key2 = "env-remote:proj-a"; const initialState = makeUiState({ projectOrder: [key1, key2, "env-local:proj-b"], }); - const next = reorderProjectGroup(initialState, [key1, key2], [key1, key2]); + const next = reorderProjects(initialState, [key1, key2], [key1, key2]); expect(next).toBe(initialState); }); - it("reorderProjectGroup is a no-op when dragged keys are not in projectOrder", () => { + it("reorderProjects is a no-op when dragged keys are not in projectOrder", () => { const initialState = makeUiState({ projectOrder: ["env-local:proj-a", "env-local:proj-b"], }); - const next = reorderProjectGroup(initialState, ["env-local:missing"], ["env-local:proj-b"]); + const next = reorderProjects(initialState, ["env-local:missing"], ["env-local:proj-b"]); expect(next).toBe(initialState); }); diff --git a/apps/web/src/uiStateStore.ts b/apps/web/src/uiStateStore.ts index 5fdbd8e602..7ae7232063 100644 --- a/apps/web/src/uiStateStore.ts +++ b/apps/web/src/uiStateStore.ts @@ -481,31 +481,6 @@ export function setProjectExpanded(state: UiState, projectId: string, expanded: } export function reorderProjects( - state: UiState, - draggedProjectId: string, - targetProjectId: string, -): UiState { - if (draggedProjectId === targetProjectId) { - return state; - } - const draggedIndex = state.projectOrder.findIndex((projectId) => projectId === draggedProjectId); - const targetIndex = state.projectOrder.findIndex((projectId) => projectId === targetProjectId); - if (draggedIndex < 0 || targetIndex < 0) { - return state; - } - const projectOrder = [...state.projectOrder]; - const [draggedProject] = projectOrder.splice(draggedIndex, 1); - if (!draggedProject) { - return state; - } - projectOrder.splice(targetIndex, 0, draggedProject); - return { - ...state, - projectOrder, - }; -} - -export function reorderProjectGroup( state: UiState, draggedProjectIds: readonly string[], targetProjectIds: readonly string[], @@ -557,8 +532,7 @@ interface UiStateStore extends UiState { setThreadChangedFilesExpanded: (threadId: string, turnId: string, expanded: boolean) => void; toggleProject: (projectId: string) => void; setProjectExpanded: (projectId: string, expanded: boolean) => void; - reorderProjects: (draggedProjectId: string, targetProjectId: string) => void; - reorderProjectGroup: ( + reorderProjects: ( draggedProjectIds: readonly string[], targetProjectIds: readonly string[], ) => void; @@ -578,10 +552,8 @@ export const useUiStateStore = create((set) => ({ toggleProject: (projectId) => set((state) => toggleProject(state, projectId)), setProjectExpanded: (projectId, expanded) => set((state) => setProjectExpanded(state, projectId, expanded)), - reorderProjects: (draggedProjectId, targetProjectId) => - set((state) => reorderProjects(state, draggedProjectId, targetProjectId)), - reorderProjectGroup: (draggedProjectIds, targetProjectIds) => - set((state) => reorderProjectGroup(state, draggedProjectIds, targetProjectIds)), + reorderProjects: (draggedProjectIds, targetProjectIds) => + set((state) => reorderProjects(state, draggedProjectIds, targetProjectIds)), })); useUiStateStore.subscribe((state) => debouncedPersistState.maybeExecute(state)); From 275f9fa5ecbd32e0747503072a7fe2b983da79ee Mon Sep 17 00:00:00 2001 From: Mike Olson Date: Fri, 10 Apr 2026 21:31:09 -0400 Subject: [PATCH 6/6] style: fix oxfmt formatting in uiStateStore test --- apps/web/src/uiStateStore.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/web/src/uiStateStore.test.ts b/apps/web/src/uiStateStore.test.ts index 6a65b4d1b3..c906bbc1d7 100644 --- a/apps/web/src/uiStateStore.test.ts +++ b/apps/web/src/uiStateStore.test.ts @@ -141,11 +141,7 @@ describe("uiStateStore pure functions", () => { projectOrder: [keyALocal, keyARemote, keyBLocal, keyBRemote], }); - const next = reorderProjects( - initialState, - [keyALocal, keyARemote], - [keyBLocal, keyBRemote], - ); + const next = reorderProjects(initialState, [keyALocal, keyARemote], [keyBLocal, keyBRemote]); // Target members may become non-contiguous; this is fine because the // sidebar groups by logical key using first-occurrence positioning.