From 544c599a5ae7666ff498bad7588fb85f222f3ad7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 08:52:33 +0000 Subject: [PATCH] Keep the composer above the on-screen keyboard on the first tap Both composers are position:sticky;bottom:0, which pins them to the bottom of the *layout* viewport. A phone keyboard does not shrink that viewport -- it only covers it -- so the first tap on the input left the box sitting behind the keyboard. Tapping back and focusing again appeared to work only because the page had been scrolled during the first attempt, so the browser's scroll-into-view landed somewhere else. Two engines, two halves: - Chromium honours interactive-widget=resizes-content on the viewport meta, which makes the layout viewport shrink when the keyboard opens, so 100dvh and sticky bottoms account for it on their own. - iOS Safari ignores that flag and only shrinks the visual viewport, so measure the covered height from visualViewport and publish it as --kb-inset; the composers offset their sticky bottom by it. On browsers that already resized the layout viewport the measurement is ~0, so the same rule is a no-op there instead of a double lift. The pages also gain a matching bottom margin so the last beat can still be scrolled clear of a lifted composer. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011BC7Dsg4gSQm3NLcZcG5SK --- frontend/index.html | 9 ++++++++- frontend/src/index.css | 24 ++++++++++++++++++++---- frontend/src/keyboard.js | 32 ++++++++++++++++++++++++++++++++ frontend/src/main.jsx | 3 +++ 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 frontend/src/keyboard.js diff --git a/frontend/index.html b/frontend/index.html index e004457..0134879 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,14 @@ - + + { + // Pinch-zoom shrinks the visual viewport too, and that is not a keyboard. + const covered = vv.scale > 1.01 ? 0 : window.innerHeight - vv.height - vv.offsetTop + const inset = covered > MIN_KEYBOARD ? Math.round(covered) : 0 + document.documentElement.style.setProperty('--kb-inset', `${inset}px`) + } + // resize fires as the keyboard animates in; scroll fires as the page pans + // under a keyboard that is already up, which moves offsetTop. + vv.addEventListener('resize', apply) + vv.addEventListener('scroll', apply) + apply() +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index ecba514..9350bb0 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -11,8 +11,11 @@ import Scripts from './pages/Scripts.jsx' import ScriptEditor from './pages/ScriptEditor.jsx' import Settings from './pages/Settings.jsx' import Chat from './pages/Chat.jsx' +import { trackKeyboardInset } from './keyboard.js' import './index.css' +trackKeyboardInset() + const router = createBrowserRouter([ { path: '/',