fix: wire modifier key detection into SessionItem click handler

Ctrl+Click (⌘+Click on Mac) and Shift+Click were not triggering
multi-select mode because modifier key detection in handleSessionClick
was never called from the actual click chain.

Root cause:
- SessionItem.handleClick only checked isSelecting state, never
  inspected modifier keys on the mouse event
- ProjectGroup passed onSessionSelect directly (plain navigation),
  bypassing handleSessionClick entirely
- Search results called handleSessionClick with a hardcoded fake event
  where all modifier keys were false

Fix:
- Move modifier key detection into SessionItem.handleClick, which
  receives the real React.MouseEvent with accurate modifier state
- Add onEnterSelectionMode and onShiftSelect props to SessionItem
- Thread new callbacks through ProjectGroup
- Remove dead handleSessionClick function from Sidebar
- Fix search results to use the new callback-based approach

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-15 08:27:44 +02:00
co-authored by Copilot
parent d6008da0af
commit c22bd32f97
+43 -23
View File
@@ -194,6 +194,8 @@ function SessionItem({
isSelected, isSelected,
selectionIndex, selectionIndex,
onToggleSelection, onToggleSelection,
onEnterSelectionMode,
onShiftSelect,
}: { }: {
session: SessionRecord; session: SessionRecord;
workflow?: WorkflowDefinition; workflow?: WorkflowDefinition;
@@ -207,6 +209,8 @@ function SessionItem({
isSelected?: boolean; isSelected?: boolean;
selectionIndex?: number; selectionIndex?: number;
onToggleSelection?: () => void; onToggleSelection?: () => void;
onEnterSelectionMode?: () => void;
onShiftSelect?: () => void;
}) { }) {
const isRunning = session.status === 'running'; const isRunning = session.status === 'running';
const isError = session.status === 'error'; const isError = session.status === 'error';
@@ -216,7 +220,7 @@ function SessionItem({
const visual = modeVisuals[mode]; const visual = modeVisuals[mode];
const ModeIcon = visual.icon; const ModeIcon = visual.icon;
const agentCount = workflow ? resolveWorkflowAgentNodes(workflow).length : 1; const agentCount = workflow ? resolveWorkflowAgentNodes(workflow).length : 1;
const isSelectDisabled = isRunning && isSelecting; const isSelectDisabled = isRunning;
const [renameText, setRenameText] = useState(session.title); const [renameText, setRenameText] = useState(session.title);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@@ -250,10 +254,25 @@ function SessionItem({
function handleClick(e: React.MouseEvent) { function handleClick(e: React.MouseEvent) {
if (isRenaming) return; if (isRenaming) return;
// Already in selection mode — toggle or range-select
if (isSelecting) { if (isSelecting) {
if (!isSelectDisabled) onToggleSelection?.(); if (isSelectDisabled) return;
if (e.shiftKey) {
onShiftSelect?.();
} else {
onToggleSelection?.();
}
return; return;
} }
// Not in selection mode — check for modifier key to enter it
const modKey = isMac ? e.metaKey : e.ctrlKey;
if (modKey && !isSelectDisabled) {
onEnterSelectionMode?.();
return;
}
onSelect(); onSelect();
} }
@@ -438,6 +457,8 @@ function ProjectGroup({
isSelecting, isSelecting,
isSelected, isSelected,
onToggleSelection, onToggleSelection,
onEnterSelectionMode,
onShiftSelect,
}: { }: {
project: ProjectRecord; project: ProjectRecord;
sessions: SessionRecord[]; sessions: SessionRecord[];
@@ -455,6 +476,8 @@ function ProjectGroup({
isSelecting?: boolean; isSelecting?: boolean;
isSelected?: (sessionId: string) => boolean; isSelected?: (sessionId: string) => boolean;
onToggleSelection?: (sessionId: string) => void; onToggleSelection?: (sessionId: string) => void;
onEnterSelectionMode?: (sessionId: string) => void;
onShiftSelect?: (sessionId: string) => void;
}){ }){
const [expanded, setExpanded] = useState(true); const [expanded, setExpanded] = useState(true);
const isScratchpad = isScratchpadProject(project); const isScratchpad = isScratchpadProject(project);
@@ -592,6 +615,8 @@ function ProjectGroup({
isSelected={isSelected?.(session.id)} isSelected={isSelected?.(session.id)}
selectionIndex={index} selectionIndex={index}
onToggleSelection={() => onToggleSelection?.(session.id)} onToggleSelection={() => onToggleSelection?.(session.id)}
onEnterSelectionMode={() => onEnterSelectionMode?.(session.id)}
onShiftSelect={() => onShiftSelect?.(session.id)}
/> />
))} ))}
{onNewSession ? ( {onNewSession ? (
@@ -728,27 +753,10 @@ export function Sidebar({
[selection.selectedIds, workspace.sessions], [selection.selectedIds, workspace.sessions],
); );
function handleSessionClick(sessionId: string, e: React.MouseEvent) { function handleEnterSelectionMode(sessionId: string) {
const modKey = isMac ? e.metaKey : e.ctrlKey;
const session = workspace.sessions.find((s) => s.id === sessionId); const session = workspace.sessions.find((s) => s.id === sessionId);
const isRunning = session?.status === 'running'; if (session?.status === 'running') return;
selection.enterSelectionMode(sessionId);
if (selection.isSelecting) {
if (isRunning) return;
if (e.shiftKey) {
selection.rangeSelect(sessionId, allVisibleIds);
} else {
selection.toggle(sessionId);
}
return;
}
if (modKey && !isRunning) {
selection.enterSelectionMode(sessionId);
return;
}
onSessionSelect(sessionId);
} }
function handleToggleSelection(sessionId: string) { function handleToggleSelection(sessionId: string) {
@@ -757,6 +765,12 @@ export function Sidebar({
selection.toggle(sessionId); selection.toggle(sessionId);
} }
function handleShiftSelect(sessionId: string) {
const session = workspace.sessions.find((s) => s.id === sessionId);
if (session?.status === 'running') return;
selection.rangeSelect(sessionId, allVisibleIds);
}
const handleBatchArchive = useCallback(() => { const handleBatchArchive = useCallback(() => {
const ids = [...selection.selectedIds]; const ids = [...selection.selectedIds];
const isArchived = !allSelectedArchived; const isArchived = !allSelectedArchived;
@@ -873,7 +887,7 @@ export function Sidebar({
isActive={workspace.selectedSessionId === session.id} isActive={workspace.selectedSessionId === session.id}
isRenaming={renamingSessionId === session.id} isRenaming={renamingSessionId === session.id}
key={session.id} key={session.id}
onSelect={() => handleSessionClick(session.id, { ctrlKey: false, metaKey: false, shiftKey: false } as React.MouseEvent)} onSelect={() => onSessionSelect(session.id)}
onOpenMenu={(e) => handleOpenMenu(session.id, e)} onOpenMenu={(e) => handleOpenMenu(session.id, e)}
onRenameSubmit={(title) => handleRenameSubmit(session.id, title)} onRenameSubmit={(title) => handleRenameSubmit(session.id, title)}
onRenameCancel={() => setRenamingSessionId(undefined)} onRenameCancel={() => setRenamingSessionId(undefined)}
@@ -883,6 +897,8 @@ export function Sidebar({
isSelected={selection.isSelected(session.id)} isSelected={selection.isSelected(session.id)}
selectionIndex={index} selectionIndex={index}
onToggleSelection={() => handleToggleSelection(session.id)} onToggleSelection={() => handleToggleSelection(session.id)}
onEnterSelectionMode={() => handleEnterSelectionMode(session.id)}
onShiftSelect={() => handleShiftSelect(session.id)}
/> />
)) ))
)} )}
@@ -911,6 +927,8 @@ export function Sidebar({
isSelecting={selection.isSelecting} isSelecting={selection.isSelecting}
isSelected={selection.isSelected} isSelected={selection.isSelected}
onToggleSelection={handleToggleSelection} onToggleSelection={handleToggleSelection}
onEnterSelectionMode={handleEnterSelectionMode}
onShiftSelect={handleShiftSelect}
/> />
</div> </div>
)} )}
@@ -962,6 +980,8 @@ export function Sidebar({
isSelecting={selection.isSelecting} isSelecting={selection.isSelecting}
isSelected={selection.isSelected} isSelected={selection.isSelected}
onToggleSelection={handleToggleSelection} onToggleSelection={handleToggleSelection}
onEnterSelectionMode={handleEnterSelectionMode}
onShiftSelect={handleShiftSelect}
/> />
))} ))}
</div> </div>