--- name: ticket28-f1-frontend-delivered description: Lot F1 du ticket #28 livré sur feature/ticket28-firstrun-detect-hang — invariant busy/detectProfiles, flag detecting, timeout UI, test de non-régression. metadata: type: reference --- Lot **F1** (frontend) du bug #28 implémenté sur `feature/ticket28-firstrun-detect-hang` (non committé, Git s'en charge). **Invariant posé** : dans `frontend/src/features/first-run/useFirstRun.ts`, le retour de `busy` à `false` ne dépend d'aucun appel à `detectProfiles`. `busy` n'entoure plus que `firstRunState()` (reload) et `configureProfiles()` (finish). Toute future régression qui remet un `await profile.detectProfiles(...)` sur un chemin gouvernant `busy` re-gèle le wizard. **Mécanisme** : `runDetection(candidates, { preselect, reportError })` factorise auto-détection et bouton manuel. Le flag `detecting` est relâché par un `setTimeout(DETECT_TIMEOUT_MS = 3_000)`, **jamais** par la promesse — car le `finally` d'un `await` sur une promesse jamais settled (panic Rust ⇒ pas de réponse IPC) n'est jamais atteint. Un compteur `detectRun` invalide les rounds périmés (re-clic, unmount). **Piège UI à retenir** : `