Skip to content

fix(webview): anchor thinking timer to message timestamp to survive Virtuoso remounts#881

Open
umi008 wants to merge 3 commits into
Zoo-Code-Org:mainfrom
umi008:fix/656-reasoning-timer-reset
Open

fix(webview): anchor thinking timer to message timestamp to survive Virtuoso remounts#881
umi008 wants to merge 3 commits into
Zoo-Code-Org:mainfrom
umi008:fix/656-reasoning-timer-reset

Conversation

@umi008

@umi008 umi008 commented Jul 11, 2026

Copy link
Copy Markdown

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 ReasoningBlock used Date.now() as the timer anchor at component mount time — but inside a Virtuoso virtual list, expand/collapse can trigger DOM recycling that unmounts and remounts the component. On remount, startTimeRef gets a fresh Date.now(), and since isStreaming is already false (the message finished streaming before the user interacted), the timer effect never runs — leaving elapsed at 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 (ts prop, already passed from ChatRow.tsx but previously discarded in the destructuring) 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 0.

Test Procedure

  1. Start a chat with any provider that supports thinking/reasoning (Claude with extended thinking, DeepSeek, etc.)
  2. Wait for the thinking section to appear with a timer (e.g., "30s")
  3. Expand the thinking section, then collapse it
  4. Verify the timer still shows the original duration — not 0s or hidden
  5. Repeat with different locales (en, zh_CN) to confirm the fix is locale-independent

Pre-Submission Checklist

  • Issue Linked: Closes [BUG] Timer of Thinking will reset to 0 after expand and collapse it #656
  • Scope: One focused fix — anchors timer to stable message data
  • Self-Review: Props interface already had ts; component just wasn't using it — this is a pure hook into existing data
  • Testing: TypeScript compilation verified; tested the remount scenario with the init function fallback
  • Documentation Impact: No documentation updates required
  • Contribution Guidelines: Read and agree

Screenshots / Videos

N/A — state management fix, no visual change beyond the timer not resetting.

Documentation Updates

  • No documentation updates are required.

Additional Notes

Edge case: for messages scrolled away for minutes and then scrolled back, Date.now() - ts will 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

  • Bug Fixes
    • Improved elapsed-time display for streaming reasoning by using the message’s creation time, ensuring timing remains accurate when the content appears after a delay.

…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
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

ReasoningBlock now uses the message creation timestamp to initialize elapsed reasoning time, preserving the timer across component remounts.

Changes

Reasoning timer persistence

Layer / File(s) Summary
Timestamp-based timer initialization
webview-ui/src/components/chat/ReasoningBlock.tsx
Adds the ts prop and initializes elapsed time from Date.now() - ts while streaming; non-streaming instances start at zero.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: anchoring the thinking timer to the message timestamp.
Description check ✅ Passed The description covers the issue, fix, testing steps, checklist, and additional context, with only a minor non-critical section missing.
Linked Issues check ✅ Passed The change directly addresses issue #656 by preventing the thinking timer from resetting after expand/collapse remounts.
Out of Scope Changes check ✅ Passed The PR stays focused on the timer remount bug and does not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
webview-ui/src/components/chat/ReasoningBlock.tsx (1)

17-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Vitest coverage for the ReasoningBlock remount timer path.
Add a local test under webview-ui/src/components/chat/__tests__ that unmounts and remounts the component with the same ts and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 116b70e and b84aa59.

📒 Files selected for processing (1)
  • webview-ui/src/components/chat/ReasoningBlock.tsx

Comment thread webview-ui/src/components/chat/ReasoningBlock.tsx
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 33.33333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
webview-ui/src/components/chat/ReasoningBlock.tsx 33.33% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Timer of Thinking will reset to 0 after expand and collapse it

1 participant