fix(plugins): support export default { activate }, cleanup partiel et erreurs visibles

Le loader accepte désormais aussi la forme export default { activate }
en plus de l'export nommé, les subscriptions partiellement établies
sont nettoyées en cas d'échec d'activation, et une erreur de
runtime-catalog est remontée visuellement dans PluginsPanel avec un
badge Invalide/Isolé sur les plugins concernés (#116/#120).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 00:05:57 +02:00
parent 50b71f4ece
commit 46086af026
5 changed files with 144 additions and 7 deletions

View File

@ -38,6 +38,13 @@ const EMPTY_PLUGIN_RUNTIME: PluginRuntimeContextValue = {
const PluginRuntimeContext = createContext<PluginRuntimeContextValue>(EMPTY_PLUGIN_RUNTIME);
function describeError(e: unknown): string {
if (e && typeof e === "object" && "message" in e) {
return String((e as { message: unknown }).message);
}
return String(e);
}
interface PluginRuntimeProviderProps {
children: ReactNode;
/** Test/Storybook escape hatch — skips the gateway fetch and uses this value as-is. */
@ -72,10 +79,19 @@ export function PluginRuntimeProvider({ children, value: injected }: PluginRunti
if (cancelled) return;
setValue({ registry: result.registry, failures: result.failures, loading: false });
})
.catch(() => {
.catch((e: unknown) => {
// No plugin gateway / catalog fetch failed: run with zero plugins
// rather than blocking the app (full-trust plugins are additive).
if (!cancelled) setValue((prev) => ({ ...prev, loading: false }));
if (!cancelled) {
setValue((prev) => ({
...prev,
failures: [
...prev.failures,
{ pluginId: "<runtime-catalog>", reason: describeError(e) },
],
loading: false,
}));
}
});
return () => {
cancelled = true;