Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions apps/app/src/components/promptbox/FollowUpPromptBox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,27 @@ describe("FollowUpPromptBox", () => {
expect(queuedMessages.previousElementSibling).toBe(pluginHeaderRoot);
});

it("lays the banner stack on an explicit single-column track", () => {
// jsdom has no layout engine, so this guards the track declaration rather
// than the resulting geometry. A bare `grid gap-2` puts every card in an
// implicit `auto` column, whose base size is the widest card's min-content
// width — and a row built as `min-w-0 flex-1 truncate` still reports its
// full untruncated string there, because that guard only bites once the
// parent width is definite. One long queued message sized the column at
// ~1350px inside a 370px composer and stretched every other card off the
// viewport with it.
const props = createFollowUpPromptBoxProps({ kind: "ready" });
render(
<FollowUpPromptBox
{...props}
stack={<div data-testid="queued-messages">Queued messages</div>}
/>,
);

const stack = screen.getByTestId("queued-messages").parentElement;
expect(stack?.className).toContain("grid-cols-[minmax(0,1fr)]");
});

it("does not mount plugin banners for a retained inactive composer without a real scope", () => {
setPluginSlotRegistrations("inactive-banner", {
homepageSections: [],
Expand Down
8 changes: 6 additions & 2 deletions apps/app/src/components/promptbox/FollowUpPromptBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { usePointerCoarse } from "@bb/shared-ui/hooks/use-pointer-coarse";
import { ThreadTimelineScrollToBottomButton } from "@/views/thread-detail/ThreadTimelineScrollToBottomButton";
import { useOptionalPaneContext } from "@/views/thread-detail/PaneContext";
import { ThreadContextWindowIndicator } from "@/components/thread/timeline";
import { PROMPT_STACK_TRACK_CLASS } from "@/components/promptbox/banner/PromptStackCard";
import { THREAD_PROMPT_CONTEXT_BANNER_ROW_HEIGHT } from "@/components/promptbox/banner/ThreadPromptContextBanner";
import {
isPlanModePrompt,
Expand Down Expand Up @@ -279,7 +280,7 @@ function FollowUpPromptBoxStackOnly({
<PluginComposerViewProvider value={composerView}>
<PluginComposerHostProvider value={pluginComposerHost ?? null}>
<div data-promptbox-shell="" className="space-y-2">
<div className="grid gap-2">
<div className={`grid gap-2 ${PROMPT_STACK_TRACK_CLASS}`}>
{composerScope ? (
<ComposerBannersSlot>{stack}</ComposerBannersSlot>
) : (
Expand Down Expand Up @@ -858,7 +859,10 @@ function DefaultFollowUpComposer({
data-promptbox-shell=""
className="space-y-2"
>
<div ref={stackRef} className="grid gap-2">
<div
ref={stackRef}
className={`grid gap-2 ${PROMPT_STACK_TRACK_CLASS}`}
>
{hasPluginComposerScope ? (
<ComposerBannersSlot>{stack}</ComposerBannersSlot>
) : (
Expand Down
5 changes: 4 additions & 1 deletion apps/app/src/components/promptbox/NewThreadPromptBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { Host, ProjectSource, PromptTextMention } from "@bb/domain";
import type { ComposerView } from "@get-bb/plugin-sdk";
import type { ComposerTextEffectSource } from "@/lib/composer-text-effects";
import { ComposerBannersSlot } from "@/components/plugin/PluginComposerBanners";
import { PROMPT_STACK_TRACK_CLASS } from "@/components/promptbox/banner/PromptStackCard";
import {
type PluginComposerHost,
usePluginComposerViewModel,
Expand Down Expand Up @@ -385,7 +386,9 @@ const DefaultNewThreadComposer = memo(function DefaultNewThreadComposer({
data-promptbox-shell=""
className="w-full"
>
<div className="mb-2 grid gap-2 empty:hidden">
<div
className={`mb-2 grid gap-2 empty:hidden ${PROMPT_STACK_TRACK_CLASS}`}
>
<ComposerBannersSlot ownerPlacement="before">
{modeConfig.banner}
</ComposerBannersSlot>
Expand Down
17 changes: 17 additions & 0 deletions apps/app/src/components/promptbox/banner/PromptStackCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ const BASE_CHROME = cn(
"border border-border bg-surface-raised-solid",
);

/**
* The track the stack itself lays cards on.
*
* A bare `grid gap-2` drops every card into an implicit `auto` column, and an
* auto column's base size is the widest card's MIN-CONTENT width. That is not
* the width the card renders at: a row built as `min-w-0 flex-1 truncate`
* still reports its full untruncated string as its min-content contribution,
* because the truncation only bites once the parent hands it a definite width.
* So one long queued message — or one plugin banner with a long rule in it —
* sized the column at ~1350px inside a 370px composer, and every other card
* stretched to match and left the viewport with it.
*
* `minmax(0, 1fr)` pins the track to the shell's width instead. Each card then
* gets a definite width and its own truncation does the rest.
*/
export const PROMPT_STACK_TRACK_CLASS = "grid-cols-[minmax(0,1fr)]";

export interface PromptStackCardProps {
children: ReactNode;
/**
Expand Down
Loading