fix(mobile): revert app-breaking interactive edge-back (iOS touchcancel left UI stuck)

The finger-follow edge gesture starting at the very left edge collided with iOS's own edge-swipe,
which fires touchcancel instead of touchend, so cleanup never ran and .content stayed transformed +
chat-dragging stuck on top -> bottom nav hidden and all clicks blocked. Back to the safe release-based
swipe (still gets showWelcome's parallax slide). Keyboard --kb inset, #7 no-dance, #8 image swipe kept.
Build marker -> 2026-07-17-batch96.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 23:58:42 +05:30
parent 3393e44691
commit 763b2996d1
+12 -30
View File
@@ -1112,7 +1112,7 @@
</head> </head>
<body> <body>
<script src="/icons.js?v=6"></script> <script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-17-batch95';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD); <script>window.__BUILD='2026-07-17-batch96';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
@@ -4696,39 +4696,21 @@ function bzcBack(){
} }
try{ history.pushState(null,'',location.href); }catch(_){} try{ history.pushState(null,'',location.href); }catch(_){}
window.addEventListener('popstate', ()=>{ if(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 // #4: iOS/Android have no on-screen back button in a full-screen webview, so give the app a left-edge
// left-edge back gesture. When a conversation is the top layer, the drag is INTERACTIVE — the whole // SWIPE-back: a drag that begins at the very left edge and moves clearly rightward closes the topmost
// conversation follows the finger and the chat list is revealed underneath (like Teams); releasing past a // layer (popup → in-conversation search → open chat) via bzcBack() — which runs showWelcome()'s parallax
// third of the width commits the back, otherwise it springs shut. For other layers (popup / in-conversation // slide. This is RELEASE-triggered on purpose: an interactive finger-follow drag conflicts with iOS's own
// search) it falls back to a release-triggered bzcBack(). // edge gesture (it fires touchcancel), which left the UI stuck; release-based never leaves that state.
(function(){ (function(){
let sx=0, sy=0, mode='', content=null, w=0, decided=false; let sx=0, sy=0, edge=false;
const convoIsTop=()=> window.matchMedia('(max-width:760px)').matches
&& document.body.classList.contains('chat-open')
&& !document.querySelector('.modal-ov') && !document.getElementById('lightbox')
&& !(function(){ const sh=document.getElementById('convoSearchHead'); return sh && sh.style.display!=='none'; })();
window.addEventListener('touchstart',(e)=>{ window.addEventListener('touchstart',(e)=>{
mode=''; if(e.touches.length!==1){ edge=false; return; }
if(e.touches.length!==1) return; const t=e.touches[0]; edge = t.clientX<=24; sx=t.clientX; sy=t.clientY; // only arm when starting at the left edge
const t=e.touches[0]; if(t.clientX>28) return; // only from the very left edge
sx=t.clientX; sy=t.clientY; decided=false; w=window.innerWidth||360;
if(convoIsTop()){ mode='convo'; content=document.querySelector('.shell > .content'); renderChats(searchVal()); } // list ready underneath
else mode='back';
}, {passive:true});
window.addEventListener('touchmove',(e)=>{
if(mode!=='convo'||!content) return;
const t=e.touches[0], dx=t.clientX-sx, dy=t.clientY-sy;
if(!decided){ if(Math.abs(dx)>10||Math.abs(dy)>10){ decided=true; if(Math.abs(dy)>Math.abs(dx)){ mode=''; return; } document.body.classList.add('chat-dragging'); } }
if(decided && dx>=0){ content.style.transition='none'; content.style.transform='translateX('+dx+'px)'; }
}, {passive:true}); }, {passive:true});
window.addEventListener('touchend',(e)=>{ window.addEventListener('touchend',(e)=>{
const t=e.changedTouches&&e.changedTouches[0]; const dx=t?(t.clientX-sx):0, dy=t?(t.clientY-sy):0; if(!edge) return; edge=false;
if(mode==='convo' && content){ const t=e.changedTouches&&e.changedTouches[0]; if(!t) return;
content.style.transition='transform .17s ease-out'; if((t.clientX-sx)>60 && Math.abs(t.clientY-sy)<50) bzcBack(); // rightward, mostly-horizontal swipe = back
if(dx > w*0.32){ content.style.transform='translateX(100%)'; setTimeout(()=>{ content.style.transition=''; content.style.transform=''; document.body.classList.remove('chat-dragging'); selected=null; document.body.classList.remove('chat-open'); renderChats(searchVal()); renderChatPanel(); updateRailUnread(); }, 165); }
else { content.style.transform='translateX(0)'; setTimeout(()=>{ content.style.transition=''; content.style.transform=''; document.body.classList.remove('chat-dragging'); }, 165); }
} else if(mode==='back'){ if(dx>60 && Math.abs(dy)<50) bzcBack(); }
mode=''; content=null;
}, {passive:true}); }, {passive:true});
})(); })();
// #9: swipe a message bubble to the right to reply to it (mobile), like WhatsApp/Teams. The bubble // #9: swipe a message bubble to the right to reply to it (mobile), like WhatsApp/Teams. The bubble