mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
fix(desktop): lift right auxiliary pane above shared header backdrop (#6966)
## Summary The sidebar close (`X`) and `Edit` controls disappeared for in-channel side panels on `main`. They stayed clickable but were visually washed out. [#6901](https://github.com/block/buzz/pull/6901) added `isolate` to `RightAuxiliaryPane` so project workspace sheets slide over an open thread cleanly. The pane carries no `z-index`, so isolating it makes the whole pane subtree paint at stacking level 0 — below the channel's sibling `z-30` shared-header backdrop in split layout. That translucent blur strip then washes out the pane's own `z-40` header chrome, where `X`/`Edit` live. The backdrop is `pointer-events-none`, so the controls remained clickable but invisible (matching the reported videos). Threads, channel settings, and in-channel agent panels all route through this wrapper, so all three were affected; the standalone Agents-nav panel does not render inside `ChannelPane`, so it had no backdrop and worked. ## Fix Add `z-31` to the pane's `aside` — above the `z-30` backdrop, still below the `z-41` thread drawer/sheet overlays. `isolate` stays, so #6901's sheet-over-thread layering is preserved and both behaviors coexist. ## Regression coverage `tests/e2e/auxiliary-pane-close-visibility.spec.ts` opens a split-layout thread from inside a channel and asserts the pane establishes its `isolate` stacking context and that its `z-index` outranks the shared-header backdrop. It fails on the pre-fix tree (pane `z-index` is `auto`) and passes with `z-31`. #6901's `project workspace sheet stays independent from an open thread` spec still passes. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -30,6 +30,7 @@ export default defineConfig({
|
||||
"**/navigation.spec.ts",
|
||||
"**/channels.spec.ts",
|
||||
"**/channel-shared-header-backdrop.spec.ts",
|
||||
"**/auxiliary-pane-close-visibility.spec.ts",
|
||||
"**/channel-composer-overflow.spec.ts",
|
||||
"**/badge.spec.ts",
|
||||
"**/channel-browser.spec.ts",
|
||||
|
||||
@@ -31,7 +31,11 @@ export function RightAuxiliaryPane({
|
||||
return (
|
||||
<aside
|
||||
className={cn(
|
||||
"group/right-pane relative isolate flex h-full shrink-0 flex-col overflow-hidden bg-background",
|
||||
// `isolate` lets project sheets cover threads cleanly (#6901) but
|
||||
// traps this pane's z-40 header chrome (X/Edit) below the channel's
|
||||
// shared z-30 header blur strip in split layout. `z-31` lifts the
|
||||
// pane above that backdrop while staying under the z-41 thread drawer.
|
||||
"group/right-pane relative isolate z-31 flex h-full shrink-0 flex-col overflow-hidden bg-background",
|
||||
detached
|
||||
? "bg-transparent"
|
||||
: "before:pointer-events-none before:absolute before:bottom-0 before:left-0 before:top-0 before:z-50 before:w-px before:bg-border/80 before:content-['']",
|
||||
|
||||
113
desktop/tests/e2e/auxiliary-pane-close-visibility.spec.ts
Normal file
113
desktop/tests/e2e/auxiliary-pane-close-visibility.spec.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { waitForAnimations } from "../helpers/animations";
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
type MockMessageWindow = Window & {
|
||||
__BUZZ_E2E_EMIT_MOCK_MESSAGE__?: (input: {
|
||||
channelName: string;
|
||||
content: string;
|
||||
parentEventId?: string | null;
|
||||
pubkey?: string;
|
||||
}) => { id: string } | undefined;
|
||||
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
|
||||
channelName: string;
|
||||
}) => boolean;
|
||||
};
|
||||
|
||||
const CHANNEL_NAME = "engineering";
|
||||
const MOCK_IDENTITY_PUBKEY = "deadbeef".repeat(8);
|
||||
const ALICE_PUBKEY =
|
||||
"953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f";
|
||||
|
||||
async function waitForMockLiveSubscription(
|
||||
page: import("@playwright/test").Page,
|
||||
channelName: string,
|
||||
) {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return page.evaluate((name) => {
|
||||
return (
|
||||
(
|
||||
window as MockMessageWindow
|
||||
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({ channelName: name }) ??
|
||||
false
|
||||
);
|
||||
}, channelName);
|
||||
})
|
||||
.toBe(true);
|
||||
}
|
||||
|
||||
test.describe("auxiliary pane close visibility", () => {
|
||||
test.use({ viewport: { width: 1280, height: 720 } });
|
||||
|
||||
// Regression for #6901: `isolate` on the right auxiliary pane makes it its
|
||||
// own stacking context. With no z-index (`auto`, i.e. level 0) the entire
|
||||
// pane subtree — including the z-40 header chrome where X/Edit live — paints
|
||||
// below the channel's sibling z-30 shared-header backdrop in split layout,
|
||||
// washing out the controls (they stay clickable because the backdrop is
|
||||
// pointer-events-none, matching the reported videos). The pane's own
|
||||
// stacking level must sit above the backdrop for the header to show through.
|
||||
test("close button paints above the shared header backdrop in a channel thread", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page);
|
||||
await page.goto("/");
|
||||
await page.getByTestId(`channel-${CHANNEL_NAME}`).click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText(CHANNEL_NAME);
|
||||
await waitForMockLiveSubscription(page, CHANNEL_NAME);
|
||||
|
||||
const rootId = await page.evaluate(
|
||||
({ channelName, pubkey }) =>
|
||||
(window as MockMessageWindow).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||||
channelName,
|
||||
content: "Root message for auxiliary pane close visibility.",
|
||||
pubkey,
|
||||
})?.id ?? null,
|
||||
{ channelName: CHANNEL_NAME, pubkey: MOCK_IDENTITY_PUBKEY },
|
||||
);
|
||||
expect(rootId).not.toBeNull();
|
||||
|
||||
await page.evaluate(
|
||||
({ channelName, parentEventId, pubkey }) => {
|
||||
(window as MockMessageWindow).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||||
channelName,
|
||||
content: "Reply that opens a split thread panel.",
|
||||
parentEventId,
|
||||
pubkey,
|
||||
});
|
||||
},
|
||||
{
|
||||
channelName: CHANNEL_NAME,
|
||||
parentEventId: rootId,
|
||||
pubkey: ALICE_PUBKEY,
|
||||
},
|
||||
);
|
||||
|
||||
const replyButton = page.locator('[data-testid^="reply-message-"]').first();
|
||||
await expect(replyButton).toBeVisible();
|
||||
await replyButton.click({ force: true });
|
||||
await expect(page.getByTestId("message-thread-panel")).toBeVisible();
|
||||
await waitForAnimations(page);
|
||||
|
||||
const closeButton = page.getByTestId("auxiliary-panel-close");
|
||||
await expect(closeButton).toBeVisible();
|
||||
|
||||
const backdrop = page.getByTestId("channel-shared-header-backdrop");
|
||||
await expect(backdrop).toHaveCount(1);
|
||||
|
||||
// The pane is an isolated stacking context (`isolate`) that sits over the
|
||||
// channel timeline. Its close button paints correctly only when the pane's
|
||||
// own stacking level clears the shared-header backdrop it overlaps. Assert
|
||||
// the pane both establishes that context and outranks the backdrop.
|
||||
const pane = page.getByTestId("message-thread-panel");
|
||||
const [paneIsolation, paneZIndex, backdropZIndex] = await Promise.all([
|
||||
pane.evaluate((element) => getComputedStyle(element).isolation),
|
||||
pane.evaluate((element) => Number(getComputedStyle(element).zIndex)),
|
||||
backdrop.evaluate((element) => Number(getComputedStyle(element).zIndex)),
|
||||
]);
|
||||
|
||||
expect(paneIsolation).toBe("isolate");
|
||||
expect(paneZIndex).toBeGreaterThan(backdropZIndex);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user