Mobile: chat composer stays too big after layout shifts (stale JS-measured height)
Mobile: chat composer stays too big after layout shifts (stale JS-measured height)
Summary
On mobile, the chat composer (input box) sometimes renders much taller than its content and stays that way until the user types again. It is intermittent: opening a chat, opening the sidebar, or the keyboard appearing/disappearing can all leave the box oversized. Once the user presses a key, the box suddenly snaps back to the correct size — which makes the bug feel random.
The root cause is that the composer's height is computed in JavaScript from scrollHeight and written as an explicit inline pixel height. Any measurement taken while layout is in a transient state (sidebar opening animation, chat view still rendering, font swap, keyboard shift) freezes an inflated value into the inline style, and nothing re-measures until the next input event.
Reproduction
- Open Agent Zero on a phone (or a narrow browser window with touch emulation).
- Start the app (welcome page), then open the sidebar and switch to a chat.
- Observe the chat composer: it is often much taller than one line, with no content to justify it.
- Tap inside the composer (opens the keyboard) or type any character — the box snaps to the correct size.
Also reproducible by typing a long message (box grows), then clearing it on some devices — the box can remain large until the next input event.
Root cause
webui/components/chat/input/input-store.js, adjustTextareaHeight():
adjustTextareaHeight($event = null) {
const target = $event?.target || null;
for (const chatInput of this._composerTextareas(target)) {
chatInput.style.height = "auto";
chatInput.style.height = chatInput.scrollHeight + "px";
// pick up any layout shift triggered by the height assignment
chatInput.style.height = Math.max(chatInput.scrollHeight, parseInt(chatInput.style.height)) + "px";
}
},The Math.max line was added in commit a0fc9367 ("fix: chat-input tremor during typing") as an anti-jitter measure. Within a single pass it is close to a no-op (scrollHeight is always ≥ the height just assigned), but the deeper problem is the explicit px height itself:
- The composer (
#chat-inputinwebui/components/chat/input/chat-bar-input.html) is a contenteditable div, which auto-sizes with its content natively. - An explicit inline height measured during a transient layout state (welcome→sidebar animation, chat switch still rendering,
@font-faceswap, visual-viewport change from the keyboard) freezes the inflated value. - Height recalculation only runs on input events (and a few store actions), so the oversized box persists until the user types — matching the "sometimes big, then it resets when I press it" symptom.
- Additionally, the CSS cap
max-height: 30vhuses viewport units that do not shrink when the on-screen keyboard opens on mobile.
Proposed fix
Stop computing an explicit height; let the contenteditable element size itself and let CSS handle the bounds:
adjustTextareaHeight($event = null) {
const target = $event?.target || null;
for (const chatInput of this._composerTextareas(target)) {
// Clear any inline height: the contenteditable element auto-sizes
// with its content; CSS min-height and the max-height cap handle
// the rest. A measured px height can be taken during a transient
// layout state and then goes stale.
if (chatInput.style.height) chatInput.style.height = "";
}
},And in chat-bar-input.html, add a dynamic-viewport fallback:
max-height: 30vh;
max-height: 30dvh; /* respects the mobile keyboard viewport */This cannot reintroduce the tremor fixed in a0fc9367: that jitter came from field-sizing: content conflicting with JS-assigned heights. With no JS-assigned heights at all, there is nothing to conflict with.
Optional hardening (included in our local patch, not strictly required): a debounced visualViewport resize listener that re-runs adjustTextareaHeight() so the composer follows keyboard open/close.
Before / after
| Scenario | Before | After |
|---|---|---|
| First chat load / sidebar animation | Often renders oversized, stays until typing | Correct immediately (CSS min-height) |
| Keyboard opens / closes | Height can persist from the previous viewport | Cap follows dvh; listener re-checks |
| Long text | Grows, capped at 30vh (keyboard-blind) | Grows, capped at 30dvh, scrolls |
| Clearing text | Can stay large until next input | Shrinks immediately |
| Desktop typing | Unchanged behavior | Unchanged (natural sizing, same bounds) |
Environment
- Agent Zero Dockerized (current
master), accessed from Android Chrome (mobile WebUI) - Code affected present in current upstream
webui/components/chat/input/input-store.jsandchat-bar-input.html - Fix tested locally on mobile by the reporter across repeated cold starts, chat switches, and keyboard transitions
Happy to open a PR with the change if maintainers agree with the approach.
Source: agent0ai/agent-zero