fix(frontend,plugins): évite le crash sur install/uninstall/reinstall par rechargement systématique de la liste (#120)

- usePlugins.ts: remplace les mises à jour optimistes du state par des appels listPlugins() après chaque opération backend
- PluginsPanel.tsx: ajoute la normalisation isInstallable() pour éviter les crashes si installable est undefined
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 19:10:44 +02:00
parent fd2ab4a0d7
commit 0061685b08
2 changed files with 19 additions and 11 deletions

View File

@ -217,11 +217,13 @@ export function PluginsPanel() {
<ConfirmDialog
title={`Installer "${installFlow.review.displayName}" ?`}
body={reviewSummary(installFlow.review)}
confirmLabel={installFlow.review.installable ? "Installer" : "Corriger avant d'installer"}
confirmLabel={
isInstallable(installFlow.review) ? "Installer" : "Corriger avant d'installer"
}
busy={vm.busy}
danger={!installFlow.review.installable}
danger={!isInstallable(installFlow.review)}
onConfirm={() => {
if (installFlow.review.installable) void confirmInstall();
if (isInstallable(installFlow.review)) void confirmInstall();
else setInstallFlow(null);
}}
onCancel={() => setInstallFlow(null)}
@ -259,7 +261,13 @@ function reviewSummary(review: PluginReview): string {
review.description ?? null,
`Confiance : full-trust — ce plugin peut exécuter du code arbitraire dans IdeA.`,
`Contributions : ${c.topLevelMenus} menu(s), ${c.menuItems} item(s), ${c.layouts} layout(s), ${c.mcpServers} serveur(s) MCP.`,
...review.issues.map((i) => `${i.severity === "error" ? "Erreur" : "Avertissement"} : ${i.message}`),
...(review.issues ?? []).map(
(i) => `${i.severity === "error" ? "Erreur" : "Avertissement"} : ${i.message}`,
),
].filter((p): p is string => Boolean(p));
return parts.join("\n");
}
function isInstallable(review: PluginReview): boolean {
return review.installable ?? true;
}

View File

@ -86,8 +86,8 @@ export function usePlugins(): PluginsViewModel {
setBusy(true);
setError(null);
try {
const result = await plugin.installFromArchive(path);
setPlugins((prev) => [...prev.filter((p) => p.id !== result.plugin.id), result.plugin]);
await plugin.installFromArchive(path);
setPlugins(await plugin.listPlugins());
return true;
} catch (e) {
setError(describe(e));
@ -104,8 +104,8 @@ export function usePlugins(): PluginsViewModel {
setBusy(true);
setError(null);
try {
const result = await plugin.installFromDirectory(path);
setPlugins((prev) => [...prev.filter((p) => p.id !== result.plugin.id), result.plugin]);
await plugin.installFromDirectory(path);
setPlugins(await plugin.listPlugins());
return true;
} catch (e) {
setError(describe(e));
@ -122,8 +122,8 @@ export function usePlugins(): PluginsViewModel {
setBusy(true);
setError(null);
try {
const updated = await plugin.setEnabled(pluginId, enabled);
setPlugins((prev) => prev.map((p) => (p.id === pluginId ? updated : p)));
await plugin.setEnabled(pluginId, enabled);
setPlugins(await plugin.listPlugins());
} catch (e) {
setError(describe(e));
} finally {
@ -139,7 +139,7 @@ export function usePlugins(): PluginsViewModel {
setError(null);
try {
await plugin.uninstall(pluginId);
setPlugins((prev) => prev.filter((p) => p.id !== pluginId));
setPlugins(await plugin.listPlugins());
} catch (e) {
setError(describe(e));
} finally {