mirror of
https://github.com/block/buzz.git
synced 2026-10-30 05:51:46 +00:00
🤖 ## Summary Every Buzz account is identified by a long public key. Before this change, when someone had no display name, surfaces fell back to inconsistent labels — mostly raw hex fragments like `abcd1234…wxyz`, sometimes a generic role label with no key — so the same person looked different from surface to surface, and nothing looked like an npub address. This PR applies the npub identity foundation from #7488 to the everyday surfaces: a person without a display name now falls back to the same compact npub everywhere — `npub1xxxx…yyyy`, the human-readable spelling of their public key (first 8 + last 4 characters of the full npub) — across messages and mentions, reactions, huddles, member and participant lists, the sidebar and channel activity, search, projects, tray, notifications, and workflow surfaces. - **Mentions and messages**: key-only mention chips render the compact npub. Pasting a copied mention back still re-binds it byte-exactly to the identity it declares, for both the new npub chips and legacy hex-truncated chips copied by older clients — wrong, missing, or tampered key qualification is rejected instead of silently degrading to plain text. - **Reactions and huddles**: huddle reaction events and the huddle roster/participants render the compact npub for unnamed participants; workflow reaction triggers describe authors with the same form. - **Members and sidebar**: channel and community member lists, add-member results and invites, the members sidebar, the channel-activity popover, search, projects (assignees/reviewers/PR panels), the tray menu, and desktop notifications all fall back to the compact npub; titles and aria labels keep the machine-readable full labels. - **Profile labels**: panel/popover display names and owner handles fall back to the compact npub (never raw hex) when there is no name; linked-event (nevent) message metadata shows the npub-shaped author fallback while the event lookup and event IDs are unchanged. - **Workflows**: author-picker secondary labels, step destination keys, and trigger-author references render compact npubs; event and blob IDs keep their existing hex compacts (they are not identities). - **Avatars stay distinct**: fallback avatars for key-only identities derive initials from the key's tail, so prefixed role labels like "Participant npub1…" no longer collapse every unnamed participant onto the same initials; people with names keep their name initials. Preserved exactly: display names and distinct avatars, internal hex keys (storage/API forms unchanged), clipboard identity roundtrips, event/blob ID compaction, private keys (no nsec path is touched), and nevent link handling. Scope: this PR changes what identity labels **display**, not identity controls — profile/settings copy controls, the respond-to allowlist, workflow key fields, and agent dialogs are the sibling slice #7489, and the shared primitives (`canonicalNpub`, `truncateNpub`, the `<PubKey>` gate, strict input parsing) come from the foundation #7488. ### Related issue - Fixes: N/A. Searched existing issues/PRs for duplicates — none found; the related work is the npub identity stack this slice belongs to. - Base/dependency: stacks on #7488 (foundation) — this PR does not stand alone on main. - #7489 is a sibling slice on the same #7488 base (profile/agent/workflow controls), not a dependency: this PR does not require #7489, and #7489 does not require this PR — both only require #7488. ### Testing At exact head `4763cbeae1dd521309755e6d61f657324cb98667` (base: `fix/desktop-npub-identity-d1a` @ `5f3a4a8111998c8aa41ad77cf66992bd1c85343c`; 71 files, +656/−189 — production +277/−136, test support +379/−53): - At this head: targeted `mentions.spec.ts` (1/1), the e2e build, typecheck, and biome — green. - 9 changed/related unit files: 100/100 green; typecheck, e2e build, biome, and px text/truncation checks clean; huddle-roster focused run green; channel-activity e2e 11/11; mutation checks confirm the fallback wiring (removing it collapses shared initials and drops fallback rows). - Known pre-existing local e2e failures, unchanged by this PR and reproduced identically at the upstream merge-base: huddle-transcription voice-menu attribution (25 pass / 1 fail) and the `workflow-local-controls` 438px caret drift. Not claimed green locally. - Update at head `236af9e6137386737e84d3a474d6bc808a704c50` (test-only follow-ups `1143af345` + `236af9e6`): the `workflow-local-controls` races were fixed in the test drivers, and the 438px diff was shown to be a stale Darwin snapshot baseline (name-row enable switch already absent and `message_posted` already MessageSquare at recording commit `9390e11c9`) and refreshed — the focused screenshot test, including keyboard/caret assertions, now passes locally (twice). The full spec was not rerun after the snapshot refresh; the huddle-transcription item above is unchanged. Label/copy text changes are asserted by the e2e specs (`mentions`, `mention-recipients`, `pubkey-display-screenshots`, `huddle-transcription`, `channel-activity-popover`, `workflow-local-controls`) rather than new screenshots; the screenshot spec pins the compact npub text forms. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
1863 lines
58 KiB
TypeScript
1863 lines
58 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
||
|
||
import {
|
||
KIND_HUDDLE_ENDED,
|
||
KIND_HUDDLE_STARTED,
|
||
} from "../../src/shared/constants/kinds";
|
||
import { truncateNpub } from "../../src/shared/lib/pubkey";
|
||
import { waitForAnimations } from "../helpers/animations";
|
||
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
||
|
||
const HUDDLE_CHANNEL_ID = "11111111-1111-4111-8111-111111111111";
|
||
const HUDDLE_PARENT_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
|
||
const HUDDLE_THREAD_ROOT_ID = "mock-general-welcome";
|
||
|
||
/**
|
||
* Initials a key-fallback avatar shows: the two characters just before the
|
||
* compact npub label's visible tail — the fragment `getInitials` reads from
|
||
* a key label, whose unit suite pins the derivation itself.
|
||
*/
|
||
function keyTailInitials(pubkey: string) {
|
||
return truncateNpub(pubkey).slice(-4, -2).toUpperCase();
|
||
}
|
||
|
||
async function waitForMockLiveSubscription(
|
||
page: import("@playwright/test").Page,
|
||
channelName: string,
|
||
kind?: number,
|
||
) {
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
({ kind, name }) =>
|
||
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
||
channelName: name,
|
||
kind,
|
||
}) ?? false,
|
||
{ kind, name: channelName },
|
||
),
|
||
)
|
||
.toBe(true);
|
||
}
|
||
|
||
async function setHuddleSnapshot(
|
||
page: import("@playwright/test").Page,
|
||
members: Array<{
|
||
pubkey: string;
|
||
role: "member" | "bot";
|
||
}>,
|
||
transcriptionEnabled: boolean,
|
||
) {
|
||
await page.evaluate(
|
||
async ({ nextMembers, nextTranscriptionEnabled }) => {
|
||
const setSnapshot = (
|
||
window as Window & {
|
||
__BUZZ_E2E_SET_MOCK_HUDDLE_SNAPSHOT__?: (input: {
|
||
members: Array<{
|
||
pubkey: string;
|
||
role: "member" | "bot";
|
||
}>;
|
||
transcriptionEnabled: boolean;
|
||
}) => Promise<void>;
|
||
}
|
||
).__BUZZ_E2E_SET_MOCK_HUDDLE_SNAPSHOT__;
|
||
if (!setSnapshot) {
|
||
throw new Error("Mock huddle snapshot control is not installed.");
|
||
}
|
||
await setSnapshot({
|
||
members: nextMembers,
|
||
transcriptionEnabled: nextTranscriptionEnabled,
|
||
});
|
||
},
|
||
{ nextMembers: members, nextTranscriptionEnabled: transcriptionEnabled },
|
||
);
|
||
}
|
||
|
||
async function installFakeHuddleMicrophone(
|
||
page: import("@playwright/test").Page,
|
||
) {
|
||
await page.addInitScript(() => {
|
||
const mediaDevices = navigator.mediaDevices ?? ({} as MediaDevices);
|
||
if (!navigator.mediaDevices) {
|
||
Object.defineProperty(navigator, "mediaDevices", {
|
||
configurable: true,
|
||
value: mediaDevices,
|
||
});
|
||
}
|
||
|
||
Object.defineProperty(mediaDevices, "enumerateDevices", {
|
||
configurable: true,
|
||
value: async () => [
|
||
{
|
||
deviceId: "mock-huddle-microphone",
|
||
groupId: "mock-huddle-audio",
|
||
kind: "audioinput",
|
||
label: "Mock Huddle Microphone",
|
||
toJSON() {
|
||
return this;
|
||
},
|
||
} as MediaDeviceInfo,
|
||
],
|
||
});
|
||
Object.defineProperty(mediaDevices, "getUserMedia", {
|
||
configurable: true,
|
||
value: async () => {
|
||
const context = new AudioContext({ sampleRate: 48_000 });
|
||
const oscillator = context.createOscillator();
|
||
const gain = context.createGain();
|
||
const destination = context.createMediaStreamDestination();
|
||
gain.gain.value = 0;
|
||
oscillator.connect(gain);
|
||
gain.connect(destination);
|
||
oscillator.start();
|
||
return destination.stream;
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
test("keeps the drawer open until the huddle is expanded", async ({ page }) => {
|
||
await page.addInitScript(() => {
|
||
window.localStorage.setItem("buzz-theme", "buzz-dark");
|
||
});
|
||
await installMockBridge(page, {
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
transcriptionEnabled: true,
|
||
},
|
||
});
|
||
|
||
await page.goto("/");
|
||
|
||
const gradientUnderlay = page.locator(".buzz-theme-gradient-underlay");
|
||
const openGradient = await gradientUnderlay.evaluate(
|
||
(element) => getComputedStyle(element).backgroundImage,
|
||
);
|
||
expect(openGradient).not.toBe("none");
|
||
|
||
const transcriptButton = page.getByRole("button", {
|
||
name: "Stop transcript",
|
||
});
|
||
await expect(transcriptButton).toBeVisible();
|
||
const huddleShell = page.locator(".buzz-huddle-shell");
|
||
await expect(huddleShell).toHaveAttribute("data-huddle-open", "true");
|
||
const huddleBackdrop = page.locator(".buzz-huddle-drawer-backdrop");
|
||
await expect(huddleBackdrop).toHaveClass(/buzz-huddle-drawer-backdrop-open/);
|
||
const [huddleBackdropColor, huddleDrawerColor] = await Promise.all([
|
||
huddleBackdrop.evaluate(
|
||
(element) => getComputedStyle(element).backgroundColor,
|
||
),
|
||
page
|
||
.locator(".buzz-huddle-drawer")
|
||
.first()
|
||
.evaluate((element) => getComputedStyle(element).backgroundColor),
|
||
]);
|
||
expect(huddleBackdropColor).toBe(huddleDrawerColor);
|
||
await expect
|
||
.poll(() =>
|
||
huddleBackdrop.evaluate((element) => {
|
||
const shell = element.parentElement;
|
||
const appSurface = shell?.querySelector(".buzz-huddle-app-surface");
|
||
if (!appSurface) return false;
|
||
const backdropStyle = getComputedStyle(element);
|
||
const appStyle = getComputedStyle(appSurface);
|
||
return Number(backdropStyle.zIndex) < Number(appStyle.zIndex);
|
||
}),
|
||
)
|
||
.toBe(true);
|
||
const drawerAgentMenu = page.getByRole("button", {
|
||
name: "Voice settings for alice",
|
||
});
|
||
await expect(
|
||
drawerAgentMenu.getByTestId("huddle-participant-avatar"),
|
||
).toBeVisible();
|
||
await drawerAgentMenu.click();
|
||
await expect(
|
||
page.getByText("Agent text-to-speech", { exact: true }),
|
||
).toBeVisible();
|
||
await expect(page.getByText("Agent voice", { exact: true })).toBeVisible();
|
||
await expect(
|
||
page.getByRole("button", { name: "Remove alice from huddle" }),
|
||
).toBeVisible();
|
||
await page.keyboard.press("Escape");
|
||
await expect(page.getByTestId("profile-huddle-control")).toHaveCount(0);
|
||
await page
|
||
.getByRole("button", { name: "Open huddle in a new window" })
|
||
.click();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "open_huddle_window",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(1);
|
||
const huddleControl = page.getByTestId("profile-huddle-control");
|
||
await expect(huddleControl).toBeVisible();
|
||
const mainContentSurface = page.locator(
|
||
"[data-buzz-content-surface]:not([data-buzz-content-unframed])",
|
||
);
|
||
await expect(mainContentSurface).toBeVisible();
|
||
const [mainContentColor, huddleCardColor] = await Promise.all([
|
||
mainContentSurface.evaluate(
|
||
(element) => getComputedStyle(element).backgroundColor,
|
||
),
|
||
huddleControl.evaluate(
|
||
(element) => getComputedStyle(element).backgroundColor,
|
||
),
|
||
]);
|
||
expect(huddleCardColor).toBe(mainContentColor);
|
||
await expect(huddleControl).toHaveCSS("border-top-width", "1px");
|
||
await expect(huddleControl).toContainText("In a huddle");
|
||
await expect(huddleControl).toContainText("#general");
|
||
const compactMicButton = huddleControl.getByRole("button", {
|
||
name: /microphone/i,
|
||
});
|
||
await expect(compactMicButton).toBeVisible();
|
||
await expect(compactMicButton).toHaveClass(/bg-destructive\/15/);
|
||
await expect(compactMicButton).toHaveClass(/text-destructive/);
|
||
await expect(
|
||
huddleControl.getByRole("button", { name: "Audio settings" }),
|
||
).not.toHaveClass(/bg-destructive\/15/);
|
||
const [micContainerRadius, micHoverRadius] = await Promise.all([
|
||
compactMicButton
|
||
.locator("..")
|
||
.evaluate((element) => getComputedStyle(element).borderTopLeftRadius),
|
||
compactMicButton.evaluate(
|
||
(element) => getComputedStyle(element).borderTopLeftRadius,
|
||
),
|
||
]);
|
||
expect(micHoverRadius).toBe(micContainerRadius);
|
||
await expect(huddleControl).toContainText("Leave");
|
||
await expect(
|
||
huddleControl.getByRole("button", { name: /huddle speaker/i }),
|
||
).toHaveCount(0);
|
||
await expect(transcriptButton).toHaveCount(0);
|
||
await huddleControl
|
||
.getByRole("button", { name: "Open huddle window" })
|
||
.click();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "open_huddle_window",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(2);
|
||
await huddleControl.getByRole("button", { name: "Leave huddle" }).click();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "leave_huddle",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(1);
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"false",
|
||
);
|
||
const closedGradient = await gradientUnderlay.evaluate(
|
||
(element) => getComputedStyle(element).backgroundImage,
|
||
);
|
||
expect(closedGradient).toBe(openGradient);
|
||
});
|
||
|
||
test("floats the in-app huddle tray over the glass background", async ({
|
||
page,
|
||
}) => {
|
||
await page.addInitScript(() => {
|
||
window.localStorage.setItem("buzz-theme", "buzz-dark");
|
||
window.localStorage.setItem("buzz-glass-background", "true");
|
||
(window as typeof window & { isTauri?: boolean }).isTauri = true;
|
||
Object.defineProperty(navigator, "platform", {
|
||
configurable: true,
|
||
get: () => "MacIntel",
|
||
});
|
||
});
|
||
await installMockBridge(page, {
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
transcriptionEnabled: true,
|
||
},
|
||
});
|
||
|
||
await page.goto("/");
|
||
|
||
const root = page.locator("html");
|
||
const shell = page.locator('.buzz-huddle-shell[data-huddle-window="false"]');
|
||
const slot = shell.locator(".buzz-huddle-drawer-slot");
|
||
const drawer = slot.locator(":scope > .buzz-huddle-drawer");
|
||
const backdrop = shell.locator(".buzz-huddle-drawer-backdrop");
|
||
const transcriptButton = drawer.getByRole("button", {
|
||
name: "Stop transcript",
|
||
});
|
||
|
||
await expect(root).toHaveAttribute("data-glass-background", "");
|
||
await expect(shell).toHaveAttribute("data-huddle-open", "true");
|
||
await expect(shell).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||
await expect(backdrop).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||
await expect(drawer).toHaveCSS("border-top-left-radius", "16px");
|
||
await expect(drawer).toHaveCSS("border-top-right-radius", "16px");
|
||
await expect(drawer).toHaveCSS("border-bottom-right-radius", "16px");
|
||
await expect(drawer).toHaveCSS("border-bottom-left-radius", "16px");
|
||
await expect(drawer).toHaveCSS("padding-top", "8px");
|
||
await expect(drawer).toHaveCSS("padding-right", "8px");
|
||
await expect(drawer).toHaveCSS("padding-bottom", "8px");
|
||
await expect(drawer).toHaveCSS("padding-left", "8px");
|
||
|
||
const geometry = await Promise.all([
|
||
slot.boundingBox(),
|
||
drawer.boundingBox(),
|
||
transcriptButton.boundingBox(),
|
||
]);
|
||
const [slotBox, drawerBox, transcriptButtonBox] = geometry;
|
||
expect(slotBox).not.toBeNull();
|
||
expect(drawerBox).not.toBeNull();
|
||
expect(transcriptButtonBox).not.toBeNull();
|
||
if (!slotBox || !drawerBox || !transcriptButtonBox) return;
|
||
expect(drawerBox.x - slotBox.x).toBe(8);
|
||
expect(drawerBox.y - slotBox.y).toBe(8);
|
||
expect(slotBox.x + slotBox.width - (drawerBox.x + drawerBox.width)).toBe(8);
|
||
expect(slotBox.y + slotBox.height - (drawerBox.y + drawerBox.height)).toBe(8);
|
||
expect(
|
||
Math.abs(
|
||
transcriptButtonBox.y +
|
||
transcriptButtonBox.height / 2 -
|
||
(drawerBox.y + drawerBox.height / 2),
|
||
),
|
||
).toBeLessThanOrEqual(1);
|
||
});
|
||
|
||
test("keeps the popped-out huddle dock full-width over glass", async ({
|
||
page,
|
||
}) => {
|
||
await page.addInitScript(() => {
|
||
window.localStorage.setItem("buzz-theme", "buzz-dark");
|
||
window.localStorage.setItem("buzz-glass-background", "true");
|
||
(window as typeof window & { isTauri?: boolean }).isTauri = true;
|
||
Object.defineProperty(navigator, "platform", {
|
||
configurable: true,
|
||
get: () => "MacIntel",
|
||
});
|
||
});
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
transcriptionEnabled: true,
|
||
},
|
||
});
|
||
|
||
await page.goto("/");
|
||
|
||
const root = page.locator("html");
|
||
const shell = page.locator('.buzz-huddle-shell[data-huddle-window="true"]');
|
||
const slot = shell.locator(".buzz-huddle-drawer-slot");
|
||
const drawer = slot.locator(":scope > .buzz-huddle-drawer");
|
||
|
||
await expect(root).toHaveAttribute("data-glass-background", "");
|
||
await expect(shell).toHaveAttribute("data-huddle-open", "true");
|
||
await expect(shell).not.toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||
await expect(slot).toHaveCSS("padding-top", "0px");
|
||
await expect(drawer).toHaveCSS("border-top-left-radius", "0px");
|
||
await expect(drawer).toHaveCSS("border-top-right-radius", "0px");
|
||
});
|
||
|
||
test("shows speaker identity on every huddle chat message", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
huddleThreadEventId: HUDDLE_THREAD_ROOT_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
transcriptionEnabled: true,
|
||
},
|
||
mock: { threadRepliesDelayMs: 1_500 },
|
||
});
|
||
await page.goto("/");
|
||
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() => window.__TAURI_INTERNALS__?.metadata.currentWindow.label,
|
||
),
|
||
)
|
||
.toBe(`huddle-${HUDDLE_CHANNEL_ID}`);
|
||
await expect(page.getByTestId("huddle-transcript-intro")).toBeVisible();
|
||
await expect(page.getByTestId("message-thread-replies-loading")).toHaveCount(
|
||
0,
|
||
);
|
||
const transcriptTopGap = await page.evaluate(() => {
|
||
const participantStrip = document.querySelector(
|
||
'[data-testid="huddle-participant-strip"]',
|
||
);
|
||
const participantTray = participantStrip?.closest("header");
|
||
const transcriptIntro = document.querySelector(
|
||
'[data-testid="huddle-transcript-intro"]',
|
||
);
|
||
if (!participantTray || !transcriptIntro) return null;
|
||
return Math.round(
|
||
transcriptIntro.getBoundingClientRect().top -
|
||
participantTray.getBoundingClientRect().bottom,
|
||
);
|
||
});
|
||
expect(transcriptTopGap).toBe(16);
|
||
await waitForMockLiveSubscription(page, "huddle");
|
||
await page.evaluate(() => {
|
||
const threadRoot = window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||
channelName: "huddle",
|
||
content: "First huddle message",
|
||
});
|
||
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||
channelName: "huddle",
|
||
content: "Second huddle message",
|
||
});
|
||
if (!threadRoot) throw new Error("Failed to create Huddle thread root.");
|
||
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||
channelName: "huddle",
|
||
content: "Agent response flattened into the huddle chat",
|
||
parentEventId: threadRoot.id,
|
||
pubkey: "deadbeef".repeat(8),
|
||
});
|
||
});
|
||
|
||
for (const content of [
|
||
"First huddle message",
|
||
"Second huddle message",
|
||
"Agent response flattened into the huddle chat",
|
||
]) {
|
||
const row = page.getByTestId("message-row").filter({ hasText: content });
|
||
await expect(row).toBeVisible();
|
||
await expect(row.locator('[data-testid^="message-avatar-"]')).toBeVisible();
|
||
await expect(row.getByTestId("message-author")).toBeVisible();
|
||
await expect(row.getByTestId("message-timestamp")).toBeVisible();
|
||
}
|
||
await expect(page.getByTestId("message-thread-panel")).toHaveCount(0);
|
||
});
|
||
|
||
test("ignores persisted community onboarding in the huddle room", async ({
|
||
page,
|
||
}) => {
|
||
const persistedTransaction = {
|
||
id: "main-window-community-transaction",
|
||
source: "deep-link-join",
|
||
stage: "claiming",
|
||
relayUrl: "wss://other.example",
|
||
inviteCode: "main-window-invite",
|
||
communityName: "Other community",
|
||
createdAt: "2026-08-03T20:00:00.000Z",
|
||
updatedAt: "2026-08-03T20:00:00.000Z",
|
||
};
|
||
await page.addInitScript((transaction) => {
|
||
window.localStorage.setItem(
|
||
"buzz-community-onboarding-transaction.v1",
|
||
JSON.stringify(transaction),
|
||
);
|
||
}, persistedTransaction);
|
||
const claimRequests: string[] = [];
|
||
page.on("request", (request) => {
|
||
if (request.url().includes("/api/invites/claim")) {
|
||
claimRequests.push(request.url());
|
||
}
|
||
});
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
});
|
||
|
||
for (let load = 0; load < 2; load += 1) {
|
||
await page.goto("/");
|
||
await expect(page.getByTestId("huddle-transcript-intro")).toBeVisible();
|
||
await expect(
|
||
page.getByText("Setting up your community", { exact: true }),
|
||
).toHaveCount(0);
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() => {
|
||
const raw = window.localStorage.getItem(
|
||
"buzz-community-onboarding-transaction.v1",
|
||
);
|
||
return raw ? JSON.parse(raw) : null;
|
||
}),
|
||
)
|
||
.toEqual(persistedTransaction);
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() =>
|
||
JSON.parse(window.localStorage.getItem("buzz-communities") ?? "[]"),
|
||
),
|
||
)
|
||
.not.toContainEqual(
|
||
expect.objectContaining({ relayUrl: persistedTransaction.relayUrl }),
|
||
);
|
||
expect(claimRequests).toHaveLength(0);
|
||
}
|
||
});
|
||
|
||
test("keeps main-app shortcuts from navigating the huddle room", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
pendingCommunityDeepLinks: [
|
||
{
|
||
id: "companion-must-not-consume",
|
||
kind: "connect",
|
||
relayUrl: "wss://other.example",
|
||
},
|
||
],
|
||
});
|
||
await page.goto("/");
|
||
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/channels/${HUDDLE_CHANNEL_ID.replaceAll("-", "\\-")}`),
|
||
);
|
||
const huddleUrl = page.url();
|
||
const primaryModifier = process.platform === "darwin" ? "Meta" : "Control";
|
||
|
||
for (const shortcut of ["Shift+K", "Shift+A", "Comma"]) {
|
||
await page.keyboard.press(`${primaryModifier}+${shortcut}`);
|
||
await expect.poll(() => page.url()).toBe(huddleUrl);
|
||
await expect(page.getByTestId("settings-view")).toHaveCount(0);
|
||
}
|
||
|
||
await page.evaluate(async (channelId) => {
|
||
await window.__BUZZ_E2E_EMIT_TAURI_EVENT__?.("deep-link-message", {
|
||
channelId,
|
||
messageId: "mock-general-welcome",
|
||
threadRootId: null,
|
||
});
|
||
}, HUDDLE_PARENT_ID);
|
||
await expect.poll(() => page.url()).toBe(huddleUrl);
|
||
|
||
const pendingCommunityLink = {
|
||
code: null,
|
||
id: "companion-must-not-consume",
|
||
kind: "connect" as const,
|
||
name: null,
|
||
relayUrl: "wss://other.example",
|
||
};
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_EMIT_TAURI_EVENT__?.("deep-link-connect", null);
|
||
});
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(async () =>
|
||
window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
||
"take_pending_community_deep_link",
|
||
),
|
||
),
|
||
)
|
||
.toEqual(pendingCommunityLink);
|
||
});
|
||
|
||
test("speaks the first eligible agent reply with its participant identity", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
ttsEnabled: true,
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
await waitForMockLiveSubscription(page, "huddle");
|
||
|
||
await page.evaluate((agentPubkey) => {
|
||
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||
channelName: "huddle",
|
||
content: "This first reply should be spoken.",
|
||
pubkey: agentPubkey,
|
||
});
|
||
}, TEST_IDENTITIES.alice.pubkey);
|
||
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).find(
|
||
(entry) => entry.command === "speak_agent_message",
|
||
)?.payload,
|
||
),
|
||
)
|
||
.toMatchObject({
|
||
text: "This first reply should be spoken.",
|
||
speakerPubkey: TEST_IDENTITIES.alice.pubkey,
|
||
});
|
||
await expect(
|
||
page.getByRole("img", {
|
||
name: "Anyone can send instructions to this agent",
|
||
}),
|
||
).toHaveCount(0);
|
||
});
|
||
|
||
test("animates the responding agent with the shared speaker ring", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
|
||
const agentAvatar = page
|
||
.getByTestId("huddle-participant-strip")
|
||
.locator(".buzz-huddle-speaking-avatar")
|
||
.nth(1);
|
||
await expect(agentAvatar).toBeVisible();
|
||
await page.evaluate(async (pubkey) => {
|
||
await window.__BUZZ_E2E_EMIT_MOCK_HUDDLE_TTS_SPEAKER__?.({
|
||
pubkey,
|
||
level: 0.8,
|
||
});
|
||
}, TEST_IDENTITIES.alice.pubkey);
|
||
await expect
|
||
.poll(() =>
|
||
agentAvatar.evaluate((element) =>
|
||
element.style.getPropertyValue("--buzz-huddle-speaker-opacity"),
|
||
),
|
||
)
|
||
.toBe("0.890");
|
||
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_EMIT_MOCK_HUDDLE_TTS_SPEAKER__?.({
|
||
pubkey: null,
|
||
level: 0,
|
||
});
|
||
});
|
||
await expect
|
||
.poll(() =>
|
||
agentAvatar.evaluate((element) =>
|
||
element.style.getPropertyValue("--buzz-huddle-speaker-opacity"),
|
||
),
|
||
)
|
||
.toBe("0");
|
||
});
|
||
|
||
test("stops the speaking agent from the huddle controls", async ({ page }) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
const roomMicButton = page.getByRole("button", {
|
||
name: "Microphone unavailable",
|
||
});
|
||
await expect(roomMicButton).toHaveClass(/bg-destructive\/35/);
|
||
await expect(roomMicButton).toHaveClass(/text-destructive/);
|
||
await expect(
|
||
page.getByRole("button", { name: "Audio settings" }),
|
||
).not.toHaveClass(/bg-destructive\/35/);
|
||
const roomSpeakerButton = page.getByRole("button", {
|
||
name: "Unmute agent speech",
|
||
});
|
||
await expect(roomSpeakerButton).toHaveClass(/bg-destructive\/35/);
|
||
await expect(roomSpeakerButton).toHaveClass(/text-destructive/);
|
||
await expect(
|
||
page.getByRole("button", { name: "Speaker settings" }),
|
||
).not.toHaveClass(/bg-destructive\/35/);
|
||
await waitForAnimations(page);
|
||
const agentTile = page
|
||
.getByTestId("huddle-participant-strip")
|
||
.getByTestId("huddle-participant-tile")
|
||
.nth(1);
|
||
const agentAvatar = page
|
||
.getByTestId("huddle-participant-strip")
|
||
.locator(".buzz-huddle-speaking-avatar")
|
||
.nth(1);
|
||
const labelSlots = page.getByTestId("huddle-participant-label-slot");
|
||
await expect(agentAvatar).toBeVisible();
|
||
await waitForAnimations(page);
|
||
const idleTileHeight = await agentTile.evaluate(
|
||
(element) => element.offsetHeight,
|
||
);
|
||
const idleAgentLabelBox = await labelSlots.nth(1).boundingBox();
|
||
const humanLabelBox = await labelSlots.nth(0).boundingBox();
|
||
expect(humanLabelBox?.y).toBe(idleAgentLabelBox?.y);
|
||
expect(humanLabelBox?.height).toBe(idleAgentLabelBox?.height);
|
||
expect(humanLabelBox?.width).toBe(idleAgentLabelBox?.width);
|
||
await page.evaluate(async (pubkey) => {
|
||
await window.__BUZZ_E2E_EMIT_MOCK_HUDDLE_TTS_SPEAKER__?.({
|
||
pubkey,
|
||
level: 0.8,
|
||
});
|
||
}, TEST_IDENTITIES.alice.pubkey);
|
||
await expect
|
||
.poll(() =>
|
||
agentAvatar.evaluate((element) =>
|
||
element.style.getPropertyValue("--buzz-huddle-speaker-opacity"),
|
||
),
|
||
)
|
||
.toBe("0.890");
|
||
|
||
const stopButton = page.getByRole("button", { name: /^Stop .* speaking$/ });
|
||
await expect(stopButton).toBeVisible();
|
||
await expect(stopButton).toHaveClass(/bg-destructive\/15/);
|
||
expect(await labelSlots.nth(1).boundingBox()).toEqual(idleAgentLabelBox);
|
||
expect(await agentTile.evaluate((element) => element.offsetHeight)).toBe(
|
||
idleTileHeight,
|
||
);
|
||
await stopButton.click();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).findLast(
|
||
(entry) => entry.command === "interrupt_huddle_speech",
|
||
)?.payload ?? null,
|
||
),
|
||
)
|
||
.toEqual({ agentPubkey: TEST_IDENTITIES.alice.pubkey });
|
||
});
|
||
|
||
test("assigns distinct agent voices and exposes compact per-agent controls", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
ttsSettings: {
|
||
version: 1,
|
||
agentTextToSpeech: true,
|
||
voicePreferences: ["pocket:vera"],
|
||
},
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
{ pubkey: TEST_IDENTITIES.bob.pubkey, role: "bot" },
|
||
],
|
||
ttsEnabled: true,
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
|
||
const voiceMenus = page.getByTestId("huddle-agent-voice-menu-trigger");
|
||
await expect(voiceMenus).toHaveCount(2);
|
||
await expect
|
||
.poll(async () => {
|
||
const state = (await page.evaluate(() =>
|
||
window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("get_huddle_state"),
|
||
)) as {
|
||
agent_voice_settings: Record<
|
||
string,
|
||
{ enabled: boolean; voice_key: string }
|
||
>;
|
||
};
|
||
return state.agent_voice_settings;
|
||
})
|
||
.toMatchObject({
|
||
[TEST_IDENTITIES.alice.pubkey]: {
|
||
enabled: true,
|
||
voice_key: "pocket:vera",
|
||
},
|
||
});
|
||
const assignedVoices = await page.evaluate(async () => {
|
||
const state = (await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
||
"get_huddle_state",
|
||
)) as {
|
||
agent_voice_settings: Record<string, { voice_key: string }>;
|
||
};
|
||
return Object.values(state.agent_voice_settings).map(
|
||
(settings) => settings.voice_key,
|
||
);
|
||
});
|
||
expect(new Set(assignedVoices).size).toBe(2);
|
||
|
||
await page.getByRole("button", { name: "Voice settings for alice" }).click();
|
||
await waitForAnimations(page);
|
||
const voiceMenu = page.locator(
|
||
'[data-testid="huddle-agent-voice-menu-content"][data-state="open"]',
|
||
);
|
||
await expect(voiceMenu).toBeVisible();
|
||
await expect(
|
||
voiceMenu.getByText("Agent text-to-speech", { exact: true }),
|
||
).toBeVisible();
|
||
const ttsToggle = voiceMenu.getByTestId("huddle-agent-tts-toggle");
|
||
await expect(ttsToggle).toBeChecked();
|
||
await expect(
|
||
voiceMenu.getByTestId("huddle-agent-voice-selector"),
|
||
).toContainText("Vera");
|
||
|
||
await ttsToggle.click();
|
||
await expect(
|
||
voiceMenu.getByTestId("huddle-agent-voice-selector"),
|
||
).toHaveCount(0);
|
||
await ttsToggle.click();
|
||
await expect(ttsToggle).toBeChecked();
|
||
const voiceSelector = voiceMenu.getByTestId("huddle-agent-voice-selector");
|
||
await expect(voiceSelector).toBeEnabled();
|
||
await voiceSelector.click();
|
||
await page.getByRole("menuitemradio", { name: "Jane" }).click();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() => {
|
||
const updates = (window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "set_huddle_agent_voice",
|
||
);
|
||
return updates.at(-1)?.payload ?? null;
|
||
}),
|
||
)
|
||
.toMatchObject({
|
||
agentPubkey: TEST_IDENTITIES.alice.pubkey,
|
||
voiceKey: "pocket:jane",
|
||
});
|
||
});
|
||
|
||
test("adds channel-mentioned agents to the live huddle roster", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
|
||
const participantTiles = page.getByTestId("huddle-participant-tile");
|
||
await expect(participantTiles).toHaveCount(2);
|
||
const result = await page.evaluate(
|
||
async ({ channelId, pubkey }) =>
|
||
window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
||
"sync_agents_to_active_huddle",
|
||
{
|
||
channelId,
|
||
agentPubkeys: [pubkey],
|
||
},
|
||
),
|
||
{
|
||
channelId: HUDDLE_PARENT_ID,
|
||
pubkey: TEST_IDENTITIES.bob.pubkey,
|
||
},
|
||
);
|
||
|
||
expect(result).toEqual({
|
||
matched_active_huddle: true,
|
||
added: [TEST_IDENTITIES.bob.pubkey],
|
||
});
|
||
await expect(participantTiles).toHaveCount(3);
|
||
await expect(
|
||
page.getByRole("button", { name: "Voice settings for Bob" }),
|
||
).toBeVisible();
|
||
});
|
||
|
||
test("does not enroll available agents when sending an ordinary message", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
managedAgents: [
|
||
{
|
||
pubkey: TEST_IDENTITIES.alice.pubkey,
|
||
name: "alice",
|
||
status: "running",
|
||
},
|
||
{
|
||
pubkey: TEST_IDENTITIES.bob.pubkey,
|
||
name: "bob",
|
||
status: "running",
|
||
},
|
||
],
|
||
});
|
||
await page.goto("/");
|
||
|
||
const participantTiles = page.getByTestId("huddle-participant-tile");
|
||
await expect(participantTiles).toHaveCount(1);
|
||
await page.getByTestId("message-input").fill("A message for the room");
|
||
await page.getByTestId("send-message").click();
|
||
|
||
await expect(
|
||
page
|
||
.getByTestId("message-row")
|
||
.filter({ hasText: "A message for the room" }),
|
||
).toBeVisible();
|
||
await expect(participantTiles).toHaveCount(1);
|
||
expect(
|
||
await page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "sync_agents_to_active_huddle",
|
||
).length,
|
||
),
|
||
).toBe(0);
|
||
});
|
||
|
||
test("keeps the colored startup surface while huddle controls connect", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
phase: "connected",
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
|
||
await expect(page.getByTestId("huddle-starting-view")).toBeVisible();
|
||
await expect(
|
||
page.getByRole("status", { name: "Starting huddle" }),
|
||
).toBeVisible();
|
||
await expect(
|
||
page.getByTestId("huddle-starting-view").locator(".bee-sprite"),
|
||
).toBeVisible();
|
||
await expect(page.getByTestId("setup-grainient-background")).toBeVisible();
|
||
await expect(
|
||
page.getByRole("button", { name: "Leave huddle" }),
|
||
).toBeVisible();
|
||
await expect(page.getByTestId("message-thread-loading")).toHaveCount(0);
|
||
await expect(page.getByTestId("huddle-participant-strip")).toHaveCount(0);
|
||
await expect(page.getByTestId("huddle-transcript-intro")).toHaveCount(0);
|
||
});
|
||
|
||
test("returns the companion transcript to the same channel in the main app", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
huddleThreadEventId: HUDDLE_THREAD_ROOT_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
transcriptionEnabled: true,
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"true",
|
||
);
|
||
await page.evaluate(() => {
|
||
const threadRoot = window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||
channelName: "huddle",
|
||
content: "Transcript written while the companion is open",
|
||
});
|
||
if (!threadRoot) throw new Error("Failed to create Huddle thread root.");
|
||
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||
channelName: "huddle",
|
||
content: "Agent reply preserved across the Huddle handoff",
|
||
parentEventId: threadRoot.id,
|
||
pubkey: "deadbeef".repeat(8),
|
||
});
|
||
});
|
||
|
||
await page
|
||
.getByRole("button", { name: "Open huddle in a new window" })
|
||
.click();
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"false",
|
||
);
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("close_huddle_companion");
|
||
});
|
||
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/channels/${HUDDLE_CHANNEL_ID.replaceAll("-", "\\-")}`),
|
||
);
|
||
await expect(
|
||
page
|
||
.getByTestId("message-row")
|
||
.filter({ hasText: "Transcript written while the companion is open" }),
|
||
).toBeVisible();
|
||
await expect(
|
||
page
|
||
.getByTestId("message-row")
|
||
.filter({ hasText: "Agent reply preserved across the Huddle handoff" }),
|
||
).toBeVisible();
|
||
await expect(page.getByTestId("message-thread-panel")).toHaveCount(0);
|
||
|
||
await page
|
||
.getByRole("button", { name: "Open huddle in a new window" })
|
||
.click();
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/channels/${HUDDLE_PARENT_ID.replaceAll("-", "\\-")}`),
|
||
);
|
||
await expect(
|
||
page.locator(`[data-channel-id="${HUDDLE_CHANNEL_ID}"]`),
|
||
).toHaveCount(0);
|
||
});
|
||
|
||
test("returns to the parent channel when leaving a huddle channel in view", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"true",
|
||
);
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("close_huddle_companion");
|
||
});
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/channels/${HUDDLE_CHANNEL_ID.replaceAll("-", "\\-")}`),
|
||
);
|
||
|
||
await page.getByRole("button", { name: "Leave huddle" }).click();
|
||
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/channels/${HUDDLE_PARENT_ID.replaceAll("-", "\\-")}`),
|
||
);
|
||
await expect(
|
||
page.locator(`[data-channel-id="${HUDDLE_CHANNEL_ID}"]`),
|
||
).toHaveCount(0);
|
||
});
|
||
|
||
test("keeps the huddle avatar strip compact and exposes the full roster", async ({
|
||
page,
|
||
}) => {
|
||
// One named member plus ten key-only members (hex 01..0a + "a"×62), with
|
||
// the last as the huddle bot. The generated keys' npub tails are what
|
||
// key-fallback avatars abbreviate: 01… → npub1qx4…x2tc → X2,
|
||
// 02… → npub1q24…levs → LE, 0a… → npub1p24…sqjt → SQ.
|
||
const namedMember = "77".repeat(32);
|
||
const unnamedMembers = Array.from({ length: 10 }, (_, index) => ({
|
||
pubkey: `${(index + 1).toString(16).padStart(2, "0")}${"a".repeat(62)}`,
|
||
role: index === 9 ? ("bot" as const) : ("member" as const),
|
||
}));
|
||
const members = [
|
||
{ pubkey: namedMember, role: "member" as const },
|
||
...unnamedMembers,
|
||
];
|
||
|
||
await installMockBridge(page, {
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members,
|
||
},
|
||
searchProfiles: [{ pubkey: namedMember, displayName: "Ada Lovelace" }],
|
||
});
|
||
await page.goto("/");
|
||
|
||
const participantStrip = page.getByTestId("huddle-participant-strip");
|
||
const participantTrigger = page.getByRole("button", {
|
||
name: "Show all huddle participants (11)",
|
||
});
|
||
await expect(participantTrigger).toBeVisible();
|
||
await expect(
|
||
participantStrip.getByTestId("huddle-participant-avatar"),
|
||
).toHaveCount(9);
|
||
await expect(participantTrigger).toContainText("+2");
|
||
await expect(page.getByTestId("profile-huddle-control")).toHaveCount(0);
|
||
|
||
// Strip avatars stay distinct: the named member keeps name initials
|
||
// (AL), and a key-only participant shows its npub's tail (X2), never the
|
||
// word initials of the "Participant npub1…" label (PN).
|
||
const stripAvatars = participantStrip.getByTestId(
|
||
"huddle-participant-avatar",
|
||
);
|
||
await expect(stripAvatars.first()).toHaveText("AL");
|
||
await expect(stripAvatars.nth(1)).toHaveText(
|
||
keyTailInitials(unnamedMembers[0].pubkey),
|
||
);
|
||
|
||
await participantTrigger.click();
|
||
const roster = page
|
||
.getByRole("dialog")
|
||
.filter({ has: page.getByRole("heading", { name: "Participants" }) });
|
||
await expect(
|
||
page.getByRole("heading", { name: "Participants" }),
|
||
).toBeVisible();
|
||
const rosterRows = roster.getByRole("listitem");
|
||
// Visible labels keep their generated forms alongside the avatar initials.
|
||
await expect(rosterRows.filter({ hasText: "Ada Lovelace" })).toHaveCount(1);
|
||
await expect(
|
||
rosterRows.filter({
|
||
hasText: `Participant ${truncateNpub(unnamedMembers[0].pubkey)}`,
|
||
}),
|
||
).toHaveCount(1);
|
||
const agentRow = rosterRows.filter({
|
||
hasText: `Agent ${truncateNpub(unnamedMembers[9].pubkey)}`,
|
||
});
|
||
await expect(agentRow).toHaveCount(1);
|
||
await expect(
|
||
page.getByRole("button", { name: /Remove Agent .* from huddle/ }),
|
||
).toHaveCount(1);
|
||
// The roster's agent avatar abbreviates its key tail (SQ), never the
|
||
// "Agent" word initials of the prefixed label (AN).
|
||
await expect(agentRow.getByTestId("huddle-participant-avatar")).toHaveText(
|
||
keyTailInitials(unnamedMembers[9].pubkey),
|
||
);
|
||
});
|
||
|
||
test("removes an agent from its menu without showing an extra participant control", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
windowLabel: `huddle-${HUDDLE_CHANNEL_ID}`,
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
},
|
||
});
|
||
await page.goto("/");
|
||
|
||
await expect(
|
||
page.getByRole("button", { name: "Manage huddle participants" }),
|
||
).toHaveCount(0);
|
||
|
||
await page.getByTestId("huddle-agent-voice-menu-trigger").click();
|
||
const removeButton = page.getByRole("button", {
|
||
name: "Remove alice from huddle",
|
||
});
|
||
await expect(removeButton).toBeVisible();
|
||
page.once("dialog", (dialog) => dialog.accept());
|
||
await removeButton.click({ force: true });
|
||
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).some(
|
||
(entry) => entry.command === "remove_agent_from_huddle",
|
||
),
|
||
),
|
||
)
|
||
.toBe(true);
|
||
await expect(page.getByTestId("huddle-agent-voice-menu-trigger")).toHaveCount(
|
||
0,
|
||
);
|
||
});
|
||
|
||
test("keeps a newer huddle event over a delayed hydration snapshot", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [
|
||
{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" },
|
||
{ pubkey: TEST_IDENTITIES.alice.pubkey, role: "bot" },
|
||
],
|
||
transcriptionEnabled: true,
|
||
},
|
||
huddleStateReadDelayMs: 250,
|
||
});
|
||
await page.goto("/");
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).some(
|
||
(entry) => entry.command === "get_huddle_state",
|
||
),
|
||
),
|
||
)
|
||
.toBe(true);
|
||
|
||
await setHuddleSnapshot(
|
||
page,
|
||
[{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
false,
|
||
);
|
||
|
||
await expect(
|
||
page
|
||
.getByTestId("huddle-participant-strip")
|
||
.getByTestId("huddle-participant-avatar"),
|
||
).toHaveCount(1);
|
||
await expect(
|
||
page.getByRole("button", { name: /Show all huddle participants/ }),
|
||
).toHaveCount(0);
|
||
await expect(
|
||
page.getByRole("button", { name: "Start transcript" }),
|
||
).toHaveAttribute("aria-pressed", "false");
|
||
await page.waitForTimeout(300);
|
||
await expect(
|
||
page
|
||
.getByTestId("huddle-participant-strip")
|
||
.getByTestId("huddle-participant-avatar"),
|
||
).toHaveCount(1);
|
||
});
|
||
|
||
test("keeps a starting huddle in the drawer after its companion closes", async ({
|
||
page,
|
||
}) => {
|
||
await installFakeHuddleMicrophone(page);
|
||
await installMockBridge(page, {
|
||
openHuddleWindowDelayMs: 500,
|
||
startHuddleReturnDelayMs: 1_500,
|
||
});
|
||
await page.goto("/");
|
||
await page.getByTestId("channel-alice-tyler").click();
|
||
await page.getByTestId("channel-start-huddle-trigger").click();
|
||
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "open_huddle_window",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(1);
|
||
const ephemeralChannelId = await page.evaluate(async () => {
|
||
const state = (await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
||
"get_huddle_state",
|
||
)) as { ephemeral_channel_id: string };
|
||
return state.ephemeral_channel_id;
|
||
});
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("close_huddle_companion");
|
||
});
|
||
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"true",
|
||
);
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/channels/${ephemeralChannelId.replaceAll("-", "\\-")}`),
|
||
);
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(async () => {
|
||
const state = (await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
||
"get_huddle_state",
|
||
)) as { phase: string };
|
||
return state.phase;
|
||
}),
|
||
)
|
||
.toBe("active");
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "open_huddle_window",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(1);
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"true",
|
||
);
|
||
await expect(page.getByTestId("profile-huddle-control")).toHaveCount(0);
|
||
await expect(
|
||
page.locator(`[data-channel-id="${ephemeralChannelId}"]`),
|
||
).toBeVisible();
|
||
});
|
||
|
||
test("starts muted with Push to Talk while preserving manual microphone control", async ({
|
||
page,
|
||
}) => {
|
||
await installFakeHuddleMicrophone(page);
|
||
await installMockBridge(page, {
|
||
openHuddleWindowDelayMs: 10_000,
|
||
startHuddleReturnDelayMs: 1_500,
|
||
});
|
||
await page.goto("/");
|
||
await page.getByTestId("channel-alice-tyler").click();
|
||
await page.getByTestId("channel-start-huddle-trigger").click();
|
||
|
||
// Push to Talk huddles start muted (matching the Rust
|
||
// manual_mic_unmuted default) so nothing is hot until the user opts in.
|
||
const muteButton = page.getByRole("button", { name: "Mute microphone" });
|
||
const unmuteButton = page.getByRole("button", {
|
||
name: "Unmute microphone",
|
||
});
|
||
await expect(unmuteButton).toBeVisible();
|
||
await expect(unmuteButton).toHaveClass(/bg-destructive\/15/);
|
||
await expect(unmuteButton).toHaveClass(/text-destructive/);
|
||
|
||
// Manually unmuting hands the mic back to the user.
|
||
await unmuteButton.click();
|
||
await expect(muteButton).toBeVisible();
|
||
await expect(muteButton).not.toHaveClass(/bg-destructive\/15/);
|
||
await expect(
|
||
page.getByRole("button", { name: "Audio settings" }),
|
||
).not.toHaveClass(/bg-destructive\/15/);
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).findLast(
|
||
(entry) => entry.command === "set_huddle_manual_mic_unmuted",
|
||
)?.payload,
|
||
),
|
||
)
|
||
.toEqual({ enabled: true });
|
||
|
||
await muteButton.click();
|
||
await expect(unmuteButton).toBeVisible();
|
||
await expect(unmuteButton).toHaveClass(/bg-destructive\/15/);
|
||
await expect(unmuteButton).toHaveClass(/text-destructive/);
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).findLast(
|
||
(entry) => entry.command === "set_huddle_manual_mic_unmuted",
|
||
)?.payload,
|
||
),
|
||
)
|
||
.toEqual({ enabled: false });
|
||
await page.mouse.move(0, 0);
|
||
await unmuteButton.hover();
|
||
await expect(page.getByRole("tooltip")).toHaveAccessibleName(
|
||
/Click to unmute or hold .*Space/,
|
||
);
|
||
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_EMIT_TAURI_EVENT__?.("ptt-state", true);
|
||
});
|
||
await expect(muteButton).toBeVisible();
|
||
await muteButton.click();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).findLast(
|
||
(entry) => entry.command === "set_huddle_manual_mic_unmuted",
|
||
)?.payload,
|
||
),
|
||
)
|
||
.toEqual({ enabled: false });
|
||
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_EMIT_TAURI_EVENT__?.("ptt-state", false);
|
||
});
|
||
await expect(unmuteButton).toBeVisible();
|
||
|
||
await page.getByRole("button", { name: "Audio settings" }).click();
|
||
await page.getByRole("button", { name: "Turn off Push to Talk" }).click();
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_EMIT_TAURI_EVENT__?.("ptt-state", true);
|
||
});
|
||
await expect(unmuteButton).toBeVisible();
|
||
await page.getByRole("button", { name: "Turn on Push to Talk" }).click();
|
||
await expect(unmuteButton).toBeVisible();
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_EMIT_TAURI_EVENT__?.("ptt-state", true);
|
||
});
|
||
await expect(muteButton).toBeVisible();
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_EMIT_TAURI_EVENT__?.("ptt-state", false);
|
||
});
|
||
await expect(unmuteButton).toBeVisible();
|
||
|
||
await unmuteButton.click();
|
||
await expect(muteButton).toBeVisible();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).findLast(
|
||
(entry) => entry.command === "set_huddle_manual_mic_unmuted",
|
||
)?.payload,
|
||
),
|
||
)
|
||
.toEqual({ enabled: true });
|
||
});
|
||
|
||
test("toggles the current channel huddle with Control+Shift+Space", async ({
|
||
page,
|
||
}) => {
|
||
await installFakeHuddleMicrophone(page);
|
||
await installMockBridge(page, {
|
||
openHuddleWindowDelayMs: 10_000,
|
||
});
|
||
await page.goto("/");
|
||
await page.getByTestId("channel-alice-tyler").click();
|
||
await expect(page.getByTestId("channel-start-huddle-trigger")).toBeEnabled();
|
||
const pressHuddleShortcut = () =>
|
||
page.evaluate(() => {
|
||
window.dispatchEvent(
|
||
new KeyboardEvent("keydown", {
|
||
bubbles: true,
|
||
code: "Space",
|
||
ctrlKey: true,
|
||
key: " ",
|
||
shiftKey: true,
|
||
}),
|
||
);
|
||
});
|
||
|
||
await pressHuddleShortcut();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "start_huddle",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(1);
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(async () => {
|
||
const state = (await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
||
"get_huddle_state",
|
||
)) as { phase: string };
|
||
return state.phase;
|
||
}),
|
||
)
|
||
.toBe("active");
|
||
|
||
await pressHuddleShortcut();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "leave_huddle",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(1);
|
||
});
|
||
|
||
test("starts an agent DM huddle and hides its backing channel after it ends", async ({
|
||
page,
|
||
}) => {
|
||
await installFakeHuddleMicrophone(page);
|
||
await installMockBridge(page, {
|
||
openHuddleWindowDelayMs: 500,
|
||
startHuddleReturnDelayMs: 1_500,
|
||
});
|
||
await page.goto("/");
|
||
await page.getByTestId("channel-alice-tyler").click();
|
||
await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler");
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
(pubkey) =>
|
||
window.__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?.({
|
||
ownerPubkey: pubkey,
|
||
kind: 44100,
|
||
}) ?? false,
|
||
"deadbeef".repeat(8),
|
||
),
|
||
)
|
||
.toBe(true);
|
||
const channelReadsBeforeStart = await page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "get_channels",
|
||
).length,
|
||
);
|
||
|
||
const startButton = page.getByTestId("channel-start-huddle-trigger");
|
||
await expect(startButton).toBeEnabled();
|
||
await startButton.click();
|
||
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).find(
|
||
(entry) => entry.command === "start_huddle",
|
||
)?.payload,
|
||
),
|
||
)
|
||
.toMatchObject({
|
||
memberPubkeys: [TEST_IDENTITIES.alice.pubkey],
|
||
parentChannelId: "f48efb06-0c93-5025-aac9-2e646bb6bfa8",
|
||
});
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "open_huddle_window",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(1);
|
||
const startingState = await page.evaluate(async () =>
|
||
window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("get_huddle_state"),
|
||
);
|
||
expect(startingState).toMatchObject({ phase: "creating" });
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "get_channels",
|
||
).length,
|
||
),
|
||
)
|
||
.toBeGreaterThan(channelReadsBeforeStart);
|
||
const pendingEphemeralChannelId = await page.evaluate(async () => {
|
||
const state = (await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
||
"get_huddle_state",
|
||
)) as { ephemeral_channel_id: string };
|
||
return state.ephemeral_channel_id;
|
||
});
|
||
await expect(
|
||
page.locator(`[data-channel-id="${pendingEphemeralChannelId}"]`),
|
||
).toHaveCount(0);
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"false",
|
||
);
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(
|
||
() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
||
(entry) => entry.command === "open_huddle_window",
|
||
).length,
|
||
),
|
||
)
|
||
.toBe(1);
|
||
await expect(page.getByTestId("profile-huddle-control")).toBeVisible();
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"false",
|
||
);
|
||
|
||
const ephemeralChannelId = await page.evaluate(async () => {
|
||
const state = (await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
||
"get_huddle_state",
|
||
)) as { ephemeral_channel_id: string };
|
||
return state.ephemeral_channel_id;
|
||
});
|
||
const huddleSidebarChannel = page.locator(
|
||
`[data-channel-id="${ephemeralChannelId}"]`,
|
||
);
|
||
await expect(huddleSidebarChannel).toHaveCount(0);
|
||
await page.evaluate(async () => {
|
||
await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("close_huddle_companion");
|
||
});
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"true",
|
||
);
|
||
await expect(page.getByTestId("profile-huddle-control")).toHaveCount(0);
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/channels/${ephemeralChannelId.replaceAll("-", "\\-")}`),
|
||
);
|
||
await expect(huddleSidebarChannel).toBeVisible();
|
||
|
||
await page
|
||
.getByRole("button", { name: "Open huddle in a new window" })
|
||
.click();
|
||
await expect(huddleSidebarChannel).toHaveCount(0);
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"false",
|
||
);
|
||
await expect(page.getByTestId("profile-huddle-control")).toBeVisible();
|
||
|
||
await page.getByRole("button", { name: "Leave huddle" }).click();
|
||
await expect(huddleSidebarChannel).toHaveCount(0);
|
||
await expect(page.locator(".buzz-huddle-shell")).toHaveAttribute(
|
||
"data-huddle-open",
|
||
"false",
|
||
);
|
||
|
||
await waitForMockLiveSubscription(page, "alice-tyler");
|
||
const lifecycleCreatedAt = Math.floor(Date.now() / 1000);
|
||
await page.evaluate(
|
||
({ createdAt, ephemeralChannelId, kind }) => {
|
||
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||
channelName: "alice-tyler",
|
||
content: JSON.stringify({ ephemeral_channel_id: ephemeralChannelId }),
|
||
createdAt,
|
||
id: "3".repeat(64),
|
||
kind,
|
||
});
|
||
},
|
||
{
|
||
createdAt: lifecycleCreatedAt,
|
||
ephemeralChannelId,
|
||
kind: KIND_HUDDLE_STARTED,
|
||
},
|
||
);
|
||
await expect(page.getByTestId("huddle-attachment")).toBeVisible();
|
||
await waitForMockLiveSubscription(page, "alice-tyler", KIND_HUDDLE_ENDED);
|
||
await page.evaluate(
|
||
({ createdAt, ephemeralChannelId, kind }) => {
|
||
window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||
channelName: "alice-tyler",
|
||
content: JSON.stringify({ ephemeral_channel_id: ephemeralChannelId }),
|
||
createdAt,
|
||
id: "4".repeat(64),
|
||
kind,
|
||
});
|
||
},
|
||
{
|
||
createdAt: lifecycleCreatedAt + 1,
|
||
ephemeralChannelId,
|
||
kind: KIND_HUDDLE_ENDED,
|
||
},
|
||
);
|
||
const endedHuddle = page
|
||
.getByTestId("huddle-attachment")
|
||
.filter({ hasText: "Huddle · Ended" });
|
||
await expect(endedHuddle).toBeVisible();
|
||
await endedHuddle.getByRole("button", { name: "View huddle" }).click();
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/channels/${ephemeralChannelId.replaceAll("-", "\\-")}`),
|
||
);
|
||
await expect(huddleSidebarChannel).toBeVisible();
|
||
await expect(page.getByTestId("message-thread-panel")).toHaveCount(0);
|
||
});
|
||
|
||
test("closes add-agent dialog when parent membership is already satisfied", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
addAgentToHuddleResult: {
|
||
ephemeral_added: true,
|
||
parent_added: true,
|
||
parent_error: null,
|
||
},
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
managedAgents: [
|
||
{
|
||
pubkey: TEST_IDENTITIES.bob.pubkey,
|
||
name: "bob",
|
||
status: "running",
|
||
},
|
||
],
|
||
});
|
||
await page.goto("/");
|
||
|
||
await page.getByRole("button", { name: "Add agent to huddle" }).click();
|
||
const dialog = page.getByRole("dialog", { name: "Add agents" });
|
||
await expect(dialog).toBeVisible();
|
||
await dialog.getByRole("button", { name: /bob/i }).click();
|
||
|
||
await expect(dialog).toHaveCount(0);
|
||
await expect(
|
||
page.getByText("Added to huddle, but parent channel failed:"),
|
||
).toHaveCount(0);
|
||
});
|
||
|
||
test("starts an available stopped agent before adding it to the huddle", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
addAgentToHuddleResult: {
|
||
ephemeral_added: true,
|
||
parent_added: true,
|
||
parent_error: null,
|
||
},
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
managedAgents: [
|
||
{
|
||
avatarUrl: "https://example.com/bob.png",
|
||
pubkey: TEST_IDENTITIES.bob.pubkey,
|
||
name: "bob",
|
||
status: "stopped",
|
||
},
|
||
],
|
||
});
|
||
await page.goto("/");
|
||
|
||
await page.getByRole("button", { name: "Add agent to huddle" }).click();
|
||
const dialog = page.getByRole("dialog", { name: "Add agents" });
|
||
await expect(dialog).toBeVisible();
|
||
await expect(dialog).toHaveClass(/data-\[state=open\]:animate-in/);
|
||
await expect(dialog).toHaveClass(/data-\[state=open\]:fade-in-0/);
|
||
await expect(dialog).toHaveClass(/data-\[state=open\]:zoom-in-95/);
|
||
await expect(dialog).toHaveClass(/duration-200/);
|
||
await expect(dialog).toHaveClass(/motion-reduce:animate-none/);
|
||
await expect(
|
||
dialog.getByText("Choose an agent to join this huddle.", { exact: true }),
|
||
).toBeVisible();
|
||
await expect(dialog.getByText("stopped", { exact: true })).toHaveCount(0);
|
||
await dialog.getByRole("button", { name: /bob/i }).click();
|
||
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? [])
|
||
.filter(
|
||
(entry) =>
|
||
entry.command === "start_managed_agent" ||
|
||
entry.command === "add_agent_to_huddle",
|
||
)
|
||
.map((entry) => entry.command),
|
||
),
|
||
)
|
||
.toEqual(["start_managed_agent", "add_agent_to_huddle"]);
|
||
await expect(dialog).toHaveCount(0);
|
||
});
|
||
|
||
test("stops an agent started solely for a failed huddle add", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
addAgentToHuddleError: "membership publish rejected",
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
managedAgents: [
|
||
{
|
||
avatarUrl: "https://example.com/bob.png",
|
||
pubkey: TEST_IDENTITIES.bob.pubkey,
|
||
name: "bob",
|
||
status: "stopped",
|
||
},
|
||
],
|
||
});
|
||
await page.goto("/");
|
||
|
||
await page.getByRole("button", { name: "Add agent to huddle" }).click();
|
||
const dialog = page.getByRole("dialog", { name: "Add agents" });
|
||
await dialog.getByRole("button", { name: /bob/i }).click();
|
||
|
||
await expect(dialog.getByText(/Failed to add agent:/)).toBeVisible();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? [])
|
||
.filter((entry) =>
|
||
[
|
||
"start_managed_agent",
|
||
"add_agent_to_huddle",
|
||
"stop_managed_agent",
|
||
].includes(entry.command),
|
||
)
|
||
.map((entry) => entry.command),
|
||
),
|
||
)
|
||
.toEqual([
|
||
"start_managed_agent",
|
||
"add_agent_to_huddle",
|
||
"stop_managed_agent",
|
||
]);
|
||
});
|
||
|
||
test("does not deploy a provider agent when its huddle add fails", async ({
|
||
page,
|
||
}) => {
|
||
await installMockBridge(page, {
|
||
addAgentToHuddleError: "membership publish rejected",
|
||
huddle: {
|
||
parentChannelId: HUDDLE_PARENT_ID,
|
||
ephemeralChannelId: HUDDLE_CHANNEL_ID,
|
||
members: [{ pubkey: TEST_IDENTITIES.tyler.pubkey, role: "member" }],
|
||
},
|
||
managedAgents: [
|
||
{
|
||
avatarUrl: "https://example.com/bob.png",
|
||
backend: { type: "provider", id: "mock", config: {} },
|
||
pubkey: TEST_IDENTITIES.bob.pubkey,
|
||
name: "bob",
|
||
status: "not_deployed",
|
||
},
|
||
],
|
||
});
|
||
await page.goto("/");
|
||
|
||
await page.getByRole("button", { name: "Add agent to huddle" }).click();
|
||
const dialog = page.getByRole("dialog", { name: "Add agents" });
|
||
await dialog.getByRole("button", { name: /bob/i }).click();
|
||
|
||
await expect(dialog.getByText(/Failed to add agent:/)).toBeVisible();
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() =>
|
||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? [])
|
||
.filter((entry) =>
|
||
[
|
||
"start_managed_agent",
|
||
"add_agent_to_huddle",
|
||
"stop_managed_agent",
|
||
].includes(entry.command),
|
||
)
|
||
.map((entry) => entry.command),
|
||
),
|
||
)
|
||
.toEqual(["add_agent_to_huddle"]);
|
||
});
|