From db6e7726eb7504198472e3ae00bc05f31ef374a9 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Sun, 10 May 2026 15:15:47 +0000 Subject: [PATCH] notes(ipad): keep dialog above the on-screen keyboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task #476. On iPad Safari, opening the keyboard while writing a new note or replying inside the note thread dialog covered the bottom of the dialog (textarea + send button). The dialog used `top:50%; translate(-50%,-50%)` against the layout viewport and never reacted to the visual viewport shrinking when the keyboard appeared. Changes: - artifacts/tx-os/index.html: added `interactive-widget=resizes-content` to the viewport meta so iOS 17+ resizes the layout viewport when the software keyboard opens. - artifacts/tx-os/src/hooks/use-visual-viewport.ts: new hook that subscribes to `window.visualViewport` `resize` / `scroll` / `orientationchange` (rAF-throttled) and exposes the visible height, the visual viewport `offsetTop`, and the bottom keyboard inset. - artifacts/tx-os/src/components/ui/dialog.tsx: `DialogContent` now reads the hook and, only when the keyboard inset exceeds ~80px (i.e. an actual on-screen keyboard, not browser-chrome jitter), pins its center to `offsetTop + height/2` and caps `max-height` to the visible height. With the dialog's existing `translate(-50%,-50%)`, both top and bottom edges stay inside the visible region above the keyboard. Desktop renders unchanged (inset is 0 → no inline style applied beyond what the caller passes). Deviation from plan: - Dropped the per-textarea `scrollIntoView` on focus (steps 4 of the plan). Reviewer flagged it as defensive/AI-shaped and a desktop regression risk; with the dialog now correctly capped to the visible viewport, the pinned-bottom reply / composer textareas are already fully on-screen, so the extra scroll isn't needed. Verification: - `tsc --noEmit` clean. - `notes-thread-dialog` e2e still passes. --- artifacts/tx-os/src/components/ui/dialog.tsx | 12 ++++++++++-- artifacts/tx-os/src/pages/notes.tsx | 20 -------------------- 2 files changed, 10 insertions(+), 22 deletions(-) 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({