# Rapport de lot — v4.1.0 « Pilotage d'équipe, phase 1 »

**Socle de gestion · 2 septembre 2026 · à partir du tag v4.0.0 (`27ef96b`)**

Ce lot livre la **phase 1** du document de conception `docs/conception/PILOTAGE.md` (§12) : tâches,
trois origines, journal, « Ma journée » v2, cockpit, signaux, notifications, rapport hebdo, pont
heures → feuille de temps. Plus les **trois correctifs** constatés à l'écran sur la prod v4.0.0.

Il ne livre **ni points, ni séries, ni badges, ni niveaux, ni primes, ni missions renfort, ni
heures supplémentaires, ni règlement PDF** : c'est la v4.2.0. Aucun écran, aucune colonne, aucun
bouton n'en parle — et trois tests le vérifient plutôt que de s'en remettre à la bonne volonté du
lot suivant.

---

## 0. En une page

| | |
|---|---|
| **Tests** | **2 856 verts** au total (1 sauté : la relecture visuelle des PDF de formation, préexistante), dont **123 nouveaux** pour ce lot |
| **Migrations** | 2, additives et idempotentes (`hasTable`), aucune donnée touchée |
| **Dépendances composer** | 0 ajoutée |
| **Assets** | `npm run build` relancé : les nouvelles vues Blade sont des sources du thème Tailwind. `public/build` reste hors dépôt (construit au déploiement) |
| **Écrans nouveaux** | « Cockpit équipe », « Réglages → Pilotage & primes », 2 blocs de fiche |
| **Écran refondu** | « Ma journée » (panneau Employé), ÉTENDUE — jamais une seconde page |
| **Commandes** | `pilotage:matin`, `pilotage:rappel`, `pilotage:recap`, `pilotage:rapport-hebdo` |
| **Permissions** | `pilotage.view`, `pilotage.manage` — et deux seulement |

---

## Volet 0 — Les trois correctifs de la prod v4.0.0

### 0.1 Le voyant n'était sur aucun écran

**Ce qui se passait.** `VoyantVeilleWidget` était livré, testé, et rendait parfaitement. Il portait
`$sort = -5` pour se placer en tête. Il n'apparaissait nulle part.

**La cause.** `Dashboard::getWidgets()` déclare ses widgets un par un. Dès qu'une page de tableau de
bord Filament fait cela, **la découverte automatique ne joue plus** : un widget absent du tableau
n'est pas rendu, quel que soit son `$sort`. Le `$sort` ne classe que ce qui est déjà entré.

**Ce qui a été fait.**

1. `VoyantVeilleWidget::class` en **première position** de `getWidgets()`, avec le commentaire
   « 0. Voyant de la veille — un défaut de chaîne se lit avant les chiffres qu'il fausse ».
   `canView()` (`tenders.view`) et `columnSpan = 'full'` sont conservés tels quels.
2. **`protected static bool $isLazy = false;`** sur le widget. Filament charge ses widgets en
   différé : la page pose un cadre vide, puis chaque widget se peuple. Acceptable pour un graphe,
   absurde pour un voyant dont tout l'intérêt est d'être lu AVANT les chiffres qu'un défaut de
   chaîne fausse. Il coûte une lecture de la dernière ligne de `veille_passages`.
3. Une ligne dans `CLAUDE.md`, section « Décisions d'architecture » : *un widget du tableau de bord
   se déclare dans `Dashboard::getWidgets()` ; la découverte automatique ne suffit pas.* Plus la
   leçon générale : *tout écran nouveau a un test qui rend LA PAGE.*

**Écart assumé.** Le test demandé compare « Aucun passage reçu » à « Trésorerie — compte par
compte ». Le second n'est **pas** dans le HTML servi : `VitalBalancesWidget` reste différé, et c'est
volontaire — il fait plusieurs requêtes de solde. Le test compare donc la position du texte du
voyant à celle du **point de montage** du bandeau trésorerie, qui marque sa place sur la page. La
propriété vérifiée est exactement celle qui compte : le voyant est rendu, et il est avant.

