feat: le seuil de puce devient un réglage, et les libellés suivent la configuration - #50
Merged
Conversation
… une constante MIN_PRODUCTS_FOR_CHIP est une constante du front dont le commentaire dit « pas un réglage (ADR-025) ». ADR-024 justifie qu'un seuil EXISTE — en 2022 « Autres » menait à un seul produit, un quart de barre pour une tuile — mais aucune mesure ne dit pourquoi cinq. C'est ce qui le rend discutable, et ADR-057 a déjà tranché le même genre de question en accordant un réglage à `ui.grid_columns`. La conception tient en un champ : `ui.min_products_for_chip`, défaut 5, plancher 1, pas de plafond, appliqué à chaque catégorie sur son propre effectif. Le comportement livré ne bouge pas — un fichier sans la clé retombe sur 5 par le profil neutre. Écarté et dit : le tableau par rayon et son module de phrases (la demande était UN paramètre), le plafond (réversible en revenant sur le champ), et un `0` valant « aucune puce » (autre décision, autre réglage le jour venu). Numéros vérifiés dans le dépôt plutôt que supposés : contrôle 50 (49 est le plus haut), ADR-059 (058 est le plus haut).
La spec disait « la clé, à 5, dans testdata/config-lacagette.json ». Faux, et dangereux : les trois aides de test du dépôt décodent le fichier livré dans un Config ZÉRO, et le fichier livré ne porte pas non plus grid_columns — il se tait sur ce qu'il ne règle pas. Un contrôle à plancher 1 aurait donc refusé la configuration livrée, et les bancs auraient servi un seuil de 0, soit une puce à toute catégorie y compris vide. C'est le défaut du 28/07/2026 que config_test.go:281-284 nomme déjà. grid_columns y échappe parce que son défaut sûr EST le zéro, délibérément. Le seuil de puce n'a pas cette chance, alors Config.UnmarshalJSON corrige son zéro vers le défaut, comme il le fait déjà pour update.repository, à trois lignes de là. La spec gagne un §4 bis qui l'énonce, et le fichier livré reste muet.
…dernier import catalogOf lisait les rayons dans le snapshot — donc dans la table SQLite `categories`, qui n'existe que comme parent de la clé étrangère products.category_code et que seul un IMPORT met à jour. Renommer un rayon dans config.json n'atteignait l'écran qu'au prochain catalogue publié par le producteur : un rayon renommé le lundi gardait son ancien nom jusqu'à l'export du vendredi, sans rien à l'écran pour le dire. §10.2 bis dit pourtant que la configuration porte le libellé, le rang, la couleur et « montrer cette catégorie sur ce poste », et §11.4 que ces quatre-là rechargent à chaud. Le commentaire du cache, dix lignes plus haut, l'écrivait déjà : « les catégories, les tarifs et les trois réglages d'écran viennent de la CONFIGURATION ». C'est la clé du cache qui avait raison, pas la boucle. L'empreinte de configuration n'était épinglée que sur `Visible` ; le cache du payload en dépend, donc le test couvre maintenant aussi le libellé. Une assertion épinglait l'ancien couplage (« une seule catégorie servie ») : elle vise en fait l'effectif du rayon de l'ail, et c'est ce qu'elle vérifie désormais.
…compte « 48 controls » était déjà faux avant le contrôle 50 (il y en avait 49), et l'énumération des contrôles à station propre aurait dû gagner un 50 de plus : deux nombres qui pourrissent au premier contrôle ajouté ou retiré. L'en-tête dit maintenant ce qui distingue ces tests -- une configuration entière et un poste à eux, pas un seul champ muté sur le corpus -- sans dénombrer ni énumérer.
validate.go, config.go et validate_options.go répétaient chacun le même décompte que l'en-tête de validate_test.go corrigé juste avant -- déjà faux avant le contrôle 50 (49 contrôles existaient déjà), et faux plus fort maintenant. Un nombre recopié à plusieurs endroits ne se met pas à jour tout seul ; les trois disent désormais « the controls » sans les compter, pour rester vrais quel que soit leur nombre. TestControl50HasNothingToSayAboutTheDeliveredFile portait un nom sans proposition complète après le deux-points, contrairement à la convention du fichier. Corrigé en même temps que sa réouverture.
…empreinte TestEveryFieldOfThePresentationEntersItsDigest couvre le nouveau champ par réflexion, sans y toucher ; le golden state.json est régénéré parce que sa valeur de presentation_digest bouge maintenant que ce champ y entre.
…s la constante TS
Les tâches 4 et 5 ont modifié web/src (écran client et écran d'administration) sans reconstruire internal/web/dist/, commité et embarqué dans le binaire. Sans ce commit, le poste livrerait encore l'écran d'avant ces deux tâches.
… en dessous de -5 » La mutation de la tâche 7 (< 1 remplacé par < 0 dans validateChipThreshold) n'a rien cassé au premier essai : TestControl50RefusesAThresholdUnderOne n'essayait que -1 et -5, jamais 0, alors que Validate voit bien 0 sur une Config construite en mémoire — seul le décodage JSON le corrige en DefaultMinProductsForChip. Le plancher n'était donc pinné à aucune valeur précise, seulement « quelque part en dessous de -5 ». Ajoute 0 à la liste des valeurs refusées et documente pourquoi il y figure malgré l'absence de tout fichier livré qui puisse le porter. Rejoué avec la mutation d'origine (< 0) : ROUGE exactement sur le sous-cas 0, les deux autres restent verts. Mutation annulée, suite complète repassée verte.
Le libellé affirmait qu'en dessous du seuil la catégorie disparaît de l'écran ; elle perd seulement sa puce, ses produits restent dans « Tout » et à la recherche. « Produits » désignait aussi le mauvais effectif : le compte porte sur les tuiles servies, pas sur les lignes du CSV. Ce libellé est servi sans aucune note autour dans la barre de refus et le tableau de différences d'un import, il doit donc porter le sens à lui seul.
…alogue Les deux commentaires affirmaient qu'un bénévole voit un rayon renommé sur la grille sans attendre. Faux : le navigateur ne redemande le catalogue que sur catalog_count ou presentation_digest, et un renommage ne bouge ni l'un ni l'autre (web/src/lib/session.svelte.ts). Le serveur sert bien le nouveau libellé au prochain GET, mais rien ne force ce GET -- un poste-kiosque allumé garde l'ancien libellé jusqu'à son prochain chargement. Le mécanisme n'est pas touché ; c'est ce qui reste ouvert, dit en une incise pour que le prochain lecteur n'y voie pas un oubli.
§11.3 dit désormais que la liste énumérée y fait foi, pas un total recopié ailleurs, qui a déjà menti une fois pour avoir oublié une suppression. Ce seuil de puce en ajoutant un 50e était le premier ajout suivant que cette règle anticipait.
…s reçues 355 est un nombre de lignes reçues, 107 un nombre de tuiles pesables : les deux commentaires les mettaient en balance comme s'ils mesuraient la même chose. Ce contrôle porte sur des tuiles ; la paire cohérente est 331 pesables / 107 pesables. Corrigé aux deux endroits du domaine et dans le dossier de conception d'où l'erreur vient.
grid_columns et show_by_unit_products ont chacun une entrée dans TestFingerprintChangesWhenASharedValueChanges ; min_products_for_chip n'en avait pas. Le chemin était correct dans BlockFingerprint, c'est la garde qui manquait -- et le cache de catalogBytes d'internal/web en dépend.
… du cas « trente-et-un champs cassés » est exact aujourd'hui, mais c'est la classe de défaut que cette branche a déjà retirée quatre fois ailleurs : un compteur recopié à côté d'une liste finit par la contredire. La liste want fait foi ; le nom du cas s'en tient à ce qu'il décrit sans le redire, « du contrôle 1 au contrôle 50 ».
… valeur
La TSDoc écrivait « Défaut 5. », qui devait rester en phase avec la constante
déclarée 35 lignes plus bas dans le même fichier. Un {@link} ne peut pas
diverger de ce qu'il désigne.
104 caractères pour un printWidth de 100. Le fichier est sur .prettierignore, mais cette liste est un cliquet de dette et pas une amnistie pour une ligne neuve.
C'est presentation.min_products_for_chip que chips() applique, MIN_PRODUCTS_FOR_CHIP n'étant que son repli. Le fixture leur donne ici la même valeur, donc le test ne changeait pas de comportement, mais le commentaire nommait la mauvaise chose.
Le libellé du seuil de puce, sa TSDoc et l'import replié changent les octets des bundles admin et écran client ; le dépôt a déjà livré une fois l'écran d'avant pour avoir oublié cette étape.
…es ont désalignée `golangci-lint` refuse le fichier sur gofmt. Les deux entrées ajoutées à TestFingerprintChangesWhenASharedValueChanges — le libellé d'une catégorie et le seuil de puce — portent des clés plus longues que les précédentes, et gofmt réaligne alors tout le littéral. La faute est une lacune de vérification et pas une faute d'écriture : la vérification locale a lancé `make vet`, quand la CI lance `make lint`, qui ajoute golangci-lint et donc gofmt. `make vet` ne voit pas ce format.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deux choses, à ne pas confondre : un correctif que la seconde a mis au jour, et un réglage.
Le correctif
Les libellés des rayons de la grille venaient du snapshot — donc de la table SQLite
categories, que seul un import met à jour — au lieu de la configuration. Renommer un rayon dansconfig.jsonn'atteignait l'écran qu'au prochain catalogue publié par un producteur : un rayon renommé le lundi gardait son ancien nom jusqu'à l'export du vendredi, sans rien à l'écran pour le dire.§10.2 bis dit pourtant que la configuration porte le libellé, le rang, la couleur et « montrer cette catégorie sur ce poste », et §11.4 que ces quatre-là rechargent à chaud. Le commentaire du cache, dix lignes plus haut, l'écrivait déjà : « les catégories, les tarifs et les trois réglages d'écran viennent de la CONFIGURATION ». C'est la clé du cache qui avait raison, pas la boucle.
Ce que ce correctif ne fait pas, et c'est écrit dans le code. Une grille déjà chargée dans un navigateur ne va pas rechercher le nouveau libellé d'elle-même : le front ne redemande le catalogue que si
catalog_countoupresentation_digestbouge, et un renommage ne bouge ni l'un ni l'autre. Le serveur sert le bon libellé au prochain GET ; il faut donc un rechargement de page. Élargir l'empreinte aux catégories fermerait ce dernier cran — c'est une décision d'architecture, laissée ouverte et nommée dans les commentaires plutôt que tranchée ici.Le réglage —
ui.min_products_for_chip(ADR-059)Entier, défaut 5, plancher 1, pas de plafond. Appliqué à chaque catégorie séparément, sur son propre effectif de tuiles servies.
Sous le seuil, une catégorie perd sa puce de filtre, jamais ses tuiles : ses produits restent dans « Tout » et à la recherche. Retirer vraiment une catégorie de l'écran reste
categories[].visible, et les deux ne se confondent pas.MIN_PRODUCTS_FOR_CHIPétait une constante du front dont le commentaire disait « pas un réglage (ADR-025) ». Ce qu'ADR-024 a mesuré est qu'un seuil doit exister — en 2022, « Autres » menait à un seul produit, soit un quart de barre de navigation pour une tuile. Ce qu'aucune mesure ne dit, c'est pourquoi cinq plutôt que trois ou huit : la forme d'un catalogue s'inverse d'un export à l'autre (flv.csvdonneA = 140, V = 118, L = 68, F = 29;flv_1.csvdonnaitL = 84, V = 58, F = 10, A = 1). ADR-059 amende ADR-024 sans le renverser, sur le précédent exact d'ADR-057 pourui.grid_columns.Conséquence assumée : sans plafond, un seuil supérieur au plus gros rayon laisse la barre avec « Tout » seul, et rien ne l'annonce avant l'enregistrement. C'est le prix de « un seul paramètre », réversible en revenant sur le champ, et c'est écrit dans ADR-059.
Le comportement livré ne bouge pas
Un fichier qui ne porte pas la clé — et le fichier livré ne la porte pas, délibérément — se relit au défaut :
Config.UnmarshalJSONcorrige le zéro, comme il le fait déjà pourupdate.repository.Sans cette précaution, le contrôle 50 aurait refusé la configuration livrée, et les bancs auraient servi un seuil de 0 — une puce à toute catégorie, y compris vide. C'est le défaut du 28/07/2026 que
config_test.gonomme déjà.ui.grid_columnsy échappe parce que son défaut sûr est le zéro ; le seuil de puce n'a pas cette chance.Ce que la relecture a trouvé, au-delà du périmètre
Ce qui reste ouvert, volontairement
web/src/App.svelte—activeCategoryn'est pas réinitialisé quand un seuil relevé retire la puce active : la grille reste filtrée, aucune puce n'est allumée, et rien ne l'explique. Récupérable en touchant « Tout ». Le défaut préexiste pourcategories[].visible; cette branche est ce qui rend le seuil modifiable en cours de service. C'est un changement de comportement de l'écran client, pas une correction — il mérite sa propre décision.Hors branche : le décompte des contrôles est encore faux dans une quinzaine d'endroits, à trois valeurs différentes. Chantier à part.
Vérification
internal/web/dist/est régénéré et committé : le dépôt a déjà livré « l'écran d'avant » pour l'avoir oublié.Deux garanties vérifiées en les cassant. Figer
chips()sur la constante rend rouge le cas à seuil 70. Relâcher le contrôle 50 de< 1à< 0ne cassait rien — le plancher n'était pinné par aucun test ; comblé, et la mutation refaite échoue désormais exactement sur le sous-cas0.