fix(app): collapse session location names only in narrow headers (#57870)

This commit is contained in:
usrnk1
2026-10-21 19:53:53 +02:00
committed by GitHub
Unverified
parent 1744121801
commit 4d680fbb31
2 changed files with 95 additions and 12 deletions
@@ -130,6 +130,66 @@ test.describe("location", () => {
sessions: [{ id: "ses_location", title: "Location session", directory: worktree }],
}
test("only collapses location names when the timeline header is narrow", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 })
await openSession(page, {
...workspace,
sessions: [{ id: "ses_location", title: "Investigate session header layout regression", directory: worktree }],
})
const header = page.locator("[data-session-title]")
const names = header.locator('[data-slot="session-location-project"], [data-slot="session-location-worktree"]')
const review = page.getByRole("button", { name: "Toggle review", exact: true })
await expect(names).toHaveText(["Location", "quiet-falcon"])
for (const panel of [false, true, false]) {
if (panel || (await review.getAttribute("aria-expanded")) === "true") await review.click()
await expect(review).toHaveAttribute("aria-expanded", String(panel))
await expect
.poll(() =>
names.evaluateAll((elements) => elements.every((element) => getComputedStyle(element).clipPath === "none")),
)
.toBe(!panel)
await expect(header.getByRole("button", { name: "More options", exact: true })).toBeInViewport()
await expect
.poll(() => header.getByRole("heading").evaluate((element) => element.scrollWidth <= element.clientWidth))
.toBe(true)
if (panel) {
await expect
.poll(() =>
header.evaluate((element) => {
const icon = element.querySelector('svg:has(use[href="#opencode-v2-icon-outline-worktree"])')
const before = icon?.previousElementSibling?.getBoundingClientRect()
const separator = element.querySelector('[data-slot="session-title-separator"]')
const after = separator?.getBoundingClientRect()
const title = element.querySelector("h1")
const avatar = element
.querySelector('[data-slot="session-location-trigger"]')
?.firstElementChild?.getBoundingClientRect()
const bounds = icon?.getBoundingClientRect()
return bounds && before && after && avatar && separator && title
? [
before.left - avatar.right,
bounds.left - before.right,
after.left - bounds.right,
title.getBoundingClientRect().left +
parseFloat(getComputedStyle(title).paddingLeft) -
after.right +
parseFloat(getComputedStyle(separator).paddingRight),
]
: []
}),
)
.toEqual([10, 8, 8, 10])
const location = header.locator('[data-slot="session-location-trigger"]')
await location.hover()
await expect(location).toHaveCSS("padding-inline-start", "4px")
await expect(location).toHaveCSS("padding-inline-end", "4px")
}
await header.screenshot({ path: test.info().outputPath(`location-${panel ? "compact" : "expanded"}.png`) })
}
})
test("names the project and worktree, and the card copies the full path", async ({ page, context }) => {
await context.grantPermissions(["clipboard-read", "clipboard-write"])
await openSession(page, workspace)
@@ -3,6 +3,7 @@
}
.session-header-layout {
container: session-header / inline-size;
display: flex;
align-items: center;
gap: 2px;
@@ -20,20 +21,42 @@
.session-header-location > [data-slot="session-location-trigger"] {
max-inline-size: 100%;
gap: 6px;
padding-inline: 4px;
}
/* The header shows the location as the project avatar and, in a worktree, the worktree icon, so the title keeps the
row. The names stay for assistive tech, and the location card shows them in full. */
.session-header-location [data-slot="session-location-project"],
.session-header-location [data-slot="session-location-worktree"] {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
/* Use the header's available width, not the window: opening a side panel also narrows the timeline. */
@container session-header (max-width: 640px) {
.session-header-location {
gap: 4px;
}
.session-header-location > [data-slot="session-location-trigger"] {
gap: 8px;
padding-inline: 4px;
}
.session-header-location [data-component="project-avatar-v2"] {
margin-inline-end: 2px;
}
.session-header-location [data-slot="session-location-project"] + span {
padding-inline: 0;
}
.session-header-location > .session-location-separator[data-variant="slash"] {
/* Together with the row's 2px gap and the title's 4px padding, leave 10px before the title. */
padding-inline: 0 4px;
}
/* Keep the names available to assistive tech while the icons leave room for the title. */
.session-header-location [data-slot="session-location-project"],
.session-header-location [data-slot="session-location-worktree"] {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
}
.session-header-title {