Skip to content

Commit df88a55

Browse files
committed
test(app): update background-shortcut e2e for the shell panel
Clicking a shell in the activity pill now opens its output in the menu instead of revealing its call in the timeline, so four e2e tests expected the old behaviour: - "separates blocking and already-backgrounded work" now checks that the shell row opens the panel, goes Back, and opens the Used group itself before checking the shell call's shimmer. - "holds a revealed shell ... under its stuck group header" (3 cases) now reveal a starting subagent call, the pill row that still reveals its call, with subagents grouped so the call sits in the collapsed group. They keep covering revealPart's group expansion and stuck-header hold. Nothing reveals a call by shell ID any more, so revealPart drops that match.
1 parent 263d11c commit df88a55

4 files changed

Lines changed: 34 additions & 65 deletions

File tree

‎packages/app/e2e/regression/timeline-stream.spec.ts‎

Lines changed: 29 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,14 +1352,11 @@ test.describe("background shortcut", () => {
13521352
/^Explore\s*Background task\d+h \d{2}m$/,
13531353
/^sleep 120\d+h \d{2}m$/,
13541354
])
1355+
// A shell opens its output in the menu rather than revealing its call in the timeline.
13551356
await list.getByRole("menuitem", { name: /sleep 120/ }).click()
1356-
await expect(list).toHaveCount(0)
1357-
await expect(used).toHaveAttribute("aria-expanded", "true")
1358-
const shellCall = page.locator('[data-timeline-part-id="call_shell_backgrounded"]')
1359-
await expect(shellCall.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true")
1360-
await expect(shellCall).toBeInViewport()
1361-
1362-
await activity.click()
1357+
await expect(list).toHaveAttribute("data-view", "shell")
1358+
await expect(used).toHaveAttribute("aria-expanded", "false")
1359+
await list.getByRole("menuitem", { name: "Back to background work", exact: true }).click()
13631360

13641361
const interrupt = page.waitForRequest(
13651362
(request) =>
@@ -1384,6 +1381,7 @@ test.describe("background shortcut", () => {
13841381
await expect(list).toHaveCount(0)
13851382
await expect(backgroundCard).toContainText("Background task (background)")
13861383
await expect(backgroundCard.locator('[data-component="session-progress-indicator-v2"]')).toBeVisible()
1384+
await used.click()
13871385
await expect(
13881386
page.locator('[data-timeline-part-id="call_shell_backgrounded"] [data-component="text-shimmer"]'),
13891387
).toHaveAttribute("data-active", "true")
@@ -1407,24 +1405,19 @@ test.describe("background shortcut", () => {
14071405
// Reaching the shell lands on the end of the timeline, which would otherwise follow new content again.
14081406
{ name: "at the end of the timeline", before: 40, searches: 0, after: 0 },
14091407
]) {
1410-
test(`holds a revealed shell ${fixture.name} just under its stuck group header`, async ({ page }) => {
1408+
// A starting subagent call is the pill's row that reveals its call; grouping subagents puts it inside the group.
1409+
test(`holds a revealed call ${fixture.name} just under its stuck group header`, async ({ page }) => {
14111410
const timeline = await setupTimeline(page, {
1412-
settings: { timelineDetail: detailed },
1411+
settings: { timelineDetail: { ...detailed, subagents: { placement: "grouped" } } },
14131412
sessionStatus: { [sessionID]: { type: "busy" } },
14141413
messages: [
14151414
userMessage(),
14161415
assistantMessage(
14171416
[
14181417
...(fixture.before ? [textPart("prt_earlier", notes("Earlier", fixture.before))] : []),
14191418
...searches(0, 42 - fixture.searches),
1420-
toolPart(
1421-
"prt_far_shell",
1422-
"shell",
1423-
"completed",
1424-
{ command: "sleep 120" },
1425-
{ output: "working", metadata: { shellID: "shell_far", status: "running" } },
1426-
),
1427-
// The open group outgrows the viewport, so aligning the group instead of the shell misses it.
1419+
toolPart("prt_far_call", "subagent", "running", { description: "Map the far code", agent: "explore" }),
1420+
// The open group outgrows the viewport, so aligning the group instead of the call misses it.
14281421
...searches(42 - fixture.searches, fixture.searches),
14291422
...(fixture.after ? [textPart("prt_follow_up", notes("Follow-up", fixture.after))] : []),
14301423
],
@@ -1433,60 +1426,40 @@ test.describe("background shortcut", () => {
14331426
],
14341427
})
14351428

1436-
await timeline.transport.send({
1437-
id: "evt_far_shell_created",
1438-
created: 3,
1439-
type: "shell.created",
1440-
location: { directory },
1441-
data: {
1442-
info: {
1443-
id: "shell_far",
1444-
status: "running",
1445-
command: "sleep 120",
1446-
cwd: directory,
1447-
shell: "bash",
1448-
file: "/tmp/far.out",
1449-
metadata: { sessionID },
1450-
time: { started: 2 },
1451-
},
1452-
},
1453-
})
1454-
14551429
const group = page
14561430
.locator('[data-component="collapsed-tool-group"]')
14571431
.locator(':scope > [data-component="collapsible"] > [data-slot="collapsible-trigger"]')
14581432

1459-
const shellTrigger = page.locator('[data-timeline-part-id="prt_far_shell"] [data-slot="collapsible-trigger"]')
1433+
const call = page.locator('[data-timeline-part-id="prt_far_call"]')
14601434

14611435
const aligned = () =>
14621436
expect
14631437
.poll(async () => {
1464-
const [header, shell] = await Promise.all([group.boundingBox(), shellTrigger.boundingBox()])
1438+
const [header, row] = await Promise.all([group.boundingBox(), call.boundingBox()])
14651439

1466-
return header && shell ? Math.round(shell.y - (header.y + header.height)) : undefined
1440+
return header && row ? Math.round(row.y - (header.y + header.height)) : undefined
14671441
})
14681442
.toBe(0)
14691443

14701444
await expect(
14711445
fixture.after ? page.getByText(`Follow-up note ${fixture.after}.`, { exact: true }) : group,
14721446
).toBeInViewport()
1473-
await expect(shellTrigger).toHaveCount(0)
1474-
await page.getByRole("button", { name: "1 shell", exact: true }).click()
1447+
await expect(call).toHaveCount(0)
1448+
await page.getByRole("button", { name: "1 subagent", exact: true }).click()
14751449
await page
1476-
.getByRole("menu", { name: "1 shell", exact: true })
1477-
.getByRole("menuitem", { name: /sleep 120/ })
1450+
.getByRole("menu", { name: "1 subagent", exact: true })
1451+
.getByRole("menuitem", { name: /Map the far code/ })
14781452
.click()
14791453

14801454
await expect(group).toHaveAttribute("aria-expanded", "true")
1481-
await expect(shellTrigger).toHaveAttribute("aria-expanded", "true")
1482-
await expect(shellTrigger).toBeInViewport()
1455+
await expect(call).toBeInViewport()
14831456

1484-
// A shell at the very end cannot rise under the header until more content arrives below it.
1457+
// A call at the very end cannot rise under the header until more content arrives below it.
14851458
if (fixture.searches) await aligned()
14861459
// Nothing covers the row, so it takes the next click.
1487-
await shellTrigger.click({ trial: true })
1460+
await call.click({ trial: true })
14881461

1489-
// The agent keeps writing below the shell; the shell stays where it was revealed.
1462+
// The agent keeps writing below the call; the call stays where it was revealed.
14901463
await timeline.send(partUpdated(textPart("prt_late", notes("Late", 30))))
14911464
await expect(page.getByText("Late note 30.", { exact: true })).toBeAttached()
14921465
await aligned()
@@ -2142,10 +2115,7 @@ test("highlights a quote's source only while its first comment is written", asyn
21422115
await expect
21432116
.poll(() =>
21442117
first.evaluate((element) =>
2145-
[
2146-
"::selection",
2147-
"::highlight(timeline-quote-source)",
2148-
].map((pseudo) => {
2118+
["::selection", "::highlight(timeline-quote-source)"].map((pseudo) => {
21492119
const style = getComputedStyle(element, pseudo)
21502120

21512121
return {
@@ -2245,11 +2215,13 @@ test("keeps the native selection height after quoting wrapped and formatted text
22452215
const height = await source.locator("p").evaluate((element) => parseFloat(getComputedStyle(element).lineHeight))
22462216
await expect
22472217
.poll(() =>
2248-
source.locator('[data-component="timeline-quote-background"] > span').evaluateAll(
2249-
(lines, height) =>
2250-
lines.length > 0 && lines.every((line) => Math.abs(line.getBoundingClientRect().height - height) < 0.1),
2251-
height,
2252-
),
2218+
source
2219+
.locator('[data-component="timeline-quote-background"] > span')
2220+
.evaluateAll(
2221+
(lines, height) =>
2222+
lines.length > 0 && lines.every((line) => Math.abs(line.getBoundingClientRect().height - height) < 0.1),
2223+
height,
2224+
),
22532225
)
22542226
.toBe(true)
22552227
})

‎packages/app/src/session/composer/region.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ export function ActiveSessionComposerRegion(props: {
449449

450450
if (item.sessionID) return openRoute(current, item.sessionID)
451451

452-
// Shells and starting subagent calls live in this timeline.
452+
// A starting subagent call has no session yet, so it lives in this timeline.
453453
props.onReveal(item.target)
454454
}
455455

‎packages/app/src/session/session-identity-header.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ export function SessionAncestorTrail(props: {
507507
}
508508

509509
// Opens a related session in the tab showing `from`, and records its parent so the tab's route stays consistent.
510-
// `reveal` names a tool call or shell the opened timeline scrolls to and expands.
510+
// `reveal` names a tool call the opened timeline scrolls to and expands.
511511
export function useOpenSessionRoute() {
512512
const server = useServer()
513513
const tabs = useTabs()

‎packages/app/src/session/timeline/virtualizer.tsx‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -429,18 +429,15 @@ export function createTimelineVirtualizer(input: Input) {
429429
flushTouchAdjustment()
430430
}
431431

432-
// Scrolls to a tool call, named by its ID or by the shell ID a backgrounded shell call reports, and expands it and
433-
// the collapsed context group that hides it. Returns whether the call is in the list.
432+
// Scrolls to a tool call, named by its ID, and expands it and the collapsed context group that hides it. Returns
433+
// whether the call is in the list.
434434
function revealPart(target: string) {
435435
if (!active()) return false
436436

437437
const matches = (ref: { messageID: string; partID: string }) => {
438438
const content = Timeline.resolveContent(input.projection.messageByID().get(ref.messageID), ref.partID)
439439

440-
return (
441-
content?.type === "tool" &&
442-
(content.id === target || (content.state.status !== "streaming" && content.state.metadata?.shellID === target))
443-
)
440+
return content?.type === "tool" && content.id === target
444441
}
445442

446443
const found = rows()

0 commit comments

Comments
 (0)