From 5bf12ab7839d9f25ac5baf70f4611909fbe0770a Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Fri, 3 Apr 2026 11:22:55 -0500
Subject: [PATCH] chore: cleanup subagent header
---
.../session/session-child-navigation.spec.ts | 17 +
.../src/pages/session/message-timeline.tsx | 360 ++++++++++--------
2 files changed, 221 insertions(+), 156 deletions(-)
diff --git a/packages/app/e2e/session/session-child-navigation.spec.ts b/packages/app/e2e/session/session-child-navigation.spec.ts
index 4b21223846..c9fad1af85 100644
--- a/packages/app/e2e/session/session-child-navigation.spec.ts
+++ b/packages/app/e2e/session/session-child-navigation.spec.ts
@@ -29,6 +29,9 @@ test("task tool child-session link does not trigger stale show errors", async ({
await project.gotoSession(session.id)
+ const header = page.locator("[data-session-title]")
+ await expect(header.getByRole("button", { name: "More options" })).toBeVisible({ timeout: 30_000 })
+
const card = page
.locator('[data-component="task-tool-card"]')
.filter({ hasText: /open child session/i })
@@ -37,6 +40,20 @@ test("task tool child-session link does not trigger stale show errors", async ({
await card.click()
await expect(page).toHaveURL(new RegExp(`/session/${child.sessionID}(?:[/?#]|$)`), { timeout: 30_000 })
+ await expect(header.locator('[data-slot="session-title-parent"]')).toHaveText(session.title)
+ await expect(header.locator('[data-slot="session-title-child"]')).toHaveText(taskInput.description)
+ await expect(header.locator('[data-slot="session-title-separator"]')).toHaveText("/")
+ await expect
+ .poll(
+ () =>
+ header.locator('[data-slot="session-title-separator"]').evaluate((el) => ({
+ left: getComputedStyle(el).paddingLeft,
+ right: getComputedStyle(el).paddingRight,
+ })),
+ { timeout: 30_000 },
+ )
+ .toEqual({ left: "8px", right: "8px" })
+ await expect(header.getByRole("button", { name: "More options" })).toHaveCount(0)
await expect(page.getByText("Subagent sessions cannot be prompted.")).toBeVisible({ timeout: 30_000 })
await expect(page.getByRole("button", { name: "Back to main session." })).toBeVisible({ timeout: 30_000 })
await expect.poll(() => errs, { timeout: 5_000 }).toEqual([])
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx
index 14983094ab..349acd5720 100644
--- a/packages/app/src/pages/session/message-timeline.tsx
+++ b/packages/app/src/pages/session/message-timeline.tsx
@@ -68,6 +68,14 @@ const messageComments = (parts: Part[]): MessageComment[] =>
]
})
+const taskDescription = (part: Part, sessionID: string) => {
+ if (part.type !== "tool" || part.tool !== "task") return
+ const metadata = "metadata" in part.state ? part.state.metadata : undefined
+ if (metadata?.sessionId !== sessionID) return
+ const value = part.state.input?.description
+ if (typeof value === "string" && value) return value
+}
+
const boundaryTarget = (root: HTMLElement, target: EventTarget | null) => {
const current = target instanceof Element ? target : undefined
const nested = current?.closest("[data-scrollable]")
@@ -300,8 +308,27 @@ export function MessageTimeline(props: {
if (!id) return
return sync.session.get(id)
})
+ const parentMessages = createMemo(() => {
+ const id = parentID()
+ if (!id) return emptyMessages
+ return sync.data.message[id] ?? emptyMessages
+ })
const parentTitle = createMemo(() => sessionTitle(parent()?.title) ?? language.t("command.session.new"))
- const childTitle = createMemo(() => titleLabel() ?? (parentID() ? language.t("command.session.new") : ""))
+ const childTaskDescription = createMemo(() => {
+ const id = sessionID()
+ if (!id) return
+ return parentMessages()
+ .flatMap((message) => sync.data.part[message.id] ?? [])
+ .map((part) => taskDescription(part, id))
+ .findLast((value): value is string => !!value)
+ })
+ const childTitle = createMemo(() => {
+ if (!parentID()) return titleLabel() ?? ""
+ if (childTaskDescription()) return childTaskDescription()
+ const value = titleLabel()?.replace(/\s+\(@[^)]+ subagent\)$/, "")
+ if (value) return value
+ return language.t("command.session.new")
+ })
const showHeader = createMemo(() => !!(titleValue() || parentID()))
const stageCfg = { init: 1, batch: 3 }
const staging = createTimelineStaging({
@@ -405,8 +432,20 @@ export function MessageTimeline(props: {
),
)
+ createEffect(
+ on(
+ () => [parentID(), childTaskDescription()] as const,
+ ([id, description]) => {
+ if (!id || description) return
+ if (sync.data.message[id] !== undefined) return
+ void sync.session.sync(id)
+ },
+ { defer: true },
+ ),
+ )
+
const openTitleEditor = () => {
- if (!sessionID()) return
+ if (!sessionID() || parentID()) return
setTitle({ editing: true, draft: titleLabel() ?? "" })
requestAnimationFrame(() => {
titleRef?.focus()
@@ -660,12 +699,17 @@ export function MessageTimeline(props: {