mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Polish glass Huddle tray behavior (#5590)
## Summary - inset the in-app Huddle tray with four rounded corners and even 8px spacing when Glass background is enabled - keep the popped-out Huddle dock full-width - hide and suppress Glass background on Linux ## Why The in-app tray reused the opaque backing needed by non-glass windows, which covered the native vibrancy around it. Linux does not support this window treatment. ## Testing - `pnpm -C desktop build:e2e` - focused Appearance and Huddle Playwright smoke tests - pre-push desktop checks, typecheck, and 4,666 unit tests --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
This commit is contained in:
co-authored by
Princess Donut
parent
76f114a252
commit
0571f5455b
@@ -68,7 +68,7 @@ export function AppHuddleShell({
|
||||
{children}
|
||||
</div>
|
||||
{isRoom || !isCompanionOpen ? (
|
||||
<div className="absolute inset-x-0 bottom-0 z-[2] h-(--buzz-huddle-drawer-height)">
|
||||
<div className="buzz-huddle-drawer-slot absolute inset-x-0 bottom-0 z-[2] h-(--buzz-huddle-drawer-height)">
|
||||
<AppHuddleBar
|
||||
mode={isRoom ? "room" : "main"}
|
||||
onOpenHuddleWindow={isRoom ? undefined : onCompanionOpen}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
useLinkPreviewStyle,
|
||||
type LinkPreviewStyle,
|
||||
} from "@/shared/lib/linkPreviewStylePreference";
|
||||
import { isLinuxPlatform } from "@/shared/lib/platform";
|
||||
import {
|
||||
ACCENT_COLORS,
|
||||
DEFAULT_GLASS_OPACITY,
|
||||
@@ -168,6 +169,9 @@ export function GlassBackgroundSetting() {
|
||||
setGlassOpacity,
|
||||
} = useTheme();
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
if (isLinuxPlatform()) return null;
|
||||
|
||||
const shouldShowOpacity = glassBackgroundSupported && glassBackground;
|
||||
const opacityRow = (
|
||||
<SettingsOptionRow data-testid="glass-opacity-row">
|
||||
|
||||
@@ -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%);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<void>("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<string>(() => {
|
||||
applyCachedVars();
|
||||
@@ -499,8 +504,9 @@ export function ThemeProvider({
|
||||
const [glassBackground, setGlassBackgroundState] = useState<boolean>(() => {
|
||||
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;
|
||||
});
|
||||
@@ -668,17 +674,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);
|
||||
@@ -704,7 +720,7 @@ export function ThemeProvider({
|
||||
followSystem,
|
||||
glassBackground,
|
||||
glassOpacity,
|
||||
glassBackgroundSupported: isTauri() && isMacPlatform(),
|
||||
glassBackgroundSupported,
|
||||
prominentActiveTab,
|
||||
hasPair,
|
||||
terminalPalette,
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
@@ -1178,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();
|
||||
|
||||
@@ -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,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user