History

Constrain provider update popover overflow (#2669)

Julius Marminge committed 6 months agov0.0.24-nightly.20260513.276

Commit 7e20b231 parent b83e9c92 files · +59 −8Browse files

Unified · Split

2 files
  1. apps/web/src/components/settings/ProviderInstanceCard.tsx +13 −6
  2. apps/web/src/components/settings/SettingsPanels.browser.tsx +46 −2
apps/web/src/components/settings/ProviderInstanceCard.tsx+13−6
@@ -29,6 +29,7 @@ import { Button } from "../ui/button"; · 28 unmodified lines
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({ · 662 unmodified lines
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({ · 30 unmodified lines
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+46−2
@@ -236,7 +236,10 @@ function createBaseServerConfig(): ServerConfig { · 235 unmodified lines
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 { · 12 unmodified lines
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", () => { · 893 unmodified lines
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", () => {