import React from 'react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { RiArrowDownSLine, RiCheckLine, RiCloseLine, RiEqualizer2Line, RiNodeTree, RiPencilAiLine, RiSearchLine, RiStickyNoteLine, } from '@remixicon/react'; import { ArrowsMerge } from '@/components/icons/ArrowsMerge'; import { ProjectNotesTodoPanel } from '../ProjectNotesTodoPanel'; import { formatDirectoryName } from '@/lib/utils'; import { formatProjectLabel } from './utils'; import type { ProjectRef } from '@/lib/openchamberConfig'; import { useSessionDisplayStore } from '@/stores/useSessionDisplayStore'; type ProjectItem = { id: string; label?: string; normalizedPath: string; }; type ActiveProject = { id: string; label?: string; normalizedPath: string; } | null; type Props = { hideDirectoryControls: boolean; hideProjectSelector: boolean; activeProjectForHeader: ActiveProject; homeDirectory: string | null; normalizedProjects: ProjectItem[]; activeProjectId: string | null; setActiveProjectIdOnly: (projectId: string) => void; isProjectRenameInline: boolean; setIsProjectRenameInline: (value: boolean) => void; handleStartInlineProjectRename: () => void; handleSaveInlineProjectRename: () => void; projectRenameDraft: string; setProjectRenameDraft: (value: string) => void; removeProject: (projectId: string) => void; handleOpenDirectoryDialog: () => void; addProjectButtonClass: string; headerActionIconClass: string; reserveHeaderActionsSpace: boolean; stableActiveProjectIsRepo: boolean; useMobileNotesPanel: boolean; projectNotesPanelOpen: boolean; setProjectNotesPanelOpen: (open: boolean) => void; activeProjectRefForHeader: ProjectRef | null; openMultiRunLauncher: () => void; headerActionButtonClass: string; setNewWorktreeDialogOpen: (open: boolean) => void; setActiveMainTab: (tab: 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files') => void; isSessionSearchOpen: boolean; setIsSessionSearchOpen: (open: boolean | ((prev: boolean) => boolean)) => void; sessionSearchInputRef: React.RefObject; sessionSearchQuery: string; setSessionSearchQuery: (value: string) => void; hasSessionSearchQuery: boolean; searchMatchCount: number; }; export function SidebarHeader(props: Props): React.ReactNode { const { hideDirectoryControls, hideProjectSelector, activeProjectForHeader, homeDirectory, normalizedProjects, activeProjectId, setActiveProjectIdOnly, isProjectRenameInline, setIsProjectRenameInline, handleStartInlineProjectRename, handleSaveInlineProjectRename, projectRenameDraft, setProjectRenameDraft, removeProject, addProjectButtonClass, headerActionIconClass, reserveHeaderActionsSpace, stableActiveProjectIsRepo, useMobileNotesPanel, projectNotesPanelOpen, setProjectNotesPanelOpen, activeProjectRefForHeader, openMultiRunLauncher, headerActionButtonClass, setNewWorktreeDialogOpen, setActiveMainTab, isSessionSearchOpen, setIsSessionSearchOpen, sessionSearchInputRef, sessionSearchQuery, setSessionSearchQuery, hasSessionSearchQuery, searchMatchCount, } = props; const displayMode = useSessionDisplayStore((state) => state.displayMode); const setDisplayMode = useSessionDisplayStore((state) => state.setDisplayMode); if (hideDirectoryControls) { return null; } return (
{!hideProjectSelector && (
{ if (!open) setIsProjectRenameInline(false); }} > {normalizedProjects.map((project) => { const label = formatProjectLabel( project.label?.trim() || formatDirectoryName(project.normalizedPath, homeDirectory) || project.normalizedPath, ); return ( setActiveProjectIdOnly(project.id)} className={`truncate ${project.id === activeProjectId ? 'text-primary' : ''}`} > {label} ); })}
{!isProjectRenameInline ? ( { event.preventDefault(); handleStartInlineProjectRename(); }} className="gap-2" > Rename project ) : (
{ event.preventDefault(); handleSaveInlineProjectRename(); }} > setProjectRenameDraft(event.target.value)} className="h-7 flex-1 rounded border border-border bg-transparent px-2 typography-ui-label text-foreground outline-none focus-visible:ring-2 focus-visible:ring-primary/50" placeholder="Rename project" autoFocus onKeyDown={(event) => { if (event.key === 'Escape') { event.stopPropagation(); setIsProjectRenameInline(false); return; } if (event.key === ' ' || event.key === 'Enter') { event.stopPropagation(); } }} />
)} { if (!activeProjectForHeader) return; removeProject(activeProjectForHeader.id); }} className="text-destructive focus:text-destructive gap-2" > Close project

Display mode

setDisplayMode('default')} className="flex items-center justify-between" > Default {displayMode === 'default' ? : null} setDisplayMode('minimal')} className="flex items-center justify-between" > Minimal {displayMode === 'minimal' ? : null}
)} {reserveHeaderActionsSpace ? (
{activeProjectForHeader ? ( <>
{stableActiveProjectIsRepo ? ( <>

New worktree

New multi-run

) : null} {useMobileNotesPanel ? (

Project notes

) : (

Project notes

setProjectNotesPanelOpen(false)} />
)}

Search sessions

Display mode

setDisplayMode('default')} className="flex items-center justify-between" > Default {displayMode === 'default' ? : null} setDisplayMode('minimal')} className="flex items-center justify-between" > Minimal {displayMode === 'minimal' ? : null}
{isSessionSearchOpen ? (
{hasSessionSearchQuery ? ( {searchMatchCount} {searchMatchCount === 1 ? 'match' : 'matches'} ) : } Esc to clear
setSessionSearchQuery(event.target.value)} placeholder="Search sessions..." className="h-8 w-full rounded-md border border-border bg-transparent pl-8 pr-8 typography-ui-label text-foreground outline-none focus-visible:ring-2 focus-visible:ring-primary/50" onKeyDown={(event) => { if (event.key === 'Escape') { event.stopPropagation(); if (hasSessionSearchQuery) { setSessionSearchQuery(''); } else { setIsSessionSearchOpen(false); } } }} /> {sessionSearchQuery.length > 0 ? ( ) : null}
) : null} ) : null}
) : null}
); }