fix(desktop): hide short-project fold control and shrink context meter

The fold button was shown whenever a project was marked expanded, including
short lists auto-expanded by history paging. Keep it only above the 6-session
threshold. Replace the composer percentage chip with a compact ring.
This commit is contained in:
程序员阿江(Relakkes)
2026-09-15 20:21:49 +08:00
parent 3e160f7e7a
commit 8a9e3800a7
4 changed files with 67 additions and 13 deletions
@@ -635,7 +635,7 @@ describe('ContextUsageIndicator touch target', () => {
render(<ContextUsageIndicator sessionId="session-1" chatState="idle" messageCount={1} compact />)
const trigger = screen.getByTestId('context-usage-indicator')
expect(trigger).toHaveClass('h-8')
expect(trigger).toHaveClass('h-8', 'w-8')
expect(trigger).not.toHaveClass('h-11')
})
@@ -646,7 +646,7 @@ describe('ContextUsageIndicator touch target', () => {
render(<ContextUsageIndicator sessionId="session-1" chatState="idle" messageCount={1} compact />)
const trigger = screen.getByTestId('context-usage-indicator')
expect(trigger).toHaveClass('h-11')
expect(trigger).toHaveClass('h-11', 'w-11')
expect(trigger).not.toHaveClass('h-8')
})
@@ -700,6 +700,9 @@ describe('ContextUsageIndicator presentation', () => {
await waitFor(() => {
expect(screen.getByTestId('context-usage-indicator')).toHaveTextContent('21%')
})
const trigger = screen.getByTestId('context-usage-indicator')
expect(trigger).toHaveClass('h-8', 'w-8')
expect(trigger.querySelector('.font-mono')).not.toBeInTheDocument()
expect(screen.queryByTestId('context-usage-popover')).not.toBeInTheDocument()
fireEvent.click(screen.getByTestId('context-usage-indicator'))
@@ -544,29 +544,31 @@ export function ContextUsageIndicator({
onClick={handleTriggerClick}
title={t('contextIndicator.title')}
data-testid="context-usage-indicator"
className={`flex shrink-0 items-center gap-[7px] rounded-full border border-[var(--color-border)] bg-transparent text-[var(--color-text-secondary)] transition-[background-color,color,border-color] duration-150 ease-out hover:border-[var(--color-outline)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-container-lowest)] ${
isMobileBrowser ? 'h-11' : 'h-8'
} ${compact ? 'px-2' : 'px-3'} ${detailsOpen ? 'border-[var(--color-outline)] bg-[var(--color-surface-hover)] text-[var(--color-text-primary)]' : ''}`}
className={`group grid shrink-0 place-items-center rounded-full bg-transparent text-[var(--color-text-secondary)] transition-[background-color,color] duration-150 ease-out hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface-container-lowest)] ${
isMobileBrowser ? 'h-11 w-11' : 'h-8 w-8'
} ${detailsOpen ? 'bg-[var(--color-surface-hover)] text-[var(--color-text-primary)]' : ''}`}
>
<span className="relative grid h-[18px] w-[18px] shrink-0 place-items-center rounded-full">
<span className={`relative grid shrink-0 place-items-center rounded-full ${isMobileBrowser ? 'h-[22px] w-[22px]' : 'h-5 w-5'}`}>
{loading && !displayContext ? (
<span className="absolute inset-[2px] rounded-full border-2 border-[var(--color-text-tertiary)] border-t-transparent motion-safe:animate-spin" />
) : (
<span
className="relative grid h-[18px] w-[18px] place-items-center rounded-full"
className={`relative grid place-items-center rounded-full ${isMobileBrowser ? 'h-[22px] w-[22px]' : 'h-5 w-5'}`}
style={ringStyle}
>
<span className="absolute inset-[3px] rounded-full bg-[var(--color-surface-container-lowest)]" />
<span className={`absolute inset-[3.5px] rounded-full transition-colors duration-150 ${
detailsOpen
? 'bg-[var(--color-surface-hover)]'
: 'bg-[var(--color-surface-container-lowest)] group-hover:bg-[var(--color-surface-hover)]'
}`} />
<span
className="relative h-[5px] w-[5px] rounded-full"
className="relative h-1.5 w-1.5 rounded-full"
style={{ backgroundColor: displayContext ? strokeColor : 'var(--color-text-tertiary)' }}
/>
</span>
)}
</span>
<span className="font-mono text-[11px] font-semibold tabular-nums">
{displayPercent}
</span>
<span className="sr-only">{displayPercent}</span>
</button>
{!preferSheet && detailsOpen && popoverPosition && createPortal(
@@ -572,6 +572,54 @@ describe('Sidebar', () => {
expect(screen.getByRole('button', { name: 'Collapse display' })).toBeInTheDocument()
})
it('does not show a fold control when a project is at or below the collapse threshold', () => {
const base = new Date('2026-05-15T10:00:00.000Z').getTime()
useSessionStore.setState({
sessions: [
...Array.from({ length: 6 }, (_, index) => (
makeSession(
`alpha-${index + 1}`,
`Alpha ${index + 1}`,
'/workspace/alpha',
new Date(base - index * 1000).toISOString(),
)
)),
makeSession('beta-1', 'Beta only', '/workspace/beta', new Date(base - 7000).toISOString()),
],
})
render(<Sidebar />)
expect(screen.getByRole('button', { name: /Alpha 1/ })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /Alpha 6/ })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /Beta only/ })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Expand display' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Collapse display' })).not.toBeInTheDocument()
})
it('does not show a fold control after a short project is auto-expanded for history', () => {
useSessionStore.setState({
sessions: [
makeSession('alpha-1', 'Alpha one', '/workspace/alpha', '2026-05-15T10:00:00.000Z'),
makeSession('alpha-2', 'Alpha two', '/workspace/alpha', '2026-05-14T10:00:00.000Z'),
],
})
render(<Sidebar />)
const scroller = screen.getByTestId('sidebar-project-session-list-workspace-alpha')
Object.defineProperties(scroller, {
clientHeight: { configurable: true, value: 80 },
scrollHeight: { configurable: true, value: 80 },
})
fireEvent.wheel(scroller, { deltaY: 20 })
expect(screen.getByRole('button', { name: /Alpha one/ })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /Alpha two/ })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Expand display' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Collapse display' })).not.toBeInTheDocument()
})
it('lets a manual session refresh supersede a stuck automatic refresh', async () => {
fetchSessions.mockReturnValue(new Promise(() => {}))
+2 -1
View File
@@ -1236,6 +1236,7 @@ export function Sidebar({
? []
: getVisibleProjectSessions(project.sessions, sessionsExpanded, activeTabId)
const hiddenCount = project.sessions.length - visibleItems.length
const showSessionFoldControl = project.sessions.length > PROJECT_GROUP_VISIBLE_COUNT
const groupIds = project.sessions.map((session) => session.id)
const groupSelectedCount = groupIds.filter((id) => selectedSessionIds.has(id)).length
const history = projectHistory[project.key]
@@ -1444,7 +1445,7 @@ export function Sidebar({
</div>
))}
</ProjectSessionList>
{(hiddenCount > 0 || sessionsExpanded) && (
{showSessionFoldControl && (
<div className="mt-2 flex justify-start px-2.5">
<button
type="button"