feat(tickets): suppression d'un ticket (backend + frontend)

Ajoute la suppression complète d'un ticket (#6).

Backend Rust :
- port IssueStore::delete (NotFound si absent) + event IssueDeleted{freed_sprint}
- FsIssueStore::delete : supprime .ideai/tickets/<N>/ et l'index sous lock
- use case DeleteIssue + adaptations des tests sprint/ticket_assistant au port
- commande Tauri ticket_delete + câblage events/state/lib

Frontend :
- gateway delete (ports, adapter ticket + mock, domain)
- useTicketDetail : retrait de la liste et fermeture du détail via event issueDeleted
- intégration TicketDetail + tests

Tests verts : application/issue_usecases (6), infrastructure/issue_store (7),
app-tauri --lib (56), frontend vitest (503), npm run build (exit 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 06:27:09 +02:00
parent d25e340b44
commit 5417bd756b
20 changed files with 689 additions and 95 deletions

View File

@ -241,6 +241,16 @@ pub enum DomainEventDto {
/// New optimistic version.
version: u64,
},
/// An issue-backed public ticket was deleted.
#[serde(rename_all = "camelCase")]
IssueDeleted {
/// Project id.
project_id: String,
/// Deleted public ticket reference (`#N`).
issue_ref: String,
/// Released sprint id, when any.
freed_sprint: Option<String>,
},
/// A public ticket status changed.
#[serde(rename_all = "camelCase")]
IssueStatusChanged {
@ -781,6 +791,15 @@ impl From<&DomainEvent> for DomainEventDto {
issue_ref: issue_ref.to_string(),
version: version.get(),
},
DomainEvent::IssueDeleted {
project_id,
issue_ref,
freed_sprint,
} => Self::IssueDeleted {
project_id: project_id.to_string(),
issue_ref: issue_ref.to_string(),
freed_sprint: freed_sprint.map(|sprint| sprint.to_string()),
},
DomainEvent::IssueStatusChanged {
issue_ref,
status,
@ -1106,6 +1125,22 @@ mod tests {
assert_eq!(json["version"], 3);
}
#[test]
fn issue_deleted_relays_to_dto_and_wire() {
let project_id = domain::ProjectId::from_uuid(uuid::Uuid::from_u128(40));
let sprint_id = domain::SprintId::from_uuid(uuid::Uuid::from_u128(42));
let dto = DomainEventDto::from(&DomainEvent::IssueDeleted {
project_id,
issue_ref: domain::IssueRef::from(domain::IssueNumber::new(7).unwrap()),
freed_sprint: Some(sprint_id),
});
let json = serde_json::to_value(&dto).expect("serialisable");
assert_eq!(json["type"], "issueDeleted");
assert_eq!(json["projectId"], project_id.to_string());
assert_eq!(json["issueRef"], "#7");
assert_eq!(json["freedSprint"], sprint_id.to_string());
}
#[test]
fn ticket_assistant_events_relay_to_dto_and_wire() {
let profile_id = domain::ProfileId::from_uuid(uuid::Uuid::from_u128(9));