| author | |
| committer | |
| log | 7e20b23e7ea5dc1daf4cbfa65d61301d99168bae |
| tree | b6f89ecf0f33b645c15e97870acb942139e03f6f |
| parent | b83e9c95e4f3bd1dcb771ee436dcfafcfaf6b3fa |
| signature | Signed by PGP key B5690EEEBB952194 |
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"; |
| 29 | import { Collapsible, CollapsibleContent } from "../ui/collapsible"; | 29 | import { Collapsible, CollapsibleContent } from "../ui/collapsible"; |
| 30 | import { DraftInput } from "../ui/draft-input"; | 30 | import { DraftInput } from "../ui/draft-input"; |
| 31 | import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; | 31 | import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; |
| 32 | import { ScrollArea } from "../ui/scroll-area"; | ||
| 32 | import { Switch } from "../ui/switch"; | 33 | import { Switch } from "../ui/switch"; |
| 33 | import { stackedThreadToast, toastManager } from "../ui/toast"; | 34 | import { stackedThreadToast, toastManager } from "../ui/toast"; |
| 34 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | 35 | import { 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 available | 709 | 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 | <TooltipTrigger | 750 | <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 | } |
| 238 | 238 | ||
| 239 | function createOutdatedProvider(driver: string): ServerProvider { | 239 | function 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 | }); |
| 1159 | 1203 | ||
| 1160 | describe("SourceControlSettingsPanel discovery states", () => { | 1204 | describe("SourceControlSettingsPanel discovery states", () => { |