mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user