feat(backend,domain,infrastructure,application,app-tauri,web-server,frontend): suppression d'un projet du registre IdeA sans toucher son dossier disque — #308 (QA verte)

cause: l'utilisateur ne pouvait pas retirer un projet du registre IdeA (projects.json) ; la seule issue laissait l'entrée fantôme dans la liste des projets, sans capacité générique de retrait.

domain: port ProjectStore::remove_project(id) -> Option<Project> avec implémentation par défaut « not supported » pour ne pas casser les stores spécialisés.

infrastructure: FsProjectStore implémente remove_project — retire l'entrée du registre persisté et la renvoie, None si id inconnu, ordre relatif du registre préservé ; test store dédié.

application: use case RemoveProjectFromRegistry (remove.rs) — NotFound explicite si id absent, retourne le projet retiré + registre rafraîchi.

backend/app-tauri/web-server: DTO RemoveProjectRequestDto/RemoveProjectResponseDto (removed, projects, restore_state, was_open), adapter partagé remove_project_for_adapter réutilisé par la commande Tauri remove_project et la route HTTP — même contrat desktop/web ; remove_project_and_deactivate désactive le projet s'il était ouvert, purge l'entrée open-projects restore correspondante et renvoie was_open/restore_state ; test adapter vérifiant le contrat de bout en bout (entrée retirée, restore purgé, root intact).

frontend: DTO + ProjectGateway.removeProject (HTTP + mock), ProjectsView expose un menu d'actions projet (ProjectActionsMenu) avec entrée Retirer, RemoveProjectDialog — confirmation explicite précisant que le dossier disque n'est pas supprimé, invalidation/rechargement de la liste des projets après succès (useProjects), tests ProjectsView/projects/adapters/httpInvoker/services.

QA verte: cargo targeted tests/check OK (domain, application project_usecases, infrastructure project_store, backend, app-tauri, web-server), npm targeted/typecheck/build OK ; suite npm test complète rouge non bloquante hors #308 (échecs préexistants listProjectSettings/web). Vérifications Git: git diff --check OK, cargo fmt --all -- --check OK.

fichiers .ideai/tickets/308/, tickets/counter.json, tickets/index.json, attachments/ et scripts/ hors périmètre et préservés en worktree (sync store traité séparément) ; aucun push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 18:27:15 +02:00
parent ca1a27a2b9
commit b9f8ed303e
25 changed files with 1009 additions and 29 deletions

View File

@ -19,6 +19,7 @@ mod create;
mod files;
pub(crate) mod meta;
mod open;
mod remove;
mod restore;
pub use close::{CloseProject, CloseProjectInput, CloseProjectOutput, CloseTab, CloseTabInput};
@ -35,6 +36,7 @@ pub use meta::ProjectMeta;
pub use open::{
ListProjects, ListProjectsOutput, OpenProject, OpenProjectInput, OpenProjectOutput,
};
pub use remove::{RemoveProjectFromRegistry, RemoveProjectFromRegistryOutput};
pub use restore::{
OpenProjectRestoreService, ReadOpenProjectRestoreState, RecordProjectClosedForRestore,
RecordProjectOpenedForRestore, RemoveProjectFromRestore, RestoreOpenProjectFailure,

View File

@ -0,0 +1,44 @@
//! Removal of a project from the known-project registry.
use std::sync::Arc;
use domain::ports::ProjectStore;
use domain::{Project, ProjectId};
use crate::AppError;
/// Result of removing one registered project.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RemoveProjectFromRegistryOutput {
/// The registry entry that was removed.
pub removed: Project,
/// Complete registry after removal, preserving its prior relative order.
pub projects: Vec<Project>,
}
/// Removes a registry entry without performing any operation on its root path.
pub struct RemoveProjectFromRegistry {
store: Arc<dyn ProjectStore>,
}
impl RemoveProjectFromRegistry {
/// Builds the use case from the project registry port.
#[must_use]
pub fn new(store: Arc<dyn ProjectStore>) -> Self {
Self { store }
}
/// Removes `project_id` and returns the refreshed registry.
pub async fn execute(
&self,
project_id: ProjectId,
) -> Result<RemoveProjectFromRegistryOutput, AppError> {
let removed = self
.store
.remove_project(project_id)
.await?
.ok_or_else(|| AppError::NotFound(format!("project {project_id}")))?;
let projects = self.store.list_projects().await?;
Ok(RemoveProjectFromRegistryOutput { removed, projects })
}
}