mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(site/src/pages/AgentsPage): prevent planning pill overlap
This commit is contained in:
@@ -842,6 +842,76 @@ export const PlanningIndicator: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
const narrowPlanningContextUsage: AgentContextUsage = {
|
||||
usedTokens: 100_000,
|
||||
contextLimitTokens: 200_000,
|
||||
};
|
||||
|
||||
const narrowPlanningModelOptions = [
|
||||
{
|
||||
id: "long-model-name",
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-5-long-name",
|
||||
displayName: "Claude Sonnet 4.5 Extended Thinking",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const PlanningIndicatorNarrow: Story = {
|
||||
args: {
|
||||
planModeEnabled: true,
|
||||
onPlanModeToggle: fn(),
|
||||
contextUsage: narrowPlanningContextUsage,
|
||||
selectedModel: narrowPlanningModelOptions[0].id,
|
||||
modelOptions: [...narrowPlanningModelOptions],
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ width: 360 }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const composer = await canvas.findByTestId("chat-composer");
|
||||
const sendButton = canvas.getByRole("button", { name: "Send" });
|
||||
const contextUsageButton = canvas.getByRole("button", {
|
||||
name: /Context usage/,
|
||||
});
|
||||
const planningBadge = canvasElement.querySelector<HTMLElement>(
|
||||
"[data-testid='planning-badge']",
|
||||
);
|
||||
const isVisible = (element: HTMLElement) => {
|
||||
const style = getComputedStyle(element);
|
||||
const rect = element.getBoundingClientRect();
|
||||
return (
|
||||
style.display !== "none" &&
|
||||
style.visibility !== "hidden" &&
|
||||
rect.width > 0 &&
|
||||
rect.height > 0
|
||||
);
|
||||
};
|
||||
|
||||
await waitFor(() => {
|
||||
const composerRect = composer.getBoundingClientRect();
|
||||
const sendButtonRect = sendButton.getBoundingClientRect();
|
||||
const contextUsageRect = contextUsageButton.getBoundingClientRect();
|
||||
|
||||
expect(contextUsageRect.left).toBeGreaterThanOrEqual(composerRect.left);
|
||||
expect(sendButtonRect.right).toBeLessThanOrEqual(composerRect.right);
|
||||
|
||||
if (planningBadge && isVisible(planningBadge)) {
|
||||
expect(planningBadge.getBoundingClientRect().right).toBeLessThanOrEqual(
|
||||
contextUsageRect.left + 1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
expect(canvas.getByRole("button", { name: "1 more item" })).toBeVisible();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const DisablePlanModeFromBadge: Story = {
|
||||
args: {
|
||||
planModeEnabled: true,
|
||||
|
||||
@@ -196,7 +196,8 @@ export interface AttachedWorkspaceInfo {
|
||||
type ToolBadgeData =
|
||||
| { kind: "workspace"; name: string }
|
||||
| ({ kind: "attached-workspace" } & AttachedWorkspaceInfo)
|
||||
| { kind: "mcp"; server: TypesGen.MCPServerConfig };
|
||||
| { kind: "mcp"; server: TypesGen.MCPServerConfig }
|
||||
| { kind: "planning" };
|
||||
|
||||
// Small `X` button rendered inside pill-style badges (attached
|
||||
// workspace, MCP server, planning indicator) to dismiss or disable
|
||||
@@ -224,13 +225,38 @@ const ToolBadge: FC<{
|
||||
badge: ToolBadgeData;
|
||||
onRemoveWorkspace?: () => void;
|
||||
onRemoveMcp?: (serverId: string) => void;
|
||||
onRemovePlanning?: () => void;
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
}> = ({ badge, onRemoveWorkspace, onRemoveMcp, className }) => {
|
||||
}> = ({
|
||||
badge,
|
||||
onRemoveWorkspace,
|
||||
onRemoveMcp,
|
||||
onRemovePlanning,
|
||||
isDisabled,
|
||||
className,
|
||||
}) => {
|
||||
const badgeCls = cn(
|
||||
"inline-flex shrink-0 items-center gap-1 rounded-full bg-surface-secondary px-2 py-0.5 text-xs font-medium text-content-secondary",
|
||||
className,
|
||||
);
|
||||
|
||||
if (badge.kind === "planning") {
|
||||
return (
|
||||
<span data-testid="planning-badge" className={badgeCls}>
|
||||
<PencilIcon className="size-3" />
|
||||
Planning
|
||||
{onRemovePlanning && (
|
||||
<BadgeDismissButton
|
||||
onClick={onRemovePlanning}
|
||||
ariaLabel="Disable plan mode"
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (badge.kind === "attached-workspace") {
|
||||
return (
|
||||
<Tooltip>
|
||||
@@ -525,6 +551,9 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
|
||||
// Ordered list of active tool badge data so we can determine
|
||||
// which ones ended up in the overflow popover.
|
||||
const allBadges: ToolBadgeData[] = [];
|
||||
if (planModeEnabled) {
|
||||
allBadges.push({ kind: "planning" });
|
||||
}
|
||||
// When workspace data is available, WorkspacePill handles
|
||||
// the display (including app dropdown). Otherwise fall back
|
||||
// to the simple attached-workspace ToolBadge.
|
||||
@@ -1339,24 +1368,12 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
|
||||
disabled={isDisabled}
|
||||
placeholder={modelSelectorPlaceholder}
|
||||
formatProviderLabel={formatProviderLabel}
|
||||
className="md:shrink"
|
||||
dropdownSide="top"
|
||||
dropdownAlign="center"
|
||||
enableMobileFullWidthDropdown
|
||||
/>
|
||||
)}
|
||||
{planModeEnabled && (
|
||||
<span className="hidden shrink-0 items-center gap-1 rounded-full bg-surface-secondary px-2 py-0.5 text-xs font-medium text-content-secondary sm:inline-flex">
|
||||
<PencilIcon className="size-3" />
|
||||
Planning
|
||||
{onPlanModeToggle && (
|
||||
<BadgeDismissButton
|
||||
onClick={handleDisablePlanMode}
|
||||
ariaLabel="Disable plan mode"
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
)}{" "}
|
||||
{/* Badge row; all badges and the pill always
|
||||
* render so the DOM structure never changes.
|
||||
* Overflow badges use invisible + order-1 to
|
||||
@@ -1387,6 +1404,10 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
|
||||
badge={badge}
|
||||
onRemoveWorkspace={removeWorkspaceHandler}
|
||||
onRemoveMcp={handleRemoveMcp}
|
||||
onRemovePlanning={
|
||||
onPlanModeToggle ? handleDisablePlanMode : undefined
|
||||
}
|
||||
isDisabled={isDisabled}
|
||||
className={isOverflow ? "invisible order-1" : undefined}
|
||||
/>
|
||||
);
|
||||
@@ -1427,6 +1448,10 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
|
||||
badge={badge}
|
||||
onRemoveWorkspace={removeWorkspaceHandler}
|
||||
onRemoveMcp={handleRemoveMcp}
|
||||
onRemovePlanning={
|
||||
onPlanModeToggle ? handleDisablePlanMode : undefined
|
||||
}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
))}
|
||||
</PopoverContent>
|
||||
|
||||
Reference in New Issue
Block a user