authorgravatar for 206951365+cursor[bot]@users.noreply.github.comcursor[bot] <206951365+cursor[bot]@users.noreply.github.com> 2026-05-11 10:07:50+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-05-11 10:07:50+00:00
log447236d51f4d6835482f6707d627dbc8a39eb553
treedbc06768f653be34829b8590f21ae70c1fe7a1ea
parent5165b8c333c0da8566a93f98011672ebd1dff9bb
signature Signed by PGP key B5690EEEBB952194

Stabilize project grouping settings selector (#2629)

Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com>

5 files changed, 24 insertions(+), 22 deletions(-)

apps/web/src/components/ChatView.tsx+5-5
...@@ -119,7 +119,10 @@ import { getProviderModelCapabilities, resolveSelectableProvider } from "../prov...@@ -119,7 +119,10 @@ import { getProviderModelCapabilities, resolveSelectableProvider } from "../prov
119import { useSettings } from "../hooks/useSettings";119import { useSettings } from "../hooks/useSettings";
120import { resolveAppModelSelectionForInstance } from "../modelSelection";120import { resolveAppModelSelectionForInstance } from "../modelSelection";
121import { isTerminalFocused } from "../lib/terminalFocus";121import { isTerminalFocused } from "../lib/terminalFocus";
122import { deriveLogicalProjectKeyFromSettings } from "../logicalProject";122import {
123 deriveLogicalProjectKeyFromSettings,
124 selectProjectGroupingSettings,
125} from "../logicalProject";
123import {126import {
124 reconnectSavedEnvironment,127 reconnectSavedEnvironment,
125 useSavedEnvironmentRegistryStore,128 useSavedEnvironmentRegistryStore,
...@@ -942,10 +945,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -942,10 +945,7 @@ export default function ChatView(props: ChatViewProps) {
942 },945 },
943 [],946 [],
944 );947 );
945 const projectGroupingSettings = useSettings((settings) => ({948 const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
946 sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
947 sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
948 }));
949 const logicalProjectEnvironments = useMemo(() => {949 const logicalProjectEnvironments = useMemo(() => {
950 if (!activeProject) return [];950 if (!activeProject) return [];
951 const logicalKey = deriveLogicalProjectKeyFromSettings(activeProject, projectGroupingSettings);951 const logicalKey = deriveLogicalProjectKeyFromSettings(activeProject, projectGroupingSettings);
apps/web/src/components/Sidebar.tsx+3-8
...@@ -183,6 +183,7 @@ import {...@@ -183,6 +183,7 @@ import {
183 derivePhysicalProjectKey,183 derivePhysicalProjectKey,
184 deriveProjectGroupingOverrideKey,184 deriveProjectGroupingOverrideKey,
185 getProjectOrderKey,185 getProjectOrderKey,
186 selectProjectGroupingSettings,
186} from "../logicalProject";187} from "../logicalProject";
187import {188import {
188 useSavedEnvironmentRegistryStore,189 useSavedEnvironmentRegistryStore,
...@@ -940,10 +941,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -940,10 +941,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
940 const defaultThreadEnvMode = useSettings<ThreadEnvMode>(941 const defaultThreadEnvMode = useSettings<ThreadEnvMode>(
941 (settings) => settings.defaultThreadEnvMode,942 (settings) => settings.defaultThreadEnvMode,
942 );943 );
943 const projectGroupingSettings = useSettings((settings) => ({944 const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
944 sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
945 sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
946 }));
947 const { updateSettings } = useUpdateSettings();945 const { updateSettings } = useUpdateSettings();
948 const sidebarThreadPreviewCount = useSettings<SidebarThreadPreviewCount>(946 const sidebarThreadPreviewCount = useSettings<SidebarThreadPreviewCount>(
949 (settings) => settings.sidebarThreadPreviewCount,947 (settings) => settings.sidebarThreadPreviewCount,
...@@ -2800,10 +2798,7 @@ export default function Sidebar() {...@@ -2800,10 +2798,7 @@ export default function Sidebar() {
2800 const sidebarThreadSortOrder = useSettings((s) => s.sidebarThreadSortOrder);2798 const sidebarThreadSortOrder = useSettings((s) => s.sidebarThreadSortOrder);
2801 const sidebarProjectSortOrder = useSettings((s) => s.sidebarProjectSortOrder);2799 const sidebarProjectSortOrder = useSettings((s) => s.sidebarProjectSortOrder);
2802 const sidebarProjectGroupingMode = useSettings((s) => s.sidebarProjectGroupingMode);2800 const sidebarProjectGroupingMode = useSettings((s) => s.sidebarProjectGroupingMode);
2803 const projectGroupingSettings = useSettings((settings) => ({2801 const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
2804 sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
2805 sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
2806 }));
2807 const sidebarThreadPreviewCount = useSettings((s) => s.sidebarThreadPreviewCount);2802 const sidebarThreadPreviewCount = useSettings((s) => s.sidebarThreadPreviewCount);
2808 const { updateSettings } = useUpdateSettings();2803 const { updateSettings } = useUpdateSettings();
2809 const { handleNewThread } = useNewThreadHandler();2804 const { handleNewThread } = useNewThreadHandler();
apps/web/src/hooks/useHandleNewThread.ts+6-5
...@@ -10,7 +10,11 @@ import {...@@ -10,7 +10,11 @@ import {
10} from "../composerDraftStore";10} from "../composerDraftStore";
11import { newDraftId, newThreadId } from "../lib/utils";11import { newDraftId, newThreadId } from "../lib/utils";
12import { orderItemsByPreferredIds } from "../components/Sidebar.logic";12import { orderItemsByPreferredIds } from "../components/Sidebar.logic";
13import { deriveLogicalProjectKeyFromSettings, getProjectOrderKey } from "../logicalProject";13import {
14 deriveLogicalProjectKeyFromSettings,
15 getProjectOrderKey,
16 selectProjectGroupingSettings,
17} from "../logicalProject";
14import { selectProjectsAcrossEnvironments, useStore } from "../store";18import { selectProjectsAcrossEnvironments, useStore } from "../store";
15import { createThreadSelectorByRef } from "../storeSelectors";19import { createThreadSelectorByRef } from "../storeSelectors";
16import { resolveThreadRouteTarget } from "../threadRoutes";20import { resolveThreadRouteTarget } from "../threadRoutes";
...@@ -19,10 +23,7 @@ import { useSettings } from "./useSettings";...@@ -19,10 +23,7 @@ import { useSettings } from "./useSettings";
1923
20function useNewThreadState() {24function useNewThreadState() {
21 const projects = useStore(useShallow((store) => selectProjectsAcrossEnvironments(store)));25 const projects = useStore(useShallow((store) => selectProjectsAcrossEnvironments(store)));
22 const projectGroupingSettings = useSettings((settings) => ({26 const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
23 sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
24 sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
25 }));
26 const router = useRouter();27 const router = useRouter();
27 const getCurrentRouteTarget = useCallback(() => {28 const getCurrentRouteTarget = useCallback(() => {
28 const currentRouteParams = router.state.matches[router.state.matches.length - 1]?.params ?? {};29 const currentRouteParams = router.state.matches[router.state.matches.length - 1]?.params ?? {};
apps/web/src/logicalProject.ts+8
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1import { scopedProjectKey, scopeProjectRef } from "@t3tools/client-runtime";1import { scopedProjectKey, scopeProjectRef } from "@t3tools/client-runtime";
2import type { ScopedProjectRef, SidebarProjectGroupingMode } from "@t3tools/contracts";2import type { ScopedProjectRef, SidebarProjectGroupingMode } from "@t3tools/contracts";
3import type { UnifiedSettings } from "@t3tools/contracts/settings";
3import { normalizeProjectPathForComparison } from "./lib/projectPaths";4import { normalizeProjectPathForComparison } from "./lib/projectPaths";
4import type { Project } from "./types";5import type { Project } from "./types";
56
...@@ -10,6 +11,13 @@ export interface ProjectGroupingSettings {...@@ -10,6 +11,13 @@ export interface ProjectGroupingSettings {
1011
11export type ProjectGroupingMode = SidebarProjectGroupingMode;12export type ProjectGroupingMode = SidebarProjectGroupingMode;
1213
14export function selectProjectGroupingSettings(settings: UnifiedSettings): ProjectGroupingSettings {
15 return {
16 sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
17 sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
18 };
19}
20
13function uniqueNonEmptyValues(values: ReadonlyArray<string | null | undefined>): string[] {21function uniqueNonEmptyValues(values: ReadonlyArray<string | null | undefined>): string[] {
14 const seen = new Set<string>();22 const seen = new Set<string>();
15 const unique: string[] = [];23 const unique: string[] = [];
apps/web/src/routes/__root.tsx+2-4
...@@ -33,6 +33,7 @@ import { useSettings } from "../hooks/useSettings";...@@ -33,6 +33,7 @@ import { useSettings } from "../hooks/useSettings";
33import {33import {
34 deriveLogicalProjectKeyFromSettings,34 deriveLogicalProjectKeyFromSettings,
35 derivePhysicalProjectKeyFromPath,35 derivePhysicalProjectKeyFromPath,
36 selectProjectGroupingSettings,
36} from "../logicalProject";37} from "../logicalProject";
37import {38import {
38 getServerConfigUpdatedNotification,39 getServerConfigUpdatedNotification,
...@@ -281,10 +282,7 @@ function EventRouter() {...@@ -281,10 +282,7 @@ function EventRouter() {
281 const setActiveEnvironmentId = useStore((store) => store.setActiveEnvironmentId);282 const setActiveEnvironmentId = useStore((store) => store.setActiveEnvironmentId);
282 const navigate = useNavigate();283 const navigate = useNavigate();
283 const pathname = useLocation({ select: (loc) => loc.pathname });284 const pathname = useLocation({ select: (loc) => loc.pathname });
284 const projectGroupingSettings = useSettings((settings) => ({285 const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
285 sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
286 sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
287 }));
288 const readPathname = useEffectEvent(() => pathname);286 const readPathname = useEffectEvent(() => pathname);
289 const handledBootstrapThreadIdRef = useRef<string | null>(null);287 const handledBootstrapThreadIdRef = useRef<string | null>(null);
290 const seenServerConfigUpdateIdRef = useRef(getServerConfigUpdatedNotification()?.id ?? 0);288 const seenServerConfigUpdateIdRef = useRef(getServerConfigUpdatedNotification()?.id ?? 0);