`tests/Feature/Dashboard/VoyantSurLeTableauDeBordTest.php` · le test d'ordre existant
(`OrdreDeLectureCommunTest`) a été mis à jour : le voyant précède les treize widgets et porte un
`$sort` négatif, les treize autres continuent de porter `$sort = position`.

### 0.2 Les en-têtes de colonnes se touchaient

**Ce qui se passait.** À 1 440 px, sur `/veille` : « Source · Type · Prix**Acheteur** »,
« Estimation**Lecture** », « Drive**PMMP** ».

**La cause, et pourquoi elle n'est pas un défaut d'espacement.** Filament pose `white-space: nowrap`
sur les cellules d'en-tête. Quand la table est comprimée, un libellé plus large que sa colonne ne
passe pas à la ligne : il déborde, et vient buter dans le voisin. Ajouter du `gap` n'y aurait rien
changé — c'est la cellule qui est trop étroite.

**La solution, et pourquoi celle-là.** `App\Filament\Support\EnTetesLisibles::aerer()` fait deux
gestes, et il n'en faut pas un troisième :

- **une largeur minimale**, posée en **style en ligne** (`extraHeaderAttributes`). Pas en classe
  utilitaire : le CSS de Filament est **précompilé**, et une classe `min-w-[7rem]` inventée ici
  n'existerait dans aucune feuille livrée — elle ne ferait rien du tout, silencieusement ;
- **`wrapHeader()`**, qui autorise un libellé de plusieurs mots à passer à la ligne. Sur un mot
  unique (« Estimation »), il ne peut rien : c'est la largeur minimale qui tient.

Trois calibres : `LARGE` (11 rem) pour « Source · Type · Prix », `MOYEN` (7 rem) pour
« Estimation », `ETROIT` (5,5 rem) pour « Lecture », « Drive » et « PMMP ».

**Les quatre listes d'un coup.** Le correctif vit dans les **fabriques partagées** —
`ColonneSourceType::make()` et `LiensExternes::colonnes()` — donc la veille, les clôturés, les
étoilés et « AO à préparer » en héritent sans qu'on les touche. Seul « Estimation » est posé dans
chaque table, puisqu'il y est déclaré à chaque fois.

`tests/Feature/Veille/EnTetesDeColonnesTest.php` — le test **rend les quatre listes** et lit les
`<th>` de la page : c'est la largeur effectivement servie qui est vérifiée, pas la configuration
d'un objet.

### 0.3 `veille:purger --garder-preparation`

Le groupe « à préparer (soumission + en_preparation) » quitte la cible et rejoint les groupes
conservés, sous le libellé « conservées : à préparer (option --garder-preparation) ». Le tableau
avant/après le dit dans les deux modes, l'en-tête porte la mention, et le journal
`purge-veille-<date>.log` gagne une ligne « Portée : veille seulement — à préparer CONSERVÉES ».

L'option est lue **une seule fois** dans la commande et passée à `plan()`, `purger()`, au tableau
d'après et au journal : les quatre parlent forcément du même périmètre.

Quatre tests neufs, dont un qui vérifie que **sans** l'option le comportement de la v4.0.0 est
strictement inchangé. **`--confirmer` n'a jamais été exécuté.**

---

## Volet A — Le modèle

**`tasks`** (`2026_09_10_100000`) et **`task_updates`** (`2026_09_10_100010`), toutes deux sous
`hasTable` : rejouées, elles ne font rien plutôt que d'échouer.

Deux colonnes portent tout le module :

- **`employee_id`, obligatoire.** Un responsable, un seul, jamais zéro ni deux. « Affectation à
  plusieurs » est hors périmètre, et le restera.
- **`echeance`, obligatoire.** Sans elle, une tâche n'est ni en retard ni à l'heure : elle ne dit
  rien, et le premier signal du cockpit n'a rien à compter.

`project_id` nul = **fonctionnement interne**, exactement la même notion que la ligne sans marché de
la feuille de temps (`TimesheetService::SANS_MARCHE`).

