fix(mobile): disable zoom via viewport meta (not font-size) + reveal msgs during slide

- Revert font-size:16px !important (it made the composer ~4 lines tall). Disable iOS zoom with
  viewport maximum-scale=1,user-scalable=no instead -> composer keeps its size, AND double-tap-to-zoom
  (in-chat search arrows #3) is gone. touch-action:manipulation on controls as belt-and-suspenders.
  Custom lightbox pinch still works (it's transform-based, not browser zoom).
- Transition: show the newest message immediately (was hidden until images loaded -> empty pane slid in
  then popped = the 'jumping' feel). Pinning still prevents dance.
Build marker -> 2026-07-18-batch114.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 17:27:54 +05:30
parent 78ea36f7d0
commit 676612a12b
+12 -15
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>Biz Connect</title> <title>Biz Connect</title>
<!-- PWA: installable on Android & iOS ("Add to Home Screen"); also enables iOS web push --> <!-- PWA: installable on Android & iOS ("Add to Home Screen"); also enables iOS web push -->
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
@@ -1028,7 +1028,10 @@
That zoom scales & offsets the visual viewport (vvW 428→394, vvTop→74 in the device report), which That zoom scales & offsets the visual viewport (vvW 428→394, vvTop→74 in the device report), which
scrambled every keyboard/safe-area calculation. Forcing 16px on all form fields stops the zoom, so scrambled every keyboard/safe-area calculation. Forcing 16px on all form fields stops the zoom, so
the keyboard behaves predictably and the composer sits correctly. This is THE keyboard root cause. */ the keyboard behaves predictably and the composer sits correctly. This is THE keyboard root cause. */
input,textarea,select{font-size:16px !important;} /* !important: beats .composer-row textarea{.92rem} etc. — any <16px focus re-triggers the iOS zoom */ /* iOS zoom is now disabled via the viewport meta (maximum-scale=1), so inputs can keep their real
size — no font-size override needed. touch-action:manipulation also removes the double-tap-to-zoom
gesture on controls (e.g. the in-chat search up/down arrows). */
button,a,.iconbtn,.ic-btn,.railbtn,.chat-row,[role=button]{touch-action:manipulation;}
/* #3: kill any horizontal drift on phones (long content, over-wide children, safe-area math) — the /* #3: kill any horizontal drift on phones (long content, over-wide children, safe-area math) — the
page must never pan left/right. Clip every scroll surface and let long words/URLs wrap. */ page must never pan left/right. Clip every scroll surface and let long words/URLs wrap. */
html,body{overflow-x:hidden;} html,body{overflow-x:hidden;}
@@ -1120,7 +1123,7 @@
</head> </head>
<body> <body>
<script src="/icons.js?v=6"></script> <script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-18-batch113';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD); <script>window.__BUILD='2026-07-18-batch114';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network. // Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
// //
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from // We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
@@ -2688,18 +2691,12 @@ function renderThread(keepScroll){
// the newest message, no hide/reveal, so there's no second flash/"dance". // the newest message, no hide/reveal, so there's no second flash/"dance".
box.scrollTop=box.scrollHeight; requestAnimationFrame(()=>{ const b=document.getElementById('msgs'); if(b) b.scrollTop=b.scrollHeight; }); box.scrollTop=box.scrollHeight; requestAnimationFrame(()=>{ const b=document.getElementById('msgs'); if(b) b.scrollTop=b.scrollHeight; });
} else { } else {
// #7: FIRST render of this open — keep the thread hidden until its images have loaded (or a short // #7: FIRST render — show the newest message IMMEDIATELY (visible), so the slide-in transition
// timeout), then jump to the newest message and reveal, so it never visibly reflows/"dances". // reveals real content rather than an empty pane that then pops (that was the "jumping" feel). The
box.style.visibility='hidden'; box.scrollTop=box.scrollHeight; // pin schedule + image-load pins in openConvo keep it glued to the bottom as late images load, so it
const pending=[...box.querySelectorAll('img')].filter(im=>!im.complete); // still doesn't dance.
const reveal=()=>{ if(_convoRevealed) return; _convoRevealed=true; const b=document.getElementById('msgs'); if(!b) return; if(!b.querySelector('.new-sep')) b.scrollTop=b.scrollHeight; b.style.visibility='visible'; }; _convoRevealed=true; box.style.visibility='visible'; box.scrollTop=box.scrollHeight;
if(!pending.length){ requestAnimationFrame(reveal); } requestAnimationFrame(()=>{ const b=document.getElementById('msgs'); if(b && !b.querySelector('.new-sep')) b.scrollTop=b.scrollHeight; });
else {
let left=pending.length;
const one=()=>{ if(--left<=0) reveal(); };
pending.forEach(im=>{ const h=()=>{ im.removeEventListener('load',h); im.removeEventListener('error',h); one(); }; im.addEventListener('load',h); im.addEventListener('error',h); });
setTimeout(reveal, 700); // never wait longer than this for slow images
}
} }
} }
if(_selMode) box.querySelectorAll('.bubble').forEach(b=>{ if(_selIds.has(b.dataset.id)) b.classList.add('selected'); }); // #1: keep selection across re-render if(_selMode) box.querySelectorAll('.bubble').forEach(b=>{ if(_selIds.has(b.dataset.id)) b.classList.add('selected'); }); // #1: keep selection across re-render