From d6aef4cb92db35ba497bf631bacda477b04af223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 20 Aug 2026 21:10:37 +0000 Subject: [PATCH] plugins: delete is the second verb, and the list says what it is MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uninstalling a plugin left it under a heading that said "Installed", with a caption saying "not installed". It never moved, so a successful uninstall read as a failed one — and nothing on screen said the directory was still sitting there deciding what a reinstall would run. That mattered more than the wording. Install skips the clone when the directory exists (install.ts findPlugin, fetch.ts refusing an existing dir) and never pulls, so reinstalling jellyfin on alpha silently re-applied a checkout from two days earlier. The provisioning code was not on the machine, jellyfin_accounts was never created, and connecting a server did nothing at all — with no error anywhere, because every step it did run succeeded. Two changes, both UI: - The heading is "On this machine", which is what the list is: every plugin DIRECTORY, installed or not. Delete is what moves a plugin back to the marketplace, and now the screen shows why. - Delete is gated on being uninstalled and is a button rather than a small underlined link. It was previously offered in every state, so two clicks separated a running plugin from its tables being dropped. Uninstall, then delete — the order the buttons now teach. Still true and not addressed here: uninstall followed by install reuses the existing directory as it stands. The detail pane says so now; making install fast-forward a clean checkout is a separate call. Co-Authored-By: Claude Opus 5 --- .../src/apps/Plugins/PluginDetail.tsx | 32 ++++++++++++------- .../src/apps/Plugins/PluginsList.tsx | 6 +++- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/Plugins/PluginDetail.tsx b/src/workspaces/officerdev/src/apps/Plugins/PluginDetail.tsx index 78cddd4e..9d765935 100644 --- a/src/workspaces/officerdev/src/apps/Plugins/PluginDetail.tsx +++ b/src/workspaces/officerdev/src/apps/Plugins/PluginDetail.tsx @@ -295,24 +295,32 @@ const Readme = ({ appName }: { appName: string }) => { * plugin. A modal that says "Are you sure?" tests whether you can click twice, not whether you know what * you are about to lose. * - * Offered whether or not the plugin is installed, because the state that needs it most is the one where - * a broken install has already been uninstalled and the directory is still sitting there hiding the - * plugin from the marketplace. + * ── Only once it is uninstalled ── + * + * This used to be offered in every state, including while the plugin was running. Two clicks — one of + * them a confirmation nobody reads the second time — separated a working plugin from its tables being + * dropped. Now deleting is the SECOND of two verbs: uninstall stops it and forgets it, and only then can + * it be deleted. That is also the order the buttons appear in, so the screen teaches the sequence. + * + * Nothing is lost by the gate. The state this exists for — a directory sitting on disk keeping the plugin + * out of the marketplace — is by definition an uninstalled one. */ const Purge = ({ plugin, busy, onPurge }: { plugin: PluginItem; busy: boolean; onPurge: () => void }) => { const [confirming, setConfirming] = useState(false); + // Installed means uninstall first. The button is absent rather than disabled: a disabled control invites + // a hunt for what would enable it, and the answer here is a different button three inches away. + if (plugin.installed) return null; + if (!confirming) { return (
- + +

+ Removes its tables, its data and plugins/{plugin.appName} — and puts it back in the marketplace. +

); } @@ -482,7 +490,7 @@ export const PluginDetail = () => {

{plugin.installed ? 'Disabling unmounts its routes and stops its sidecar. Uninstalling also forgets the install — neither deletes anything the plugin stored.' - : 'Not installed, but the code is still on this machine — which is why the marketplace does not offer it.'} + : 'Not installed, but the code is still on this machine — which is why the marketplace does not offer it. Installing again reuses this directory as it stands; deleting it below gets you a fresh clone.'}

run('purge', plugin.appName)} /> diff --git a/src/workspaces/officerdev/src/apps/Plugins/PluginsList.tsx b/src/workspaces/officerdev/src/apps/Plugins/PluginsList.tsx index 7e892f39..c8124ae3 100644 --- a/src/workspaces/officerdev/src/apps/Plugins/PluginsList.tsx +++ b/src/workspaces/officerdev/src/apps/Plugins/PluginsList.tsx @@ -34,7 +34,11 @@ export const PluginsList = () => { that: a count would imply a check that has not happened. */} {plugins.length ? (
- Installed + {/* "On this machine", not "Installed" — the list is every plugin DIRECTORY, and an uninstalled + one stays in it. Under the old heading such a row read "Installed · not installed", which is + how a real uninstall got mistaken for a failed one: the plugin never moved, so nothing on + screen said the directory was still there deciding what a reinstall would run. */} + On this machine