Skip to content

feat: le seuil de puce devient un réglage, et les libellés suivent la configuration - #50

Merged
lostmind84 merged 27 commits into
mainfrom
feat/seuil-de-puce-reglable
Aug 4, 2026
Merged

feat: le seuil de puce devient un réglage, et les libellés suivent la configuration#50
lostmind84 merged 27 commits into
mainfrom
feat/seuil-de-puce-reglable

Conversation

@lostmind84

Copy link
Copy Markdown
Owner

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 dans config.json n'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_count ou presentation_digest bouge, 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.csv donne A = 140, V = 118, L = 68, F = 29 ; flv_1.csv donnait L = 84, V = 58, F = 10, A = 1). ADR-059 amende ADR-024 sans le renverser, sur le précédent exact d'ADR-057 pour ui.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.UnmarshalJSON corrige le zéro, comme il le fait déjà pour update.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.go nomme déjà. ui.grid_columns y é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

  • Quatre commentaires Go annonçaient « the 48 controls » alors qu'il y en avait 49 puis 50 ; §11.3 affichait « 49 numéros, 47 contrôles ». Ces totaux cèdent la place au critère que la liste énumérée fait vivre — un numéro retiré n'est jamais repris. Le passage portait déjà une note disant qu'il s'était trompé une fois ; la leçon est conservée, la date généralisée.
  • Un commentaire comparait 355 lignes reçues à 107 tuiles pesables. La paire cohérente est 331 / 107.
  • Le libellé d'administration disait « Produits minimum pour afficher une catégorie » — le contresens exact, servi sans note dans la barre de refus et le tableau de différences. Devenu « Tuiles minimum pour donner sa puce à une catégorie ».

Ce qui reste ouvert, volontairement

web/src/App.svelteactiveCategory n'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 pour categories[].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

make.ps1 test        exit 0   passe -race puis passe CGO_ENABLED=0, boundary, deps
make.ps1 vet         exit 0
make.ps1 front-check exit 0   svelte-check 373 fichiers / 0 erreur
                              vitest 38 fichiers / 1008 tests
                              80 586 o gzip pour un budget de 112 640 — 71,5 % de marge

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 à < 0 ne cassait rien — le plancher n'était pinné par aucun test ; comblé, et la mutation refaite échoue désormais exactement sur le sous-cas 0.

… 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.
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.
@lostmind84
lostmind84 merged commit b2a7a99 into main Aug 4, 2026
8 checks passed
@lostmind84
lostmind84 deleted the feat/seuil-de-puce-reglable branch August 4, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant