Skip to content

Commit 1725a5e

Browse files
committed
🤖 Fix Ctrl+J/K workspace navigation to use sorted order
Navigation now follows visual order displayed in sidebar. **Problem:** - PR #205 added recency-based sorting to workspace display - Ctrl+J/K navigation still used unsorted config order - Caused confusion: pressing next didn't select next visible workspace **Solution:** - Move sorting logic from ProjectSidebar to App.tsx - Share sorted list between navigation and display - Both now use same recency-sorted order **Changes:** - Added sortedWorkspacesByProject memo in App.tsx - Updated handleNavigateWorkspace to use sorted list - Pass sorted list through LeftSidebar to ProjectSidebar - Remove duplicate sorting logic from ProjectSidebar
1 parent 539d428 commit 1725a5e

File tree

3 files changed

+36
-31
lines changed

3 files changed

+36
-31
lines changed

src/App.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useCallback, useRef } from "react";
1+
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
22
import styled from "@emotion/styled";
33
import { Global, css } from "@emotion/react";
44
import { GlobalColors } from "./styles/colors";
@@ -287,28 +287,51 @@ function AppInner() {
287287
[]
288288
);
289289

290+
// Sort workspaces by recency (most recent first)
291+
// This ensures navigation follows the visual order displayed in the sidebar
292+
const sortedWorkspacesByProject = useMemo(() => {
293+
const result = new Map<string, ProjectConfig["workspaces"]>();
294+
for (const [projectPath, config] of projects) {
295+
result.set(
296+
projectPath,
297+
config.workspaces.slice().sort((a, b) => {
298+
const aMeta = workspaceMetadata.get(a.path);
299+
const bMeta = workspaceMetadata.get(b.path);
300+
if (!aMeta || !bMeta) return 0;
301+
302+
// Get timestamp of most recent user message (0 if never used)
303+
const aTimestamp = workspaceRecency[aMeta.id] ?? 0;
304+
const bTimestamp = workspaceRecency[bMeta.id] ?? 0;
305+
return bTimestamp - aTimestamp;
306+
})
307+
);
308+
}
309+
return result;
310+
}, [projects, workspaceMetadata, workspaceRecency]);
311+
290312
const handleNavigateWorkspace = useCallback(
291313
(direction: "next" | "prev") => {
292314
if (!selectedWorkspace) return;
293315

294-
const projectConfig = projects.get(selectedWorkspace.projectPath);
295-
if (!projectConfig || projectConfig.workspaces.length <= 1) return;
316+
// Use sorted workspaces to match visual order in sidebar
317+
const sortedWorkspaces = sortedWorkspacesByProject.get(selectedWorkspace.projectPath);
318+
if (!sortedWorkspaces || sortedWorkspaces.length <= 1) return;
296319

297-
// Find current workspace index
298-
const currentIndex = projectConfig.workspaces.findIndex(
320+
// Find current workspace index in sorted list
321+
const currentIndex = sortedWorkspaces.findIndex(
299322
(ws) => ws.path === selectedWorkspace.workspacePath
300323
);
301324
if (currentIndex === -1) return;
302325

303326
// Calculate next/prev index with wrapping
304327
let targetIndex: number;
305328
if (direction === "next") {
306-
targetIndex = (currentIndex + 1) % projectConfig.workspaces.length;
329+
targetIndex = (currentIndex + 1) % sortedWorkspaces.length;
307330
} else {
308-
targetIndex = currentIndex === 0 ? projectConfig.workspaces.length - 1 : currentIndex - 1;
331+
targetIndex = currentIndex === 0 ? sortedWorkspaces.length - 1 : currentIndex - 1;
309332
}
310333

311-
const targetWorkspace = projectConfig.workspaces[targetIndex];
334+
const targetWorkspace = sortedWorkspaces[targetIndex];
312335
if (!targetWorkspace) return;
313336

314337
const metadata = workspaceMetadata.get(targetWorkspace.path);
@@ -321,7 +344,7 @@ function AppInner() {
321344
workspaceId: metadata.id,
322345
});
323346
},
324-
[selectedWorkspace, projects, workspaceMetadata, setSelectedWorkspace]
347+
[selectedWorkspace, sortedWorkspacesByProject, workspaceMetadata, setSelectedWorkspace]
325348
);
326349

327350
// Register command sources with registry
@@ -555,6 +578,7 @@ function AppInner() {
555578
onGetSecrets={handleGetSecrets}
556579
onUpdateSecrets={handleUpdateSecrets}
557580
workspaceRecency={workspaceRecency}
581+
sortedWorkspacesByProject={sortedWorkspacesByProject}
558582
/>
559583
<MainContent>
560584
<ContentArea>

src/components/LeftSidebar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ interface LeftSidebarProps {
4444
onGetSecrets: (projectPath: string) => Promise<Secret[]>;
4545
onUpdateSecrets: (projectPath: string, secrets: Secret[]) => Promise<void>;
4646
workspaceRecency: Record<string, number>;
47+
sortedWorkspacesByProject: Map<string, ProjectConfig["workspaces"]>;
4748
}
4849

4950
export function LeftSidebar(props: LeftSidebarProps) {

src/components/ProjectSidebar.tsx

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ interface ProjectSidebarProps {
586586
onGetSecrets: (projectPath: string) => Promise<Secret[]>;
587587
onUpdateSecrets: (projectPath: string, secrets: Secret[]) => Promise<void>;
588588
workspaceRecency: Record<string, number>;
589+
sortedWorkspacesByProject: Map<string, Workspace[]>;
589590
}
590591

591592
const ProjectSidebar: React.FC<ProjectSidebarProps> = ({
@@ -606,32 +607,11 @@ const ProjectSidebar: React.FC<ProjectSidebarProps> = ({
606607
onGetSecrets,
607608
onUpdateSecrets,
608609
workspaceRecency,
610+
sortedWorkspacesByProject,
609611
}) => {
610612
// Subscribe to git status updates (causes this component to re-render every 10s)
611613
const gitStatus = useGitStatus();
612614

613-
// Sort workspaces by last user message (most recent first)
614-
// workspaceRecency only updates when timestamps actually change (stable reference optimization)
615-
const sortedWorkspacesByProject = useMemo(() => {
616-
const result = new Map<string, Workspace[]>();
617-
for (const [projectPath, config] of projects) {
618-
result.set(
619-
projectPath,
620-
config.workspaces.slice().sort((a, b) => {
621-
const aMeta = workspaceMetadata.get(a.path);
622-
const bMeta = workspaceMetadata.get(b.path);
623-
if (!aMeta || !bMeta) return 0;
624-
625-
// Get timestamp of most recent user message (0 if never used)
626-
const aTimestamp = workspaceRecency[aMeta.id] ?? 0;
627-
const bTimestamp = workspaceRecency[bMeta.id] ?? 0;
628-
return bTimestamp - aTimestamp;
629-
})
630-
);
631-
}
632-
return result;
633-
}, [projects, workspaceMetadata, workspaceRecency]);
634-
635615
// Store as array in localStorage, convert to Set for usage
636616
const [expandedProjectsArray, setExpandedProjectsArray] = usePersistedState<string[]>(
637617
"expandedProjects",

0 commit comments

Comments
 (0)