Files
NetMesh/components/terminalLayer/TerminalLayerTabBridge.tsx

705 lines
29 KiB
TypeScript
Raw Normal View History

/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';
import { useActiveTabId } from '../../application/state/activeTabStore';
import { sessionCapabilitiesStore } from '../../application/state/sessionCapabilitiesStore';
import { useSystemManagerBackend } from '../../application/state/useSystemManagerBackend';
import { isTerminalSessionEligibleForSftpReuse } from '../../application/state/terminalConnectionReuse';
import { resolveSystemSidebarSession } from '../../domain/systemManager/resolveSystemSession';
import type { TerminalContextReader } from '../../domain/terminalContextRead';
import { useSystemCapabilitiesWarmup } from '../../application/state/useSystemManager';
import { cn } from '../../lib/utils';
import type { Host, TerminalSession, Workspace } from '../../types';
import { resolveTerminalHibernateEnabled } from '../../domain/terminalHibernate';
import { shouldMeasureTerminalLayerLayout } from '../terminalPaneVisibility';
import { TerminalLayerView } from './TerminalLayerView';
import { useTerminalAiContexts } from '../../application/state/useTerminalAiContexts';
import { useTerminalLayerEffects } from './useTerminalLayerEffects';
import { useTerminalThemePanelState } from './useTerminalThemePanelState';
import { useManualTerminalChromeSurfaceInjection } from '../../application/state/useManualTerminalChromeSurfaceInjection';
import { sidePanelLiveStore, type SidePanelLiveSnapshot } from '../../application/state/sidePanelLiveStore';
import { terminalCwdStore } from '../../application/state/terminalCwdStore';
import { useTerminalWorkspaceLayout } from './useTerminalWorkspaceLayout';
import type { SidePanelTab } from './TerminalLayerSupport';
import {
collectSidePanelPanes,
type SidePanelLayout,
} from '../../domain/sidePanelLayout';
type StableRef = React.MutableRefObject<Record<string, any>>;
export function TerminalLayerTabBridge({ stableRef }: { stableRef: StableRef }) {
const s = stableRef.current;
const activeTabId = useActiveTabId();
// Subscribe to cwd store so OSC 7 updates reach the side-panel live snapshot
// without TerminalLayerInner setState.
const terminalCwdVersion = useSyncExternalStore(
terminalCwdStore.subscribe,
terminalCwdStore.getVersion,
terminalCwdStore.getVersion,
);
const systemBackend = useSystemManagerBackend();
const terminalContextReadersRef = useRef<Map<string, TerminalContextReader>>(new Map());
s.activeTabIdRef.current = activeTabId;
const workspaceById = s.workspaceById as Map<string, Workspace>;
const sessions = s.sessions as TerminalSession[];
const sessionHostsMap = s.sessionHostsMap as Map<string, Host>;
const sftpHostForTab = s.sftpHostForTab as Map<string, Host>;
const sidePanelOpenTabs = s.sidePanelOpenTabs as Map<string, SidePanelTab>;
const sidePanelLayouts = s.sidePanelLayouts as Map<string, SidePanelLayout>;
const showHostTreeSidebar = s.showHostTreeSidebar as boolean | undefined;
const activeWorkspace = useMemo(
() => (activeTabId ? workspaceById.get(activeTabId) : undefined),
[activeTabId, workspaceById],
);
const activeSession = useMemo(
() => sessions.find((session) => session.id === activeTabId),
[activeTabId, sessions],
);
const isFocusMode = activeWorkspace?.viewMode === 'focus';
const focusedSessionId = activeWorkspace?.focusedSessionId;
const hibernateHiddenTabs = resolveTerminalHibernateEnabled(s.terminalSettings);
const localWorkspaceIds = useMemo(() => new Set(
sessions
.filter((session) => session.workspaceId && sessionHostsMap.get(session.id)?.protocol === 'local')
.map((session) => session.workspaceId as string),
), [sessionHostsMap, sessions]);
const shouldKeepHiddenWorkspaceLaidOut = useCallback(
(workspace: Workspace) => !hibernateHiddenTabs || localWorkspaceIds.has(workspace.id),
[hibernateHiddenTabs, localWorkspaceIds],
);
const keepHiddenLayoutActive = !hibernateHiddenTabs || localWorkspaceIds.size > 0;
const effectiveFocusedSessionId = useMemo((): string | null => {
if (activeWorkspace) {
if (focusedSessionId) return focusedSessionId;
return sessions.find((session) => session.workspaceId === activeWorkspace.id)?.id ?? null;
}
return activeSession?.id ?? null;
}, [activeSession?.id, activeWorkspace, focusedSessionId, sessions]);
s.activeWorkspaceRef.current = activeWorkspace;
s.activeSessionRef.current = activeSession;
s.focusedSessionIdRef.current = focusedSessionId;
const isVisible = Boolean(activeSession || activeWorkspace || s.draggingSessionId);
const isTerminalLayerVisible = isVisible || !!s.draggingSessionId;
const {
activeResizers,
computeSplitHint,
dropHint,
findSplitNode,
handleWorkspaceDrop,
resizing,
setDropHint,
setResizing,
setWorkspaceArea,
workspaceArea,
workspaceInnerRef,
workspaceOuterRef,
workspaceOverlayRef,
workspaceRectsById,
} = useTerminalWorkspaceLayout({
activeSession,
activeWorkspace,
isFocusMode,
shouldKeepHiddenWorkspaceLaidOut,
onAddSessionToWorkspace: s.onAddSessionToWorkspace,
onCreateWorkspaceFromSessions: s.onCreateWorkspaceFromSessions,
onSetDraggingSessionId: s.onSetDraggingSessionId,
onUpdateSplitSizes: s.onUpdateSplitSizes,
sessions,
workspaces: s.workspaces,
});
const isSidePanelOpenForCurrentTab = activeTabId ? sidePanelOpenTabs.has(activeTabId) : false;
const activeSidePanelTab = activeTabId ? sidePanelOpenTabs.get(activeTabId) ?? null : null;
const activeSidePanelLayout = activeTabId ? sidePanelLayouts.get(activeTabId) ?? null : null;
const activeSidePanelTools = useMemo(
() => new Set(activeSidePanelLayout ? collectSidePanelPanes(activeSidePanelLayout.root).map((pane) => pane.tool) : []),
[activeSidePanelLayout],
);
const isSftpOpenForCurrentTab = activeSidePanelTools.has('sftp');
const activeHostIdForSidebar = useMemo(() => {
const sessionId = activeWorkspace ? focusedSessionId : activeSession?.id;
if (!sessionId) return null;
return sessionHostsMap.get(sessionId)?.id
?? sessions.find((session) => session.id === sessionId)?.hostId
?? null;
}, [activeWorkspace, focusedSessionId, activeSession, sessionHostsMap, sessions]);
const sftpActiveHost = useMemo((): Host | null => {
if (!isSftpOpenForCurrentTab || !activeTabId) return null;
if (activeWorkspace && focusedSessionId) {
return sessionHostsMap.get(focusedSessionId) ?? sftpHostForTab.get(activeTabId) ?? null;
}
if (activeSession) {
return sessionHostsMap.get(activeSession.id) ?? sftpHostForTab.get(activeTabId) ?? null;
}
return sftpHostForTab.get(activeTabId) ?? null;
}, [activeSession, activeTabId, activeWorkspace, focusedSessionId, isSftpOpenForCurrentTab, sessionHostsMap, sftpHostForTab]);
// Keep the same-endpoint SSH session id across disconnected/connecting so
// SftpSidePanel can observe status transitions and rebind after Start over.
// Transport reuse for openSftp still requires status === "connected".
const activeTerminalSessionIdForSftp = useMemo((): string | null => {
if (!isSftpOpenForCurrentTab || !sftpActiveHost) return null;
const sessionId = activeWorkspace ? focusedSessionId : activeSession?.id;
if (!sessionId) return null;
const session = sessions.find((candidate) => candidate.id === sessionId);
if (!session || !isTerminalSessionEligibleForSftpReuse(session)) return null;
const sessionHost = sessionHostsMap.get(session.id);
if (!sessionHost) return null;
const sameEndpoint =
sessionHost.hostname === sftpActiveHost.hostname
&& (sessionHost.port || 22) === (sftpActiveHost.port || 22)
&& (sessionHost.username || 'root') === (sftpActiveHost.username || 'root');
return sameEndpoint ? session.id : null;
}, [activeSession?.id, activeWorkspace, focusedSessionId, isSftpOpenForCurrentTab, sessions, sessionHostsMap, sftpActiveHost]);
const linkedTerminalSessionIdForSftp = useMemo((): string | null => {
if (!isSftpOpenForCurrentTab) return null;
if (activeTerminalSessionIdForSftp) return activeTerminalSessionIdForSftp;
return activeWorkspace ? (focusedSessionId ?? null) : (activeSession?.id ?? null);
}, [
activeSession?.id,
activeTerminalSessionIdForSftp,
activeWorkspace,
focusedSessionId,
isSftpOpenForCurrentTab,
]);
// Recomputed when terminalCwdVersion changes (useSyncExternalStore above).
const activeTerminalCwd = linkedTerminalSessionIdForSftp
? (
terminalCwdStore.getCwd(linkedTerminalSessionIdForSftp)
?? s.terminalRendererCwdBySessionRef.current.get(linkedTerminalSessionIdForSftp)
?? null
)
: null;
const activeTerminalCwdSource = linkedTerminalSessionIdForSftp
? (
terminalCwdStore.getSource(linkedTerminalSessionIdForSftp)
?? s.terminalRendererCwdSourceBySessionRef.current.get(linkedTerminalSessionIdForSftp)
)
: undefined;
const activeTerminalCwdTrusted = activeTerminalCwdSource === 'osc7'
|| activeTerminalCwdSource === 'backend-strict';
void terminalCwdVersion;
const historySessionId = effectiveFocusedSessionId;
const activeTerminalSessionForSystem = useMemo(
() => resolveSystemSidebarSession(sessions, activeWorkspace, focusedSessionId, activeSession),
[activeSession, activeWorkspace, focusedSessionId, sessions],
);
const activeSystemSessionHost = useMemo((): Host | null => {
const id = activeTerminalSessionForSystem?.id;
if (!id) return null;
return sessionHostsMap.get(id) ?? null;
}, [activeTerminalSessionForSystem?.id, sessionHostsMap]);
const systemWarmupSessionIds = useMemo(() => {
if (!activeTabId || !activeSidePanelTools.has('system')) return [];
const session = activeTerminalSessionForSystem;
if (!session || session.status !== 'connected') return [];
return [session.id];
}, [activeSidePanelTools, activeTabId, activeTerminalSessionForSystem]);
useSystemCapabilitiesWarmup(
systemWarmupSessionIds,
systemBackend,
systemWarmupSessionIds.length > 0,
(s.terminalSettings?.systemManagerProcessRefreshInterval ?? 3) * 1000,
);
useEffect(() => {
sessionCapabilitiesStore.prune(new Set(sessions.map((session) => session.id)));
}, [sessions]);
const focusedHost = useMemo((): Host | null => {
if (!historySessionId) return null;
return sessionHostsMap.get(historySessionId) ?? null;
}, [historySessionId, sessionHostsMap]);
const themeState = useTerminalThemePanelState({
activeSession,
activeSidePanelTab: activeSidePanelTools.has('theme') ? 'theme' : activeSidePanelTab,
activeWorkspace,
followAppTerminalTheme: s.followAppTerminalTheme,
focusedSessionId,
fontSize: s.fontSize,
hostMap: s.hostMap,
isSidePanelOpenForCurrentTab,
isVisible,
onUpdateHost: s.onUpdateHost,
onUpdateTerminalFontFamilyId: s.onUpdateTerminalFontFamilyId,
onUpdateTerminalFontSize: s.onUpdateTerminalFontSize,
onUpdateSessionFontSize: s.onUpdateSessionFontSize,
onClearSessionFontSizeOverride: s.onClearSessionFontSizeOverride,
onUpdateTerminalFontWeight: s.onUpdateTerminalFontWeight,
onUpdateTerminalThemeId: s.onUpdateTerminalThemeId,
pickTheme: s.pickTerminalTheme,
clearIntent: s.clearThemeIntent,
resolveFocusedAppearance: s.resolveSessionAppearance,
sessionHostsMap,
terminalFontFamilyId: s.terminalFontFamilyId,
terminalSettings: s.terminalSettings,
terminalTheme: s.terminalTheme,
});
useManualTerminalChromeSurfaceInjection(
themeState.resolvedPreviewTheme,
!s.followAppTerminalTheme && isTerminalLayerVisible,
);
const sidePanelLiveSnapshot = useMemo<SidePanelLiveSnapshot>(() => ({
sftpActiveHost,
activeTerminalSessionIdForSftp,
activeTerminalCwd,
activeTerminalCwdTrusted,
activeWorkspace,
activeTerminalSessionForSystem: activeTerminalSessionForSystem ?? null,
activeSystemSessionHost,
focusedHost,
focusedSessionId: effectiveFocusedSessionId,
historySessionId,
resolvedPreviewTheme: themeState.resolvedPreviewTheme,
previewedOrVisibleThemeId: themeState.previewedOrVisibleThemeId,
focusedFontFamilyId: themeState.focusedFontFamilyId,
focusedFontFamilyOverridden: themeState.focusedFontFamilyOverridden,
focusedFontSize: themeState.focusedFontSize,
focusedFontSizeOverridden: themeState.focusedFontSizeOverridden,
focusedFontWeight: themeState.focusedFontWeight,
focusedFontWeightOverridden: themeState.focusedFontWeightOverridden,
focusedThemeOverridden: themeState.focusedThemeOverridden,
}), [
activeSystemSessionHost,
activeTerminalCwd,
activeTerminalCwdTrusted,
activeTerminalSessionForSystem,
activeTerminalSessionIdForSftp,
activeWorkspace,
effectiveFocusedSessionId,
focusedHost,
historySessionId,
sftpActiveHost,
themeState.focusedFontFamilyId,
themeState.focusedFontFamilyOverridden,
themeState.focusedFontSize,
themeState.focusedFontSizeOverridden,
themeState.focusedFontWeight,
themeState.focusedFontWeightOverridden,
themeState.focusedThemeOverridden,
themeState.previewedOrVisibleThemeId,
themeState.resolvedPreviewTheme,
]);
useLayoutEffect(() => {
sidePanelLiveStore.update(sidePanelLiveSnapshot);
}, [sidePanelLiveSnapshot]);
const { aiContextsByTabId, resolveAIExecutorContext } = useTerminalAiContexts({
hosts: s.hosts,
hostsRef: s.hostsRef,
portForwardingRules: s.portForwardingRules,
portForwardingRulesRef: s.portForwardingRulesRef,
mountedAiTabIds: s.mountedAiTabIds,
sessionHostsMap,
sessions,
sessionsRef: s.sessionsRef,
terminalContextReadersRef,
workspaces: s.workspaces,
workspacesRef: s.workspacesRef,
});
const handleTerminalContextReaderChange = React.useCallback((
sessionId: string,
reader: TerminalContextReader | null,
) => {
if (reader) {
terminalContextReadersRef.current.set(sessionId, reader);
} else {
terminalContextReadersRef.current.delete(sessionId);
}
}, []);
const prevFocusedSessionIdRef = useRef<string | undefined>(undefined);
useTerminalLayerEffects({
activeSidePanelTab,
activeSidePanelLayout,
activeTabId,
activeTabIdRef: s.activeTabIdRef,
activeWorkspace,
activityTrackedSessions: s.activityTrackedSessions,
cancelAnimationFrame,
ChunkedEscapeFilter: s.ChunkedEscapeFilter,
clearTimeout,
document,
dropHint,
effectiveHosts: s.effectiveHosts,
filterTabsMap: s.filterTabsMap,
onConnectToHost: s.onConnectToHost,
focusedSessionId,
getSessionActivityIdsToClear: s.getSessionActivityIdsToClear,
handleToggleAiFromTopBar: s.handleToggleAiFromTopBar,
handleToggleSystemFromTopBar: s.handleToggleSystemFromTopBar,
handleToggleScriptsSidePanel: s.handleToggleScriptsSidePanel,
handleToggleSidePanel: s.handleToggleSidePanel,
hasNotifiableTerminalOutput: s.hasNotifiableTerminalOutput,
isComposeBarOpen: s.isComposeBarOpen,
isFocusMode,
isTerminalLayerVisible,
shouldMeasureTerminalLayerLayout: shouldMeasureTerminalLayerLayout({
isTerminalLayerVisible,
keepHiddenLayoutActive,
workspaceArea,
}),
lastSidePanelTabRef: s.lastSidePanelTabRef,
Map,
Math,
onSessionData: s.onSessionData,
onSplitSessionRef: s.onSplitSessionRef,
onToggleBroadcastRef: s.onToggleBroadcastRef,
onToggleWorkspaceViewModeRef: s.onToggleWorkspaceViewModeRef,
prevFocusedSessionIdRef,
refocusActiveTerminalSession: s.refocusActiveTerminalSession,
requestAnimationFrame,
ResizeObserver,
sessionActivityStore: s.sessionActivityStore,
sessionHostsMap,
sessions,
Set,
setDropHint,
setSftpHostForTab: s.setSftpHostForTab,
setSftpInitialLocationForTab: s.setSftpInitialLocationForTab,
setSftpPendingUploadsForTab: s.setSftpPendingUploadsForTab,
setAiMountedTabIds: s.setAiMountedTabIds,
setNotesMountedTabIds: s.setNotesMountedTabIds,
setScriptsMountedTabIds: s.setScriptsMountedTabIds,
setSystemMountedTabIds: s.setSystemMountedTabIds,
setThemeMountedTabIds: s.setThemeMountedTabIds,
setSidePanelOpenTabs: s.setSidePanelOpenTabs,
setSidePanelLayouts: s.setSidePanelLayouts,
setTimeout,
setWorkspaceArea,
sidePanelPosition: s.sidePanelPosition,
sidePanelWidth: s.sidePanelWidth,
sftpActiveHost,
sftpHostForTab,
sftpPaneClosedTabIdsRef: s.sftpPaneClosedTabIdsRef,
shouldMarkSessionActivity: s.shouldMarkSessionActivity,
sidePanelOpenTabs,
splitHorizontalHandlersRef: s.splitHorizontalHandlersRef,
splitVerticalHandlersRef: s.splitVerticalHandlersRef,
terminalRendererCwdBySessionRef: s.terminalRendererCwdBySessionRef,
toggleScriptsSidePanelRef: s.toggleScriptsSidePanelRef,
toggleSidePanelRef: s.toggleSidePanelRef,
validAIScopeTargetIds: s.validAIScopeTargetIds,
validSessionActivityIds: s.validSessionActivityIds,
window,
workspaceBroadcastHandlersRef: s.workspaceBroadcastHandlersRef,
workspaceFocusHandlersRef: s.workspaceFocusHandlersRef,
workspaceInnerRef,
workspaces: s.workspaces,
});
const ctx = useMemo(() => ({
accentMode: s.accentMode,
activeHostIdForSidebar,
activeResizers,
activeSidePanelTab,
activeSidePanelLayout,
activeTabId,
activeTerminalCwd,
activeTerminalCwdTrusted,
activeTerminalSessionIdForSftp,
activeWorkspace,
AIChatPanelsHost: s.AIChatPanelsHost,
AISidePanelStateRoot: s.AISidePanelStateRoot,
aiContextsByTabId,
Array: s.Array,
Button: s.Button,
cn,
composeBarThemeColors: themeState.composeBarThemeColors,
computeSplitHint,
customAccent: s.customAccent,
customGroups: s.customGroups,
draggingSessionId: s.draggingSessionId,
dropHint,
editorWordWrap: s.editorWordWrap,
effectiveHosts: s.effectiveHosts,
findSplitNode,
focusedFontFamilyId: themeState.focusedFontFamilyId,
focusedFontFamilyOverridden: themeState.focusedFontFamilyOverridden,
focusedFontSize: themeState.focusedFontSize,
focusedFontSizeOverridden: themeState.focusedFontSizeOverridden,
focusedFontWeight: themeState.focusedFontWeight,
focusedFontWeightOverridden: themeState.focusedFontWeightOverridden,
focusedHost,
focusedSessionId,
focusedThemeOverridden: themeState.focusedThemeOverridden,
FolderTree: s.FolderTree,
followAppTerminalTheme: s.followAppTerminalTheme,
handleHistoryPaste: s.handleHistoryPaste,
handleHistoryDelete: s.handleHistoryDelete,
handleHistoryRun: s.handleHistoryRun,
handleFocusSidePanelPane: s.handleFocusSidePanelPane,
handleMagnifySidePanelPane: s.handleMagnifySidePanelPane,
handleRestoreMagnifiedPane: s.handleRestoreMagnifiedPane,
handleMagnifyTerminalPane: s.handleMagnifyTerminalPane,
handleTerminalPaneInteraction: s.handleTerminalPaneInteraction,
handleSplitSidePanelPane: s.handleSplitSidePanelPane,
handleCloseSidePanelPane: s.handleCloseSidePanelPane,
handleResizeSidePanelSplit: s.handleResizeSidePanelSplit,
fontSize: s.fontSize,
getTerminalCwd: s.getTerminalCwd,
handleAddKnownHost: s.handleAddKnownHost,
handleAddSelectionToAI: s.handleAddSelectionToAI,
handleBroadcastInput: s.handleBroadcastInput,
handleCloseSession: s.handleCloseSession,
handleCloseSidePanel: s.handleCloseSidePanel,
handleCommandExecuted: s.handleCommandExecuted,
handleCommandSubmitted: s.handleCommandSubmitted,
handleComposeSend: s.handleComposeSend,
handleFontFamilyChangeForFocusedSession: themeState.handleFontFamilyChangeForFocusedSession,
handleFontFamilyResetForFocusedSession: themeState.handleFontFamilyResetForFocusedSession,
handleFontSizeChangeForFocusedSession: themeState.handleFontSizeChangeForFocusedSession,
handleFontSizeResetForFocusedSession: themeState.handleFontSizeResetForFocusedSession,
handleFontWeightChangeForFocusedSession: themeState.handleFontWeightChangeForFocusedSession,
handleFontWeightResetForFocusedSession: themeState.handleFontWeightResetForFocusedSession,
handleOpenAI: s.handleOpenAI,
handleOpenNotes: s.handleOpenNotes,
handleOpenSystem: s.handleOpenSystem,
handleOpenHistory: s.handleOpenHistory,
handleOpenScripts: s.handleOpenScripts,
activeTerminalSessionForSystem,
activeSystemSessionHost,
handleOpenSftp: s.handleOpenSftp,
handleOpenTheme: s.handleOpenTheme,
handleBackFromNotes: s.handleBackFromNotes,
handleOpenHostFromNotes: s.handleOpenHostFromNotes,
History: s.History,
historySessionId,
HistorySidePanel: s.HistorySidePanel,
// remoteHistory / shellHistory are owned by History side-panel slot stores
hibernateHiddenTabs,
handleOsDetected: s.handleOsDetected,
handlePendingTerminalSelectionConsumed: s.handlePendingTerminalSelectionConsumed,
handlePendingUploadHandled: s.handlePendingUploadHandled,
handleSessionExit: s.handleSessionExit,
handleSftpCurrentPathChange: s.handleSftpCurrentPathChange,
handleSftpActiveTransfersChange: s.handleSftpActiveTransfersChange,
handleSftpActiveExternalEditsChange: s.handleSftpActiveExternalEditsChange,
handleSftpInitialLocationApplied: s.handleSftpInitialLocationApplied,
persistSidePanelWidth: s.persistSidePanelWidth,
setSidePanelWidth: s.setSidePanelWidth,
handleSnippetClickForFocusedSession: s.handleSnippetClickForFocusedSession,
handleSnippetFromPanel: s.handleSnippetFromPanel,
handleRunScriptFromPanel: s.handleRunScriptFromPanel,
handleRunScriptOnWorkspace: s.handleRunScriptOnWorkspace,
handleStartRecordingFromPanel: s.handleStartRecordingFromPanel,
handleStopScriptRun: s.handleStopScriptRun,
handlePauseScriptRun: s.handlePauseScriptRun,
handleResumeScriptRun: s.handleResumeScriptRun,
handleSnippetExecutorChange: s.handleSnippetExecutorChange,
handleBroadcastInterruptPriorityChange: s.handleBroadcastInterruptPriorityChange,
handleProgrammaticCommandLogRewriteChange: s.handleProgrammaticCommandLogRewriteChange,
handleStatusChange: s.handleStatusChange,
handleTerminalCwdChange: s.handleTerminalCwdChange,
handleTerminalTitleChange: s.handleTerminalTitleChange,
handleTerminalBell: s.handleTerminalBell,
handleTerminalOutput: s.handleTerminalOutput,
handleTerminalDataCapture: s.handleTerminalDataCapture,
handleTerminalContextReaderChange,
handleTerminalFontSizeChange: s.handleTerminalFontSizeChange,
handleThemeChangeForFocusedSession: themeState.handleThemeChangeForFocusedSession,
handleThemeResetForFocusedSession: themeState.handleThemeResetForFocusedSession,
handleToggleSftpFromBar: s.handleToggleSftpFromBar,
handleToggleWorkspaceComposeBar: s.handleToggleWorkspaceComposeBar,
handleUpdateHost: s.handleUpdateHost,
handleWorkspaceDrop,
hosts: s.hosts,
hotkeyScheme: s.hotkeyScheme,
disableTerminalFontZoom: s.disableTerminalFontZoom,
restoreTerminalCwd: s.restoreTerminalCwd,
identities: s.identities,
isBroadcastEnabled: s.isBroadcastEnabled,
isComposeBarOpen: s.isComposeBarOpen,
isFocusMode,
isSidePanelOpenForCurrentTab,
isTerminalLayerVisible,
keyBindings: s.keyBindings,
keys: s.keys,
knownHosts: s.knownHosts,
MessageSquare: s.MessageSquare,
magnifiedPane: s.magnifiedPane,
mountedAiTabIds: s.mountedAiTabIds,
mountedSftpTabIds: s.mountedSftpTabIds,
notesMountedTabIds: s.notesMountedTabIds,
notesOpenNoteByTab: s.notesOpenNoteByTab,
NotesManager: s.NotesManager,
scriptsMountedTabIds: s.scriptsMountedTabIds,
systemMountedTabIds: s.systemMountedTabIds,
themeMountedTabIds: s.themeMountedTabIds,
onConnectToHost: s.onConnectToHost,
onCreateLocalTerminal: s.onCreateLocalTerminal,
onHotkeyAction: s.onHotkeyAction,
onReorderWorkspaceSessions: s.onReorderWorkspaceSessions,
onReorderTabs: s.onReorderTabs,
onCopySession: s.onCopySession,
onDuplicateSession: s.onDuplicateSession,
onCopySessionToNewWindow: s.onCopySessionToNewWindow,
onUpdateSessionRestoreCwd: s.onUpdateSessionRestoreCwd,
onUpdateSessionDynamicTitle: s.onUpdateSessionDynamicTitle,
onUpdateSessionCodingCliProvider: s.onUpdateSessionCodingCliProvider,
onRequestAddToWorkspace: s.onRequestAddToWorkspace,
onAppendHostToWorkspace: s.onAppendHostToWorkspace,
onSetWorkspaceFocusedSession: s.onSetWorkspaceFocusedSession,
onStartSessionRename: s.onStartSessionRename,
onSubmitSessionRename: s.onSubmitSessionRename,
onRemoveSessionFromWorkspace: s.onRemoveSessionFromWorkspace,
onOpenVaultNoteFromChat: s.onOpenVaultNoteFromChat,
onOpenVaultHostFromChat: s.onOpenVaultHostFromChat,
onOpenVaultSectionFromChat: s.onOpenVaultSectionFromChat,
onOpenVaultSnippetFromChat: s.onOpenVaultSnippetFromChat,
onStartSessionDrag: s.onStartSessionDrag,
onEndSessionDrag: s.onEndSessionDrag,
onSplitSession: s.onSplitSession,
onToggleWorkspaceViewMode: s.onToggleWorkspaceViewMode,
Palette: s.Palette,
PanelLeft: s.PanelLeft,
PanelRight: s.PanelRight,
pendingTerminalSelectionForAI: s.pendingTerminalSelectionForAI,
previewedOrVisibleThemeId: themeState.previewedOrVisibleThemeId,
refocusActiveTerminalSession: s.refocusActiveTerminalSession,
refocusTerminalSession: s.refocusTerminalSession,
resizing,
resolveAIExecutorContext,
resolvedPreviewTheme: themeState.resolvedPreviewTheme,
ScriptsSidePanel: s.ScriptsSidePanel,
sessionChainHostsMap: s.sessionChainHostsMap,
sessionHostsMap,
resolvedSessionHostIds: s.resolvedSessionHostIds,
sessionLogConfig: s.sessionLogConfig,
sessionSudoAutofillPasswordsMap: s.sessionSudoAutofillPasswordsMap,
sessionSudoAutofillCandidatesMap: s.sessionSudoAutofillCandidatesMap,
sessions,
setDropHint,
setEditorWordWrap: s.setEditorWordWrap,
setIsComposeBarOpen: s.setIsComposeBarOpen,
setResizing,
showHostTreeSidebar,
setSidePanelPosition: s.setSidePanelPosition,
setSftpFollowTerminalCwd: s.setSftpFollowTerminalCwd,
sftpActiveHost,
sftpHostForTab,
sftpAutoSync: s.sftpAutoSync,
sftpDefaultViewMode: s.sftpDefaultViewMode,
sftpDoubleClickBehavior: s.sftpDoubleClickBehavior,
sftpFollowTerminalCwd: s.sftpFollowTerminalCwd,
sftpInitialLocationForTab: s.sftpInitialLocationForTab,
sftpPendingUploadsForTab: s.sftpPendingUploadsForTab,
sftpPaneClosedTabIdsRef: s.sftpPaneClosedTabIdsRef,
sftpShowHiddenFiles: s.sftpShowHiddenFiles,
SftpSidePanel: s.SftpSidePanel,
sftpUseCompressedUpload: s.sftpUseCompressedUpload,
sidePanelPosition: s.sidePanelPosition,
sidePanelWidth: s.sidePanelWidth,
sidePanelOpenTabs,
sidePanelLayouts,
snippetPackages: s.snippetPackages,
snippets: s.snippets,
updateSnippetPackages: s.updateSnippetPackages,
updateSnippets: s.updateSnippets,
splitHorizontalHandlersRef: s.splitHorizontalHandlersRef,
splitVerticalHandlersRef: s.splitVerticalHandlersRef,
sshDebugLogsEnabled: s.sshDebugLogsEnabled,
t: s.t,
TerminalComposeBar: s.TerminalComposeBar,
terminalFontFamilyId: s.terminalFontFamilyId,
TerminalPanesHost: s.TerminalPanesHost,
terminalSettings: s.terminalSettings,
terminalTheme: s.terminalTheme,
terminalThemeId: s.terminalThemeId,
resolveSessionAppearance: s.resolveSessionAppearance,
hostMap: s.hostMap,
ThemeSidePanel: s.ThemeSidePanel,
Tooltip: s.Tooltip,
TooltipContent: s.TooltipContent,
TooltipTrigger: s.TooltipTrigger,
updateHosts: s.updateHosts,
validAIScopeTargetIds: s.validAIScopeTargetIds,
workspaceBroadcastHandlersRef: s.workspaceBroadcastHandlersRef,
workspaceById,
isGlobalBroadcastEnabled: s.isGlobalBroadcastEnabled,
canUseGlobalBroadcast: s.canUseGlobalBroadcast,
onToggleGlobalBroadcast: s.onToggleGlobalBroadcastRef.current,
// AI scope maintenance (merge/dissolve handoff) needs the full list; do not
// rely on workspaceById alone — SidePanelStateRoot reads ctx.workspaces.
workspaces: s.workspaces,
workspaceFocusHandlersRef: s.workspaceFocusHandlersRef,
workspaceInnerRef,
workspaceOuterRef,
workspaceOverlayRef,
workspaceRectsById,
X: s.X,
Zap: s.Zap,
// stableRef fields are intentionally omitted from deps — they update every parent render.
// eslint-disable-next-line react-hooks/exhaustive-deps
}), [
activeHostIdForSidebar,
activeResizers,
activeSidePanelLayout,
activeSidePanelTab,
activeTabId,
activeTerminalCwd,
activeTerminalCwdTrusted,
activeTerminalSessionIdForSftp,
activeWorkspace,
aiContextsByTabId,
computeSplitHint,
dropHint,
focusedHost,
focusedSessionId,
s.restoreTerminalCwd,
handleWorkspaceDrop,
handleTerminalContextReaderChange,
hibernateHiddenTabs,
historySessionId,
isFocusMode,
isSidePanelOpenForCurrentTab,
isTerminalLayerVisible,
s.magnifiedPane,
resizing,
resolveAIExecutorContext,
sessionHostsMap,
sidePanelLayouts,
s.resolvedSessionHostIds,
sessions,
s.terminalSettings,
showHostTreeSidebar,
sftpActiveHost,
s.sftpFollowTerminalCwd,
themeState,
workspaceById,
s.workspaces,
workspaceInnerRef,
workspaceOuterRef,
workspaceOverlayRef,
workspaceRectsById,
s.terminalTheme,
s.resolveSessionAppearance,
s.hostMap,
s.isGlobalBroadcastEnabled,
s.canUseGlobalBroadcast,
s.onToggleGlobalBroadcastRef,
]);
return <TerminalLayerView ctx={ctx} />;
}