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

@ -158,6 +158,43 @@ describe("loadPlugins", () => {
expect(registry.get("com.example.hello-plugin")).toBeUndefined();
});
it("loads the hello-plugin contribution shape with omitted optional arrays", async () => {
const bundle = dataUrl(`
export function activate(ctx) {
ctx.commands.registerCommand("hello-plugin.sayHello", () => {
globalThis.__helloArchiveCommandRan = true;
});
}
`);
const { registry, failures } = await loadPlugins(
[
entry({
id: "com.example.hello-plugin",
displayName: "Hello Plugin",
bundleUrl: bundle,
contributes: {
menus: [{ id: "hello-plugin.menu", label: "Hello", topLevel: true }],
menuItems: [
{
id: "hello-plugin.sayHello.item",
targetMenuId: "hello-plugin.menu",
label: "Say Hello",
command: "hello-plugin.sayHello",
},
],
} as unknown as PluginContributionDto,
}),
],
gateways,
);
expect(failures).toEqual([]);
expect(registry.get("com.example.hello-plugin")?.contributes.layouts).toEqual([]);
expect(registry.get("com.example.hello-plugin")?.contributes.mcpServers).toEqual([]);
await registry.runCommand("com.example.hello-plugin", "hello-plugin.sayHello");
expect((globalThis as Record<string, unknown>).__helloArchiveCommandRan).toBe(true);
});
it("calls dispose() on removal (best-effort)", async () => {
const bundle = dataUrl(`
export function activate(ctx) {