From ca60b77d9acb85ad10c4a06a8723d1610e760d15 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Thu, 26 Mar 2026 19:53:18 +0000 Subject: [PATCH] feat: collapsible chat sidebar with top bar mode Sidebar collapses to a horizontal top bar with "New" and "History" buttons plus active chat title. Persists to localStorage. Fixes layout nesting so TaskLane renders correctly when sidebar is collapsed. Also removes chat input separator line. Co-Authored-By: Claude Opus 4.6 --- .../src/components/assistant/ChatSidebar.tsx | 69 ++++++++++++++++++- frontend/src/pages/AssistantChatPage.tsx | 57 +++++++++++---- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/assistant/ChatSidebar.tsx b/frontend/src/components/assistant/ChatSidebar.tsx index d474e5e9..133f0a60 100644 --- a/frontend/src/components/assistant/ChatSidebar.tsx +++ b/frontend/src/components/assistant/ChatSidebar.tsx @@ -1,4 +1,4 @@ -import { Plus, Pin, Trash2, MessageSquare } from 'lucide-react' +import { Plus, Pin, Trash2, MessageSquare, History, X } from 'lucide-react' import { cn } from '@/lib/utils' import type { ChatListItem } from '@/types/assistant-chat' @@ -11,6 +11,8 @@ interface ChatSidebarProps { onTogglePin: (id: string, pinned: boolean) => void mobileOpen?: boolean onMobileClose?: () => void + collapsed?: boolean + onToggleCollapse?: () => void } export function ChatSidebar({ @@ -22,6 +24,8 @@ export function ChatSidebar({ onTogglePin, mobileOpen = false, onMobileClose, + collapsed = false, + onToggleCollapse, }: ChatSidebarProps) { const pinnedChats = chats.filter(c => c.pinned) const unpinnedChats = chats.filter(c => !c.pinned) @@ -36,6 +40,11 @@ export function ChatSidebar({ onMobileClose?.() } + // When collapsed on desktop, render nothing — parent renders the top bar + if (collapsed && !mobileOpen) { + return null + } + return ( <> {/* Mobile overlay */} @@ -52,14 +61,23 @@ export function ChatSidebar({ style={{ background: 'var(--color-bg-sidebar)' }} > {/* Header */} -
+
+ {onToggleCollapse && ( + + )}
{/* Chat list */} @@ -108,6 +126,51 @@ export function ChatSidebar({ ) } +/** Collapsed top bar — rendered by the parent page above the chat area */ +export function ChatSidebarCollapsedBar({ + chats, + activeChatId, + onNewChat, + onExpand, +}: { + chats: ChatListItem[] + activeChatId: string | null + onNewChat: () => void + onExpand: () => void +}) { + return ( +
+ + +
+ {activeChatId && ( + + {chats.find(c => c.id === activeChatId)?.title} + + )} +
+ ) +} + function ChatItem({ chat, isActive, diff --git a/frontend/src/pages/AssistantChatPage.tsx b/frontend/src/pages/AssistantChatPage.tsx index 05747412..c8de1740 100644 --- a/frontend/src/pages/AssistantChatPage.tsx +++ b/frontend/src/pages/AssistantChatPage.tsx @@ -10,7 +10,7 @@ import { aiSessionsApi } from '@/api/aiSessions' import { useBranching } from '@/hooks/useBranching' import { analytics } from '@/lib/analytics' import { toast } from '@/lib/toast' -import { ChatSidebar } from '@/components/assistant/ChatSidebar' +import { ChatSidebar, ChatSidebarCollapsedBar } from '@/components/assistant/ChatSidebar' import { ChatMessage } from '@/components/assistant/ChatMessage' import { TaskLane } from '@/components/assistant/TaskLane' import { ConcludeSessionModal } from '@/components/assistant/ConcludeSessionModal' @@ -45,6 +45,14 @@ export default function AssistantChatPage() { const [activeQuestions, setActiveQuestions] = useState([]) const [activeActions, setActiveActions] = useState([]) const [showTaskLane, setShowTaskLane] = useState(false) + const [sidebarCollapsed, setSidebarCollapsed] = useState(() => + localStorage.getItem('rf-chat-sidebar-collapsed') === 'true' + ) + const toggleSidebarCollapse = () => { + const next = !sidebarCollapsed + setSidebarCollapsed(next) + localStorage.setItem('rf-chat-sidebar-collapsed', String(next)) + } const messagesEndRef = useRef(null) const inputRef = useRef(null) const fileInputRef = useRef(null) @@ -470,17 +478,20 @@ export default function AssistantChatPage() { <>
- {/* Sidebar — hidden on mobile, slide-out via toggle */} -
- -
+ {/* Sidebar — hidden on mobile, collapsed to top bar or full sidebar on desktop */} + {!sidebarCollapsed && ( +
+ +
+ )}
{/* Main chat area + optional branch sidebar */} -
+
+ + {/* Collapsed sidebar top bar — desktop only */} + {sidebarCollapsed && ( +
+ +
+ )} + + {/* Chat content row: chat column + TaskLane side by side */} +
{/* Mobile header with chat history toggle */}
@@ -555,7 +581,7 @@ export default function AssistantChatPage() {
{/* Rich Input */} -
+
{/* close chat content row */} +
{/* close outer flex-col */} {/* Conclude Session Modal */}