mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
**Category:** fix **User Impact:** Expanded thread panels now stay fully visible within the desktop channel area instead of being cut off. **Problem:** The resize handler clamped the thread panel against the full window width, even though the panel renders inside a narrower channel surface. On a 1720px window, this allowed a 1160px requested width where only 1111px could render, leaving persisted and visible geometry out of sync. **Solution:** Clamp resizing against the measured channel-surface width so the stored width matches what the layout can render while preserving the minimum 300px main pane. <details> <summary>File changes</summary> **desktop/src/features/channels/ui/ChannelScreen.tsx** Passes the measured channel-surface width into the thread-panel sizing hook. **desktop/src/shared/hooks/useThreadPanelWidth.ts** Clamps drag-resize updates against the available channel width instead of the full viewport. **desktop/tests/e2e/threadpane-ultrawide.spec.ts** Adds a 1720px regression proving the requested and rendered panel widths match, while retaining the ultrawide expansion case. </details> ### Reproduction steps 1. Open a channel thread in the desktop app at a 1720×900 window size. 2. Drag the thread panel's left resize handle toward the left edge to expand it as far as possible. 3. Confirm the panel remains fully bounded inside the channel surface and the main channel pane remains at least 300px wide. 4. Reload the channel and confirm the persisted expanded width renders without clipping. ### Testing - `pnpm --dir desktop build:e2e` - `pnpm --dir desktop exec playwright test tests/e2e/threadpane-ultrawide.spec.ts` — 2 passed - Push hooks: `desktop-check` and `desktop-test` passed - `git diff --check origin/main..HEAD` ### Screenshot  ### Related issue None found. Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
161 lines
5.1 KiB
TypeScript
161 lines
5.1 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge";
|
|
|
|
// Ultrawide viewport: 3440px is a common 21:9 monitor width.
|
|
const ULTRAWIDE = { width: 3440, height: 1440 };
|
|
|
|
async function waitForMockLiveSubscription(
|
|
page: import("@playwright/test").Page,
|
|
channelName: string,
|
|
) {
|
|
await expect
|
|
.poll(async () =>
|
|
page.evaluate(
|
|
({ ch }) =>
|
|
(
|
|
window as Window & {
|
|
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
|
|
channelName: string;
|
|
}) => boolean;
|
|
}
|
|
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({ channelName: ch }) ??
|
|
false,
|
|
{ ch: channelName },
|
|
),
|
|
)
|
|
.toBe(true);
|
|
}
|
|
|
|
async function emitMockReply(
|
|
page: import("@playwright/test").Page,
|
|
channelName: string,
|
|
content: string,
|
|
parentEventId: string,
|
|
) {
|
|
await page.evaluate(
|
|
({ ch, msg, parent, pubkey }) =>
|
|
(
|
|
window as Window & {
|
|
__BUZZ_E2E_EMIT_MOCK_MESSAGE__?: (input: {
|
|
channelName: string;
|
|
content: string;
|
|
parentEventId?: string | null;
|
|
pubkey?: string;
|
|
createdAt?: number;
|
|
}) => unknown;
|
|
}
|
|
).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
|
channelName: ch,
|
|
content: msg,
|
|
parentEventId: parent,
|
|
pubkey,
|
|
createdAt: Math.floor(Date.now() / 1000) - 10,
|
|
}),
|
|
{
|
|
ch: channelName,
|
|
msg: content,
|
|
parent: parentEventId,
|
|
pubkey: TEST_IDENTITIES.alice.pubkey,
|
|
},
|
|
);
|
|
}
|
|
|
|
async function openThread(page: import("@playwright/test").Page) {
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
await waitForMockLiveSubscription(page, "general");
|
|
|
|
// Seed a reply so a thread summary row appears, then open it.
|
|
await emitMockReply(
|
|
page,
|
|
"general",
|
|
"Reply to welcome",
|
|
"mock-general-welcome",
|
|
);
|
|
const threadSummary = page.getByTestId("message-thread-summary").first();
|
|
await expect(threadSummary).toBeVisible();
|
|
await threadSummary.click();
|
|
await expect(page.getByTestId("message-thread-panel")).toBeVisible();
|
|
}
|
|
|
|
test.describe("thread pane on ultrawide monitors", () => {
|
|
test("expands well past the legacy 720px cap", async ({ page }) => {
|
|
await page.setViewportSize(ULTRAWIDE);
|
|
await installMockBridge(page);
|
|
await openThread(page);
|
|
|
|
const pane = page.getByTestId("message-thread-panel");
|
|
const handle = page.getByTestId("right-auxiliary-pane-resize-handle");
|
|
|
|
const beforeBox = await pane.boundingBox();
|
|
if (!beforeBox) throw new Error("thread panel not laid out");
|
|
// The panel opens at its default narrow width, far from the viewport edge.
|
|
expect(beforeBox.width).toBeLessThan(720);
|
|
await page.screenshot({
|
|
path: "test-results/threadpane-ultrawide-before.png",
|
|
});
|
|
|
|
// Drag the resize handle far to the left to widen the right-hand pane.
|
|
const handleBox = await handle.boundingBox();
|
|
if (!handleBox) throw new Error("resize handle not laid out");
|
|
const startX = handleBox.x + handleBox.width / 2;
|
|
const startY = handleBox.y + handleBox.height / 2;
|
|
await page.mouse.move(startX, startY);
|
|
await page.mouse.down();
|
|
// Move left in steps so pointermove fires repeatedly.
|
|
for (let x = startX; x >= 600; x -= 120) {
|
|
await page.mouse.move(x, startY);
|
|
}
|
|
await page.mouse.up();
|
|
|
|
const afterBox = await pane.boundingBox();
|
|
if (!afterBox) throw new Error("thread panel not laid out after resize");
|
|
// The pane is now far wider than the old 720px hard cap.
|
|
expect(afterBox.width).toBeGreaterThan(1200);
|
|
await page.screenshot({
|
|
path: "test-results/threadpane-ultrawide-after.png",
|
|
});
|
|
});
|
|
|
|
test("clamps the requested width to the channel surface", async ({
|
|
page,
|
|
}) => {
|
|
await page.setViewportSize({ width: 1720, height: 900 });
|
|
await installMockBridge(page);
|
|
await openThread(page);
|
|
|
|
const pane = page.getByTestId("message-thread-panel");
|
|
const handle = page.getByTestId("right-auxiliary-pane-resize-handle");
|
|
const handleBox = await handle.boundingBox();
|
|
if (!handleBox) throw new Error("resize handle not laid out");
|
|
|
|
const startX = handleBox.x + handleBox.width / 2;
|
|
const startY = handleBox.y + handleBox.height / 2;
|
|
await page.mouse.move(startX, startY);
|
|
await page.mouse.down();
|
|
for (let x = startX; x >= 500; x -= 60) {
|
|
await page.mouse.move(x, startY);
|
|
}
|
|
await page.mouse.up();
|
|
|
|
const renderedWidth = await pane.evaluate((element) =>
|
|
Math.round(element.getBoundingClientRect().width),
|
|
);
|
|
const storedWidth = await page.evaluate(() =>
|
|
Number(window.sessionStorage.getItem("buzz.desktop.thread-panel-width")),
|
|
);
|
|
const mainWidth = await page
|
|
.getByTestId("channel-drop-zone")
|
|
.evaluate((element) => Math.round(element.getBoundingClientRect().width));
|
|
|
|
await page.screenshot({
|
|
path: "test-results/threadpane-expanded-after-fix.png",
|
|
});
|
|
|
|
expect(renderedWidth).toBe(storedWidth);
|
|
expect(mainWidth).toBeGreaterThanOrEqual(300);
|
|
});
|
|
});
|