Compare commits

...
Author SHA1 Message Date
Hona b2af928895 fix(session-ui): count patched files in tool summaries
Grouped tool summaries used the raw tool-call count, so repeated patch calls reported the number of invocations instead of the distinct files changed. Derive the count from successful patch metadata while keeping the existing fallback for other tools.
2026-09-09 23:39:58 +00:00
4 changed files with 37 additions and 11 deletions
@@ -43,13 +43,14 @@ story("merges follow-up patches into one stack with a distinct file count", asyn
await root.getByRole("button", { name: "Start follow-up patch" }).click()
const usage = group.locator('[data-component="context-tool-group-trigger"] [data-slot="context-tool-group-usage"]')
await expect(usage.locator('[data-slot="context-tool-group-prefix"]')).toHaveText("Used")
await expect(usage.locator('[data-slot="context-tool-group-count"]')).toHaveText("3")
await expect(usage.locator('[data-slot="context-tool-group-count"]')).toHaveText("2")
await expect(patches).toHaveCount(1)
await expect(patches.getByText("2 files", { exact: true })).toBeVisible()
await root.getByRole("button", { name: "Finish follow-up patch" }).click()
await expect(usage.locator('[data-slot="context-tool-group-count"]')).toHaveText("4")
await expect(patches).toHaveCount(1)
await expect(patches.getByText("3 files", { exact: true })).toBeVisible()
await expect(patches.locator('[data-slot="apply-patch-filename"]')).toHaveText(["a.ts", "b.ts", "c.ts"])
await expect(patches.getByText("4 files", { exact: true })).toBeVisible()
await expect(patches.locator('[data-slot="apply-patch-filename"]')).toHaveText(["a.ts", "b.ts", "c.ts", "d.ts"])
await expect(first).toHaveAttribute("aria-expanded", "true")
await expect(patches.locator('[data-component="file"]')).toBeVisible()
await group.screenshot({ path: info.outputPath("merged.png") })
@@ -61,7 +62,13 @@ for (const separator of ["shell", "error", "reasoning"]) {
await root.getByRole("button", { name: "Finish follow-up patch" }).click()
const group = root.locator('[data-component="collapsed-tool-group"]')
await expect(group.locator('[data-component="apply-patch-tool"]')).toHaveCount(2)
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText(["a.ts", "b.ts", "a.ts", "c.ts"])
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText([
"a.ts",
"b.ts",
"a.ts",
"c.ts",
"d.ts",
])
if (separator === "error") await expect(group.locator('[data-kind="tool-error-card"]')).toBeVisible()
})
}
@@ -72,8 +79,14 @@ story("does not retain patch files in the wrong batch when thoughts are shown",
await root.getByRole("button", { name: "Finish follow-up patch" }).click()
const group = root.locator('[data-component="collapsed-tool-group"]')
await expect(group.locator('[data-component="apply-patch-tool"]')).toHaveCount(1)
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText(["a.ts", "b.ts", "c.ts"])
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText(["a.ts", "b.ts", "c.ts", "d.ts"])
await root.getByRole("button", { name: "Show thoughts", exact: true }).click()
await expect(group.locator('[data-component="apply-patch-tool"]')).toHaveCount(2)
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText(["a.ts", "b.ts", "a.ts", "c.ts"])
await expect(group.locator('[data-slot="apply-patch-filename"]')).toHaveText([
"a.ts",
"b.ts",
"a.ts",
"c.ts",
"d.ts",
])
})
@@ -29,16 +29,16 @@ for (const open of [true, false]) {
await expect(second).toHaveAttribute("aria-expanded", "true")
await expect(diff).toBeVisible()
const original = await patch.elementHandle()
for (const count of [3, 4]) {
for (const call of [1, 2]) {
await root.getByRole("button", { name: "Append tool call", exact: true }).click()
await expect(
group.locator('[data-component="context-tool-group-trigger"] [data-slot="basic-tool-tool-title"]'),
).toHaveText("Shell, Patch")
await expect(trigger).toHaveAccessibleName(`Used ${count} Shell, Patch`)
await expect(trigger).toHaveAccessibleName("Used 2 Shell, Patch")
await expect(diff).toBeVisible()
await root
.locator('[data-component="session-timeline"]')
.screenshot({ path: info.outputPath(`append-${count}.png`) })
.screenshot({ path: info.outputPath(`append-${call}.png`) })
await expect(shell).toHaveAttribute("aria-expanded", String(open))
await expect(first).toHaveAttribute("aria-expanded", String(open))
await expect(second).toHaveAttribute("aria-expanded", "true")
@@ -132,7 +132,10 @@ export const PatchFollowUps = {
state.phase === "running" ? "running" : "completed",
{},
{
metadata: state.phase === "running" ? {} : { files: [file("src/a.ts", 1, 2), file("src/c.ts", 0, 1)] },
metadata:
state.phase === "running"
? {}
: { files: [file("src/a.ts", 1, 2), file("src/c.ts", 0, 1), file("src/d.ts", 0, 1)] },
},
),
]),
@@ -533,6 +533,15 @@ export function CurrentContextToolGroup(props: {
),
].join(", "),
)
const patchedFiles = createMemo(() =>
patchFileGroups(
tools().flatMap((tool) => {
if (tool.name !== "patch" || tool.state.status === "error") return []
const files = currentToolMetadata(tool).files
return Array.isArray(files) ? files : []
}),
).length,
)
const label = createMemo(() => {
const thoughts = props.parts.filter((part) => part.type === "reasoning").length
if (!names() && !thoughts) {
@@ -540,7 +549,8 @@ export function CurrentContextToolGroup(props: {
return { text: title, title, before: "", count: "", between: "", after: "" }
}
const title = names() || i18n.plural("ui.messagePart.context.thought", thoughts)
const count = props.parts.filter((part) => part.type === "tool" || part.type === "shell").length || thoughts
const count =
patchedFiles() || props.parts.filter((part) => part.type === "tool" || part.type === "shell").length || thoughts
const text = i18n.plural("ui.messagePart.tools.used", count, { tools: title })
const index = text.indexOf(title)
const before = text.slice(0, index).trim()