fix(plugins): isole les menus plugin et durcit hello-plugin SDK contre la rechute #120
Requalification Architect du 2026-08-01: le crash INTERFACE INTERROMPUE au chargement d'un plugin (menu + item) remonte via ProjectsView -> usePluginMenus -> MenuBar jusqu'à RootErrorBoundary, quel que soit le plugin (ancien ou reconstruit via SDK). - usePluginMenus isole la résolution/conversion des contributions plugin : toute erreur retombe sur [] au lieu de propager. - ProjectsView sépare les menus natifs des menus enrichis par plugin et rend MenuBar derrière une error boundary locale (fallback menus natifs seuls). - hello-plugin (SDK) et les tests d'installation associés durcis en cohérence. Bookkeeping ticket #120 uniquement (issue.md, carnet.md) ; les fichiers counter.json/index.json et le dossier tickets/122/ restent hors commit car une collision de numérotation #122 existe entre cette base et feature/ticket120-hello-plugin-install-path-audit (deux tickets différents revendiquent #122) — à arbitrer avant de committer le bookkeeping global. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -178,12 +178,16 @@ describe("loadPlugins", () => {
|
||||
expect(registry.get("com.example.hello-plugin")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("loads the hello-plugin contribution shape with omitted optional arrays", async () => {
|
||||
it("loads the hello-plugin command and layout contribution shape", async () => {
|
||||
const bundle = dataUrl(`
|
||||
export function activate(ctx) {
|
||||
ctx.commands.registerCommand("hello-plugin.sayHello", () => {
|
||||
ctx.commands.registerCommand("hello-plugin", () => {
|
||||
globalThis.__helloArchiveCommandRan = true;
|
||||
});
|
||||
ctx.layouts.register({
|
||||
type: "hello-plugin.hello-world",
|
||||
component: () => "hello-world",
|
||||
});
|
||||
}
|
||||
`);
|
||||
const { registry, failures } = await loadPlugins(
|
||||
@ -193,13 +197,20 @@ describe("loadPlugins", () => {
|
||||
displayName: "Hello Plugin",
|
||||
bundleUrl: bundle,
|
||||
contributes: {
|
||||
menus: [{ id: "hello-plugin.menu", label: "Hello", topLevel: true }],
|
||||
menus: [{ id: "hello-plugin.menu", label: "Hello Plugin", topLevel: true }],
|
||||
menuItems: [
|
||||
{
|
||||
id: "hello-plugin.sayHello.item",
|
||||
id: "hello-plugin.command.item",
|
||||
targetMenuId: "hello-plugin.menu",
|
||||
label: "Say Hello",
|
||||
command: "hello-plugin.sayHello",
|
||||
label: "hello-plugin",
|
||||
command: "hello-plugin",
|
||||
},
|
||||
],
|
||||
layouts: [
|
||||
{
|
||||
type: "hello-plugin.hello-world",
|
||||
label: "hello-world",
|
||||
component: "hello-world",
|
||||
},
|
||||
],
|
||||
} as unknown as PluginContributionDto,
|
||||
@ -209,10 +220,22 @@ describe("loadPlugins", () => {
|
||||
);
|
||||
|
||||
expect(failures).toEqual([]);
|
||||
expect(registry.get("com.example.hello-plugin")?.contributes.layouts).toEqual([]);
|
||||
expect(registry.get("com.example.hello-plugin")?.contributes.layouts).toEqual([
|
||||
{
|
||||
type: "hello-plugin.hello-world",
|
||||
label: "hello-world",
|
||||
component: "hello-world",
|
||||
},
|
||||
]);
|
||||
expect(registry.get("com.example.hello-plugin")?.contributes.mcpServers).toEqual([]);
|
||||
await registry.runCommand("com.example.hello-plugin", "hello-plugin.sayHello");
|
||||
await registry.runCommand("com.example.hello-plugin", "hello-plugin");
|
||||
expect((globalThis as Record<string, unknown>).__helloArchiveCommandRan).toBe(true);
|
||||
const Layout = registry.layoutComponent(
|
||||
"com.example.hello-plugin",
|
||||
"hello-plugin.hello-world",
|
||||
);
|
||||
expect(Layout).toBeDefined();
|
||||
expect((Layout as unknown as () => string)()).toBe("hello-world");
|
||||
});
|
||||
|
||||
it("confines a malformed runtime catalog entry and still loads healthy plugins", async () => {
|
||||
|
||||
@ -301,8 +301,20 @@ export async function loadPlugins(
|
||||
entries.map((entry) => loadOne(entry, gateways, resolvedOptions)),
|
||||
);
|
||||
for (const result of results) {
|
||||
if ("failure" in result) failures.push(result.failure);
|
||||
else registry.add(result.plugin);
|
||||
if ("failure" in result) {
|
||||
failures.push(result.failure);
|
||||
console.warn(
|
||||
`[plugins] load failed plugin=${result.failure.pluginId}: ${result.failure.reason}`,
|
||||
);
|
||||
} else {
|
||||
registry.add(result.plugin);
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.length > 0) {
|
||||
console.info(
|
||||
`[plugins] load complete loaded=${registry.list().length} failed=${failures.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
return { registry, failures };
|
||||
|
||||
Reference in New Issue
Block a user