Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a027534
fix(site): keep at least 8 characters of the model name visible in th…
tracyjohnsonux Aug 24, 2026
9dbf76e
fix(site): floor the model selector trigger width instead of removing…
tracyjohnsonux Aug 24, 2026
85ca53f
Merge branch 'main' into tracyjohnsonux/model-selector-min-width
tracyjohnsonux Aug 25, 2026
fc59496
fix(site): give the workspace pill the same 8-character width floor
tracyjohnsonux Aug 25, 2026
33a9daf
fix(site): use valid calc syntax for the model selector width floor
tracyjohnsonux Aug 25, 2026
501fd98
fix(site): use valid calc syntax for the workspace pill width floor
tracyjohnsonux Aug 25, 2026
21646ec
fix(site): let the workspace pill wrapper shrink so the pill truncates
tracyjohnsonux Aug 25, 2026
232ef5e
fix(site): collapse the workspace pill into overflow before it miniat…
tracyjohnsonux Aug 25, 2026
05ae5f5
fix(site): remove the workspace pill's icon-only state in favor of +N…
tracyjohnsonux Aug 25, 2026
6ee9c04
fix(site): remove emdash from story comment
tracyjohnsonux Aug 25, 2026
2817c51
Merge branch 'main' into tracyjohnsonux/model-selector-min-width
tracyjohnsonux Aug 25, 2026
f8a8c1d
Merge branch 'main' into tracyjohnsonux/model-selector-min-width
tracyjohnsonux Aug 26, 2026
57deee3
Merge branch 'main' into tracyjohnsonux/model-selector-min-width
tracyjohnsonux Aug 26, 2026
6bc341d
Merge branch 'main' into tracyjohnsonux/model-selector-min-width
tracyjohnsonux Aug 26, 2026
88211a4
Update ModelSelector.tsx
tracyjohnsonux Aug 26, 2026
4ecc1b3
Merge branch 'main' into tracyjohnsonux/model-selector-min-width
tracyjohnsonux Aug 26, 2026
ff37112
fix(site): repair comment fragments and trailing whitespace in ModelS…
tracyjohnsonux Aug 26, 2026
29c3a61
Merge branch 'main' into tracyjohnsonux/model-selector-min-width
tracyjohnsonux Aug 26, 2026
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 @@ -126,7 +126,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 @@ -137,29 +140,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