the list refreshes before the message, and the tab follows the result

reported as "the list does not update without a refresh". the counts were in fact
correct, which is the more interesting half: fofos-admin was ALREADY INSTALLED on
that host, so the fetch put it in Installed while the confirmation said "click it
to install" and pointed at an empty Available tab. right numbers, wrong story.

three fixes, and only one of them is the refresh.

AWAIT THE REFETCH. `invalidateQueries` is fire-and-forget, so the confirmation
appeared while the grid was still rendering the previous answer and the plugin
turned up a moment later. that reads as "nothing happened" and sends somebody
looking for a refresh button — which is exactly what happened.

SAY WHICH THING HAPPENED. a directory and an install row are separate facts:
deleting a plugin's source leaves the row, so a re-fetch can land on something the
platform still considers installed. the route now reports it, and the message is
either "added — click it to install" or "added — already installed".

FOLLOW IT TO ITS TAB. if it came back installed, the overview switches to
Installed rather than leaving somebody staring at the tab it is not in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-29 00:01:48 +01:00
co-authored by Claude Opus 5
parent 9a70089d6b
commit 3d936309a5
4 changed files with 54 additions and 12 deletions
+13 -1
View File
@@ -8,6 +8,7 @@ import { fetchCatalogue, fetchReadme } from '../../plugins/marketplace';
import { findOrphanTables } from '../../plugins/orphans';
import { fetchPlugin } from '../../plugins/fetch';
import { discoverPlugins } from '../../plugins/discover';
import { listPluginInstalls } from 'officerdb';
import { iconUrl } from '../../app-store/assets';
import {
installPlugin,
@@ -309,7 +310,18 @@ pluginsRouter.post('/fetch-from-url', async (ctx) => {
);
}
return ctx.json({ ok: true, appName, label: found.manifest?.label ?? appName });
/*
Whether it is ALREADY installed, so the UI can say the true thing and show the right tab.
A directory and an install row are separate facts — deleting a plugin's source leaves the row, so a
re-fetch can land on something the platform still considers installed. Reported here because the
caller has just been told "added" and needs to know where to look; "click it to install" pointing at
an empty tab is worse than no message.
*/
const installs = await listPluginInstalls();
const installed = installs.some((entry) => entry.appName === appName && entry.enabled);
return ctx.json({ ok: true, appName, label: found.manifest?.label ?? appName, installed });
});
pluginsRouter.post('/:appName/install', async (ctx) => {