front : le tableau de bord affiche l'heure du poste, et le dit #254

Merged
gabriel merged 2 commits from lenaic/heure-locale into develop 2026-09-09 19:59:28 +00:00
Owner

Ce que ça change

Toutes les heures de données s'affichaient en UTC. Le tableau de bord montre
désormais l'heure du poste, et l'écrit à côté de « dernière relève ».

Pas de ticket : le défaut a été vu à l'écran ce soir, il n'était pas au
backlog. À rattacher si vous en ouvrez un.

Preuve

Le défaut, relevé ce soir sur la production, écran ouvert à 20 h 10 heure
de Paris :

dernière relève   17:59      <- 19:59 ici, soit 11 minutes
dernier bâton     16 h       <- l'heure de 18 h

La conclusion naturelle est « la chaîne a deux heures de retard ». Elle a été
tirée en interne, sur une chaîne qui venait justement de passer aux dix
minutes. Devant un jury, elle serait tirée aussi, et personne ne serait là
pour détromper.

510 cas front passés (44 fichiers)
construction Vite : OK

Relecture

  • Un pair a relu et laissé un commentaire, même court
  • Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas

Où regarder en priorité

1. Une exception assumée : formaterJour reste en UTC. Le journal de
collecte nomme une journée, pas un instant. « 2026-09-01 » est une date nue
que le navigateur lit à minuit UTC ; la convertir ferait afficher la veille à
l'ouest de Greenwich, soit un journal décalé d'un jour, en silence. Un cas
d'essai garde l'exception, sans quoi le prochain passage l'effacerait par souci
de cohérence.

2. Le fuseau des cas d'essai est fixé à Europe/Paris dans
vitest.config.js. Un lancement en UTC passerait sans rien prouver — les deux
formes coïncident, et la conversion ne serait jamais exercée. Posé dans la
configuration et non dans le script npm : TZ=… npm run ne marche pas sur un
poste Windows, et la moitié de l'équipe en a un.

3. Deux cas ajoutés pour ce que le décalage change vraiment. Une série qui
bascule de jour à 23:30 UTC — l'axe portait deux « lun. 31 » à la suite — et
l'heure d'hiver, où l'écart tombe à une heure. Un décalage figé à deux heures
serait faux la moitié de l'année, et personne ne le verrait avant décembre.

4. Le fuseau affiché est lu à l'exécution, pas écrit en dur : « heure de
Paris » figé mentirait à qui ouvrirait le tableau de bord ailleurs. Il est
nommé une seule fois, à côté de « dernière relève », la seule valeur de
l'en-tête qu'un lecteur compare à sa montre. Les « (UTC) » des en-têtes de
colonne disparaissent plutôt que de devenir « (heure locale) » répété.

5. datetime des balises <time> reste en UTC. C'est la donnée machine,
elle n'a pas à suivre l'affichage. Un cas d'essai le garde.

Ce qui n'est pas dans cette PR

Le retard structurel de la courbe, qui est autre chose et n'a rien d'un défaut :
une heure ne peut pas être résumée avant d'être finie, donc le seau de H est
visible à (H+1):10 depuis le #246. Soixante-dix minutes au pire, indépendant
de la cadence. Si on veut l'heure en cours à l'écran, il faut materialized_only = false, et border d'abord la recopie du réalisé du #117 — sans quoi elle
figerait une heure partielle comme définitive.

