feat(application,backend,infrastructure,app-tauri,frontend): contributions UI/runtime des plugins limitées aux projets liés — #321 (QA verte)

Les contributions runtime des plugins (menus, slash commands, layouts,
surfaces) ne sont exposées que dans les projets liés au plugin.

- Définition du lien: ProjectPluginSettingsStore par racine projet
  (domaine) — une contribution runtime-active n'entre dans le catalogue
  que si elle est explicitement activée pour le projet courant; hors
  projet focalisé, catalogue vide
- Application: ListPluginRuntimeContributions.execute prend un
  project_id optionnel, charge projet + settings et filtre le registre
  par activation projet; tests fake store/settings (catalogue vide sans
  projet, activation, layout, slash commands)
- Backend/app-tauri: plugin_list_runtime_contributions et slash
  commands list/execute acceptent projectId optionnel (DTO camelCase,
  défaut serde), BackendCore recâblé avec store_port et
  project_plugin_settings_store; test DTO de scope optionnel
- Frontend: PluginGateway.listRuntimeContributions(projectId) (ports,
  tauri, http/unsupported, mock alignés); PluginRuntimeProvider
  re-scope le catalogue au changement de focus projet (vidage du
  registre précédent, disposition des bundles, requêtes obsolètes
  ignorées par projectId/révision); PluginLayoutCellView ignore les
  références de layout devenues hors périmètre via onChooseAnotherLayout
  et ne rend rien pendant le rechargement (aucune fuite inter-projets)
- Tests: plugin_install_load (infrastructure), dto slash commands,
  plugin.test, PluginRuntimeProvider.test, PluginLayoutCellView.test

QA verte sur feature/321-plugin-contributions-linked-projects. Aucun push.
This commit is contained in:
2026-09-11 17:46:03 +02:00
parent 6bdbe755fa
commit bf33e58508
16 changed files with 523 additions and 110 deletions

View File

@ -917,6 +917,9 @@ impl From<HealthReport> for HealthResponseDto {
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListSlashCommandsRequestDto {
/// Project whose explicitly enabled plugin contributions may be listed.
#[serde(default)]
pub project_id: Option<String>,
/// Optional typed prefix, with or without the leading `/`.
#[serde(default)]
pub prefix: Option<String>,
@ -945,6 +948,9 @@ impl From<ListSlashCommandsOutput> for SlashCommandListDto {
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteSlashCommandRequestDto {
/// Project whose explicitly enabled plugin contribution may be executed.
#[serde(default)]
pub project_id: Option<String>,
/// Command name, with or without the leading `/`.
pub name: String,
/// Current structured chat session id, required by `/clean` and future
@ -6251,6 +6257,28 @@ mod tests {
use serde_json::json;
use uuid::Uuid;
#[test]
fn slash_command_requests_accept_optional_project_scope() {
let project_id = Uuid::from_u128(321).to_string();
let list: ListSlashCommandsRequestDto = serde_json::from_value(json!({
"projectId": project_id,
"prefix": "/graph"
}))
.unwrap();
assert_eq!(list.project_id.as_deref(), Some(project_id.as_str()));
let execute: ExecuteSlashCommandRequestDto = serde_json::from_value(json!({
"projectId": project_id,
"name": "/graph",
"arguments": []
}))
.unwrap();
assert_eq!(execute.project_id.as_deref(), Some(project_id.as_str()));
let unscoped: ListSlashCommandsRequestDto = serde_json::from_value(json!({})).unwrap();
assert!(unscoped.project_id.is_none());
}
use super::*;
#[test]

View File

@ -3831,6 +3831,8 @@ impl BackendCore {
Arc::clone(&plugin_package_store),
Arc::clone(&plugin_registry_store),
Arc::clone(&plugin_manifest_validator),
Arc::clone(&store_port),
Arc::clone(&project_plugin_settings_store),
));
let reconcile_plugin_mcp_servers = Arc::new(ReconcilePluginMcpServers::new(
Arc::clone(&plugin_package_store),