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";
2929import { Collapsible, CollapsibleContent } from "../ui/collapsible";
3030import { DraftInput } from "../ui/draft-input";
3131import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";
32import { ScrollArea } from "../ui/scroll-area";
3233import { Switch } from "../ui/switch";
3334import { stackedThreadToast, toastManager } from "../ui/toast";
3435import { 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+46-2
......@@ -236,7 +236,10 @@ function createBaseServerConfig(): ServerConfig {
236236 };
237237}
238238
239function createOutdatedProvider(driver: string): ServerProvider {
239function 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
11601204describe("SourceControlSettingsPanel discovery states", () => {