diff --git a/src/gui/src/UI/Dashboard/TabApps.js b/src/gui/src/UI/Dashboard/TabApps.js index 258870469..c60fdf38d 100644 --- a/src/gui/src/UI/Dashboard/TabApps.js +++ b/src/gui/src/UI/Dashboard/TabApps.js @@ -183,7 +183,7 @@ function showUninstallModal ({ appName, appTitle, appUid, removesTile, self, $el // and restores its position if it comes back. if ( removesTile ) { self._apps = self._apps.filter(a => a.name !== appName); - self.renderApps($el_window, { preservePage: true }); + self.renderApps($el_window, { preservePage: true, instant: true }); } // Keep the user's page and skip the load fade — this is a // background sync, not a fresh visit. @@ -925,11 +925,17 @@ const TabApps = { this._pendingLoad = null; if ( pending.loadSeq < (this._appliedSeq || 0) ) return; this._appliedSeq = pending.loadSeq; - this._savedOrderNames = pending.orderedNames; - const orderedNames = this._hasCustomOrder && Array.isArray(this._apps) - ? serializeAppOrder(this._apps) + // The drag that deferred this load may have just saved a newer order; + // _savedOrderNames tracks the canonical saved list (saveOrder keeps + // it merged, with hidden apps at their ranks). Prefer it over the kv + // snapshot the stashed load fetched — that may predate the drag — and + // never reconcile against the visible-only on-screen order, which + // would tail-append any app returning to the grid. + const orderedNames = this._hasCustomOrder && Array.isArray(this._savedOrderNames) + ? this._savedOrderNames : pending.orderedNames; + this._savedOrderNames = orderedNames; this._hasCustomOrder = Array.isArray(orderedNames) && orderedNames.length > 0; this._apps = reconcileAppOrder(pending.merged, orderedNames); this.renderApps(pending.$el_window, { preservePage: true, instant: true }); diff --git a/src/gui/src/UI/Dashboard/appOrder.js b/src/gui/src/UI/Dashboard/appOrder.js index 42d94dfa8..5803c0c8f 100644 --- a/src/gui/src/UI/Dashboard/appOrder.js +++ b/src/gui/src/UI/Dashboard/appOrder.js @@ -94,35 +94,26 @@ export function mergeSavedOrder (currentNames, previousNames) { const currentSet = new Set(result); const prevSet = new Set(previousNames); // A survivor is a name present in both lists; re-inserted missing names - // and brand-new names never count when locating the k-th survivor. + // and brand-new names never count when advancing past a survivor. const isSurvivor = name => currentSet.has(name) && prevSet.has(name); const seen = new Set(); - let rank = 0; // survivors encountered so far in the saved order - let lastInsert = -1; // keeps runs of missing names in their saved order + // Single forward pointer over `result`: for each survivor in the saved + // order it advances just past the next survivor; each missing name is + // spliced in at the pointer, which puts it right after the same number + // of survivors that preceded it in the saved order — its rank. + let at = 0; for ( const name of previousNames ) { if ( typeof name !== 'string' || name.length === 0 ) continue; if ( seen.has(name) ) continue; seen.add(name); if ( currentSet.has(name) ) { - rank++; - lastInsert = -1; + while ( at < result.length && ! isSurvivor(result[at]) ) at++; + at++; continue; } - // Find the index just past the rank-th survivor in the result. - let at = 0; - if ( rank > 0 ) { - let survivors = 0; - for ( let i = 0; i < result.length; i++ ) { - if ( isSurvivor(result[i]) && ++survivors === rank ) { - at = i + 1; - break; - } - } - } - if ( lastInsert >= at ) at = lastInsert + 1; result.splice(at, 0, name); - lastInsert = at; + at++; } return result; }