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)
})
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 () => {
const sessionId = 'terminal-hide-session'

View File

@ -335,6 +335,7 @@ export function ActiveSession() {
const streamingText = sessionState?.streamingText ?? ''
const activeGoal = sessionState?.activeGoal ?? null
const isEmpty = messages.length === 0 && !streamingText && (session?.messageCount ?? 0) === 0
const compactEmptyHero = isEmpty && showTerminalPanel
const isHistoryLoading =
!isMemberSession &&
(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-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 && (
<div className="shrink-0 border-b border-[var(--color-border)] bg-[var(--color-surface-container)]">
@ -418,11 +419,17 @@ export function ActiveSession() {
)}
{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">
{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)]">
{memberInfo?.status === 'running'
? `${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" />
<h1 className="mb-2 text-3xl font-extrabold tracking-tight text-[var(--color-text-primary)]" style={{ fontFamily: 'var(--font-headline)' }}>
<img
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')}
</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')}
</p>
</>
@ -543,7 +554,7 @@ export function ActiveSession() {
<div
data-testid="session-terminal-panel"
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',
].join(' ')}
style={{ height: showTerminalPanel ? terminalPanelHeight : 0 }}

View File

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