fix(mobile): stop iOS input auto-zoom (font-size 16px) = real keyboard fix; robust pin-to-newest

Device log revealed the truth:
- Keyboard: focusing the <16px composer made iOS ZOOM the page (vvW 428->394, vvTop->74), scrambling
  every viewport/keyboard calc. Set all form fields to 16px on mobile -> no zoom -> keyboard math holds.
- Latest messages: on a 500-msg chat atBottom was false because late images grow the thread after the
  initial scroll. Now re-pin on a schedule + on each image load while near the bottom.
- Budge already gone (device reported ovf:false).
Build marker -> 2026-07-18-batch109.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 14:17:20 +05:30
parent f9d6472f77
commit 0e38bd9229
+13 -3
View File
@@ -1024,6 +1024,11 @@
relocated into the chat-list header (see placeHdrRight). The shell is pushed below the
device status bar so no view overlaps it. */
header{display:none;}
/* CRITICAL (keyboard): iOS auto-ZOOMS into any focused <input>/<textarea> whose font-size is < 16px.
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
the keyboard behaves predictably and the composer sits correctly. This is THE keyboard root cause. */
input,textarea,select{font-size:16px;}
/* #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. */
html,body{overflow-x:hidden;}
@@ -1112,7 +1117,7 @@
</head>
<body>
<script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-18-batch108';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
<script>window.__BUILD='2026-07-18-batch109';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.
//
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
@@ -2850,8 +2855,13 @@ async function openConvo(kind,id){
// #7: guarantee the newest message is in view on open — force the scroll to the bottom a few times as
// late content (avatars, images, day separators) settles, unless we deliberately stopped at a "New
// messages" divider. This is the safety net over renderThread()'s own pin.
[0,120,320,600].forEach((d)=>setTimeout(()=>{ if(selected&&selected.kind===kind&&selected.id===id){ const b=document.getElementById('msgs'); if(b && !b.querySelector('.new-sep')) b.scrollTop=b.scrollHeight; } }, d));
setTimeout(()=>{ try{ const b=document.getElementById('msgs'); if(b && window.bzDbg) bzDbg('convoOpen',{count:THREAD.length, scrollTop:Math.round(b.scrollTop), scrollH:Math.round(b.scrollHeight), clientH:Math.round(b.clientHeight), atBottom:(b.scrollHeight-b.scrollTop-b.clientHeight)<8, newsep:!!b.querySelector('.new-sep'), vis:b.style.visibility||'visible'}); }catch(_){} }, 700);
// #7: land on the newest message AND stay there while late content (images/avatars in long threads)
// keeps growing the thread after the first render. Re-pin on a schedule and on every image load, but
// only while still near the bottom, so a user who deliberately scrolled up isn't yanked back down.
const _pinNewest=()=>{ if(!(selected&&selected.kind===kind&&selected.id===id)) return; const b=document.getElementById('msgs'); if(!b||b.querySelector('.new-sep')) return; if((b.scrollHeight-b.scrollTop-b.clientHeight)<1200) b.scrollTop=b.scrollHeight; };
[0,120,320,600,1000,1600,2400,3400].forEach((d)=>setTimeout(_pinNewest, d));
const _pinBox=document.getElementById('msgs'); if(_pinBox){ _pinBox.querySelectorAll('img').forEach((im)=>{ if(!im.complete) im.addEventListener('load', _pinNewest, {once:true}); }); }
setTimeout(()=>{ try{ const b=document.getElementById('msgs'); if(b && window.bzDbg) bzDbg('convoOpen',{count:THREAD.length, scrollTop:Math.round(b.scrollTop), scrollH:Math.round(b.scrollHeight), clientH:Math.round(b.clientHeight), atBottom:(b.scrollHeight-b.scrollTop-b.clientHeight)<8, newsep:!!b.querySelector('.new-sep')}); }catch(_){} }, 3600);
}
// ---------- @mentions (group chat) ----------
let mentionItems=[], mentionIdx=0, mentionStart=-1;