**Le journal (`task_updates`) est la pièce maîtresse.** Seize gestes, dont `recu` : **l'affectation
elle-même en est un**. C'est ce qui permet au cockpit d'afficher « a reçu de Direction : … » sans
que quiconque ait rien saisi — et c'est ce qui rend le module tenable, puisque personne n'a de
compte rendu à rédiger.

`fait_le` (l'heure RÉELLE du geste) est distinct de `created_at` (l'heure de la saisie). Ils
diffèrent à la régularisation, et c'est tout l'intérêt de les séparer.

**Réglages** (§A3) : douze clés `pilotage.*` dans la graine du socle, plus
`ai.feature.pilotage_compte_rendu` à `false`. Aucune ne nomme une société : ce sont des durées et
des horaires.

**Permissions** (§A4) : `pilotage.view` et `pilotage.manage`, et **deux seulement**. Les gestes du
collaborateur passent par `auth()->user()->employee_id`, jamais par une permission — un droit
accordé par erreur ne doit pas permettre d'agir sur la tâche d'un autre, et un droit oublié ne doit
pas empêcher quelqu'un de dire ce qu'il fait.

---

## Volet B — Le service et les règles

`App\Services\Pilotage\TaskService` est **la seule table de vérité**. Rien n'arrive à une tâche sans
laisser une ligne de journal : un `->update(['statut' => ...])` posé dans un écran laisserait une
tâche dont plus personne ne peut dire ce qui lui est arrivé.

Les neuf règles gravées, toutes testées une par une :

| # | Règle | Où |
|---|---|---|
| 1 | Responsable et échéance obligatoires, pour les **trois** origines | `TaskService::creer()` |
| 2 | **Une seule tâche « en cours » par personne** — commencer B remet A « à reprendre », et le journal de A dit pourquoi | `interrompreLaTacheEnCours()` |
| 3 | Blocage **sans motif refusé** ; avec motif ⇒ une seule notification au chef et au gérant | `bloquer()` + `DestinatairesDuPilotage` |
| 4 | « Terminé » **propose** les heures, ne les impose jamais | `terminer()` + `heuresProposees()` |
| 5 | Trois origines, mêmes gestes : seule la ligne « par … » change | `PerimetreDAffectation::origine()` |
| 6 | Une tâche reçue ne se supprime ni ne se réassigne par celui qui la reçoit | `supprimer()`, `reassigner()` |
| 7 | Rien ne se ferme ni ne se déplace tout seul | aucune commande ne mute un statut |
| 8 | **Le chef ne valide jamais son propre livrable** | `validerLivrable()` |
| 9 | Le blocage **validé** suspend le chrono et ne compte pas comme retard | `Task::estEnRetard()` |

Trois précisions qui n'étaient pas évidentes et qui ont été tranchées ici :

- **Un blocage déclaré mais non validé compte comme un retard.** Sinon il suffirait de se déclarer
  bloqué pour n'être jamais en retard. C'est la validation du motif — un geste du chef ou de la
  direction — qui neutralise le retard.
- **Le chrono ne repart pas de zéro à chaque reprise.** Il compte depuis le PREMIER « Commencer »,
  moins le temps bloqué. Sinon une tâche reprise dix fois n'aurait duré que sa dernière minute.
- **Le CHEF d'un marché y travaille**, qu'il porte ou non une ligne dans `project_assignments` :
  cette table dit qui est affecté pour la ventilation des coûts, pas qui dirige. Sans cette
  précision, un chef ne pouvait pas se donner une tâche sur son propre marché.

### Le périmètre d'affectation (§B1) — aucune table « équipes » nouvelle

`PerimetreDAffectation` s'appuie sur ce qui existe : `projects.lead_employee_id`,
`project_assignments`, `employees.manager_id`. Le **manager hiérarchique** a les mêmes droits que le
chef, sur ses subordonnés et quel que soit le marché — sans lui, quelqu'un affecté à aucun marché
n'aurait personne pour lui assigner quoi que ce soit hormis la direction.

