feat(layout): supporte la création de layouts plugins (customPluginLayout)

Étend le flux de création de layout backend (DTO, usecases, store) et le
frontend (sélecteur, adaptateurs, LayoutTabs/LayoutGrid) pour permettre
d'ouvrir un layout déclaré par un plugin installé (ex. Android Health),
sans passer par le message bloquant "extension backend pas encore livrée".

Ticket #141 — QA vert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 18:34:21 +02:00
parent 168f93df78
commit 45617992ef
29 changed files with 907 additions and 99 deletions

View File

@ -603,6 +603,38 @@ impl LayoutTree {
Ok(tree)
}
/// Replaces the opaque state of a plugin-provided custom layout leaf.
///
/// Pure: returns a new validated tree.
///
/// # Errors
/// - [`LayoutError::NodeNotFound`] if `target` is not a custom plugin layout
/// leaf in the tree.
pub fn set_plugin_layout_state(
&self,
target: NodeId,
state: serde_json::Value,
) -> Result<Self, LayoutError> {
let mut found = false;
let root = map_node(&self.root, &mut |node| {
if let LayoutNode::CustomPluginLayout(cell) = node {
if cell.id == target {
found = true;
let mut cell = cell.clone();
cell.state = state.clone();
return LayoutNode::CustomPluginLayout(cell);
}
}
node.clone()
});
if !found {
return Err(LayoutError::NodeNotFound(target));
}
let tree = Self { root };
tree.validate()?;
Ok(tree)
}
/// Sets (or, with `None`, clears) the **engine session id** cache
/// ([`LeafCell::engine_session_id`]) on the leaf `target` — the resumable id of
/// the current provider (id Claude `--resume`, UUID minté pour `--session-id`).