mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
## 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>
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
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);
|
|
});
|
|
});
|