fix(site/src/pages/AgentsPage): keep terminal tabs rightmost (#26166)

This updates the agents right-panel tab model so Desktop is included in
the normal tab list instead of being rendered separately by
`SidebarTabView`. Desktop now appears before terminal tabs, which keeps
the built-in terminal and any newly opened terminal tabs at the right
edge. `SidebarTabView` no longer needs a Desktop-specific prop or render
path because it simply renders the ordered tabs it receives.
This commit is contained in:
Ethan
2026-06-09 15:06:48 +00:00
committed by GitHub
parent 1ed5c6f9df
commit 6d5b2921f7
3 changed files with 16 additions and 46 deletions
+15 -12
View File
@@ -41,6 +41,7 @@ import { SidebarTabView } from "./components/ChatsSidebar/tabs/SidebarTabView";
import { ChatTopBar } from "./components/ChatTopBar";
import { GitPanel } from "./components/GitPanel/GitPanel";
import { DebugPanel } from "./components/RightPanel/DebugPanel/DebugPanel";
import { DesktopPanel } from "./components/RightPanel/DesktopPanel";
import { RightPanel } from "./components/RightPanel/RightPanel";
import { getWorkspaceStatus, StatusIcon } from "./components/StatusIcon";
import { TerminalPanel } from "./components/TerminalPanel";
@@ -413,10 +414,9 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
};
})();
// Desktop is only available when the workspace + agent are ready;
// `SidebarTabView` gates the desktop tab/panel on the same condition,
// so resolve tab selection against the same availability to avoid
// picking "desktop" when no desktop panel is rendered.
// Desktop is only available when the workspace and agent are ready;
// include it in the tab list on that same condition to avoid selecting
// "desktop" when no desktop panel is rendered.
const availableDesktopChatId =
workspace && workspaceAgent ? desktopChatId : undefined;
@@ -425,10 +425,11 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
// Single source of truth for available tabs and their order. The list
// of tab IDs used by `getEffectiveTabId` is derived from this so a
// new tab can never be added to one without the other going out of
// sync.
// sync. Desktop is ordered before terminals so terminals are rightmost.
const builtInSidebarTabConfigs = [
{ id: "git", label: "Git" },
...(debugLoggingEnabled ? [{ id: "debug", label: "Debug" }] : []),
...(availableDesktopChatId ? [{ id: "desktop", label: "Desktop" }] : []),
...(workspace && workspaceAgent && !defaultTerminalHidden
? [{ id: "terminal", label: "Terminal" }]
: []),
@@ -512,6 +513,13 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
chatInputRef={editing.chatInputRef}
/>
);
case "desktop":
return availableDesktopChatId ? (
<DesktopPanel
chatId={availableDesktopChatId}
isVisible={effectiveSidebarTabId === "desktop"}
/>
) : null;
case "terminal":
return workspace && workspaceAgent ? (
<TerminalPanel
@@ -558,12 +566,8 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
setPendingTabId((currentTabId) =>
currentTabId === tabId ? null : currentTabId,
);
const visibleTabIds = [
...sidebarTabIds,
...(availableDesktopChatId ? ["desktop"] : []),
];
const remainingTabIds = visibleTabIds.filter((id) => id !== tabId);
const closedTabIndex = visibleTabIds.indexOf(tabId);
const remainingTabIds = sidebarTabIds.filter((id) => id !== tabId);
const closedTabIndex = sidebarTabIds.indexOf(tabId);
if (tabId === "terminal") {
setDefaultTerminalHiddenState(true);
@@ -816,7 +820,6 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
isSidebarCollapsed={isSidebarCollapsed}
onToggleSidebarCollapsed={onToggleSidebarCollapsed}
chatTitle={chatTitle}
desktopChatId={availableDesktopChatId}
/>
</RightPanel>
</div>
@@ -81,7 +81,6 @@ export const EmptyState: Story = {
export const DesktopHidden: Story = {
args: {
tabs: [],
desktopChatId: undefined,
},
};
@@ -18,7 +18,6 @@ import {
} from "react";
import { Button } from "#/components/Button/Button";
import { cn } from "#/utils/cn";
import { DesktopPanel } from "../../RightPanel/DesktopPanel";
/** A single tab definition for the sidebar panel. */
export interface SidebarTab {
@@ -48,8 +47,6 @@ interface SidebarTabViewProps {
chatTitle?: string;
/** Callback to close the panel (used on mobile). */
onClose?: () => void;
/** Desktop chat ID. Omitted if desktop is not available. */
desktopChatId?: string;
/**
* The resolved tab ID to render as active (computed by the parent
* with `getEffectiveTabId`). Keeping a single source of truth in the
@@ -159,7 +156,6 @@ export const SidebarTabView: FC<SidebarTabViewProps> = ({
onToggleSidebarCollapsed,
chatTitle,
onClose,
desktopChatId,
effectiveTabId,
onActiveTabChange,
addTabControl,
@@ -177,19 +173,8 @@ export const SidebarTabView: FC<SidebarTabViewProps> = ({
id: t.id,
content: t.content,
}));
if (desktopChatId) {
allPanels.push({
id: "desktop",
content: (
<DesktopPanel
chatId={desktopChatId}
isVisible={effectiveTabId === "desktop"}
/>
),
});
}
if (tabs.length === 0 && !desktopChatId) {
if (tabs.length === 0) {
return (
<div className="flex h-full min-w-0 flex-col overflow-hidden bg-surface-primary">
<div
@@ -337,23 +322,6 @@ export const SidebarTabView: FC<SidebarTabViewProps> = ({
</div>
);
})}
{desktopChatId && (
<Button
id={`${tabIdPrefix}-tab-desktop`}
role="tab"
aria-selected={effectiveTabId === "desktop"}
onClick={() => onActiveTabChange("desktop")}
variant="outline"
size="lg"
className={cn(
"shrink-0 h-6 min-w-0 gap-1.5 px-2 py-0 bg-surface-primary",
effectiveTabId === "desktop" &&
"bg-surface-quaternary/25 text-content-primary hover:bg-surface-quaternary/50",
)}
>
Desktop
</Button>
)}
{addTabControl}
</div>
{canScrollRight && (