From 91b3e5c52268075f31d9faa52d21ea76d98c9d57 Mon Sep 17 00:00:00 2001 From: sravan Date: Sun, 19 Jul 2026 00:31:57 +0530 Subject: [PATCH] mobile: kill slide end-hitch (compositor teardown), fix reopen-at-latest + native download hang (batch124) Slide: the 40-128ms hitch was a compositor RE-RASTER at transitionend, not JS (longtasks:[], renderMs 2-6ms in the device log). Removed contain:layout paint / isolation:isolate from the .content slide layer and the filter:brightness parallax on .chatcol (both force a full re-raster on teardown); defer the layer demotion two frames past transitionend so it lands off the motion path. Also replaced the fragile length+lastId slide-end skip (it almost never held once a live message grew the cache, so the full innerHTML rebuild kept running at slide-end) with an exact per-message diff. Reopen-at-latest: loadOlder wrote the GROWN thread (100+ msgs) into THREAD_CACHE, so re-opening re-rendered all of it and stranded you mid-history as images shifted the (pre-load) bottom past the 1200px pin guard. Open now renders the latest PAGE only; added _forcePinOpen to glue to the newest through late image loads until the user scrolls. Download hang: navigated the whole WKWebView away to the raw file (no back, app frozen). Native-only capture-phase interceptor opens downloads in the system browser. Lightbox close/download buttons: solid dark chips so they're visible over bright images. Co-Authored-By: Claude Opus 4.8 --- server/public/home.html | 50 ++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/server/public/home.html b/server/public/home.html index 43fc85d..16dfce8 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -345,10 +345,10 @@ .lightbox img{max-width:92vw;max-height:88vh;border-radius:10px;box-shadow:0 16px 50px rgba(0,0,0,.5);} /* new #2: on iOS these sat UNDER the Dynamic Island / notch and couldn't be tapped. Push them below the safe area (and clear of the right-side inset in landscape). */ - .lightbox .lb-close,.lightbox .lb-dl{position:absolute;top:calc(18px + env(safe-area-inset-top,0px));border:none;background:rgba(255,255,255,.14);color:#fff;width:44px;height:44px;border-radius:50%;display:grid;place-items:center;cursor:pointer;text-decoration:none;z-index:2;} + .lightbox .lb-close,.lightbox .lb-dl{position:absolute;top:calc(18px + env(safe-area-inset-top,0px));border:1px solid rgba(255,255,255,.55);background:rgba(0,0,0,.58);color:#fff;width:46px;height:46px;border-radius:50%;display:grid;place-items:center;cursor:pointer;text-decoration:none;z-index:2;box-shadow:0 2px 12px rgba(0,0,0,.55);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);} /* solid dark chip + border so the ✕ / download stay clearly visible over bright images */ .lightbox .lb-close{right:calc(18px + env(safe-area-inset-right,0px));} .lightbox .lb-dl{right:calc(74px + env(safe-area-inset-right,0px));} - .lightbox .lb-close:hover,.lightbox .lb-dl:hover{background:rgba(255,255,255,.28);} + .lightbox .lb-close:hover,.lightbox .lb-dl:hover{background:rgba(0,0,0,.78);} .lightbox img{max-width:82vw;} /* #3 zoom: transform-based, anchored at the cursor; arrows hide while zoomed so panning isn't hijacked */ .lightbox img{transition:transform .08s linear;transform-origin:center center;will-change:transform;} @@ -1105,10 +1105,10 @@ its own promoted GPU layer while the message DOM renders + scrolls to bottom. Phase 2 (.chat-anim, added by JS on a double-rAF AFTER the render is laid out & painted): the compositor transitions transform 100%→0 with the main thread idle → a true 60fps slide, not a jump. Dropped at transitionend. */ - body.chat-opening .content{display:block!important;width:100vw;max-width:100vw;z-index:2;box-shadow:-12px 0 30px rgba(15,23,42,.18);transform:translate3d(100%,0,0);will-change:transform;backface-visibility:hidden;-webkit-backface-visibility:hidden;isolation:isolate;contain:layout paint;} + body.chat-opening .content{display:block!important;width:100vw;max-width:100vw;z-index:2;box-shadow:-12px 0 30px rgba(15,23,42,.18);transform:translate3d(100%,0,0);will-change:transform;backface-visibility:hidden;-webkit-backface-visibility:hidden;} /* NB: NO contain/isolation — those force a full re-raster when the layer is torn down at transitionend (the 40-128ms end-of-slide hitch the device log showed). will-change alone promotes to a GPU layer; demoting it is cheap. */ body.chat-opening.chat-anim .content{transform:translate3d(0,0,0);transition:transform .3s cubic-bezier(.32,.72,0,1);} body.chat-opening .chatcol{display:flex!important;position:absolute;inset:0;z-index:0;transform:translate3d(0,0,0);will-change:transform;backface-visibility:hidden;} - body.chat-opening.chat-anim .chatcol{transform:translate3d(-22%,0,0);filter:brightness(.97);transition:transform .3s cubic-bezier(.32,.72,0,1),filter .3s cubic-bezier(.32,.72,0,1);} + body.chat-opening.chat-anim .chatcol{transform:translate3d(-22%,0,0);transition:transform .3s cubic-bezier(.32,.72,0,1);} /* translate-only parallax — animating filter:brightness re-rasters the list every frame AND again on teardown; the dim isn't worth the jank. */ /* CLOSE / drag-back: reveal the list underneath (parallax in) while the conversation slides off right (.content moved by an inline transform in showWelcome()/the edge-drag gesture). */ body.chat-closing .content, body.chat-dragging .content{display:block!important;z-index:2;box-shadow:-12px 0 30px rgba(15,23,42,.18);will-change:transform;} @@ -1140,7 +1140,7 @@ -