feat(toggleMode): restore file preview tab when returning from terminal

When pressing cmd+j to switch between AI/terminal mode, the previously
active file preview tab is now saved and restored. This allows users
to view a file, switch to terminal, run commands, then switch back and
have the same file preview re-opened automatically.

Added preTerminalFileTabIdRef to UILayoutContext to track the active
file tab before switching to terminal mode. Updated toggleInputMode
to save/restore this state.
This commit is contained in:
Pedram Amini
2026-02-02 19:57:17 -06:00
parent 4b2f7e046d
commit 2fd83bdc4a
3 changed files with 65 additions and 8 deletions

View File

@@ -460,6 +460,43 @@ describe('useMainKeyboardHandler', () => {
expect(mockReopenUnifiedClosedTab).toHaveBeenCalledWith(mockActiveSession);
expect(mockSetSessions).toHaveBeenCalled();
});
it('should allow toggleMode shortcut (Cmd+J) when only overlays are open', () => {
const { result } = renderHook(() => useMainKeyboardHandler());
const mockToggleInputMode = vi.fn();
const mockActiveSession = {
id: 'test-session',
name: 'Test',
inputMode: 'ai',
aiTabs: [{ id: 'tab-1', name: 'Tab 1', logs: [] }],
activeTabId: 'tab-1',
filePreviewTabs: [{ id: 'file-tab-1', path: '/test.ts' }],
activeFileTabId: 'file-tab-1', // File preview is active
};
result.current.keyboardHandlerRef.current = createMockContext({
hasOpenLayers: () => true, // Overlay is open (file preview)
hasOpenModal: () => false, // But no true modal
isShortcut: (_e: KeyboardEvent, actionId: string) => actionId === 'toggleMode',
activeSessionId: 'test-session',
activeSession: mockActiveSession,
toggleInputMode: mockToggleInputMode,
});
act(() => {
window.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'j',
metaKey: true,
bubbles: true,
})
);
});
// Cmd+J should toggle mode even when file preview overlay is open
expect(mockToggleInputMode).toHaveBeenCalled();
});
});
describe('navigation handlers delegation', () => {

View File

@@ -757,6 +757,8 @@ function MaestroConsoleInner() {
const { showUnreadOnly, setShowUnreadOnly } = useUILayout();
// Track the active tab ID before entering unread filter mode, so we can restore it when exiting
const { preFilterActiveTabIdRef } = useUILayout();
// Track the active file tab ID before switching to terminal mode, so we can restore it when returning to AI mode
const { preTerminalFileTabIdRef } = useUILayout();
// File Explorer State
const [filePreviewLoading, setFilePreviewLoading] = useState<{
@@ -9799,14 +9801,27 @@ You are taking over this conversation. Based on the context above, provide a bri
prev.map((s) => {
if (s.id !== activeSessionId) return s;
const newMode = s.inputMode === 'ai' ? 'terminal' : 'ai';
// Clear file preview when switching to terminal mode
// This ensures cmd+j from file preview goes directly to terminal
const clearFileTab = newMode === 'terminal';
return {
...s,
inputMode: newMode,
...(clearFileTab && { activeFileTabId: null }),
};
if (newMode === 'terminal') {
// Switching to terminal mode: save current file tab (if any) and clear it
preTerminalFileTabIdRef.current = s.activeFileTabId;
return {
...s,
inputMode: newMode,
activeFileTabId: null,
};
} else {
// Switching to AI mode: restore previous file tab if it still exists
const savedFileTabId = preTerminalFileTabIdRef.current;
const fileTabStillExists =
savedFileTabId && s.filePreviewTabs?.some((t) => t.id === savedFileTabId);
preTerminalFileTabIdRef.current = null;
return {
...s,
inputMode: newMode,
...(fileTabStillExists && { activeFileTabId: savedFileTabId }),
};
}
})
);
// Close any open dropdowns when switching modes

View File

@@ -50,6 +50,8 @@ export interface UILayoutContextValue {
setShowUnreadOnly: React.Dispatch<React.SetStateAction<boolean>>;
toggleShowUnreadOnly: () => void;
preFilterActiveTabIdRef: React.MutableRefObject<string | null>;
// Track the active file tab ID before switching to terminal mode, so we can restore it when returning to AI mode
preTerminalFileTabIdRef: React.MutableRefObject<string | null>;
// Session sidebar selection
selectedSidebarIndex: number;
@@ -128,6 +130,8 @@ export function UILayoutProvider({ children }: UILayoutProviderProps) {
const [showUnreadOnly, setShowUnreadOnly] = useState(false);
// Track the active tab ID before entering unread filter mode, so we can restore it when exiting
const preFilterActiveTabIdRef = useRef<string | null>(null);
// Track the active file tab ID before switching to terminal mode, so we can restore it when returning to AI mode
const preTerminalFileTabIdRef = useRef<string | null>(null);
// Session sidebar selection
const [selectedSidebarIndex, setSelectedSidebarIndex] = useState(0);
@@ -206,6 +210,7 @@ export function UILayoutProvider({ children }: UILayoutProviderProps) {
setShowUnreadOnly,
toggleShowUnreadOnly,
preFilterActiveTabIdRef,
preTerminalFileTabIdRef,
// Session sidebar selection
selectedSidebarIndex,