From f5a0ef52390ca51039e724afee0b4f21017fed0c Mon Sep 17 00:00:00 2001 From: kenny lopez Date: Tue, 11 Aug 2026 19:39:28 +0100 Subject: [PATCH 1/2] Polish glass Huddle behavior Signed-off-by: kenny lopez --- desktop/src/app/AppHuddleShell.tsx | 2 +- .../ui/AppearanceSettingsControls.tsx | 4 + .../src/shared/styles/globals/components.css | 1 + desktop/src/shared/styles/globals/theme.css | 41 ++++++- desktop/src/shared/theme/ThemeProvider.tsx | 54 ++++++--- .../tests/e2e/buzz-theme-screenshots.spec.ts | 66 +++++++++++ .../tests/e2e/huddle-transcription.spec.ts | 111 ++++++++++++++++++ 7 files changed, 256 insertions(+), 23 deletions(-) diff --git a/desktop/src/app/AppHuddleShell.tsx b/desktop/src/app/AppHuddleShell.tsx index 29dcd26cdfb..68f9eacd6fd 100644 --- a/desktop/src/app/AppHuddleShell.tsx +++ b/desktop/src/app/AppHuddleShell.tsx @@ -68,7 +68,7 @@ export function AppHuddleShell({ {children} {isRoom || !isCompanionOpen ? ( -
+
diff --git a/desktop/src/shared/styles/globals/components.css b/desktop/src/shared/styles/globals/components.css index 5ab551bed08..172c1b180c2 100644 --- a/desktop/src/shared/styles/globals/components.css +++ b/desktop/src/shared/styles/globals/components.css @@ -7,6 +7,7 @@ .buzz-huddle-shell { --buzz-huddle-drawer-height: 5rem; + --buzz-huddle-drawer-inset: 8px; --buzz-huddle-drawer-radius: 24px; --buzz-huddle-drawer-ease: cubic-bezier(0.32, 0.72, 0, 1); --buzz-huddle-drawer-surface: var(--huddle-drawer-surface, 0 0% 0%); diff --git a/desktop/src/shared/styles/globals/theme.css b/desktop/src/shared/styles/globals/theme.css index 171f8c0b2e1..5fd2593c79c 100644 --- a/desktop/src/shared/styles/globals/theme.css +++ b/desktop/src/shared/styles/globals/theme.css @@ -901,7 +901,9 @@ background: transparent; } -:root[data-glass-background] .buzz-huddle-app-surface-open::after { +:root[data-glass-background] + .buzz-huddle-shell[data-huddle-window="true"] + .buzz-huddle-app-surface-open::after { background: hsl(var(--buzz-huddle-drawer-surface)); } @@ -914,8 +916,8 @@ } :root[data-buzz-sidebar][data-glass-background] - .buzz-huddle-shell[data-huddle-open="true"] { - background-color: hsl(var(--buzz-huddle-drawer-surface)); + .buzz-huddle-shell[data-huddle-window="false"][data-huddle-open="true"] { + background-color: transparent; background-image: none; } @@ -930,3 +932,36 @@ position: absolute; z-index: 0; } + +/* In the main glass window, let vibrancy continue around the Huddle controls + * and present the tray as an inset four-corner surface. The dedicated Huddle + * window keeps the full-width dark control dock above. */ +:root[data-glass-background] + .buzz-huddle-shell[data-huddle-window="false"] + .buzz-huddle-drawer-backdrop { + background: transparent; +} + +:root[data-buzz-sidebar][data-glass-background] + .buzz-huddle-shell[data-huddle-window="false"][data-huddle-open="true"]::before { + content: none; +} + +:root[data-glass-background] + .buzz-huddle-shell[data-huddle-window="false"] + .buzz-huddle-drawer-slot { + padding: var(--buzz-huddle-drawer-inset); +} + +:root[data-glass-background] + .buzz-huddle-shell[data-huddle-window="false"] + .buzz-huddle-drawer-slot + > .buzz-huddle-drawer { + border-radius: calc( + var(--buzz-huddle-drawer-radius) - + var(--buzz-huddle-drawer-inset) + ); + height: 100%; + padding-block: var(--buzz-huddle-drawer-inset); + padding-inline: var(--buzz-huddle-drawer-inset); +} diff --git a/desktop/src/shared/theme/ThemeProvider.tsx b/desktop/src/shared/theme/ThemeProvider.tsx index d5851661352..0fb7543ea18 100644 --- a/desktop/src/shared/theme/ThemeProvider.tsx +++ b/desktop/src/shared/theme/ThemeProvider.tsx @@ -361,6 +361,14 @@ async function applyWindowGlass(enabled: boolean) { glassBackgroundPreferenceEnabled = enabled; const requestToken = ++glassVibrancyRequest; + if (!isTauri() || !isMacPlatform()) { + glassBackgroundPreferenceEnabled = false; + glassVibrancyEnabled = false; + glassVibrancyReady = false; + setGlassBackgroundActive(false); + return; + } + if (enabled && glassVibrancyEnabled && glassVibrancyReady) { maybeEnableGlassBackground(requestToken); return; @@ -368,11 +376,6 @@ async function applyWindowGlass(enabled: boolean) { glassVibrancyReady = false; - if (!isTauri()) { - setGlassBackgroundActive(false); - return; - } - try { await invokeTauri("set_window_vibrancy", { enabled, @@ -478,6 +481,8 @@ export function ThemeProvider({ children, defaultTheme = "buzz", }: ThemeProviderProps) { + const glassBackgroundSupported = isTauri() && isMacPlatform(); + // Apply cached vars synchronously before first render const [selectedTheme, setSelectedTheme] = useState(() => { applyCachedVars(); @@ -499,8 +504,9 @@ export function ThemeProvider({ const [glassBackground, setGlassBackgroundState] = useState(() => { const stored = getStorageItem(GLASS_BACKGROUND_STORAGE_KEY); // Glass is opt-in. Explicitly saved preferences remain intact, while a - // fresh profile starts with the normal opaque window treatment. - const enabled = stored === "true"; + // fresh profile starts with the normal opaque window treatment. Keep an + // unsupported platform opaque without erasing a preference saved on Mac. + const enabled = glassBackgroundSupported && stored === "true"; glassBackgroundPreferenceEnabled = enabled; return enabled; }); @@ -663,17 +669,27 @@ export function ThemeProvider({ [], ); - const setGlassBackground = useCallback((enabled: boolean) => { - window.localStorage.setItem( - GLASS_BACKGROUND_STORAGE_KEY, - enabled ? "true" : "false", - ); - glassBackgroundPreferenceEnabled = enabled; - if (!enabled) { - setGlassBackgroundActive(false); - } - setGlassBackgroundState(enabled); - }, []); + const setGlassBackground = useCallback( + (enabled: boolean) => { + if (!glassBackgroundSupported) { + glassBackgroundPreferenceEnabled = false; + setGlassBackgroundActive(false); + setGlassBackgroundState(false); + return; + } + + window.localStorage.setItem( + GLASS_BACKGROUND_STORAGE_KEY, + enabled ? "true" : "false", + ); + glassBackgroundPreferenceEnabled = enabled; + if (!enabled) { + setGlassBackgroundActive(false); + } + setGlassBackgroundState(enabled); + }, + [glassBackgroundSupported], + ); const setGlassOpacity = useCallback((opacity: number) => { const nextOpacity = clampGlassOpacity(opacity); @@ -699,7 +715,7 @@ export function ThemeProvider({ followSystem, glassBackground, glassOpacity, - glassBackgroundSupported: isTauri() && isMacPlatform(), + glassBackgroundSupported, prominentActiveTab, hasPair, terminalPalette, diff --git a/desktop/tests/e2e/buzz-theme-screenshots.spec.ts b/desktop/tests/e2e/buzz-theme-screenshots.spec.ts index a5218142905..cd29d17fe2e 100644 --- a/desktop/tests/e2e/buzz-theme-screenshots.spec.ts +++ b/desktop/tests/e2e/buzz-theme-screenshots.spec.ts @@ -478,6 +478,13 @@ test("appearance groups theme and preferences into labeled rows", async ({ page, }) => { await seedTheme(page, "github-light"); + await page.addInitScript((prominentActiveTabStorageKey) => { + window.localStorage.setItem(prominentActiveTabStorageKey, "true"); + Object.defineProperty(navigator, "platform", { + configurable: true, + get: () => "MacIntel", + }); + }, PROMINENT_ACTIVE_TAB_STORAGE_KEY); await installMockBridge(page); await openAppearance(page, "light"); const themeCard = page.getByTestId("appearance-theme-card"); @@ -539,6 +546,22 @@ test("appearance groups theme and preferences into labeled rows", async ({ await themeCard.getByTestId("theme-option-buzz").click(); await expect(themeStyleTrigger).toHaveAttribute("aria-expanded", "true"); await expect(themeStyleOptions).toBeVisible(); + await expect( + themeCard.getByTestId("prominent-active-tab-toggle"), + ).toBeChecked(); + const glassBeforeProminent = await themeCard.evaluate((card) => { + const glass = card.querySelector('[data-testid="glass-background-toggle"]'); + const prominent = card.querySelector( + '[data-testid="prominent-active-tab-row"]', + ); + return Boolean( + glass && + prominent && + glass.compareDocumentPosition(prominent) & + Node.DOCUMENT_POSITION_FOLLOWING, + ); + }); + expect(glassBeforeProminent).toBe(true); const colorModeLabelBox = await themeCard .getByTestId("appearance-color-mode-row") @@ -1127,6 +1150,49 @@ test("glass background keeps the content panel solid", async ({ page }) => { .toBe("false"); }); +test("glass background is unavailable on Linux", async ({ page }) => { + await seedTheme(page, "buzz"); + await page.addInitScript((storageKey) => { + window.localStorage.setItem(storageKey, "true"); + (window as typeof window & { isTauri?: boolean }).isTauri = true; + Object.defineProperty(navigator, "platform", { + configurable: true, + get: () => "Linux x86_64", + }); + Object.defineProperty(navigator, "userAgent", { + configurable: true, + get: () => "Buzz Desktop Linux", + }); + }, GLASS_BACKGROUND_STORAGE_KEY); + await installMockBridge(page); + await openAppearance(page, "light"); + + await expect(page.getByTestId("glass-background-row")).toHaveCount(0); + await expect(page.getByTestId("glass-background-toggle")).toHaveCount(0); + await expect(page.getByTestId("glass-opacity-slider")).toHaveCount(0); + await expect(page.locator("html")).not.toHaveAttribute( + "data-glass-background", + "", + ); + await expect + .poll(() => + page.evaluate(() => + (window.__BUZZ_E2E_COMMAND_LOG__ ?? []).some( + (entry) => entry.command === "set_window_vibrancy", + ), + ), + ) + .toBe(false); + await expect + .poll(() => + page.evaluate( + (storageKey) => window.localStorage.getItem(storageKey), + GLASS_BACKGROUND_STORAGE_KEY, + ), + ) + .toBe("true"); +}); + test("non-Buzz glass preserves the selected theme sidebar tint", async ({ page, }) => { diff --git a/desktop/tests/e2e/huddle-transcription.spec.ts b/desktop/tests/e2e/huddle-transcription.spec.ts index 404f145fa28..734e4ba17ea 100644 --- a/desktop/tests/e2e/huddle-transcription.spec.ts +++ b/desktop/tests/e2e/huddle-transcription.spec.ts @@ -264,6 +264,117 @@ test("keeps the drawer open until the huddle is expanded", async ({ page }) => { 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, }) => { From e45151c5ebfb6ceda066f5e8918735c70674a05a Mon Sep 17 00:00:00 2001 From: Princess Donut Date: Tue, 11 Aug 2026 20:48:44 +0100 Subject: [PATCH 2/2] Stabilize appearance ordering test platform Co-authored-by: Princess Donut Signed-off-by: Princess Donut --- desktop/tests/e2e/buzz-theme-screenshots.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/desktop/tests/e2e/buzz-theme-screenshots.spec.ts b/desktop/tests/e2e/buzz-theme-screenshots.spec.ts index cd29d17fe2e..2bcd36908eb 100644 --- a/desktop/tests/e2e/buzz-theme-screenshots.spec.ts +++ b/desktop/tests/e2e/buzz-theme-screenshots.spec.ts @@ -1244,6 +1244,12 @@ test("accent picker reveals/hides when toggling Buzz", async ({ page }) => { // non-Buzz tile brings it back. Asserts the presence toggle (the motion // wrapper) works end to end. await seedTheme(page, "github-light"); + await page.addInitScript(() => { + Object.defineProperty(navigator, "platform", { + configurable: true, + get: () => "MacIntel", + }); + }); await installMockBridge(page); await openAppearance(page, "light"); await expect(page.getByTestId("accent-color-neutral")).toBeVisible();