Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions site/src/pages/AgentsPage/components/AgentChatInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1479,6 +1479,21 @@ export const OverflowBadges: Story = {
],
selectedWorkspaceId: "ws-1",
onWorkspaceChange: fn(),
attachedWorkspace: {
id: "ws-1",
name: "my-long-workspace-name",
route: "/@admin/my-long-workspace-name",
statusIcon: <MonitorDotIcon className="size-3" />,
statusLabel: "Workspace running",
},
workspace: {
...MockWorkspace,
id: "ws-1",
name: "my-long-workspace-name",
owner_name: "admin",
},
workspaceAgent: MockWorkspaceAgent,
chatId: "overflow-chat-id",
},
parameters: {
viewport: { defaultViewport: "mobile2" },
Expand Down Expand Up @@ -1546,12 +1561,19 @@ export const ContextNearLimit: Story = {
},
};

/** Long workspace name at iPhone SE width — verifies truncation. */
/** Long workspace name at iPhone SE width collapses into +N overflow. */
export const LongWorkspaceNameMobile: Story = {
args: {
...mcpDefaults,
mcpServers: [githubMCPConnected],
selectedMCPServerIds: [githubMCPConnected.id],
attachedWorkspace: {
id: MockWorkspace.id,
name: "my-super-extremely-long-workspace-name-that-overflows",
route: `/@${MockWorkspace.owner_name}/my-super-extremely-long-workspace-name-that-overflows`,
statusIcon: <MonitorDotIcon className="size-3" />,
statusLabel: "Workspace running",
},
workspace: {
...MockWorkspace,
name: "my-super-extremely-long-workspace-name-that-overflows",
Expand All @@ -1565,15 +1587,23 @@ export const LongWorkspaceNameMobile: Story = {
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// The workspace pill button should be present.
const pill = await canvas.findByRole("button", {
name: /workspace menu/,
// Too narrow for the pill's minimum width: it must collapse into
// the overflow popover instead of clipping to a tiny pill.
const overflowPill = await canvas.findByRole("button", {
name: /more item/,
});
await waitFor(() => {
expect(pill).toBeVisible();
expect(overflowPill).toBeVisible();
});
await userEvent.click(overflowPill);
const popover = await within(document.body).findByRole("dialog");
expect(
within(popover).getByText(
"my-super-extremely-long-workspace-name-that-overflows",
),
).toBeInTheDocument();
// The toolbar row should not cause horizontal overflow.
const toolbar = pill.closest(
const toolbar = overflowPill.closest(
".flex.items-center.justify-between",
) as HTMLElement;
if (toolbar?.parentElement) {
Expand Down
54 changes: 38 additions & 16 deletions site/src/pages/AgentsPage/components/AgentChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -585,16 +585,25 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
const shouldOverflowPlanningBadge =
planModeEnabled && contextUsage !== undefined;

// When workspace data is available the badge renders as the
// interactive WorkspacePill; its overflow-popover fallback uses the
// richer attachedWorkspace data when present.
let workspacePillBadge: ToolBadgeData | undefined;
if (workspace && workspaceAgent && chatId) {
workspacePillBadge = attachedWorkspace
? { kind: "attached-workspace", ...attachedWorkspace }
: { kind: "workspace", name: workspace.name };
}

// Ordered list of active tool badge data so we can determine
// which ones ended up in the overflow popover.
const allBadges: ToolBadgeData[] = [];
if (shouldOverflowPlanningBadge) {
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.
if (!(workspace && workspaceAgent && chatId) && attachedWorkspace) {
if (workspacePillBadge) {
allBadges.push(workspacePillBadge);
} else if (attachedWorkspace) {
allBadges.push({ kind: "attached-workspace", ...attachedWorkspace });
}
if (shouldShowSelectedWorkspaceBadge && selectedWorkspace) {
Expand Down Expand Up @@ -1447,24 +1456,37 @@ export const AgentChatInput: FC<AgentChatInputProps> = ({
* hide and reorder via CSS. The pill is invisible
* when there's no overflow but still occupies
* layout space, preventing measurement flicker. */}
{workspace && workspaceAgent && chatId && (
<span className="ml-1 sm:ml-0">
<WorkspacePill
workspace={workspace}
agent={workspaceAgent}
chatId={chatId}
sshCommand={sshCommand}
folder={folder}
onRemoveWorkspace={removeWorkspaceHandler}
/>
</span>
)}
<div
ref={badgeContainerRef}
className="flex min-w-0 items-center gap-1 overflow-hidden"
>
{allBadges.map((badge, i) => {
const isOverflow = overflowCount > 0 && i >= visibleCount;
if (
badge === workspacePillBadge &&
workspace &&
workspaceAgent &&
chatId
) {
return (
<span
key="workspace-pill"
className={cn(
"flex min-w-[calc(8ch_+_3.125rem)] text-xs",
isOverflow && "invisible order-1",
)}
>
<WorkspacePill
workspace={workspace}
agent={workspaceAgent}
chatId={chatId}
sshCommand={sshCommand}
folder={folder}
onRemoveWorkspace={removeWorkspaceHandler}
/>
</span>
);
}
return (
<ToolBadge
key={badge.kind === "mcp" ? badge.server.id : badge.kind}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
role="combobox"
type="button"
variant="subtle"
// min-w floor: ~8ch of the label plus 3.125rem of fixed chrome
// stay visible before truncation kicks in.
className={cn(
"h-7 min-w-0 shrink justify-start gap-1 rounded-full border-0 bg-surface-secondary px-2 py-0.5 text-xs font-medium shadow-none transition-colors hover:bg-surface-tertiary hover:text-content-primary focus:ring-0 focus-visible:ring-2 focus-visible:ring-content-link [&>svg]:!size-3.5 [&>svg]:p-0 [&>svg]:shrink-0 [&>svg]:transition [&>svg]:hover:text-content-primary [&>img]:!size-3 [&>img]:!p-0",
"h-7 min-w-[calc(8ch_+_3.125rem)] shrink justify-start gap-1 rounded-full border-0 bg-surface-secondary px-2 py-0.5 text-xs font-medium shadow-none transition-colors hover:bg-surface-tertiary hover:text-content-primary focus:ring-0 focus-visible:ring-2 focus-visible:ring-content-link [&>svg]:!size-3.5 [&>svg]:p-0 [&>svg]:shrink-0 [&>svg]:transition [&>svg]:hover:text-content-primary [&>img]:!size-3 [&>img]:!p-0",
className,
)}
onTouchStart={onTriggerTouchStart}
Expand Down Expand Up @@ -196,11 +198,9 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
contentClassName,
)}
onOpenAutoFocus={(event) => {
// On touch devices, auto-focusing the search input pops the
// software keyboard as soon as the picker opens, hiding the
// model list behind it. Only keep the WAI-ARIA combobox
// focus-into-input behavior for fine pointers (keyboard and
// mouse users on desktop).
// Touch devices auto-focus search on open, and the software
// keyboard hides the model list. Keep the WAI-ARIA combobox
// focus-into-input behavior for fine pointers only.
if (matchMedia("(pointer: coarse)").matches) {
event.preventDefault();
}
Expand Down
24 changes: 10 additions & 14 deletions site/src/pages/AgentsPage/components/WorkspacePill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ export const WorkspacePill: FC<WorkspacePillProps> = ({
}
}}
>
<span className="inline-flex min-w-0 items-center overflow-hidden rounded-full bg-surface-secondary text-xs font-medium text-content-secondary md:min-w-[2.75rem]">
{/* Floor of ~8ch of name + 3.125rem chrome (padding, status icon,
* gaps, chevron). Below the floor the overflow system moves the
* pill into the +N popover instead of shrinking it further. */}
<span className="inline-flex min-w-[calc(8ch_+_3.125rem)] items-center overflow-hidden rounded-full bg-surface-secondary text-xs font-medium text-content-secondary">
<Tooltip
open={tooltipOpen}
onOpenChange={(v) => setTooltipOpen(v && !open)}
Expand All @@ -136,29 +139,22 @@ export const WorkspacePill: FC<WorkspacePillProps> = ({
type="button"
aria-label={`${workspace.name} workspace menu`}
className={cn(
"inline-flex min-w-0 cursor-pointer items-center justify-center gap-1 rounded-full border-0 bg-transparent p-0 text-xs font-medium text-content-secondary transition-colors hover:bg-surface-tertiary hover:text-content-primary",
"size-7 md:size-auto md:max-w-[200px] md:justify-start md:px-2 md:py-0.5",
"inline-flex min-w-0 cursor-pointer items-center justify-start gap-1 rounded-full border-0 bg-transparent p-0 text-xs font-medium text-content-secondary transition-colors hover:bg-surface-tertiary hover:text-content-primary",
"h-7 w-full max-w-[200px] px-2 py-0.5",
)}
>
<StatusIcon
type={effectiveType}
className="size-icon-sm shrink-0 md:size-3"
/>
<span className="hidden min-w-0 truncate md:inline">
{workspace.name}
</span>
<StatusIcon type={effectiveType} className="size-3 shrink-0" />
<span className="min-w-0 truncate">{workspace.name}</span>
<ChevronDownIcon
className={cn(
"hidden size-3.5 shrink-0 transition-transform md:block",
"size-3.5 shrink-0 transition-transform",
open && "rotate-180",
)}
/>
</button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent className="hidden md:block">
{statusLabel}
</TooltipContent>
<TooltipContent>{statusLabel}</TooltipContent>
</Tooltip>
</span>

Expand Down
Loading