mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
fix: restore empty session launcher layout
The repository launch controls should extend the existing empty-session composer, not replace the session layout. This restores the previous mascot, title, composer sizing, textarea, and run button while keeping the project, branch, and worktree controls attached directly beneath the input. Constraint: User requested the original session layout stay intact except for adding repository controls under the chat input Rejected: Keep the Codex-style centered launcher rewrite | it changed too much of the existing session surface Confidence: high Scope-risk: narrow Directive: Future changes to repository launch controls should treat EmptySession layout as fixed unless explicitly requested otherwise Tested: cd desktop && bun run test -- --run src/components/shared/DirectoryPicker.test.tsx src/pages/EmptySession.test.tsx src/__tests__/pages.test.tsx Tested: cd desktop && bun run lint Tested: bun run check:desktop Tested: agent-browser screenshot /tmp/cc-haha-layout-restored-selected.png on localhost:4790 Not-tested: root bun run verify not rerun for this narrow layout correction
This commit is contained in:
parent
f3dc2399c2
commit
0f220f4af5
@ -159,7 +159,7 @@ describe('Content-only pages render without errors', () => {
|
||||
expect(screen.queryByText('/internal-only')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('EmptySession renders the launch composer', async () => {
|
||||
it('EmptySession renders mascot and composer', async () => {
|
||||
let container!: HTMLElement
|
||||
await act(async () => {
|
||||
container = render(<EmptySession />).container
|
||||
@ -167,8 +167,8 @@ describe('Content-only pages render without errors', () => {
|
||||
await Promise.resolve()
|
||||
})
|
||||
expect(container.querySelector('textarea')).toBeInTheDocument()
|
||||
expect(container.innerHTML).toContain('What should we build?')
|
||||
expect(container.innerHTML).toContain('Ask Claude anything')
|
||||
expect(container.innerHTML).toContain('New session')
|
||||
expect(container.innerHTML).toContain('Ask anything')
|
||||
})
|
||||
|
||||
it('EmptySession shows draft context usage before a session is created', async () => {
|
||||
@ -235,11 +235,11 @@ describe('Content-only pages render without errors', () => {
|
||||
})
|
||||
const { container } = render(<ActiveSession />)
|
||||
// With empty messages, the hero is shown
|
||||
expect(container.innerHTML).toContain('What should we build?')
|
||||
expect(container.innerHTML).toContain('New session')
|
||||
// ChatInput has a textarea
|
||||
const textarea = container.querySelector('textarea')
|
||||
expect(textarea).toBeInTheDocument()
|
||||
expect(textarea).toHaveAttribute('placeholder', 'Ask Claude anything. Type @ to mention files or tools')
|
||||
expect(textarea).toHaveAttribute('placeholder', 'Ask anything...')
|
||||
expect(textarea).toHaveAttribute('rows', '2')
|
||||
expect(container.innerHTML).not.toContain('Preview')
|
||||
// Cleanup
|
||||
|
||||
@ -260,7 +260,7 @@ export function RepositoryLaunchControls({
|
||||
|
||||
return (
|
||||
<div ref={rootRef} className="flex min-w-0 flex-col gap-2">
|
||||
<div className="flex min-h-[58px] min-w-0 flex-wrap items-center gap-x-3 gap-y-1 rounded-b-[28px] bg-[var(--color-surface-container)] px-5 py-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.65)]">
|
||||
<div className="flex min-h-[58px] min-w-0 flex-wrap items-center gap-x-3 gap-y-1 rounded-b-xl bg-[var(--color-surface-container)] px-5 py-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.65)]">
|
||||
<DirectoryPicker value={workDir} onChange={onWorkDirChange} variant="workbar" />
|
||||
|
||||
{loading && workDir && (
|
||||
|
||||
@ -679,9 +679,9 @@ export const en = {
|
||||
'settings.general.webSearchSave': 'Save',
|
||||
|
||||
// ─── Empty Session ──────────────────────────────────────
|
||||
'empty.title': 'What should we build?',
|
||||
'empty.title': 'New session',
|
||||
'empty.subtitle': 'Start a fresh coding session. Claude is ready to help you build, debug, and architect your project.',
|
||||
'empty.placeholder': 'Ask Claude anything. Type @ to mention files or tools',
|
||||
'empty.placeholder': 'Ask anything...',
|
||||
'empty.addFiles': 'Add files or photos',
|
||||
'empty.slashCommands': 'Slash commands',
|
||||
'empty.failedToCreate': 'Failed to create session',
|
||||
|
||||
@ -681,9 +681,9 @@ export const zh: Record<TranslationKey, string> = {
|
||||
'settings.general.webSearchSave': '保存',
|
||||
|
||||
// ─── Empty Session ──────────────────────────────────────
|
||||
'empty.title': '我们该构建什么?',
|
||||
'empty.title': '新建会话',
|
||||
'empty.subtitle': '开始一个新的编码会话。Claude 已准备好帮你构建、调试和架构你的项目。',
|
||||
'empty.placeholder': '向 Claude 询问任何事。输入 @ 使用文件或工具',
|
||||
'empty.placeholder': '随便问点什么...',
|
||||
'empty.addFiles': '添加文件或图片',
|
||||
'empty.slashCommands': '斜杠命令',
|
||||
'empty.failedToCreate': '创建会话失败',
|
||||
|
||||
@ -533,17 +533,25 @@ export function EmptySession() {
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-1 flex-col overflow-hidden bg-[var(--color-surface)]">
|
||||
<div className="flex min-h-full flex-1 items-center justify-center px-8 py-12">
|
||||
<div className="flex w-full max-w-[1120px] -translate-y-6 flex-col items-center gap-12">
|
||||
<h1 className="text-center text-[44px] font-semibold leading-tight text-[var(--color-text-primary)]" style={{ fontFamily: 'var(--font-headline)' }}>
|
||||
<div className="flex flex-1 flex-col items-center justify-center p-8 pb-32">
|
||||
<div className="flex max-w-md flex-col items-center text-center">
|
||||
<img src="/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)' }}>
|
||||
{t('empty.title')}
|
||||
</h1>
|
||||
<div className="flex w-full max-w-[1040px] flex-col">
|
||||
<div
|
||||
className="relative flex min-h-[194px] flex-col gap-4 rounded-t-[28px] border border-[var(--color-border)] bg-[var(--color-surface-container-lowest)] px-6 pb-4 pt-5 shadow-[0_22px_70px_rgba(15,23,42,0.10),0_5px_18px_rgba(15,23,42,0.06)]"
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<p className="mx-auto max-w-xs text-[var(--color-text-secondary)]" style={{ fontFamily: 'var(--font-body)' }}>
|
||||
{t('empty.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 left-0 right-0 flex justify-center px-8">
|
||||
<div className="flex w-full max-w-3xl flex-col">
|
||||
<div
|
||||
className="glass-panel relative flex flex-col gap-3 rounded-t-xl rounded-b-none p-4"
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
{fileSearchOpen && (
|
||||
<FileSearchMenu
|
||||
ref={fileSearchRef}
|
||||
@ -639,22 +647,22 @@ export function EmptySession() {
|
||||
onChange={(event) => handleInputChange(event.target.value, event.target.selectionStart ?? event.target.value.length)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
className="min-h-[92px] flex-1 resize-none border-none bg-transparent py-4 text-[22px] leading-relaxed text-[var(--color-text-primary)] outline-none placeholder:text-[var(--color-text-tertiary)]"
|
||||
className="flex-1 resize-none border-none bg-transparent py-2 leading-relaxed text-[var(--color-text-primary)] outline-none placeholder:text-[var(--color-text-tertiary)]"
|
||||
style={{ fontFamily: 'var(--font-body)' }}
|
||||
placeholder={t('empty.placeholder')}
|
||||
rows={3}
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between border-t border-[var(--color-border-separator)] pt-4">
|
||||
<div className="flex items-center justify-between border-t border-[var(--color-border-separator)] pt-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div ref={plusMenuRef} className="relative">
|
||||
<button
|
||||
onClick={() => setPlusMenuOpen((prev) => !prev)}
|
||||
aria-label="Open composer tools"
|
||||
className="rounded-full p-2 text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)]/35"
|
||||
className="rounded-lg p-1.5 text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-hover)]"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[28px]">add</span>
|
||||
<span className="material-symbols-outlined text-[18px]">add</span>
|
||||
</button>
|
||||
|
||||
{plusMenuOpen && (
|
||||
@ -693,29 +701,27 @@ export function EmptySession() {
|
||||
/>
|
||||
<ModelSelector runtimeKey={DRAFT_RUNTIME_SELECTION_KEY} disabled={isSubmitting} />
|
||||
<button
|
||||
aria-label={t('common.run')}
|
||||
onClick={handleSubmit}
|
||||
disabled={!canSubmit}
|
||||
className="flex h-14 w-14 items-center justify-center rounded-full bg-[var(--color-text-secondary)] text-[var(--color-surface-container-lowest)] shadow-[0_10px_24px_rgba(15,23,42,0.18)] transition-all hover:bg-[var(--color-text-primary)] disabled:bg-[var(--color-text-tertiary)] disabled:opacity-45"
|
||||
className="flex w-[112px] items-center justify-center gap-1 rounded-lg bg-[image:var(--gradient-btn-primary)] px-3 py-1.5 text-xs font-semibold text-[var(--color-btn-primary-fg)] shadow-[var(--shadow-button-primary)] transition-all hover:brightness-105 disabled:opacity-30"
|
||||
>
|
||||
<span className="sr-only">{t('common.run')}</span>
|
||||
<span className="material-symbols-outlined text-[28px]">arrow_upward</span>
|
||||
{t('common.run')}
|
||||
<span className="material-symbols-outlined text-[14px]">arrow_forward</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RepositoryLaunchControls
|
||||
workDir={workDir}
|
||||
onWorkDirChange={handleWorkDirChange}
|
||||
branch={selectedBranch}
|
||||
onBranchChange={setSelectedBranch}
|
||||
useWorktree={useWorktree}
|
||||
onUseWorktreeChange={setUseWorktree}
|
||||
onLaunchReadyChange={setRepositoryLaunchReady}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<RepositoryLaunchControls
|
||||
workDir={workDir}
|
||||
onWorkDirChange={handleWorkDirChange}
|
||||
branch={selectedBranch}
|
||||
onBranchChange={setSelectedBranch}
|
||||
useWorktree={useWorktree}
|
||||
onUseWorktreeChange={setUseWorktree}
|
||||
onLaunchReadyChange={setRepositoryLaunchReady}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user