From b5c927e90cb44740df1f547a4c94473c8e30a650 Mon Sep 17 00:00:00 2001 From: Thomas Petersen Date: Sun, 23 Aug 2026 23:29:34 -0400 Subject: [PATCH] fix(projects): stabilize workspace sheet transitions Collapse the context rail before a workspace drawer enters so the sheet keeps a fixed width, and hide the sidebar-style resize guide without removing resizing. Signed-off-by: Thomas Petersen Co-authored-by: Thomas Petersen --- .../src/features/channels/ui/ChannelPane.tsx | 4 +- .../channels/ui/RightAuxiliaryPane.tsx | 9 +++- .../projects/ui/ProjectChannelHome.tsx | 7 +++ .../projects/ui/ProjectContextRail.tsx | 4 +- .../projects/ui/ProjectHomeColumn.tsx | 1 + .../tests/e2e/project-commit-detail.spec.ts | 50 +++++++++++++++++-- 6 files changed, 67 insertions(+), 8 deletions(-) diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index 1e7a0985b7..701e0152d1 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -828,8 +828,8 @@ export const ChannelPane = React.memo(function ChannelPane({ ) : null} - {/* Keep focus drawers mounted until their exit animation finishes. */} - + {/* Serialize replacements so focus drawers keep one travel direction. */} + {channelManagementOpen && activeChannel ? ( void; onResizeStart: (event: React.PointerEvent) => void; + showResizeIndicator?: boolean; testId?: string; widthPx: number; }; @@ -23,6 +24,7 @@ export function RightAuxiliaryPane({ detached = false, onResetWidth, onResizeStart, + showResizeIndicator = true, testId, widthPx, }: RightAuxiliaryPaneProps) { @@ -56,7 +58,12 @@ export function RightAuxiliaryPane({ } type="button" > - + {showResizeIndicator ? ( + + ) : null}
{children} diff --git a/desktop/src/features/projects/ui/ProjectChannelHome.tsx b/desktop/src/features/projects/ui/ProjectChannelHome.tsx index 7d4619096e..d7b0d766fa 100644 --- a/desktop/src/features/projects/ui/ProjectChannelHome.tsx +++ b/desktop/src/features/projects/ui/ProjectChannelHome.tsx @@ -134,6 +134,12 @@ export function ProjectChannelHome({ null; const workspaceSheetOpen = workspaceSheetTab != null && workspaceRepository != null; + const previousWorkspaceSheetOpenRef = React.useRef(workspaceSheetOpen); + const workspaceSheetVisibilityChanged = + previousWorkspaceSheetOpenRef.current !== workspaceSheetOpen; + React.useEffect(() => { + previousWorkspaceSheetOpenRef.current = workspaceSheetOpen; + }, [workspaceSheetOpen]); const summaryVisible = summaryOpen && !workspaceSheetOpen; const openWorkspaceSheet = React.useCallback( @@ -390,6 +396,7 @@ export function ProjectChannelHome({ projects={projects} /> diff --git a/desktop/tests/e2e/project-commit-detail.spec.ts b/desktop/tests/e2e/project-commit-detail.spec.ts index 694a0e3bc2..beea06c584 100644 --- a/desktop/tests/e2e/project-commit-detail.spec.ts +++ b/desktop/tests/e2e/project-commit-detail.spec.ts @@ -774,6 +774,49 @@ test("latest files commit opens its detail without a divider", async ({ await expect(page.getByTestId("project-commit-detail")).toBeVisible(); }); +test("project workspace sheet enters at its settled width", async ({ + page, +}) => { + await enableProjectsFeature(page); + await installMockBridge(page); + await page.goto("/", { waitUntil: "domcontentloaded" }); + await page.getByTestId("open-projects-view").click(); + await openCreateProjectDialog(page); + await page.getByTestId("create-project-name").fill("sheet-motion-demo"); + await page.getByTestId("create-project-submit").click(); + await expect(page.getByTestId("project-channel-home")).toBeVisible(); + + const summaryColumn = page.getByTestId("project-home-summary-column"); + const resizeHandle = summaryColumn.getByTestId( + "right-auxiliary-pane-resize-handle", + ); + await expect(resizeHandle).toBeVisible(); + await resizeHandle.hover(); + await expect( + resizeHandle.getByTestId("right-auxiliary-pane-resize-indicator"), + ).toHaveCount(0); + const summaryRail = page.getByTestId("project-home-summary-rail"); + const openRailWidth = await summaryRail.evaluate( + (element) => element.getBoundingClientRect().width, + ); + expect(openRailWidth).toBeGreaterThan(0); + await page.getByTestId("project-home-context-tasks").click(); + await expect(page.getByTestId("project-home-workspace-sheet")).toBeVisible(); + + const collapsedRailWidth = await summaryRail.evaluate( + (element) => element.getBoundingClientRect().width, + ); + expect(collapsedRailWidth).toBeLessThanOrEqual(1); + const focusDrawer = page.getByTestId("focus-thread-drawer"); + const enteringDrawerWidth = (await focusDrawer.boundingBox())?.width ?? 0; + expect(enteringDrawerWidth).toBeGreaterThan(0); + await waitForAnimations(page); + const settledDrawerWidth = (await focusDrawer.boundingBox())?.width ?? 0; + expect( + Math.abs(settledDrawerWidth - enteringDrawerWidth), + ).toBeLessThanOrEqual(1); +}); + test("commit detail opens from the commits feed with a diff", async ({ page, }) => { @@ -806,10 +849,9 @@ test("commit detail opens from the commits feed with a diff", async ({ await expect(page.getByTestId("focus-thread-drawer")).toBeVisible(); await page.getByTestId("project-home-context-tasks").click(); await expect(page.getByTestId("project-home-workspace-sheet")).toBeVisible(); - await expect(page.getByTestId("focus-thread-drawer")).toHaveCSS( - "outline-style", - "none", - ); + const focusDrawer = page.getByTestId("focus-thread-drawer"); + await expect(focusDrawer).toHaveCount(1); + await expect(focusDrawer).toHaveCSS("outline-style", "none"); const workspaceSheet = page.getByTestId("project-home-workspace-sheet"); await expect(workspaceSheet).toHaveAttribute("data-tab", "issues"); await expect(