From ea0455659687072dc172808b798624cda071b08e Mon Sep 17 00:00:00 2001 From: sravan Date: Sun, 19 Jul 2026 00:48:34 +0530 Subject: [PATCH] mobile: definitive slide fix (zero teardown) + real native download + reconcile (batch125) Slide: the residual ~40ms end-of-slide hitch (down from 128ms in b124) was the layer DEMOTE + the deferred full renderThread at transitionend. Two fixes: - Keep .content promoted for the WHOLE time a chat is open (will-change lives on body.chat-open, not on the animating class). The open transition now only changes the transform VALUE 100%->0; when it ends there is nothing to tear down -> no demote raster. - Slide-end no longer rebuilds all 40 nodes: reconcileOpen() appends only the 0-2 genuinely new tail messages (full renderThread only if the page structure diverged). Download: routing to Safari failed auth (no login cookie). Real fix: fetch the file in the WebView (cookie present) and hand the bytes to the OS save/share sheet via Filesystem+Share (added to mobile deps; ships next TestFlight build). Until then, images fall back to the iOS long-press "Save to Photos" instead of breaking the app. Co-Authored-By: Claude Opus 4.8 --- mobile/package.json | 2 ++ server/public/home.html | 63 +++++++++++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/mobile/package.json b/mobile/package.json index 7feb8f1..c1ac6a8 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -15,8 +15,10 @@ "@capacitor/app": "^7.0.0", "@capacitor/camera": "^7.0.0", "@capacitor/core": "^7.0.0", + "@capacitor/filesystem": "^7.0.0", "@capacitor/ios": "^7.0.0", "@capacitor/keyboard": "^7.0.0", + "@capacitor/share": "^7.0.0", "@capacitor/push-notifications": "^7.0.0", "@capacitor/splash-screen": "^7.0.0", "@capacitor/status-bar": "^7.0.0" diff --git a/server/public/home.html b/server/public/home.html index 16dfce8..b58ffa9 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -1079,6 +1079,11 @@ body.chat-open .chatcol{display:none;} .content{display:none;} body.chat-open .content, .shell:not(.is-chat) .content{display:block;} + /* Keep the conversation pane on its own GPU layer for the WHOLE time it's open (not just while the + slide animates). The open transition then only changes the transform VALUE 100%→0; when it ends there + is NOTHING to tear down (no contain/box-shadow/will-change toggle) → the ~40ms end-of-slide demote + raster is gone. The layer is demoted only when you leave the chat, which has its own close animation. */ + body.chat-open .content{z-index:2;box-shadow:-12px 0 30px rgba(15,23,42,.18);will-change:transform;backface-visibility:hidden;-webkit-backface-visibility:hidden;} .shell:not(.is-chat) .chatcol{display:none;} .content .panel{top:var(--sat);bottom:calc(60px + var(--sab));} /* below the notch; clear the bottom nav + home indicator */ /* #2 (mobile keyboard): inside an open conversation, hide the bottom tab bar so the composer sits @@ -1105,7 +1110,7 @@ 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;} /* 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 .content{display:block!important;transform:translate3d(100%,0,0);} /* ONLY the start transform — display/z-index/box-shadow/will-change all come from the resting body.chat-open rule above, so removing chat-opening/chat-anim at transitionend changes NOTHING (transform 0→none is a no-op, the layer stays promoted). Zero teardown = zero end-of-slide hitch. */ 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);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. */ @@ -1140,7 +1145,7 @@ -