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:
@ -69,15 +69,16 @@ use crate::dto::{
|
||||
ProjectListDto, ProjectMcpToolPermissionsDto, ProjectPermissionsDto,
|
||||
ProjectSystemPermissionsDto, ProjectWorkStateDto, ReadAgentContextResponseDto,
|
||||
ReadChatDraftResponseDto, ReadConversationPageRequestDto, ReattachChatDto, ReattachResultDto,
|
||||
RecallMemoryRequestDto, RemoveOpenProjectRestoreEntryRequestDto, RenameLayoutRequestDto,
|
||||
ReplyChunk, ResizeTerminalRequestDto, ResolveAgentPermissionsRequestDto,
|
||||
ResolveAgentPermissionsResponseDto, ResolveAgentSystemPermissionsRequestDto,
|
||||
ResolvedAgentSystemPermissionsDto, RestoreOpenProjectsOutputDto, ResumableAgentListDto,
|
||||
RetryOpenProjectRestoreRequestDto, SaveChatDraftRequestDto, SaveEmbedderProfileRequestDto,
|
||||
SaveModelServerRequestDto, SaveOpenCodeProviderProfileRequestDto,
|
||||
SaveOpenProjectRestoreOrderRequestDto, SaveProfileRequestDto, SetActiveLayoutRequestDto,
|
||||
SetActiveLayoutResultDto, SetAgentMcpToolAllowedRequestDto, SkillDto, SkillListDto,
|
||||
SlashCommandEffectDto, SlashCommandListDto, StopLiveAgentRequestDto, StopLiveAgentResponseDto,
|
||||
RecallMemoryRequestDto, RemoveOpenProjectRestoreEntryRequestDto, RemoveProjectRequestDto,
|
||||
RemoveProjectResponseDto, RenameLayoutRequestDto, ReplyChunk, ResizeTerminalRequestDto,
|
||||
ResolveAgentPermissionsRequestDto, ResolveAgentPermissionsResponseDto,
|
||||
ResolveAgentSystemPermissionsRequestDto, ResolvedAgentSystemPermissionsDto,
|
||||
RestoreOpenProjectsOutputDto, ResumableAgentListDto, RetryOpenProjectRestoreRequestDto,
|
||||
SaveChatDraftRequestDto, SaveEmbedderProfileRequestDto, SaveModelServerRequestDto,
|
||||
SaveOpenCodeProviderProfileRequestDto, SaveOpenProjectRestoreOrderRequestDto,
|
||||
SaveProfileRequestDto, SetActiveLayoutRequestDto, SetActiveLayoutResultDto,
|
||||
SetAgentMcpToolAllowedRequestDto, SkillDto, SkillListDto, SlashCommandEffectDto,
|
||||
SlashCommandListDto, StopLiveAgentRequestDto, StopLiveAgentResponseDto,
|
||||
SyncAgentWithTemplateRequestDto, SyncResultDto, TemplateDto, TemplateListDto,
|
||||
TerminalClosedDto, TerminalSessionDto, TurnPageDto, UnassignSkillRequestDto,
|
||||
UpdateAgentContactableRequestDto, UpdateAgentContextRequestDto, UpdateAgentEffortRequestDto,
|
||||
@ -584,6 +585,45 @@ mod open_project_template_tests {
|
||||
assert!(after_open.default_system_permissions.is_none());
|
||||
assert!(after_open.default_mcp_tool_policy.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remove_project_adapter_returns_the_shared_contract() {
|
||||
let state = AppState::build(temp_path("remove-appdata"));
|
||||
let project_root = temp_path("remove-project");
|
||||
let project = state
|
||||
.create_project
|
||||
.execute(CreateProjectInput {
|
||||
name: "Remove Adapter".to_owned(),
|
||||
root: project_root.to_string_lossy().into_owned(),
|
||||
remote: None,
|
||||
default_profile_id: None,
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.project;
|
||||
state
|
||||
.record_project_opened_for_restore
|
||||
.execute(project.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = remove_project_for_adapter(
|
||||
RemoveProjectRequestDto {
|
||||
project_id: project.id.to_string(),
|
||||
},
|
||||
&state,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.removed.id, project.id.to_string());
|
||||
assert!(response.projects.is_empty());
|
||||
assert!(response.restore_state.entries.is_empty());
|
||||
assert!(response.was_open);
|
||||
assert!(project_root.exists());
|
||||
|
||||
let _ = std::fs::remove_dir_all(project_root);
|
||||
}
|
||||
}
|
||||
|
||||
/// `close_project` — persist state and release resources for a project.
|
||||
@ -619,6 +659,28 @@ pub async fn close_project(project_id: String, state: State<'_, AppState>) -> Re
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Removes one project from IdeA's registry without touching its root directory.
|
||||
#[tauri::command]
|
||||
pub async fn remove_project(
|
||||
request: RemoveProjectRequestDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<RemoveProjectResponseDto, ErrorDto> {
|
||||
remove_project_for_adapter(request, &state).await
|
||||
}
|
||||
|
||||
/// Shared desktop/web adapter body for project removal.
|
||||
pub async fn remove_project_for_adapter(
|
||||
request: RemoveProjectRequestDto,
|
||||
state: &AppState,
|
||||
) -> Result<RemoveProjectResponseDto, ErrorDto> {
|
||||
let project_id = parse_project_id(&request.project_id)?;
|
||||
state
|
||||
.remove_project_and_deactivate(project_id)
|
||||
.await
|
||||
.map(Into::into)
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// Reads the ordered open-project restore state without opening projects.
|
||||
#[tauri::command]
|
||||
pub async fn read_open_project_restore_state(
|
||||
|
||||
@ -254,6 +254,7 @@ pub fn run() {
|
||||
commands::create_project,
|
||||
commands::open_project,
|
||||
commands::close_project,
|
||||
commands::remove_project,
|
||||
commands::read_open_project_restore_state,
|
||||
commands::restore_open_projects,
|
||||
commands::retry_open_project_restore,
|
||||
|
||||
Reference in New Issue
Block a user