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:
@ -1,6 +1,13 @@
|
||||
# Hello Plugin
|
||||
|
||||
Minimal IdeA plugin example using the public SDK types.
|
||||
Installable IdeA plugin example rebuilt from the public SDK types.
|
||||
|
||||
It exercises the current plugin primitives end to end:
|
||||
|
||||
- top-level menu: `Hello Plugin`;
|
||||
- menu entry: `hello-plugin`;
|
||||
- command: `hello-plugin`, returning `hello-world`;
|
||||
- layout contribution: `hello-plugin.hello-world`, rendered as `hello-world`.
|
||||
|
||||
```sh
|
||||
npm run typecheck:examples
|
||||
@ -10,3 +17,15 @@ npm run package:hello-plugin
|
||||
The installable archive is emitted at `examples/hello-plugin/build/hello-plugin-0.1.0.zip`.
|
||||
It contains `idea-plugin.json` at the ZIP root and the compiled ESM entrypoint at
|
||||
`dist/index.js`, matching the manifest `main` field.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
During activation the plugin logs:
|
||||
|
||||
- whether the command and layout runtime registries are available;
|
||||
- successful registration of the `hello-plugin` command;
|
||||
- successful registration of the `hello-plugin.hello-world` layout;
|
||||
- the first layout render, including project/node identifiers.
|
||||
|
||||
These messages are intentionally small and stable so installation, bundle import, activation and
|
||||
layout rendering failures can be separated quickly in IdeA logs/devtools.
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"displayName": "Hello Plugin",
|
||||
"publisher": "IdeA Examples",
|
||||
"version": "0.1.0",
|
||||
"description": "Minimal IdeA plugin example.",
|
||||
"description": "SDK example plugin for validating command, menu and layout loading.",
|
||||
"main": "dist/index.js",
|
||||
"engines": {
|
||||
"idea": ">=0.1.0"
|
||||
@ -17,16 +17,26 @@
|
||||
"menus": [
|
||||
{
|
||||
"id": "hello-plugin.menu",
|
||||
"label": "Hello",
|
||||
"topLevel": true
|
||||
"label": "Hello Plugin",
|
||||
"topLevel": true,
|
||||
"order": 100
|
||||
}
|
||||
],
|
||||
"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",
|
||||
"order": 10
|
||||
}
|
||||
],
|
||||
"layouts": [
|
||||
{
|
||||
"type": "hello-plugin.hello-world",
|
||||
"label": "hello-world",
|
||||
"component": "hello-world",
|
||||
"order": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,15 +1,75 @@
|
||||
import type { ActivateContext, IdeAPluginModule } from "@idea/plugin-sdk";
|
||||
import type { ActivateContext, CommandDisposable, IdeAPluginModule } from "@idea/plugin-sdk";
|
||||
|
||||
const COMMAND_ID = "hello-plugin";
|
||||
const LAYOUT_TYPE = "hello-plugin.hello-world";
|
||||
|
||||
type HelloPluginLayoutProps = {
|
||||
projectId?: string;
|
||||
nodeId?: string;
|
||||
layoutType?: string;
|
||||
state?: unknown;
|
||||
};
|
||||
|
||||
type LayoutRegistry = {
|
||||
register(definition: {
|
||||
type: string;
|
||||
component: (props: HelloPluginLayoutProps) => string;
|
||||
}): CommandDisposable;
|
||||
};
|
||||
|
||||
type HelloPluginContext = ActivateContext & {
|
||||
layouts?: LayoutRegistry;
|
||||
};
|
||||
|
||||
let hasLoggedFirstLayoutRender = false;
|
||||
|
||||
function HelloWorldLayout(props: HelloPluginLayoutProps): string {
|
||||
if (!hasLoggedFirstLayoutRender) {
|
||||
hasLoggedFirstLayoutRender = true;
|
||||
console.info("[hello-plugin] layout first render", {
|
||||
projectId: props.projectId,
|
||||
nodeId: props.nodeId,
|
||||
layoutType: props.layoutType,
|
||||
hasState: props.state !== undefined
|
||||
});
|
||||
}
|
||||
|
||||
return "hello-world";
|
||||
}
|
||||
|
||||
export function activate(ctx: ActivateContext): void {
|
||||
ctx.logger.info("Hello from the IdeA hello plugin.");
|
||||
|
||||
const disposable = ctx.commands?.registerCommand("hello-plugin.sayHello", () => {
|
||||
ctx.logger.info("Hello command executed.");
|
||||
return "Hello from IdeA";
|
||||
const pluginContext = ctx as HelloPluginContext;
|
||||
ctx.logger.info("activating hello-plugin", {
|
||||
pluginId: ctx.pluginId,
|
||||
hasCommands: Boolean(pluginContext.commands),
|
||||
hasLayouts: Boolean(pluginContext.layouts)
|
||||
});
|
||||
|
||||
if (disposable) {
|
||||
ctx.subscriptions.push(disposable);
|
||||
const commandDisposable = pluginContext.commands?.registerCommand(COMMAND_ID, () => {
|
||||
ctx.logger.info("command executed", { commandId: COMMAND_ID });
|
||||
return "hello-world";
|
||||
});
|
||||
|
||||
if (commandDisposable) {
|
||||
ctx.subscriptions.push(commandDisposable);
|
||||
ctx.logger.info("command registered", { commandId: COMMAND_ID });
|
||||
} else {
|
||||
ctx.logger.warn("command registry unavailable", { commandId: COMMAND_ID });
|
||||
}
|
||||
|
||||
const layoutDisposable = pluginContext.layouts?.register({
|
||||
type: LAYOUT_TYPE,
|
||||
component: HelloWorldLayout
|
||||
});
|
||||
|
||||
if (layoutDisposable) {
|
||||
ctx.subscriptions.push(layoutDisposable);
|
||||
ctx.logger.info("layout registered", {
|
||||
layoutType: LAYOUT_TYPE,
|
||||
component: "hello-world"
|
||||
});
|
||||
} else {
|
||||
ctx.logger.warn("layout registry unavailable", { layoutType: LAYOUT_TYPE });
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,4 +78,3 @@ const plugin: IdeAPluginModule = {
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user