#1873·agent-zero

Mobile: chat composer stays too big after layout shifts (stale JS-measured height)

Author: nico7masterCreated Sep 4, 2026Updated Sep 4, 2026

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

  1. Open Agent Zero on a phone (or a narrow browser window with touch emulation).
  2. Start the app (welcome page), then open the sidebar and switch to a chat.
  3. Observe the chat composer: it is often much taller than one line, with no content to justify it.
  4. 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():

javascript
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-input in webui/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-face swap, 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: 30vh uses 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:

javascript
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:

css
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.js and chat-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.