Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@
import { useSettings } from "../hooks/useSettings";
import { resolveAppModelSelectionForInstance } from "../modelSelection";
import { isTerminalFocused } from "../lib/terminalFocus";
import { deriveLogicalProjectKeyFromSettings } from "../logicalProject";
import {
deriveLogicalProjectKeyFromSettings,
selectProjectGroupingSettings,
} from "../logicalProject";
import {
reconnectSavedEnvironment,
useSavedEnvironmentRegistryStore,
Expand Down Expand Up @@ -942,10 +945,7 @@
},
[],
);
const projectGroupingSettings = useSettings((settings) => ({
sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
}));
const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
const logicalProjectEnvironments = useMemo(() => {
if (!activeProject) return [];
const logicalKey = deriveLogicalProjectKeyFromSettings(activeProject, projectGroupingSettings);
Expand Down Expand Up @@ -1780,7 +1780,7 @@
);

const focusComposer = useCallback(() => {
composerRef.current?.focusAtEnd();

Check warning on line 1783 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
}, []);
const scheduleComposerFocus = useCallback(() => {
window.requestAnimationFrame(() => {
Expand All @@ -1788,7 +1788,7 @@
});
}, [focusComposer]);
const addTerminalContextToDraft = useCallback((selection: TerminalContextSelection) => {
composerRef.current?.addTerminalContext(selection);

Check warning on line 1791 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
}, []);
const setTerminalOpen = useCallback(
(open: boolean) => {
Expand Down Expand Up @@ -2467,7 +2467,7 @@
const shortcutContext = {
terminalFocus: isTerminalFocused(),
terminalOpen: Boolean(terminalState.terminalOpen),
modelPickerOpen: composerRef.current?.isModelPickerOpen() ?? false,

Check warning on line 2470 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

react-hooks(exhaustive-deps)

React Hook useEffect has a missing dependency: 'composerRef.current'
};

const command = resolveShortcutCommand(event, keybindings, {
Expand Down Expand Up @@ -3019,7 +3019,7 @@
};
});
promptRef.current = "";
composerRef.current?.resetCursorState({ cursor: 0 });

Check warning on line 3022 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
},
[activePendingProgress?.activeQuestion, activePendingUserInput],
);
Expand All @@ -3046,7 +3046,7 @@
),
},
}));
const snapshot = composerRef.current?.readSnapshot();

Check warning on line 3049 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
if (
snapshot?.value !== value ||
snapshot.cursor !== nextCursor ||
Expand Down Expand Up @@ -3109,7 +3109,7 @@
return;
}

const sendCtx = composerRef.current?.getSendContext();

Check warning on line 3112 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
if (!sendCtx) {
return;
}
Expand Down Expand Up @@ -3246,7 +3246,7 @@
return;
}

const sendCtx = composerRef.current?.getSendContext();

