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}