feat(backend): système de plugins — domaine, application, infrastructure (#43)

Lots B1-B4 : modèle de domaine des plugins (manifeste, capacités menus/layouts/MCP),
port et registre applicatif, chargement/validation en infrastructure, exposition DTO
et commandes Tauri. Tests cargo verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 07:37:03 +02:00
parent 47aacc6da9
commit bb35641715
24 changed files with 4304 additions and 41 deletions

View File

@ -44,6 +44,7 @@ fn leaf_for(tree: &LayoutTree, id: domain::NodeId) -> Option<LeafCell> {
match n {
LayoutNode::Leaf(l) if l.id == id => Some(l.clone()),
LayoutNode::Leaf(_) => None,
LayoutNode::CustomPluginLayout(_) => None,
LayoutNode::Split(s) => s.children.iter().find_map(|c| walk(&c.node, id)),
LayoutNode::Grid(g) => g.cells.iter().find_map(|c| walk(&c.node, id)),
}
@ -235,6 +236,7 @@ fn session_for(tree: &LayoutTree, id: domain::NodeId) -> Option<domain::SessionI
match n {
LayoutNode::Leaf(l) if l.id == id => Some(l.session),
LayoutNode::Leaf(_) => None,
LayoutNode::CustomPluginLayout(_) => None,
LayoutNode::Split(s) => s.children.iter().find_map(|c| walk(&c.node, id)),
LayoutNode::Grid(g) => g.cells.iter().find_map(|c| walk(&c.node, id)),
}

View File

@ -4,10 +4,10 @@
mod helpers;
use domain::{
Agent, AgentManifest, AgentOrigin, AgentProfile, AgentTemplate, ContextInjection, Direction,
LayoutNode, LayoutTree, LeafCell, ManifestEntry, MarkdownDoc, Project, ProjectPath, RemoteRef,
SessionStrategy, Skill, SkillId, SkillRef, SkillScope, SplitContainer, SshAuth,
TemplateVersion, WeightedChild,
Agent, AgentManifest, AgentOrigin, AgentProfile, AgentTemplate, ContextInjection,
CustomPluginLayoutCell, Direction, LayoutNode, LayoutTree, LeafCell, ManifestEntry,
MarkdownDoc, PluginId, PluginLayoutType, Project, ProjectPath, RemoteRef, SessionStrategy,
Skill, SkillId, SkillRef, SkillScope, SplitContainer, SshAuth, TemplateVersion, WeightedChild,
};
use helpers::{node, session};
use uuid::Uuid;
@ -472,3 +472,29 @@ fn leaf_with_agent_roundtrip_and_omits_null() {
"agent field should be omitted when None; json was {json2}"
);
}
#[test]
fn custom_plugin_layout_roundtrips_with_opaque_state() {
let tree = LayoutTree::new(LayoutNode::CustomPluginLayout(CustomPluginLayoutCell {
id: node(43),
plugin_id: PluginId::new("dev.acme.gitgraph").unwrap(),
layout_type: PluginLayoutType::new("dev.acme.gitgraph.layout").unwrap(),
state: serde_json::json!({"branch":"main","zoom":2}),
}));
assert_eq!(roundtrip(&tree), tree);
let json = serde_json::to_string(&tree).unwrap();
assert!(
json.contains("\"type\":\"customPluginLayout\""),
"json was {json}"
);
assert!(
json.contains("\"pluginId\":\"dev.acme.gitgraph\""),
"json was {json}"
);
assert!(
json.contains("\"layoutType\":\"dev.acme.gitgraph.layout\""),
"json was {json}"
);
assert!(json.contains("\"state\""), "json was {json}");
}