diff --git a/apps/desktop/src/main/terminal-host/session-lifecycle.test.ts b/apps/desktop/src/main/terminal-host/session-lifecycle.test.ts
index 19e456cb5e4..6b0b4691dbf 100644
--- a/apps/desktop/src/main/terminal-host/session-lifecycle.test.ts
+++ b/apps/desktop/src/main/terminal-host/session-lifecycle.test.ts
@@ -686,6 +686,11 @@ describe("Terminal Host Session Lifecycle", () => {
await sendRequest(control, createRequest);
+ const isReady = await waitForSessionReady(control, "test-session-kill");
+ expect(isReady).toBe(true);
+
+ const exitPromise = waitForEvent(stream, "exit", 5000);
+
// Kill session
const killRequest: IpcRequest = {
id: "test-kill-2",
@@ -699,7 +704,7 @@ describe("Terminal Host Session Lifecycle", () => {
expect(killResponse.ok).toBe(true);
// Wait for exit event
- const exitEvent = await waitForEvent(stream, "exit", 5000);
+ const exitEvent = await exitPromise;
expect(exitEvent.sessionId).toBe("test-session-kill");
} finally {
control.destroy();
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
index 46ec01f90ec..2453df26867 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
@@ -75,6 +75,18 @@ export function DiffViewer({
const [isEditorMounted, setIsEditorMounted] = useState(false);
const hasScrolledToFirstDiffRef = useRef(false);
+ useEffect(() => {
+ if (!isMonacoReady) return;
+ if (!isEditorMounted) return;
+
+ requestAnimationFrame(() => {
+ const modifiedEditor = modifiedEditorRef.current;
+ if (modifiedEditor) {
+ modifiedEditor.layout();
+ }
+ });
+ }, [isMonacoReady, isEditorMounted]);
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Reset on file change only
useEffect(() => {
hasScrolledToFirstDiffRef.current = false;
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx
index d2f4aed315f..8e82a2d5289 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx
@@ -16,7 +16,6 @@ interface TabPaneProps {
paneId: string;
path: MosaicBranch[];
isActive: boolean;
- isTabVisible: boolean;
tabId: string;
workspaceId: string;
splitPaneAuto: (
@@ -46,7 +45,6 @@ export function TabPane({
paneId,
path,
isActive,
- isTabVisible,
tabId,
workspaceId,
splitPaneAuto,
@@ -125,11 +123,7 @@ export function TabPane({
onMoveToNewTab={onMoveToNewTab}
>
-
+
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
index 6b7e00c4dbd..4e3355474a7 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
@@ -21,10 +21,9 @@ import { TabPane } from "./TabPane";
interface TabViewProps {
tab: Tab;
- isTabVisible?: boolean;
}
-export function TabView({ tab, isTabVisible = true }: TabViewProps) {
+export function TabView({ tab }: TabViewProps) {
const updateTabLayout = useTabsStore((s) => s.updateTabLayout);
const removePane = useTabsStore((s) => s.removePane);
const removeTab = useTabsStore((s) => s.removeTab);
@@ -149,7 +148,6 @@ export function TabView({ tab, isTabVisible = true }: TabViewProps) {
paneId={paneId}
path={path}
isActive={isActive}
- isTabVisible={isTabVisible}
tabId={tab.id}
workspaceId={tab.workspaceId}
splitPaneAuto={splitPaneAuto}
@@ -166,7 +164,6 @@ export function TabView({ tab, isTabVisible = true }: TabViewProps) {
[
tabPanes,
focusedPaneId,
- isTabVisible,
tab.id,
tab.workspaceId,
worktreePath,
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
index 802aee8f0cc..40f9073fff4 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
@@ -94,11 +94,7 @@ type CreateOrAttachResult = {
};
};
-export const Terminal = ({
- tabId,
- workspaceId,
- isTabVisible,
-}: TerminalProps) => {
+export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
const paneId = tabId;
// Use granular selectors to avoid re-renders when other panes change
const pane = useTabsStore((s) => s.panes[paneId]);
@@ -160,8 +156,6 @@ export const Terminal = ({
const initialThemeRef = useRef(terminalTheme);
const isFocused = focusedPaneId === paneId;
- const isTabVisibleRef = useRef(isTabVisible);
- isTabVisibleRef.current = isTabVisible;
// Gate streaming until initial state restoration is applied to avoid interleaving output.
const isStreamReadyRef = useRef(false);
@@ -972,16 +966,13 @@ export const Terminal = ({
}, [isFocused]);
useEffect(() => {
- if (isFocused && isTabVisible && xtermRef.current) {
- xtermRef.current.focus();
- }
- }, [isFocused, isTabVisible]);
+ const xterm = xtermRef.current;
+ if (!xterm) return;
- useEffect(() => {
- if (!isTabVisible && xtermRef.current) {
- xtermRef.current.blur();
+ if (isFocused) {
+ xterm.focus();
}
- }, [isTabVisible]);
+ }, [isFocused]);
useAppHotkey(
"FIND_IN_TERMINAL",
@@ -1124,9 +1115,6 @@ export const Terminal = ({
if (isRestoredModeRef.current || connectionErrorRef.current) {
return;
}
- if (!isTabVisibleRef.current) {
- return;
- }
if (isExitedRef.current) {
if (!isFocusedRef.current || wasKilledByUserRef.current) {
return;
@@ -1145,9 +1133,6 @@ export const Terminal = ({
if (isRestoredModeRef.current || connectionErrorRef.current) {
return;
}
- if (!isTabVisibleRef.current) {
- return;
- }
const { domEvent } = event;
if (domEvent.key === "Enter") {
// Don't auto-title from keyboard when in alternate screen (TUI apps like vim, codex)
@@ -1197,7 +1182,7 @@ export const Terminal = ({
const cancelInitialAttach = scheduleTerminalAttach({
paneId,
- priority: isTabVisible ? (isFocusedRef.current ? 0 : 1) : 2,
+ priority: isFocusedRef.current ? 0 : 1,
run: (done) => {
if (isTerminalKilledByUser(paneId)) {
wasKilledByUserRef.current = true;
@@ -1338,7 +1323,7 @@ export const Terminal = ({
};
const handleWrite = (data: string) => {
- if (!isTabVisibleRef.current || isExitedRef.current) {
+ if (isExitedRef.current) {
return;
}
writeRef.current({ paneId, data });
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts
index 2a99ae85548..aa767114c9a 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts
@@ -1,7 +1,6 @@
export interface TerminalProps {
tabId: string;
workspaceId: string;
- isTabVisible: boolean;
}
export type TerminalStreamEvent =
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
index b6f6bd21314..5751f508037 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
@@ -1,40 +1,21 @@
import { useParams } from "@tanstack/react-router";
-import { useEffect, useMemo, useState } from "react";
-import { electronTrpc } from "renderer/lib/electron-trpc";
+import { useMemo } from "react";
import { useSidebarStore } from "renderer/stores";
import {
MAX_SIDEBAR_WIDTH,
MIN_SIDEBAR_WIDTH,
} from "renderer/stores/sidebar-state";
import { useTabsStore } from "renderer/stores/tabs/store";
-import type { Pane, Tab } from "renderer/stores/tabs/types";
-import {
- extractPaneIdsFromLayout,
- resolveActiveTabIdForWorkspace,
-} from "renderer/stores/tabs/utils";
+import { resolveActiveTabIdForWorkspace } from "renderer/stores/tabs/utils";
import { ResizablePanel } from "../../../ResizablePanel";
import { Sidebar } from "../../Sidebar";
import { EmptyTabView } from "./EmptyTabView";
import { TabView } from "./TabView";
-const WARM_TERMINAL_TAB_LIMIT = 8;
-
-/**
- * Check if a tab contains at least one terminal pane.
- * Used to determine which tabs need to stay mounted for persistence.
- */
-function hasTerminalPane(tab: Tab, panes: Record): boolean {
- const paneIds = extractPaneIdsFromLayout(tab.layout);
- return paneIds.some((paneId) => panes[paneId]?.type === "terminal");
-}
-
export function TabsContent() {
const { workspaceId: activeWorkspaceId } = useParams({ strict: false });
- const { data: terminalPersistence } =
- electronTrpc.settings.getTerminalPersistence.useQuery();
const allTabs = useTabsStore((s) => s.tabs);
const activeTabIds = useTabsStore((s) => s.activeTabIds);
- const panes = useTabsStore((s) => s.panes);
const tabHistoryStacks = useTabsStore((s) => s.tabHistoryStacks);
const {
@@ -66,118 +47,11 @@ export function TabsContent() {
return allTabs.find((tab) => tab.id === activeTabId) || null;
}, [activeTabId, allTabs]);
- const activeTabHasTerminal = useMemo(() => {
- if (!tabToRender) return false;
- return hasTerminalPane(tabToRender, panes);
- }, [tabToRender, panes]);
-
- // Per-run warm set of terminal tab IDs (MRU order). Not persisted.
- const [warmTerminalTabIds, setWarmTerminalTabIds] = useState([]);
-
- // Track terminal tab visits to keep a bounded set mounted for smooth switching.
- useEffect(() => {
- if (!terminalPersistence) return;
- if (!activeTabId) return;
- if (!activeTabHasTerminal) return;
-
- setWarmTerminalTabIds((prev) => {
- const next = [activeTabId, ...prev.filter((id) => id !== activeTabId)];
- return next.slice(0, WARM_TERMINAL_TAB_LIMIT);
- });
- }, [terminalPersistence, activeTabId, activeTabHasTerminal]);
-
- // When terminal persistence is enabled, keep a bounded set of terminal tabs
- // mounted across workspace/tab switches. This prevents TUI white screen issues
- // for recently used terminals by avoiding the unmount/remount cycle that
- // requires complex reattach/rehydration, while avoiding startup fan-out.
- // Non-terminal tabs use normal unmount behavior to save memory.
- // Uses visibility:hidden (not display:none) to preserve xterm dimensions.
- if (terminalPersistence) {
- // Partition tabs: a bounded set of terminal tabs stay mounted, non-terminal tabs unmount when inactive.
- const terminalTabs = allTabs.filter((tab) => hasTerminalPane(tab, panes));
- const terminalTabsById = new Map(terminalTabs.map((tab) => [tab.id, tab]));
-
- const warmIdsFiltered = warmTerminalTabIds.filter((id) =>
- terminalTabsById.has(id),
- );
-
- // Ensure active terminal tab is included in the mounted set even before the
- // warm-set effect runs (first render after tab switch).
- const terminalTabIdsToRender = (() => {
- const ids = [...warmIdsFiltered];
- if (activeTabHasTerminal && activeTabId && !ids.includes(activeTabId)) {
- ids.unshift(activeTabId);
- }
- return ids.slice(0, WARM_TERMINAL_TAB_LIMIT);
- })();
-
- const terminalTabsToRender = terminalTabIdsToRender
- .map((id) => terminalTabsById.get(id))
- .filter((tab): tab is Tab => !!tab);
-
- const activeNonTerminalTab =
- tabToRender && !activeTabHasTerminal ? tabToRender : null;
-
- return (
-
-
- {/* Terminal tabs: keep mounted with visibility toggle */}
- {terminalTabsToRender.map((tab) => {
- const isVisible =
- tab.workspaceId === activeWorkspaceId && tab.id === activeTabId;
-
- return (
-
-
-
- );
- })}
- {/* Active non-terminal tab: render normally (unmounts when switching) */}
- {activeNonTerminalTab && (
-
-
-
- )}
- {/* Fallback: show empty view without unmounting terminal tabs */}
- {!activeNonTerminalTab && !tabToRender && (
-
-
-
- )}
-
- {isSidebarOpen && (
-
-
-
- )}
-
- );
- }
-
// Original behavior when persistence disabled: only render active tab
return (
- {tabToRender ? (
-
- ) : (
-
- )}
+ {tabToRender ? : }
{isSidebarOpen && (