diff --git a/src/renderer/components/MainPanel.tsx b/src/renderer/components/MainPanel.tsx
index 4e990d42..15e9d888 100644
--- a/src/renderer/components/MainPanel.tsx
+++ b/src/renderer/components/MainPanel.tsx
@@ -742,6 +742,86 @@ export const MainPanel = React.memo(
[setActiveSessionId, onTabSelect]
);
+ // Memoized props for FilePreview to prevent re-renders that cause image flickering
+ // The file object must be stable - recreating it on each render causes the to remount
+ const memoizedFilePreviewFile = useMemo(() => {
+ if (!activeFileTab) return null;
+ return {
+ name: activeFileTab.name + activeFileTab.extension,
+ content: activeFileTab.content,
+ path: activeFileTab.path,
+ };
+ }, [activeFileTab?.name, activeFileTab?.extension, activeFileTab?.content, activeFileTab?.path]);
+
+ // Memoized callbacks for FilePreview
+ const handleFilePreviewClose = useCallback(() => {
+ if (activeFileTabId) {
+ onFileTabClose?.(activeFileTabId);
+ }
+ }, [activeFileTabId, onFileTabClose]);
+
+ const handleFilePreviewEditModeChange = useCallback(
+ (editMode: boolean) => {
+ if (activeFileTabId) {
+ onFileTabEditModeChange?.(activeFileTabId, editMode);
+ }
+ },
+ [activeFileTabId, onFileTabEditModeChange]
+ );
+
+ const handleFilePreviewSave = useCallback(
+ async (path: string, content: string) => {
+ await window.maestro.fs.writeFile(path, content);
+ if (activeFileTabId) {
+ onFileTabEditContentChange?.(activeFileTabId, undefined, content);
+ }
+ },
+ [activeFileTabId, onFileTabEditContentChange]
+ );
+
+ // Compute cwd for FilePreview - memoized to prevent recalculation on every render
+ const filePreviewCwd = useMemo(() => {
+ if (!activeSession?.fullPath || !activeFileTab?.path) return '';
+ if (!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) : '';
+ }, [activeSession?.fullPath, activeFileTab?.path]);
+
+ const handleFilePreviewEditContentChange = useCallback(
+ (content: string) => {
+ if (activeFileTabId && activeFileTab) {
+ const hasChanges = content !== activeFileTab.content;
+ onFileTabEditContentChange?.(activeFileTabId, hasChanges ? content : undefined);
+ }
+ },
+ [activeFileTabId, activeFileTab?.content, onFileTabEditContentChange]
+ );
+
+ const handleFilePreviewScrollPositionChange = useCallback(
+ (scrollTop: number) => {
+ if (activeFileTabId) {
+ onFileTabScrollPositionChange?.(activeFileTabId, scrollTop);
+ }
+ },
+ [activeFileTabId, onFileTabScrollPositionChange]
+ );
+
+ const handleFilePreviewSearchQueryChange = useCallback(
+ (query: string) => {
+ if (activeFileTabId) {
+ onFileTabSearchQueryChange?.(activeFileTabId, query);
+ }
+ },
+ [activeFileTabId, onFileTabSearchQueryChange]
+ );
+
+ // Memoize sshRemoteId to prevent object recreation
+ const filePreviewSshRemoteId = useMemo(
+ () => activeSession?.sshRemoteId || activeSession?.sessionSshRemoteConfig?.remoteId || undefined,
+ [activeSession?.sshRemoteId, activeSession?.sessionSshRemoteConfig?.remoteId]
+ );
+
// Handler to view git diff
const handleViewGitDiff = async () => {
if (!activeSession || !activeSession.isGitRepo) return;
@@ -1528,8 +1608,9 @@ export const MainPanel = React.memo(
- ) : activeFileTabId && activeFileTab ? (
+ ) : activeFileTabId && activeFileTab && memoizedFilePreviewFile ? (
// New file tab system - FilePreview rendered as tab content (no close button, tab handles closing)
+ // Note: All props are memoized to prevent unnecessary re-renders that cause image flickering