Fix mention autocomplete layout in narrow threads (#1282)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Wes
2026-06-25 09:26:00 -07:00
committed by GitHub
co-authored by Pinky
parent 2499d33971
commit 227518c234
2 changed files with 156 additions and 30 deletions
@@ -96,44 +96,58 @@ export const MentionAutocomplete = React.memo(function MentionAutocomplete({
avatarUrl={suggestion.avatarUrl ?? null}
displayName={suggestion.displayName}
size="xs"
testId="mention-suggestion-avatar"
/>
<span className="flex min-w-0 flex-1 items-center gap-2">
<span className="flex min-w-0 flex-1 items-baseline gap-1">
<span className="truncate font-medium">
{suggestion.displayName}
</span>
{suggestion.isAgent ? (
<span
className={cn(
"inline-flex shrink-0 items-center gap-1 text-xs",
index === selectedIndex
? "text-accent-foreground/70"
: "text-muted-foreground",
)}
>
<Bot
aria-hidden="true"
className="h-4 w-4"
data-testid="mention-agent-icon"
/>
{agentLabel}
</span>
) : suggestion.role ? (
<Badge variant="secondary">{suggestion.role}</Badge>
) : null}
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
<span
className="min-w-0 break-words font-medium leading-snug"
title={suggestion.displayName}
>
{suggestion.displayName}
</span>
{suggestion.ownerLabel || suggestion.notInChannel ? (
{suggestion.isAgent ||
suggestion.role ||
suggestion.ownerLabel ||
suggestion.notInChannel ? (
<span
className={cn(
"ml-auto min-w-0 shrink truncate text-xs",
"flex min-w-0 items-center gap-1.5 text-2xs leading-none",
index === selectedIndex
? "text-accent-foreground/65"
? "text-accent-foreground/60"
: "text-muted-foreground",
)}
>
{suggestion.ownerLabel
? `owned by ${suggestion.ownerLabel}${suggestion.notInChannel ? " · not in channel" : ""}`
: "not in channel"}
{suggestion.isAgent ? (
<span className="inline-flex shrink-0 items-center gap-1">
<Bot
aria-hidden="true"
className="h-3.5 w-3.5"
data-testid="mention-agent-icon"
/>
{agentLabel}
</span>
) : suggestion.role ? (
<Badge
className="max-w-24 shrink-0 truncate"
variant="secondary"
>
{suggestion.role}
</Badge>
) : null}
{suggestion.ownerLabel || suggestion.notInChannel ? (
<span
className="min-w-0 truncate"
title={
suggestion.ownerLabel
? `owned by ${suggestion.ownerLabel}${suggestion.notInChannel ? " · not in channel" : ""}`
: "not in channel"
}
>
{suggestion.ownerLabel
? `owned by ${suggestion.ownerLabel}${suggestion.notInChannel ? " · not in channel" : ""}`
: "not in channel"}
</span>
) : null}
</span>
) : null}
</span>
+112
View File
@@ -46,6 +46,46 @@ function commandCount(commands: string[], command: string) {
return commands.filter((entry) => entry === command).length;
}
async function emitMockMessage(
page: import("@playwright/test").Page,
channelName: string,
content: string,
options?: {
parentEventId?: string;
pubkey?: string;
},
) {
const event = await page.evaluate(
({ ch, msg, parentEventId, pubkey }) => {
return (
window as Window & {
__BUZZ_E2E_EMIT_MOCK_MESSAGE__?: (input: {
channelName: string;
content: string;
parentEventId?: string | null;
pubkey?: string;
}) => { id: string; created_at: number; pubkey: string };
}
).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
channelName: ch,
content: msg,
parentEventId: parentEventId ?? undefined,
pubkey: pubkey ?? undefined,
});
},
{
ch: channelName,
msg: content,
parentEventId: options?.parentEventId ?? null,
pubkey: options?.pubkey ?? TEST_IDENTITIES.alice.pubkey,
},
);
if (!event) {
throw new Error("Mock message emitter is not installed");
}
return event;
}
async function waitForMockLiveSubscription(
page: import("@playwright/test").Page,
channelName: string,
@@ -135,6 +175,78 @@ test("@ trigger shows unified autocomplete with agents first", async ({
expect(bobIndex).toBeLessThan(charlieIndex);
});
test("thread autocomplete keeps multiple long names readable in a narrow panel", async ({
page,
}) => {
await installMockBridge(page, {
managedAgents: [
{
pubkey:
"9999999999999999999999999999999999999999999999999999999999999999",
name: "Brain With A Very Long Name",
status: "stopped",
},
{
pubkey:
"9999999999999999999999999999999999999999999999999999999999999998",
name: "Brainstorming Assistant With A Long Name",
status: "stopped",
},
{
pubkey:
"9999999999999999999999999999999999999999999999999999999999999997",
name: "Brainy Helper With Another Long Name",
status: "stopped",
},
],
});
await page.setViewportSize({ width: 900, height: 640 });
await page.addInitScript(() => {
window.sessionStorage.setItem("buzz.desktop.thread-panel-width", "300");
});
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
await page.setViewportSize({ width: 760, height: 640 });
await emitMockMessage(page, "general", "Reply to open the thread", {
parentEventId: "mock-general-welcome",
});
const threadSummary = page.getByTestId("message-thread-summary").first();
await expect(threadSummary).toBeVisible();
await threadSummary.click();
const threadPanel = page.getByTestId("message-thread-panel");
await expect(threadPanel).toBeVisible();
const panelBox = await threadPanel.boundingBox();
expect(panelBox?.width ?? Number.POSITIVE_INFINITY).toBeLessThanOrEqual(320);
const input = threadPanel.getByTestId("message-input");
await input.fill("@Brain");
const dropdown = threadPanel.getByTestId("mention-autocomplete");
await expect(dropdown).toBeVisible();
for (const name of [
"Brain With A Very Long Name",
"Brainstorming Assistant With A Long Name",
"Brainy Helper With Another Long Name",
]) {
const row = dropdown.locator("button", { hasText: name });
await expect(row).toBeVisible();
await expect(
row.getByTestId("mention-suggestion-avatar-fallback"),
).toBeVisible();
await expect(row.getByText("agent")).toBeVisible();
await expect(row.getByText(/owned by npub1mock/)).toBeVisible();
await expect(row.getByText(name)).not.toHaveCSS(
"text-overflow",
"ellipsis",
);
}
});
test("autocomplete filters suggestions as user types", async ({ page }) => {
await page.goto("/");
await page.getByTestId("channel-general").click();