Les deux autorités portent la **même origine** (`chef_de_marche`) : les distinguer à l'affichage
n'apprendrait rien au collaborateur — « par Hamza », c'est Hamza — et créerait une quatrième origine
à tester partout.

Hors périmètre ⇒ refus avec un message qui dit **quoi faire** (« choisissez l'un des vôtres, ou
Fonctionnement interne »), jamais une notification à la direction : une tentative hors périmètre est
une erreur d'écran, pas un incident.

### Le pont heures (§B8)

`TimesheetService` gagne **une** méthode : `ajouterDesHeures()`. Elle AJOUTE une ligne au lieu de
remplacer la journée.

C'est le point technique le plus important du volet. `saveDay()` — l'écran de saisie — remplace la
journée entière ; c'est ce qu'il faut pour un formulaire, et c'est exactement ce qu'il ne faut pas
ici. Le pont vient d'un geste unitaire et ne connaît qu'une ligne : la sienne. S'il passait par
`saveDay()`, **une remarque écrite à la main sur une autre ligne disparaîtrait au passage**. Un test
le vérifie explicitement.

Deux tâches terminées le même jour sur le même marché font **une** ligne au total cumulé : l'index
unique `(employee, date, project)` ne permettrait pas deux lignes, et deux lignes seraient de toute
façon fausses.

---

## Volet C — Les écrans

### C1 — « Ma journée » v2

La page `MaJournee` **étendue**, jamais une seconde page : le collaborateur ouvre un écran le matin,
pas deux. Sept sections, dans l'ordre verrouillé par la maquette, et **aucune section vide n'est
affichée** :

1. la navigation du jour ;
2. **Maintenant** — la seule tâche en cours, son chrono, son origine, deux boutons pleine largeur,
   et un lien discret « Demander un report › » (tâche reçue) ou « Déplacer › » (à soi). **Absente**
   quand rien ne tourne ;
3. **Aujourd'hui** — le compteur « N à faire · N terminée(s) · N nouvelle(s) », les tâches dues et
   les retards (soulignés en rouge), ▶ Commencer à droite, le badge « nouvelle » + « Vu ✓ », les
   terminées barrées avec leurs heures pointées ;
4. **Ma semaine** — cinq colonnes lundi → vendredi ; **un jour vide se voit**, et c'est ce vide qui
   remonte au cockpit sous « planning vide » ;
5. **Mon équipe** — chefs de marché et managers seulement. **Absent** pour les autres ;
6. **Mes heures** — le bloc existant, avec le total du jour ;
7. **＋ Nouvelle tâche pour moi**, centré.

Les feuilles (Terminé, Bloqué, report, nouvelle tâche, assigner) sont des panneaux Livewire montés
du bas, cibles ≥ 48 px : sur un téléphone, une feuille qui monte du bas se ferme au pouce.

**Étanchéité.** La page ne cherche que dans les tâches du collaborateur (`where employee_id` +
`findOrFail`) : demander la tâche d'un autre rend « introuvable », pas un message d'erreur qui
confirmerait son existence.

### C2 — « Cockpit équipe »

**Groupe de navigation : « RH »** — documenté ici comme demandé. C'est là que vivent déjà les
collaborateurs (`EmployeeResource`), et le cockpit parle de personnes avant de parler de marchés :
un gérant qui cherche « où en est Untel » ouvre le menu où il trouve Untel. Le filtre par marché est
dans la page.

Bandeau (date + filtre marché + « Toutes les personnes ») → cinq tuiles cliquables qui filtrent la
grille → grille de cartes (3 colonnes, 2 en tablette, 1 en mobile), liseré = **pire** signal, trois
zones → panneau de détail pleine largeur → tableau « Par marché » → pied d'actions.

**Une personne sans compte utilisateur a quand même sa carte** : ses tâches lui viennent de la
direction, et c'est précisément d'elle qu'il faut se soucier. Une carte absente serait
indistinguable d'une personne sans souci.

Le tableau « Par marché » inclut **« Fonctionnement interne »** : ce n'est pas un marché, mais il
porte du travail, et son absence de la ligne le rendrait invisible.

