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:
+13
-3
@@ -1024,6 +1024,11 @@
|
|||||||
relocated into the chat-list header (see placeHdrRight). The shell is pushed below the
|
relocated into the chat-list header (see placeHdrRight). The shell is pushed below the
|
||||||
device status bar so no view overlaps it. */
|
device status bar so no view overlaps it. */
|
||||||
header{display:none;}
|
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
|
/* #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;}
|
||||||
@@ -1112,7 +1117,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-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.
|
// 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
|
||||||
@@ -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
|
// #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
|
// 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.
|
// 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));
|
// #7: land on the newest message AND stay there while late content (images/avatars in long threads)
|
||||||
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);
|
// 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) ----------
|
// ---------- @mentions (group chat) ----------
|
||||||
let mentionItems=[], mentionIdx=0, mentionStart=-1;
|
let mentionItems=[], mentionIdx=0, mentionStart=-1;
|
||||||
|
|||||||
Reference in New Issue
Block a user