feat(coach): re-pin the message-thread scroll when the soft keyboard opens

Subscribe to window.visualViewport.resize so the last bubble stays
visible when the phone's soft keyboard slides in. Without the listener
the scroller's scrollHeight reflects the layout viewport rather than
the visible region, leaving the tail of the thread hidden behind the
keyboard. The handler only re-pins when the user was already at the
bottom, matching the existing auto-scroll contract.
This commit is contained in:
Marc-André Bombeck committed 2026-05-15 19:07:42 +02:00
1 parent 40916d3127
commit 650d4f8e45
1 file changed
+22
@@ -198,6 +198,28 @@ export function MessageThread({
}
}, [messages.length, streaming?.content, optimisticUser?.localId]);
// v1.4.27 R3d MB4 / CF-74 — re-pin to the bottom when the
// visual viewport shrinks (typically the soft keyboard opening on
// a phone). Without this the last bubble drifts behind the keyboard
// because the scroller's `scrollHeight` references the layout
// viewport, not the visible region. Listening on
// `window.visualViewport.resize` and re-issuing the scroll keeps the
// tail of the thread visible as the keyboard slides in and out.
useEffect(() => {
if (typeof window === "undefined") return;
const vv = window.visualViewport;
if (!vv) return;
const handleResize = () => {
const el = scrollerRef.current;
if (!el) return;
if (wasPinnedRef.current) {
el.scrollTo({ top: el.scrollHeight, behavior: "auto" });
}
};
vv.addEventListener("resize", handleResize);
return () => vv.removeEventListener("resize", handleResize);
}, []);
if (messages.length === 0 && !streamingActive && !optimisticActive) {
return (
<div