feat: improve terminal scrollbar and pane focus handling

This commit is contained in:
Mohamed Boudra
2026-03-18 20:39:16 +07:00
parent ed9a15c0bd
commit 7e0a220fde
9 changed files with 503 additions and 129 deletions
@@ -57,7 +57,7 @@ type WorkspaceDesktopTabsRowProps = {
onSelectNewTabOption: (selection: NewTabSelection) => void;
newTabAgentOptionId: NewTabOptionId;
onReorderTabs: (nextTabs: WorkspaceTabDescriptor[]) => void;
onNewTerminalTab: () => void;
onNewTerminalTab: (input: { paneId?: string }) => void;
onSplitRight: () => void;
onSplitDown: () => void;
externalDndContext?: boolean;
@@ -346,7 +346,7 @@ export function WorkspaceDesktopTabsRow({
data={tabs}
keyExtractor={(tab) => `${tab.tab.key}:${tab.tab.kind}`}
useDragHandle
disabled={tabs.length < 2}
disabled={!externalDndContext && tabs.length < 2}
onDragEnd={(nextTabs) => onReorderTabs(nextTabs.map((tab) => tab.tab))}
externalDndContext={externalDndContext}
activeId={activeDragTabId}
@@ -428,7 +428,7 @@ export function WorkspaceDesktopTabsRow({
</Tooltip>
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger
onPress={onNewTerminalTab}
onPress={() => onNewTerminalTab({ paneId })}
accessibilityRole="button"
accessibilityLabel="New terminal tab"
style={({ hovered, pressed }) => [
@@ -554,13 +554,13 @@ function WorkspaceScreenContent({
});
const terminals = terminalsQuery.data?.terminals ?? [];
const createTerminalMutation = useMutation({
mutationFn: async () => {
mutationFn: async (input?: { paneId?: string }) => {
if (!client) {
throw new Error("Host is not connected");
}
return await client.createTerminal(normalizedWorkspaceId);
},
onSuccess: (payload) => {
onSuccess: (payload, input) => {
const createdTerminal = payload.terminal;
if (createdTerminal) {
queryClient.setQueryData<ListTerminalsPayload>(
@@ -588,6 +588,9 @@ function WorkspaceScreenContent({
if (!workspaceKey) {
return;
}
if (input?.paneId) {
focusWorkspacePane(workspaceKey, input.paneId);
}
const tabId = useWorkspaceLayoutStore
.getState()
.openTab(workspaceKey, { kind: "terminal", terminalId: createdTerminal.id });
@@ -1133,14 +1136,14 @@ function WorkspaceScreenContent({
openWorkspaceDraftTab();
}, [openWorkspaceDraftTab]);
const handleCreateTerminal = useCallback(() => {
const handleCreateTerminal = useCallback((input?: { paneId?: string }) => {
if (createTerminalMutation.isPending) {
return;
}
if (!normalizedWorkspaceId.startsWith("/")) {
return;
}
createTerminalMutation.mutate();
createTerminalMutation.mutate(input);
}, [createTerminalMutation, normalizedWorkspaceId]);
const handleSelectSwitcherTab = useCallback(
@@ -1162,6 +1165,23 @@ function WorkspaceScreenContent({
[focusWorkspacePane, handleCreateDraftTab, persistenceKey]
);
const handleCreateDraftSplit = useCallback(
(input: { targetPaneId: string; position: "left" | "right" | "top" | "bottom" }) => {
if (!persistenceKey) {
return;
}
const paneId = splitWorkspacePaneEmpty(persistenceKey, input);
if (!paneId) {
return;
}
focusWorkspacePane(persistenceKey, paneId);
openWorkspaceDraftTab();
},
[focusWorkspacePane, openWorkspaceDraftTab, persistenceKey, splitWorkspacePaneEmpty]
);
const runCloseFlowForTab = useCallback(
async (input: { tabId: string; action: () => Promise<void> }): Promise<void> => {
const normalizedTabId = input.tabId.trim();
@@ -1569,26 +1589,18 @@ function WorkspaceScreenContent({
}
if (action.id === "workspace.pane.split.right") {
const activePaneTabId = focusedPaneTabState.activeTabId;
if (activePaneTabId) {
splitWorkspacePane(persistenceKey, {
tabId: activePaneTabId,
targetPaneId: focusedPane.id,
position: "right",
});
}
handleCreateDraftSplit({
targetPaneId: focusedPane.id,
position: "right",
});
return true;
}
if (action.id === "workspace.pane.split.down") {
const activePaneTabId = focusedPaneTabState.activeTabId;
if (activePaneTabId) {
splitWorkspacePane(persistenceKey, {
tabId: activePaneTabId,
targetPaneId: focusedPane.id,
position: "bottom",
});
}
handleCreateDraftSplit({
targetPaneId: focusedPane.id,
position: "bottom",
});
return true;
}
@@ -1647,9 +1659,9 @@ function WorkspaceScreenContent({
[
closeWorkspaceTab,
focusWorkspacePane,
handleCreateDraftSplit,
moveWorkspaceTabToPane,
persistenceKey,
splitWorkspacePane,
focusedPaneTabState.activeTabId,
focusedPaneTabState.pane,
workspaceLayout,
@@ -2023,7 +2035,7 @@ function WorkspaceScreenContent({
splitWorkspacePane(persistenceKey, input);
}}
onSplitPaneEmpty={(input) => {
splitWorkspacePaneEmpty(persistenceKey, input);
handleCreateDraftSplit(input);
}}
onMoveTabToPane={(tabId, toPaneId) => {
moveWorkspaceTabToPane(persistenceKey, tabId, toPaneId);