Skip to content

Commit b47b5a5

Browse files
thomaspblockWintermute
andauthored
fix(desktop): back split thread headers (#7137)
## Summary - render an auxiliary panel's requested header backdrop in docked/split mode - preserve explicit transparent-backdrop behavior - cover a populated, scrolled thread pane so timeline content cannot bleed through its header ## Root cause `RightAuxiliaryPane` correctly paints above the channel's shared header backdrop so close/edit controls remain visible. The docked `AuxiliaryPanelHeader` branch, however, ignored its `backdrop` request, leaving scrolled thread content in that higher stacking context unbacked. ## Verification - desktop unit suite: 5,801 passed - desktop TypeScript: passed - Biome checks: passed (existing unrelated repository warnings only in the earlier full run) - targeted Playwright scroll regression: passed - ultrawide thread-pane Playwright coverage: passed Signed-off-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz> Co-authored-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz>
1 parent cb31449 commit b47b5a5

3 files changed

Lines changed: 124 additions & 16 deletions

File tree

desktop/src/shared/layout/AuxiliaryPanelHeader.tsx

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ function AuxiliaryPanelHeaderBackdrop({
9999
"pointer-events-none absolute inset-x-0 top-0 z-40 h-13",
100100
getAuxiliaryPanelSurfaceClass(surface),
101101
)}
102+
data-testid="auxiliary-panel-header-backdrop"
102103
/>
103104
);
104105
}
@@ -166,25 +167,30 @@ export function AuxiliaryPanelHeader({
166167
}
167168

168169
return (
169-
<div
170-
className={cn(
171-
"pointer-events-none relative z-40 overflow-visible",
172-
getAuxiliaryPanelSurfaceClass(
173-
resolvedTransparent ? "transparent" : surface,
174-
),
175-
channelChrome.negativeMargin,
176-
)}
177-
{...props}
178-
>
170+
<>
171+
{backdrop && backdropSurface !== "transparent" ? (
172+
<AuxiliaryPanelHeaderBackdrop surface={backdropSurface} />
173+
) : null}
179174
<div
180-
className="pointer-events-auto relative z-40 shrink-0 cursor-default select-none py-2 pl-5 pr-3"
181-
data-tauri-drag-region
175+
className={cn(
176+
"pointer-events-none relative z-40 overflow-visible",
177+
getAuxiliaryPanelSurfaceClass(
178+
resolvedTransparent ? "transparent" : surface,
179+
),
180+
channelChrome.negativeMargin,
181+
)}
182+
{...props}
182183
>
183-
<div className="flex h-9 min-w-0 items-center gap-2.5">
184-
{renderAuxiliaryPanelHeaderContent(children)}
184+
<div
185+
className="pointer-events-auto relative z-40 shrink-0 cursor-default select-none py-2 pl-5 pr-3"
186+
data-tauri-drag-region
187+
>
188+
<div className="flex h-9 min-w-0 items-center gap-2.5">
189+
{renderAuxiliaryPanelHeaderContent(children)}
190+
</div>
185191
</div>
186192
</div>
187-
</div>
193+
</>
188194
);
189195
}
190196

desktop/src/shared/layout/auxiliaryPanelContext.test.mjs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,54 @@ test("AuxiliaryPanelHeader renders a generic close action from context", () => {
177177
assert.match(html, /data-testid="auxiliary-panel-close"/);
178178
});
179179

180+
test("AuxiliaryPanelHeader adds its requested backdrop in docked mode", () => {
181+
const html = render(
182+
React.createElement(
183+
AuxiliaryPanel,
184+
{
185+
header: React.createElement(
186+
AuxiliaryPanelHeader,
187+
{ backdrop: true },
188+
React.createElement(AuxiliaryPanelHeaderGroup, null, "Title"),
189+
),
190+
layout: "split",
191+
onClose: () => {},
192+
widthPx: 420,
193+
},
194+
"Panel",
195+
),
196+
);
197+
198+
assert.match(html, /data-testid="auxiliary-panel-header-backdrop"/);
199+
assert.match(html, /pointer-events-none absolute inset-x-0 top-0 z-40 h-13/);
200+
});
201+
202+
test("AuxiliaryPanelHeader honors an explicit transparent docked backdrop", () => {
203+
const html = render(
204+
React.createElement(
205+
AuxiliaryPanel,
206+
{
207+
header: React.createElement(
208+
AuxiliaryPanelHeader,
209+
{ backdrop: true, backdropSurface: "transparent" },
210+
React.createElement(AuxiliaryPanelHeaderGroup, null, "Title"),
211+
),
212+
layout: "split",
213+
onClose: () => {},
214+
transparentChrome: true,
215+
widthPx: 420,
216+
},
217+
"Panel",
218+
),
219+
);
220+
221+
assert.doesNotMatch(html, /data-testid="auxiliary-panel-header-backdrop"/);
222+
assert.doesNotMatch(
223+
html,
224+
/pointer-events-none absolute inset-x-0 top-0 z-40 h-13/,
225+
);
226+
});
227+
180228
test("AuxiliaryPanelHeader keeps resize border in single-panel mode when requested", () => {
181229
const html = render(
182230
React.createElement(

desktop/tests/e2e/channel-shared-header-backdrop.spec.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async function waitForMockLiveSubscription(
4141
test.describe("channel shared header backdrop", () => {
4242
test.use({ viewport: { width: 1280, height: 720 } });
4343

44-
test("spans channel and split auxiliary columns with one backdrop", async ({
44+
test("backs a scrolled split auxiliary header above the shared channel backdrop", async ({
4545
page,
4646
}) => {
4747
await installMockBridge(page);
@@ -82,6 +82,43 @@ test.describe("channel shared header backdrop", () => {
8282
await replyButton.click({ force: true });
8383
await expect(page.getByTestId("message-thread-panel")).toBeVisible();
8484

85+
await page.evaluate(
86+
({ channelName, parentEventId, pubkey }) => {
87+
for (let index = 0; index < 24; index += 1) {
88+
(window as MockMessageWindow).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
89+
channelName,
90+
content: `Scrollable thread reply ${index + 1}. `.repeat(4),
91+
parentEventId,
92+
pubkey,
93+
});
94+
}
95+
},
96+
{
97+
channelName: CHANNEL_NAME,
98+
parentEventId: rootId,
99+
pubkey: ALICE_PUBKEY,
100+
},
101+
);
102+
103+
const threadBody = page.getByTestId("message-thread-body");
104+
await expect
105+
.poll(() =>
106+
threadBody.evaluate(
107+
(element) => element.scrollHeight > element.clientHeight,
108+
),
109+
)
110+
.toBe(true);
111+
await threadBody.evaluate((element) => {
112+
element.scrollTop = element.scrollHeight;
113+
element.dispatchEvent(new Event("scroll"));
114+
});
115+
await expect
116+
.poll(() => threadBody.evaluate((element) => element.scrollTop))
117+
.toBeGreaterThan(0);
118+
119+
const paneBackdrop = page.getByTestId("auxiliary-panel-header-backdrop");
120+
await expect(paneBackdrop).toHaveCount(1);
121+
85122
const sharedBackdrop = page.getByTestId("channel-shared-header-backdrop");
86123
await expect(sharedBackdrop).toHaveCount(1);
87124

@@ -93,6 +130,9 @@ test.describe("channel shared header backdrop", () => {
93130
const [
94131
hostBox,
95132
backdropBox,
133+
paneBackdropBox,
134+
paneBackdropBackground,
135+
paneBackdropFilter,
96136
backdropFilter,
97137
backdropZIndex,
98138
headerZIndex,
@@ -101,6 +141,13 @@ test.describe("channel shared header backdrop", () => {
101141
] = await Promise.all([
102142
page.getByTestId("channel-drop-zone").locator("..").boundingBox(),
103143
sharedBackdrop.boundingBox(),
144+
paneBackdrop.boundingBox(),
145+
paneBackdrop.evaluate(
146+
(element) => getComputedStyle(element).backgroundColor,
147+
),
148+
paneBackdrop.evaluate(
149+
(element) => getComputedStyle(element).backdropFilter,
150+
),
104151
sharedBackdrop.evaluate(
105152
(element) => getComputedStyle(element).backdropFilter,
106153
),
@@ -120,6 +167,13 @@ test.describe("channel shared header backdrop", () => {
120167

121168
expect(hostBox).not.toBeNull();
122169
expect(backdropBox).not.toBeNull();
170+
expect(paneBackdropBox).not.toBeNull();
171+
expect(Math.round(paneBackdropBox?.y ?? 0)).toBe(
172+
Math.round(backdropBox?.y ?? 0),
173+
);
174+
expect(Math.round(paneBackdropBox?.height ?? 0)).toBe(52);
175+
expect(paneBackdropBackground).not.toBe("rgba(0, 0, 0, 0)");
176+
expect(paneBackdropFilter).not.toBe("none");
123177
expect(Math.round(backdropBox?.x ?? 0)).toBe(Math.round(hostBox?.x ?? 0));
124178
expect(Math.round(backdropBox?.width ?? 0)).toBe(
125179
Math.round(hostBox?.width ?? 0),

0 commit comments

Comments
 (0)