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}×