authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-05-13 10:23:31+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-05-13 10:23:31+02:00
log7e20b23e7ea5dc1daf4cbfa65d61301d99168bae
treeb6f89ecf0f33b645c15e97870acb942139e03f6f
parentb83e9c95e4f3bd1dcb771ee436dcfafcfaf6b3fa
signature Signed by PGP key B5690EEEBB952194

Constrain provider update popover overflow (#2669)


2 files changed, 59 insertions(+), 8 deletions(-)

apps/web/src/components/settings/ProviderInstanceCard.tsx+13-6
...@@ -29,6 +29,7 @@ import { Button } from "../ui/button";...@@ -29,6 +29,7 @@ import { Button } from "../ui/button";
29import { Collapsible, CollapsibleContent } from "../ui/collapsible";29import { Collapsible, CollapsibleContent } from "../ui/collapsible";
30import { DraftInput } from "../ui/draft-input";30import { DraftInput } from "../ui/draft-input";
31import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";31import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";
32import { ScrollArea } from "../ui/scroll-area";
32import { Switch } from "../ui/switch";33import { Switch } from "../ui/switch";
33import { stackedThreadToast, toastManager } from "../ui/toast";34import { stackedThreadToast, toastManager } from "../ui/toast";
34import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";35import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
...@@ -697,8 +698,12 @@ export function ProviderInstanceCard({...@@ -697,8 +698,12 @@ export function ProviderInstanceCard({
697 </Button>698 </Button>
698 }699 }
699 />700 />
700 <PopoverPopup side="bottom" align="start" className="w-84">701 <PopoverPopup
701 <div className="grid gap-3">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">
702 <div className="grid gap-0.5">707 <div className="grid gap-0.5">
703 <p className="text-[13px] font-semibold leading-tight text-foreground">708 <p className="text-[13px] font-semibold leading-tight text-foreground">
704 Update available709 Update available
...@@ -735,10 +740,12 @@ export function ProviderInstanceCard({...@@ -735,10 +740,12 @@ export function ProviderInstanceCard({
735 </div>740 </div>
736 ) : null}741 ) : null}
737 {updateCommand ? (742 {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">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">
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">744 <ScrollArea scrollFade className="h-8 min-w-0 flex-1 rounded-none">
740 {updateCommand}745 <code className="flex h-full w-max items-center whitespace-nowrap pr-3 font-mono text-[11px] text-foreground">
741 </code>746 {updateCommand}
747 </code>
748 </ScrollArea>
742 <Tooltip>749 <Tooltip>
743 <TooltipTrigger750 <TooltipTrigger
744 render={751 render={
apps/web/src/components/settings/SettingsPanels.browser.tsx+46-2
...@@ -236,7 +236,10 @@ function createBaseServerConfig(): ServerConfig {...@@ -236,7 +236,10 @@ function createBaseServerConfig(): ServerConfig {
236 };236 };
237}237}
238238
239function createOutdatedProvider(driver: string): ServerProvider {239function createOutdatedProvider(
240 driver: string,
241 updateCommand = "npm install -g openai/codex@latest",
242): ServerProvider {
240 return {243 return {
241 instanceId: ProviderInstanceId.make(driver),244 instanceId: ProviderInstanceId.make(driver),
242 driver: ProviderDriverKind.make(driver),245 driver: ProviderDriverKind.make(driver),
...@@ -255,7 +258,7 @@ function createOutdatedProvider(driver: string): ServerProvider {...@@ -255,7 +258,7 @@ function createOutdatedProvider(driver: string): ServerProvider {
255 latestVersion: "1.1.0",258 latestVersion: "1.1.0",
256 message: "Update available.",259 message: "Update available.",
257 checkedAt: "2026-05-04T10:00:00.000Z",260 checkedAt: "2026-05-04T10:00:00.000Z",
258 updateCommand: "npm install -g openai/codex@latest",261 updateCommand,
259 canUpdate: true,262 canUpdate: true,
260 },263 },
261 };264 };
...@@ -1155,6 +1158,47 @@ describe("GeneralSettingsPanel observability", () => {...@@ -1155,6 +1158,47 @@ describe("GeneralSettingsPanel observability", () => {
1155 instanceId: ProviderInstanceId.make("codex"),1158 instanceId: ProviderInstanceId.make("codex"),
1156 });1159 });
1157 });1160 });
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 });
1158});1202});
11591203
1160describe("SourceControlSettingsPanel discovery states", () => {1204describe("SourceControlSettingsPanel discovery states", () => {