fix(app): keep the phone titlebar clear of the iOS status bar blur (#53297)

This commit is contained in:
Luke Parker
2026-10-05 18:12:38 +10:00
committed by GitHub
Unverified
parent fad352c5d0
commit eab83d63fd
2 changed files with 27 additions and 3 deletions
@@ -30,9 +30,11 @@ for (const position of ["top", "bottom"] as const) {
const navigation = page.locator('[data-slot="session-mobile-view-navigation"]')
const more = navigation.getByRole("button", { name: "More options", exact: true })
const picker = tabs.getByRole("tab", { selected: true })
const message = page.locator(
`[data-timeline-row="UserMessage"][data-message-id="${fixture.expected.targetMessageIDs.at(-1)}"]`,
)
const composer = page.getByRole("textbox", { name: "Prompt", exact: true })
await expect(picker).toHaveText("Session")
await expect(message).toBeVisible()
@@ -40,9 +42,17 @@ for (const position of ["top", "bottom"] as const) {
await expect(tabs.getByRole("tab")).toHaveText(["Session", "Changes", "Files", "Terminal"])
await expect(tabs).toHaveCSS("padding-left", "0px")
await expect(tabs).toHaveCSS("padding-right", "0px")
if (position === "top") {
const titlebar = page.locator('[data-slot="titlebar-v2"]')
await expect(titlebar).toHaveCSS("padding-top", "16px")
await expect(titlebar).toHaveCSS("height", "44px")
}
await expect
.poll(async () => {
const bounds = await navigation.boundingBox()
return !!bounds && bounds.x >= 8 && bounds.x <= 9 && bounds.width >= 372 && bounds.width <= 374
})
.toBe(true)
@@ -51,6 +61,7 @@ for (const position of ["top", "bottom"] as const) {
const bar = await tabs.boundingBox()
const input = await composer.boundingBox()
const panel = await page.locator('[data-slot="session-chat-panel"]').boundingBox()
return !!bar && !!input && !!panel && Math.abs(bar.y - panel.y) <= 1 && bar.y + bar.height <= input.y
})
.toBe(true)
@@ -149,16 +160,19 @@ for (const position of ["top", "bottom"] as const) {
// The view resets to Session whenever the routed session changes, including through Home.
const trigger = page.locator('[data-slot="mobile-tabs-trigger"]')
const openTabs = async () => {
await trigger.click()
await expect(drawer).not.toHaveAttribute("data-transitioning")
}
const openTab = async (title: string) => {
await openTabs()
await drawer.locator('[data-slot="tab-link"]').filter({ hasText: title }).click()
await expect(drawer).toBeHidden()
await expect(trigger).toContainText(title)
}
await more.click()
await page.getByRole("menuitem", { name: "Usage", exact: true }).click()
await expect(page.getByText("Total Cost", { exact: true })).toBeVisible()
@@ -338,6 +352,7 @@ test.describe("touch", () => {
.poll(async () => {
const navigation = await settings.getByRole("button", { name: "Models", exact: true }).boundingBox()
const content = await panel.boundingBox()
return !!navigation && !!content && navigation.y + navigation.height <= content.y
})
.toBe(true)
+12 -3
View File
@@ -59,6 +59,7 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
const location = useLocation()
const mobile = createMediaQuery("(max-width: 767px)")
const bottom = createMemo(() => mobile() && settings.general.mobileTitlebarPosition() === "bottom")
const mobileTop = createMemo(() => platform.platform === "web" && mobile() && !bottom())
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
@@ -150,9 +151,17 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
platform.platform === "web"
? bottom()
? "calc(28px + max(8px, var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))))"
: "calc(28px + max(8px, env(safe-area-inset-top, 0px)))"
: mobileTop()
? "calc(28px + 16px + env(safe-area-inset-top, 0px))"
: "calc(28px + max(8px, env(safe-area-inset-top, 0px)))"
: undefined,
"padding-top": bottom() ? "0px" : "env(safe-area-inset-top, 0px)",
// iOS blurs page content just below the status bar in Home Screen web apps, so keep the phone titlebar row
// 16px clear of the safe area, as ChatGPT's phone header does.
"padding-top": bottom()
? "0px"
: mobileTop()
? "calc(16px + env(safe-area-inset-top, 0px))"
: "env(safe-area-inset-top, 0px)",
"padding-bottom": bottom() ? "var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))" : "0px",
"min-height": minHeight(),
// Keep native macOS traffic lights clear even when the desktop window is narrow.
@@ -512,7 +521,7 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
<div
class="h-full flex-1 overflow-hidden flex flex-row items-center gap-1.5 px-2 md:pe-3"
classList={{
"pt-[max(0px,calc(8px-env(safe-area-inset-top,0px)))]": !bottom() && !windows(),
"pt-[max(0px,calc(8px-env(safe-area-inset-top,0px)))]": !bottom() && !windows() && !mobileTop(),
"pb-[max(0px,calc(8px-var(--safe-area-inset-bottom,env(safe-area-inset-bottom,0px))))]": bottom(),
"pl-4": macTrafficLights(),
// Center the 20px app icon over the sidebar's 16px icon column.