Skip to content
Merged
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
42 changes: 41 additions & 1 deletion plugins/thread-briefs/app.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,16 @@ describe("the header popover", () => {
getBrief?: () => BriefState;
setStageOverride?: (input: unknown) => BriefState;
refresh?: () => { queued: boolean };
isCompactViewport?: boolean;
}) => {
const captured = await loadApp();
return renderSlot(
captured.threadHeaderActions[0]!,
{ threadId: "thr_1", projectId: "proj_1", isCompactViewport: false },
{
threadId: "thr_1",
projectId: "proj_1",
isCompactViewport: options.isCompactViewport ?? false,
},
{
rpc: {
getBrief: options.getBrief ?? (() => READY),
Expand Down Expand Up @@ -142,6 +147,41 @@ describe("the header popover", () => {
slot.lifecycle.unmount();
});

it("caps its height and scrolls, so a long brief is reachable on a phone", async () => {
const slot = await render({ isCompactViewport: true });
fireEvent.click(await slot.findByRole("button", { name: "Thread brief" }));

const goal = await slot.findByText("Ship the thread-briefs plugin");
const panel = goal.closest("[style*='max-height']") as HTMLElement | null;
expect(panel).not.toBeNull();
// Without both of these the panel grows past the viewport with no way to
// reach the fields below the fold.
expect(panel?.style.maxHeight).toContain(
"--radix-popover-content-available-height",
);
expect(panel?.className).toContain("overflow-y-auto");

slot.lifecycle.unmount();
});

it("goes near-full-width on a compact viewport and a fixed column otherwise", async () => {
const compact = await render({ isCompactViewport: true });
fireEvent.click(await compact.findByRole("button", { name: "Thread brief" }));
const compactPanel = (
await compact.findByText("Ship the thread-briefs plugin")
).closest("[style*='max-height']") as HTMLElement | null;
expect(compactPanel?.className).toContain("w-[calc(100vw-1rem)]");
compact.lifecycle.unmount();

const wide = await render({ isCompactViewport: false });
fireEvent.click(await wide.findByRole("button", { name: "Thread brief" }));
const widePanel = (
await wide.findByText("Ship the thread-briefs plugin")
).closest("[style*='max-height']") as HTMLElement | null;
expect(widePanel?.className).toContain("w-80");
wide.lifecycle.unmount();
});

it("sets a manual stage", async () => {
const slot = await render({});
fireEvent.click(await slot.findByRole("button", { name: "Thread brief" }));
Expand Down
15 changes: 14 additions & 1 deletion plugins/thread-briefs/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,20 @@ function BriefHeaderAction({
<Popover.Content
align="end"
sideOffset={6}
className="z-50 w-80 max-w-[calc(100vw-2rem)] rounded-md border border-border bg-card p-3 shadow-md"
collisionPadding={8}
// A five-field brief is easily taller than a phone viewport, so the
// panel has to cap its height and scroll inside. Radix measures the
// room it actually has and publishes it as this variable; the vh
// fallback covers the case where collision detection is skipped.
// Inline rather than a Tailwind arbitrary value so it cannot depend
// on what the plugin's Tailwind pass chose to emit.
style={{
maxHeight: "var(--radix-popover-content-available-height, 70vh)",
}}
className={`z-50 overflow-y-auto overscroll-contain rounded-md border border-border bg-card p-3 shadow-md ${
// Near-full width on a phone; a fixed column on a wide screen.
isCompactViewport ? "w-[calc(100vw-1rem)]" : "w-80"
}`}
>
<BriefBody state={state} onPick={onPick} onRefresh={onRefresh} />
</Popover.Content>
Expand Down
Loading