mirror of
https://github.com/block/buzz.git
synced 2026-11-14 13:21:02 +00:00
refactor(voice): extract reusable Pocket primitives + Pocket voice settings (relands #2467 + #3208) (#3910)
Relands **#2467** (extract `buzz-voice` crate) and **#3208** (Pocket
voice settings) onto main, after #3266 and #3180 merged.
## Why a fresh PR
The repo is squash-only with delete-branch-on-merge. Squashing #3266
deleted `jtennant/pocket-tts-2026-04`, which was #2467's base — GitHub
auto-closed #2467 and it cannot be reopened. Squash merges also sever
ancestry, so GitHub's natural merge-base reports phantom conflicts for
the whole remaining stack.
## Content provenance
- Byte-identical to the blessed `jt/buzz-voice-refactor` branch
(`93029c577`, tree `6729e0eff` — reviewed by Dawn (#2467) and Max
(#3208) at exact heads) **except** the three files where #3180 and #3208
genuinely interact.
- Three-file resolution (union of both sides):
- `huddle/mod.rs` — #3180's pipeline re-exports + #3208's
`agent_tts_routing` imports.
- `huddle/state.rs` — `reset_preserving_generation` preserves both
`huddle_generation` (#3180) and `tts_enabled` (#3208); test sets merged
into one `tests` module.
- `desktop/src/testing/e2eBridge.ts` — both switch arms kept; no
duplicate case labels.
## Verification at cf32dacd7
- `cargo test` (desktop/src-tauri, pinned 1.95.0): **2047 + 3 pass / 0
fail** (14 ignored: 8 keychain, 4 real_relay, 2 flag-gated)
- `cargo clippy --all-targets -- -D warnings`: clean; `cargo fmt
--check`: clean
- `cargo check --workspace` (root, includes new `buzz-voice` member):
clean; `cargo test -p buzz-voice`: 5/0
- `pnpm test`: **3885 / 0**; `tsc --noEmit`: clean; lint: clean
The 3180×3208 interaction resolution is getting an independent team
re-review before merge.
Buzz channel: buzz-desktop-voice `fd5fb402-b651-4238-89b1-bb3e2fa4dc96`,
thread `b4798ecc`.
Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
This commit is contained in:
119
desktop/tests/e2e/voice-settings.spec.ts
Normal file
119
desktop/tests/e2e/voice-settings.spec.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { waitForAnimations } from "../helpers/animations";
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
import { openSettings } from "../helpers/settings";
|
||||
|
||||
const SCREENSHOT_PATH = "test-results/voice-settings/pocket-voices.png";
|
||||
|
||||
test.describe("Pocket voice settings", () => {
|
||||
test.use({ viewport: { width: 1100, height: 760 } });
|
||||
|
||||
test("selects and retains a bundled voice while text to speech is off", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await openSettings(page, "voice");
|
||||
|
||||
const card = page.getByTestId("settings-voice");
|
||||
await expect(card).toBeVisible();
|
||||
await expect(
|
||||
page.getByText("Agent text to speech", { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText("Pocket TTS voice", { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(card).not.toContainText("April INT8");
|
||||
|
||||
await page.getByTestId("pocket-voice-selector").click();
|
||||
await expect(page.getByRole("menuitemradio")).toHaveCount(12);
|
||||
await page.getByRole("menuitemradio", { name: "Eve" }).click();
|
||||
await expect(page.getByTestId("pocket-voice-selector")).toContainText(
|
||||
"Eve",
|
||||
);
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Pocket TTS voice: Eve" }),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByTestId("agent-text-to-speech-toggle").click();
|
||||
await expect(
|
||||
page.getByTestId("agent-text-to-speech-toggle"),
|
||||
).toHaveAttribute("aria-checked", "false");
|
||||
await expect(page.getByTestId("pocket-voice-controls")).toHaveAttribute(
|
||||
"aria-disabled",
|
||||
"true",
|
||||
);
|
||||
await expect(page.getByTestId("pocket-voice-selector")).toContainText(
|
||||
"Eve",
|
||||
);
|
||||
|
||||
const savedCommands = await page.evaluate(() =>
|
||||
(window.__BUZZ_E2E_COMMAND_LOG__ ?? [])
|
||||
.filter((entry) =>
|
||||
["set_pocket_voice", "set_tts_enabled"].includes(entry.command),
|
||||
)
|
||||
.map((entry) => ({ command: entry.command, payload: entry.payload })),
|
||||
);
|
||||
expect(savedCommands).toEqual([
|
||||
{
|
||||
command: "set_pocket_voice",
|
||||
payload: { voiceKey: "pocket:eve" },
|
||||
},
|
||||
{
|
||||
command: "set_tts_enabled",
|
||||
payload: { enabled: false },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("captures the complete VCTK preset settings surface", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, {
|
||||
ttsSettings: {
|
||||
version: 1,
|
||||
agentTextToSpeech: true,
|
||||
voicePreferences: ["pocket:eve"],
|
||||
},
|
||||
});
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await openSettings(page, "voice");
|
||||
|
||||
const card = page.getByTestId("settings-voice");
|
||||
await expect(card).toBeVisible();
|
||||
await expect(page.getByTestId("pocket-voice-selector")).toContainText(
|
||||
"Eve",
|
||||
);
|
||||
await page.getByTestId("pocket-voice-selector").click();
|
||||
await expect(page.getByRole("menuitemradio")).toHaveCount(12);
|
||||
const menu = page.getByRole("menu");
|
||||
await expect(menu).toBeVisible();
|
||||
await waitForAnimations(page);
|
||||
const cardBox = await card.boundingBox();
|
||||
const menuBox = await menu.boundingBox();
|
||||
const viewport = page.viewportSize();
|
||||
if (!cardBox || !menuBox || !viewport) {
|
||||
throw new Error("Voice settings screenshot bounds are unavailable");
|
||||
}
|
||||
const x = Math.max(0, Math.min(cardBox.x, menuBox.x) - 16);
|
||||
const y = Math.max(0, Math.min(cardBox.y, menuBox.y) - 16);
|
||||
const right = Math.min(
|
||||
viewport.width,
|
||||
Math.max(cardBox.x + cardBox.width, menuBox.x + menuBox.width) + 16,
|
||||
);
|
||||
const bottom = Math.min(
|
||||
viewport.height,
|
||||
Math.max(cardBox.y + cardBox.height, menuBox.y + menuBox.height) + 16,
|
||||
);
|
||||
await page.screenshot({
|
||||
path: SCREENSHOT_PATH,
|
||||
clip: {
|
||||
x: Math.floor(x),
|
||||
y: Math.floor(y),
|
||||
width: Math.ceil(right - x),
|
||||
height: Math.ceil(bottom - y),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -159,6 +159,11 @@ type MockInstallRuntimeResult = {
|
||||
};
|
||||
|
||||
type MockBridgeOptions = {
|
||||
ttsSettings?: {
|
||||
version: number;
|
||||
agentTextToSpeech: boolean;
|
||||
voicePreferences: string[];
|
||||
};
|
||||
/** Advertised HEAD for the first mock project without adding that branch. */
|
||||
projectHeadBranch?: string;
|
||||
/** Relay NIP-11 identity used to sign authoritative repository state. */
|
||||
|
||||
@@ -3,6 +3,7 @@ import { expect, type Page } from "@playwright/test";
|
||||
type SettingsSection =
|
||||
| "profile"
|
||||
| "notifications"
|
||||
| "voice"
|
||||
| "agents"
|
||||
| "channel-templates"
|
||||
| "compute"
|
||||
|
||||
Reference in New Issue
Block a user