Skip to content

Commit eb57601

Browse files
authored
fix(chat): resolve composer cursor dead-space and add multi-line scroll (#670)
* fix(chat): resolve composer cursor dead-space and add multi-line scroll The chatbox input had a clickable dead zone: minHeight was applied to the outer wrapper (52px) while the ProseMirror editable element inside was content-height, so clicks in the lower area hit the wrapper instead of the editor — no caret appeared until the cursor moved into the top content region. The inner scroll div used h-full which doesn't resolve against a min/maxHeight-only parent, so scrolling never engaged. - Move min-height from the outer wrapper to the ProseMirror element via a --composer-min-h CSS variable (index.css), making the full area editable and clickable. - ChatInputBar: start at 1 line (26px), cap at 3 lines (78px), then scroll. - Add overscroll-contain to the scroll area to prevent scroll chaining. - Remove redundant outer maxHeight (inner div already constrains). - Merge adjacent @layer components blocks in index.css. * style: split inline style prop to satisfy Biome 100-char width * chore: trigger CI re-run * chore: retrigger CI
1 parent 8eea076 commit eb57601

3 files changed

Lines changed: 32 additions & 12 deletions

File tree

src/renderer/components/chat/ChatInputBar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,8 @@ export function ChatInputBar({
653653
editorRef={editorRef}
654654
inputRef={composerInputRef}
655655
disabled={disabled || sending}
656-
minHeight={52}
657-
maxHeight={160}
656+
minHeight={26} /* 1 line: 16px × 1.625 */
657+
maxHeight={78} /* 3 lines: 3 × 26px, then scroll */
658658
placeholder={
659659
disabled
660660
? 'Composer unavailable'

src/renderer/components/chat/composer/ChatComposerEditor.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ export interface ChatComposerEditorProps {
6262
placeholder?: string
6363
ariaLabel?: string
6464
autoFocus?: boolean
65-
/** Min/max heights (CSS px). The editor grows with content up to `maxHeight`,
66-
* then scrolls — replaces the old textarea `clampHeight`/`resetHeight`. */
65+
/** Min height (CSS px) applied to the editable surface so the full area is
66+
* clickable — the ProseMirror element itself stretches to this height, so
67+
* clicks anywhere inside place the caret (no dead space below short text). */
6768
minHeight?: number
69+
/** Max height (CSS px). The editor grows with content up to this height,
70+
* then the scroll area engages. */
6871
maxHeight?: number
6972
className?: string
7073
editorClassName?: string
@@ -201,7 +204,10 @@ export function ChatComposerEditor({
201204
placeholder,
202205
ariaLabel,
203206
autoFocus = false,
204-
minHeight = 52,
207+
// 26px = 1 line: text-base (16px) × leading-relaxed (1.625). Applied to the
208+
// ProseMirror element via the `--composer-min-h` CSS var (index.css) so the
209+
// full area is clickable — no dead space below short/empty text.
210+
minHeight = 26,
205211
maxHeight = 160,
206212
className,
207213
editorClassName
@@ -456,11 +462,16 @@ export function ChatComposerEditor({
456462
}, [editor, disabled])
457463

458464
return (
459-
<div
460-
className={cn('relative w-full overflow-hidden', disabled && 'opacity-60', className)}
461-
style={{ minHeight: `${minHeight}px`, maxHeight: `${maxHeight}px` }}
462-
>
463-
<div className="h-full w-full overflow-y-auto">
465+
<div className={cn('relative w-full overflow-hidden', disabled && 'opacity-60', className)}>
466+
<div
467+
className="w-full overflow-y-auto overscroll-contain"
468+
style={
469+
{
470+
maxHeight: `${maxHeight}px`,
471+
'--composer-min-h': `${minHeight}px`
472+
} as React.CSSProperties
473+
}
474+
>
464475
<EditorContent editor={editor} innerRef={editorContentRef} />
465476
</div>
466477
</div>

src/renderer/index.css

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,13 +473,22 @@
473473
}
474474
}
475475

476-
/* Chat composer (Tiptap) empty-state placeholder.
476+
/* Chat composer (Tiptap) editable surface + empty-state placeholder.
477477
`@tiptap/extension-placeholder` sets `data-placeholder` + the `is-empty`
478478
class on the empty `<p>` node, but ships no CSS — the app must render the
479479
hint via `::before { content: attr(data-placeholder) }`. Applies to both
480480
the agent launcher and the existing-chat ChatInputBar (both share
481-
ChatComposerEditor). */
481+
ChatComposerEditor).
482+
483+
The min-height is applied to the ProseMirror element itself (not a wrapper)
484+
via the `--composer-min-h` CSS variable set inline by ChatComposerEditor.
485+
This makes the full area clickable — the caret appears wherever the user
486+
clicks, eliminating the dead space below short/empty text that occurred
487+
when min-height was on the outer wrapper. */
482488
@layer components {
489+
[data-composer-editor="true"] {
490+
min-height: var(--composer-min-h, auto);
491+
}
483492
[data-composer-editor="true"] p.is-empty:first-child::before {
484493
content: attr(data-placeholder);
485494
color: hsl(var(--muted-foreground));

0 commit comments

Comments
 (0)