fix(ui): prevent sidebar instance reordering and new conversation flicker#213
Conversation
…cker Two separate bugs caused a jarring experience when switching between instances or creating new conversations in the chat sidebar: 1. Sidebar instances reordered on every interaction because the old sortAgentGroupsForFocus() moved the focused agent to the top and fetchAgents re-ran on every URL change, replacing state with a potentially different order. Fixed by always sorting alphabetically in the Sidebar component and stabilising fetchAgents with refs so it only runs on mount. 2. Creating a new conversation caused a visible flicker because React Router had two separate Route elements for the chat page (c/:name? and c/:name/:conversationId). Navigating from one to the other unmounted and remounted ChatPage, resetting all state. Fixed by collapsing both routes into a single catch-all (c/*) and parsing the name/conversationId from the wildcard param. Additional fixes: - handleNewConversation now updates local state directly instead of calling fetchAgents, eliminating a full re-fetch cycle. - applyConversationUpdate only updates selectedConversation if it matches the currently viewed conversation, preventing stale WebSocket events from overwriting the selection.
👍 GitRank PR AnalysisScore: 20 points
Eligibility Checks
Impact SummaryThis PR fixes two distinct UI bugs: (1) sidebar instances reordering on every interaction by switching from focus-based sorting to alphabetical sorting and stabilizing the fetchAgents callback, and (2) new conversation creation flicker by consolidating two separate React Router routes into a single catch-all route to prevent component remounting. The fix includes comprehensive test coverage with 166 new test lines and updates to 28 test files, demonstrating thorough validation of the changes. Analysis DetailsComponent Classification: This PR affects UI components (sidebar, chat page routing, and state management) but doesn't fit neatly into a single specialized category. The changes span multiple UI layers without a dominant component type, making OTHER the appropriate classification. Severity Justification: This is a P2 (Medium) severity fix addressing functional bugs with workarounds. While the sidebar reordering and conversation flicker are jarring UX issues that impact user experience, they don't cause data loss, service outages, or security risks. Users can work around these issues by refreshing or manually managing their workflow. Eligibility Notes: Issue: True - PR clearly describes two specific bugs being fixed. Fix Implementation: True - code changes directly address both problems (route consolidation, alphabetical sorting, ref-based stability). PR Linked: True - detailed problem statement, root cause analysis, and test plan provided. Tests: True - 166 lines of new tests added covering instance ordering, conversation switching, and flicker prevention. Tests Required: True - this is a bug fix in business logic (routing and state management) that requires regression testing to ensure the fixes work and don't introduce new issues. Analyzed by GitRank 🤖 |
PR #213 stabilised fetchAgents by moving URL params into refs and running the effect only on mount. A side-effect was that navigating to a conversation URL (e.g. from a Discord link) could trigger an infinite WebSocket reconnection loop when the conversation was not immediately available in the list response. Root causes and fixes: 1. fetchAgents auto-created a second conversation when the URL-specified one was missing from the list. Now it tries a direct GET for the conversation first, and if that also fails it returns early instead of auto-creating a duplicate. 2. applyConversationUpdate silently dropped WebSocket events when selectedConversation was null (null && ... evaluates to null). Changed the guard from (prev && prev.name === conv.name) to (!prev || prev.name === conv.name) so the first event properly sets the selection. 3. After mount, URL param changes had no way to update the selected conversation from already-fetched data. Added a useEffect that watches [name, urlConversationId, agents] and syncs the selection.
fix(ui): prevent WebSocket reconnection loop after PR #213 PR #213 stabilised fetchAgents by moving URL params into refs and running the effect only on mount. A side-effect was that navigating to a conversation URL (e.g. from a Discord link) could trigger an infinite WebSocket reconnection loop when the conversation was not immediately available in the list response. Root causes and fixes: 1. fetchAgents auto-created a second conversation when the URL-specified one was missing from the list. Now it tries a direct GET for the conversation first, and if that also fails it returns early instead of auto-creating a duplicate. 2. applyConversationUpdate silently dropped WebSocket events when selectedConversation was null (null && ... evaluates to null). Changed the guard from (prev && prev.name === conv.name) to (!prev || prev.name === conv.name) so the first event properly sets the selection. 3. After mount, URL param changes had no way to update the selected conversation from already-fetched data. Added a useEffect that watches [name, urlConversationId, agents] and syncs the selection.
Problem
Two bugs in the chat UI caused a jarring experience:
Sidebar instances reorder on every interaction — When switching between conversations across different instances (e.g. Claude Code and OpenClaw), the sidebar moved the focused instance to the top, causing the list to jump around. This happened because
sortAgentGroupsForFocus()prioritized the focused agent andfetchAgentsre-ran on every URL change, replacing the agent list with a potentially different order each time.New conversation creation causes visible flicker — Creating a new conversation briefly flashed the old conversation before showing the new one. The root cause was two separate React Router
<Route>elements rendering ChatPage (c/:name?andc/:name/:conversationId). When the URL changed from/c/nameto/c/name/conv-id, React Router unmounted and remounted the component, resetting all state and triggering a full re-fetch.Fix
c/*route so React Router never unmounts ChatPage during navigation.nameandconversationIdfrom the wildcard splat param instead of named route params.fetchAgentsusing refs for URL params so it only runs once on mount, not on every navigation.handleNewConversationto insert the new conversation into local state directly instead of callingfetchAgents, eliminating a full re-fetch cycle.applyConversationUpdateso stale WebSocket events don't overwrite the currently selected conversation.Test plan