Détection de conflits #5
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
priority
high
priority
low
priority
medium
question
step
backlog
step
delivered
step
done
step
in-progress
step
todo
wontfix
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
maxime/OrganisateurFamilial#5
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Description
Deux membres qui modifient le même élément au même moment : le dernier écrit écrase le premier, sans que personne ne le sache. Le polling (3 s sur la liste ouverte) réduit la fenêtre, il ne la ferme pas — et sur l'écran de modification d'un élément, ouvert plusieurs minutes, elle reste grande ouverte.
Ce que ça débloque
Deux chantiers attendent explicitement cette brique :
L'existant sur lequel s'appuyer
revision, incrémenté à chaque mutation et comparé par le polling. C'est la moitié d'un versionnage optimiste : il manque de l'envoyer avec la mutation et de refuser celle-ci s'il a bougé.Ce qu'il faut trancher
revisionest au niveau de la liste : deux personnes cochant deux articles différents de la même liste entreraient en conflit pour rien. Un compteur par élément est plus juste et plus coûteux.Plan d'action
Détail : conception technique.
basefacultatif dansPUT /api/items/:id, comparaison par champ dans la transaction d'écriture, refus 409Conflict(§1, §2)Recette
make lint typecheck test,make build-frontend(bundle IIFE),make l10n-check,cd e2e && npm test/code-review, retours en commentaireArchitecture, renvois depuis D35 et D36, la fiche d'élément dansUX; paragraphe « What is left » d'AGENTS.mdPlan de MEP
info.xml: une nouvelle image suffitbase, donc sans condition, comme aujourd'hui : pas de coupure, la protection arrive au rechargementConception technique
Choix arrêtés avec Maxime (2026-09-22)
1. Le contrat de
PUT /api/items/:idLe corps garde ses champs à plat et gagne un objet facultatif
base: pour chaque champ modifié, la valeur qu'avait la fiche à l'ouverture.baseest conditionnel : refusé si sa valeur actuelle diffère debaseet de la nouvelle valeur. Deux membres qui font le même changement ne sont pas en conflit.bases'écrit sans condition. C'est toujours le cas dechecked—basen'accepte pas cette clé : cocher envoie un état cible, idempotent, jamais refusé. C'est aussi le cas d'une requête venue d'un bundle antérieur resté ouvert : elle écrit comme aujourd'hui.baseest.strict()et reprend les bornes delist-schema.ts.Conflict extends LocalizedError, message générique traduit (« This item was changed while you were editing it. »). Tout ou rien : un seul champ en conflit refuse la modification entière — sinon la fiche serait à moitié enregistrée, et le bandeau devrait dire quelle moitié.write()relit déjà le snapshot (resync()) ; c'est de ce snapshot autoritatif que la fiche déduit quoi afficher. Aucun branchement sur le statut HTTP (D26).info.xml:PUTsur^/api/.*est déjà déclaré.2. La comparaison est atomique avec l'écriture
patchItemlit l'élément avant l'awaitdefamilyForUser: une autre requête peut écrire entre les deux, et comparer à cetitem-là laisserait passer exactement le conflit qu'on cherche. La comparaison se fait donc dans la transaction dedb/list-items.ts(updateItemou une variante) : relire la ligne, comparer, écrire,touchList, sans rendre la main — better-sqlite3 est synchrone, la transaction est réellement atomique. La fonction renvoie les champs en conflit, le service lève le 409 ; rien n'est alors projeté ni notifié.C'est ce morceau que l'import qui met à jour (#2) et la file hors ligne (#15) réutiliseront.
Égalité : sur les valeurs telles que le schéma les produit (chaînes trimées, vide →
null, priorité entière).3. Réordonnancement : tolérer et normaliser
reorderItemsréécrit toutes les positions de la liste dans sa transaction : d'abord les ids reçus qui existent encore, doublons retirés, dans l'ordre reçu ; puis les éléments absents de la requête — insérés entre-temps — dans leur ordre relatif actuel. Les ids disparus sont ignorés. Plus de positions en double, qu'un ordre envoyé sur un état périmé peut produire aujourd'hui. Pas de changement d'API.4. La fiche (
ItemDialog/ListDetail)resyncdu store passé, elle compare sa base à l'élément relu :constants/lists.ts) et bouton « Le recréer » :addItemavec toute la saisie, en fin de liste. C'est un nouvel élément ; la suppression de l'autre membre n'est pas annulée.store.errors'affiche aujourd'hui dansLists.vue, derrière la fenêtre modale : la fiche a besoin de sa propre zone de message, conflit ou pas.t(…), puismake l10n-build.5. Ce qui ne change pas
6. Tests
db/list-items.test.ts: champ inchangé → écrit ; changé → refusé sans rien écrire,revisionintacte ; même valeur des deux côtés → écrit ;checkedjamais conditionnel ; sansbase→ sans condition.reorderItems: ids inconnus, doublons, éléments non listés placés à la suite, positions uniques.services/list.test.ts: 409 levé, ni projection ni notification.base(clé inconnue refusée,checkedrefusé).lists.spec.ts: fiche ouverte, échéance changée par l'API à travers le proxy, enregistrement → bandeau, saisie conservée, second enregistrement écrase ; « Reprendre la leur » ; suppression par l'API → « Le recréer ». Chacun doit échouer si le bandeau disparaît.Recette — contrôles et revue (2026-09-22)
Branche
feat/item-conflicts, 5 commits surmain.Contrôles
make lint typecheck test: 0 erreur (+3 avertissements de taille de fichier), 497 + 402 tests unitaires.make build-frontend: bundle IIFE ;make l10n-check: 429 sources, toutes traduites.families.spec« renaming a family » (la famille n'apparaît pas à sa création), passe au second passage, sans rapport avec les listes : instabilité de l'instance.item-conflicts.spec.ts(6 tests) +lists.spec.ts: 23/23 après les corrections ; le 409 et son message français arrivent intacts à travers le proxy AppAPI./code-review— deux retours, corrigés dans1df6f5f:Le test e2e écrit pour le premier en a trouvé un troisième : la liste ouverte supprimée, la fiche retombait sur le vocabulaire « courses » et perdait la saisie. La fiche garde désormais le type avec lequel elle s'est ouverte.
Wiki : D40 (Architecture), renvois dans D35 et D36, règle de la fiche dans UX.
AGENTS.md: invariant « une fiche envoie ses changements avec leur base ».