diff --git a/mobile/src/session/MobileNativeChatMessage.test.ts b/mobile/src/session/MobileNativeChatMessage.test.ts index f38a437d55c..4749dcd58ee 100644 --- a/mobile/src/session/MobileNativeChatMessage.test.ts +++ b/mobile/src/session/MobileNativeChatMessage.test.ts @@ -141,4 +141,14 @@ describe('MobileNativeChatMessage', () => { '{\n "cmd": "git status",\n "description": "Inspect changes"\n}' ) }) + + it('does not echo the row label as detail when a row has nothing to expand', () => { + // The Tools toggle opens every row at once, bypassing the tap guard — a row + // whose formatted input is its own label would echo itself in a panel that + // no tap can dismiss. + const tree = render(toolMessage([{ type: 'tool-call', name: 'ListTodos', input: '{}' }]), { + toolsExpanded: true + }) + expect(textIn(tree.root).filter((text) => text === '{}')).toHaveLength(1) + }) }) diff --git a/mobile/src/session/MobileNativeChatMessage.tsx b/mobile/src/session/MobileNativeChatMessage.tsx index 6efd4069959..2a93c3dde6e 100644 --- a/mobile/src/session/MobileNativeChatMessage.tsx +++ b/mobile/src/session/MobileNativeChatMessage.tsx @@ -109,6 +109,10 @@ function ToolLine({ result !== undefined || preview.length > 40 || (call !== undefined && isStructuredToolInput(call.input)) + // The group toggle opens every line at once, bypassing the tap guard, so the + // panel has to consult it too — else a detail-less row echoes its own label + // under itself and no tap can dismiss it. + const showDetail = hasDetail && expanded // A tool that targets a file (Read/Edit/Write…) renders its preview as a // tappable link that opens the file, independent of the line's expand tap. const filePath = call ? toolFilePath(call.input) : null @@ -120,7 +124,7 @@ function ToolLine({ onPress={() => hasDetail && setExpanded((v) => !v)} hitSlop={6} > - {expanded ? ( + {showDetail ? ( ) : ( @@ -137,7 +141,7 @@ function ToolLine({ ) : null} - {expanded ? ( + {showDetail ? ( {callDiff ? : null} {callDetail ? {callDetail} : null}