fix(app): put the sidebar channel icon under the macOS traffic lights (#57235)

Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-10-20 00:56:51 +00:00
committed by GitHub
Unverified
parent 0871812772
commit e50f112667
2 changed files with 64 additions and 8 deletions
+39
View File
@@ -463,6 +463,45 @@ for (const [window, fullscreen, start] of [
})
}
// macOS 26 traffic lights sit 14px down and are 14px tall. With vertical tabs the channel icon goes below them, over
// Home's icon. A fullscreen window has no traffic lights, so the icon stays in the header beside the sidebar toggle.
for (const [window, fullscreen] of [
["windowed", false],
["fullscreen", true],
] as const) {
test(`sidebar channel icon sits below the macOS traffic lights (${window})`, async ({ page }) => {
await mockWorkspace(page, {
name: "Tabs",
sessions: [a],
seed: { settings: { appearance: { tabLayout: "vertical" } } },
})
await page.goto(
`/e2e/utils/macos-window.html?${new URLSearchParams({ server: SERVER, route: "/", fullscreen: String(fullscreen) })}`,
)
const sidebar = page.locator('[data-slot="vertical-tabs-sidebar"]')
const indicator = sidebar.locator('[data-slot="channel-indicator"]')
await expect(indicator).toBeVisible()
const icon = await indicator.boundingBox()
const toggle = await sidebar.getByRole("button", { name: "Toggle sidebar", exact: true }).boundingBox()
const home = await sidebar.getByRole("button", { name: "Home", exact: true }).boundingBox()
const homeIcon = await sidebar.getByRole("button", { name: "Home", exact: true }).locator("svg").boundingBox()
if (!icon || !toggle || !home || !homeIcon) throw new Error("sidebar header has no bounding boxes")
if (fullscreen) {
expect(icon.y + icon.height / 2).toBe(toggle.y + toggle.height / 2)
return
}
expect(icon.y).toBeGreaterThan(14 + 14)
expect(icon.x + icon.width / 2).toBe(homeIcon.x + homeIcon.width / 2)
expect(icon.y + icon.height).toBeLessThan(home.y)
// The toggle keeps the session header's center line beside the icon.
expect(toggle.y + toggle.height / 2).toBe(32)
})
}
test("vertical tab gaps hover and select the nearest row", async ({ page }) => {
await mockWorkspace(page, {
name: "Tabs",
+25 -8
View File
@@ -114,6 +114,7 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
const titlebarItems = useTitlebarItems()
const hideVerticalTitlebar = createMemo(() => !!props.verticalTabs && !windows())
const homeInSidebarHeader = () => !windows() && !macTrafficLights() && !channelIndicatorVisible
const channelIndicatorBelowTrafficLights = () => macTrafficLights() && channelIndicatorVisible
const back = () => {
const next = backPath(history)
@@ -782,12 +783,15 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
>
<Show when={!windows()}>
{/* Shares the session header's 48px center line. Without traffic lights or a channel
icon, Home fills the start so the row doesn't leave an empty band. */}
icon, Home fills the start so the row doesn't leave an empty band. With both, the
channel icon goes under the traffic lights and the row grows only by what it needs. */}
<div
class="flex w-full shrink-0 gap-2"
classList={{
"items-start pt-2.5": homeInSidebarHeader(),
"mb-2 h-12 items-center": !homeInSidebarHeader(),
"mb-2 h-12 items-center":
!homeInSidebarHeader() && !channelIndicatorBelowTrafficLights(),
"mb-2 items-start": channelIndicatorBelowTrafficLights(),
}}
style={{
"min-height": macVerticalTabs()
@@ -799,23 +803,36 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
<Show when={macTrafficLights()}>
{/* The sidebar's 10px start padding already covers part of the traffic lights. */}
<div
class="shrink-0"
style={{ width: `calc(${macTrafficLightsBaseWidth / zoom()}px - 10px)` }}
aria-hidden="true"
/>
class="flex shrink-0"
style={{
width: `calc(${macTrafficLightsBaseWidth / zoom()}px - 10px)`,
"padding-top": channelIndicatorBelowTrafficLights()
? `${macTrafficLightsTopClearance / zoom()}px`
: undefined,
}}
>
<ChannelIndicator sidebar />
</div>
</Show>
<Show
when={homeInSidebarHeader()}
fallback={
<>
<ChannelIndicator sidebar />
<Show when={!macTrafficLights()}>
<ChannelIndicator sidebar />
</Show>
<div class="flex-1" />
</>
}
>
<div class="min-w-0 flex-1">{homeButton(true)}</div>
</Show>
<SidebarToggle />
<div
class="flex shrink-0"
classList={{ "mt-2.5": channelIndicatorBelowTrafficLights() }}
>
<SidebarToggle />
</div>
</div>
</Show>
<Show when={!homeInSidebarHeader()}>{homeButton(true)}</Show>