diff --git a/src/main/codex/codex-structured-item-translation.test.ts b/src/main/codex/codex-structured-item-translation.test.ts
index 523c4fb934c..1d64158cb60 100644
--- a/src/main/codex/codex-structured-item-translation.test.ts
+++ b/src/main/codex/codex-structured-item-translation.test.ts
@@ -197,24 +197,24 @@ describe('codex item bodies', () => {
})
it('names a classified read command by its class and keeps the raw command', () => {
- expect(
- codexItemBody({
- type: 'commandExecution',
- id: 'item-read',
- command: "sed -n '1,200p' notes.txt",
- cwd: '/repo',
- status: 'completed',
- exitCode: 0,
- commandActions: [
- {
- type: 'read',
- command: "sed -n '1,200p' notes.txt",
- name: 'notes.txt',
- path: '/repo/notes.txt'
- }
- ]
- })
- ).toEqual({
+ const body = codexItemBody({
+ type: 'commandExecution',
+ id: 'item-read',
+ command: "sed -n '1,200p' notes.txt",
+ cwd: '/repo',
+ status: 'completed',
+ exitCode: 0,
+ commandActions: [
+ {
+ type: 'read',
+ command: "sed -n '1,200p' notes.txt",
+ name: 'notes.txt',
+ path: '/repo/notes.txt'
+ }
+ ]
+ })
+
+ expect(body).toEqual({
kind: 'tool-call',
name: 'read',
// `name` is the target's basename, which `path` already carries and no
@@ -222,6 +222,11 @@ describe('codex item bodies', () => {
input: { command: "sed -n '1,200p' notes.txt", cwd: '/repo', path: '/repo/notes.txt' },
state: 'completed'
})
+ // `read` is the one class that keeps `path`, so its row stays a tappable
+ // file on mobile — the other half of the rule `list`/`search` obey below.
+ const display = createToolInputDisplay(body?.kind === 'tool-call' ? body.input : null)
+ expect(display.filePath).toBe('/repo/notes.txt')
+ expect(display.label).toBe('/repo/notes.txt')
})
it('carries a classified search query so the row labels by term, not scan root', () => {
diff --git a/src/renderer/src/components/native-chat/NativeChatToolRun.tsx b/src/renderer/src/components/native-chat/NativeChatToolRun.tsx
index 7aaf6f79020..fd6b7f99e16 100644
--- a/src/renderer/src/components/native-chat/NativeChatToolRun.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatToolRun.tsx
@@ -226,11 +226,12 @@ export function NativeChatToolRun({
() => (open ? buildEditCards(blocks) : NO_EDIT_CARDS),
[open, blocks]
)
- // The header is named by the run's latest tool in both states, so its glyph is
- // chosen once and does not change when that tool settles. State rides on the
- // trailing mark instead — a leading glyph that flipped to a check would read as
- // the row changing identity.
- const headerCall = latestActiveCall ?? blocks.findLast(isToolCallBlock) ?? null
+ // Only the settled header reads this; the live one names `latestActiveCall`.
+ // Those are the same call except under out-of-order completion, where the
+ // header re-names on settle to the run's last tool. The glyph is fixed for
+ // whichever tool the header names, so state rides on the trailing mark — a
+ // leading glyph that flipped to a check would read as a change of identity.
+ const settledHeaderCall = blocks.findLast(isToolCallBlock) ?? null
const fallbackLabel =
callCount === 1
? translate('components.native-chat.tool.countOne', NATIVE_CHAT_TOOL_ACTIVITY_COPY.countOne)
@@ -275,8 +276,11 @@ export function NativeChatToolRun({
className="group flex min-h-6 w-full items-center gap-1.5 py-0.5 text-left"
aria-expanded={open}
>
- {structuredActivityUi && headerCall ? (
-
+ {structuredActivityUi && settledHeaderCall ? (
+
) : null}
{callCount}×