---
issueRef: "#86"
version: 7
updatedBy: {"kind":"agent","agent_id":"a6ced819-b893-4213-b003-9e9dc79b9641"}
updatedAt: 1784612705899
---
# Conception UX — tickets et sprints dans le client web

## Intention

La version web doit permettre de consulter et modifier les tickets/sprints sans reproduire le shell desktop. Le client web existant est une surface autonome en colonne verticale unique : après appairage, l'utilisateur choisit un projet, puis voit l'état live et les cellules agents. #86 doit ajouter une surface de pilotage tickets/sprints cohérente avec ce modèle mobile/web, pas importer les docks, fenêtres flottantes et overlays desktop.

Décision UX : réutiliser les contrats métier et les hooks transport-neutres autant que possible (`useTickets`, `useTicketDetail`, recherche, filtres, événements live), mais créer une présentation web dédiée. Ce n'est pas une version appauvrie en capacités, c'est une adaptation de navigation et de densité.

La cible prioritaire web/mobile est l'intervention rapide : lire, trier, changer statut/priorité, corriger titre/description/carnet, créer un ticket simple, assigner/désassigner, déplacer dans un sprint, créer/renommer/supprimer un sprint. Les opérations complexes restent disponibles mais rangées derrière des panneaux dédiés : liens entre tickets, carnet long, gestion complète des sprints.

## Placement dans le client web

Dans `WebWorkspace`, après ouverture d'un projet, ajouter une navigation de projet compacte au-dessus des surfaces projet :

```text
Projet: IdeA                                      [Rafraîchir]
[ Live ] [ Tickets ] [ Sprints ]
```

- `Live` : surface actuelle `État live`, inchangée dans son rôle.
- `Tickets` : nouvelle liste et édition des tickets.
- `Sprints` : gestion des sprints.

Sur mobile, ces onglets sont des boutons segmentés scrollables horizontalement si nécessaire. Sur desktop web large, ils restent dans la même colonne contrainte, pas de dock ni layout grid.

Ne pas placer les tickets sous la liste des agents : ce sont deux surfaces de projet au même niveau. L'utilisateur web doit pouvoir passer de l'état live au backlog sans chercher dans une carte agent.

## Vue Tickets — layout

La vue `Tickets` est une pile verticale : barre d'actions, recherche/filtres compacts, liste groupée par sprint, puis écran de détail lorsqu'un ticket est ouvert.

```text
Tickets                                      [+ Ticket]
[Recherche…]
[Statut ▼] [Priorité ▼] [Assigné ▼] [Tri ▼]

Sprint courant (4)
#86  medium  open       Ajouter tickets/sprints au web
#83  medium  open       Confirmation fermeture

Sans sprint (2)
#78  low     open       Langue uniforme Settings

[Charger plus]
```

Sur petit écran, un ticket s'affiche comme une ligne compacte à deux étages :

```text
#86  Ajouter tickets/sprints au web
open · medium · Sprint courant · Main
```

La ligne entière ouvre le détail. Les badges doivent rester textuels et lisibles ; ne pas empiler 5 pastilles colorées si elles provoquent un retour ligne incohérent à 360 px.

## Actions prioritaires tickets

Priorité haute sur web :

- consulter la liste ;
- rechercher et filtrer ;
- ouvrir un ticket ;
- changer statut et priorité ;
- éditer titre, description et carnet ;
- créer un ticket simple ;
- supprimer un ticket avec confirmation ;
- assigner/désassigner des agents ;
- changer le sprint d'un ticket.

Priorité secondaire, mais à conserver si les endpoints existent :

- gérer les liens entre tickets ;
- utiliser le `TicketPicker` adapté mobile pour lier/ajouter des tickets à un sprint ;
- copier le contexte de délégation.

Hors périmètre recommandé pour la première livraison web : assistant IA de ticket. Le desktop peut garder cette affordance avancée ; sur mobile/web, elle ajoute du coût UI et des permissions sans être nécessaire à la demande utilisateur.

## Création de ticket

Le bouton `+ Ticket` ouvre un panneau plein écran mobile ou une section expansée desktop web, pas un petit formulaire inline permanent. Le formulaire est court :

- `Titre` obligatoire ;
- `Priorité` ;
- `Sprint` avec picker ;
- `Description` optionnelle, textarea repliable ou sous le champ titre ;
- actions `Annuler` et `Créer`.

Après création : ouvrir automatiquement le détail du ticket créé pour permettre de compléter carnet, assignations ou liens. Si l'affectation sprint échoue après création, garder le ticket créé et afficher l'erreur sans perdre le résultat.

Libellés : `Nouveau ticket`, `Titre`, `Description`, `Priorité`, `Sprint`, `Sans sprint`, `Créer`.

## Détail ticket sur petit écran

Sur web/mobile, le détail ticket remplace temporairement la liste dans la colonne, au lieu d'utiliser la fenêtre flottante desktop. Navigation : bouton retour en haut.

```text
[← Tickets]  #86                         [⋯]
Ajouter tickets/sprints au web
open · medium · Sprint courant

[Résumé]
Titre
Description
[Enregistrer]

[Statut et priorité]
Statut       [open ▼]
Priorité     [medium ▼]
Sprint       [Sprint courant ▼]

[Carnet]
textarea Markdown
[Enregistrer le carnet]

[Agents assignés]
Main  DevFrontend                         [+]

[Liens]
relatesTo #83                             [+ Lier]
```

Les sections du détail sont accordéons ou blocs empilés. Ouverts par défaut : `Résumé`, `Statut et priorité`, `Carnet`. Repliés par défaut : `Agents assignés`, `Liens`, `Zone dangereuse`.

Le bouton de suppression vit dans une section `Zone dangereuse` ou dans un menu `⋯`, jamais dans la première ligne d'actions. Confirmation obligatoire :

Titre : `Supprimer ce ticket ?`
Corps : `Le ticket {ref} sera supprimé définitivement. Cette action ne supprime pas les sprints ni les agents assignés.`
Actions : `Annuler` / `Supprimer`

Gestion des changements non enregistrés : si l'utilisateur revient à la liste avec des modifications locales non sauvegardées, afficher la confirmation existante adaptée en français : `Des modifications ne sont pas enregistrées.` avec `Continuer l'édition`, `Ignorer`, `Enregistrer et quitter` si techniquement disponible.

## Édition rapide

Pour les champs à faible risque (`statut`, `priorité`, `sprint`), la modification peut être enregistrée immédiatement au changement de select, avec spinner discret sur la ligne/section. Pour les champs texte (`titre`, `description`, `carnet`), garder une action explicite `Enregistrer` afin d'éviter les sauvegardes involontaires sur mobile.

En cas de conflit de version : afficher un message inline en haut du détail : `Ce ticket a été modifié ailleurs et rechargé. Réappliquez votre modification.` Ne pas écraser silencieusement le brouillon local.

## Vue Sprints — layout

La vue `Sprints` est une surface dédiée, pas un overlay plein écran au-dessus de la liste comme sur desktop. Elle est accessible depuis l'onglet projet `Sprints` et depuis le bouton `Gérer les sprints` dans la vue tickets si ce raccourci est conservé.

```text
Sprints                                      [+ Sprint]

#1  Sprint courant                           [⋯]
4 tickets
[Voir tickets] [Ajouter tickets]

#2  Backlog client web                       [⋯]
7 tickets
[Voir tickets] [Ajouter tickets]
```

Créer un sprint : bouton `+ Sprint`, champ `Nom du sprint`, action `Créer`. Si le nom est vide, soit désactiver `Créer`, soit reprendre le comportement desktop de nom automatique `Sprint N`; préférence UX web : désactiver tant qu'un nom n'est pas saisi, car le mobile bénéficie d'une décision explicite.

Renommer : action dans menu `⋯`, ouvre une ligne d'édition inline dans la carte sprint ou un panneau bas sur petit écran. Actions `Annuler` / `Enregistrer`.

Réordonner : boutons `Monter` / `Descendre` dans le menu ou dans une section `Ordre`. Pas de drag-and-drop requis sur mobile.

Supprimer : confirmation obligatoire :

Titre : `Supprimer le sprint « {name} » ?`
Corps : `Les tickets de ce sprint seront conservés et passeront en « Sans sprint ».`
Actions : `Annuler` / `Supprimer`

Ajouter des tickets à un sprint : ouvrir un picker mobile de tickets, avec recherche et multi-sélection. Action finale `Ajouter {count} ticket(s)`. Les tickets déjà dans le sprint sont exclus ou affichés cochés et désactivés ; ne pas laisser l'utilisateur croire qu'ils seront ajoutés une seconde fois.

Retirer un ticket d'un sprint : depuis la carte sprint, dans la liste compacte des tickets, action `Retirer du sprint` accessible via menu ligne. Cela ne supprime pas le ticket.

## Pickers et popups sur mobile/web

Les pickers desktop (`TicketPicker`, `SprintPicker`) ne doivent pas apparaître comme de petites modales centrées sur téléphone. Adapter leur chrome :

- mobile : panneau plein écran ou bottom sheet haute, avec header fixe, recherche en haut, liste scrollable, actions en bas ;
- desktop web large : modal centrée acceptable, mais largeur limitée et focus trap ;
- toujours garder `Échap` / retour / bouton `Fermer` selon plateforme.

Le même contenu métier peut être réutilisé : recherche, filtres, sélection simple ou multiple. Seul le contenant responsive change.

## Cohérence avec le web existant

Respecter les patrons #69 :

- une seule colonne verticale dans `WebWorkspace` ;
- pas de `LayoutGrid`, docks, fenêtres flottantes desktop ou tabs projet desktop ;
- padding avec safe-area ;
- contrôles qui wrap proprement à 360 px ;
- hauteurs basées sur `dvh` pour les panneaux longs ;
- reconnect banner existante conservée au-dessus des surfaces.

Les surfaces tickets/sprints doivent continuer à se resynchroniser sur les événements domaine via le transport web live, comme `État live` le fait déjà. En cas de reconnexion, refetch complet du snapshot/listes plutôt qu'application de deltas manqués.

## Langue et libellés

Conformément à la règle UX #78, les libellés humains sont en français. Les valeurs métier techniques peuvent rester celles du domaine si elles sont déjà exposées comme enums (`open`, `closed`, `medium`) seulement si leur traduction demanderait un chantier transversal ; préférence UX : afficher `Ouvert`, `Fermé`, `Faible`, `Moyenne`, `Haute`, `Critique` dans l'UI.

Libellés principaux :

- `Tickets`
- `Sprints`
- `Nouveau ticket`
- `Créer un ticket`
- `Gérer les sprints`
- `Nouveau sprint`
- `Créer un sprint`
- `Modifier`
- `Enregistrer`
- `Annuler`
- `Supprimer`
- `Sans sprint`
- `Charger plus`
- `Aucun ticket.`
- `Aucun sprint.`
- `Tickets liés`
- `Agents assignés`
- `Carnet`
- `Zone dangereuse`

## États et erreurs

- Chargement liste tickets : `Chargement des tickets…` avec spinner compact.
- Liste vide sans filtre : `Aucun ticket dans ce projet.` + bouton `Créer un ticket`.
- Liste vide avec filtres : `Aucun ticket ne correspond aux filtres.` + `Réinitialiser les filtres`.
- Chargement sprints : `Chargement des sprints…`.
- Aucun sprint : `Aucun sprint.` + bouton `Créer un sprint`.
- Erreur réseau/session : message inline en haut de la surface, avec `Réessayer`.
- Déconnexion live : conserver la bannière existante `Connexion perdue — reconnexion en cours…` ; les formulaires peuvent rester éditables, mais sauvegarder doit afficher l'erreur réelle si le transport est indisponible.
- Suppression réussie : retour à la liste, ticket/sprint retiré après événement ou refresh.

## Accessibilité

- Les onglets `Live`, `Tickets`, `Sprints` utilisent `role="tablist"`, `role="tab"`, `aria-selected` et navigation clavier gauche/droite.
- Chaque ticket de liste est un bouton ou lien avec nom accessible complet : `{ref}, {title}, statut {status}, priorité {priority}`.
- Les formulaires ont labels visibles ou labels accessibles explicites ; ne dépendre d'aucun placeholder comme seul label.
- Les confirmations de suppression utilisent `role="alertdialog"`, focus initial sur `Annuler`, action destructive textuelle.
- Les menus `⋯` ont un label explicite : `Actions du ticket {ref}` ou `Actions du sprint {name}`.
- Les zones scrollables longues conservent le focus et ne piègent pas la navigation clavier hors modal.

## Critères d'acceptation UX/frontend

- Après ouverture d'un projet web, l'utilisateur peut basculer entre `Live`, `Tickets` et `Sprints` sans shell desktop.
- La vue web reste une colonne verticale responsive et ne monte pas le layout grid/docks/floating windows desktop.
- L'utilisateur peut lister, filtrer, créer, ouvrir, modifier et supprimer des tickets depuis le web.
- L'utilisateur peut créer, renommer, réordonner, supprimer des sprints et ajouter/retirer des tickets d'un sprint depuis le web.
- L'édition ticket sur mobile se fait dans une vue détail pleine colonne avec retour explicite, pas dans une petite popup desktop.
- Les suppressions ticket/sprint demandent confirmation et précisent ce qui est supprimé ou conservé.
- Les pickers ticket/sprint sont adaptés mobile : plein écran ou bottom sheet, recherche en haut, actions claires en bas.
- Les changements texte nécessitent `Enregistrer`; les changements statut/priorité/sprint peuvent être immédiats avec feedback de sauvegarde.
- Les libellés visibles sont en français.
- L'assistant IA de ticket n'est pas requis pour la première livraison web de #86.

---

# Cadrage technique — contrat web-server tickets/sprints

## Résultat de l'audit

Le socle applicatif tickets/sprints existe déjà dans `BackendCore` : création, lecture, liste, update, suppression, carnet, liens, assignation agent, création/liste/rename/reorder/suppression sprint, assignation/désassignation ticket→sprint. Le problème #86 n'est donc pas un manque de use case application ni de store : c'est un écart de driving adapter web.

Côté desktop, `crates/app-tauri/src/lib.rs` enregistre les commandes UI implémentées dans `crates/app-tauri/src/tickets.rs`. Côté web, `crates/web-server/src/lib.rs` expose seulement quelques commandes dans le dispatcher `POST /api/invoke` (`health`, `list_projects`, `open_project`, `get_project_work_state`, tâches de fond). Aucune commande `ticket_*` ou `sprint_*` n'y est reconnue aujourd'hui, donc le `HttpTicketGateway` web tombe en `UNKNOWN_COMMAND`.

Le frontend web est déjà prêt côté transport : `frontend/src/adapters/http/streamGateways.ts` contient `HttpTicketGateway`, câblé dans `frontend/src/adapters/http/index.ts`, et il appelle les mêmes noms de commandes que le desktop via `/api/invoke`. Le blocage principal est donc backend/contrat, pas layout/UI.

## Commandes web-server à ajouter

Rester sur le style RPC existant : ajouter des branches à `POST /api/invoke`, pas créer une nouvelle API REST `/api/tickets`.

Tickets :

- `ticket_create` — `{ request: { projectId, title, description?, priority?, status?, assignedAgentIds?, links? } }` → `TicketDto`.
- `ticket_read` — `{ request: { projectId, ref, includeCarnet? } }` → `TicketDto`.
- `ticket_list` — `{ request: { projectId, statuses?, priorities?, assignedAgentId?, sprintId?, text?, sort?, limit?, cursor? } }` → `TicketListDto`.
- `ticket_update` — `{ request: { projectId, ref, title?, description?, status?, priority?, assignedAgentIds?, expectedVersion } }` → `TicketDto`. Cette commande couvre l'édition statut/priorité côté UI ; les commandes séparées `idea_ticket_update_status` / `idea_ticket_update_priority` sont des tools MCP agent, pas des commandes UI Tauri.
- `ticket_delete` — `{ request: { projectId, ref } }` → vide/null.
- `ticket_read_carnet` — `{ request: { projectId, ref } }` → `TicketCarnetDto`.
- `ticket_update_carnet` — `{ request: { projectId, ref, carnet, expectedVersion } }` → `TicketDto`.
- `ticket_link` — `{ request: { projectId, ref, targetRef, kind, expectedVersion } }` → `TicketDto`.
- `ticket_unlink` — `{ request: { projectId, ref, targetRef, kind?, expectedVersion } }` → `TicketDto`.
- `ticket_assign` — `{ request: { projectId, ref, agentId, assigned, expectedVersion } }` → `TicketDto`.
- `ticket_assign_sprint` — `{ request: { projectId, ref, sprintId, expectedVersion } }` → `TicketDto`.
- `ticket_unassign_sprint` — `{ request: { projectId, ref, expectedVersion } }` → `TicketDto`.

Sprints :

- `sprint_create` — `{ request: { projectId, name, status? } }` → `SprintDto`.
- `sprint_list` — `{ request: { projectId } }` → `SprintListDto { items }`.
- `sprint_rename` — `{ request: { projectId, sprintId, name, expectedVersion } }` → `SprintDto`.
- `sprint_reorder` — `{ request: { projectId, orderedIds } }` → `SprintListDto { items }`.
- `sprint_delete` — `{ request: { projectId, sprintId } }` → vide/null ; le comportement existant conserve les tickets et les repasse en `Sans sprint`.

Commandes assistant ticket proches mais hors minimum #86 : `open_ticket_chat`, `close_ticket_chat`, puis le flux `sendTicketChat`/`chat.*`. Recommandation : ne pas les inclure dans le lot initial, car la demande vise l'édition tickets/sprints et le flux chat web est une surface live distincte.

## Factorisation nécessaire

Les DTO publics tickets/sprints (`TicketDto`, `TicketSummaryDto`, `TicketListDto`, `TicketCarnetDto`, `SprintDto`, `SprintListDto`, request DTOs, pagination/tri/parse helpers) vivent aujourd'hui dans `crates/app-tauri/src/tickets.rs`. `web-server` ne doit pas dépendre de `app-tauri`.

Option recommandée : déplacer le contrat pur tickets/sprints vers `backend` (`backend::dto` ou `backend::tickets`) puis faire consommer ce module par les deux driving adapters :

- `app-tauri` garde uniquement les wrappers `#[tauri::command]` ;
- `web-server` ajoute des helpers `invoke_ticket_*` / `invoke_sprint_*` ;
- les tests de pagination/tri/conflit actuellement proches du module Tauri migrent avec la logique pure.

Option minimale possible mais moins saine : dupliquer DTO/conversions dans `web-server`. À éviter, car cela crée deux contrats wire desktop/web à maintenir.

## Auth et sécurité web (#77)

Les nouvelles commandes doivent rester derrière le gate existant de `POST /api/invoke` : pairing device, cookie de session HttpOnly, contrôle d'origine/CORS, révocation device et logs sécurité. Ne pas exposer de mutation ticket/sprint en GET ni hors allowlist.

Point à arbitrer produit/sécurité : un device web appairé pourra modifier les fichiers projet `.ideai/tickets` et `.ideai/sprints`. C'est cohérent avec la demande, mais il n'existe pas actuellement de permission fine par device lecture seule/écriture. Si cette granularité est souhaitée, c'est un chantier séparé de policy device, pas un prérequis technique au #86.

## Événements live

Les domain events `issue*` et `sprint*` existent déjà dans `backend::events::DomainEventDto`, et le web-server relaie les événements domaine sur websocket `event.domain` en excluant seulement `PtyOutput`. Après exposition des commandes, les vues web peuvent réutiliser les helpers frontend `isTicketEvent` / `isSprintEvent` et refaire un refetch, sans nouveau canal événementiel.

## Tests backend attendus

- Non authentifié : `ticket_list` et une mutation ticket/sprint retournent `UNAUTHORIZED`.
- Authentifié : `ticket_list` et `sprint_list` retournent le même shape que Tauri.
- Cycle ticket : create → read → update titre/statut/priorité → carnet → link/unlink → delete.
- Cycle sprint : create → list → rename → reorder → assign ticket → unassign ticket → delete.
- Conflit `expectedVersion` propagé en `ErrorDto` cohérent avec le desktop.
- Les commandes hors allowlist restent `UNKNOWN_COMMAND`.

## Découpe proposée

Lot 1 — backend/contrat web : factorisation DTO, ajout des 17 commandes `ticket_*`/`sprint_*` dans `/api/invoke`, tests auth/lecture/mutation/conflit.

Lot 2 — surface web UX/frontend : implémenter les vues décrites ci-dessus sur le `HttpTicketGateway` existant, gérer responsive, confirmations et conflits.

Lot 3 optionnel — assistant ticket web : exposer et finaliser le flux chat seulement si la surface assistant est explicitement demandée.
