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

@ -146,6 +146,10 @@ export interface LoadedPlugin {
dispose(): Promise<void>;
}
function arrayOrEmpty<T>(value: unknown): T[] {
return Array.isArray(value) ? (value as T[]) : [];
}
/**
* Aggregate, session-scoped registry every loaded plugin's contributions land
* in. `PluginRuntimeRegistry` itself never imports bundles (see `loader.ts`);
@ -180,7 +184,7 @@ export class PluginRuntimeRegistry {
/** All top-level menu contributions across every loaded plugin. */
topLevelMenus(): Array<{ pluginId: string; pluginDisplayName: string; menu: PluginTopLevelMenuContribution }> {
return this.list().flatMap((p) =>
p.contributes.menus.map((menu) => ({
arrayOrEmpty<PluginTopLevelMenuContribution>(p.contributes.menus).map((menu) => ({
pluginId: p.pluginId,
pluginDisplayName: p.displayName,
menu,
@ -191,7 +195,7 @@ export class PluginRuntimeRegistry {
/** All menu-item contributions across every loaded plugin. */
menuItems(): Array<{ pluginId: string; pluginDisplayName: string; item: PluginMenuItemContribution }> {
return this.list().flatMap((p) =>
p.contributes.menuItems.map((item) => ({
arrayOrEmpty<PluginMenuItemContribution>(p.contributes.menuItems).map((item) => ({
pluginId: p.pluginId,
pluginDisplayName: p.displayName,
item,
@ -214,7 +218,7 @@ export class PluginRuntimeRegistry {
layout: PluginLayoutContribution;
}> {
return this.list().flatMap((p) =>
p.contributes.layouts.map((layout) => ({
arrayOrEmpty<PluginLayoutContribution>(p.contributes.layouts).map((layout) => ({
pluginId: p.pluginId,
pluginDisplayName: p.displayName,
layout,