MAESTRO: Add handleCloseCurrentTab function for Cmd+W with unified tab support

Implements handleCloseCurrentTab that:
- Determines which tab is active (file tab via activeFileTabId first, then AI tab via activeTabId)
- Closes file tabs immediately without wizard confirmation
- For AI tabs, returns info to allow keyboard handler to show wizard confirmation
- Prevents closing the last AI tab (keeps at least one AI tab)

Updated keyboard handler (Cmd+W) to use handleCloseCurrentTab for unified tab support.
This commit is contained in:
Pedram Amini
2026-02-02 03:36:59 -06:00
parent 43518623fe
commit 3c6cf91ffa
2 changed files with 140 additions and 26 deletions

View File

@@ -5577,6 +5577,112 @@ You are taking over this conversation. Based on the context above, provide a bri
);
}, []);
/**
* Close the currently active tab (for Cmd+W).
* Determines which tab is active (checking activeFileTabId first, then activeTabId),
* and calls the appropriate close handler.
*
* For file tabs: closes immediately.
* For AI tabs: prevents closing if it's the last AI tab (keeps at least one AI tab).
*
* Returns an object indicating what action was taken, for the keyboard handler
* to potentially show confirmation modals for wizard tabs.
*/
const handleCloseCurrentTab = useCallback((): {
type: 'file' | 'ai' | 'prevented' | 'none';
tabId?: string;
isWizardTab?: boolean;
} => {
const session = sessionsRef.current.find((s) => s.id === activeSessionIdRef.current);
if (!session) return { type: 'none' };
// Check if a file tab is active first
if (session.activeFileTabId) {
const tabId = session.activeFileTabId;
// File tabs can always be closed (no wizard confirmation needed)
setSessions((prev) =>
prev.map((s) => {
if (s.id !== activeSessionIdRef.current) return s;
// Find the tab to close
const tabToClose = s.filePreviewTabs.find((tab) => tab.id === tabId);
if (!tabToClose) return s;
// Remove from filePreviewTabs
const updatedFilePreviewTabs = s.filePreviewTabs.filter(
(tab) => tab.id !== tabId
);
// Remove from unifiedTabOrder
const closedTabIndex = s.unifiedTabOrder.findIndex(
(ref) => ref.type === 'file' && ref.id === tabId
);
const updatedUnifiedTabOrder = s.unifiedTabOrder.filter(
(ref) => !(ref.type === 'file' && ref.id === tabId)
);
// Determine new active tab if we closed the active file tab
let newActiveFileTabId: string | null = null;
let newActiveTabId = s.activeTabId;
// This was the active tab - find the next tab in unifiedTabOrder
if (updatedUnifiedTabOrder.length > 0 && closedTabIndex !== -1) {
// Try to select the tab at the same position (or previous if at end)
const newIndex = Math.min(
closedTabIndex,
updatedUnifiedTabOrder.length - 1
);
const nextTabRef = updatedUnifiedTabOrder[newIndex];
if (nextTabRef.type === 'file') {
// Next tab is a file tab
newActiveFileTabId = nextTabRef.id;
} else {
// Next tab is an AI tab - switch to it
newActiveTabId = nextTabRef.id;
newActiveFileTabId = null;
}
} else if (updatedUnifiedTabOrder.length > 0) {
// Fallback: just select the first available tab
const firstTabRef = updatedUnifiedTabOrder[0];
if (firstTabRef.type === 'file') {
newActiveFileTabId = firstTabRef.id;
} else {
newActiveTabId = firstTabRef.id;
newActiveFileTabId = null;
}
}
return {
...s,
filePreviewTabs: updatedFilePreviewTabs,
unifiedTabOrder: updatedUnifiedTabOrder,
activeFileTabId: newActiveFileTabId,
activeTabId: newActiveTabId,
};
})
);
return { type: 'file', tabId };
}
// AI tab is active
if (session.activeTabId) {
// Prevent closing if it's the last AI tab
if (session.aiTabs.length <= 1) {
return { type: 'prevented' };
}
const tabId = session.activeTabId;
const tab = session.aiTabs.find((t) => t.id === tabId);
const isWizardTab = tab ? hasActiveWizard(tab) : false;
// Return info for the keyboard handler to show confirmation modal if needed
return { type: 'ai', tabId, isWizardTab };
}
return { type: 'none' };
}, []);
const handleRemoveQueuedItem = useCallback((itemId: string) => {
setSessions((prev) =>
prev.map((s) => {
@@ -12242,6 +12348,9 @@ You are taking over this conversation. Based on the context above, provide a bri
handleCloseTabsLeft,
handleCloseTabsRight,
// Close current tab (Cmd+W) - works with both file and AI tabs
handleCloseCurrentTab,
// Session bookmark toggle
toggleBookmark,
};

View File

@@ -462,36 +462,41 @@ export function useMainKeyboardHandler(): UseMainKeyboardHandlerReturn {
}
if (ctx.isTabShortcut(e, 'closeTab')) {
e.preventDefault();
const activeTab = ctx.activeSession.aiTabs.find(
(t: AITab) => t.id === ctx.activeSession.activeTabId
);
// Use handleCloseCurrentTab to close the active tab (file or AI)
// This handles both file preview tabs and AI tabs with unified tab system
const closeResult = ctx.handleCloseCurrentTab();
// Check if this is a wizard tab - show confirmation before closing
if (activeTab && ctx.hasActiveWizard && ctx.hasActiveWizard(activeTab)) {
ctx.setConfirmModalMessage(
'Close this wizard? Your progress will be lost and cannot be restored.'
);
ctx.setConfirmModalOnConfirm(() => () => {
ctx.performTabClose(ctx.activeSession.activeTabId);
trackShortcut('closeTab');
});
ctx.setConfirmModalOpen(true);
} else {
// Regular tab - use closeTab directly with skipHistory for wizard tabs
const isWizardTab = activeTab && ctx.hasActiveWizard && ctx.hasActiveWizard(activeTab);
const result = ctx.closeTab(
ctx.activeSession,
ctx.activeSession.activeTabId,
ctx.showUnreadOnly,
{ skipHistory: isWizardTab }
);
if (result) {
ctx.setSessions((prev: Session[]) =>
prev.map((s: Session) => (s.id === ctx.activeSession!.id ? result.session : s))
if (closeResult.type === 'file') {
// File tab was already closed by handleCloseCurrentTab
trackShortcut('closeTab');
} else if (closeResult.type === 'ai' && closeResult.tabId) {
// AI tab - need to handle wizard confirmation
if (closeResult.isWizardTab) {
ctx.setConfirmModalMessage(
'Close this wizard? Your progress will be lost and cannot be restored.'
);
trackShortcut('closeTab');
ctx.setConfirmModalOnConfirm(() => () => {
ctx.performTabClose(closeResult.tabId);
trackShortcut('closeTab');
});
ctx.setConfirmModalOpen(true);
} else {
// Regular AI tab - close it
const result = ctx.closeTab(
ctx.activeSession,
closeResult.tabId,
ctx.showUnreadOnly,
{ skipHistory: false }
);
if (result) {
ctx.setSessions((prev: Session[]) =>
prev.map((s: Session) => (s.id === ctx.activeSession!.id ? result.session : s))
);
trackShortcut('closeTab');
}
}
}
// 'prevented' or 'none' - do nothing (can't close last AI tab)
}
if (ctx.isTabShortcut(e, 'closeAllTabs')) {
e.preventDefault();