Make batch-selected sidebar rows easier to scan

The previous batch selection state let adjacent selected sessions visually merge into a single block. This keeps the existing row structure but adds per-row spacing, a transparent baseline border, and a subtle selected-row shadow so the selection is easier to read without changing behavior.

Constraint: Keep the change local to the desktop sidebar and avoid new dependencies.
Rejected: Add global sidebar CSS tokens | unnecessary for this small component-level state and harder for changed-line coverage.
Confidence: high
Scope-risk: narrow
Directive: Keep batch selection spacing on the row wrapper so adjacent selected rows remain visually separated.
Tested: bun run verify
This commit is contained in:
程序员阿江(Relakkes) 2026-05-12 20:48:44 +08:00
parent a381bf0412
commit fc3a6ea2e1
2 changed files with 57 additions and 5 deletions

View File

@ -260,6 +260,58 @@ describe('Sidebar', () => {
})
})
it('renders batch-selected sessions as separated selected rows', () => {
const now = new Date().toISOString()
useSessionStore.setState({
sessions: [
{
id: 'session-1',
title: 'First Session',
createdAt: now,
modifiedAt: now,
messageCount: 1,
projectPath: '/workspace/project',
workDir: '/workspace/project',
workDirExists: true,
},
{
id: 'session-2',
title: 'Second Session',
createdAt: now,
modifiedAt: now,
messageCount: 1,
projectPath: '/workspace/project',
workDir: '/workspace/project',
workDirExists: true,
},
{
id: 'session-3',
title: 'Third Session',
createdAt: now,
modifiedAt: now,
messageCount: 1,
projectPath: '/workspace/project',
workDir: '/workspace/project',
workDirExists: true,
},
],
})
useTabStore.setState({
tabs: [{ sessionId: 'session-2', title: 'Second Session', type: 'session', status: 'idle' }],
activeTabId: 'session-2',
})
render(<Sidebar />)
fireEvent.click(screen.getByRole('button', { name: 'Batch manage' }))
fireEvent.click(screen.getByRole('button', { name: /First Session/ }))
expect(screen.getByRole('button', { name: /First Session/ }).parentElement).toHaveClass('mb-1.5')
expect(screen.getByRole('button', { name: /First Session/ })).toHaveClass('sidebar-session-row--selected')
expect(screen.getByRole('button', { name: /Second Session/ })).toHaveClass('sidebar-session-row--active')
expect(screen.getByRole('button', { name: /Third Session/ })).toHaveClass('sidebar-session-row--idle')
})
it('collapses into an icon rail and expands back', async () => {
render(<Sidebar />)

View File

@ -493,7 +493,7 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) {
)}
</div>
{items.map((session) => (
<div key={session.id} className="relative">
<div key={session.id} className="relative mb-1.5 last:mb-0">
{renamingId === session.id ? (
<input
autoFocus
@ -522,12 +522,12 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) {
}}
onContextMenu={(e) => handleContextMenu(e, session.id)}
className={`
group w-full rounded-[12px] px-3 ${isMobile ? 'py-3' : 'py-2'} text-left text-sm transition-colors duration-200
group w-full rounded-[12px] border px-3 ${isMobile ? 'py-3' : 'py-2'} text-left text-sm transition-[background,border-color,box-shadow,filter,color] duration-200
${selectedSessionIds.has(session.id)
? 'bg-[var(--color-sidebar-item-active)] text-[var(--color-text-primary)] ring-1 ring-[var(--color-brand)]/15'
? 'sidebar-session-row--selected border-[var(--color-sidebar-item-active-border)] bg-[var(--color-sidebar-item-active)] text-[var(--color-text-primary)] shadow-[inset_0_1px_0_rgba(255,255,255,0.72),0_3px_10px_rgba(143,72,47,0.07)] hover:brightness-[0.995]'
: session.id === activeTabId
? 'bg-[var(--color-sidebar-item-active)] text-[var(--color-text-primary)]'
: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-sidebar-item-hover)]'
? 'sidebar-session-row--active border-transparent bg-[var(--color-sidebar-item-active)] text-[var(--color-text-primary)]'
: 'sidebar-session-row--idle border-transparent text-[var(--color-text-secondary)] hover:bg-[var(--color-sidebar-item-hover)]'
}
`}
aria-pressed={isBatchMode ? selectedSessionIds.has(session.id) : undefined}