Files
IdeA/.ideai/tickets/43/carnet.md
Blomios 162e3ae641 chore(ideai): état runtime — tickets, mémoire, tâches de fond
Ticket #92 rouvert, mises à jour de carnets/tâches de fond.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 08:57:15 +02:00

349 lines
11 KiB
Markdown

---
issueRef: "#43"
version: 6
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784707900405
---
# Ticket #43 — Cadrage consolidé v2 du système de plugins
> Mise à jour Architect du 2026-07-21 après retour QA F4. Ce carnet v2 annule les ambiguïtés du cadrage initial, en particulier sur le schéma de layout custom plugin.
## 0. Décisions discovery conservées
- Plugins **full-trust** v1 : pas de sandbox UI.
- Bundle **JS/ESM pré-compilé**, importé dynamiquement ; IdeA ne compile pas de TS/TSX.
- Installation **globale** dans le dossier données utilisateur de l'application, pas dans les projets.
- Distribution v1 locale : dossier ou archive locale ; packaging compatible archive partageable future type `.vsix`.
- Contributions v1 : menus top-level, items de menus existants, layouts custom React arbitraires, serveurs MCP externes.
- MCP plugin = process serveur MCP externe déclaré par manifeste et supervisé via le pont MCP existant.
- Propreté retrait : après uninstall + redémarrage, aucune contribution, aucun process, aucune entrée fantôme, aucun état plugin-specific.
## 1. Store global et cycle de vie
Store global :
```text
{app_data_dir}/plugins/
registry.json
installed/
<pluginId>/
idea-plugin.json
dist/index.js
assets/...
servers/...
```
États v1 persistés :
```text
enabled
disabled
pending-enable
pending-disable
pending-uninstall
invalid
```
Règles :
- Installer depuis archive ou dossier local copie toujours un snapshot dans `installed/<pluginId>/`.
- `registry.json` porte seulement l'état global nécessaire ; une désinstallation réussie supprime l'entrée registry.
- Disable/uninstall en session masque les contributions UI et arrête MCP immédiatement si possible, mais le code ESM déjà importé n'est purgé strictement qu'au redémarrage.
- Au boot, le frontend reconstruit une registry plugin vide puis charge uniquement les plugins `enabled` et non `pending-uninstall`.
## 2. Manifeste v1
Fichier obligatoire : `idea-plugin.json`.
```json
{
"ideaPluginManifestVersion": 1,
"id": "dev.acme.gitgraph",
"displayName": "Git Graph",
"publisher": "Acme DevTools",
"version": "1.2.3",
"engines": { "idea": ">=0.1.0 <1.0.0" },
"main": "dist/index.js",
"icon": "assets/icon.svg",
"trustLevel": "full",
"capabilities": ["ui", "mcp"],
"contributes": {
"menus": [],
"menuItems": [],
"layouts": [],
"mcpServers": []
},
"archive": {
"files": ["idea-plugin.json", "dist/**", "assets/**", "servers/**"]
}
}
```
Validation :
- `trustLevel` vaut uniquement `full` en v1.
- `main`, `icon`, assets et commandes MCP relatives ne doivent contenir ni chemin absolu ni `..`.
- `id` stable, unique, reverse-DNS recommandé.
- `version` SemVer.
- `engines.idea` incompatible => plugin `invalid`, jamais chargé.
## 3. Contrat définitif des layouts custom plugin
### 3.1 Décision ferme
`CustomPluginLayout` est une **vraie variante top-level de `LayoutNode`**, pas un champ optionnel de `LeafCell`.
Motif : un layout plugin occupe une zone de l'arbre au même niveau conceptuel qu'une leaf terminal, un split ou une grid. Le mettre dans `LeafCell` mélangerait deux responsabilités incompatibles : terminal/session/agent d'un côté, composant React arbitraire et état opaque plugin de l'autre.
### 3.2 Schéma JSON canonique
Le layout tree garde le format serde existant `#[serde(tag = "type", content = "node")]`.
Union canonique :
```ts
export type LayoutNode =
| { type: "leaf"; node: LeafCell }
| { type: "split"; node: SplitContainer }
| { type: "grid"; node: GridContainer }
| { type: "customPluginLayout"; node: CustomPluginLayoutCell };
```
Payload canonique :
```ts
export interface CustomPluginLayoutCell {
id: string;
pluginId: string;
layoutType: string;
state: unknown;
}
```
Exemple complet :
```json
{
"root": {
"type": "customPluginLayout",
"node": {
"id": "018f0c5a-2b4b-70d4-a7c2-300000000001",
"pluginId": "dev.acme.gitgraph",
"layoutType": "dev.acme.gitgraph.layout",
"state": {
"branchFilter": "main"
}
}
}
}
```
Dans un split :
```json
{
"type": "split",
"node": {
"id": "split-1",
"direction": "row",
"children": [
{
"weight": 1,
"node": {
"type": "leaf",
"node": { "id": "terminal-1" }
}
},
{
"weight": 1,
"node": {
"type": "customPluginLayout",
"node": {
"id": "plugin-cell-1",
"pluginId": "dev.acme.gitgraph",
"layoutType": "dev.acme.gitgraph.layout",
"state": {}
}
}
}
]
}
}
```
Dans une grid, `GridCell.node` peut pareillement être `{ type: "customPluginLayout", node: ... }`.
### 3.3 Ce qui est explicitement interdit
Cette forme n'est **pas** contractuelle et ne doit plus être produite ni consommée comme modèle canonique :
```json
{
"type": "leaf",
"node": {
"id": "leaf-1",
"pluginLayout": {
"pluginId": "dev.acme.gitgraph",
"layoutType": "dev.acme.gitgraph.layout",
"state": {}
}
}
}
```
`LeafCell.pluginLayout` est une divergence frontend issue de l'ambiguïté initiale. Elle doit être supprimée du modèle domaine TS ou limitée à une migration locale temporaire de tests/mocks ; elle ne fait pas partie de l'IPC ni de la persistance.
### 3.4 Disponibilité et fallback
La disponibilité n'est pas stockée dans le layout. Elle est dérivée côté UI à partir du plugin registry/runtime catalog :
```ts
export type PluginLayoutAvailability =
| "available"
| "plugin-disabled"
| "plugin-missing"
| "incompatible";
```
Rendu :
- `available` : rendre le composant React enregistré pour `layoutType`.
- `plugin-disabled`, `plugin-missing`, `incompatible` : rendre le fallback non destructif `Layout indisponible`.
- Le fallback ne transforme pas automatiquement le nœud et ne supprime jamais `state`.
### 3.5 Ajustements requis
Verdict convergence F4 : **frontend à ajuster, backend à conserver**.
Backend :
- L'implémentation actuelle `LayoutNode::CustomPluginLayout(CustomPluginLayoutCell)` sérialisée `type: "customPluginLayout"` est le contrat canonique.
- À vérifier seulement : roundtrip serde et DTO Tauri exposent bien `pluginId`, `layoutType`, `state` en camelCase.
Frontend :
- Ajouter la variante `{ type: "customPluginLayout"; node: CustomPluginLayoutCell }` à `LayoutNode`.
- Retirer `pluginLayout?: CustomPluginLayoutCell` de `LeafCell` comme contrat domaine.
- Adapter `LayoutGrid`/renderer récursif pour router `type === "customPluginLayout"` vers `PluginLayoutCellView`.
- Adapter `layoutAvailability`, fallback et tests pour recevoir le payload depuis `node.node` de la variante top-level.
- Ajouter un test de parsing/rendu avec un layout JSON produit par Rust contenant `type: "customPluginLayout"`.
## 4. Contribution points menus et `when`
Menus v1 :
```ts
type MenuTargetId = "panels" | "settings" | `plugin:${string}`;
```
Items v1 :
```ts
interface PluginMenuItemContribution {
id: string;
targetMenuId: MenuTargetId;
label: string;
command: string;
order?: number;
icon?: string;
when?: string;
}
```
`when` v1 utilise le mini-langage booléen `&&`, `||`, `!`, parenthèses.
Variables :
```ts
type WhenVariable =
| "projectOpen"
| "gitRepository"
| "agentSelected"
| "terminalFocused"
| "layoutCellFocused";
```
Arbitrage QA sur les variables actuellement figées à `false` :
- `projectOpen` : obligatoire v1, doit refléter l'état réel.
- `gitRepository` : **bloquant avant clôture #43/F3**. Le manifeste exemple et le cas GitGraph dépendent de `projectOpen && gitRepository`; le laisser à `false` rend des contributions valides inatteignables.
- `agentSelected`, `terminalFocused`, `layoutCellFocused` : dette acceptable v1 si elles restent explicitement documentées comme **best-effort non câblé** et donc `false` jusqu'à un lot focus/selection dédié. Ce n'est pas bloquant pour F4 ni pour la clean-removal QA, sauf si un plugin de validation les utilise dans son manifeste.
Ajustement recommandé : DevFrontend câble au minimum `gitRepository` depuis l'état projet/git déjà disponible, ou retire temporairement `gitRepository` des manifests/tests de validation. La préférence architecture est de le câbler, car il est déjà annoncé comme variable v1.
## 5. Contribution MCP et `${appDataDir}`
Manifest MCP :
```ts
interface PluginMcpServerContribution {
id: string;
displayName: string;
command: string;
args?: string[];
env?: Record<string, string>;
cwd?: "${pluginRoot}" | "${appDataDir}" | string;
transport: "stdio";
autoStart?: boolean;
}
```
Variables de substitution contractuelles dans `command`, `args`, `env` et `cwd` :
- `${pluginRoot}` : obligatoire v1.
- `${appDataDir}` : **obligatoire v1 si la spec continue de l'accepter**.
- `${projectRoot}` : interdit v1.
Arbitrage QA sur `${appDataDir}` non implémenté :
- Pas bloquant pour F4 layout.
- **Bloquant pour clôture B4/#43** si le manifeste continue de documenter `${appDataDir}` comme disponible. Un contrat annoncé mais non substitué crée des specs MCP fausses.
- Deux sorties acceptables, par ordre de préférence :
1. DevBackend implémente la substitution `${appDataDir}` dans les specs MCP plugin et ajoute tests args/env/cwd.
2. Si le coût est refusé pour v1, retirer `${appDataDir}` du contrat manifeste et du validator, puis documenter explicitement `${pluginRoot}` comme seule variable v1.
Décision architecture par défaut : garder `${appDataDir}` et le faire implémenter par DevBackend, car le store global est déjà résolu côté backend et le coût est borné.
## 6. Lots et responsabilités actualisés
### F4 — convergence layout custom plugin
Owner : DevFrontend.
Livrables :
- Union TS `LayoutNode` alignée sur Rust avec `customPluginLayout` top-level.
- Renderer/fallback branchés sur cette variante.
- Suppression du contrat `LeafCell.pluginLayout`.
- Test frontend à partir d'un JSON Rust réel.
Backend F4 : pas de refonte attendue ; seulement vérifier/maintenir les tests serde existants.
### F3 — `when.gitRepository`
Owner : DevFrontend.
Livrable : `gitRepository` ne doit plus être figé à `false` pour un projet Git réel avant clôture #43/F3.
### B4 — `${appDataDir}` MCP
Owner : DevBackend.
Livrable : substitution `${appDataDir}` dans `command`, `args`, `env`, `cwd` des specs MCP plugin, ou retrait explicite du contrat si arbitré à la baisse. Par défaut : implémenter.
### QA — non-régression à ajouter
- Layout JSON backend `customPluginLayout` top-level rendu côté frontend.
- Fallback atteint si plugin missing/disabled avec nœud top-level.
- Aucun fallback basé sur `leaf.node.pluginLayout` ne compte comme validation du contrat.
- MCP spec avec `${pluginRoot}` et `${appDataDir}`.
- Menu item `when: "projectOpen && gitRepository"` actif dans un projet Git.
## 7. Non-objectifs maintenus v1
- Pas de sandbox UI.
- Pas de hot-unload mémoire garanti dans la même session.
- Pas de marketplace distant.
- Pas de compilation TS/TSX.
- Pas de `${projectRoot}` pour serveurs MCP plugin globaux.
- Pas de garantie v1 sur `agentSelected`, `terminalFocused`, `layoutCellFocused` tant qu'un lot focus/selection n'a pas été cadré.