---
name: ui-rework-sprint-scoping-contracts
description: Frontières hexagonales et contrats figés du sprint UI rework — les 4 tickets sont frontend-purs, la popup TicketPicker #18, l'échelle z-index et le curseur opaque.
metadata:
  type: reference
---
Sprint « UI rework » (order 1), cadré 2026-07-06. Les **4 tickets sont frontend-purs** : aucun nouveau port/read-model/endpoint.

**Fait clé** : filtrage/recherche tickets déjà backend-driven via `TicketGateway.list(projectId, TicketListQuery{statuses[],priorities[],assignedAgentId,text,limit,cursor})` (ports/index.ts:717) → adapter transmet à `ticket_list`. Client ne fait que le groupement par sprint. #18 réutilise ce contrat, pas d'endpoint dédié.

**G4 (contrat ticket_list) levé pour B** après revue DevBackend : statuses/priorities OK (OR intra / AND inter). Deux écarts en DETTE BASSE : (a) `text` ne matche pas `#ref` (tickets.rs:681, issues.rs:322/326/465) ; (b) `cursor` = offset numérique non opaque/non stable inter-pages (tickets.rs:1231). Acceptable pour picker single-select court.

**G3-bis (garde non-breaking)** : TicketPicker/useTicketSearch traitent `cursor` comme token OPAQUE — ne jamais le construire/incrémenter, ne relayer que le cursor de la page précédente. Migration curseur opaque backend = zéro changement frontend.

**Base de code** : pas de routeur ni store global (navigation = useState local App.tsx + ProjectsView.tsx, sidebar inline ProjectsView.tsx:224). Aucune primitive Modal/Portal partagée ; ~7 features hand-roll `fixed inset-0 role=dialog` z-50/z-[60]. Pas de createPortal. #16 introduit `shared/ui/FloatingWindow` (focus-trap, monté niveau chrome) + `shared/ui/zIndex.ts`.

**Contrat #18 figé** — `TicketPicker` props `{open, projectId, title?, confirmLabel?, selectionMode?:"single"|"multi" (défaut single), excludeRefs?: TicketRef[] (filtré client-side), initialQuery?: TicketListQuery, onSelect(TicketPickerResult|[]), onClose}`. `TicketPickerResult={ref,id,number,title,status,priority}`. Extraire `TicketFacetsBar` (fieldsets statut/priorité + recherche depuis TicketsPanel.tsx) + hook `useTicketSearch` (ne PAS réutiliser useTickets, trop lourd). #17 et #19 consomment en single.

**Échelle z-index figée (G2)** : in-cell inchangé (chrome 2-5, F3 z-20, write-portal z-4, resume z-5) ; chrome full-window menuDropdown=40, floatingWindow=50, floatingWindowNested=60, toast=70.

**Règle intégration (G5)** : fenêtres flottantes/menus montées niveau chrome (ProjectsView/App), JAMAIS dans LayoutGrid/LeafView. Ne pas toucher shouldShowWritePortalVeil ni exclusion write-portal↔F3 (cf. [[ticket4-overlay-composition-leafview]]). Focus-trap obligatoire (xterm garde le focus clavier).

**Ordre** : A(#16) ∥ B(#18) racines → prioriser B (chemin critique #17+#19) ; D(#19) après B ; C(#17) après A+B. Tout DevFrontend ; DevBackend = ticket dette basse (matching #ref + curseur opaque/stable).