Skip to content

Commit 7e20b23

Browse files
Constrain provider update popover overflow (pingdotgg#2669)
1 parent b83e9c9 commit 7e20b23

2 files changed

Lines changed: 59 additions & 8 deletions

File tree

apps/web/src/components/settings/ProviderInstanceCard.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Button } from "../ui/button";
2929
import { Collapsible, CollapsibleContent } from "../ui/collapsible";
3030
import { DraftInput } from "../ui/draft-input";
3131
import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";
32+
import { ScrollArea } from "../ui/scroll-area";
3233
import { Switch } from "../ui/switch";
3334
import { stackedThreadToast, toastManager } from "../ui/toast";
3435
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
@@ -697,8 +698,12 @@ export function ProviderInstanceCard({
697698
</Button>
698699
}
699700
/>
700-
<PopoverPopup side="bottom" align="start" className="w-84">
701-
<div className="grid gap-3">
701+
<PopoverPopup
702+
side="bottom"
703+
align="start"
704+
className="w-[min(21rem,calc(100vw-1.5rem))] [--popup-width:min(21rem,calc(100vw-1.5rem))]"
705+
>
706+
<div className="grid min-w-0 gap-3">
702707
<div className="grid gap-0.5">
703708
<p className="text-[13px] font-semibold leading-tight text-foreground">
704709
Update available
@@ -735,10 +740,12 @@ export function ProviderInstanceCard({
735740
</div>
736741
) : null}
737742
{updateCommand ? (
738-
<div className="flex items-center gap-1 rounded-md border border-border/70 bg-muted/40 py-0.5 pr-0.5 pl-2">
739-
<code className="min-w-0 flex-1 overflow-x-auto whitespace-nowrap font-mono text-[11px] text-foreground [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
740-
{updateCommand}
741-
</code>
743+
<div className="flex min-w-0 items-center gap-1 rounded-md border border-border/70 bg-muted/40 py-0.5 pr-0.5 pl-2">
744+
<ScrollArea scrollFade className="h-8 min-w-0 flex-1 rounded-none">
745+
<code className="flex h-full w-max items-center whitespace-nowrap pr-3 font-mono text-[11px] text-foreground">
746+
{updateCommand}
747+
</code>
748+
</ScrollArea>
742749
<Tooltip>
743750
<TooltipTrigger
744751
render={

apps/web/src/components/settings/SettingsPanels.browser.tsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,10 @@ function createBaseServerConfig(): ServerConfig {
236236
};
237237
}
238238

239-
function createOutdatedProvider(driver: string): ServerProvider {
239+
function createOutdatedProvider(
240+
driver: string,
241+
updateCommand = "npm install -g openai/codex@latest",
242+
): ServerProvider {
240243
return {
241244
instanceId: ProviderInstanceId.make(driver),
242245
driver: ProviderDriverKind.make(driver),
@@ -255,7 +258,7 @@ function createOutdatedProvider(driver: string): ServerProvider {
255258
latestVersion: "1.1.0",
256259
message: "Update available.",
257260
checkedAt: "2026-05-04T10:00:00.000Z",
258-
updateCommand: "npm install -g openai/codex@latest",
261+
updateCommand,
259262
canUpdate: true,
260263
},
261264
};
@@ -1155,6 +1158,47 @@ describe("GeneralSettingsPanel observability", () => {
11551158
instanceId: ProviderInstanceId.make("codex"),
11561159
});
11571160
});
1161+
1162+
it("keeps long provider update commands inside the fixed-width popover", async () => {
1163+
const longUpdateCommand =
1164+
"npm install -g @anthropic-ai/claude-code@latest --registry=https://registry.npmjs.org --cache=/tmp/t3code-provider-update-cache";
1165+
1166+
setServerConfigSnapshot({
1167+
...createBaseServerConfig(),
1168+
providers: [createOutdatedProvider("codex", longUpdateCommand)],
1169+
});
1170+
1171+
mounted = await render(
1172+
<AppAtomRegistryProvider>
1173+
<ProviderSettingsPanel />
1174+
</AppAtomRegistryProvider>,
1175+
);
1176+
1177+
await page.getByRole("button", { name: "Update available — view details" }).click();
1178+
await expect.element(page.getByText(longUpdateCommand)).toBeInTheDocument();
1179+
1180+
await vi.waitFor(() => {
1181+
const popup = document.querySelector<HTMLElement>('[data-slot="popover-popup"]');
1182+
const commandCode = Array.from(document.querySelectorAll<HTMLElement>("code")).find(
1183+
(element) => element.textContent === longUpdateCommand,
1184+
);
1185+
const scrollViewport = commandCode?.closest<HTMLElement>(
1186+
'[data-slot="scroll-area-viewport"]',
1187+
);
1188+
1189+
expect(popup).toBeTruthy();
1190+
expect(commandCode).toBeTruthy();
1191+
expect(scrollViewport).toBeTruthy();
1192+
1193+
const popupRect = popup!.getBoundingClientRect();
1194+
const viewportRect = scrollViewport!.getBoundingClientRect();
1195+
1196+
expect(popupRect.width).toBeGreaterThan(300);
1197+
expect(popupRect.width).toBeLessThanOrEqual(337);
1198+
expect(viewportRect.right).toBeLessThanOrEqual(popupRect.right + 0.5);
1199+
expect(scrollViewport!.scrollWidth).toBeGreaterThan(scrollViewport!.clientWidth);
1200+
});
1201+
});
11581202
});
11591203

11601204
describe("SourceControlSettingsPanel discovery states", () => {

0 commit comments

Comments
 (0)