feat(desktop): add Bestie sidebar entry

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Arjun Mahanti <arjun@squareup.com>
This commit is contained in:
Arjun Mahanti
2026-08-26 17:55:20 -04:00
committed by Fizz
parent b04317f46a
commit e506962ee3
5 changed files with 132 additions and 0 deletions

View File

@@ -20,6 +20,7 @@
"test:e2e": "pnpm build:e2e && playwright test",
"test:e2e:smoke": "pnpm build:e2e && playwright test --project=smoke",
"test:e2e:integration": "pnpm build:e2e && playwright test --project=integration",
"test:e2e:bestie": "VITE_BUZZ_BESTIE=1 pnpm build:e2e && playwright test --config=playwright.bestie.config.ts",
"test:e2e:release-smoke": "pnpm build:e2e && playwright test --config=playwright.release-smoke.config.ts",
"test:e2e:report": "playwright show-report",
"tauri:build": "tauri build"

View File

@@ -0,0 +1,25 @@
import { defineConfig, devices } from "@playwright/test";
const webPort = process.env.BUZZ_BESTIE_E2E_WEB_PORT ?? "4174";
const webUrl = `http://127.0.0.1:${webPort}`;
export default defineConfig({
testDir: "./tests/e2e",
testMatch: "**/bestie-sidebar.spec.ts",
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
workers: 1,
reporter: [["list"]],
use: {
...devices["Desktop Chrome"],
baseURL: webUrl,
screenshot: "only-on-failure",
trace: "on-first-retry",
},
webServer: {
command: `python3 -m http.server ${webPort} -d dist`,
cwd: ".",
reuseExistingServer: false,
url: webUrl,
},
});

View File

@@ -552,7 +552,9 @@ export function AppSidebar({
data-testid="sidebar-scroll-content"
>
<AppSidebarPrimaryMenu
bestieRelayUrl={activeCommunity?.relayUrl}
homeBadgeCount={homeBadgeCount}
onOpenDm={onOpenDm}
onSelectAgents={onSelectAgents}
onSelectHome={onSelectHome}
onSelectProjects={onSelectProjects}

View File

@@ -1,5 +1,9 @@
import { Activity, Bot, Folders, Inbox, Zap } from "lucide-react";
import * as React from "react";
import { useManagedAgentsQuery } from "@/features/agents/hooks";
import { pickBestieAgent } from "@/features/agents/lib/bestie";
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
import { TopbarSearch } from "@/features/search/ui/TopbarSearch";
import { SidebarProjectsSection } from "@/features/sidebar/ui/SidebarProjectsSection";
import { FeatureGate } from "@/shared/features";
@@ -39,7 +43,9 @@ type AppSidebarPinnedHeaderProps = {
};
type AppSidebarPrimaryMenuProps = {
bestieRelayUrl?: string | null;
homeBadgeCount: number;
onOpenDm: (input: { pubkeys: string[] }) => Promise<void>;
onSelectAgents: () => void;
onSelectHome: () => void;
onSelectProjects: () => void;
@@ -89,7 +95,9 @@ export function AppSidebarPinnedHeader({
}
export function AppSidebarPrimaryMenu({
bestieRelayUrl,
homeBadgeCount,
onOpenDm,
onSelectAgents,
onSelectHome,
onSelectProjects,
@@ -167,6 +175,12 @@ export function AppSidebarPrimaryMenu({
<SidebarMenuLabel>Agents</SidebarMenuLabel>
</SidebarMenuButton>
</SidebarMenuItem>
<FeatureGate feature="bestie">
<BestieSidebarMenuItem
onOpenDm={onOpenDm}
relayUrl={bestieRelayUrl}
/>
</FeatureGate>
<FeatureGate feature="workflows">
<SidebarMenuItem>
<SidebarMenuButton
@@ -187,3 +201,39 @@ export function AppSidebarPrimaryMenu({
</>
);
}
function BestieSidebarMenuItem({
onOpenDm,
relayUrl,
}: {
onOpenDm: (input: { pubkeys: string[] }) => Promise<void>;
relayUrl?: string | null;
}) {
const managedAgentsQuery = useManagedAgentsQuery();
const bestieAgent = React.useMemo(
() => pickBestieAgent(managedAgentsQuery.data ?? [], relayUrl),
[managedAgentsQuery.data, relayUrl],
);
if (!bestieAgent) return null;
return (
<SidebarMenuItem>
<SidebarMenuButton
data-testid="open-bestie-dm"
onClick={() => void onOpenDm({ pubkeys: [bestieAgent.pubkey] })}
tooltip={`Message ${bestieAgent.name}`}
type="button"
>
<ProfileAvatar
avatarUrl={bestieAgent.avatarUrl}
className="size-4 text-3xs shadow-none"
label={bestieAgent.name}
plain
testId="bestie-sidebar-avatar"
/>
<SidebarMenuLabel>Bestie</SidebarMenuLabel>
</SidebarMenuButton>
</SidebarMenuItem>
);
}

View File

@@ -0,0 +1,54 @@
import { expect, test } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
import { FEATURE_OVERRIDES_STORAGE_KEY } from "../helpers/features";
const BESTIE_PUBKEY =
"be571e0000000000000000000000000000000000000000000000000000000000";
const bestie = {
avatarUrl: null,
name: "Bestie",
personaId: "builtin:bestie",
pubkey: BESTIE_PUBKEY,
status: "running" as const,
};
test("the enabled Bestie experiment adds a direct-message entry below Agents", async ({
page,
}) => {
await installMockBridge(page, { managedAgents: [bestie] });
await page.goto("/");
const agentsEntry = page.getByTestId("open-agents-view");
const bestieEntry = page.getByTestId("open-bestie-dm");
await expect(bestieEntry).toBeVisible();
await expect(bestieEntry).toContainText("Bestie");
const [agentsBox, bestieBox] = await Promise.all([
agentsEntry.boundingBox(),
bestieEntry.boundingBox(),
]);
expect(agentsBox).not.toBeNull();
expect(bestieBox).not.toBeNull();
expect(bestieBox?.y).toBeGreaterThan(agentsBox?.y ?? 0);
await bestieEntry.click();
await expect(page.getByTestId("chat-title")).toHaveText("Bestie");
});
test("the disabled Bestie experiment does not mount the sidebar entry", async ({
page,
}) => {
await installMockBridge(page, { managedAgents: [bestie] });
await page.addInitScript((key) => {
const overrides = JSON.parse(
window.localStorage.getItem(key) ?? "{}",
) as Record<string, boolean>;
overrides.bestie = false;
window.localStorage.setItem(key, JSON.stringify(overrides));
}, FEATURE_OVERRIDES_STORAGE_KEY);
await page.goto("/");
await expect(page.getByTestId("open-bestie-dm")).toHaveCount(0);
});