-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Summary (Raw)
When the user types inside the AI & Search input while it already contains multiple lines of text, editing in the middle of the content causes the scroll position to jump to the bottom of the input. In this specific case, the caret should stay where the user is typing and the visible text block should remain anchored around the caret, not auto-scroll to the bottom.
This affects both:
- The native bar:
GlobalBottomBar(React NativeTextInputwrapped in aScrollView) - The web-only bar:
GlobalBottomBarWeb(HTML<textarea>with custom scroll logic)
Where it’s implemented
-
Native bottom bar
- File:
app/app/components/GlobalBottomBar.tsx - Relevant area: dynamic height + scroll logic around the input:
- Height/lines calculation and
dynamicHeight/barHeight(rawLines,visibleLines,dynamicHeight,barHeight). - Scroll syncing and snapping:
onScrollViewContentSizeChange→scrollRef.current.scrollToEnd({ animated: false });- Effect that scrolls on first 7th line appearance:
if (rawLines === 7 && dynamicHeight >= MAX_BAR_HEIGHT && scrollY === 0) { scrollRef.current?.scrollTo({ y: INNER_PADDING, animated: false }); }
- Custom scrollbar math using
scrollY,scrollRange,contentHeightWithGaps.
- Height/lines calculation and
- File:
-
Web-only bottom bar
- File:
app/app/components/GlobalBottomBarWeb.tsx - Relevant area: DOM-based mirror + textarea scroll logic:
domMirrorHeight,contentHeight,dynamicHeight,barHeight.measureAndResizeandhandleInput(usingrequestAnimationFrame).- Effects that:
- Snap the textarea to bottom when in scroll mode (8+ lines).
- Shift content when the 7th line first appears.
- Custom scrollbar math using
scrollY,domScrollRange,contentHeightWithGaps.
- File:
These two implementations are meant to stay in sync (native vs web reference implementation), so any fix should likely touch both files.
Steps to reproduce
- Open the app (either in TMA or local web) and focus the AI & Search bottom bar.
- Type/paste a long multi-line prompt (e.g. 8–10 lines) so the internal scroll mode is active.
- Scroll the input slightly and place the caret in the middle of the text (not at the very bottom).
- Start typing or deleting characters in the middle.
Actual behavior
- As soon as you type in the middle of the text, the internal scroll position snaps to the bottom (or very close to it).
- The caret effectively jumps down with the content, so the user loses context and can no longer see the lines they were editing.
Expected behavior
- When the caret is positioned in the middle of the text, typing or deleting:
- Keeps the visible content anchored around the caret.
- Does not auto-scroll the internal
ScrollView/<textarea>to the bottom unless the caret is at (or close to) the last line. - Custom scrollbar position should reflect the new scroll state without forcing a jump.
In short: typing in the middle should not force a bottom snap; the user should remain at their current edit position.
Likely root causes
Native (GlobalBottomBar.tsx):
- The combination of:
onScrollViewContentSizeChangecallingscrollToEndwheneverh > viewportHeight.- The 7th-line alignment effect that scrolls by
INNER_PADDINGwhenrawLines === 7andscrollY === 0.
- These behaviors assume “user is editing near the bottom and we want to keep the last line visible”, but they don’t distinguish between:
- The user editing at the bottom (good to snap).
- The user editing in the middle (snap is wrong).
Web (GlobalBottomBarWeb.tsx):
- Similar logic:
- Scroll-to-bottom effect in the “scroll mode” effect where it sets
el.scrollTop = rangewhenisScrollModebecomes true. - 7th-line alignment effect that nudges the content by
INNER_PADDING.
- Scroll-to-bottom effect in the “scroll mode” effect where it sets
- These always push the textarea to the bottom of content, regardless of caret position.
Because the two implementations intentionally mirror each other, the bug is visible on both platforms.
Possible solutions
High level idea: only auto-scroll when the caret is near the bottom, not when the user is editing in the middle.
Option A – Track caret position and gate bottom snap
Native (GlobalBottomBar.tsx):
- Track caret position via
TextInputevents:- Use
onSelectionChangeto captureselection.start/selection.end. - Derive whether the caret is on the last visible line (or within N characters of the end).
- Use
- Update the scroll behavior:
- In
onScrollViewContentSizeChange, only callscrollToEndif:caretIsNearEnd === true, andh > viewportHeight.
- In the 7th-line effect, only auto-shift when
caretIsNearEndis true.
- In
Web (GlobalBottomBarWeb.tsx):
- Track caret via DOM APIs:
- On
<textarea>: useselectionStart/selectionEndin anonSelectoronInputhandler. - Determine if caret is near the end of the text.
- On
- Gate the “scroll to bottom” logic similarly:
- Only set
el.scrollTop = rangewhencaretIsNearEndis true.
- Only set
Pros:
- Behavior matches user intent: when they type at the bottom, it stays pinned; when they edit in the middle, it stays there.
Cons:
- Slightly more complex state (track caret position, maybe debounce selection updates).
Option B – Hybrid: threshold-based auto-scroll
Instead of checking exact caret position, use a scroll position threshold:
- If we are already near the bottom (e.g. last N pixels of
scrollRange), allow auto scroll-to-end. - If we are scrolled somewhere in the middle, do not touch
scrollTop.
This avoids tracking caret, but still respects “user scrolled up to read context”.
Notes
- Whatever fix is chosen, it should be applied to both:
GlobalBottomBar.tsx(React Native / TMA)GlobalBottomBarWeb.tsx(web-only reference)
- The behavior should be validated specifically in Telegram Mini App on mobile, where the keyboard + viewport resizing can interact with scroll logic in subtle ways.