From ba1ab98c2ef553b7bb9116dbdf7088528c9a32e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 15 May 2026 19:36:52 +0800 Subject: [PATCH] fix: reduce risky sidebar controls to project organization The sidebar now groups conversations by project, so the embedded project picker in the search box duplicated the main navigation model and hid state inside a compact icon. Remove that picker and its store state, leaving search as plain text filtering and keeping the project header menu focused on organization and sorting. The archive-all entry was also hidden because a broad destructive action does not belong in the lightweight project menu. Constraint: Project grouping is now the primary project navigation surface. Rejected: Keep the embedded project picker hidden in place | it would leave dead filtering state and a stale recovery path. Rejected: Keep archive-all in the header menu | broad destructive actions are too risky for this surface. Confidence: high Scope-risk: moderate Directive: Do not reintroduce broad session deletion or hidden project filters into the sidebar header without a dedicated reviewed management flow. Tested: cd desktop && bun run test src/components/layout/Sidebar.test.tsx src/stores/sessionStore.test.ts src/components/layout/TabBar.test.tsx Tested: cd desktop && bun run lint Tested: cd desktop && bun run build --- desktop/src/__tests__/agentsSettings.test.tsx | 3 - desktop/src/__tests__/mcpSettings.test.tsx | 3 - desktop/src/__tests__/skillsSettings.test.tsx | 2 - .../components/layout/ProjectFilter.test.tsx | 96 ----- .../src/components/layout/ProjectFilter.tsx | 369 ------------------ .../src/components/layout/Sidebar.test.tsx | 55 ++- desktop/src/components/layout/Sidebar.tsx | 50 +-- desktop/src/components/layout/TabBar.test.tsx | 2 - desktop/src/i18n/locales/en.ts | 4 +- desktop/src/i18n/locales/zh.ts | 4 +- desktop/src/stores/sessionStore.test.ts | 2 - desktop/src/stores/sessionStore.ts | 9 +- 12 files changed, 43 insertions(+), 556 deletions(-) delete mode 100644 desktop/src/components/layout/ProjectFilter.test.tsx delete mode 100644 desktop/src/components/layout/ProjectFilter.tsx diff --git a/desktop/src/__tests__/agentsSettings.test.tsx b/desktop/src/__tests__/agentsSettings.test.tsx index dd035ecb..e1c3b417 100644 --- a/desktop/src/__tests__/agentsSettings.test.tsx +++ b/desktop/src/__tests__/agentsSettings.test.tsx @@ -165,15 +165,12 @@ describe('Settings > Agents tab', () => { activeSessionId: 'session-1', isLoading: false, error: null, - selectedProjects: [], - availableProjects: [], fetchSessions: noopFetch, createSession: vi.fn(), deleteSession: vi.fn(), renameSession: vi.fn(), updateSessionTitle: vi.fn(), setActiveSession: vi.fn(), - setSelectedProjects: vi.fn(), }) useAgentStore.setState({ activeAgents: [], diff --git a/desktop/src/__tests__/mcpSettings.test.tsx b/desktop/src/__tests__/mcpSettings.test.tsx index 418dd5c3..a1f9bf20 100644 --- a/desktop/src/__tests__/mcpSettings.test.tsx +++ b/desktop/src/__tests__/mcpSettings.test.tsx @@ -26,15 +26,12 @@ describe('McpSettings', () => { activeSessionId: 'session-1', isLoading: false, error: null, - selectedProjects: [], - availableProjects: [], fetchSessions: vi.fn(), createSession: vi.fn(), deleteSession: vi.fn(), renameSession: vi.fn(), updateSessionTitle: vi.fn(), setActiveSession: vi.fn(), - setSelectedProjects: vi.fn(), }) useMcpStore.setState({ servers: [], diff --git a/desktop/src/__tests__/skillsSettings.test.tsx b/desktop/src/__tests__/skillsSettings.test.tsx index 2b3d21bb..0ecefea1 100644 --- a/desktop/src/__tests__/skillsSettings.test.tsx +++ b/desktop/src/__tests__/skillsSettings.test.tsx @@ -82,8 +82,6 @@ describe('Settings > Skills tab', () => { activeSessionId: 'session-1', isLoading: false, error: null, - selectedProjects: [], - availableProjects: ['/workspace/project'], }) useTabStore.setState({ tabs: [], activeTabId: null }) useUIStore.setState({ pendingSettingsTab: null }) diff --git a/desktop/src/components/layout/ProjectFilter.test.tsx b/desktop/src/components/layout/ProjectFilter.test.tsx deleted file mode 100644 index 1a861490..00000000 --- a/desktop/src/components/layout/ProjectFilter.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import '@testing-library/jest-dom' - -const { getRecentProjectsMock } = vi.hoisted(() => ({ - getRecentProjectsMock: vi.fn(), -})) - -vi.mock('../../api/sessions', async () => { - const actual = await vi.importActual('../../api/sessions') - return { - ...actual, - sessionsApi: { - ...actual.sessionsApi, - getRecentProjects: getRecentProjectsMock, - }, - } -}) - -vi.mock('../../i18n', () => ({ - useTranslation: () => (key: string) => { - const translations: Record = { - 'sidebar.allProjects': 'All projects', - 'sidebar.other': 'Other', - 'sidebar.noSessions': 'No sessions', - 'common.loading': 'Loading', - } - - return translations[key] ?? key - }, -})) - -import { useSessionStore } from '../../stores/sessionStore' -import { ProjectFilter } from './ProjectFilter' - -describe('ProjectFilter', () => { - beforeEach(() => { - getRecentProjectsMock.mockReset() - useSessionStore.setState({ - sessions: [], - activeSessionId: null, - isLoading: false, - error: null, - selectedProjects: [], - availableProjects: [ - 'Users-nanmi-workspace-myself_code-OpenCutSkill', - 'Users-nanmi-workspace-myself_code-claude-code-haha', - ], - }) - }) - - it('renders recent project metadata instead of bare fallback folder names', async () => { - getRecentProjectsMock.mockResolvedValue({ - projects: [ - { - projectPath: 'Users-nanmi-workspace-myself_code-claude-code-haha', - realPath: '/Users/nanmi/workspace/myself_code/claude-code-haha', - projectName: 'claude-code-haha', - isGit: true, - repoName: 'NanmiCoder/cc-haha', - branch: 'main', - modifiedAt: '2026-04-20T10:00:00.000Z', - sessionCount: 4, - }, - { - projectPath: 'Users-nanmi-workspace-myself_code-OpenCutSkill', - realPath: '/Users/nanmi/workspace/myself_code/OpenCutSkill', - projectName: 'OpenCutSkill', - isGit: true, - repoName: 'NanmiCoder/OpenCutSkill', - branch: 'main', - modifiedAt: '2026-04-20T09:00:00.000Z', - sessionCount: 2, - }, - ], - }) - - render() - - fireEvent.click(screen.getByRole('button', { name: /All projects/i })) - - await waitFor(() => { - expect(screen.getByText('NanmiCoder/cc-haha')).toBeInTheDocument() - expect(screen.getByText('/Users/nanmi/workspace/myself_code/claude-code-haha')).toBeInTheDocument() - expect(screen.getByText('NanmiCoder/OpenCutSkill')).toBeInTheDocument() - }) - - fireEvent.click(screen.getByRole('button', { name: /NanmiCoder\/cc-haha/i })) - - await waitFor(() => { - expect(useSessionStore.getState().selectedProjects).toEqual(['Users-nanmi-workspace-myself_code-claude-code-haha']) - }) - - expect(screen.getAllByRole('button', { name: /NanmiCoder\/cc-haha/i })).toHaveLength(2) - }) -}) diff --git a/desktop/src/components/layout/ProjectFilter.tsx b/desktop/src/components/layout/ProjectFilter.tsx deleted file mode 100644 index 9900b7b9..00000000 --- a/desktop/src/components/layout/ProjectFilter.tsx +++ /dev/null @@ -1,369 +0,0 @@ -import { useState, useRef, useEffect, useMemo, useCallback } from 'react' -import { createPortal } from 'react-dom' -import { sessionsApi, type RecentProject } from '../../api/sessions' -import { useSessionStore } from '../../stores/sessionStore' -import { useTranslation } from '../../i18n' - -type DropdownPos = { - top: number - left: number - direction: 'up' | 'down' -} - -type ProjectOption = { - projectPath: string - title: string - subtitle: string | null - isGit: boolean - branch: string | null - modifiedAt?: string -} - -let cachedProjects: RecentProject[] | null = null -let cacheTimestamp = 0 -const CACHE_TTL = 30_000 - -export function ProjectFilter({ variant = 'default' }: { variant?: 'default' | 'embedded' }) { - const t = useTranslation() - const { availableProjects, selectedProjects, setSelectedProjects } = useSessionStore() - const [open, setOpen] = useState(false) - const [projects, setProjects] = useState([]) - const [loading, setLoading] = useState(false) - const [dropdownPos, setDropdownPos] = useState(null) - const ref = useRef(null) - const triggerRef = useRef(null) - const dropdownRef = useRef(null) - - const updateDropdownPos = useCallback(() => { - if (!triggerRef.current) return - const rect = triggerRef.current.getBoundingClientRect() - const dropdownHeight = 420 - const spaceAbove = rect.top - const spaceBelow = window.innerHeight - rect.bottom - const direction = spaceBelow >= dropdownHeight || spaceBelow >= spaceAbove ? 'down' : 'up' - - setDropdownPos({ - top: direction === 'down' ? rect.bottom + 8 : rect.top - 8, - left: rect.left, - direction, - }) - }, []) - - useEffect(() => { - if (!open) return - - const handleClick = (e: MouseEvent) => { - const target = e.target as Node - if (ref.current?.contains(target)) return - if (dropdownRef.current?.contains(target)) return - setOpen(false) - } - - document.addEventListener('mousedown', handleClick) - return () => document.removeEventListener('mousedown', handleClick) - }, [open]) - - useEffect(() => { - if (!open) return - updateDropdownPos() - window.addEventListener('scroll', updateDropdownPos, true) - window.addEventListener('resize', updateDropdownPos) - return () => { - window.removeEventListener('scroll', updateDropdownPos, true) - window.removeEventListener('resize', updateDropdownPos) - } - }, [open, updateDropdownPos]) - - useEffect(() => { - if (!open) return - if (cachedProjects && Date.now() - cacheTimestamp < CACHE_TTL) { - setProjects(cachedProjects) - return - } - - setLoading(true) - sessionsApi.getRecentProjects(200) - .then(({ projects: nextProjects }) => { - cachedProjects = nextProjects - cacheTimestamp = Date.now() - setProjects(nextProjects) - }) - .catch(() => setProjects([])) - .finally(() => setLoading(false)) - }, [open]) - - const isAllSelected = selectedProjects.length === 0 - - const options = useMemo(() => { - const availableSet = new Set(availableProjects) - const optionsByPath = new Map() - - for (const project of projects) { - const optionPath = availableSet.has(project.realPath) ? project.realPath : project.projectPath - if (!availableSet.has(optionPath)) continue - optionsByPath.set(optionPath, { - projectPath: optionPath, - title: project.repoName || project.projectName, - subtitle: project.realPath, - isGit: project.isGit, - branch: project.branch, - modifiedAt: project.modifiedAt, - }) - } - - for (const projectPath of availableProjects) { - if (optionsByPath.has(projectPath)) continue - optionsByPath.set(projectPath, { - projectPath, - title: fallbackProjectTitle(projectPath, t('sidebar.other')), - subtitle: null, - isGit: false, - branch: null, - }) - } - - return [...optionsByPath.values()].sort(compareProjectOptions) - }, [availableProjects, projects, t]) - - const optionByPath = useMemo( - () => new Map(options.map((option) => [option.projectPath, option])), - [options], - ) - - const label = isAllSelected - ? t('sidebar.allProjects') - : selectedProjects.length === 1 - ? optionByPath.get(selectedProjects[0]!)?.title || fallbackProjectTitle(selectedProjects[0]!, t('sidebar.other')) - : `${selectedProjects.length} projects` - const triggerLabel = isAllSelected ? t('sidebar.allProjects') : label - - const toggleProject = (projectPath: string) => { - if (isAllSelected) { - setSelectedProjects([projectPath]) - return - } - - if (selectedProjects.includes(projectPath)) { - const next = selectedProjects.filter((path) => path !== projectPath) - setSelectedProjects(next.length === 0 ? [] : next) - return - } - - const next = [...selectedProjects, projectPath] - setSelectedProjects(next.length >= availableProjects.length ? [] : next) - } - - const selectAll = () => setSelectedProjects([]) - - return ( -
- - - {open && dropdownPos && createPortal( -
-
- - -
- - {loading ? ( -
{t('common.loading')}
- ) : options.length === 0 ? ( -
{t('sidebar.noSessions')}
- ) : ( - options.map((option) => { - const checked = !isAllSelected && selectedProjects.includes(option.projectPath) - return ( - - ) - }) - )} -
-
, - document.body, - )} -
- ) -} - -function compareProjectOptions(a: ProjectOption, b: ProjectOption) { - if (a.modifiedAt && b.modifiedAt && a.modifiedAt !== b.modifiedAt) { - return b.modifiedAt.localeCompare(a.modifiedAt) - } - if (a.modifiedAt && !b.modifiedAt) return -1 - if (!a.modifiedAt && b.modifiedAt) return 1 - return a.title.localeCompare(b.title) -} - -function fallbackProjectTitle(projectPath: string, fallback: string) { - if (!projectPath || projectPath === '_unknown') return fallback - if (projectPath.includes('/')) { - return projectPath.split('/').filter(Boolean).pop() || fallback - } - - const segments = projectPath.split('-').filter(Boolean) - return segments[segments.length - 1] || projectPath || fallback -} - -function ChevronIcon({ open }: { open: boolean }) { - return ( - - - - ) -} - -function FolderIcon({ className = 'text-[var(--color-text-secondary)]' }: { className?: string }) { - return ( - - - - ) -} - -function GitBranchIcon({ className = 'text-[var(--color-text-secondary)]' }: { className?: string }) { - return ( - - - - - - - ) -} - -function CheckIcon() { - return ( - - - - ) -} diff --git a/desktop/src/components/layout/Sidebar.test.tsx b/desktop/src/components/layout/Sidebar.test.tsx index 4c6f9e6f..473c41a1 100644 --- a/desktop/src/components/layout/Sidebar.test.tsx +++ b/desktop/src/components/layout/Sidebar.test.tsx @@ -2,10 +2,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react' import '@testing-library/jest-dom' -vi.mock('./ProjectFilter', () => ({ - ProjectFilter: () =>
, -})) - const desktopUiPreferencesApiMock = vi.hoisted(() => ({ getPreferences: vi.fn(), updateSidebarPreferences: vi.fn(), @@ -62,8 +58,8 @@ vi.mock('../../i18n', () => ({ 'sidebar.restoreProjectToSidebar': 'Restore to Sidebar', 'sidebar.projectHidden': '{project} was removed from the sidebar.', 'sidebar.newSessionInProject': 'New session in {project}', - 'sidebar.showMoreSessions': 'Show {count} more', - 'sidebar.showFewerSessions': 'Show fewer', + 'sidebar.showMoreSessions': 'Expand display', + 'sidebar.showFewerSessions': 'Collapse display', 'sidebar.expandProject': 'Expand {project}', 'sidebar.collapseProject': 'Collapse {project}', 'sidebar.worktree': 'worktree', @@ -205,8 +201,6 @@ describe('Sidebar', () => { activeSessionId: null, isLoading: false, error: null, - selectedProjects: [], - availableProjects: [], isBatchMode: false, selectedSessionIds: new Set(), fetchSessions, @@ -282,10 +276,10 @@ describe('Sidebar', () => { expect(screen.queryByRole('button', { name: /Alpha hidden/ })).not.toBeInTheDocument() expect(screen.getByRole('button', { name: /Alpha newest/ }).closest('[class*="pl-0"]')).toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: 'Show 5 more' })) + fireEvent.click(screen.getByRole('button', { name: 'Expand display' })) expect(screen.getByRole('button', { name: /Alpha hidden/ })).toBeInTheDocument() - expect(screen.getByRole('button', { name: 'Show fewer' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Collapse display' })).toBeInTheDocument() }) it('reorders project groups by dragging project headers while preserving expanded state', async () => { @@ -307,7 +301,7 @@ describe('Sidebar', () => { render() - fireEvent.click(screen.getByRole('button', { name: 'Show 5 more' })) + fireEvent.click(screen.getByRole('button', { name: 'Expand display' })) expect(screen.getByRole('button', { name: /Alpha hidden/ })).toBeInTheDocument() expect(projectGroupNames().slice(0, 3)).toEqual(['alpha', 'beta', 'gamma']) @@ -388,9 +382,15 @@ describe('Sidebar', () => { render() - fireEvent.click(screen.getByRole('button', { name: 'Show 8 more' })) + const expandButton = screen.getByRole('button', { name: 'Expand display' }) + expect(expandButton).toHaveAttribute('aria-expanded', 'false') + expect(expandButton.parentElement).toHaveClass('justify-start') + expect(expandButton).toHaveClass('text-[var(--color-text-tertiary)]', 'opacity-75') + + fireEvent.click(expandButton) expect(screen.getByTestId('sidebar-project-session-list-workspace-alpha')).toHaveClass('max-h-[420px]', 'overflow-y-auto') + expect(screen.getByRole('button', { name: 'Collapse display' })).toHaveAttribute('aria-expanded', 'true') }) it('creates a new session from the project group context', async () => { @@ -469,7 +469,7 @@ describe('Sidebar', () => { expect(window.localStorage.getItem(PROJECT_SORT_STORAGE_KEY)).toBe('createdAt') }) - it('uses the project header menu to request archiving all visible sessions', () => { + it('hides archive-all from the project header menu', () => { const now = new Date().toISOString() useSessionStore.setState({ sessions: [ @@ -481,12 +481,11 @@ describe('Sidebar', () => { render() fireEvent.click(screen.getByRole('button', { name: 'Project menu' })) - fireEvent.click(screen.getByRole('menuitem', { name: 'Archive all chats' })) - const dialog = screen.getByRole('dialog') - expect(within(dialog).getByText('Delete 2 sessions? This cannot be undone.')).toBeInTheDocument() - expect(within(dialog).getByText('Alpha Session')).toBeInTheDocument() - expect(within(dialog).getByText('Beta Session')).toBeInTheDocument() + expect(screen.queryByRole('menuitem', { name: 'Archive all chats' })).not.toBeInTheDocument() + expect(screen.getByRole('menuitem', { name: 'Organize sidebar' })).toBeInTheDocument() + expect(screen.getByRole('menuitem', { name: 'Sort condition' })).toBeInTheDocument() + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() }) it('keeps project row actions hidden until project hover or focus', () => { @@ -580,11 +579,10 @@ describe('Sidebar', () => { }) }) - it('restores hidden project state from localStorage and shows selected hidden projects for recovery', () => { + it('keeps hidden projects out of the sidebar without the removed project filter', () => { window.localStorage.setItem(PROJECT_HIDDEN_STORAGE_KEY, JSON.stringify(['/workspace/beta'])) const now = new Date().toISOString() useSessionStore.setState({ - selectedProjects: ['/workspace/beta'], sessions: [ makeSession('alpha-1', 'Alpha Session', '/workspace/alpha', now), makeSession('beta-1', 'Beta Session', '/workspace/beta', now), @@ -593,11 +591,9 @@ describe('Sidebar', () => { render() - expect(screen.queryByText('alpha')).not.toBeInTheDocument() - expect(screen.getByText('beta')).toBeInTheDocument() - - fireEvent.click(screen.getByRole('button', { name: 'Project actions for beta' })) - expect(screen.getByRole('menuitem', { name: 'Restore to Sidebar' })).toBeInTheDocument() + expect(screen.getByText('alpha')).toBeInTheDocument() + expect(screen.queryByText('beta')).not.toBeInTheDocument() + expect(screen.queryByTestId('project-filter')).not.toBeInTheDocument() }) it('uses server sidebar preferences across browser and desktop storage contexts', async () => { @@ -917,11 +913,14 @@ describe('Sidebar', () => { expect(screen.getByRole('complementary')).toHaveAttribute('data-state', 'open') }) - it('keeps the project filter section overflow visible for dropdown menus', () => { + it('renders search controls without the removed embedded project filter', () => { render() - expect(screen.getByTestId('sidebar-project-filter-section')).toHaveStyle({ overflow: 'visible' }) - expect(screen.getByTestId('sidebar-project-filter-section')).toHaveClass('relative', 'z-20') + expect(screen.getByTestId('sidebar-search-controls-section')).toHaveStyle({ overflow: 'visible' }) + expect(screen.getByTestId('sidebar-search-controls-section')).toHaveClass('relative', 'z-20') + expect(screen.getByPlaceholderText('Search sessions')).toBeInTheDocument() + expect(screen.queryByRole('button', { name: /All projects/i })).not.toBeInTheDocument() + expect(screen.queryByTestId('project-filter')).not.toBeInTheDocument() }) it('keeps the session list section in a constrained flex column for scrolling', () => { diff --git a/desktop/src/components/layout/Sidebar.tsx b/desktop/src/components/layout/Sidebar.tsx index da5b8ae0..a4593ddf 100644 --- a/desktop/src/components/layout/Sidebar.tsx +++ b/desktop/src/components/layout/Sidebar.tsx @@ -1,9 +1,8 @@ import { useEffect, useState, useCallback, useMemo, useRef } from 'react' -import { Archive, Check, ChevronDown, Clock, Folder, FolderOpen, FolderPlus, MoreHorizontal, Pin, PinOff, RefreshCw, RotateCcw, SquarePen, X } from 'lucide-react' +import { Check, ChevronDown, Clock, Folder, FolderOpen, FolderPlus, MoreHorizontal, Pin, PinOff, RefreshCw, RotateCcw, SquarePen, X } from 'lucide-react' import { useSessionStore } from '../../stores/sessionStore' import { useUIStore } from '../../stores/uiStore' import { useTranslation } from '../../i18n' -import { ProjectFilter } from './ProjectFilter' import { ConfirmDialog } from '../shared/ConfirmDialog' import type { SessionListItem } from '../../types/session' import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID } from '../../stores/tabStore' @@ -43,7 +42,6 @@ type SidebarProps = { export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { const t = useTranslation() const sessions = useSessionStore((s) => s.sessions) - const selectedProjects = useSessionStore((s) => s.selectedProjects) const isLoading = useSessionStore((s) => s.isLoading) const error = useSessionStore((s) => s.error) const fetchSessions = useSessionStore((s) => s.fetchSessions) @@ -107,15 +105,12 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { const filteredSessions = useMemo(() => { let result = sessions - if (selectedProjects.length > 0) { - result = result.filter((s) => selectedProjects.includes(getSessionProjectKey(s))) - } if (searchQuery) { const q = searchQuery.toLowerCase() result = result.filter((s) => s.title.toLowerCase().includes(q)) } return result - }, [sessions, selectedProjects, searchQuery]) + }, [sessions, searchQuery]) const projectGroups = useMemo(() => groupByProject(filteredSessions, projectSortBy), [filteredSessions, projectSortBy]) const orderedProjectGroups = useMemo( @@ -125,9 +120,9 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { const visibleProjectGroups = useMemo(() => { if (hiddenProjectKeys.size === 0) return orderedProjectGroups return orderedProjectGroups.filter((project) => ( - !hiddenProjectKeys.has(project.key) || selectedProjects.includes(project.key) + !hiddenProjectKeys.has(project.key) )) - }, [hiddenProjectKeys, orderedProjectGroups, selectedProjects]) + }, [hiddenProjectKeys, orderedProjectGroups]) const showInitialLoading = isLoading && sessions.length === 0 const filteredSessionIds = useMemo(() => filteredSessions.map((session) => session.id), [filteredSessions]) const selectedCount = selectedSessionIds.size @@ -444,12 +439,6 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { setPendingBatchDeleteSessionIds([...new Set(ids)]) }, []) - const requestArchiveAllVisibleSessions = useCallback(() => { - setProjectHeaderMenu(null) - setProjectHeaderSubmenu(null) - requestBatchDelete(filteredSessionIds) - }, [filteredSessionIds, requestBatchDelete]) - const confirmBatchDelete = useCallback(async () => { const ids = pendingBatchDeleteSessionIds ?? [] if (ids.length === 0) return @@ -668,14 +657,12 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { {expanded ? ( <>
-
- -