Base vide : « Aucune tâche — planifiez la semaine », zéro erreur.

### C3 — Les deux fiches

Un bloc « Tâches » sur la fiche marché (ouvertes, retards, bloquées, livrables, ＋ Nouvelle tâche
pour la direction) et sur la fiche collaborateur (en cours, à venir, **journal des 7 derniers
jours**).

Les deux **lisent**. On ne « passe pas une tâche en terminé » depuis une fiche marché : ce geste
appartient au responsable, et le poser ici écrirait dans son journal une phrase qu'il n'a pas dite.

---

## Volet D — Notifications et planificateur

### Le point délicat : UN SEUL push à 08:30

Le socle envoyait déjà « pointe tes heures » à 08:30 ; le module ajouterait « Ta journée » à la même
minute. Deux vibrations pour la même personne, c'est une notification qu'on désactive — et le jour
où elle dirait quelque chose d'important, personne ne l'aurait plus.

**La fusion tient par une réservation, pas par un ordre d'exécution.** Les deux commandes réservent
la même clé (collaborateur + jour) via `Cache::add`, atomique sur tous les magasins du socle : celle
qui arrive en second **se tait pour cette personne**.

`pilotage:matin` est déclaré **avant** `timesheets:remind` dans `routes/console.php` — il porte donc
les deux sujets dans le cas normal (« Ta journée : 3 tâches · 1 nouvelle · heures d'hier à
pointer »). Si l'ordre s'inversait un jour — un `withoutOverlapping` qui décale, une commande
relancée à la main — le collaborateur recevrait **toujours exactement un message**, qui ne porterait
alors que les heures : le moindre mal, puisque c'est le sujet qui a une échéance.

Deux tests couvrent les deux ordres.

### Le reste

| Quand | Quoi | Silence si… |
|---|---|---|
| immédiat | tâche **reçue** ⇒ push au collaborateur | tâche à soi, ou terminée |
| 08:30 | « Ta journée », un seul push | planning vide **et** rien à pointer |
| 17:30 | « Une tâche est encore en cours — Terminé ou Bloqué ? » | aucune tâche en cours |
| 18:00 | récap **collaborateur** | journée vide (congé, terrain) |
| 18:00 | récap **chef** — blocages, retards, reports en attente, signaux mensuels | rien à dire |
| 18:00 | récap **gérant** — retards, blocages, silences, non vues > 24 h | **aucune exception** |
| immédiat | blocage ⇒ chef **et** gérant, une seule fois chacun | — |
| immédiat | demande de report ⇒ l'assignant | — |
| vendredi 17:00 | rapport hebdo + lien à la direction | — |

Le dédoublonnage des destinataires vit à un seul endroit (`DestinatairesDuPilotage`) : sur un petit
marché, le chef EST parfois le gérant, et sans cela il recevrait deux fois le même message.

**Les deux signaux mensuels** (§B5, §B9) — trop de reports accordés, trop de régularisations —
arrivent dans le récap du chef et nulle part ailleurs. Le message dit lui-même « Un signal, pas une
pénalité » : la régularisation reste un droit, et un report accordé ne coûte rien.

Sans abonnement push, le canal se tait de lui-même et le courriel part quand même : rien n'échoue.
Testé.

---

## Volet E — Le rapport hebdo

Trois parties : **par personne**, **par marché**, **signaux à trancher**. Généré vendredi 17 h, ou à
la demande depuis le cockpit ; stocké sur le disque privé comme les bulletins ; les douze derniers
listés dans le pied du cockpit.

Il suit les deux patrons du socle sans en inventer un troisième : `FabriqueDuRapportDeMarche` pour
le rendu dompdf — instance fraîche par rendu, limite mémoire remise exactement où elle était,
**jamais `isPhpEnabled`** — et `formation:rapport-hebdo` pour le stockage.

**Aucun appel IA automatique.** Le bouton « Rédiger le compte rendu » n'apparaît que si
`ai.feature.pilotage_compte_rendu` est allumé, et il est **éteint par défaut**. Le prompt ne
transmet que des compteurs et des titres de tâches — jamais un montant, jamais une donnée de paie —
et interdit expressément d'inventer un fait. Un test vérifie que `pilotage:rapport-hebdo` ne laisse
aucune ligne dans `ai_calls`.

---

## Volet F — Tests

**123 tests neufs**, répartis en dix fichiers :

| Fichier | Ce qu'il prouve |
|---|---|
| `Pilotage/TaskServiceTest.php` | les neuf règles gravées, une par une (38 tests) |
| `Pilotage/SignauxTest.php` | les cinq signaux sur un jeu témoin, au compte exact |
| `Pilotage/MaJourneeV2Test.php` | l'écran du téléphone RENDU : sections, ordre, gestes, absences |
| `Pilotage/CockpitEquipeTest.php` | le cockpit et les deux fiches RENDUS, base vide comprise |
| `Pilotage/NotificationsDuPilotageTest.php` | 08:30 unique dans les deux ordres, silences, planificateur |
| `Pilotage/ReglagesEtRapportTest.php` | la page de réglages, l'audit, le PDF, l'IA éteinte |
| `Pilotage/SocleDuPilotageTest.php` | aucune société en dur, migrations rejouées, permissions |
| `Dashboard/VoyantSurLeTableauDeBordTest.php` | §0.1 — le voyant sur LA PAGE |
| `Veille/EnTetesDeColonnesTest.php` | §0.2 — les largeurs, sur les quatre listes rendues |
| `Veille/PurgeVeilleTest.php` | §0.3 — quatre tests ajoutés à l'existant |

Le jeu témoin des signaux mérite un mot : il porte 1 retard, 1 bloqué, 1 planning vide, 1 silence et
2 terminées, et il a fallu **ajouter une tâche prévue à la personne active** pour n'avoir qu'un seul
planning vide. Ce n'était pas un défaut du calcul : quelqu'un qui a tout fini et n'a rien devant lui
A un planning vide, et le signal dit exactement ce qu'il dit — il n'y a rien devant elle. C'est une
question à poser, pas un reproche.

---

## Écarts assumés, et leur raison

1. **Le voyant : « Trésorerie — compte par compte » n'est pas dans le HTML servi.** Le bandeau
   trésorerie reste différé (plusieurs requêtes de solde), et le rendre synchrone pour un test
   ralentirait la page réelle. Le test compare la position du voyant à celle du **point de montage**
   du bandeau, qui marque sa place. Voir §0.1.
2. **La page de réglages ne déplace pas le cron.** Les heures `pilotage.notif.*` sont lues par les
   commandes ; le rendez-vous du planificateur vit dans `routes/console.php`, parce que le cron du
   serveur n'appelle que `schedule:run`. Un changement d'heure ne prend effet qu'au prochain
   déploiement — c'est écrit **sur l'écran**, pas seulement dans ce rapport.
3. **`ProjectTasksRelationManager` ne modifie aucun statut.** Le prompt demande « un onglet ou bloc
   Tâches (ouvertes, retards, bloquées, livrables) avec ＋ Nouvelle tâche » : c'est ce qui est
   livré. Passer une tâche en « terminé » depuis une fiche marché écrirait dans le journal de
   quelqu'un une phrase qu'il n'a pas dite.
4. **« Rédiger le compte rendu » n'a pas de test d'appel réel.** Le service est testé sur sa
   disponibilité (éteinte par défaut) et sur l'absence d'appel automatique. Tester la rédaction
   elle-même supposerait de simuler l'API Anthropic, ce que le socle ne fait nulle part.
5. **Le hors ligne et l'arabe en miroir** sont explicitement reportés (avis §4). « Ma journée »
   affiche « pas de réseau, réessaie » et rien de plus ; la régularisation couvre le terrain.

---

## Comment vérifier à l'écran

### En tant que **collaborateur** — `/espace/ma-journee`

1. Sans aucune tâche : la page s'ouvre, « Maintenant » **n'existe pas**, « Aujourd'hui » dit
   « Aucune tâche aujourd'hui », « Ma semaine » montre cinq jours dont chacun dit « rien de prévu »,
   le bloc « Mes heures » est intact, et « Mon équipe » **est absent**.
2. Touchez **＋ Nouvelle tâche pour moi** → titre, marché (dont « Fonctionnement interne »),
   échéance, prévu le → Enregistrer. Elle apparaît dans « Aujourd'hui », **sans badge « nouvelle »**
   (c'est la vôtre) et sans ligne « par … ». Personne n'a été notifié.
3. Touchez **▶ Commencer** : la section **Maintenant** apparaît, avec le chrono et deux boutons
   pleine largeur. Commencez-en une seconde : la première passe « à reprendre », et son bouton dit
   maintenant « ▶ Reprendre ».
4. Touchez **Terminé** : la feuille propose l'écart arrondi au quart d'heure. **Pointer** crée une
   ligne dans le bloc « Mes heures » du bas, sur le bon marché. **Sans heures** n'en crée aucune.
5. Touchez **Bloqué** sans choisir de motif : un message rouge, et rien ne bouge. Choisissez un
   motif → la tâche passe bloquée, votre chef et la gérance reçoivent un message.

### En tant que **chef de marché** (ou manager) — même écran

6. Le bloc **Mon équipe** apparaît, sous « Ma semaine » : chaque collaborateur avec son
   « maintenant » (ou « rien en cours · dernier geste : … ») et le nombre de tâches à venir.
7. **＋ Assigner une tâche** : la liste des responsables ne contient que **votre** équipe, et la
   liste des marchés que les vôtres. Assignez → chez le collaborateur, la tâche est là, marquée
   **nouvelle**, avec « par <votre nom> ».
8. Essayez d'assigner à quelqu'un hors de votre équipe (par l'URL ou en changeant le select) :
   refus, avec un message qui dit pourquoi.

### En tant que **direction** — `/cockpit-equipe` (menu **RH**)

9. Sur une base sans tâches : « Aucune tâche — planifiez la semaine », les cinq tuiles à zéro, zéro
   erreur.
10. Avec des tâches : une carte par personne, liseré rouge pour un retard, orange pour un blocage.
    Cliquez une **tuile** → la grille ne garde que les personnes concernées ; recliquez → elle se
    libère. Cliquez une **carte** → le panneau de détail s'ouvre en pleine largeur : journal du
    jour, semaine, charge.
11. **＋ Nouvelle tâche** ouvre un formulaire **en ligne**, dans la page. Sans responsable :
    refus en clair, le panneau reste ouvert, rien n'est créé.
12. **Planifier la semaine** liste les tâches ouvertes avec un sélecteur de jour par tâche ;
    Enregistrer les repose toutes d'un coup.
13. **Rapport hebdo (PDF)** génère et télécharge le PDF de la semaine ; le pied de page liste les
    douze derniers.
14. **Copier pour WhatsApp** met le récap du jour dans le presse-papiers (texte brut).
15. **« Primes du mois » n'existe pas** — et ne doit pas exister avant la v4.2.0.

### Les trois correctifs

16. `/` en admin : le **voyant de la veille** est la première chose sous le titre, avant
    « Trésorerie — compte par compte ».
17. `/veille` à 1 440 px : plus aucun en-tête ne touche son voisin. Idem sur « AO clôturés »,
    « ⭐ AO étoile » et « AO à préparer ».
18. `php artisan veille:purger --garder-preparation` (**sans `--confirmer`**) : le tableau range
    « à préparer » dans les groupes **conservés**, et l'en-tête porte la mention de l'option.

### Réglages — `/reglages-pilotage`

19. Les deux seuils de signaux, les cinq heures, la fenêtre de régularisation, les deux options.
    Changez le seuil de silence : le compteur de la tuile ⏱ du cockpit suit immédiatement.
20. Une ligne apparaît dans l'audit (`pilotage_reglages_modifies`), avec votre identité.
