fix(app): reduce unnecessary workspace screen re-renders from unstable callback deps
- Add useCloseTabs hook to centralize tab close state with stable closeTab callback and reactive closingTabIds - Replace killTerminalMutation/isArchivingAgent props with closingTabIds Set in split container - Stabilize archiveAgent callback by depending on mutateAsync instead of whole mutation object - Add bail-out to focusTabInLayout and focusPaneInLayout when already focused - Remove redundant isArchivingAgent guard (closeTab handles dedup)
This commit is contained in:
@@ -57,9 +57,7 @@ interface SplitContainerProps {
|
|||||||
hoveredCloseTabKey: string | null;
|
hoveredCloseTabKey: string | null;
|
||||||
setHoveredTabKey: Dispatch<SetStateAction<string | null>>;
|
setHoveredTabKey: Dispatch<SetStateAction<string | null>>;
|
||||||
setHoveredCloseTabKey: Dispatch<SetStateAction<string | null>>;
|
setHoveredCloseTabKey: Dispatch<SetStateAction<string | null>>;
|
||||||
isArchivingAgent: (input: { serverId: string; agentId: string }) => boolean;
|
closingTabIds: Set<string>;
|
||||||
killTerminalPending: boolean;
|
|
||||||
killTerminalId: string | null;
|
|
||||||
onNavigateTab: (tabId: string) => void;
|
onNavigateTab: (tabId: string) => void;
|
||||||
onCloseTab: (tabId: string) => Promise<void> | void;
|
onCloseTab: (tabId: string) => Promise<void> | void;
|
||||||
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
|
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
|
||||||
@@ -162,9 +160,7 @@ export function SplitContainer({
|
|||||||
hoveredCloseTabKey,
|
hoveredCloseTabKey,
|
||||||
setHoveredTabKey,
|
setHoveredTabKey,
|
||||||
setHoveredCloseTabKey,
|
setHoveredCloseTabKey,
|
||||||
isArchivingAgent,
|
closingTabIds,
|
||||||
killTerminalPending,
|
|
||||||
killTerminalId,
|
|
||||||
onNavigateTab,
|
onNavigateTab,
|
||||||
onCloseTab,
|
onCloseTab,
|
||||||
onCopyResumeCommand,
|
onCopyResumeCommand,
|
||||||
@@ -410,9 +406,7 @@ export function SplitContainer({
|
|||||||
hoveredCloseTabKey={hoveredCloseTabKey}
|
hoveredCloseTabKey={hoveredCloseTabKey}
|
||||||
setHoveredTabKey={setHoveredTabKey}
|
setHoveredTabKey={setHoveredTabKey}
|
||||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||||
isArchivingAgent={isArchivingAgent}
|
closingTabIds={closingTabIds}
|
||||||
killTerminalPending={killTerminalPending}
|
|
||||||
killTerminalId={killTerminalId}
|
|
||||||
onNavigateTab={onNavigateTab}
|
onNavigateTab={onNavigateTab}
|
||||||
onCloseTab={onCloseTab}
|
onCloseTab={onCloseTab}
|
||||||
onCopyResumeCommand={onCopyResumeCommand}
|
onCopyResumeCommand={onCopyResumeCommand}
|
||||||
@@ -538,9 +532,7 @@ function SplitNodeView({
|
|||||||
hoveredCloseTabKey,
|
hoveredCloseTabKey,
|
||||||
setHoveredTabKey,
|
setHoveredTabKey,
|
||||||
setHoveredCloseTabKey,
|
setHoveredCloseTabKey,
|
||||||
isArchivingAgent,
|
closingTabIds,
|
||||||
killTerminalPending,
|
|
||||||
killTerminalId,
|
|
||||||
onNavigateTab,
|
onNavigateTab,
|
||||||
onCloseTab,
|
onCloseTab,
|
||||||
onCopyResumeCommand,
|
onCopyResumeCommand,
|
||||||
@@ -575,9 +567,7 @@ function SplitNodeView({
|
|||||||
hoveredCloseTabKey={hoveredCloseTabKey}
|
hoveredCloseTabKey={hoveredCloseTabKey}
|
||||||
setHoveredTabKey={setHoveredTabKey}
|
setHoveredTabKey={setHoveredTabKey}
|
||||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||||
isArchivingAgent={isArchivingAgent}
|
closingTabIds={closingTabIds}
|
||||||
killTerminalPending={killTerminalPending}
|
|
||||||
killTerminalId={killTerminalId}
|
|
||||||
onNavigateTab={onNavigateTab}
|
onNavigateTab={onNavigateTab}
|
||||||
onCloseTab={onCloseTab}
|
onCloseTab={onCloseTab}
|
||||||
onCopyResumeCommand={onCopyResumeCommand}
|
onCopyResumeCommand={onCopyResumeCommand}
|
||||||
@@ -626,9 +616,7 @@ function SplitNodeView({
|
|||||||
hoveredCloseTabKey={hoveredCloseTabKey}
|
hoveredCloseTabKey={hoveredCloseTabKey}
|
||||||
setHoveredTabKey={setHoveredTabKey}
|
setHoveredTabKey={setHoveredTabKey}
|
||||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||||
isArchivingAgent={isArchivingAgent}
|
closingTabIds={closingTabIds}
|
||||||
killTerminalPending={killTerminalPending}
|
|
||||||
killTerminalId={killTerminalId}
|
|
||||||
onNavigateTab={onNavigateTab}
|
onNavigateTab={onNavigateTab}
|
||||||
onCloseTab={onCloseTab}
|
onCloseTab={onCloseTab}
|
||||||
onCopyResumeCommand={onCopyResumeCommand}
|
onCopyResumeCommand={onCopyResumeCommand}
|
||||||
@@ -677,9 +665,7 @@ function SplitPaneView({
|
|||||||
hoveredCloseTabKey,
|
hoveredCloseTabKey,
|
||||||
setHoveredTabKey,
|
setHoveredTabKey,
|
||||||
setHoveredCloseTabKey,
|
setHoveredCloseTabKey,
|
||||||
isArchivingAgent,
|
closingTabIds,
|
||||||
killTerminalPending,
|
|
||||||
killTerminalId,
|
|
||||||
onNavigateTab,
|
onNavigateTab,
|
||||||
onCloseTab,
|
onCloseTab,
|
||||||
onCopyResumeCommand,
|
onCopyResumeCommand,
|
||||||
@@ -718,32 +704,16 @@ function SplitPaneView({
|
|||||||
const activeTabDescriptor = paneState.activeTab?.descriptor ?? null;
|
const activeTabDescriptor = paneState.activeTab?.descriptor ?? null;
|
||||||
const desktopTabRowItems = useMemo<WorkspaceDesktopTabRowItem[]>(
|
const desktopTabRowItems = useMemo<WorkspaceDesktopTabRowItem[]>(
|
||||||
() =>
|
() =>
|
||||||
paneTabs.map((tab) => {
|
paneTabs.map((tab) => ({
|
||||||
const isClosingAgent =
|
tab,
|
||||||
tab.target.kind === "agent" &&
|
isActive: tab.key === activeTabDescriptor?.key,
|
||||||
isArchivingAgent({
|
isCloseHovered: hoveredCloseTabKey === tab.key,
|
||||||
serverId: normalizedServerId,
|
isClosingTab: closingTabIds.has(tab.tabId),
|
||||||
agentId: tab.target.agentId,
|
})),
|
||||||
});
|
|
||||||
const isClosingTerminal =
|
|
||||||
tab.target.kind === "terminal" &&
|
|
||||||
killTerminalPending &&
|
|
||||||
killTerminalId === tab.target.terminalId;
|
|
||||||
|
|
||||||
return {
|
|
||||||
tab,
|
|
||||||
isActive: tab.key === activeTabDescriptor?.key,
|
|
||||||
isCloseHovered: hoveredCloseTabKey === tab.key,
|
|
||||||
isClosingTab: isClosingAgent || isClosingTerminal,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
[
|
[
|
||||||
activeTabDescriptor?.key,
|
activeTabDescriptor?.key,
|
||||||
|
closingTabIds,
|
||||||
hoveredCloseTabKey,
|
hoveredCloseTabKey,
|
||||||
isArchivingAgent,
|
|
||||||
killTerminalId,
|
|
||||||
killTerminalPending,
|
|
||||||
normalizedServerId,
|
|
||||||
paneTabs,
|
paneTabs,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -194,20 +194,13 @@ export function useArchiveAgent() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const archiveMutateAsync = archiveMutation.mutateAsync;
|
||||||
|
|
||||||
const archiveAgent = useCallback(
|
const archiveAgent = useCallback(
|
||||||
async (input: ArchiveAgentInput): Promise<void> => {
|
async (input: ArchiveAgentInput): Promise<void> => {
|
||||||
if (
|
await archiveMutateAsync(input);
|
||||||
isAgentArchiving({
|
|
||||||
queryClient,
|
|
||||||
serverId: input.serverId,
|
|
||||||
agentId: input.agentId,
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await archiveMutation.mutateAsync(input);
|
|
||||||
},
|
},
|
||||||
[archiveMutation, queryClient]
|
[archiveMutateAsync]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isArchivingAgent = useCallback(
|
const isArchivingAgent = useCallback(
|
||||||
|
|||||||
@@ -121,11 +121,13 @@ const NEW_TAB_TERMINAL_OPTION_ID = "__new_tab_terminal__";
|
|||||||
type NewTabOptionId = typeof NEW_TAB_AGENT_OPTION_ID | typeof NEW_TAB_TERMINAL_OPTION_ID;
|
type NewTabOptionId = typeof NEW_TAB_AGENT_OPTION_ID | typeof NEW_TAB_TERMINAL_OPTION_ID;
|
||||||
const EMPTY_UI_TABS: WorkspaceTab[] = [];
|
const EMPTY_UI_TABS: WorkspaceTab[] = [];
|
||||||
const EMPTY_PINNED_AGENT_IDS = new Set<string>();
|
const EMPTY_PINNED_AGENT_IDS = new Set<string>();
|
||||||
|
const EMPTY_SET = new Set<string>();
|
||||||
|
|
||||||
type WorkspaceScreenProps = {
|
type WorkspaceScreenProps = {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
openIntent?: WorkspaceOpenIntent | null;
|
openIntent?: WorkspaceOpenIntent | null;
|
||||||
|
onOpenIntentConsumed?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function trimNonEmpty(value: string | null | undefined): string | null {
|
function trimNonEmpty(value: string | null | undefined): string | null {
|
||||||
@@ -508,6 +510,7 @@ export function WorkspaceScreen({
|
|||||||
serverId,
|
serverId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
openIntent,
|
openIntent,
|
||||||
|
onOpenIntentConsumed,
|
||||||
}: WorkspaceScreenProps) {
|
}: WorkspaceScreenProps) {
|
||||||
const isFocused = useIsFocused();
|
const isFocused = useIsFocused();
|
||||||
|
|
||||||
@@ -521,15 +524,47 @@ export function WorkspaceScreen({
|
|||||||
serverId={serverId}
|
serverId={serverId}
|
||||||
workspaceId={workspaceId}
|
workspaceId={workspaceId}
|
||||||
openIntent={openIntent}
|
openIntent={openIntent}
|
||||||
|
onOpenIntentConsumed={onOpenIntentConsumed}
|
||||||
/>
|
/>
|
||||||
</ExplorerSidebarAnimationProvider>
|
</ExplorerSidebarAnimationProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UseCloseTabsResult {
|
||||||
|
closingTabIds: Set<string>;
|
||||||
|
closeTab: (tabId: string, action: () => Promise<void>) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useCloseTabs(): UseCloseTabsResult {
|
||||||
|
const pendingRef = useRef(new Set<string>());
|
||||||
|
const [closingTabIds, setClosingTabIds] = useState<Set<string>>(EMPTY_SET);
|
||||||
|
|
||||||
|
const closeTab = useCallback(
|
||||||
|
async (tabId: string, action: () => Promise<void>) => {
|
||||||
|
const normalized = tabId.trim();
|
||||||
|
if (!normalized || pendingRef.current.has(normalized)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingRef.current.add(normalized);
|
||||||
|
setClosingTabIds(new Set(pendingRef.current));
|
||||||
|
try {
|
||||||
|
await action();
|
||||||
|
} finally {
|
||||||
|
pendingRef.current.delete(normalized);
|
||||||
|
setClosingTabIds(new Set(pendingRef.current));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { closingTabIds, closeTab };
|
||||||
|
}
|
||||||
|
|
||||||
function WorkspaceScreenContent({
|
function WorkspaceScreenContent({
|
||||||
serverId,
|
serverId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
openIntent,
|
openIntent,
|
||||||
|
onOpenIntentConsumed,
|
||||||
}: WorkspaceScreenProps) {
|
}: WorkspaceScreenProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
@@ -643,7 +678,7 @@ function WorkspaceScreenContent({
|
|||||||
return payload;
|
return payload;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { archiveAgent, isArchivingAgent } = useArchiveAgent();
|
const { archiveAgent } = useArchiveAgent();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!client || !isConnected || !normalizedWorkspaceId.startsWith("/")) {
|
if (!client || !isConnected || !normalizedWorkspaceId.startsWith("/")) {
|
||||||
@@ -827,7 +862,7 @@ function WorkspaceScreenContent({
|
|||||||
);
|
);
|
||||||
const pendingByDraftId = useCreateFlowStore((state) => state.pendingByDraftId);
|
const pendingByDraftId = useCreateFlowStore((state) => state.pendingByDraftId);
|
||||||
const consumedOpenIntentsRef = useRef(new Set<string>());
|
const consumedOpenIntentsRef = useRef(new Set<string>());
|
||||||
const pendingCloseTabIdsRef = useRef(new Set<string>());
|
const { closingTabIds, closeTab } = useCloseTabs();
|
||||||
const [resolvedOpenIntentKey, setResolvedOpenIntentKey] = useState<string | null>(null);
|
const [resolvedOpenIntentKey, setResolvedOpenIntentKey] = useState<string | null>(null);
|
||||||
const currentOpenIntentKey = useMemo(
|
const currentOpenIntentKey = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -971,10 +1006,6 @@ function WorkspaceScreenContent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentOpenIntentPinnedAgentId) {
|
|
||||||
pinWorkspaceAgent(persistenceKey, currentOpenIntentPinnedAgentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const intentKey = currentOpenIntentKey;
|
const intentKey = currentOpenIntentKey;
|
||||||
if (consumedOpenIntentsRef.current.has(intentKey)) {
|
if (consumedOpenIntentsRef.current.has(intentKey)) {
|
||||||
if (resolvedOpenIntentKey !== intentKey) {
|
if (resolvedOpenIntentKey !== intentKey) {
|
||||||
@@ -984,12 +1015,17 @@ function WorkspaceScreenContent({
|
|||||||
}
|
}
|
||||||
consumedOpenIntentsRef.current.add(intentKey);
|
consumedOpenIntentsRef.current.add(intentKey);
|
||||||
|
|
||||||
|
if (currentOpenIntentPinnedAgentId) {
|
||||||
|
pinWorkspaceAgent(persistenceKey, currentOpenIntentPinnedAgentId);
|
||||||
|
}
|
||||||
|
|
||||||
if (openIntent.kind === "draft") {
|
if (openIntent.kind === "draft") {
|
||||||
const draftId = openIntent.draftId.trim();
|
const draftId = openIntent.draftId.trim();
|
||||||
const tabId = openWorkspaceDraftTab({
|
const tabId = openWorkspaceDraftTab({
|
||||||
...(draftId === "new" ? {} : { draftId }),
|
...(draftId === "new" ? {} : { draftId }),
|
||||||
focus: true,
|
focus: true,
|
||||||
});
|
});
|
||||||
|
onOpenIntentConsumed?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1003,10 +1039,12 @@ function WorkspaceScreenContent({
|
|||||||
if (tabId) {
|
if (tabId) {
|
||||||
focusWorkspaceTab(persistenceKey, tabId);
|
focusWorkspaceTab(persistenceKey, tabId);
|
||||||
}
|
}
|
||||||
|
onOpenIntentConsumed?.();
|
||||||
}, [
|
}, [
|
||||||
currentOpenIntentPinnedAgentId,
|
currentOpenIntentPinnedAgentId,
|
||||||
currentOpenIntentKey,
|
currentOpenIntentKey,
|
||||||
focusWorkspaceTab,
|
focusWorkspaceTab,
|
||||||
|
onOpenIntentConsumed,
|
||||||
openIntent,
|
openIntent,
|
||||||
openWorkspaceDraftTab,
|
openWorkspaceDraftTab,
|
||||||
openWorkspaceTab,
|
openWorkspaceTab,
|
||||||
@@ -1302,81 +1340,56 @@ function WorkspaceScreenContent({
|
|||||||
[focusWorkspacePane, openWorkspaceDraftTab, persistenceKey, splitWorkspacePaneEmpty]
|
[focusWorkspacePane, openWorkspaceDraftTab, persistenceKey, splitWorkspacePaneEmpty]
|
||||||
);
|
);
|
||||||
|
|
||||||
const runCloseFlowForTab = useCallback(
|
const killTerminalAsync = killTerminalMutation.mutateAsync;
|
||||||
async (input: { tabId: string; action: () => Promise<void> }): Promise<void> => {
|
|
||||||
const normalizedTabId = input.tabId.trim();
|
|
||||||
if (!normalizedTabId || pendingCloseTabIdsRef.current.has(normalizedTabId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingCloseTabIdsRef.current.add(normalizedTabId);
|
|
||||||
try {
|
|
||||||
await input.action();
|
|
||||||
} finally {
|
|
||||||
pendingCloseTabIdsRef.current.delete(normalizedTabId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCloseTerminalTab = useCallback(
|
const handleCloseTerminalTab = useCallback(
|
||||||
async (input: { tabId: string; terminalId: string }) => {
|
async (input: { tabId: string; terminalId: string }) => {
|
||||||
const { tabId, terminalId } = input;
|
const { tabId, terminalId } = input;
|
||||||
await runCloseFlowForTab({
|
await closeTab(tabId, async () => {
|
||||||
tabId,
|
const confirmed = await confirmDialog({
|
||||||
action: async () => {
|
title: "Close terminal?",
|
||||||
if (
|
message: "Any running process in this terminal will be stopped immediately.",
|
||||||
killTerminalMutation.isPending &&
|
confirmLabel: "Close",
|
||||||
killTerminalMutation.variables === terminalId
|
cancelLabel: "Cancel",
|
||||||
) {
|
destructive: true,
|
||||||
return;
|
});
|
||||||
}
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const confirmed = await confirmDialog({
|
await killTerminalAsync(terminalId);
|
||||||
title: "Close terminal?",
|
setHoveredTabKey((current) => (current === tabId ? null : current));
|
||||||
message: "Any running process in this terminal will be stopped immediately.",
|
setHoveredCloseTabKey((current) => (current === tabId ? null : current));
|
||||||
confirmLabel: "Close",
|
|
||||||
cancelLabel: "Cancel",
|
|
||||||
destructive: true,
|
|
||||||
});
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await killTerminalMutation.mutateAsync(terminalId);
|
queryClient.setQueryData<ListTerminalsPayload>(
|
||||||
setHoveredTabKey((current) => (current === tabId ? null : current));
|
terminalsQueryKey,
|
||||||
setHoveredCloseTabKey((current) => (current === tabId ? null : current));
|
(current) => {
|
||||||
|
if (!current) {
|
||||||
queryClient.setQueryData<ListTerminalsPayload>(
|
return current;
|
||||||
terminalsQueryKey,
|
|
||||||
(current) => {
|
|
||||||
if (!current) {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...current,
|
|
||||||
terminals: current.terminals.filter(
|
|
||||||
(terminal) => terminal.id !== terminalId
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
);
|
return {
|
||||||
|
...current,
|
||||||
if (persistenceKey) {
|
terminals: current.terminals.filter(
|
||||||
closeWorkspaceTabWithCleanup({
|
(terminal) => terminal.id !== terminalId
|
||||||
tabId,
|
),
|
||||||
target: { kind: "terminal", terminalId },
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
);
|
||||||
|
|
||||||
|
if (persistenceKey) {
|
||||||
|
closeWorkspaceTabWithCleanup({
|
||||||
|
tabId,
|
||||||
|
target: { kind: "terminal", terminalId },
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
closeTab,
|
||||||
closeWorkspaceTabWithCleanup,
|
closeWorkspaceTabWithCleanup,
|
||||||
killTerminalMutation,
|
killTerminalAsync,
|
||||||
persistenceKey,
|
persistenceKey,
|
||||||
queryClient,
|
queryClient,
|
||||||
runCloseFlowForTab,
|
|
||||||
terminalsQueryKey,
|
terminalsQueryKey,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -1384,46 +1397,39 @@ function WorkspaceScreenContent({
|
|||||||
const handleCloseAgentTab = useCallback(
|
const handleCloseAgentTab = useCallback(
|
||||||
async (input: { tabId: string; agentId: string }) => {
|
async (input: { tabId: string; agentId: string }) => {
|
||||||
const { tabId, agentId } = input;
|
const { tabId, agentId } = input;
|
||||||
await runCloseFlowForTab({
|
await closeTab(tabId, async () => {
|
||||||
tabId,
|
if (!normalizedServerId) {
|
||||||
action: async () => {
|
return;
|
||||||
if (
|
}
|
||||||
!normalizedServerId ||
|
|
||||||
isArchivingAgent({ serverId: normalizedServerId, agentId })
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmed = await confirmDialog({
|
const confirmed = await confirmDialog({
|
||||||
title: "Archive agent?",
|
title: "Archive agent?",
|
||||||
message: "This closes the tab and archives the agent.",
|
message: "This closes the tab and archives the agent.",
|
||||||
confirmLabel: "Archive",
|
confirmLabel: "Archive",
|
||||||
cancelLabel: "Cancel",
|
cancelLabel: "Cancel",
|
||||||
destructive: true,
|
destructive: true,
|
||||||
|
});
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await archiveAgent({ serverId: normalizedServerId, agentId });
|
||||||
|
setHoveredTabKey((current) => (current === tabId ? null : current));
|
||||||
|
setHoveredCloseTabKey((current) => (current === tabId ? null : current));
|
||||||
|
if (persistenceKey) {
|
||||||
|
closeWorkspaceTabWithCleanup({
|
||||||
|
tabId,
|
||||||
|
target: { kind: "agent", agentId },
|
||||||
});
|
});
|
||||||
if (!confirmed) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await archiveAgent({ serverId: normalizedServerId, agentId });
|
|
||||||
setHoveredTabKey((current) => (current === tabId ? null : current));
|
|
||||||
setHoveredCloseTabKey((current) => (current === tabId ? null : current));
|
|
||||||
if (persistenceKey) {
|
|
||||||
closeWorkspaceTabWithCleanup({
|
|
||||||
tabId,
|
|
||||||
target: { kind: "agent", agentId },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
archiveAgent,
|
archiveAgent,
|
||||||
|
closeTab,
|
||||||
closeWorkspaceTabWithCleanup,
|
closeWorkspaceTabWithCleanup,
|
||||||
isArchivingAgent,
|
|
||||||
normalizedServerId,
|
normalizedServerId,
|
||||||
persistenceKey,
|
persistenceKey,
|
||||||
runCloseFlowForTab,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1457,6 +1463,17 @@ function WorkspaceScreenContent({
|
|||||||
[allTabDescriptorsById, handleCloseAgentTab, handleCloseDraftOrFileTab, handleCloseTerminalTab]
|
[allTabDescriptorsById, handleCloseAgentTab, handleCloseDraftOrFileTab, handleCloseTerminalTab]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const prevCloseTabDeps = useRef({ allTabDescriptorsById, handleCloseAgentTab, handleCloseDraftOrFileTab, handleCloseTerminalTab });
|
||||||
|
useEffect(() => {
|
||||||
|
const prev = prevCloseTabDeps.current;
|
||||||
|
const changed: string[] = [];
|
||||||
|
if (prev.allTabDescriptorsById !== allTabDescriptorsById) changed.push('allTabDescriptorsById');
|
||||||
|
if (prev.handleCloseAgentTab !== handleCloseAgentTab) changed.push('handleCloseAgentTab');
|
||||||
|
if (prev.handleCloseDraftOrFileTab !== handleCloseDraftOrFileTab) changed.push('handleCloseDraftOrFileTab');
|
||||||
|
if (prev.handleCloseTerminalTab !== handleCloseTerminalTab) changed.push('handleCloseTerminalTab');
|
||||||
|
if (changed.length > 0) console.log('[handleCloseTabById] deps changed:', changed.join(', '));
|
||||||
|
prevCloseTabDeps.current = { allTabDescriptorsById, handleCloseAgentTab, handleCloseDraftOrFileTab, handleCloseTerminalTab };
|
||||||
|
});
|
||||||
const handleCopyAgentId = useCallback(
|
const handleCopyAgentId = useCallback(
|
||||||
async (agentId: string) => {
|
async (agentId: string) => {
|
||||||
if (!agentId) return;
|
if (!agentId) return;
|
||||||
@@ -1549,49 +1566,55 @@ function WorkspaceScreenContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const { tabId, terminalId } of groups.terminalTabs) {
|
for (const { tabId, terminalId } of groups.terminalTabs) {
|
||||||
try {
|
await closeTab(tabId, async () => {
|
||||||
await killTerminalMutation.mutateAsync(terminalId);
|
try {
|
||||||
queryClient.setQueryData<ListTerminalsPayload>(terminalsQueryKey, (current) => {
|
await killTerminalAsync(terminalId);
|
||||||
if (!current) {
|
queryClient.setQueryData<ListTerminalsPayload>(terminalsQueryKey, (current) => {
|
||||||
return current;
|
if (!current) {
|
||||||
}
|
return current;
|
||||||
return {
|
}
|
||||||
...current,
|
return {
|
||||||
terminals: current.terminals.filter((terminal) => terminal.id !== terminalId),
|
...current,
|
||||||
};
|
terminals: current.terminals.filter((terminal) => terminal.id !== terminalId),
|
||||||
});
|
};
|
||||||
if (persistenceKey) {
|
|
||||||
closeWorkspaceTabWithCleanup({
|
|
||||||
tabId,
|
|
||||||
target: { kind: "terminal", terminalId },
|
|
||||||
});
|
});
|
||||||
|
if (persistenceKey) {
|
||||||
|
closeWorkspaceTabWithCleanup({
|
||||||
|
tabId,
|
||||||
|
target: { kind: "terminal", terminalId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[WorkspaceScreen] Failed to close terminal tab ${logLabel}`, { terminalId, error });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
});
|
||||||
console.warn(`[WorkspaceScreen] Failed to close terminal tab ${logLabel}`, { terminalId, error });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const { tabId, agentId } of groups.agentTabs) {
|
for (const { tabId, agentId } of groups.agentTabs) {
|
||||||
if (!normalizedServerId) {
|
if (!normalizedServerId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
await closeTab(tabId, async () => {
|
||||||
await archiveAgent({ serverId: normalizedServerId, agentId });
|
try {
|
||||||
if (persistenceKey) {
|
await archiveAgent({ serverId: normalizedServerId, agentId });
|
||||||
closeWorkspaceTabWithCleanup({
|
if (persistenceKey) {
|
||||||
tabId,
|
closeWorkspaceTabWithCleanup({
|
||||||
target: { kind: "agent", agentId },
|
tabId,
|
||||||
});
|
target: { kind: "agent", agentId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`[WorkspaceScreen] Failed to archive agent tab ${logLabel}`, { agentId, error });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
});
|
||||||
console.warn(`[WorkspaceScreen] Failed to archive agent tab ${logLabel}`, { agentId, error });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const { tabId } of groups.otherTabs) {
|
for (const { tabId } of groups.otherTabs) {
|
||||||
if (persistenceKey) {
|
await closeTab(tabId, async () => {
|
||||||
closeWorkspaceTabWithCleanup({ tabId });
|
if (persistenceKey) {
|
||||||
}
|
closeWorkspaceTabWithCleanup({ tabId });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const closedKeys = new Set(tabsToClose.map((tab) => tab.key));
|
const closedKeys = new Set(tabsToClose.map((tab) => tab.key));
|
||||||
@@ -1600,8 +1623,9 @@ function WorkspaceScreenContent({
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
archiveAgent,
|
archiveAgent,
|
||||||
|
closeTab,
|
||||||
closeWorkspaceTabWithCleanup,
|
closeWorkspaceTabWithCleanup,
|
||||||
killTerminalMutation,
|
killTerminalAsync,
|
||||||
normalizedServerId,
|
normalizedServerId,
|
||||||
persistenceKey,
|
persistenceKey,
|
||||||
queryClient,
|
queryClient,
|
||||||
@@ -1898,6 +1922,22 @@ function WorkspaceScreenContent({
|
|||||||
retargetWorkspaceTab,
|
retargetWorkspaceTab,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
const prevBuildDeps = useRef({ handleCloseTabById, handleOpenFileFromChat, focusWorkspacePane, navigateToTabId, normalizedServerId, normalizedWorkspaceId, openWorkspaceTab, persistenceKey, retargetWorkspaceTab });
|
||||||
|
useEffect(() => {
|
||||||
|
const prev = prevBuildDeps.current;
|
||||||
|
const changed: string[] = [];
|
||||||
|
if (prev.handleCloseTabById !== handleCloseTabById) changed.push('handleCloseTabById');
|
||||||
|
if (prev.handleOpenFileFromChat !== handleOpenFileFromChat) changed.push('handleOpenFileFromChat');
|
||||||
|
if (prev.focusWorkspacePane !== focusWorkspacePane) changed.push('focusWorkspacePane');
|
||||||
|
if (prev.navigateToTabId !== navigateToTabId) changed.push('navigateToTabId');
|
||||||
|
if (prev.normalizedServerId !== normalizedServerId) changed.push('normalizedServerId');
|
||||||
|
if (prev.normalizedWorkspaceId !== normalizedWorkspaceId) changed.push('normalizedWorkspaceId');
|
||||||
|
if (prev.openWorkspaceTab !== openWorkspaceTab) changed.push('openWorkspaceTab');
|
||||||
|
if (prev.persistenceKey !== persistenceKey) changed.push('persistenceKey');
|
||||||
|
if (prev.retargetWorkspaceTab !== retargetWorkspaceTab) changed.push('retargetWorkspaceTab');
|
||||||
|
if (changed.length > 0) console.log('[buildPaneContentModel] deps changed:', changed.join(', '));
|
||||||
|
prevBuildDeps.current = { handleCloseTabById, handleOpenFileFromChat, focusWorkspacePane, navigateToTabId, normalizedServerId, normalizedWorkspaceId, openWorkspaceTab, persistenceKey, retargetWorkspaceTab };
|
||||||
|
});
|
||||||
const activePaneContent = useMemo(
|
const activePaneContent = useMemo(
|
||||||
() =>
|
() =>
|
||||||
activeTabDescriptor
|
activeTabDescriptor
|
||||||
@@ -1910,6 +1950,14 @@ function WorkspaceScreenContent({
|
|||||||
: null,
|
: null,
|
||||||
[activeTabDescriptor, buildPaneContentModel, focusedPaneTabState.pane?.id]
|
[activeTabDescriptor, buildPaneContentModel, focusedPaneTabState.pane?.id]
|
||||||
);
|
);
|
||||||
|
const prevActivePaneDeps = useRef({ activeTabDescriptor, buildPaneContentModel, paneId: focusedPaneTabState.pane?.id });
|
||||||
|
useEffect(() => {
|
||||||
|
const prev = prevActivePaneDeps.current;
|
||||||
|
if (prev.activeTabDescriptor !== activeTabDescriptor) console.log('[activePaneContent] activeTabDescriptor changed');
|
||||||
|
if (prev.buildPaneContentModel !== buildPaneContentModel) console.log('[activePaneContent] buildPaneContentModel changed');
|
||||||
|
if (prev.paneId !== focusedPaneTabState.pane?.id) console.log('[activePaneContent] paneId changed');
|
||||||
|
prevActivePaneDeps.current = { activeTabDescriptor, buildPaneContentModel, paneId: focusedPaneTabState.pane?.id };
|
||||||
|
});
|
||||||
const content = shouldRenderMissingWorkspaceDescriptor({
|
const content = shouldRenderMissingWorkspaceDescriptor({
|
||||||
workspace: workspaceDescriptor,
|
workspace: workspaceDescriptor,
|
||||||
hasHydratedWorkspaces,
|
hasHydratedWorkspaces,
|
||||||
@@ -2176,9 +2224,7 @@ function WorkspaceScreenContent({
|
|||||||
hoveredCloseTabKey={hoveredCloseTabKey}
|
hoveredCloseTabKey={hoveredCloseTabKey}
|
||||||
setHoveredTabKey={setHoveredTabKey}
|
setHoveredTabKey={setHoveredTabKey}
|
||||||
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
setHoveredCloseTabKey={setHoveredCloseTabKey}
|
||||||
isArchivingAgent={isArchivingAgent}
|
closingTabIds={closingTabIds}
|
||||||
killTerminalPending={killTerminalMutation.isPending}
|
|
||||||
killTerminalId={killTerminalMutation.variables ?? null}
|
|
||||||
onNavigateTab={navigateToTabId}
|
onNavigateTab={navigateToTabId}
|
||||||
onCloseTab={handleCloseTabById}
|
onCloseTab={handleCloseTabById}
|
||||||
onCopyResumeCommand={handleCopyResumeCommand}
|
onCopyResumeCommand={handleCopyResumeCommand}
|
||||||
|
|||||||
@@ -995,6 +995,10 @@ export function focusTabInLayout(input: FocusTabInLayoutInput): WorkspaceLayout
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pane.focusedTabId === input.tabId && layout.focusedPaneId === pane.id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
root: focusTabInPane(layout.root, pane.id, input.tabId),
|
root: focusTabInPane(layout.root, pane.id, input.tabId),
|
||||||
focusedPaneId: pane.id,
|
focusedPaneId: pane.id,
|
||||||
@@ -1157,6 +1161,9 @@ export function focusPaneInLayout(input: FocusPaneInLayoutInput): WorkspaceLayou
|
|||||||
if (!findPaneById(input.layout.root, input.paneId)) {
|
if (!findPaneById(input.layout.root, input.paneId)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (input.layout.focusedPaneId === input.paneId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
root: input.layout.root,
|
root: input.layout.root,
|
||||||
focusedPaneId: input.paneId,
|
focusedPaneId: input.paneId,
|
||||||
|
|||||||
Reference in New Issue
Block a user