diff --git a/apps/web/src/components/chat/ComposerBannerStack.test.tsx b/apps/web/src/components/chat/ComposerBannerStack.test.tsx
new file mode 100644
index 00000000000..16fdff64425
--- /dev/null
+++ b/apps/web/src/components/chat/ComposerBannerStack.test.tsx
@@ -0,0 +1,37 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vite-plus/test";
+
+import { ComposerBannerStack, type ComposerBannerStackItem } from "./ComposerBannerStack";
+
+const banner = (id: string): ComposerBannerStackItem => ({
+ id,
+ variant: "warning",
+ icon: !,
+ title: `${id} warning`,
+});
+
+describe("ComposerBannerStack", () => {
+ it("keeps expanded banners in layout flow so surrounding content moves out of their way", () => {
+ const markup = renderToStaticMarkup(
+ ,
+ );
+
+ const expandedItems = markup.match(
+ /
/,
+ );
+
+ expect(expandedItems?.[1]).toContain("grid-rows-[0fr]");
+ expect(expandedItems?.[1]).toContain("group-hover/banner-stack:grid-rows-[1fr]");
+ expect(expandedItems?.[1]).toContain("z-20");
+ expect(expandedItems?.[1]).not.toContain("absolute");
+ expect(markup.indexOf("front warning")).toBeLessThan(markup.indexOf("stacked warning"));
+ expect(markup).toContain("invisible pointer-events-none");
+ expect(markup).toContain("group-focus-within/banner-stack:visible");
+ });
+
+ it("does not render an expandable region for a single banner", () => {
+ const markup = renderToStaticMarkup(
);
+
+ expect(markup).not.toContain("data-composer-banner-stack-expanded-items");
+ });
+});
diff --git a/apps/web/src/components/chat/ComposerBannerStack.tsx b/apps/web/src/components/chat/ComposerBannerStack.tsx
index 1f82c452b05..d51c23a2f27 100644
--- a/apps/web/src/components/chat/ComposerBannerStack.tsx
+++ b/apps/web/src/components/chat/ComposerBannerStack.tsx
@@ -85,7 +85,7 @@ export function ComposerBannerStack({ className, items }: ComposerBannerStackPro
@@ -119,30 +119,39 @@ export function ComposerBannerStack({ className, items }: ComposerBannerStackPro
{hasStack ? (
- {stackedItems.map((item) => (
+
-
requestDismiss(item)}
- />
+ {stackedItems.map((item) => (
+
+ requestDismiss(item)}
+ />
+
+ ))}
- ))}
+
) : null}
diff --git a/apps/web/src/components/chat/DraftHeroHeadline.tsx b/apps/web/src/components/chat/DraftHeroHeadline.tsx
index 32d195d7b2e..6a88fb8da19 100644
--- a/apps/web/src/components/chat/DraftHeroHeadline.tsx
+++ b/apps/web/src/components/chat/DraftHeroHeadline.tsx
@@ -101,7 +101,7 @@ export function DraftHeroHeadline({
);
return (
-
+
{hasResolvedProject ? (
<>What should we build in {projectSelector}?>
) : canChooseProject ? (