fix: keep desktop composer toolbar out of multiline input

The active-session composer used an absolute toolbar overlay and textarea bottom padding to reserve space. When the input grew and scrolled, the caret could render inside that reserved overlay area, making it appear to pass through the controls.

Move the non-hero toolbar back into normal layout flow and remove the oversized textarea bottom padding so the text area and controls occupy separate boxes.

Constraint: Active-session and hero composer variants share the component, so keep the change scoped to the non-hero toolbar path.
Rejected: Increase textarea bottom padding | preserves the overlapping layout that caused the caret artifact.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce an absolutely positioned toolbar over the active-session textarea without validating multiline caret rendering.
Tested: cd desktop && bun run test src/components/chat/ChatInput.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: Browser DOM check confirmed textarea and toolbar no longer overlap.
This commit is contained in:
程序员阿江(Relakkes) 2026-05-19 22:26:18 +08:00
parent 11ce6a2064
commit abd782494e
2 changed files with 20 additions and 4 deletions

View File

@ -841,6 +841,22 @@ describe('ChatInput file mentions', () => {
expect(fileSearchMenu).not.toHaveTextContent('Navigate')
})
it('keeps the active-session toolbar in flow so multiline caret cannot render behind controls', async () => {
render(<ChatInput />)
await waitFor(() => {
expect(mocks.getGitInfo).toHaveBeenCalledWith(sessionId)
})
const input = screen.getByRole('textbox')
const toolbar = screen.getByTestId('chat-input-toolbar')
expect(toolbar).not.toHaveClass('absolute')
expect(toolbar).toHaveClass('mt-2')
expect(input).not.toHaveClass('pb-12')
expect(input).not.toHaveClass('pb-14')
})
it('prioritizes active-session slash commands by command name when filtering', async () => {
useChatStore.setState({
sessions: {

View File

@ -983,15 +983,15 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
disabled={isWorkspaceMissing}
rows={1}
className={`w-full resize-none bg-transparent text-sm leading-relaxed text-[var(--color-text-primary)] outline-none placeholder:text-[var(--color-text-tertiary)] disabled:opacity-50 ${
useCompactControls ? 'py-1.5 pb-14' : 'py-2 pb-12'
useCompactControls ? 'py-1.5' : 'py-2'
}`}
/>
)}
<div className={isHeroComposer
<div data-testid="chat-input-toolbar" className={isHeroComposer
? 'flex items-center justify-between border-t border-[var(--color-border-separator)] pt-3'
: `absolute bottom-0 left-0 right-0 flex items-center justify-between border-t border-[var(--color-border-separator)] ${
useCompactControls ? 'gap-2 px-2.5 py-2' : 'px-3 py-3'
: `mt-2 flex items-center justify-between border-t border-[var(--color-border-separator)] ${
useCompactControls ? '-mx-3 -mb-3 gap-2 px-2.5 py-2' : '-mx-4 -mb-4 px-3 py-3'
}`}>
<div className="flex min-w-0 items-center gap-2">
{!isMemberSession && (