Skip to content

Commit 632fd7d

Browse files
perf(web): avoid per-row sidebar project lookups (#3)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e786266 commit 632fd7d

3 files changed

Lines changed: 57 additions & 14 deletions

File tree

apps/web/src/components/Sidebar.logic.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
resolveProjectStatusIndicator,
1717
resolveSidebarNewThreadSeedContext,
1818
resolveSidebarNewThreadEnvMode,
19+
resolveSidebarThreadGitCwd,
1920
resolveThreadRowClassName,
2021
resolveThreadStatusPill,
2122
shouldClearThreadSelectionOnMouseDown,
@@ -69,6 +70,38 @@ describe("hasUnseenCompletion", () => {
6970
});
7071
});
7172

73+
describe("resolveSidebarThreadGitCwd", () => {
74+
it("uses the worktree path before project cwd fallbacks", () => {
75+
expect(
76+
resolveSidebarThreadGitCwd({
77+
worktreePath: "/repo/.worktrees/thread",
78+
threadProjectCwd: "/repo/thread-project",
79+
projectCwd: "/repo/project",
80+
}),
81+
).toBe("/repo/.worktrees/thread");
82+
});
83+
84+
it("uses the owning thread project cwd before the displayed project cwd", () => {
85+
expect(
86+
resolveSidebarThreadGitCwd({
87+
worktreePath: null,
88+
threadProjectCwd: "/repo/thread-project",
89+
projectCwd: "/repo/display-project",
90+
}),
91+
).toBe("/repo/thread-project");
92+
});
93+
94+
it("falls back to the displayed project cwd when the thread project is unknown", () => {
95+
expect(
96+
resolveSidebarThreadGitCwd({
97+
worktreePath: null,
98+
threadProjectCwd: null,
99+
projectCwd: "/repo/display-project",
100+
}),
101+
).toBe("/repo/display-project");
102+
});
103+
});
104+
72105
describe("createThreadJumpHintVisibilityController", () => {
73106
beforeEach(() => {
74107
vi.useFakeTimers();

apps/web/src/components/Sidebar.logic.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ export function resolveSidebarNewThreadEnvMode(input: {
167167
return input.requestedEnvMode ?? input.defaultEnvMode;
168168
}
169169

170+
export function resolveSidebarThreadGitCwd(input: {
171+
worktreePath: string | null;
172+
threadProjectCwd: string | null;
173+
projectCwd: string | null;
174+
}): string | null {
175+
return input.worktreePath ?? input.threadProjectCwd ?? input.projectCwd;
176+
}
177+
170178
export function resolveSidebarNewThreadSeedContext(input: {
171179
projectId: string;
172180
defaultEnvMode: SidebarNewThreadEnvMode;

apps/web/src/components/Sidebar.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ import { isElectron } from "../env";
6464
import { isTerminalFocused } from "../lib/terminalFocus";
6565
import { isMacPlatform, newCommandId } from "../lib/utils";
6666
import {
67-
selectProjectByRef,
6867
selectProjectsAcrossEnvironments,
6968
selectSidebarThreadsForProjectRefs,
7069
selectSidebarThreadsAcrossEnvironments,
@@ -156,6 +155,7 @@ import {
156155
resolveProjectStatusIndicator,
157156
resolveSidebarNewThreadSeedContext,
158157
resolveSidebarNewThreadEnvMode,
158+
resolveSidebarThreadGitCwd,
159159
resolveThreadRowClassName,
160160
resolveThreadStatusPill,
161161
orderItemsByPreferredIds,
@@ -165,7 +165,6 @@ import {
165165
useThreadJumpHintVisibility,
166166
ThreadStatusPill,
167167
} from "./Sidebar.logic";
168-
import { sortThreads } from "../lib/threadSort";
169168
import { SidebarUpdatePill } from "./sidebar/SidebarUpdatePill";
170169
import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
171170
import { CommandDialogTrigger } from "./ui/command";
@@ -264,6 +263,7 @@ function buildThreadJumpLabelMap(input: {
264263
interface SidebarThreadRowProps {
265264
thread: SidebarThreadSummary;
266265
projectCwd: string | null;
266+
threadProjectCwd: string | null;
267267
orderedProjectThreadKeys: readonly string[];
268268
isActive: boolean;
269269
jumpLabel: string | null;
@@ -337,6 +337,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
337337
projectKey,
338338
sortable,
339339
thread,
340+
threadProjectCwd,
340341
} = props;
341342
const threadRef = scopeThreadRef(thread.environmentId, thread.id);
342343
const threadKey = scopedThreadKey(threadRef);
@@ -359,18 +360,11 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
359360
const threadEnvironmentLabel = isRemoteThread
360361
? (remoteEnvLabel ?? remoteEnvSavedLabel ?? "Remote")
361362
: null;
362-
// For grouped projects, the thread may belong to a different environment
363-
// than the representative project. Look up the thread's own project cwd
364-
// so git status (and thus PR detection) queries the correct path.
365-
const threadProjectCwd = useStore(
366-
useMemo(
367-
() => (state: import("../store").AppState) =>
368-
selectProjectByRef(state, scopeProjectRef(thread.environmentId, thread.projectId))?.cwd ??
369-
null,
370-
[thread.environmentId, thread.projectId],
371-
),
372-
);
373-
const gitCwd = thread.worktreePath ?? threadProjectCwd ?? props.projectCwd;
363+
const gitCwd = resolveSidebarThreadGitCwd({
364+
worktreePath: thread.worktreePath,
365+
threadProjectCwd,
366+
projectCwd: props.projectCwd,
367+
});
374368
const gitStatus = useGitStatus({
375369
environmentId: thread.environmentId,
376370
cwd: thread.branch != null ? gitCwd : null,
@@ -794,6 +788,7 @@ interface SidebarProjectThreadListProps {
794788
orderedProjectThreadKeys: readonly string[];
795789
pinnedThreadKeys: readonly string[];
796790
renderedThreads: readonly SidebarThreadSummary[];
791+
memberProjectByScopedKey: ReadonlyMap<string, SidebarProjectGroupMember>;
797792
showEmptyThreadState: boolean;
798793
shouldShowThreadPanel: boolean;
799794
isThreadListExpanded: boolean;
@@ -851,6 +846,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(
851846
orderedProjectThreadKeys,
852847
pinnedThreadKeys,
853848
renderedThreads,
849+
memberProjectByScopedKey,
854850
showEmptyThreadState,
855851
shouldShowThreadPanel,
856852
isThreadListExpanded,
@@ -916,9 +912,13 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(
916912
const renderThreadRow = useCallback(
917913
(thread: SidebarThreadSummary) => {
918914
const threadKey = scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id));
915+
const threadProjectKey = scopedProjectKey(
916+
scopeProjectRef(thread.environmentId, thread.projectId),
917+
);
919918
const rowProps: SidebarThreadRowProps = {
920919
thread,
921920
projectCwd,
921+
threadProjectCwd: memberProjectByScopedKey.get(threadProjectKey)?.cwd ?? null,
922922
orderedProjectThreadKeys,
923923
isActive: activeRouteThreadKey === threadKey,
924924
jumpLabel: threadJumpLabelByKey.get(threadKey) ?? null,
@@ -963,6 +963,7 @@ const SidebarProjectThreadList = memo(function SidebarProjectThreadList(
963963
handleMultiSelectContextMenu,
964964
handleThreadClick,
965965
handleThreadContextMenu,
966+
memberProjectByScopedKey,
966967
navigateToThread,
967968
openPrLink,
968969
orderedProjectThreadKeys,
@@ -2240,6 +2241,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
22402241
orderedProjectThreadKeys={orderedProjectThreadKeys}
22412242
pinnedThreadKeys={pinnedThreadKeys}
22422243
renderedThreads={renderedThreads}
2244+
memberProjectByScopedKey={memberProjectByScopedKey}
22432245
showEmptyThreadState={showEmptyThreadState}
22442246
shouldShowThreadPanel={shouldShowThreadPanel}
22452247
isThreadListExpanded={isThreadListExpanded}

0 commit comments

Comments
 (0)