Skip to content

Conversation

@KonsomeJona
Copy link
Owner

Summary

  • Fixes empty chat display issue that occurred during tab switching on macOS

Changes

  • Modified virtual scroll handling in ClaudeCodeSession component
  • Updated 110 lines in ClaudeCodeSession.tsx

Test plan

  • Test tab switching behavior on macOS
  • Verify chat content displays correctly after switching tabs
  • Ensure no regression in other tab switching functionality

- Add immediate state reset using useLayoutEffect when sessions change
- Implement requestAnimationFrame for macOS-optimized rendering
- Replace useEffect with useLayoutEffect for DOM operations
- Add stable virtualizer keys to force proper React reconciliation
- Enhanced error handling with fallback scroll mechanisms
- Guard against undefined messages in virtual list rendering
- Add debug visibility for message filtering states

Fixes race conditions in @tanstack/react-virtual on macOS WebKit
where chat messages would appear empty when switching tabs or
reloading sessions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@gemini-code-assist
Copy link

Summary of Changes

Hello @KonsomeJona, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request fixes an issue on macOS where the chat display would appear empty after switching tabs. The changes involve significant modifications to the ClaudeCodeSession component, primarily by enhancing the virtual scrolling mechanism to ensure proper re-initialization and rendering of messages. Key updates include introducing a virtualizerKey to force component re-mounting, implementing a useLayoutEffect for immediate state resets on session changes, and refining the auto-scrolling logic with requestAnimationFrame for smoother performance, particularly on macOS. Debugging logs and fallback scrolling mechanisms have also been added for improved stability and diagnostics.

Highlights

  • Summary: This pull request addresses a critical bug on macOS where the chat display would appear empty after switching tabs. The fix primarily involves refining the virtual scrolling mechanism within the ClaudeCodeSession component to ensure proper re-initialization and rendering of messages when the active session changes.
  • Intent: The primary intent of this PR is to resolve the issue of an empty chat display occurring on macOS when users switch between different chat tabs. This ensures a consistent and reliable user experience, particularly for macOS users.
  • Changes: The changes are concentrated in src/components/ClaudeCodeSession.tsx and focus on improving the stability and responsiveness of the virtualized message list:
  1. Virtualizer Re-initialization: A new virtualizerKey (derived from session?.id and displayableMessages.length) is introduced and applied to the virtualized list's container. This forces React to re-mount and re-initialize the virtualizer component when the session or message count changes, preventing stale state issues during tab switching.
  2. Immediate State Reset on Session Change: A useLayoutEffect hook was added to immediately clear messages, errors, and set isLoading to true when the session?.id changes. This preemptively resets the component state to prevent race conditions and ensure a clean slate for the new session's data.
  3. Enhanced Scrolling Logic: The auto-scrolling mechanism has been refactored using useCallback and useLayoutEffect combined with requestAnimationFrame. This provides more robust and smoother scrolling to the bottom of the chat, especially after loading history or new messages, with specific optimizations for macOS rendering.
  4. Debugging and Fallbacks: Added console.log statements for displayableMessages updates in development mode and try-catch blocks around virtualizer scroll calls with fallback to direct scrollTop manipulation, improving resilience and debuggability.
  5. Minor Refinements: Included useLayoutEffect and useCallback in imports, added debug: false to useVirtualizer, and added a conditional rendering block to display a 'Messages are being processed...' message when filtered messages are empty but raw messages exist, aiding in diagnosing display issues.
  • Reviewer Activity: No reviewer activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive set of changes to address a rendering issue on macOS related to tab switching. The strategy of forcing the virtualizer to remount using a key, combined with resetting component state via useLayoutEffect and improving scroll handling with requestAnimationFrame, is a solid approach for tackling such platform-specific UI bugs.

The code is well-commented, explaining the rationale behind the changes. The added try-catch blocks and fallback mechanisms significantly improve the robustness of the component. Overall, this is a high-quality fix for what appears to be a tricky problem.

Comment on lines +374 to +386
setTimeout(() => {
if (loadedMessages.length > 0) {
try {
rowVirtualizer.scrollToIndex(loadedMessages.length - 1, { align: 'end', behavior: 'auto' });
} catch (error) {
console.warn('Failed to scroll to index after loading history:', error);
// Fallback scroll
if (parentRef.current) {
parentRef.current.scrollTop = parentRef.current.scrollHeight;
}
}
}
}, 100); // Reduced delay since we're using RAF

Choose a reason for hiding this comment

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

medium

The use of requestAnimationFrame and try-catch here is a great improvement for robustness. However, the setTimeout with a 100ms delay introduces a magic number and can be brittle—potentially too long on fast devices and too short on slow ones.

If the goal is to wait for the next render cycle after requestAnimationFrame, a setTimeout with a 0 delay is usually sufficient and more idiomatic. This pushes the execution to the end of the browser's event queue, ensuring it runs after the current execution context (including rendering) is complete.

Consider using a 0 delay unless the 100ms wait is strictly necessary. If it is, it would be helpful to add a comment explaining why.

Suggested change
setTimeout(() => {
if (loadedMessages.length > 0) {
try {
rowVirtualizer.scrollToIndex(loadedMessages.length - 1, { align: 'end', behavior: 'auto' });
} catch (error) {
console.warn('Failed to scroll to index after loading history:', error);
// Fallback scroll
if (parentRef.current) {
parentRef.current.scrollTop = parentRef.current.scrollHeight;
}
}
}
}, 100); // Reduced delay since we're using RAF
setTimeout(() => {
if (loadedMessages.length > 0) {
try {
rowVirtualizer.scrollToIndex(loadedMessages.length - 1, { align: 'end', behavior: 'auto' });
} catch (error) {
console.warn('Failed to scroll to index after loading history:', error);
// Fallback scroll
if (parentRef.current) {
parentRef.current.scrollTop = parentRef.current.scrollHeight;
}
}
}
}, 0); // Use 0ms delay to schedule scroll after paint and other operations.

Comment on lines +1243 to +1248
displayableMessages.length === 0 && messages.length > 0 && (
<div className="absolute inset-x-4 py-8 text-center text-muted-foreground">
<p>Messages are being processed...</p>
<p className="text-xs mt-2">Raw: {messages.length}, Filtered: {displayableMessages.length}</p>
</div>
)

Choose a reason for hiding this comment

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

medium

This "Messages are being processed..." state is a good idea for providing feedback instead of showing a blank screen. However, it currently includes debugging information (Raw: ..., Filtered: ...) that probably shouldn't be in a production build. Additionally, the text "Messages are being processed..." is a bit technical for end-users.

I suggest two small improvements:

  1. Make the user-facing text more friendly, such as "Updating chat...".
  2. Wrap the debugging statistics in a process.env.NODE_ENV === 'development' check so they only appear during development.
            displayableMessages.length === 0 && messages.length > 0 && (
              <div className="absolute inset-x-4 py-8 text-center text-muted-foreground">
                <p>Updating chat...</p>
                {process.env.NODE_ENV === 'development' && (
                  <p className="text-xs mt-2">Raw: {messages.length}, Filtered: {displayableMessages.length}</p>
                )}
              </div>
            )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants