feat(tickets): surface frontend V1 du système de tickets

Surface UI complète des tickets (domaine Issue exposé « ticket »),
validée QA de bout en bout : cargo build + tests backend verts,
npm run build + npm test (459) verts.

- Domaine : DTO Ticket, 9 events Issue* + guard isTicketEvent.
- Ports : TicketGateway (+ types query/input) ajouté à Gateways.
- Adapters : TauriTicketGateway réel (+ isTicketVersionConflict),
  wiring, et MockTicketGateway pour les tests.
- Feature tickets : hooks (useTickets, useTicketDetail,
  useProjectAgents), TicketsPanel, TicketDetail, TicketsView,
  ticketMeta + tests.
- ProjectsView : onglet sidebar « Tickets ».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 22:56:36 +02:00
parent 8de7be01a8
commit c1d82eee8d
16 changed files with 2150 additions and 1 deletions

View File

@ -41,6 +41,12 @@ import type {
SkillScope,
Template,
TerminalSession,
Ticket,
TicketCarnet,
TicketLinkKind,
TicketList,
TicketPriority,
TicketStatus,
TurnPage,
Unsubscribe,
} from "@/domain";
@ -688,6 +694,98 @@ export interface ConversationGateway {
): Promise<TurnPage>;
}
/** Filter/search criteria for {@link TicketGateway.list}. */
export interface TicketListQuery {
status?: TicketStatus;
priority?: TicketPriority;
assignedAgentId?: string;
/** Free-text match over title/description. */
text?: string;
limit?: number;
cursor?: string;
}
/** Input for {@link TicketGateway.create}. */
export interface CreateTicketInput {
title: string;
description?: string;
priority?: TicketPriority;
status?: TicketStatus;
assignedAgentIds?: string[];
}
/**
* Fields to change on {@link TicketGateway.update}. Only the provided keys are
* touched; `expectedVersion` is mandatory (optimistic concurrency — a stale
* value surfaces a `versionConflict` {@link GatewayError}).
*/
export interface UpdateTicketInput {
title?: string;
description?: string;
status?: TicketStatus;
priority?: TicketPriority;
assignedAgentIds?: string[];
expectedVersion: number;
}
/**
* Ticket gateway (public wire name for the backend `Issue` model). The only
* frontend place that knows the `ticket_*` command names; features consume this
* port through DI. Every mutation carries an `expectedVersion` and rejects with
* a `versionConflict` {@link GatewayError} when the ticket moved underneath.
*/
export interface TicketGateway {
/** Creates a ticket and returns it. */
create(projectId: string, input: CreateTicketInput): Promise<Ticket>;
/** Reads one ticket, optionally including its carnet body. */
read(
projectId: string,
ref: string,
includeCarnet?: boolean,
): Promise<Ticket>;
/** Lists tickets matching the (optional) filter/search query. */
list(projectId: string, query?: TicketListQuery): Promise<TicketList>;
/** Applies partial edits (title/description/status/priority/assignees). */
update(
projectId: string,
ref: string,
input: UpdateTicketInput,
): Promise<Ticket>;
/** Reads the ticket-scoped Markdown carnet. */
readCarnet(projectId: string, ref: string): Promise<TicketCarnet>;
/** Replaces (not appends) the ticket-scoped carnet body. */
updateCarnet(
projectId: string,
ref: string,
carnet: string,
expectedVersion: number,
): Promise<Ticket>;
/** Links this ticket to another `#id` with the given relationship kind. */
link(
projectId: string,
ref: string,
targetRef: string,
kind: TicketLinkKind,
expectedVersion: number,
): Promise<Ticket>;
/** Removes a link (optionally scoped to a single kind). */
unlink(
projectId: string,
ref: string,
targetRef: string,
expectedVersion: number,
kind?: TicketLinkKind,
): Promise<Ticket>;
/** Assigns/unassigns an agent to the ticket. */
assign(
projectId: string,
ref: string,
agentId: string,
assigned: boolean,
expectedVersion: number,
): Promise<Ticket>;
}
/**
* The full set of gateways the app depends on, injected via the DI provider.
* The composition (real vs mock) is chosen in `app/`.
@ -709,4 +807,5 @@ export interface Gateways {
permission: PermissionGateway;
workState: WorkStateGateway;
conversation: ConversationGateway;
ticket: TicketGateway;
}