mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-27 08:27:43 +00:00
fix: make Dashboard app uninstall honest about recents and resilient mid-flight
- Recently-opened apps were classified as sticky-removable, but the recent list is built from app-open history that a revoke doesn't touch, so their tiles reappeared on the next load — the very bug the uninstall gating set out to fix. Treat recents like recommended. - The confirm modal claimed every staying tile was 'provided by Puter', which is false for third-party recents; describe the actual reason. - Uninstall now marks in-flight loads stale so a fetch that started before the revoke can't resurrect the removed tile. - A pagination failure after the first page no longer discards the pages already fetched — one flaky request among N used to turn the whole grid into 'Failed to load apps'.
This commit is contained in:
@@ -131,10 +131,11 @@ function showUninstallModal ({ appName, appTitle, appUid, removesTile, self, $el
|
||||
const displayName = (appTitle || appName || '').trim();
|
||||
// A recommended/recent app's tile reappears on the next load even after a
|
||||
// successful revoke; say so up front so the uninstall doesn't look like it
|
||||
// silently failed.
|
||||
// silently failed. (Recommended apps stay indefinitely; recent ones until
|
||||
// they age out of the open history — don't claim more than that.)
|
||||
const note = removesTile
|
||||
? ''
|
||||
: '<p>This app is provided by Puter, so its icon will stay in Apps.</p>';
|
||||
: '<p>Its icon will stay in Apps for now, because this app is in your recommended or recently used list.</p>';
|
||||
const $overlay = $(`
|
||||
<div class="myapps-modal-overlay">
|
||||
<div class="myapps-modal">
|
||||
@@ -178,6 +179,13 @@ function showUninstallModal ({ appName, appTitle, appUid, removesTile, self, $el
|
||||
// Keep the persisted order free of the now-uninstalled app, but
|
||||
// only if the user already has a custom order (don't create one).
|
||||
if ( self._hasCustomOrder ) self.saveOrder();
|
||||
// Mark any in-flight load stale: it fetched before the revoke,
|
||||
// and applying it would pop the just-removed tile back in.
|
||||
// Also stop sharing its promise, so the next activation
|
||||
// fetches fresh instead of joining the doomed load.
|
||||
self._loadSeq = (self._loadSeq || 0) + 1;
|
||||
self._appliedSeq = self._loadSeq;
|
||||
self._loadPromise = null;
|
||||
self.renderApps($el_window, { preservePage: true });
|
||||
}
|
||||
} catch ( err ) {
|
||||
@@ -977,23 +985,33 @@ const TabApps = {
|
||||
const MAX_PAGES = 50; // 5000 apps — a runaway backstop
|
||||
const all = [];
|
||||
for ( let page = 1; page <= MAX_PAGES; page++ ) {
|
||||
const res = await fetch(
|
||||
`${window.api_origin}/installedApps?orderBy=name&limit=${PAGE_SIZE}&page=${page}`,
|
||||
{
|
||||
headers: { 'Authorization': `Bearer ${puter.authToken}` },
|
||||
method: 'GET',
|
||||
},
|
||||
);
|
||||
const batch = await res.json();
|
||||
// An error payload (e.g. `{"error": ...}` on a 401/500) must
|
||||
// fail the whole load — reading it as end-of-pagination would
|
||||
// silently render the grid without any installed apps.
|
||||
if ( ! Array.isArray(batch) ) {
|
||||
throw new Error(`installedApps returned a non-array response (status ${res.status})`);
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${window.api_origin}/installedApps?orderBy=name&limit=${PAGE_SIZE}&page=${page}`,
|
||||
{
|
||||
headers: { 'Authorization': `Bearer ${puter.authToken}` },
|
||||
method: 'GET',
|
||||
},
|
||||
);
|
||||
const batch = await res.json();
|
||||
// An error payload (e.g. `{"error": ...}` on a 401/500)
|
||||
// must fail the page — reading it as end-of-pagination
|
||||
// would silently drop every installed app.
|
||||
if ( ! Array.isArray(batch) ) {
|
||||
throw new Error(`installedApps returned a non-array response (status ${res.status})`);
|
||||
}
|
||||
if ( batch.length === 0 ) break;
|
||||
all.push(...batch);
|
||||
if ( batch.length < PAGE_SIZE ) break;
|
||||
} catch ( err ) {
|
||||
// A first-page failure is a failed load. A later page
|
||||
// failing must not discard the pages already fetched —
|
||||
// that would turn one flaky request among N into an
|
||||
// empty grid (the single-request load never had that).
|
||||
if ( page === 1 ) throw err;
|
||||
console.error(`Failed to fetch installedApps page ${page}; showing the ${all.length} apps fetched so far:`, err);
|
||||
break;
|
||||
}
|
||||
if ( batch.length === 0 ) break;
|
||||
all.push(...batch);
|
||||
if ( batch.length < PAGE_SIZE ) break;
|
||||
}
|
||||
return all;
|
||||
};
|
||||
@@ -1026,20 +1044,26 @@ const TabApps = {
|
||||
}));
|
||||
|
||||
// Whether an uninstall makes the tile disappear for good: apps the
|
||||
// user actually installed that are NOT in the hardcoded recommended
|
||||
// list. revokeApp on a recommended app still revokes permissions,
|
||||
// but get-launch-apps re-adds the tile on the next load — the
|
||||
// context menu uses this flag to set expectations in the modal
|
||||
// (see showUninstallModal), not to withhold the action.
|
||||
// user actually installed that are in NEITHER launch list.
|
||||
// revokeApp still revokes permissions for the others, but
|
||||
// get-launch-apps re-adds their tiles on the next load — the
|
||||
// recommended list is hardcoded and the recent list is built from
|
||||
// app-open history, which a revoke doesn't touch. The context menu
|
||||
// uses this flag to set expectations in the modal (see
|
||||
// showUninstallModal), not to withhold the action.
|
||||
const recommendedNames = new Set(
|
||||
(launchData.recommended || []).map(a => a.name),
|
||||
);
|
||||
const recentNames = new Set(
|
||||
(launchData.recent || []).map(a => a.name),
|
||||
);
|
||||
const installedNames = new Set(
|
||||
(Array.isArray(installedApps) ? installedApps : []).map(a => a.name),
|
||||
);
|
||||
const isUninstallable = name =>
|
||||
installedNames.has(name)
|
||||
&& ! recommendedNames.has(name)
|
||||
&& ! recentNames.has(name)
|
||||
&& ! APP_NAMES_NO_UNINSTALL.has((name || '').toLowerCase());
|
||||
|
||||
// Build seen set from launch apps
|
||||
|
||||
Reference in New Issue
Block a user