diff --git a/src/renderer/src/components/native-chat/NativeChatReasoningRow.test.tsx b/src/renderer/src/components/native-chat/NativeChatReasoningRow.test.tsx
index fe59a95b546..75a019edcac 100644
--- a/src/renderer/src/components/native-chat/NativeChatReasoningRow.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatReasoningRow.test.tsx
@@ -72,6 +72,19 @@ describe('reasoning disclosure', () => {
expect(screen.getByRole('button')).toHaveTextContent('Thought for 1m 5s')
})
+ it('excludes later turn work after the block stops changing', () => {
+ const now = vi.spyOn(Date, 'now').mockReturnValue(10_000)
+ const { rerender } = render()
+ now.mockReturnValue(22_000)
+ rerender()
+ now.mockReturnValue(70_000)
+ rerender()
+ expect(screen.getByRole('button')).toHaveTextContent('Thinking...')
+ now.mockReturnValue(100_000)
+ rerender()
+ expect(screen.getByRole('button')).toHaveTextContent('Thought for 12s')
+ })
+
it('resets the clock when block identity changes without a streaming-state transition', () => {
const now = vi.spyOn(Date, 'now').mockReturnValue(10_000)
const { rerender } = render()
diff --git a/src/renderer/src/components/native-chat/NativeChatReasoningRow.tsx b/src/renderer/src/components/native-chat/NativeChatReasoningRow.tsx
index 1ed9f02120e..d930d67dbc1 100644
--- a/src/renderer/src/components/native-chat/NativeChatReasoningRow.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatReasoningRow.tsx
@@ -23,6 +23,8 @@ export function NativeChatReasoningRow({
const [timing, setTiming] = useState(() => ({
blockId,
streaming: isStreaming,
+ markdown,
+ lastUpdatedAt: Date.now(),
startedAt: isStreaming ? Date.now() : null,
elapsedMs: null as number | null
}))
@@ -33,22 +35,31 @@ export function NativeChatReasoningRow({
return {
blockId,
streaming: isStreaming,
+ markdown,
+ lastUpdatedAt: now,
startedAt: isStreaming ? now : null,
elapsedMs: null
}
}
- if (previous.streaming === isStreaming) {
+ if (previous.streaming === isStreaming && (!isStreaming || previous.markdown === markdown)) {
return previous
}
// Only an observed stream supplies a duration; journal history has no local start.
+ const lastUpdatedAt =
+ (isStreaming && !previous.streaming) || previous.markdown !== markdown
+ ? now
+ : previous.lastUpdatedAt
return {
...previous,
+ markdown,
+ lastUpdatedAt,
streaming: isStreaming,
- startedAt: isStreaming ? now : previous.startedAt,
- elapsedMs: isStreaming || previous.startedAt === null ? null : now - previous.startedAt
+ startedAt: isStreaming && !previous.streaming ? now : previous.startedAt,
+ elapsedMs:
+ isStreaming || previous.startedAt === null ? null : lastUpdatedAt - previous.startedAt
}
})
- }, [blockId, isStreaming])
+ }, [blockId, isStreaming, markdown])
if (!markdown.trim()) {
return null
}