authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-10 19:02:28-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-10 19:02:28-07:00
log29e09df979955a22e2ad35bd58a13518861b9551
tree154a712a3683fb31db849fd600ad4fbf555fe9ff
parent9ec472c7d9345a0c9528183d918f5fbc7f73be75
signature Signed by PGP key B5690EEEBB952194

fix(web): MCP app permission prompts no longer overflow the screen (#17962)


2 files changed, 50 insertions(+), 3 deletions(-)

apps/web/src/components/ui/alert-dialog.test.tsx created+38
......@@ -0,0 +1,38 @@
1import { renderToStaticMarkup } from "react-dom/server";
2import { describe, expect, it } from "vite-plus/test";
3
4import {
5 AlertDialog,
6 AlertDialogDescription,
7 AlertDialogFooter,
8 AlertDialogHeader,
9 AlertDialogTitle,
10} from "./alert-dialog";
11
12describe("alert dialog description", () => {
13 it("keeps a long payload in a bounded scroll region so the actions stay put", () => {
14 const payload = `{elements:[${"x".repeat(4_000)}]}`;
15 const markup = renderToStaticMarkup(
16 <AlertDialog>
17 <AlertDialogHeader>
18 <AlertDialogTitle>Allow excalidraw to run export_to_excalidraw?</AlertDialogTitle>
19 <AlertDialogDescription>{payload}</AlertDialogDescription>
20 </AlertDialogHeader>
21 <AlertDialogFooter>Cancel</AlertDialogFooter>
22 </AlertDialog>,
23 );
24 const header = /<div[^>]*data-slot="alert-dialog-header"[^>]*>/.exec(markup)?.[0] ?? "";
25 const description = /<p[^>]*data-slot="alert-dialog-description"[^>]*>/.exec(markup)?.[0] ?? "";
26
27 // The whole payload stays available to inspect, untruncated…
28 expect(markup).toContain(payload);
29 // …but bounded: the header may shrink inside the height-capped popup and
30 // the description scrolls instead of pushing the footer off screen.
31 expect(header).toContain("min-h-0");
32 expect(description).toContain("max-h-[min(24rem,60dvh)]");
33 expect(description).toContain("overflow-y-auto");
34 expect(description).toContain("overscroll-contain");
35 expect(description).toContain("wrap-anywhere");
36 expect(markup.indexOf("</p>")).toBeLessThan(markup.indexOf("Cancel"));
37 });
38});
apps/web/src/components/ui/alert-dialog.tsx+12-3
......@@ -70,7 +70,10 @@ function AlertDialogPopup({
7070function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
7171 return (
7272 <div
73 className={cn("flex flex-col gap-2 p-6 text-center max-sm:pb-4 sm:text-left", className)}
73 className={cn(
74 "flex min-h-0 flex-col gap-2 p-6 text-center max-sm:pb-4 sm:text-left",
75 className,
76 )}
7477 data-slot="alert-dialog-header"
7578 {...props}
7679 />
......@@ -101,7 +104,7 @@ function AlertDialogFooter({
101104function AlertDialogTitle({ className, ...props }: AlertDialogPrimitive.Title.Props) {
102105 return (
103106 <AlertDialogPrimitive.Title
104 className={cn("wrap-anywhere font-semibold text-xl leading-none", className)}
107 className={cn("wrap-anywhere shrink-0 font-semibold text-xl leading-none", className)}
105108 data-slot="alert-dialog-title"
106109 {...props}
107110 />
......@@ -111,7 +114,13 @@ function AlertDialogTitle({ className, ...props }: AlertDialogPrimitive.Title.Pr
111114function AlertDialogDescription({ className, ...props }: AlertDialogPrimitive.Description.Props) {
112115 return (
113116 <AlertDialogPrimitive.Description
114 className={cn("text-muted-foreground text-sm", className)}
117 className={cn(
118 // A long description (say, the arguments of a tool call awaiting
119 // approval) scrolls in place instead of pushing the actions out of
120 // the popup, which the viewport already caps.
121 "wrap-anywhere max-h-[min(24rem,60dvh)] min-h-0 overflow-y-auto overscroll-contain text-muted-foreground text-sm select-text",
122 className,
123 )}
115124 data-slot="alert-dialog-description"
116125 {...props}
117126 />