fix(desktop): keep composer contenteditable mounted across stacked toggle

The composer rendered {input} inside two different parent fragments
depending on `stacked`. When auto-expand flipped `stacked` (e.g. the
moment typed text wrapped past two lines), React reconciled the two
branches as different positions and unmounted/remounted the
contenteditable. The fresh mount started empty, so any in-flight
characters — most reliably reproduced by holding a key — were lost.

Replace the conditional with a single CSS Grid whose template-areas
swap on `stacked`. The three children (menu, input, controls) keep
stable identities across the toggle; only their grid placement
changes, which the browser handles without React tearing down the
editor.
This commit is contained in:
emozilla
2026-05-10 01:43:52 -04:00
parent eaab34e57e
commit 767736ff1e
+24 -15
View File
@@ -1006,21 +1006,30 @@ export function ChatBar({
<VoiceActivity state={voiceActivityState} /> <VoiceActivity state={voiceActivityState} />
<VoicePlaybackActivity /> <VoicePlaybackActivity />
{attachments.length > 0 && <AttachmentList attachments={attachments} onRemove={onRemoveAttachment} />} {attachments.length > 0 && <AttachmentList attachments={attachments} onRemove={onRemoveAttachment} />}
{stacked ? ( {/*
<> Single CSS Grid keeps {input} (and the contenteditable inside
{input} it) in a stable parent across the stacked/inline toggle.
<div className="flex w-full items-center gap-(--composer-control-gap)"> Earlier this was a JSX conditional that rendered {input}
{contextMenu} inside two different fragments — when `stacked` flipped (e.g.
{controls} the moment text wrapped past two lines and the auto-expand
</div> effect triggered), React reconciled them as different
</> positions and unmounted/remounted the contenteditable. The
) : ( fresh mount started empty and any in-flight characters were
<div className="flex w-full items-end gap-(--composer-control-gap)"> lost. Switching the layout via grid-template-areas keeps the
{contextMenu} exact same DOM nodes and lets the browser handle the reflow.
{input} */}
{controls} <div
</div> className={cn(
)} 'grid w-full',
stacked
? 'grid-cols-[auto_1fr] gap-(--composer-row-gap) [grid-template-areas:"input_input"_"menu_controls"]'
: 'grid-cols-[auto_1fr_auto] items-end gap-(--composer-control-gap) [grid-template-areas:"menu_input_controls"]'
)}
>
<div className="flex items-center [grid-area:menu]">{contextMenu}</div>
<div className="min-w-0 [grid-area:input]">{input}</div>
<div className="flex items-center justify-end [grid-area:controls]">{controls}</div>
</div>
</div> </div>
</div> </div>
</div> </div>