v6.8.2: the keyboard no longer pushes the chat off the screen on iOS
Reported from an iPhone, with a screenshot: tap the message box, the keyboard comes up, and the whole app has slid off the top — header gone, only the bottom edge of the composer left, black underneath it down to the keyboard. That is the visual viewport being panned. WebKit shrinks it for the keyboard and then moves it down inside the layout viewport (which it does not shrink) to reveal the focused field. The shell had shrunk correctly, to the top --sb-vh of the layout viewport; the window had simply moved off it. No CSS declines that pan, and it is not a document scroll, so there is no scrollTop to put back. So the shell is pinned to the visible rect: fixed to the layout viewport and translated by visualViewport.offsetTop. The earlier objection to pinning is kept where it was right — HEIGHT still moves on resize only, which is what stopped the layout twitching under the finger, and the offset rides a transform rather than `top`. The first cut of this had a guard that silently disabled it: the pan was only followed when window.innerHeight was much taller than the visual viewport, i.e. "a keyboard is covering us". That assumes iOS never resizes the layout viewport, which stopped being safe once WebKit started honouring interactive-widget. Only a pinch-zoomed page switches the pan off now, by scale, and offsetTop is believed whenever it is non-zero. The offset is also re-read across the keyboard's animation on focusin/focusout: on iOS 26 the viewport is documented not to settle back immediately on dismissal. The message list stays at the newest message across a keyboard open or close, instead of holding a scroll position from the taller layout and leaving the last few messages under the composer. Two smaller things on the same screen: The QR code closes itself once the safety number appears. It was only dismissed when both exchange flags cleared, and those stay set through verification, so a one-time credential sat on top of the code the user was meant to be reading. The disconnect button's label is hidden wherever the button becomes a 40px square. Its own rule stopped at 560px while the square starts at 768px, so between the two the word was still inside the box, pushing the icon off centre. ?vvdebug=1 paints the viewport numbers over the app — visible rect height, offset, scale, what the shell resolved to. A phone cannot be put on a debugger and none of those numbers show up in a screenshot of the result; this one is how the guard above was caught. Off unless named in the URL. Claude-Session: https://claude.ai/code/session_01R63J7dexujFQCJpDAeKxr9
This commit is contained in:
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/uk/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1789181644117">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1789190079159">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1789181644117"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1789190079159"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1789181644117"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1789190079159"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1789181644117">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1789190079159">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1789181644117">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1789190079159">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1789181644117"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1789190079159"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1789181644117">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1789181644117">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1789190079159">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1789190079159">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1789181644117"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1789190079159"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1789181644117"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1789190079159"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/uk.js?v=1789181644117"></script>
|
||||
<script type="module" src="/src/i18n/dict/uk.js?v=1789190079159"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1789181644117"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1789181644117"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1789190079159"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1789190079159"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1789181644117"></script>
|
||||
<script type="module" src="/dist/app.js?v=1789181644117"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1789190079159"></script>
|
||||
<script type="module" src="/dist/app.js?v=1789190079159"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1789181644117"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1789181644117" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1789181644117" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1789181644117"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1789190079159"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1789190079159" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1789190079159" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1789190079159"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user