+ );
+}
diff --git a/apps/web/src/components/settings/ProjectsSettings.tsx b/apps/web/src/components/settings/ProjectsSettings.tsx
index 826c92bca26e3..a24edaceb6d03 100644
--- a/apps/web/src/components/settings/ProjectsSettings.tsx
+++ b/apps/web/src/components/settings/ProjectsSettings.tsx
@@ -1,7 +1,9 @@
import { EnvironmentId } from "@t3tools/contracts";
import { LayersIcon } from "lucide-react";
+import { useClientSettings } from "../../hooks/useSettings";
import { Button } from "../ui/button";
+import { ProjectOrderList } from "./ProjectOrderList";
import { ProjectSettingsPanel } from "./ProjectSettingsPanel";
import { useSettingsScope } from "./SettingsScopeContext";
import { SettingsScopeNotice } from "./SettingsScopeNotice";
@@ -10,6 +12,9 @@ import { ALL_ENVIRONMENTS_VALUE, selectEnvironmentAxis } from "./settingsScopeAx
export function ProjectsSettings() {
const { search: value, scope, selectScope } = useSettingsScope();
+ const manualOrder = useClientSettings(
+ (settings) => settings.sidebarProjectSortOrder === "manual",
+ );
const unavailable =
scope.kind === "unavailable" ? (
@@ -45,6 +50,10 @@ export function ProjectsSettings() {
/>
) : unavailable ? (
unavailable
+ ) : manualOrder ? (
+
+
+
) : (
Choose a project to manage its name, icon, checkouts and actions.
diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx
index b20295847f934..a5d4a926a0c04 100644
--- a/apps/web/src/components/settings/SettingsPanels.tsx
+++ b/apps/web/src/components/settings/SettingsPanels.tsx
@@ -844,7 +844,12 @@ export function GeneralSettingsPanel() {
/>
Order of projects in the sidebar project picker and command palette.}
+ description={
+
+ Order of projects in the sidebar project picker and command palette. Set a manual
+ order by dragging projects in Settings → Projects.
+
+ }
resetAction={
settings.sidebarProjectSortOrder !==
DEFAULT_UNIFIED_SETTINGS.sidebarProjectSortOrder ? (
diff --git a/apps/web/src/uiStateStore.test.ts b/apps/web/src/uiStateStore.test.ts
index dbfb60d15bd20..8f8e618029b05 100644
--- a/apps/web/src/uiStateStore.test.ts
+++ b/apps/web/src/uiStateStore.test.ts
@@ -91,6 +91,14 @@ describe("uiStateStore pure functions", () => {
const next = reorderProjects(makeUiState(), currentOrder, [keyALocal, keyARemote], [keyC]);
expect(next.projectOrder).toEqual([keyB, keyC, keyALocal, keyARemote]);
+ expect(
+ reorderProjects(
+ makeUiState(),
+ [keyB, keyALocal, keyARemote, keyC],
+ [keyB],
+ [keyALocal, keyARemote],
+ ).projectOrder,
+ ).toEqual([keyALocal, keyARemote, keyB, keyC]);
});
});
diff --git a/apps/web/src/uiStateStore.ts b/apps/web/src/uiStateStore.ts
index 328e1a6477132..5993da8666af9 100644
--- a/apps/web/src/uiStateStore.ts
+++ b/apps/web/src/uiStateStore.ts
@@ -420,7 +420,10 @@ export function reorderProjects(
return state;
}
- const insertIndex = originalTargetIndex - Math.max(0, draggedBeforeTarget - 1);
+ const insertIndex =
+ draggedBeforeTarget > 0
+ ? projectOrder.findLastIndex((id) => targetSet.has(id)) + 1
+ : originalTargetIndex;
projectOrder.splice(insertIndex, 0, ...removed);
return {
...state,
diff --git a/docs/user/thread-sidebar.md b/docs/user/thread-sidebar.md
index 09ce63e2a3e65..f847154230857 100644
--- a/docs/user/thread-sidebar.md
+++ b/docs/user/thread-sidebar.md
@@ -150,7 +150,9 @@ cannot drag or move threads within it. Your saved order returns when you turn it
### Group threads by project (beta)
Turn on **Settings → Labs → Boring sidebar (group by project)** on web and desktop to put active
-threads in a folder for each project. Pinned projects come first. A project with no active
+threads in a folder for each project. Pinned projects come first. To fix the folder order, set
+**Settings → General → Project order** to **Manual** and drag projects into place in **Settings →
+Projects**; agent activity won't move them then. A project with no active
threads has no folder unless it is pinned or you pick it in the project filter. An archived
project's folder comes last and shows only while it has active threads. Click a folder to open or
close it. A closed folder shows how many threads it holds, and a dot when one of them