Check warning on line 3249 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
if (!sendCtx) {
return;
}
Expand Down
11 changes: 3 additions & 8 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ import {
derivePhysicalProjectKey,
deriveProjectGroupingOverrideKey,
getProjectOrderKey,
selectProjectGroupingSettings,
} from "../logicalProject";
import {
useSavedEnvironmentRegistryStore,
Expand Down Expand Up @@ -940,10 +941,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
const defaultThreadEnvMode = useSettings<ThreadEnvMode>(
(settings) => settings.defaultThreadEnvMode,
);
const projectGroupingSettings = useSettings((settings) => ({
sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
}));
const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
const { updateSettings } = useUpdateSettings();
const sidebarThreadPreviewCount = useSettings<SidebarThreadPreviewCount>(
(settings) => settings.sidebarThreadPreviewCount,
Expand Down Expand Up @@ -2800,10 +2798,7 @@ export default function Sidebar() {
const sidebarThreadSortOrder = useSettings((s) => s.sidebarThreadSortOrder);
const sidebarProjectSortOrder = useSettings((s) => s.sidebarProjectSortOrder);
const sidebarProjectGroupingMode = useSettings((s) => s.sidebarProjectGroupingMode);
const projectGroupingSettings = useSettings((settings) => ({
sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
}));
const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
const sidebarThreadPreviewCount = useSettings((s) => s.sidebarThreadPreviewCount);
const { updateSettings } = useUpdateSettings();
const { handleNewThread } = useNewThreadHandler();
Expand Down
11 changes: 6 additions & 5 deletions apps/web/src/hooks/useHandleNewThread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {
} from "../composerDraftStore";
import { newDraftId, newThreadId } from "../lib/utils";
import { orderItemsByPreferredIds } from "../components/Sidebar.logic";
import { deriveLogicalProjectKeyFromSettings, getProjectOrderKey } from "../logicalProject";
import {
deriveLogicalProjectKeyFromSettings,
getProjectOrderKey,
selectProjectGroupingSettings,
} from "../logicalProject";
import { selectProjectsAcrossEnvironments, useStore } from "../store";
import { createThreadSelectorByRef } from "../storeSelectors";
import { resolveThreadRouteTarget } from "../threadRoutes";
Expand All @@ -19,10 +23,7 @@ import { useSettings } from "./useSettings";

function useNewThreadState() {
const projects = useStore(useShallow((store) => selectProjectsAcrossEnvironments(store)));
const projectGroupingSettings = useSettings((settings) => ({
sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
}));
const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
const router = useRouter();
const getCurrentRouteTarget = useCallback(() => {
const currentRouteParams = router.state.matches[router.state.matches.length - 1]?.params ?? {};
Expand Down
8 changes: 8 additions & 0 deletions apps/web/src/logicalProject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { scopedProjectKey, scopeProjectRef } from "@t3tools/client-runtime";
import type { ScopedProjectRef, SidebarProjectGroupingMode } from "@t3tools/contracts";
import type { UnifiedSettings } from "@t3tools/contracts/settings";
import { normalizeProjectPathForComparison } from "./lib/projectPaths";
import type { Project } from "./types";

Expand All @@ -10,6 +11,13 @@ export interface ProjectGroupingSettings {

export type ProjectGroupingMode = SidebarProjectGroupingMode;

export function selectProjectGroupingSettings(settings: UnifiedSettings): ProjectGroupingSettings {
return {
sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
};
}

function uniqueNonEmptyValues(values: ReadonlyArray<string | null | undefined>): string[] {
const seen = new Set<string>();
const unique: string[] = [];
Expand Down
6 changes: 2 additions & 4 deletions apps/web/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { useSettings } from "../hooks/useSettings";
import {
deriveLogicalProjectKeyFromSettings,
derivePhysicalProjectKeyFromPath,
selectProjectGroupingSettings,
} from "../logicalProject";
import {
getServerConfigUpdatedNotification,
Expand Down Expand Up @@ -281,10 +282,7 @@ function EventRouter() {
const setActiveEnvironmentId = useStore((store) => store.setActiveEnvironmentId);
const navigate = useNavigate();
const pathname = useLocation({ select: (loc) => loc.pathname });
const projectGroupingSettings = useSettings((settings) => ({
sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode,
sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides,
}));
const projectGroupingSettings = useSettings(selectProjectGroupingSettings);
const readPathname = useEffectEvent(() => pathname);
const handledBootstrapThreadIdRef = useRef<string | null>(null);
const seenServerConfigUpdateIdRef = useRef(getServerConfigUpdatedNotification()?.id ?? 0);
Expand Down
12 changes: 12 additions & 0 deletions artifacts/react-performance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
React performance scan artifacts for the project grouping selector fix.

- `before-react-scan-project-grouping.webm`: before the fix, captured with React Scan injected before React mounts.
- `after-react-scan-project-grouping.webm`: after the fix, captured with the same authenticated navigation flow.

Capture flow:

1. Start the web dev server with React Scan injected through Playwright `addInitScript`.
2. Pair against a clean local server home.
3. Open `/`, navigate to `/settings/general`, then return to `/`.

The fix stabilizes the project grouping settings selector shared by Sidebar, ChatView, root event routing, and new-thread creation so unrelated parent renders reuse the same grouping object instead of invalidating downstream memoized project derivations.
Binary file not shown.
Binary file not shown.
Loading