MAESTRO: Migrate file click handler to open tabs instead of overlay

Updated handleFileClick in useAppHandlers.ts to use the new tab-based
file preview system. When onOpenFileTab callback is provided, file clicks
now open tabs instead of using the legacy setPreviewFile overlay.

Changes:
- Added FileTabInfo interface and onOpenFileTab optional callback to
  UseAppHandlersDeps
- Modified handleFileClick to fetch file stat for lastModified timestamp
- handleFileClick now calls onOpenFileTab when provided, falling back to
  legacy setPreviewFile behavior otherwise
- Moved handleOpenFileTab definition in App.tsx to before useAppHandlers
  call and wired it as the onOpenFileTab callback
This commit is contained in:
Pedram Amini
2026-02-02 05:45:43 -06:00
parent 64bda7f939
commit a7449f5a3a
2 changed files with 133 additions and 95 deletions

View File

@@ -3881,6 +3881,83 @@ function MaestroConsoleInner() {
[activeSessionId]
);
/**
* Open a file preview tab. If a tab with the same path already exists, select it.
* Otherwise, create a new FilePreviewTab, add it to filePreviewTabs and unifiedTabOrder,
* and set it as the active file tab (deselecting any active AI tab).
*
* For SSH remote files, pass sshRemoteId so content can be re-fetched if needed.
*/
const handleOpenFileTab = useCallback(
(file: { path: string; name: string; content: string; sshRemoteId?: string; lastModified?: number }) => {
setSessions((prev) =>
prev.map((s) => {
if (s.id !== activeSessionIdRef.current) return s;
// Check if a tab with this path already exists
const existingTab = s.filePreviewTabs.find((tab) => tab.path === file.path);
if (existingTab) {
// Tab exists - update content and lastModified if provided (e.g., after re-fetch) and select it
const updatedTabs = s.filePreviewTabs.map((tab) =>
tab.id === existingTab.id
? {
...tab,
content: file.content,
lastModified: file.lastModified ?? tab.lastModified,
isLoading: false,
}
: tab
);
return {
...s,
filePreviewTabs: updatedTabs,
activeFileTabId: existingTab.id,
activeTabId: s.activeTabId, // Keep AI tab reference but it's not visually active
};
}
// Create a new file preview tab
const newTabId = generateId();
const extension = file.name.includes('.')
? '.' + file.name.split('.').pop()
: '';
const nameWithoutExtension = extension
? file.name.slice(0, -extension.length)
: file.name;
const newFileTab: FilePreviewTab = {
id: newTabId,
path: file.path,
name: nameWithoutExtension,
extension,
content: file.content,
scrollTop: 0,
searchQuery: '',
editMode: false,
editContent: undefined,
createdAt: Date.now(),
lastModified: file.lastModified ?? Date.now(), // Use file mtime or current time as fallback
sshRemoteId: file.sshRemoteId,
isLoading: false, // Content is already loaded when this is called
};
// Create the unified tab reference
const newTabRef: UnifiedTabRef = { type: 'file', id: newTabId };
return {
...s,
filePreviewTabs: [...s.filePreviewTabs, newFileTab],
unifiedTabOrder: [...s.unifiedTabOrder, newTabRef],
activeFileTabId: newTabId,
// Deselect AI tab when file tab becomes active
// Note: activeTabId stays as is - it tracks the last active AI tab for when user switches back
};
})
);
},
[]
);
// --- APP HANDLERS (drag, file, folder operations) ---
const {
handleImageDragEnter,
@@ -3908,6 +3985,7 @@ function MaestroConsoleInner() {
setConfirmModalMessage,
setConfirmModalOnConfirm,
setConfirmModalOpen,
onOpenFileTab: handleOpenFileTab,
});
// Use custom colors when custom theme is selected, otherwise use the standard theme
@@ -5167,83 +5245,6 @@ You are taking over this conversation. Based on the context above, provide a bri
);
}, []);
/**
* Open a file preview tab. If a tab with the same path already exists, select it.
* Otherwise, create a new FilePreviewTab, add it to filePreviewTabs and unifiedTabOrder,
* and set it as the active file tab (deselecting any active AI tab).
*
* For SSH remote files, pass sshRemoteId so content can be re-fetched if needed.
*/
const handleOpenFileTab = useCallback(
(file: { path: string; name: string; content: string; sshRemoteId?: string; lastModified?: number }) => {
setSessions((prev) =>
prev.map((s) => {
if (s.id !== activeSessionIdRef.current) return s;
// Check if a tab with this path already exists
const existingTab = s.filePreviewTabs.find((tab) => tab.path === file.path);
if (existingTab) {
// Tab exists - update content and lastModified if provided (e.g., after re-fetch) and select it
const updatedTabs = s.filePreviewTabs.map((tab) =>
tab.id === existingTab.id
? {
...tab,
content: file.content,
lastModified: file.lastModified ?? tab.lastModified,
isLoading: false,
}
: tab
);
return {
...s,
filePreviewTabs: updatedTabs,
activeFileTabId: existingTab.id,
activeTabId: s.activeTabId, // Keep AI tab reference but it's not visually active
};
}
// Create a new file preview tab
const newTabId = generateId();
const extension = file.name.includes('.')
? '.' + file.name.split('.').pop()
: '';
const nameWithoutExtension = extension
? file.name.slice(0, -extension.length)
: file.name;
const newFileTab: FilePreviewTab = {
id: newTabId,
path: file.path,
name: nameWithoutExtension,
extension,
content: file.content,
scrollTop: 0,
searchQuery: '',
editMode: false,
editContent: undefined,
createdAt: Date.now(),
lastModified: file.lastModified ?? Date.now(), // Use file mtime or current time as fallback
sshRemoteId: file.sshRemoteId,
isLoading: false, // Content is already loaded when this is called
};
// Create the unified tab reference
const newTabRef: UnifiedTabRef = { type: 'file', id: newTabId };
return {
...s,
filePreviewTabs: [...s.filePreviewTabs, newFileTab],
unifiedTabOrder: [...s.unifiedTabOrder, newTabRef],
activeFileTabId: newTabId,
// Deselect AI tab when file tab becomes active
// Note: activeTabId stays as is - it tracks the last active AI tab for when user switches back
};
})
);
},
[]
);
/**
* Open a file tab with async content loading (for SSH remote files).
* Creates the tab immediately with loading state, fetches content, then updates the tab.