Summary
When drafting or pasting messages in the thread composer on iOS (both in the official @bb/mobile iOS app and in mobile Safari):
- Composer expansion & toolbar overflow: The composer expands beyond the visible screen area (
calc(50dvh - 3rem) in existing threads, and calc(70dvh - 3rem) in new chats / root-compose), pushing the bottom action bar (model selector, voice microphone button, paperclip, send button) beneath the on-screen keyboard.
- Caret trapping in text: Single-finger swipe inside the focused ProseMirror text area is captured by WebKit as cursor navigation rather than scrolling
data-promptbox-editor-scroll. Users cannot scroll down to place the cursor (caret |) at the end of their text.
- Voice dictation blocked: Because the editor doesn't scroll to the end and the bottom toolbar is pushed under the keyboard, users cannot place the cursor at the end of their draft to activate the microphone button for appended voice input.
- Timeline occlusion & no way to dismiss keyboard: The expanded composer (~400px–550px) + software keyboard (~340px) occupy the entire screen (~850px), reducing the visible chat history to an unusable ~0–20px sliver. Because
hideKeyboardAccessoryView hides the native iOS "Done" bar and BottomAnchoredScrollBody does not blur the input on timeline touch, users cannot dismiss the keyboard or scroll up to read previous assistant messages while composing.
Versions and environment
- Client: Official iOS App (
@bb/mobile, React Native WKWebView shell) and mobile Safari
- bb: 0.43.1
- Source reference on
main: c1a64f4b49b0659e92a7aa4434e79d062b3e814f
- Investigation thread:
thr_rj96urndqv
Steps to reproduce
Scenario A: In an existing chat
- Open a thread with conversation history on an iPhone (in the native iOS app or in Safari).
- Tap the thread composer to focus and bring up the software keyboard.
- Paste or type a multi-line message (15–20+ lines).
- Observe that the composer takes up the entire space between the top navigation bar and the keyboard, leaving 0px to view previous messages.
- Attempt to scroll down inside the composer to reach the bottom, tap at the end of the text, or tap the microphone button.
Scenario B: In a new chat ("New thread" / root-compose)
- Tap "New thread" on an iPhone.
- Type or paste a long prompt.
- Observe that with
calc(70dvh - 3rem), the composer card expands even further (~550px), pushing the microphone and submit buttons completely off-screen beneath the keyboard.
- Attempt to scroll down to place the cursor at the end to dictate via microphone.
Expected vs actual
Expected:
1. The composer and its action toolbar (including mic and submit buttons) remain fully visible above the software keyboard in both thread and new-chat layouts.
2. The user can scroll inside the promptbox to easily place the cursor anywhere (including the very end) and tap the microphone button.
3. Swiping the message timeline blurs the composer and dismisses the keyboard (keyboardDismissMode="on-drag"), freeing the entire screen to read past messages, or a "Done" / collapse button is available.
Actual:
1. In both existing chats (50dvh) and new chats (70dvh), the composer grows taller than the visible viewport above the keyboard; action buttons (microphone, model selector, send) are pushed beneath the keyboard.
2. Single-finger swipe inside the text area moves the text caret instead of scrolling the container.
3. The chat history is completely occluded (0–20px visible). There is no "Done" button on the keyboard, and scrolling the timeline does not dismiss the keyboard, trapping the user in the focused composer.
Root Cause Analysis
1. dvh does not shrink on iOS when the virtual keyboard is open
In apps/app/src/components/promptbox/ComposerEditorSlot.tsx:
const COMPOSER_EDITOR_MAX_HEIGHT_BY_LAYOUT: Record<ComposerEditorLayout, string> = {
thread: "calc(50dvh - 3rem)",
"root-compose": "calc(70dvh - 3rem)",
};
Per W3C CSS Values and Units Level 4 spec, dvh (Dynamic Viewport Height) is not affected by the virtual keyboard.
- In existing threads (
50dvh - 3rem), editor slot grows to ~377px.
- In new chats (
70dvh - 3rem), editor slot grows to ~547px!
- When the iOS virtual keyboard appears (~340px), visible viewport height drops to ~510px.
- Deducting top navigation header (~50px) and safe areas leaves only ~440px.
- In new chats, a 547px editor + 70px card toolbar = ~617px, which exceeds the available 440px viewport by ~177px, guaranteeing that the microphone button, attachments, and send button are pushed beneath the keyboard.
2. Timeline occlusion and keyboard trap in @bb/mobile
In apps/mobile/src/screens/webview/ProfileWebViewScreen.tsx:
<ShellWebView
contentInsetAdjustmentBehavior="never"
automaticallyAdjustContentInsets={false}
hideKeyboardAccessoryView // <--- Hides native iOS "Done" accessory bar!
...
/>
- Setting
hideKeyboardAccessoryView removes the standard iOS keyboard accessory toolbar where the user normally taps "Done" to dismiss the keyboard.
- In
apps/app/src/components/ui/bottom-anchored-scroll-body.tsx, touch events on the timeline (touchstart, touchmove) mark scroll intent but never call activeElement.blur().
- Consequently, while typing/composing, the keyboard stays up indefinitely, the composer occupies the entire visible area, and the chat history cannot be read.
3. Touch scroll trapping in contenteditable (ProseMirror)
When .ProseMirror is focused, WebKit captures vertical single-finger touch gestures for text caret navigation / text selection rather than delegating pan gestures to the overflow scroll container ([data-promptbox-editor-scroll]).
Proposed Solution / PR Blueprint
Fix 1: Dynamically clamp maxHeight by window.visualViewport.height on mobile
In apps/app/src/components/promptbox/ComposerEditorSlot.tsx:
function useVisualViewportHeight(): number | null {
const [height, setHeight] = useState<number | null>(() =>
typeof window !== "undefined" && window.visualViewport ? window.visualViewport.height : null
);
useEffect(() => {
const vv = window.visualViewport;
if (!vv) return;
const update = () => setHeight(vv.height);
vv.addEventListener("resize", update);
return () => vv.removeEventListener("resize", update);
}, []);
return height;
}
In ComposerEditorSlot:
const visualViewportHeight = useVisualViewportHeight();
// When virtual keyboard is open on small screens (< 650px visual viewport),
// clamp maxHeight to 35% of the visible viewport so composer + bottom action bar (mic, model, send) fit comfortably in both thread and root-compose layouts.
const dynamicMaxHeight = visualViewportHeight && visualViewportHeight < 650
? `${Math.max(120, Math.floor(visualViewportHeight * 0.35))}px`
: COMPOSER_EDITOR_MAX_HEIGHT_BY_LAYOUT[layout];
Apply to style:
style={{
minHeight: isCompactLayout ? "48px" : `${minHeight}px`,
height: isCompactLayout ? "48px" : undefined,
maxHeight: isCompactLayout ? "48px" : dynamicMaxHeight,
}}
Fix 2: Dismiss keyboard on timeline scroll (chat history reading)
In apps/app/src/components/ui/bottom-anchored-scroll-body.tsx:
When the user drags the timeline to read past messages, dismiss the keyboard so the timeline gets the full screen:
const markTouchMoveScrollIntent = useCallback(() => {
markUserScrollIntent();
// Dismiss software keyboard if the user starts dragging the chat history
if (
document.activeElement instanceof HTMLElement &&
document.activeElement.closest("[data-app-composer]")
) {
document.activeElement.blur();
}
}, [markUserScrollIntent]);
And in apps/mobile/src/screens/webview/ProfileWebViewScreen.tsx:
Add keyboardDismissMode="on-drag" to <WebView> so native scroll gestures also drop the keyboard.
Fix 3: Enable smooth touch scrolling on iOS & Auto-scroll on paste
In apps/app/src/components/promptbox/ComposerEditorSlot.tsx:
className={cn(
"w-full overflow-y-auto bg-transparent px-4 pb-1 pr-14 pt-3 outline-none",
"touch-pan-y [-webkit-overflow-scrolling:touch]",
COARSE_POINTER_TEXT_BASE_CLASS,
"leading-relaxed",
isCompactLayout && "h-12 overflow-hidden pb-0 pr-14 pt-0",
)}
Auto-scroll to bottom on paste:
useEffect(() => {
if (!editor) return;
const handlePaste = () => {
requestAnimationFrame(() => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight;
}
});
};
editor.view.dom.addEventListener("paste", handlePaste);
return () => editor.view.dom.removeEventListener("paste", handlePaste);
}, [editor, scrollContainerRef]);
What you ruled out
- Ruled out
bb-plugin-beautiful-chat and other UI plugins: verified against clean BB web CSS.
- Ruled out desktop browsers (macOS / Linux / Windows): reproducible exclusively on touch devices with virtual software keyboards (iOS Safari and
@bb/mobile).
Checks
AGENT GENERATED
Investigation thread: thr_rj96urndqv
Summary
When drafting or pasting messages in the thread composer on iOS (both in the official
@bb/mobileiOS app and in mobile Safari):calc(50dvh - 3rem)in existing threads, andcalc(70dvh - 3rem)in new chats / root-compose), pushing the bottom action bar (model selector, voice microphone button, paperclip, send button) beneath the on-screen keyboard.data-promptbox-editor-scroll. Users cannot scroll down to place the cursor (caret|) at the end of their text.hideKeyboardAccessoryViewhides the native iOS "Done" bar andBottomAnchoredScrollBodydoes not blur the input on timeline touch, users cannot dismiss the keyboard or scroll up to read previous assistant messages while composing.Versions and environment
@bb/mobile, React NativeWKWebViewshell) and mobile Safarimain:c1a64f4b49b0659e92a7aa4434e79d062b3e814fthr_rj96urndqvSteps to reproduce
Scenario A: In an existing chat
Scenario B: In a new chat ("New thread" / root-compose)
calc(70dvh - 3rem), the composer card expands even further (~550px), pushing the microphone and submit buttons completely off-screen beneath the keyboard.Expected vs actual
Root Cause Analysis
1.
dvhdoes not shrink on iOS when the virtual keyboard is openIn
apps/app/src/components/promptbox/ComposerEditorSlot.tsx:Per W3C CSS Values and Units Level 4 spec,
dvh(Dynamic Viewport Height) is not affected by the virtual keyboard.50dvh - 3rem), editor slot grows to ~377px.70dvh - 3rem), editor slot grows to ~547px!2. Timeline occlusion and keyboard trap in
@bb/mobileIn
apps/mobile/src/screens/webview/ProfileWebViewScreen.tsx:hideKeyboardAccessoryViewremoves the standard iOS keyboard accessory toolbar where the user normally taps "Done" to dismiss the keyboard.apps/app/src/components/ui/bottom-anchored-scroll-body.tsx, touch events on the timeline (touchstart,touchmove) mark scroll intent but never callactiveElement.blur().3. Touch scroll trapping in
contenteditable(ProseMirror)When
.ProseMirroris focused, WebKit captures vertical single-finger touch gestures for text caret navigation / text selection rather than delegating pan gestures to the overflow scroll container ([data-promptbox-editor-scroll]).Proposed Solution / PR Blueprint
Fix 1: Dynamically clamp
maxHeightbywindow.visualViewport.heighton mobileIn
apps/app/src/components/promptbox/ComposerEditorSlot.tsx:In
ComposerEditorSlot:Apply to
style:Fix 2: Dismiss keyboard on timeline scroll (chat history reading)
In
apps/app/src/components/ui/bottom-anchored-scroll-body.tsx:When the user drags the timeline to read past messages, dismiss the keyboard so the timeline gets the full screen:
And in
apps/mobile/src/screens/webview/ProfileWebViewScreen.tsx:Add
keyboardDismissMode="on-drag"to<WebView>so native scroll gestures also drop the keyboard.Fix 3: Enable smooth touch scrolling on iOS & Auto-scroll on paste
In
apps/app/src/components/promptbox/ComposerEditorSlot.tsx:Auto-scroll to bottom on paste:
What you ruled out
bb-plugin-beautiful-chatand other UI plugins: verified against clean BB web CSS.@bb/mobile).Checks
apps/app/src/components/promptbox/ComposerEditorSlot.tsx,apps/app/src/components/ui/bottom-anchored-scroll-body.tsx, andapps/mobile/src/screens/webview/ProfileWebViewScreen.tsxonmain.