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 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-26 19:53:18 +00:00
parent 56aaa276ee
commit ca60b77d9a
2 changed files with 108 additions and 18 deletions

View File

@@ -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<QuestionItem[]>([])
const [activeActions, setActiveActions] = useState<ActionItem[]>([])
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<HTMLDivElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null)
@@ -470,17 +478,20 @@ export default function AssistantChatPage() {
<>
<PageMeta title="AI Assistant" />
<div className="flex h-[calc(100vh-3.5rem)]">
{/* Sidebar — hidden on mobile, slide-out via toggle */}
<div className="hidden sm:block">
<ChatSidebar
chats={chats}
activeChatId={activeChatId}
onSelectChat={selectChat}
onNewChat={handleNewChat}
onDeleteChat={handleDeleteChat}
onTogglePin={handleTogglePin}
/>
</div>
{/* Sidebar — hidden on mobile, collapsed to top bar or full sidebar on desktop */}
{!sidebarCollapsed && (
<div className="hidden sm:block">
<ChatSidebar
chats={chats}
activeChatId={activeChatId}
onSelectChat={selectChat}
onNewChat={handleNewChat}
onDeleteChat={handleDeleteChat}
onTogglePin={handleTogglePin}
onToggleCollapse={toggleSidebarCollapse}
/>
</div>
)}
<div className="sm:hidden">
<ChatSidebar
chats={chats}
@@ -495,7 +506,22 @@ export default function AssistantChatPage() {
</div>
{/* Main chat area + optional branch sidebar */}
<div className="flex-1 flex min-w-0">
<div className="flex-1 flex flex-col min-w-0">
{/* Collapsed sidebar top bar — desktop only */}
{sidebarCollapsed && (
<div className="hidden sm:block">
<ChatSidebarCollapsedBar
chats={chats}
activeChatId={activeChatId}
onNewChat={handleNewChat}
onExpand={toggleSidebarCollapse}
/>
</div>
)}
{/* Chat content row: chat column + TaskLane side by side */}
<div className="flex-1 flex min-w-0 min-h-0">
<div className="flex-1 flex flex-col min-w-0">
{/* Mobile header with chat history toggle */}
<div className="sm:hidden flex items-center gap-2 px-3 py-2 border-b border-border shrink-0">
@@ -555,7 +581,7 @@ export default function AssistantChatPage() {
</div>
{/* Rich Input */}
<div className={cn("px-3 sm:px-6 py-3 shrink-0", !showTaskLane && "border-t border-border")}>
<div className="px-3 sm:px-6 py-3 shrink-0">
<div
className="max-w-3xl mx-auto"
onDragOver={handleDragOver}
@@ -710,7 +736,8 @@ export default function AssistantChatPage() {
{/* Branch map hidden — branching is now silent/background only.
Branches are tracked in the DB but not shown to the user.
The AI manages branch context internally. */}
</div>
</div>{/* close chat content row */}
</div>{/* close outer flex-col */}
{/* Conclude Session Modal */}
<ConcludeSessionModal