add open chats atom, bust cache#9996
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
4 issues found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="frontend/src/components/chat/chat-tabs.tsx">
<violation number="1" location="frontend/src/components/chat/chat-tabs.tsx:33">
P2: Tab selection is mouse-only on a non-interactive div. Add keyboard semantics so tabs are operable via Enter/Space.</violation>
<violation number="2" location="frontend/src/components/chat/chat-tabs.tsx:40">
P2: Icon-only close button is missing an accessible label. Add `aria-label` so assistive tech can identify the action.</violation>
</file>
<file name="frontend/src/core/ai/state.ts">
<violation number="1" location="frontend/src/core/ai/state.ts:163">
P2: Deserialization does not validate `activeChatId` against loaded chats, so state can contain a dangling active id. Normalize it to `null` when the chat is missing.</violation>
</file>
<file name="frontend/src/components/chat/chat-panel.tsx">
<violation number="1" location="frontend/src/components/chat/chat-panel.tsx:779">
P1: Streaming responses can be persisted to the wrong chat after tab switches. `onFinish` writes by current `activeChatId`, and new tabs make mid-stream chat switches easy.</violation>
</file>
Architecture diagram
sequenceDiagram
participant User
participant ChatPanel as ChatPanel (React)
participant ChatTabs as ChatTabs (React)
participant atom as Jotai Atoms
participant localStorage
User->>ChatPanel: Clicks "New Chat"
ChatPanel->>ChatPanel: Generate new Chat object
ChatPanel->>atom: setChatState(addChatAndOpenTab(prev, newChat))
atom->>atom: Insert new chat into chats Map
atom->>atom: Append new chat.id to openChatIds
atom->>atom: Set activeChatId = newChat.id
atom-->>ChatPanel: Re-render with updated state
User->>ChatTabs: Clicks a tab
ChatTabs->>atom: setActiveChat(chatId)
alt chatId != null
atom->>atom: openChatTab(): add chatId to openChatIds if absent, set activeChatId
else chatId == null
atom->>atom: Set activeChatId = null
end
atom-->>ChatTabs: Tab highlighted as active
atom-->>ChatPanel: Active chat content displayed
User->>ChatTabs: Clicks X on a tab
ChatTabs->>atom: setChatState(closeChatTab(prev, chatId))
atom->>atom: Remove chatId from openChatIds
alt chatId was activeChatId
atom->>atom: Set activeChatId to neighbor at closed index, or null if last tab
else
atom->>atom: Keep existing activeChatId
end
atom-->>ChatTabs: Tab removed, neighbor activated if needed
atom-->>ChatPanel: Active content updated
Note over atom,localStorage: On periodic persist
atom->>atom: toSerializable():
atom->>atom: Remove empty chats
alt chats.size > MAX_STORED_CHATS (25)
atom->>atom: pruneChats(): keep most recent + protected (openChatIds)
end
atom->>atom: sanitizeOpenChatIds(filter out missing)
atom->>localStorage: Write serialized ChatState (version v6)
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| activeChatId={activeChat?.id} | ||
| setActiveChat={setActiveChat} | ||
| /> | ||
| <ChatTabs /> |
There was a problem hiding this comment.
P1: Streaming responses can be persisted to the wrong chat after tab switches. onFinish writes by current activeChatId, and new tabs make mid-stream chat switches easy.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/chat/chat-panel.tsx, line 779:
<comment>Streaming responses can be persisted to the wrong chat after tab switches. `onFinish` writes by current `activeChatId`, and new tabs make mid-stream chat switches easy.</comment>
<file context>
@@ -783,6 +776,7 @@ const ChatPanelBody = () => {
activeChatId={activeChat?.id}
setActiveChat={setActiveChat}
/>
+ <ChatTabs />
</TooltipProvider>
</file context>
| </span> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" |
There was a problem hiding this comment.
P2: Icon-only close button is missing an accessible label. Add aria-label so assistive tech can identify the action.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/chat/chat-tabs.tsx, line 40:
<comment>Icon-only close button is missing an accessible label. Add `aria-label` so assistive tech can identify the action.</comment>
<file context>
@@ -0,0 +1,95 @@
+ </span>
+ <Button
+ variant="ghost"
+ size="sm"
+ className={cn(
+ "h-4 w-4 p-0 shrink-0 opacity-0 group-hover:opacity-100 hover:bg-destructive/20 hover:text-destructive",
</file context>
| size="sm" | |
| size="sm" | |
| aria-label={`Close ${chat.title}`} |
| ? "bg-background text-foreground relative z-1" | ||
| : "bg-muted/30 text-muted-foreground hover:bg-muted/50 hover:text-foreground", | ||
| )} | ||
| onClick={() => onSelect(chat.id)} |
There was a problem hiding this comment.
P2: Tab selection is mouse-only on a non-interactive div. Add keyboard semantics so tabs are operable via Enter/Space.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/chat/chat-tabs.tsx, line 33:
<comment>Tab selection is mouse-only on a non-interactive div. Add keyboard semantics so tabs are operable via Enter/Space.</comment>
<file context>
@@ -0,0 +1,95 @@
+ ? "bg-background text-foreground relative z-1"
+ : "bg-muted/30 text-muted-foreground hover:bg-muted/50 hover:text-foreground",
+ )}
+ onClick={() => onSelect(chat.id)}
+ >
+ <span className="truncate flex-1 min-w-0" title={chat.title}>
</file context>
| ); | ||
| return { | ||
| chats: [...chats.entries()], | ||
| activeChatId: value.activeChatId, |
There was a problem hiding this comment.
P2: Deserialization does not validate activeChatId against loaded chats, so state can contain a dangling active id. Normalize it to null when the chat is missing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/core/ai/state.ts, line 163:
<comment>Deserialization does not validate `activeChatId` against loaded chats, so state can contain a dangling active id. Normalize it to `null` when the chat is missing.</comment>
<file context>
@@ -64,21 +67,115 @@ function removeEmptyChats(chatState: Map<ChatId, Chat>): Map<ChatId, Chat> {
+ );
+ return {
+ chats: [...chats.entries()],
+ activeChatId: value.activeChatId,
+ openChatIds: sanitizeOpenChatIds(chats, value.openChatIds),
+ };
</file context>
| activeChatId: value.activeChatId, | |
| activeChatId: value.activeChatId && chats.has(value.activeChatId) ? value.activeChatId : null, |
📝 Summary
Add chat tabs under chat panel for easy navigation. This does mean busting the cache.
Screen.Recording.2026-06-26.at.11.44.42.AM.mov
📋 Pre-Review Checklist
✅ Merge Checklist