mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
Fixes the CI flakes that failed most often across `main` and unrelated branches from Oct 5 to Oct 14. The counts come from 150 failed `CI` runs. Every change except the nextest config is test-only. | Flake | Runs hit | Root cause | Fix | |---|---|---|---| | `probe_codex_acp_version_…descendant_holds_pipe_open` | 5 | The test writes its script in-process and then execs it. If a sibling test spawns a process in that window, the child inherits the open write fd and Linux rejects the exec with `ETXTBSY`. The probe swallows that error as `None`. | Write probe scripts from a short-lived `/bin/sh` (`write_executable_script`) | | `empty-edit-delete.spec.ts` (`:51`, `:74`, `:95`) | 11 | The seeded `mock-general-welcome` id isn't hex, so `formatTimelineMessages` can't overlay its edit. The post-subscribe window refetch then reverts the row. | Emit a per-test message with a real id after the exact channel subscription is live | | `huddle-transcription.spec.ts:798` | 9 | The composer's delayed initial autofocus (TipTap `focus()` runs in rAF) lands after the voice popover opens and dismisses it. | Wait for `message-input` focus before opening the popover | | `voice-note.spec.ts:906` | 6 | The fixture `ping.mp3` is about 1s long, so playback can end before `paused === false` is observed. | Loop the fixture audio | | `workflows.spec.ts:291` | 5 | The `Message text` fill runs before the inspector swaps to the new step, so it fills the trigger's condition instead. | Scope the fill to the step inspector after its `Action` menu appears | | `cluster_global_probe_commits_tokens_and_sessions_prove_coverage` | 3 | The `max-threads = 1` group only serializes these tests with each other. Other tests' sessions still mask cluster-wide `pg_stat_activity` (`MaskedActivity`). | `threads-required = "num-test-threads"` | | `test_usage_metrics_lock_has_single_owner_and_releases_on_drop` | 1 (this PR) | The test retries the lock right after dropping the leader. Postgres releases a session advisory lock only after the backend notices the disconnect, so the retry can run first. | Poll for the successor, bounded at 5s | ### Validation - **ETXTBSY:** a Linux experiment with Rust 1.95 using the same write→chmod→spawn pattern under concurrent spawns hit `ETXTBSY` 4–7% of the time. Using the `sh` writer, it hit 0 of 6,000. I did not catch it inside the full suite locally, so this fix is based on the mechanism, not an in-suite repro. All 11 probe tests pass on Linux and macOS. - **empty-edit-delete:** the baseline failed 16 of 45 runs (`--repeat-each`, 6 workers, no retries). The spec fix alone brings that to 4/45. The remaining failures come from a separate product race, described below. - **huddle:** the baseline failed 5 of 10 runs. With the fix, 60 of 60 passed without retries. - **nextest:** checked with a throwaway crate on 0.9.136. A grouped test with `num-test-threads` ran alone, even with `max-threads = 1`. - I have not stress-run the voice-note and workflows fixes; they rely on CI. ### Not in this PR - **Product bug in `useChannelLinks`:** the debounced `#channel` autocomplete close lets Enter accept a stale suggestion for about 120ms. That accounts for the remaining `empty-edit-delete` failures, and it affects users too. With the spec fix plus a synchronous close, the spec passed 120 of 120 runs. This needs a product decision, so it's held for a separate PR. - **Composer autofocus race:** the delayed autofocus can close an overlay the user just opened. The real fix is to revalidate when the delayed focus runs. Overlaps #6530. - **`thread-head-stale-edit.spec.ts:55`:** a forced click on a pointer-events-none action bar is suspected, but I couldn't reproduce it. - **Remaining ETXTBSY exposure:** about 40 other Rust tests write and then exec scripts, and 17 specs still edit the non-hex seed message. Root-cause lanes by Princess Donut, Mongo and Mordecai. Carl integrated and verified them. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <5d97ac8c272fa949af56c71e586146b1706e5c4c3daa125db5b4625e12e0686c@buzz.block.builderlab.xyz> Co-authored-by: Mongo <81cabd2ca1792494372ed410283c00770c0cf35afca052e940fa522883212b45@buzz.block.builderlab.xyz> Co-authored-by: Mordecai <f314b6033b6e13df94c8e9e1187d3666b6b9ce54e66ee986e31cdaa909e8dcbc@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1898 lines
60 KiB
TypeScript
1898 lines
60 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 { ROUNDED_SQUIRCLE_PATH } from "../../src/shared/ui/AvatarClipPaths";
|
||
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 expect(agentAvatar).toHaveCSS("clip-path", "none");
|
||
await expect(agentAvatar).not.toHaveClass(/rounded-squircle/);
|
||
await expect(agentAvatar).toHaveClass(/buzz-huddle-speaking-avatar-agent/);
|
||
const speakingRing = agentAvatar.getByTestId("huddle-agent-speaking-ring");
|
||
await expect(speakingRing).toBeVisible();
|
||
await expect(speakingRing.locator("path")).toHaveAttribute(
|
||
"d",
|
||
ROUNDED_SQUIRCLE_PATH,
|
||
);
|
||
await expect(speakingRing.locator("path")).toHaveAttribute("fill", "none");
|
||
await expect(speakingRing.locator("path")).toHaveAttribute(
|
||
"stroke-width",
|
||
"2",
|
||
);
|
||
await expect(speakingRing.locator("path")).toHaveAttribute(
|
||
"vector-effect",
|
||
"non-scaling-stroke",
|
||
);
|
||
await expect(speakingRing).toHaveCSS("opacity", "0.89");
|
||
await expect(agentAvatar.locator(".rounded-squircle")).toHaveCSS(
|
||
"clip-path",
|
||
/rounded-squircle-clip/,
|
||
);
|
||
|
||
const voiceMenuTrigger = page.getByTestId("huddle-agent-voice-menu-trigger");
|
||
await voiceMenuTrigger.focus();
|
||
await expect(voiceMenuTrigger).toBeFocused();
|
||
await expect(voiceMenuTrigger).toHaveCSS("clip-path", "none");
|
||
|
||
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);
|
||
|
||
// Let the huddle chat finish mounting and autofocus before opening an
|
||
// overlay; a late composer focus would dismiss the popover.
|
||
await expect(page.getByTestId("message-input")).toBeFocused();
|
||
|
||
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],
|
||
changed_channel_ids: [HUDDLE_CHANNEL_ID],
|
||
error: null,
|
||
});
|
||
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"]);
|
||
});
|