From e48f6b28ff087c637115087bf2b900263dcf6d5d Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 4 Apr 2026 07:19:52 -0700 Subject: [PATCH] fix: preserve optimistic UI during tab switch on first message When the user sends a message and the server assigns it to a new tab (because Chrome's active tab changed), switchChatTab() was blowing away the optimistic user bubble and thinking dots with a welcome screen. Now preserves the current DOM if we're mid-send with a thinking indicator. Co-Authored-By: Claude Opus 4.6 (1M context) --- extension/sidepanel.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/extension/sidepanel.js b/extension/sidepanel.js index 561ba53b..a378765d 100644 --- a/extension/sidepanel.js +++ b/extension/sidepanel.js @@ -381,10 +381,13 @@ async function pollChat() { } const data = await resp.json(); - // Detect tab switch from server — swap chat context + // Detect tab switch from server — swap chat context. + // IMPORTANT: return before cleaning up thinking dots — the agent may be + // processing on the NEW tab while the OLD tab is idle. Removing the + // thinking indicator here would kill the optimistic UI before the switch. if (data.activeTabId !== undefined && data.activeTabId !== sidebarActiveTabId) { switchChatTab(data.activeTabId); - return; // switchChatTab triggers a fresh poll + return; // switchChatTab triggers a fresh poll on the correct tab } // First successful poll — hide loading spinner @@ -409,8 +412,9 @@ async function pollChat() { } // Clean up orphaned thinking indicators after replay. - // Only show "(session ended)" if there's actually a thinking spinner - // to clean up (not on every idle poll, which would spam the chat). + // Only remove if we're on the CORRECT tab and the agent is truly idle. + // Don't clean up during tab switches — the agent may be processing on + // the new tab while the old tab shows idle. const thinking = document.getElementById('agent-thinking'); if (thinking && data.agentStatus !== 'processing') { thinking.remove(); @@ -437,10 +441,21 @@ function switchChatTab(newTabId) { sidebarActiveTabId = newTabId; - // Restore saved chat for new tab, or show welcome + // Restore saved chat for new tab, or carry over current DOM if we're + // mid-message (the server may have switched tabs because the user's + // Chrome tab changed, but we still want to show the optimistic UI). if (chatDomByTab[newTabId]) { chatMessages.innerHTML = chatDomByTab[newTabId]; chatLineCount = chatLineCountByTab[newTabId] || 0; + // Reset agent state for restored tab + agentContainer = null; + agentTextEl = null; + agentText = ''; + } else if (lastOptimisticMsg && document.getElementById('agent-thinking')) { + // We're mid-send with optimistic UI — keep it, don't blow it away. + // The poll for the new tab will pick up the entries and sync naturally. + chatLineCount = 0; + // agentContainer/agentTextEl are already set from sendMessage() } else { chatMessages.innerHTML = `
@@ -449,13 +464,12 @@ function switchChatTab(newTabId) {

Each tab has its own conversation.

`; chatLineCount = 0; + // Reset agent state for fresh tab + agentContainer = null; + agentTextEl = null; + agentText = ''; } - // Reset agent state for this tab - agentContainer = null; - agentTextEl = null; - agentText = ''; - // Immediately poll the new tab's chat pollChat(); }