Skip to content

fix(web-ui): keep the composer card synced when the input wraps - #1802

Merged
bobleer merged 1 commit into
GCWing:mainfrom
bobleer:bob/bitfun-input-height-issue-004b12
Jul 27, 2026
Merged

fix(web-ui): keep the composer card synced when the input wraps#1802
bobleer merged 1 commit into
GCWing:mainfrom
bobleer:bob/bitfun-input-height-issue-004b12

Conversation

@bobleer

@bobleer bobleer commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

At the exact point where the session composer wraps to a second line, the capsule → multi-line flip left the card and its controls out of sync: the text and the glm-5.2 / mic / send row rendered outside the rounded border, and the controls only caught up ~0.45s later.

Three causes, all on that one transition:

  1. The border box was clamped below its own content. .bitfun-chat-input__box transitions max-height from 44px (capsule) to 500px (multi-line) while its height stays auto. Mid-transition the painted border/background is stuck at the interpolated max-height while the children already occupy the full multi-line height, and overflow must stay visible for the mention/model popovers — so the content paints outside the card. Measured on the same rule set: the action row sat 55px below the painted box bottom at the start of the transition (23px at ~30% progress), versus 1px inside once unclamped.
  2. The action groups replayed a delayed reveal on every flip. __actions-left / __actions-right carried animation: bitfun-stacked-reveal … 0.17s both / … 0.22s both, and capsule mode nulled it out — so it never played on mount, it only restarted on each flip. both fill pins the from keyframe (opacity: 0), leaving the plus button and the model/mic/send cluster blank for ~0.2s and still sliding ~0.45s after the box had already resized. At the wrap boundary a single keystroke replays all of it.
  3. Layout thrash during the transition. The ResizeObserver watches the box, whose height animated every frame; each callback ran measureCapsuleInputWidth(), which clones the entire composer into the document and forces two layouts — roughly 20 clone+layout cycles per transition, exactly while the user is typing.

Fixes:

  • Dropped max-height from the box transition and kept min-height. min-height can never fall below the content, so expansion is instantly correct and collapse still animates.
  • Removed the bitfun-stacked-reveal animation from both action groups (plus the now-dead animation: none overrides and the unused keyframes) so the controls resize with the card.
  • The observer now ignores ResizeObserver entries whose width is unchanged, so the height animation no longer triggers the clone-based re-measure.

Type and Areas

Type: bug fix (UI/UX)

Areas: web UI (src/web-ui/src/flow_chat/components/ChatInput.{scss,tsx})

Motivation / Impact

Typing at the wrap boundary is a very common state, and every keystroke that crosses it replayed a visibly broken transition — the composer's own text and controls escaping the card. After the change the card and its controls always move together, and the transition stops forcing ~20 full-composer clone measurements while the user types.

Verification

pnpm run type-check:web                                  # pass
npx eslint src/flow_chat/components/ChatInput.tsx        # pass (exit 0)
npx vitest run src/flow_chat/components/chatInputRegistration.test.ts \
  src/flow_chat/components/ChatInputWorkspaceStripLayout.test.ts \
  src/flow_chat/components/richTextInputSync.test.ts \
  src/flow_chat/components/ChatInputWorkspaceStrip.test.tsx        # 4 files, 18 tests pass
node scripts/audit-theme-colors.mjs                      # no new findings (no colors touched)
sass src/flow_chat/components/ChatInput.scss             # compiles, exit 0

Behavioral check: reproduced the box's rule set in a browser and sampled getBoundingClientRect() for the card and the action row at intermediate clamp values.

expand, mid-transition collapse, mid-transition
before (max-height animated) action row +55px / +23px below the card border
after (min-height only) −1px (contained) at every intermediate value card shrinks 78 → 46px, content contained throughout (−19 / −11 / −3px)

No focused unit test was added: the defect lives entirely in CSS transition behavior, which the jsdom-based suite cannot observe (no layout).

Reviewer Notes

Trade-off worth a look: growth is no longer animated. A CSS height animation from small to large always clamps the box below its content, so smooth growth would require overflow: hidden during the transition — which would clip the mention and model-selector popovers. The border-radius, padding, box-shadow and border-color transitions still carry the shape morph, and collapse still animates via min-height.

Collapse timing is unchanged (min-height still 0.32s), so the collapse-protection windows in VirtualMessageList (1000ms / 1500ms intents) are unaffected. Expansion now grows the Virtuoso footer in one step instead of ~20 — growth does not trigger the scrollTop clamp that the shrink path guards against.

AI-assisted change. Testing level: lightly tested — checks above pass and the geometry was measured in a browser, but I have not run the full desktop app to capture before/after screenshots of the real composer. The reported behavior matches the measurements exactly.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable. (No strings or locales touched.)

At the exact point where the session input wraps to a second line, the
capsule -> multi-line flip left the card and its controls out of sync.

- The box transitioned `max-height` 44px -> 500px while its `height` stays
  `auto`, so mid-transition the painted border box was clamped below its own
  content. `overflow` must stay visible for the popovers, so the text and the
  whole model/mic/send row painted outside the rounded border. Measured on the
  same rule set: the action row sat 55px below the painted box bottom at the
  start of the transition. Only `min-height` is animated now - it can never
  fall below the content, so the collapse still animates and the card never
  clips.
- `__actions-left` / `__actions-right` re-ran `bitfun-stacked-reveal` on every
  flip (capsule mode nulled it out, so it never played on mount). With `both`
  fill the groups were pinned at `opacity: 0` through the 0.17s / 0.22s delay
  and finished sliding ~0.45s after the box had already resized.
- The ResizeObserver reacted to the animating box height, and each callback
  cloned the whole composer into the document to re-measure the capsule width
  (two forced layouts per frame of the transition). It now ignores entries
  whose width is unchanged.

Trade-off: growth is no longer animated. A CSS height animation from small to
large always clamps the box below its content; smooth growth would need
`overflow: hidden` during the transition, which would clip the mention and
model popovers. The radius, padding, shadow and border-color transitions still
carry the shape morph.
@bobleer
bobleer merged commit 5d951ea into GCWing:main Jul 27, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant