Commit Graph
2 Commits
Author SHA1 Message Date
Claude b243df4772 Étendre la préservation des formulaires, et corriger la publication
Suite de PR #31. `PersistentForm` couvre désormais les formulaires où la
saisie compte : ajout d'un salarié, d'un établissement, d'une équipe, d'une
entrée de registre, demande d'absence, heures réelles, correspondances de
paie, réglages d'envoi, conservation, dépôt de pièce.

Le composant gère `select`, `textarea`, cases et boutons radio, et reçoit
`resetAfter` : passé l'état retourné par l'action en cas de succès, le
formulaire revient aux valeurs rendues par le serveur — vide pour un ajout,
la valeur enregistrée pour un champ d'édition. C'est ce qui rend visible une
normalisation faite côté serveur.

Les champs cachés sont exclus. Ils ne portent jamais une saisie mais l'état
de l'application, et les rétablir écrase ce que le serveur vient de
renvoyer. Le cas s'est produit sur `expectedVersion`, le verrou optimiste
d'une semaine de planning.

En éprouvant cela, un défaut plus grave est apparu, antérieur et sans
rapport avec la préservation : le formulaire de publication recevait tantôt
l'action de publication, tantôt celle de dépublication selon l'état, et
cessait de suivre le changement. Lecture de l'en-tête `Next-Action` sur
trois envois consécutifs :

    1er envoi   60b8097e  (dépublier)  → brouillon
    2e  envoi   6012d40f  (publier)    → publiée
    3e  envoi   6012d40f  (publier)    → publiée

Le bouton affichait « Dépublier » et republiait, sans message d'erreur
puisque l'action réellement exécutée réussissait. Une seule action reçoit
maintenant l'intention, portée par le bouton déclencheur — un champ caché
serait remis à sa valeur d'origine par la réinitialisation que React
applique après chaque action, un bouton ne l'est jamais.

Le test du planning fait désormais deux allers-retours dans le même
chargement : le premier envoi d'une page emploie toujours la bonne action,
si bien qu'un seul aller-retour laissait passer le défaut selon l'état où
la semaine avait été trouvée. Vérifié : le test échoue sur l'ancien
câblage, passe sur le nouveau.

450 tests unitaires et d'intégration, 77 de bout en bout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-09 19:40:41 +00:00
Claude 727c05407a Écran de première installation
Les migrations posent le schéma et rien d'autre : une instance neuve n'a
aucun compte et aucun utilisateur, donc personne ne peut se connecter. Le
seed n'y remédie pas — il installe une démonstration et refuse de tourner
en production, à raison.

La première visite est désormais redirigée vers `/installation` : nom de
l'entreprise, premier établissement avec son fuseau, et compte
administrateur. L'écran crée le compte, le catalogue des capacités, les
cinq rôles fournis, le propriétaire et son périmètre, puis ouvre la session
par le chemin ordinaire — le rôle propriétaire exige aussitôt un second
facteur, comme il se doit.

Il ne se rouvre pas. Une table `Installation` d'une seule ligne, contrainte
en base et protégée par un trigger append-only, marque l'instance. Elle est
délibérément hors RLS, et c'est sa raison d'être : la politique d'`Account`
ne laisse voir que le compte courant, si bien qu'une instance installée
paraîtrait vierge à qui n'a pas de session — et la création d'un
propriétaire se rouvrirait à tout venant. Le recensement des politiques
porte l'exception, affirmée dans les deux sens.

Deux défauts trouvés en éprouvant l'écran sur une base réellement vierge :

`INSERT ... RETURNING` sur `Account` était refusé. L'insertion est permise,
mais la relecture de la ligne écrite passe par la politique de lecture, qui
exige un compte courant. L'identifiant est donc tiré côté application et
annoncé avant la création — la règle de partout, appliquée à la
transaction qui crée le compte.

Et un défaut qui dépassait cet écran : React 19 vide les champs non
contrôlés dès qu'une action se termine, refus compris. Les champs vidés
portant `required`, le clic suivant était arrêté par la validation du
navigateur avant d'émettre un `submit` — le formulaire paraissait mort. Le
formulaire de connexion en souffrait aussi ; la suite l'avait manqué parce
qu'aucun test ne soumettait deux fois de suite. `PersistentForm`
photographie la saisie à l'envoi et la rétablit, mots de passe exclus.

Au passage, `Field` rattache son indication par `aria-describedby` : placée
dans le `<label>`, elle entrait dans le nom accessible du champ.

Éprouvé sur une base vierge, image de production, rôle NOSUPERUSER
NOBYPASSRLS : redirection, trois refus motivés, installation, second
facteur exigé, écran refermé pour le propriétaire comme pour un visiteur.
450 tests unitaires et d'intégration, 77 tests de bout en bout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
2026-08-09 19:10:25 +00:00