## Ce que ça change Toutes les heures de données s'affichaient en UTC. Le tableau de bord montre désormais l'heure du poste, et l'écrit à côté de « dernière relève ». Pas de ticket : le défaut a été vu à l'écran ce soir, il n'était pas au backlog. À rattacher si vous en ouvrez un. ## Preuve Le défaut, relevé ce soir sur la production, écran ouvert à **20 h 10** heure de Paris : ``` dernière relève 17:59 <- 19:59 ici, soit 11 minutes dernier bâton 16 h <- l'heure de 18 h ``` La conclusion naturelle est « la chaîne a deux heures de retard ». Elle a été tirée en interne, sur une chaîne qui venait justement de passer aux dix minutes. Devant un jury, elle serait tirée aussi, et personne ne serait là pour détromper. ``` 510 cas front passés (44 fichiers) construction Vite : OK ``` ## Relecture - [ ] Un pair a relu et laissé un commentaire, même court - [ ] Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas ## Où regarder en priorité **1. Une exception assumée : `formaterJour` reste en UTC.** Le journal de collecte nomme une **journée**, pas un instant. « 2026-09-01 » est une date nue que le navigateur lit à minuit UTC ; la convertir ferait afficher la veille à l'ouest de Greenwich, soit un journal décalé d'un jour, en silence. Un cas d'essai garde l'exception, sans quoi le prochain passage l'effacerait par souci de cohérence. **2. Le fuseau des cas d'essai est fixé à `Europe/Paris`** dans `vitest.config.js`. Un lancement en UTC passerait sans rien prouver — les deux formes coïncident, et la conversion ne serait jamais exercée. Posé dans la configuration et non dans le script npm : `TZ=… npm run` ne marche pas sur un poste Windows, et la moitié de l'équipe en a un. **3. Deux cas ajoutés pour ce que le décalage change vraiment.** Une série qui bascule de jour à 23:30 UTC — l'axe portait deux « lun. 31 » à la suite — et l'heure d'hiver, où l'écart tombe à une heure. Un décalage figé à deux heures serait faux la moitié de l'année, et personne ne le verrait avant décembre. **4. Le fuseau affiché est lu à l'exécution**, pas écrit en dur : « heure de Paris » figé mentirait à qui ouvrirait le tableau de bord ailleurs. Il est nommé une seule fois, à côté de « dernière relève », la seule valeur de l'en-tête qu'un lecteur compare à sa montre. Les « (UTC) » des en-têtes de colonne disparaissent plutôt que de devenir « (heure locale) » répété. **5. `datetime` des balises `<time>` reste en UTC.** C'est la donnée machine, elle n'a pas à suivre l'affichage. Un cas d'essai le garde. ## Ce qui n'est pas dans cette PR Le retard structurel de la courbe, qui est autre chose et n'a rien d'un défaut : une heure ne peut pas être résumée avant d'être finie, donc le seau de `H` est visible à `(H+1):10` depuis le #246. Soixante-dix minutes au pire, indépendant de la cadence. Si on veut l'heure en cours à l'écran, il faut `materialized_only = false`, et border d'abord la recopie du réalisé du #117 — sans quoi elle figerait une heure partielle comme définitive.
front : le tableau de bord affiche l'heure du poste, et le dit
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 9s
Intégration / Terraform — format, validité et lint (pull_request) Successful in 16s
Intégration / Checkov — audit de la configuration (pull_request) Successful in 17s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 45s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 17s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m50s
a6ea7372fe
Toutes les heures de données sortaient en UTC. Un écran ouvert à Paris à 20 h 10
affichait « dernière relève 17:59 » sur une donnée vieille de onze minutes, et
son dernier bâton à « 16 h » sur l'heure de 18 h. La conclusion naturelle est
que la chaîne a deux heures de retard. Elle a été tirée en interne ce soir, sur
une chaîne qui venait justement de passer aux dix minutes. Devant un jury, elle
serait tirée aussi et personne ne serait là pour détromper.

Les instants passent donc dans le fuseau du poste : étiquettes d'axe, en-tête
d'infobulle, heure de relève, colonnes des tableaux. Ce qui ne change pas :
l'heure reste ABSOLUE, jamais « il y a X minutes » — un relatif calculé contre
l'horloge du poste grandit sans fin et ne dit rien de la fraîcheur.

LE FUSEAU EST ÉCRIT À L'ÉCRAN, à côté de « dernière relève », qui est la seule
valeur de l'en-tête qu'un lecteur compare à sa propre montre. Il est lu à
l'exécution et non figé : écrire « heure de Paris » en dur mentirait à qui
ouvrirait le tableau ailleurs.

UNE EXCEPTION, ET ELLE EST DÉLIBÉRÉE. `formaterJour` reste en UTC. Le journal
de collecte nomme une JOURNÉE, pas un instant : « 2026-09-01 » est une date
nue, que le navigateur lit à minuit UTC. La convertir ferait afficher la veille
à l'ouest de Greenwich, soit un journal décalé d'un jour, en silence. Un cas
d'essai garde cette exception, sans quoi le prochain passage l'effacerait par
souci de cohérence.

Les « (UTC) » des en-têtes de tableau disparaissent : la colonne porte l'heure
du poste, et le fuseau se nomme une fois, pas à chaque colonne.

LE FUSEAU DES CAS D'ESSAI EST FIXÉ à « Europe/Paris » dans `vitest.config.js`.
Un lancement en UTC passerait sans rien prouver, les deux formes coïncidant :
la conversion qu'on vient d'introduire ne serait jamais exercée. Posé dans la
configuration et non dans le script npm, parce que `TZ=… npm run` ne fonctionne
pas sur un poste Windows et que la moitié de l'équipe en a un.

Deux cas ajoutés pour ce que le décalage change vraiment : une série qui
bascule de jour à 23:30 UTC — l'axe portait deux « lun. 31 » à la suite — et
l'heure d'hiver, où l'écart tombe à une heure. Un décalage figé à deux heures
serait faux la moitié de l'année, et personne ne le verrait avant décembre.

Éprouvé : 510 cas front au vert, construction Vite au vert.
lenaic requested review from gabriel 2026-09-09 18:25:41 +00:00
front : l'import dupliqué de formaterJour ne tenait que par esbuild
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 9s
Intégration / Terraform — format, validité et lint (pull_request) Successful in 17s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 45s
Intégration / Checkov — audit de la configuration (pull_request) Successful in 17s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 17s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 6m16s
5f5f294564
`formaterJour` était importé deux fois dans le même bloc d'import. C'est
une SyntaxError au sens de la spec ESM — `node --check` refuse le fichier
avec « Identifier 'formaterJour' has already been declared ».

Les 510 cas passaient quand même parce qu'esbuild, qui transforme le
fichier avant Vitest, déduplique les spécificateurs en silence. Rien ne
cassait aujourd'hui ; le premier outil qui parse à la lettre — Rollup en
natif, un lint d'import, une bascule TypeScript — aurait fait tomber le
fichier entier, et avec lui les vingt-sept cas qu'il porte.
gabriel approved these changes 2026-09-09 19:54:11 +00:00
gabriel left a comment

Relu, et rejoué sur la branche : 510 cas passés (44 fichiers), vite build OK.

J'ai vérifié la conversion là où elle pouvait mordre, pas seulement à l'écran :

  • l'API sort bien des instants aware sérialisés en Z, donc new Date() parse en UTC et getHours() rend l'heure du poste — pas de piège « naïf reparsé en local » ;
  • les seaux sont alignés UTC (date_trunc('hour'|'day', …), dashboard/repository.py:541,586) : à Paris l'écart est entier, les étiquettes retombent sur des heures pleines, été comme hiver — et la bascule est couverte par un cas ;
  • les quatorze fichiers appelants n'utilisent ces formateurs que pour afficher : aucune agrégation ni comparaison indexée sur l'étiquette, donc rien à casser en dessous ;
  • plus aucun getUTC* d'affichage ailleurs dans src/, pas d'écran mi-UTC mi-local ;
  • le datetime des <time> reste en UTC, ce qui est la bonne règle.

Les cinq points de « où regarder en priorité » tiennent. L'exception formaterJour est la bonne, et le cas d'essai qui la garde est ce qui empêchera le prochain passage de l'effacer par souci de cohérence.

J'ai poussé une ligne sur la branche (5f5f294) : formaterJour était importé deux fois dans le même bloc. Les cas passaient parce qu'esbuild déduplique en silence avant Vitest, mais node --check refuse le fichier — c'est une SyntaxError ESM. Le premier outil qui parse à la lettre aurait fait tomber les vingt-sept cas du fichier d'un coup.

Deux remarques que je ne bloque pas :

  1. Le fuseau n'est écrit que sur la fiche site. TableauSites, ListeAlertes, TracabiliteDetail et SitesATraiter affichent désormais l'heure locale sans le dire. La PR l'assume, et comme les heures sont justes plus personne ne conclut faux — mais l'écran d'accueil, celui que le jury ouvre en premier, ne porte pas la mention.
  2. env: { TZ: 'Europe/Paris' } repose sur la prise en compte d'un TZ modifié après le démarrage de Node. Validé ici sur macOS ; si ça ne prend pas sur un poste Windows, ce sont une vingtaine de cas rouges là-bas — pas en CI, pas en prod. À confirmer par qui en a un.

Bon pour la mise en prod.

Relu, et rejoué sur la branche : 510 cas passés (44 fichiers), `vite build` OK. J'ai vérifié la conversion là où elle pouvait mordre, pas seulement à l'écran : - l'API sort bien des instants aware sérialisés en `Z`, donc `new Date()` parse en UTC et `getHours()` rend l'heure du poste — pas de piège « naïf reparsé en local » ; - les seaux sont alignés UTC (`date_trunc('hour'|'day', …)`, `dashboard/repository.py:541,586`) : à Paris l'écart est entier, les étiquettes retombent sur des heures pleines, été comme hiver — et la bascule est couverte par un cas ; - les quatorze fichiers appelants n'utilisent ces formateurs que pour afficher : aucune agrégation ni comparaison indexée sur l'étiquette, donc rien à casser en dessous ; - plus aucun `getUTC*` d'affichage ailleurs dans `src/`, pas d'écran mi-UTC mi-local ; - le `datetime` des `<time>` reste en UTC, ce qui est la bonne règle. Les cinq points de « où regarder en priorité » tiennent. L'exception `formaterJour` est la bonne, et le cas d'essai qui la garde est ce qui empêchera le prochain passage de l'effacer par souci de cohérence. J'ai poussé une ligne sur la branche (5f5f294) : `formaterJour` était importé deux fois dans le même bloc. Les cas passaient parce qu'esbuild déduplique en silence avant Vitest, mais `node --check` refuse le fichier — c'est une SyntaxError ESM. Le premier outil qui parse à la lettre aurait fait tomber les vingt-sept cas du fichier d'un coup. Deux remarques que je ne bloque pas : 1. Le fuseau n'est écrit que sur la fiche site. `TableauSites`, `ListeAlertes`, `TracabiliteDetail` et `SitesATraiter` affichent désormais l'heure locale sans le dire. La PR l'assume, et comme les heures sont justes plus personne ne conclut faux — mais l'écran d'accueil, celui que le jury ouvre en premier, ne porte pas la mention. 2. `env: { TZ: 'Europe/Paris' }` repose sur la prise en compte d'un `TZ` modifié après le démarrage de Node. Validé ici sur macOS ; si ça ne prend pas sur un poste Windows, ce sont une vingtaine de cas rouges là-bas — pas en CI, pas en prod. À confirmer par qui en a un. Bon pour la mise en prod.
gabriel merged commit c1d698bd04 into develop 2026-09-09 19:59:28 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!254
No description provided.