fix(ios/mobile): safe-area insets, no keyboard auto-open, no h-scroll, swipe-back, hide desktop-download

Reported on the iOS TestFlight build:
- #1/#5 notch/home-indicator: apply top safe-area inset per surface (chat list column +
  content panels) instead of on the whole shell, so headers sit below the Dynamic Island
  and nothing is cut off top/bottom.
- #2: opening a chat no longer auto-focuses the composer (was popping the phone keyboard
  and shoving the layout up); tapping the box still focuses normally.
- #3: overflow-x:hidden + max-width:100vw guards so pages can't pan left/right.
- #4: left-edge swipe-right now triggers the existing bzcBack() (close popup/search/open
  chat) — a native-feeling back gesture with no native code.
- #6: hide the 'Download app' (Windows) button when running inside the Capacitor app.
Build marker -> 2026-07-17-batch90.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 16:44:12 +05:30
parent 2e4e0210b0
commit 95e13a707e
2 changed files with 32 additions and 10 deletions
+27 -6
View File
@@ -1020,7 +1020,13 @@
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;}
.shell{padding-top:env(safe-area-inset-top,0px);}
/* #3: kill any horizontal drift on phones (long content, 100vw children, safe-area math) — the page
should never pan left/right. */
html,body{overflow-x:hidden;max-width:100vw;}
.shell,.content,.chatcol,.panel,.convo,.convo-msgs,.chatlist{overflow-x:hidden;max-width:100%;}
/* #1/#5: the top safe-area inset (notch / Dynamic Island) is applied PER SURFACE — the chat-list
column and each content panel — rather than on the whole shell, so nothing hides under the island
and there's no double-padding. Bottom inset is handled by the fixed nav + panel bottom below. */
.side-title{gap:.5rem;}
.side-title h2{flex:1;}
.side-title #hdrRight{display:flex;align-items:center;gap:.4rem;flex:0 0 auto;}
@@ -1046,12 +1052,12 @@
/* Meeting room code: always fully visible, full-width, easy to read */
.meet-bar .code{flex:1 1 100%;font-size:.9rem;white-space:normal;word-break:break-word;text-align:center;background:var(--blue-soft);border-radius:8px;padding:.35rem .5rem;}
/* Chat: one pane at a time (list, then the open conversation) */
.chatcol{width:100%;flex:1 1 100%;padding-bottom:calc(60px + env(safe-area-inset-bottom,0px));}
.chatcol{width:100%;flex:1 1 100%;padding-top:env(safe-area-inset-top,0px);padding-bottom:calc(60px + env(safe-area-inset-bottom,0px));}
body.chat-open .chatcol{display:none;}
.content{display:none;}
body.chat-open .content, .shell:not(.is-chat) .content{display:block;}
.shell:not(.is-chat) .chatcol{display:none;}
.content .panel{bottom:calc(60px + env(safe-area-inset-bottom,0px));} /* clear the bottom nav + home indicator */
.content .panel{top:env(safe-area-inset-top,0px);bottom:calc(60px + env(safe-area-inset-bottom,0px));} /* below the notch; clear the bottom nav + home indicator */
/* Bigger touch targets */
.chat-row{padding:.7rem .85rem;}
.convo-head{padding:.7rem .85rem;}
@@ -1078,7 +1084,7 @@
</head>
<body>
<script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-15-batch89';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
<script>window.__BUILD='2026-07-17-batch90';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
@@ -2713,7 +2719,7 @@ async function openConvo(kind,id){
// are still safely on the server → the "messages disappear after reopen" bug). Keep the cached
// render; only show empty if we truly have nothing cached to fall back to.
if(!THREAD_CACHE.has(ckey)){ THREAD=[]; renderThread(); }
const inp0=document.getElementById('msgInput'); if(inp0) inp0.focus();
const inp0=document.getElementById('msgInput'); if(inp0 && !isMobileUA()) inp0.focus(); // #2: don't auto-open the phone keyboard on chat open
return;
}
THREAD=msgs;
@@ -2728,7 +2734,7 @@ async function openConvo(kind,id){
const el=document.querySelector('#msgs .bubble[data-id="'+esc+'"]') || document.querySelector('#msgs [data-id="'+esc+'"]');
if(el){ const sep=document.createElement('div'); sep.className='new-sep'; sep.innerHTML='<span>New messages</span>'; el.parentNode.insertBefore(sep, el); el.scrollIntoView({block:'start'}); el.parentNode.scrollTop-=40; const jl=document.getElementById('jumpLatest'); if(jl) jl.style.display='grid'; }
}
const inp=document.getElementById('msgInput'); if(inp) inp.focus();
const inp=document.getElementById('msgInput'); if(inp && !isMobileUA()) inp.focus(); // #2: on phones, opening a chat should NOT pop the keyboard (user taps the box to type)
}
// ---------- @mentions (group chat) ----------
let mentionItems=[], mentionIdx=0, mentionStart=-1;
@@ -4611,6 +4617,21 @@ function bzcBack(){
}
try{ history.pushState(null,'',location.href); }catch(_){}
window.addEventListener('popstate', ()=>{ if(bzcBack()){ try{ history.pushState(null,'',location.href); }catch(_){} } });
// #4: iOS/Android have no on-screen back button in a full-screen webview, so give the app a native-feeling
// left-edge SWIPE-back: a drag that begins at the very left edge and moves clearly rightward closes the
// topmost layer (popup → in-conversation search → open chat) via the same bzcBack() used for the OS back.
(function(){
let sx=0, sy=0, edge=false;
window.addEventListener('touchstart',(e)=>{
if(e.touches.length!==1){ edge=false; return; }
const t=e.touches[0]; edge = t.clientX<=24; sx=t.clientX; sy=t.clientY; // only arm when starting at the left edge
}, {passive:true});
window.addEventListener('touchend',(e)=>{
if(!edge) return; edge=false;
const t=e.changedTouches&&e.changedTouches[0]; if(!t) return;
if((t.clientX-sx)>60 && Math.abs(t.clientY-sy)<50) bzcBack(); // rightward, mostly-horizontal swipe = back
}, {passive:true});
})();
// Hamburger: on mobile it slides the rail drawer in/out; on desktop it collapses the rail.
function toggleNav(){ if(window.innerWidth<=760) document.body.classList.toggle('rail-open'); else document.body.classList.toggle('rail-hidden'); }
const _navT=document.getElementById('navToggle'); if(_navT) _navT.onclick=toggleNav;