Merge pull request #5617 from nesquena/release/stage-5602

release #5602: iOS PWA session-switch instant sidebar close + race guard
This commit is contained in:
nesquena-hermes authored and GitHub committed 2026-07-04 22:46:39 -07:00
commit 58e857ec95
2 files changed
+20 -1

No files matched your search

+1
View File
@@ -14,6 +14,7 @@
### Fixed
- **iOS PWA: tapping a conversation now closes the sidebar immediately and reliably opens it.** On the mobile PWA, tapping an older (large) conversation left the sidebar drawer open for several seconds with only a tiny spinner — and a background refresh could cancel the switch mid-flight so the conversation never opened and the highlight jumped back. The sidebar now closes the instant you tap (matching how ChatGPT/Claude mobile dismiss the drawer on select, with a "Loading conversation…" placeholder in the chat area), and an in-flight session switch is protected from being cancelled by a concurrent background refresh. Desktop is unaffected. Thanks @luperrypf. (#5602)
- **OpenCode Go: the model picker no longer lists models that 404 when you send.** OpenCode Go's live model probe returned entries from the full public catalog that aren't enabled on the Go tier, so picking one failed with "model not found" on the first message. The picker now uses the curated Go-tier model list for this provider instead of the live probe. Thanks @webtecnica. (#5611, #5311)
- **The sidebar's "Retry" button now gives feedback when a conversation-list load fails.** When the session list failed to load, its Retry button behaved like an inert browser default — clicking it gave no sign anything happened while the (often slow) refetch ran, so it looked broken. Retry now shows an immediate "Retrying…" pending state, restores to "Retry" if it fails again, and is properly styled to match the sidebar. It's also accessible (the error is a polite live region; the pending button keeps keyboard focus) and can't get stuck pending if you switch profiles mid-retry. Thanks @rodboev. (#5505, #5501)
- **Mobile: the command/tool approval popup is taller so its options fit without scrolling inside it.** On phones the approval card capped its height low enough that a prompt with several options (or a long command) pushed the bottom actions off the visible area, forcing you to scroll within the cramped popup to reach them. The mobile height cap is raised (from `min(52dvh, 360px)` to `min(60dvh, 420px)`), so a typical approval prompt now shows all its options at once; very tall content still scrolls inside the card, and desktop is unaffected. Thanks @nankingjing. (#5539, #5385)
+19 -1
View File
@@ -674,6 +674,11 @@ function _scheduleActiveSessionIdleReload(sid) {
if(!sid) return;
setTimeout(async () => {
if(!S||!S.session||S.session.session_id !== sid) return;
// #5409: skip idle reload while any loadSession() is in flight — avoids
// a race where the idle reload overwrites _loadingSessionId and silently
// cancels an in-progress session switch (most visible on iOS PWA with
// large sessions where Phase 1 metadata fetch is slow).
if(typeof _loadingSessionId !== 'undefined' && _loadingSessionId) return;
if(S.busy || S.activeStreamId) return;
if(typeof _isMessageReaderUnpinned==='function'&&_isMessageReaderUnpinned()){
_deferActiveSessionExternalRefresh('idle-reconcile');
@@ -5079,6 +5084,11 @@ async function refreshActiveSessionIfExternallyUpdated(reason){
// tradeoffs).
const _recoveryReasons = {visible:true, focus:true};
const _keepStaleUntilLoaded = !!_recoveryReasons[String(reason||'')];
// #5409: skip force-reload while a different session's loadSession()
// is in flight — avoids overwriting _loadingSessionId and silently
// cancelling an in-progress session switch. All four call paths
// (idle-reconcile, poll, visibility, focus) funnel through here.
if(typeof _loadingSessionId !== 'undefined' && _loadingSessionId && _loadingSessionId !== sid) return 'skipped';
await loadSession(sid, {force:true, externalRefreshReason:reason||'poll', keepStaleUntilLoaded:_keepStaleUntilLoaded});
if(typeof renderSessionList==='function') void renderSessionList();
return 'reloaded';
@@ -7337,6 +7347,8 @@ function renderSessionListFromCache(){
row.title=t('session_lineage_segment_open');
row.onclick=async(e)=>{
e.stopPropagation();
// #5409: close mobile sidebar synchronously before navigation
if(typeof closeMobileSidebar==='function')closeMobileSidebar();
await _openSidebarSession(seg, {skipLineageResolve:true});
};
lineageList.appendChild(row);
@@ -7349,6 +7361,8 @@ function renderSessionListFromCache(){
['pointerdown','pointerup','click','touchstart','touchmove','touchend','touchcancel'].forEach(ev=>childList.addEventListener(ev,e=>e.stopPropagation()));
const sortedChildren=[...s._child_sessions].sort((a,b)=>_sessionTimestampMs(b)-_sessionTimestampMs(a));
const openChildSession=async(childSession)=>{
// #5409: close mobile sidebar synchronously before navigation
if(typeof closeMobileSidebar==='function')closeMobileSidebar();
await _openSidebarSession(childSession, {skipLineageResolve:true});
};
const childLabelFor=(child)=>{
@@ -7974,8 +7988,12 @@ function renderSessionListFromCache(){
if(_renamingSid) return;
try{
if(($('sessionSearch').value||'').trim()) _hideSearchPreviewsAfterSelect=true;
await _openSidebarSession(s);
// #5409: close mobile sidebar synchronously BEFORE awaiting _openSidebarSession
// so the user gets instant feedback that navigation is happening, even
// for large sessions where loadSession can take 3-15s (metadata fetch +
// message load + renderMessages DOM build on slow iOS WKWebView).
if(typeof closeMobileSidebar==='function')closeMobileSidebar();
await _openSidebarSession(s);
}finally{
el.classList.remove('loading');
}