MAESTRO: Integrate unified tab system into MainPanel

Connect FilePreviewTab rendering to the unified tab system:
- Add unified tab props to MainPanel (unifiedTabs, activeFileTabId, etc.)
- Remove !previewFile condition so TabBar always renders in AI mode
- Update content area to prioritize activeFileTabId over legacy previewFile
- Add showCloseButton prop to FilePreview (false when rendered as tab)
- Add activeFileTab computation in App.tsx
- Connect handlers through useMainPanelProps hook
This commit is contained in:
Pedram Amini
2026-02-02 04:19:43 -06:00
parent e7960ad3b2
commit 93d041a7f0
4 changed files with 139 additions and 15 deletions

View File

@@ -4783,6 +4783,16 @@ You are taking over this conversation. Based on the context above, provide a bri
activeSession?.unifiedTabOrder,
]);
// Get the active file preview tab (if a file tab is active)
const activeFileTab = useMemo((): FilePreviewTab | null => {
if (!activeSession?.activeFileTabId) return null;
return (
activeSession.filePreviewTabs.find(
(tab) => tab.id === activeSession.activeFileTabId
) ?? null
);
}, [activeSession?.activeFileTabId, activeSession?.filePreviewTabs]);
const isResumingSession = !!activeTab?.agentSessionId;
const canAttachImages = useMemo(() => {
if (!activeSession || activeSession.inputMode !== 'ai') return false;
@@ -12835,6 +12845,14 @@ You are taking over this conversation. Based on the context above, provide a bri
handleCloseOtherTabs,
handleCloseTabsLeft,
handleCloseTabsRight,
// Unified tab system (Phase 4)
unifiedTabs,
activeFileTabId: activeSession?.activeFileTabId ?? null,
activeFileTab,
handleFileTabSelect: handleSelectFileTab,
handleFileTabClose: handleCloseFileTab,
handleScrollPositionChange,
handleAtBottomChange,
handleMainPanelInputBlur,

View File

@@ -97,6 +97,8 @@ interface FilePreviewProps {
onOpenInGraph?: () => void;
/** SSH remote ID for remote file operations */
sshRemoteId?: string;
/** Whether to show the close button in the header (default: true, set to false when rendered as tab) */
showCloseButton?: boolean;
}
export interface FilePreviewHandle {
@@ -555,6 +557,7 @@ export const FilePreview = forwardRef<FilePreviewHandle, FilePreviewProps>(funct
hasGist,
onOpenInGraph,
sshRemoteId,
showCloseButton = true, // Default to true for backwards compatibility
},
ref
) {
@@ -1621,13 +1624,16 @@ export const FilePreview = forwardRef<FilePreviewHandle, FilePreviewProps>(funct
>
<FolderOpen className="w-4 h-4" />
</button>
<button
onClick={onClose}
className="p-2 rounded hover:bg-white/10 transition-colors"
style={{ color: theme.colors.textDim }}
>
<X className="w-5 h-5" />
</button>
{/* Close button - hidden when rendered as tab (tab's X handles closing) */}
{showCloseButton && (
<button
onClick={onClose}
className="p-2 rounded hover:bg-white/10 transition-colors"
style={{ color: theme.colors.textDim }}
>
<X className="w-5 h-5" />
</button>
)}
</div>
</div>
{/* File Stats subbar - hidden on scroll */}

View File

@@ -39,7 +39,7 @@ import { useGitBranch, useGitDetail, useGitFileStatus } from '../contexts/GitSta
import { formatShortcutKeys } from '../utils/shortcutFormatter';
import { calculateContextTokens } from '../utils/contextUsage';
import { useAgentCapabilities, useHoverTooltip } from '../hooks';
import type { Session, Theme, Shortcut, FocusArea, BatchRunState } from '../types';
import type { Session, Theme, Shortcut, FocusArea, BatchRunState, UnifiedTab, FilePreviewTab } from '../types';
interface SlashCommand {
command: string;
@@ -206,6 +206,15 @@ interface MainPanelProps {
onCloseOtherTabs?: () => void;
onCloseTabsLeft?: () => void;
onCloseTabsRight?: () => void;
// Unified tab system (Phase 4) - file preview tabs integrated with AI tabs
unifiedTabs?: UnifiedTab[];
activeFileTabId?: string | null;
activeFileTab?: FilePreviewTab | null;
onFileTabSelect?: (tabId: string) => void;
onFileTabClose?: (tabId: string) => void;
onOpenFileTab?: (filePath: string) => void;
// Scroll position persistence
onScrollPositionChange?: (scrollTop: number) => void;
// Scroll bottom state change handler (for hasUnread logic)
@@ -464,6 +473,12 @@ export const MainPanel = React.memo(
onCloseOtherTabs,
onCloseTabsLeft,
onCloseTabsRight,
// Unified tab system props (Phase 4)
unifiedTabs,
activeFileTabId,
activeFileTab,
onFileTabSelect,
onFileTabClose,
} = props;
// Get the active tab for header display
@@ -1410,9 +1425,8 @@ export const MainPanel = React.memo(
</div>
)}
{/* Tab Bar - only shown in AI mode when we have tabs (hidden during file preview) */}
{!previewFile &&
activeSession.inputMode === 'ai' &&
{/* Tab Bar - always shown in AI mode when we have tabs (includes both AI and file tabs) */}
{activeSession.inputMode === 'ai' &&
activeSession.aiTabs &&
activeSession.aiTabs.length > 0 &&
onTabSelect &&
@@ -1444,6 +1458,11 @@ export const MainPanel = React.memo(
onCloseOtherTabs={onCloseOtherTabs}
onCloseTabsLeft={onCloseTabsLeft}
onCloseTabsRight={onCloseTabsRight}
// Unified tab system props (Phase 4)
unifiedTabs={unifiedTabs}
activeFileTabId={activeFileTabId}
onFileTabSelect={onFileTabSelect}
onFileTabClose={onFileTabClose}
/>
)}
@@ -1489,7 +1508,8 @@ export const MainPanel = React.memo(
)}
{/* Show File Preview loading state when fetching remote file */}
{filePreviewLoading && !previewFile && (
{/* Show loading for both legacy previewFile and new file tab system */}
{filePreviewLoading && !previewFile && !activeFileTabId && (
<div
className="flex-1 flex items-center justify-center"
style={{ backgroundColor: theme.colors.bgMain }}
@@ -1511,9 +1531,67 @@ export const MainPanel = React.memo(
</div>
)}
{/* Show File Preview in main area when open, otherwise show terminal output and input */}
{/* Skip rendering terminal/preview when loading remote file - loading state takes over entire main area */}
{filePreviewLoading && !previewFile ? null : previewFile ? (
{/* Content area: Show FilePreview when file tab is active or previewFile is set, otherwise show terminal output */}
{/* Priority: activeFileTabId (new tab system) > previewFile (legacy) > terminal output */}
{/* Skip rendering when loading remote file - loading state takes over entire main area */}
{filePreviewLoading && !previewFile && !activeFileTabId ? null : activeFileTabId && activeFileTab ? (
// New file tab system - FilePreview rendered as tab content (no close button, tab handles closing)
<div
ref={filePreviewContainerRef}
tabIndex={-1}
className="flex-1 overflow-hidden outline-none"
>
<FilePreview
ref={filePreviewRef}
file={{
name: activeFileTab.name + activeFileTab.extension,
content: activeFileTab.editContent ?? '', // Content will be fetched in Phase 5
path: activeFileTab.path,
}}
onClose={() => {
// When rendered as tab, close via tab close handler
onFileTabClose?.(activeFileTabId);
}}
theme={theme}
markdownEditMode={activeFileTab.editMode}
setMarkdownEditMode={setMarkdownEditMode}
onSave={async (path, content) => {
await window.maestro.fs.writeFile(path, content);
// TODO: Update the file tab's editContent after save
}}
shortcuts={shortcuts}
fileTree={props.fileTree}
cwd={(() => {
// Compute relative directory from file path for proximity matching
if (
!activeSession?.fullPath ||
!activeFileTab.path.startsWith(activeSession.fullPath)
) {
return '';
}
const relativePath = activeFileTab.path.slice(activeSession.fullPath.length + 1);
const lastSlash = relativePath.lastIndexOf('/');
return lastSlash > 0 ? relativePath.slice(0, lastSlash) : '';
})()}
onFileClick={props.onFileClick}
// File tabs don't use navigation history (each file is a separate tab)
canGoBack={false}
canGoForward={false}
onOpenFuzzySearch={props.onOpenFuzzySearch}
onShortcutUsed={props.onShortcutUsed}
ghCliAvailable={props.ghCliAvailable}
onPublishGist={props.onPublishGist}
hasGist={props.hasGist}
onOpenInGraph={props.onOpenInGraph}
sshRemoteId={
activeSession?.sshRemoteId ||
activeSession?.sessionSshRemoteConfig?.remoteId ||
undefined
}
showCloseButton={false}
/>
</div>
) : previewFile ? (
<div
ref={filePreviewContainerRef}
tabIndex={-1}

View File

@@ -19,6 +19,8 @@ import type {
LogEntry,
UsageStats,
AITab,
UnifiedTab,
FilePreviewTab,
} from '../../types';
import type { TabCompletionSuggestion, TabCompletionFilter } from '../input/useTabCompletion';
import type {
@@ -216,6 +218,14 @@ export interface UseMainPanelPropsDeps {
handleCloseOtherTabs: () => void;
handleCloseTabsLeft: () => void;
handleCloseTabsRight: () => void;
// Unified tab system props (Phase 4)
unifiedTabs: UnifiedTab[];
activeFileTabId: string | null;
activeFileTab: FilePreviewTab | null;
handleFileTabSelect: (tabId: string) => void;
handleFileTabClose: (tabId: string) => void;
handleScrollPositionChange: (scrollTop: number) => void;
handleAtBottomChange: (isAtBottom: boolean) => void;
handleMainPanelInputBlur: () => void;
@@ -399,6 +409,12 @@ export function useMainPanelProps(deps: UseMainPanelPropsDeps) {
onCloseOtherTabs: deps.handleCloseOtherTabs,
onCloseTabsLeft: deps.handleCloseTabsLeft,
onCloseTabsRight: deps.handleCloseTabsRight,
// Unified tab system props (Phase 4)
unifiedTabs: deps.unifiedTabs,
activeFileTabId: deps.activeFileTabId,
activeFileTab: deps.activeFileTab,
onFileTabSelect: deps.handleFileTabSelect,
onFileTabClose: deps.handleFileTabClose,
onToggleTabSaveToHistory: deps.handleToggleTabSaveToHistory,
onToggleTabShowThinking: deps.handleToggleTabShowThinking,
onScrollPositionChange: deps.handleScrollPositionChange,
@@ -625,6 +641,12 @@ export function useMainPanelProps(deps: UseMainPanelPropsDeps) {
deps.handleCloseOtherTabs,
deps.handleCloseTabsLeft,
deps.handleCloseTabsRight,
// Unified tab system (Phase 4)
deps.unifiedTabs,
deps.activeFileTabId,
deps.activeFileTab,
deps.handleFileTabSelect,
deps.handleFileTabClose,
deps.handleScrollPositionChange,
deps.handleAtBottomChange,
deps.handleMainPanelInputBlur,