fix(plugins): isole les contributions plugin en erreur et durcit menus.ts

Le chargement de l'archive hello-plugin (build/hello-plugin-0.1.0.zip)
vidait la fenêtre principale : une contribution plugin fautive
remontait jusqu'au rendu global au lieu de rester locale à la cellule.
Ajoute un boundary local dans PluginLayoutCellView/PluginLayoutSelectorSection
et durcit menus.ts/loader.ts/registry.ts contre les entrées de menu ou
contributions malformées.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 09:08:14 +02:00
parent eec3e0a5bc
commit aa850374ce
9 changed files with 316 additions and 58 deletions

View File

@ -12,6 +12,7 @@ import {
PluginRuntimeRegistry,
type LoadedPlugin,
} from "@/plugins/runtime";
import type { PluginContributionDto } from "@/domain";
import { PluginLayoutSelectorSection, listPluginLayoutChoices } from "./PluginLayoutSelectorSection";
function stubPlugin(pluginId: string, displayName: string, layoutType: string): LoadedPlugin {
@ -66,4 +67,32 @@ describe("listPluginLayoutChoices / PluginLayoutSelectorSection", () => {
fireEvent.click(screen.getByRole("menuitem", { name: /One layout/ }));
expect(selected).toBe("dev.acme.one.layout");
});
it("skips malformed layout contributions instead of throwing during sorting", () => {
const registry = new PluginRuntimeRegistry();
const contributes = {
menus: [],
menuItems: [],
layouts: [
{ type: "dev.acme.good", label: "Good layout", component: "X" },
{ type: "dev.acme.no-label", label: undefined, component: "X" },
{ type: undefined, label: "No type", component: "X" },
] as unknown as PluginContributionDto["layouts"],
mcpServers: [],
};
registry.add({
pluginId: "dev.acme.bad",
displayName: undefined as unknown as string,
contributes,
commands: new PluginCommandRegistry("dev.acme.bad", new Set()),
layouts: new PluginLayoutRegistry("dev.acme.bad", new Set()),
menu: new PluginMenuRegistry("dev.acme.bad"),
dispose: async () => {},
});
const choices = listPluginLayoutChoices(registry);
expect(choices).toHaveLength(1);
expect(choices[0].layout.label).toBe("Good layout");
expect(choices[0].pluginDisplayName).toBe("dev.acme.bad");
});
});