fix(webview): anchor thinking timer to message timestamp to survive Virtuoso remounts#881
fix(webview): anchor thinking timer to message timestamp to survive Virtuoso remounts#881umi008 wants to merge 3 commits into
Conversation
…emounts The ReasoningBlock previously anchored its elapsed-time display to Date.now() at component mount time. When Virtuoso's virtual list recycles or remounts the component (e.g. during expand/collapse toggles or scroll), the timer reset to 0 because a fresh Date.now() was used and the effect guard (isLast && isStreaming) was false on remount. Now the component uses the message creation timestamp (ts, already passed as a prop but previously discarded) as the timer anchor. On remount after streaming has finished, elapsed is initialized from Date.now() - ts, giving a reasonable approximation of the thinking duration instead of showing 0s. This also explains the zh_CN-specific reproduction: CJK character widths in the header created slightly different DOM geometry, pushing the layout past Virtuoso's recycle threshold in certain viewports. Fixes Zoo-Code-Org#656
📝 WalkthroughWalkthrough
ChangesReasoning timer persistence
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
webview-ui/src/components/chat/ReasoningBlock.tsx (1)
17-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd Vitest coverage for the ReasoningBlock remount timer path.
Add a local test underwebview-ui/src/components/chat/__tests__that unmounts and remounts the component with the sametsand asserts the elapsed label still reflects the original message timestamp.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@webview-ui/src/components/chat/ReasoningBlock.tsx` around lines 17 - 29, Add a Vitest test under the chat component tests covering ReasoningBlock remount behavior: render it with a fixed ts, advance mocked time, unmount and remount using the same ts, then assert the elapsed label reflects time since the original timestamp rather than resetting on mount. Use the existing ReasoningBlock render props and translation/state mocks as needed.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@webview-ui/src/components/chat/ReasoningBlock.tsx`:
- Line 29: Initialize the elapsed state in ReasoningBlock directly from
Math.max(0, Date.now() - ts) for both streaming and non-streaming states,
removing the isStreaming conditional so remounts preserve the current elapsed
time and avoid the temporary hidden label.
---
Nitpick comments:
In `@webview-ui/src/components/chat/ReasoningBlock.tsx`:
- Around line 17-29: Add a Vitest test under the chat component tests covering
ReasoningBlock remount behavior: render it with a fixed ts, advance mocked time,
unmount and remount using the same ts, then assert the elapsed label reflects
time since the original timestamp rather than resetting on mount. Use the
existing ReasoningBlock render props and translation/state mocks as needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5d92d708-5c96-4b59-ac23-c94d8d20b1f5
📒 Files selected for processing (1)
webview-ui/src/components/chat/ReasoningBlock.tsx
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Related GitHub Issue
Closes: #656
Description
The thinking/reasoning timer (e.g., "Thought for 30s") was resetting to 0 or disappearing when the user expanded and then collapsed the thinking section. This happened because
ReasoningBlockusedDate.now()as the timer anchor at component mount time — but inside aVirtuosovirtual list, expand/collapse can trigger DOM recycling that unmounts and remounts the component. On remount,startTimeRefgets a freshDate.now(), and sinceisStreamingis alreadyfalse(the message finished streaming before the user interacted), the timer effect never runs — leavingelapsedat 0.This was more visible with zh_CN locale because CJK character widths in the header text produced different DOM geometry, pushing the layout past Virtuoso recycle thresholds in viewports where Latin text would not trigger a remount.
Fix: The component now uses the message creation timestamp (
tsprop, already passed fromChatRow.tsxbut previously discarded in the destructuring) as the timer anchor. On remount after streaming has finished,elapsedis initialized fromDate.now() - ts, giving a reasonable approximation of the thinking duration instead of 0.Test Procedure
Pre-Submission Checklist
ts; component just wasn't using it — this is a pure hook into existing dataScreenshots / Videos
N/A — state management fix, no visual change beyond the timer not resetting.
Documentation Updates
Additional Notes
Edge case: for messages scrolled away for minutes and then scrolled back,
Date.now() - tswill show longer than the actual thinking time. This is cosmetic and far preferable to showing 0 or hiding the timer entirely — and only affects the unlikely case of a late remount after significant wall-clock time.Summary by CodeRabbit