chore(ideai): état runtime — tickets, mémoire, tâches de fond
État d'exécution accumulé (tickets créés/mis à jour hors #43, notes de mémoire, tâche de fond) capturé au moment du commit de la feature. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -1,6 +1,349 @@
|
||||
---
|
||||
issueRef: "#43"
|
||||
version: 1
|
||||
updatedBy: {"kind":"user"}
|
||||
updatedAt: 1783879315858
|
||||
version: 5
|
||||
updatedBy: {"kind":"agent","agent_id":"dce19c75-9669-4e45-b8de-9950025157da"}
|
||||
updatedAt: 1784667145901
|
||||
---
|
||||
# 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é.
|
||||
Reference in New Issue
Block a user