fix(desktop): improve docked terminal sizing (#708)

Tested: cd desktop && bun run test -- src/pages/ActiveSession.test.tsx
Tested: bun run check:desktop
Confidence: high
Scope-risk: narrow
This commit is contained in:
程序员阿江(Relakkes) 2026-06-15 17:23:36 +08:00
parent 18c6a43033
commit 4573e71801
3 changed files with 78 additions and 10 deletions

View File

@ -958,6 +958,63 @@ describe('ActiveSession task polling', () => {
expect(useTabStore.getState().activeTabId).toBe(terminalTab?.sessionId) expect(useTabStore.getState().activeTabId).toBe(terminalTab?.sessionId)
}) })
it('keeps the docked terminal usable on a new empty session', () => {
const sessionId = 'empty-terminal-session'
useSessionStore.setState({
sessions: [{
id: sessionId,
title: 'Empty Terminal Session',
createdAt: '2026-04-10T00:00:00.000Z',
modifiedAt: '2026-04-10T00:00:00.000Z',
messageCount: 0,
projectPath: '/tmp/project-root',
workDir: '/tmp/project-root',
workDirExists: true,
}],
activeSessionId: sessionId,
isLoading: false,
error: null,
})
useTabStore.setState({
tabs: [{ sessionId, title: 'Empty Terminal Session', type: 'session', status: 'idle' }],
activeTabId: sessionId,
})
useChatStore.setState({
sessions: {
[sessionId]: {
messages: [],
chatState: 'idle',
connectionState: 'connected',
streamingText: '',
streamingToolInput: '',
activeToolUseId: null,
activeToolName: null,
activeThinkingId: null,
pendingPermission: null,
pendingComputerUsePermission: null,
tokenUsage: { input_tokens: 0, output_tokens: 0 },
streamingResponseChars: 0,
elapsedSeconds: 0,
statusVerb: '',
slashCommands: [],
agentTaskNotifications: {},
elapsedTimer: null,
},
},
})
useTerminalPanelStore.getState().openPanel(sessionId)
render(<ActiveSession />)
expect(screen.getByTestId('active-session-chat-column')).toHaveClass('min-h-0')
expect(screen.getByTestId('empty-session-hero')).toHaveClass('min-h-0')
expect(screen.getByTestId('empty-session-hero')).toHaveClass('pb-6')
expect(screen.getByTestId('empty-session-hero')).not.toHaveClass('pb-32')
expect(screen.getByTestId('session-terminal-panel')).toHaveStyle({ height: '420px' })
expect(screen.getByTestId('terminal-resize-handle')).toHaveAttribute('aria-valuemax', '760')
})
it('keeps the docked terminal mounted when the panel is hidden', async () => { it('keeps the docked terminal mounted when the panel is hidden', async () => {
const sessionId = 'terminal-hide-session' const sessionId = 'terminal-hide-session'

View File

@ -335,6 +335,7 @@ export function ActiveSession() {
const streamingText = sessionState?.streamingText ?? '' const streamingText = sessionState?.streamingText ?? ''
const activeGoal = sessionState?.activeGoal ?? null const activeGoal = sessionState?.activeGoal ?? null
const isEmpty = messages.length === 0 && !streamingText && (session?.messageCount ?? 0) === 0 const isEmpty = messages.length === 0 && !streamingText && (session?.messageCount ?? 0) === 0
const compactEmptyHero = isEmpty && showTerminalPanel
const isHistoryLoading = const isHistoryLoading =
!isMemberSession && !isMemberSession &&
(session?.messageCount ?? 0) > 0 && (session?.messageCount ?? 0) > 0 &&
@ -370,7 +371,7 @@ export function ActiveSession() {
<div data-testid="active-session-content-row" className="flex min-h-0 min-w-0 flex-1"> <div data-testid="active-session-content-row" className="flex min-h-0 min-w-0 flex-1">
<div <div
data-testid="active-session-chat-column" data-testid="active-session-chat-column"
className={`flex flex-col ${showRightPanel ? CHAT_COLUMN_WITH_WORKSPACE_CLASS : isMobileLayout ? 'min-w-0 flex-1' : 'min-w-[360px] flex-1'}`} className={`flex min-h-0 flex-col ${showRightPanel ? CHAT_COLUMN_WITH_WORKSPACE_CLASS : isMobileLayout ? 'min-w-0 flex-1' : 'min-w-[360px] flex-1'}`}
> >
{isMemberSession && ( {isMemberSession && (
<div className="shrink-0 border-b border-[var(--color-border)] bg-[var(--color-surface-container)]"> <div className="shrink-0 border-b border-[var(--color-border)] bg-[var(--color-surface-container)]">
@ -418,11 +419,17 @@ export function ActiveSession() {
)} )}
{isEmpty ? ( {isEmpty ? (
<div className="flex flex-1 flex-col items-center justify-center p-8 pb-32"> <div
data-testid="empty-session-hero"
className={[
'flex min-h-0 flex-1 flex-col items-center justify-center overflow-hidden px-8 pt-8',
compactEmptyHero ? 'pb-6' : 'pb-32',
].join(' ')}
>
<div className="flex max-w-md flex-col items-center text-center"> <div className="flex max-w-md flex-col items-center text-center">
{isMemberSession ? ( {isMemberSession ? (
<> <>
<span className="material-symbols-outlined text-[48px] mb-4 text-[var(--color-text-tertiary)]">smart_toy</span> <span className={`material-symbols-outlined mb-4 text-[var(--color-text-tertiary)] ${compactEmptyHero ? 'text-[36px]' : 'text-[48px]'}`}>smart_toy</span>
<p className="text-[var(--color-text-secondary)]"> <p className="text-[var(--color-text-secondary)]">
{memberInfo?.status === 'running' {memberInfo?.status === 'running'
? `${memberInfo.role} ${t('teams.working')}` ? `${memberInfo.role} ${t('teams.working')}`
@ -431,11 +438,15 @@ export function ActiveSession() {
</> </>
) : ( ) : (
<> <>
<img src={publicAssetPath('app-icon.png')} alt="Claude Code Haha" className="mb-6 h-24 w-24" /> <img
<h1 className="mb-2 text-3xl font-extrabold tracking-tight text-[var(--color-text-primary)]" style={{ fontFamily: 'var(--font-headline)' }}> src={publicAssetPath('app-icon.png')}
alt="Claude Code Haha"
className={compactEmptyHero ? 'mb-4 h-16 w-16' : 'mb-6 h-24 w-24'}
/>
<h1 className={`${compactEmptyHero ? 'mb-1 text-2xl' : 'mb-2 text-3xl'} font-extrabold tracking-tight text-[var(--color-text-primary)]`} style={{ fontFamily: 'var(--font-headline)' }}>
{t('empty.title')} {t('empty.title')}
</h1> </h1>
<p className="mx-auto max-w-xs text-[var(--color-text-secondary)]" style={{ fontFamily: 'var(--font-body)' }}> <p className={`mx-auto max-w-xs text-[var(--color-text-secondary)] ${compactEmptyHero ? 'text-sm' : ''}`} style={{ fontFamily: 'var(--font-body)' }}>
{t('empty.subtitle')} {t('empty.subtitle')}
</p> </p>
</> </>
@ -543,7 +554,7 @@ export function ActiveSession() {
<div <div
data-testid="session-terminal-panel" data-testid="session-terminal-panel"
className={[ className={[
'flex shrink-0 flex-col border-t border-[var(--color-border)] bg-[var(--color-surface-container-lowest)]', 'flex min-h-0 shrink-0 flex-col border-t border-[var(--color-border)] bg-[var(--color-surface-container-lowest)]',
showTerminalPanel ? '' : 'hidden', showTerminalPanel ? '' : 'hidden',
].join(' ')} ].join(' ')}
style={{ height: showTerminalPanel ? terminalPanelHeight : 0 }} style={{ height: showTerminalPanel ? terminalPanelHeight : 0 }}

View File

@ -1,9 +1,9 @@
import { create } from 'zustand' import { create } from 'zustand'
import { destroyTerminalRuntime } from '../lib/terminalRuntime' import { destroyTerminalRuntime } from '../lib/terminalRuntime'
export const TERMINAL_PANEL_DEFAULT_HEIGHT = 300 export const TERMINAL_PANEL_DEFAULT_HEIGHT = 420
export const TERMINAL_PANEL_MIN_HEIGHT = 220 export const TERMINAL_PANEL_MIN_HEIGHT = 260
export const TERMINAL_PANEL_MAX_HEIGHT = 560 export const TERMINAL_PANEL_MAX_HEIGHT = 760
export const SESSION_TERMINAL_RUNTIME_PREFIX = '__session_terminal__' export const SESSION_TERMINAL_RUNTIME_PREFIX = '__session_terminal__'
type TerminalPanelSessionState = { type TerminalPanelSessionState = {