feat(sprints): surface frontend du modèle de sprints

Ticket #10 — volet frontend des sprints.

- Domaine et ports frontend étendus au modèle de sprints (domain/index.ts,
  ports/index.ts).
- Adaptateurs : ticket.ts et mock/index.ts câblent les opérations sprints.
- UI tickets : useTickets + TicketsPanel exposent le rattachement/gestion des
  sprints, avec tests Vitest associés.

Typecheck / vitest / build verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 10:57:38 +02:00
parent 48b652ff91
commit 69759d351c
7 changed files with 423 additions and 31 deletions

View File

@ -165,6 +165,28 @@ export type DomainEvent =
agentId: string;
version: number;
}
| {
/**
* A ticket changed sprint membership (ticket #10). Mirror of the backend
* `DomainEventDto::IssueSprintChanged`. `from`/`to` are sprint ids (`null`
* ⇒ no sprint). Starts with `issue`, so {@link isTicketEvent} matches it
* and ticket lists refresh automatically.
*/
type: "issueSprintChanged";
issueRef: string;
from: string | null;
to: string | null;
version: number;
}
| { type: "sprintCreated"; sprintId: string; order: number }
| { type: "sprintRenamed"; sprintId: string; name: string; version: number }
| {
type: "sprintReordered";
sprintId: string;
order: number;
version: number;
}
| { type: "sprintDeleted"; sprintId: string }
| {
/**
* An intermediate assistant announcement emitted **during** an inter-agent
@ -197,6 +219,17 @@ export function isTicketEvent(
return event.type.startsWith("issue");
}
/**
* Whether a domain event is a sprint lifecycle event (`sprint*`). Lets the
* ticket view-model re-fetch the sprint list when sprints are created, renamed,
* reordered or deleted (ticket #10).
*/
export function isSprintEvent(
event: DomainEvent,
): event is Extract<DomainEvent, { type: `sprint${string}` }> {
return event.type.startsWith("sprint");
}
/** Where a project physically lives (mirror of the backend `RemoteRef`). */
export type RemoteRef =
| { kind: "local" }
@ -865,6 +898,22 @@ export interface TicketLink {
kind: TicketLinkKind;
}
/** Lifecycle status of a sprint (mirror of the backend `SprintStatus`). */
export type SprintStatus = "planned" | "active" | "done";
/** A sprint bucket tickets can belong to (mirror of `SprintDto`). */
export interface Sprint {
id: string;
/** Reorderable position; sections are displayed by ascending `order`. */
order: number;
name: string;
status: SprintStatus;
/** How many tickets currently belong to this sprint. */
ticketCount: number;
/** Optimistic-concurrency version. */
version: number;
}
/** The full ticket, as returned by `ticket_read`/`ticket_create`/… . */
export interface Ticket {
id: string;
@ -874,6 +923,8 @@ export interface Ticket {
description: string;
status: TicketStatus;
priority: TicketPriority;
/** Sprint membership (`null` ⇒ no sprint). Additive (ticket #10). */
sprintId?: string | null;
/** Present only when the ticket was read with `includeCarnet`. */
carnet?: string;
links: TicketLink[];
@ -893,6 +944,8 @@ export interface TicketSummary {
title: string;
status: TicketStatus;
priority: TicketPriority;
/** Sprint membership (`null` ⇒ no sprint). Additive (ticket #10). */
sprintId?: string | null;
assignedAgentIds: string[];
updatedAt: number;
}