From 142b2d2bc421f34fcad4bc754b3dac15ea56a366 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 6 Nov 2026 10:14:37 +0100 Subject: [PATCH] fix(app): keep explorer tab labels whole beside the Duo status region MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured the iPhone Duo inner screen in portrait (669×951, iOS 27.1 simulator unfolded in DeviceHub): system top 82, top bar guide {16, 26, 509, 44}, status region occlusion {525, 0, 144, 82}. The tests now use those values instead of estimates. On that screen the explorer tab rail, trimmed to the status region, cut the Changes tab down to "C". The rail now starts below an occlusion it meets, like toasts; the workspace pane keeps the band. Co-authored-by: Elsa Granger --- .../screens/workspace/explorer-sidebar.tsx | 6 ++--- packages/app/src/window-layout/model.test.ts | 24 +++++++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/app/src/screens/workspace/explorer-sidebar.tsx b/packages/app/src/screens/workspace/explorer-sidebar.tsx index f9f791241a..2f26f72b6c 100644 --- a/packages/app/src/screens/workspace/explorer-sidebar.tsx +++ b/packages/app/src/screens/workspace/explorer-sidebar.tsx @@ -19,9 +19,6 @@ import { WindowChromeRegion, WindowChromeSafeArea } from "@/utils/desktop-window import { TOUCH_TARGET } from "@/components/ui/control-sizing"; import { useTopBandRow } from "@/window-layout"; -// One tab, the new-tab button and the header action stay usable beside an occlusion. -const TOP_BAND_MIN_RAIL_WIDTH = TOUCH_TARGET * 3; - interface ExplorerSidebarDockProps extends Pick< WorkspaceDesktopTabsRowProps, | "setHoveredCloseTabKey" @@ -125,7 +122,8 @@ export function ExplorerSidebarDock({ const topBand = useTopBandRow({ offset: 0, height: TOUCH_TARGET, - minimumWidth: TOP_BAND_MIN_RAIL_WIDTH, + // Trimming the rail beside the status region truncates its tab labels; start below instead. + minimumWidth: Number.POSITIVE_INFINITY, }); const railBandStyle = useMemo( () => ({ paddingTop: topBand.top, paddingRight: topBand.trailing }), diff --git a/packages/app/src/window-layout/model.test.ts b/packages/app/src/window-layout/model.test.ts index 9dff03af28..275c38e816 100644 --- a/packages/app/src/window-layout/model.test.ts +++ b/packages/app/src/window-layout/model.test.ts @@ -58,15 +58,13 @@ describe("resolveSafeAreaInsets", () => { }); }); -// Estimated, not measured: Duo inner portrait (669×951) with a ≈80pt rectangular top and a -// top-right status region 144pt wide (202pt with a Live Activity, from #7924's ActivityKit QA). -// The top bar's start (12pt) is a placeholder: the simulator boots folded and Xcode 27 ships -// no control to unfold it, so the inner screen's guide frame has not been measured. +// Measured: iPhone Duo inner screen, portrait (669×951), iOS 27.1 simulator unfolded in +// DeviceHub. The Live Activity width (202pt) is still an estimate from #7924's ActivityKit QA. const duo = { - systemTop: 80, - topBar: { x: 0, y: 12, width: 669, height: 44 }, - statusRegion: { x: 525, y: 0, width: 144, height: 80 }, - liveActivityRegion: { x: 467, y: 0, width: 202, height: 80 }, + systemTop: 82, + topBar: { x: 16, y: 26, width: 509, height: 44 }, + statusRegion: { x: 525, y: 0, width: 144, height: 82 }, + liveActivityRegion: { x: 467, y: 0, width: 202, height: 82 }, }; function duoLayout(occlusions: WindowLayoutRect[]): WindowLayout { @@ -80,7 +78,7 @@ function duoLayout(occlusions: WindowLayoutRect[]): WindowLayout { describe("resolveTopBand", () => { it("publishes the top bar's start and the occlusions when the band is in use", () => { expect(resolveTopBand(duo.systemTop, duoLayout([duo.statusRegion]))).toEqual({ - top: 12, + top: 26, occlusions: [duo.statusRegion], }); }); @@ -93,7 +91,7 @@ describe("resolveTopBand", () => { it("ignores occlusions when the top bar guide sits below the status region", () => { const below = layout({ - topBar: { x: 0, y: 80, width: 669, height: 44 }, + topBar: { x: 16, y: 82, width: 637, height: 44 }, occlusions: [duo.statusRegion], }); expect(resolveTopBand(duo.systemTop, below)).toBeNull(); @@ -162,7 +160,7 @@ describe("resolveTopBandClearance", () => { occlusions: [duo.statusRegion], minimumWidth: compactMinimum, }), - ).toEqual({ trailing: 0, top: 60 }); + ).toEqual({ trailing: 0, top: 48 }); }); it("starts below the lowest of several overlapping occlusions", () => { @@ -172,12 +170,12 @@ describe("resolveTopBandClearance", () => { occlusions: [duo.statusRegion, { x: 600, y: 0, width: 69, height: 96 }], minimumWidth: compactMinimum, }), - ).toEqual({ trailing: 0, top: 76 }); + ).toEqual({ trailing: 0, top: 62 }); }); it("leaves rows alone that do not overlap an occlusion", () => { const sidebarRow = { x: 0, y: duo.topBar.y + 8, width: 320, height: 44 }; - const rowBelow = { x: 0, y: 80, width: 669, height: 44 }; + const rowBelow = { x: 0, y: 82, width: 669, height: 44 }; for (const row of [sidebarRow, rowBelow]) { expect( resolveTopBandClearance({ row, occlusions: [duo.statusRegion], minimumWidth: 0 }),