Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { notificationService } from '@/shared/notification-system';
import WorkspaceItem from './WorkspaceItem';
import SessionsSection, { type WorkspaceSessionScope } from '../sessions/SessionsSection';
import { isRemoteWorkspace } from '@/shared/types';
import { isSamePath } from '@/shared/utils/pathUtils';
import { useWorkspaceSessionViewStore } from '../../workspaceSessionView';
import {
isWorkspaceBackedSessionGroupActive,
projectWorkspaceBackedSessionGroups,
type SessionNavigationScope,
} from '../../sessionNavigationProjection';
Expand Down Expand Up @@ -65,9 +65,6 @@ const WorkspaceListSection: React.FC<WorkspaceListSectionProps> = ({ variant })
[sessionGroups],
);
const activeWorkspace = openedWorkspacesList.find(workspace => workspace.id === activeWorkspaceId);
const activeProjectPath = activeWorkspace?.worktree && !activeWorkspace.worktree.isMain
? activeWorkspace.worktree.mainRepoPath
: activeWorkspace?.rootPath;
const emptyLabel = variant === 'assistants'
? t('nav.workspaces.emptyAssistants')
: variant === 'projects'
Expand Down Expand Up @@ -289,10 +286,7 @@ const WorkspaceListSection: React.FC<WorkspaceListSectionProps> = ({ variant })
>
<WorkspaceItem
workspace={workspace}
isActive={
workspace.id === activeWorkspaceId ||
Boolean(activeProjectPath && isSamePath(workspace.rootPath, activeProjectPath))
}
isActive={isWorkspaceBackedSessionGroupActive(workspace, activeWorkspace)}
isSingle={workspaces.length === 1}
draggable={workspaces.length > 1}
isDragging={draggedWorkspaceId === workspace.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
WorkspaceType,
type WorkspaceInfo,
} from '@/shared/types';
import { projectWorkspaceBackedSessionGroups } from './sessionNavigationProjection';
import {
isWorkspaceBackedSessionGroupActive,
projectWorkspaceBackedSessionGroups,
} from './sessionNavigationProjection';

const createWorkspace = (
id: string,
Expand Down Expand Up @@ -88,3 +91,38 @@ describe('projectWorkspaceBackedSessionGroups', () => {
).map(group => group.workspace.id)).toEqual(['linked-worktree']);
});
});

describe('isWorkspaceBackedSessionGroupActive', () => {
it('distinguishes identical paths opened through different remote connections', () => {
const firstRemote = createWorkspace('remote-first', WorkspaceKind.Remote, {
rootPath: '/workspace',
connectionId: 'ssh-first',
sshHost: 'host-a.example',
});
const secondRemote = createWorkspace('remote-second', WorkspaceKind.Remote, {
rootPath: '/workspace',
connectionId: 'ssh-second',
sshHost: 'host-b.example',
});

expect(isWorkspaceBackedSessionGroupActive(firstRemote, firstRemote)).toBe(true);
expect(isWorkspaceBackedSessionGroupActive(secondRemote, firstRemote)).toBe(false);
});

it('keeps the canonical local project active for its selected worktree', () => {
const canonicalProject = createWorkspace('canonical-project', WorkspaceKind.Normal, {
rootPath: '/repo',
});
const linkedWorktree = createWorkspace('linked-worktree', WorkspaceKind.Normal, {
rootPath: '/repo/.worktrees/feature',
worktree: {
path: '/repo/.worktrees/feature',
mainRepoPath: '/repo',
branch: 'feature',
isMain: false,
},
});

expect(isWorkspaceBackedSessionGroupActive(canonicalProject, linkedWorktree)).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { WorkspaceInfo } from '@/shared/types';
import { isLinkedWorktreeWorkspace } from '@/shared/types';
import { isLinkedWorktreeWorkspace, isRemoteWorkspace } from '@/shared/types';
import { isSamePath } from '@/shared/utils/pathUtils';

export type SessionNavigationScope = 'all' | 'assistants' | 'projects';
Expand All @@ -19,6 +19,28 @@ export interface WorkspaceBackedSessionGroup {
workspace: WorkspaceInfo;
}

/**
* Resolve the active sidebar group without treating a remote POSIX path as a
* workspace identity. The same path can be open on multiple remote hosts, so
* remote workspaces must match by their stable workspace id. Local path
* matching remains available for a linked worktree whose canonical project is
* the visible navigation group.
*/
export function isWorkspaceBackedSessionGroupActive(
workspace: WorkspaceInfo,
activeWorkspace: WorkspaceInfo | null | undefined,
): boolean {
if (!activeWorkspace) return false;
if (workspace.id === activeWorkspace.id) return true;
if (isRemoteWorkspace(workspace) || isRemoteWorkspace(activeWorkspace)) return false;

const activeProjectPath = activeWorkspace.worktree && !activeWorkspace.worktree.isMain
? activeWorkspace.worktree.mainRepoPath
: activeWorkspace.rootPath;

return Boolean(activeProjectPath && isSamePath(workspace.rootPath, activeProjectPath));
}

const isWorkspaceInScope = (
workspace: WorkspaceInfo,
scope: SessionNavigationScope,
Expand Down
Loading