feat(04-01): wire ContextSwitcher into SidebarHeader and add keyboard shortcut
- Add ContextSwitcher to SidebarHeader Row 1 with visual separator - Add Cmd+Shift+K shortcut to cycle through available contexts - Add SSH status listener in App.tsx to refresh context list on changes - Shortcut guard: disabled during active context switch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ca60158d34
commit
58f4be0a61
3 changed files with 45 additions and 2 deletions
|
|
@ -24,6 +24,15 @@ export const App = (): React.JSX.Element => {
|
||||||
void useStore.getState().initializeContextSystem();
|
void useStore.getState().initializeContextSystem();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Refresh available contexts when SSH connection state changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!window.electronAPI.ssh?.onStatus) return;
|
||||||
|
const cleanup = window.electronAPI.ssh.onStatus(() => {
|
||||||
|
void useStore.getState().fetchAvailableContexts();
|
||||||
|
});
|
||||||
|
return cleanup;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Initialize IPC event listeners (notifications, file changes)
|
// Initialize IPC event listeners (notifications, file changes)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cleanup = initializeNotificationListeners();
|
const cleanup = initializeNotificationListeners();
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { truncateMiddle } from '@renderer/utils/stringUtils';
|
||||||
import { Check, ChevronDown, GitBranch, PanelLeft } from 'lucide-react';
|
import { Check, ChevronDown, GitBranch, PanelLeft } from 'lucide-react';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
|
|
||||||
|
import { ContextSwitcher } from '../common/ContextSwitcher';
|
||||||
import { WorktreeBadge } from '../common/WorktreeBadge';
|
import { WorktreeBadge } from '../common/WorktreeBadge';
|
||||||
|
|
||||||
import type { Worktree, WorktreeSource } from '@renderer/types/data';
|
import type { Worktree, WorktreeSource } from '@renderer/types/data';
|
||||||
|
|
@ -325,7 +326,7 @@ export const SidebarHeader = (): React.JSX.Element => {
|
||||||
{/* ROW 1: Project Identity (Title Bar / Drag Region) */}
|
{/* ROW 1: Project Identity (Title Bar / Drag Region) */}
|
||||||
<div
|
<div
|
||||||
ref={projectDropdownRef}
|
ref={projectDropdownRef}
|
||||||
className="relative flex select-none items-center justify-between pr-2"
|
className="relative flex select-none items-center gap-2 pr-2"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
height: `${HEADER_ROW1_HEIGHT}px`,
|
height: `${HEADER_ROW1_HEIGHT}px`,
|
||||||
|
|
@ -334,6 +335,16 @@ export const SidebarHeader = (): React.JSX.Element => {
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{/* Context Switcher - workspace indicator */}
|
||||||
|
<ContextSwitcher />
|
||||||
|
|
||||||
|
{/* Visual separator */}
|
||||||
|
<div
|
||||||
|
className="h-4 w-px"
|
||||||
|
style={{ backgroundColor: 'var(--color-border)' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Project name dropdown button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsProjectDropdownOpen(!isProjectDropdownOpen)}
|
onClick={() => setIsProjectDropdownOpen(!isProjectDropdownOpen)}
|
||||||
className="flex min-w-0 items-center gap-2 transition-opacity hover:opacity-80"
|
className="flex min-w-0 items-center gap-2 transition-opacity hover:opacity-80"
|
||||||
|
|
@ -356,7 +367,7 @@ export const SidebarHeader = (): React.JSX.Element => {
|
||||||
onClick={toggleSidebar}
|
onClick={toggleSidebar}
|
||||||
onMouseEnter={() => setIsCollapseHovered(true)}
|
onMouseEnter={() => setIsCollapseHovered(true)}
|
||||||
onMouseLeave={() => setIsCollapseHovered(false)}
|
onMouseLeave={() => setIsCollapseHovered(false)}
|
||||||
className="shrink-0 rounded-md p-1.5 transition-colors"
|
className="ml-auto shrink-0 rounded-md p-1.5 transition-colors"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
WebkitAppRegion: 'no-drag',
|
WebkitAppRegion: 'no-drag',
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ export function useKeyboardShortcuts(): void {
|
||||||
focusPane,
|
focusPane,
|
||||||
splitPane,
|
splitPane,
|
||||||
closePane,
|
closePane,
|
||||||
|
availableContexts,
|
||||||
|
activeContextId,
|
||||||
|
switchContext,
|
||||||
|
isContextSwitching,
|
||||||
} = useStore(
|
} = useStore(
|
||||||
useShallow((s) => ({
|
useShallow((s) => ({
|
||||||
openTabs: s.openTabs,
|
openTabs: s.openTabs,
|
||||||
|
|
@ -61,6 +65,10 @@ export function useKeyboardShortcuts(): void {
|
||||||
focusPane: s.focusPane,
|
focusPane: s.focusPane,
|
||||||
splitPane: s.splitPane,
|
splitPane: s.splitPane,
|
||||||
closePane: s.closePane,
|
closePane: s.closePane,
|
||||||
|
availableContexts: s.availableContexts,
|
||||||
|
activeContextId: s.activeContextId,
|
||||||
|
switchContext: s.switchContext,
|
||||||
|
isContextSwitching: s.isContextSwitching,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -205,6 +213,17 @@ export function useKeyboardShortcuts(): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cmd+Shift+K: Cycle to next workspace context
|
||||||
|
if (event.key === 'k' && event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!isContextSwitching && availableContexts.length > 1) {
|
||||||
|
const currentIndex = availableContexts.findIndex((c) => c.id === activeContextId);
|
||||||
|
const nextIndex = (currentIndex + 1) % availableContexts.length;
|
||||||
|
void switchContext(availableContexts[nextIndex].id);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Cmd+K: Open command palette for global search
|
// Cmd+K: Open command palette for global search
|
||||||
if (event.key === 'k') {
|
if (event.key === 'k') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -280,5 +299,9 @@ export function useKeyboardShortcuts(): void {
|
||||||
focusPane,
|
focusPane,
|
||||||
splitPane,
|
splitPane,
|
||||||
closePane,
|
closePane,
|
||||||
|
availableContexts,
|
||||||
|
activeContextId,
|
||||||
|
switchContext,
|
||||||
|
isContextSwitching,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue