diff --git a/artifacts/tx-os/src/components/ui/dialog.tsx b/artifacts/tx-os/src/components/ui/dialog.tsx index fc0ee2b2..105a9a33 100644 --- a/artifacts/tx-os/src/components/ui/dialog.tsx +++ b/artifacts/tx-os/src/components/ui/dialog.tsx @@ -32,11 +32,19 @@ const DialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, style, ...props }, ref) => { - const { keyboardInset, height } = useVisualViewport() + const { keyboardInset, height, offsetTop } = useVisualViewport() + // Only react when an on-screen keyboard is actually present. iOS Safari + // and Android Chrome both report a non-trivial bottom inset (>= ~150px) + // when their software keyboard opens; tiny insets from browser chrome + // changes are ignored so desktop and normal mobile scrolling stay + // untouched. const keyboardOpen = keyboardInset > 80 const dynamicStyle: React.CSSProperties = keyboardOpen ? { - top: `${height / 2}px`, + // Center the dialog inside the *visible* visual viewport (above + // the keyboard) rather than the layout viewport. offsetTop + // accounts for the page being scrolled or pinch-zoomed. + top: `${offsetTop + height / 2}px`, maxHeight: `${height - 16}px`, ...style, } diff --git a/artifacts/tx-os/src/pages/notes.tsx b/artifacts/tx-os/src/pages/notes.tsx index f329500f..a28b2269 100644 --- a/artifacts/tx-os/src/pages/notes.tsx +++ b/artifacts/tx-os/src/pages/notes.tsx @@ -2410,16 +2410,6 @@ function ThreadDialog({ ref={replyInputRef} value={replyText} onChange={(e) => setReplyText(e.target.value)} - onFocus={(e) => { - const el = e.currentTarget; - setTimeout(() => { - try { - el.scrollIntoView({ block: "center", behavior: "smooth" }); - } catch { - el.scrollIntoView(); - } - }, 250); - }} placeholder={t("notes.replyPlaceholder", "Write a reply…")} className="min-h-[70px] resize-none" data-testid="thread-reply-input" @@ -3024,16 +3014,6 @@ function Composer({