Une couleur par membre #10
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
maxime/OrganisateurFamilial#10
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
Chaque membre reçoit une couleur ; les événements et tâches qui lui sont affectés à lui seul la portent. Un coup d'œil suffit alors pour savoir de qui relève une case du planning, sans lire le nom écrit.
Portée
Uniquement les affectations uniques portent la couleur d'un membre : mélanger deux couleurs ne veut rien dire. Un événement familial est attribué à tout le monde par défaut — il prend la couleur de la famille, qui se reconnaît ainsi au premier coup d'œil ; un événement attribué à personne garde son aspect d'aujourd'hui.
Où la couleur se lit
CATEGORIES(D14).assignee.Ce qu'il faut trancher
Plan d'action
member_colors, attribution automatique à la lecture, route de correctionPUTRecette
make lint typecheck test, puismake build-frontend(bundle IIFE) etcd e2e && npm testPlan de MEP
appinfo/info.xml:^/api/.*relaie déjà GET, POST, PUT et DELETEConception — fonctionnel et technique
Principes
Une couleur est un attribut du membre au sein d'une famille — pas du compte Nextcloud, pas de l'appareil. Toute la famille voit la même, sur tous ses appareils ; une même personne peut donc porter une couleur différente dans deux familles. Elle n'est jamais que redondante : le nom reste affiché partout où la couleur se lit (daltonisme, aplats trop petits pour être lus seuls).
Elle se lit par affectation :
CATEGORIESnomme exactement un membre connu (D14) prend la couleur du membre ;hueOf) — rien n'est affecté, rien ne s'impose ;assigneeest renseigné — une tâche est par construction affectée à une seule personne ;Les quatre tranches
1. Où vit la couleur — dans SQLite, table
member_colors.Le test de D27 est net : un choix que toute la famille doit voir sur tous ses appareils n'est ni une préférence de personne (API Preferences) ni un réglage d'écran par appareil (
localStorage). C'est une donnée de famille, commefamilies.color. Circles n'offre pas de métadonnée par membre inscriptible sans un appel réseau de plus, et le compte Nextcloud n'est pas le membre — D14 prévoit des membres sans compte.Pourquoi un index et pas un hex : la palette est une convention d'affichage. L'index voyage sur le fil et reste stable si la palette est retouchée pour le contraste ; un hex stocké serait faux le lendemain. La clé est la clé opaque (aujourd'hui le user id — le même choix que
keyOfdu filtre du planning, et non l'id d'appartenance Circles, qui change quand on quitte puis revient).Une ligne n'affirme pas qu'un membre existe — le Circle est seul maître de l'appartenance (D4). Elle ne se lit qu'à côté d'une liste de membres réellement chargée ; une clé périmée est inerte, jamais autoritaire (D27).
2. Qui choisit — l'automatique à l'arrivée, corrigeable par n'importe quel membre.
L'attribution est matérialisée à la lecture, dans
getMembers— l'unique entonnoir des membres, déjà derrière le cache de D19 : un membre sans ligne reçoit la première couleur de la palette libre dans sa famille. Aucun branchement dans le flux d'invitation (qui enchaîne déjà des appels Nextcloud), et un membre ajouté depuis Nextcloud directement reçoit aussi sa couleur. La migration ne back-fille rien : la première lecture s'en charge, au prix d'une écriture par membre et par famille. C'est une écriture dans une route de lecture, mais bornée — le cache de D19 en est la limite.La correction vit dans le panneau Membres des paramètres de la famille : une pastille par ligne, qui ouvre la palette. N'importe quel membre peut corriger — même précédent que les abonnements (D34) : la famille est une unité de confiance, et un enfant sans compte ne peut pas choisir lui-même. L'automatique évite l'écran ; la correction évite ses couleurs immuables.
3. Les collisions — impossibles par construction.
La palette est bornée à 8. L'attribution prend la première couleur libre, dans l'ordre fixe de la palette. Le sélecteur désactive les couleurs déjà portées par un autre membre : deux membres de la même couleur ne peuvent pas exister tant que la famille tient dans la palette. Au-delà de 8 membres, le repli est un hachage stable de la clé modulo 8 — déterministe sur tous les appareils, collision inévitable mais jamais vacillante.
4. Le contraste — la palette est fixe et double.
Chaque entrée donne un fond et un texte, pour le thème clair et le thème sombre, dérivés des couleurs officielles Nextcloud, contraste ≥ 4.5 dans les deux thèmes. Pas de couleur libre : une palette choisie librement produit des cases illisibles. Les valeurs exactes se figent à la construction, contre la palette
@nextcloud/vue.Comment la couleur se lit
Une seule fonction de résolution,
memberColorOf(key), partout — pas une branche par vue :nameOf: jour, jour par membre, semaine, mois. DanscolorOf(stores/events.ts) :membersà exactement une clé connue → couleur du membre ; plusieurs → couleur de la famille, celle que l'agenda d'événements porte déjà dans la réponse de période (D31) ; aucune → teinte dérivée inchangée. Les autres agendas gardent leur couleur propre (D31). L'échéance d'une tâche affectée suit la même règle — l'issue l'exige sur les quatre vues ; sa nature reste lisible par le bandeau du jour entier en semaine et au jour, et par la fiche projetée qui nomme « Listes » au mois. La vue « jour par membre » porte en plus la pastille dans l'en-tête de colonne. Repli silencieux : une clé sans ligne connue n'a pas de couleur — le rendu est inchangé, jamais faux.assigneedeListRowprend la couleur du membre (propriété CSS--of-member-color, classes distinctes par colonne pour l'e2e).summarymarque chaqueSummaryTaskd'unassigneeColor: les membres sont lus viagetMembers(cache de D19), et l'accueil ne gagne pas d'appel parallèle — l'instantané borné est le contrat du fichier. Une lecture refusée ne fait pas échouer le résumé : la couleur estnull, la tâche reste affichée (un échec de lecture ne devient jamais un résultat vide).Ce que la couleur ne traverse pas
CATEGORIES: un téléphone ou Nextcloud Calendar l'ignorerait ou l'afficheraient mal, et changer la couleur d'un membre devrait alors réécrire chaque événement. Elle se dérive au rendu, toujours ;calendar-projection.tsest inchangé et aucune mutation de liste ne gagne de crochet de projection.Interface et tests
FamilyMembergagnecolor: number(index de palette) ; le sélecteur de correction est une pastille + palette dansFamilyMembersPanel, àNcColorPickersi le composant le permet, sinon huit pastilles enNcPopover.PUT /api/families/:id/members/:memberId/color, corps{ color }, index validé par zod (message source, jamais de phrase construite à la main) ; 404 pour unmemberIdinconnu — jamais 403, qui révélerait l'existence ; 409 avec message traduit si un autre membre porte déjà la couleur ; la réponse est l'instantané complet des membres (D26).^/api/.*relaie déjà PUT — rien à déclarer dansinfo.xml,routes.test.tsle vérifiera.--of-member-coloravec la valeur attendue (le sélecteur échoue si la couleur disparaît) ; l'événement attribué à un seul membre porte sa couleur sur la grille ; un changement de couleur depuis les paramètres se voit sur la liste. Classes distinctes par colonne (.item*,.home-entry*).t()— en getter si une constante les porte à l'import (le piège deconstants/lists.ts) ;make l10n-buildpuis lel10n-checkde la CI.Implémenté sur
feat/issue-10-member-colors(8 commits, non rebasé, non poussé).Ce qui a été construit
member_colors(family_id, member_key, color_index), clé = la clé opaque de D14, index de palette et non couleur, aucun backfill.getMembersmatérialise : chaque membre sans ligne reçoit la première couleur libre de la palette ; au-delà de huit membres, repli par hachage stable de la clé.PUT /api/families/:id/members/:memberId/color: 404 pour un membre inconnu, 409 si la couleur est portée, réponse = l'instantané complet des membres (D26).@nextcloud/vue(Purple, Feldspar, Gold, Olivine, Acapulco, Boston Blue, Mariner, Blue Violet), chacune assombrie jusqu'à un contraste de 4,7 avec le texte blanc — ce que les puces de la semaine supposent déjà, donc une seule paire pour les deux thèmes. Dansstyles/tokens.csscomme propriétés personnalisées ; les indices ne sont jamais renumérotés.colorOfdécide seul — exactement un membre connu → sa couleur (échéance affectée comprise, l'agenda n'y change rien) ; plusieurs membres → la couleur de la famille ; personne nommé → la teinte dérivée d'aujourd'hui. Pastille dans les en-têtes de colonnes de « jour par membre », puce d'assigné colorée dans les listes, « À faire aujourd'hui » coloré depuis l'instantané du résumé (un refus de lecture Circles laisse la tâche visible, sans couleur).Portes mécaniques
make lint typecheck testmake build-frontend+ IIFEcd e2e && npm testmember-colors.spec.ts(3 tests : puce de tâche, grille membre/famille, correction + couleur prise refusée)make l10n-buildRevue technique (branch-review)
Bloquant : rien.
À signaler :
Architecture(l'endpointPATCH /wiki/page/…de Forgejo se comporte comme un renommage quand le champtitlemanque) — restaurée depuis l'historique git dans la foulée, puis D39, le schéma et le paragraphe UX publiés. Les trois commits wiki sont dansOrganisateurFamilial.wiki(main).Wiki
Architecture: décision D39 + tablemember_colorsdans le schéma.UX: « a member wears one colour per family » dans les conséquences concrètes.AGENTS.md: état du projet + l'invariant (couleur dérivée, jamais écrite là où elle se lit ; indices jamais renumérotés).Intégration
Restent ouverts : revue fonctionnelle (à faire sur l'instance locale — créer une famille à deux membres, affecter une tâche, corriger une couleur depuis les paramètres) et le retrait des retours qu'elle donnera.
Recette fonctionelle
Les trois retours de la revue sont corrigés sur
feat/issue-10-member-colors(4 commits de plus, non rebasé, non poussé).1. Les pastilles n'étaient pas alignées verticalement
La pastille vivait après le nom du membre : sa position horizontale dépendait alors de la largeur de ce qui ferme la ligne — le badge « propriétaire » est plus étroit que le bouton « Retirer », et les pastilles des deux lignes se retrouvaient sur deux verticales différentes (12 px d'écart sur votre capture). La pastille vit désormais entre l'avatar et le nom : sa place ne dépend plus que de la largeur de l'avatar.
2. Le sélecteur ne se fermait pas au clic extérieur
Le sélecteur était un popover fait main, qui ne se fermait que sur son propre déclencheur. C'est un
NcPopoverdésormais — le composant que le sélecteur de famille utilise déjà — qui possède le clic extérieur et la touche Échap.3. L'accueil n'avait pas la bonne couleur
La carte « À venir » de l'accueil colore un événement par son agenda (D31), et la règle D39 n'y était jamais arrivée : un événement attribué à un seul membre portait sa couleur sur la grille mais le bleu familial sur l'accueil. L'instantané du résumé transporte désormais la carte clé → couleur à côté des événements, et « À venir » résout exactement comme la grille : un membre seul → sa couleur ; plusieurs → la couleur de la famille ; personne → celle de l'agenda. Les deux écrans ne peuvent plus se contredire.
Vérification
make lint typecheck testmake l10n-check.pota suivi les numéros de ligne déplacés, aucune source nouvelle)make build-frontend+ IIFEcd e2e && npm testLe wiki est à jour (D39 porte maintenant l'accord accueil/grille dans son tableau et son paragraphe, le paragraphe UX aussi). Un incident d'instance à noter : un premier passage de la spec sur une instance encrassée par mes exécutions répétées a fait expirer le teardown (limites de taux accumulées) — le reset suivant a purgé les compteurs et tout est repassé en 27 s ; rien à voir avec le code.
Reste : votre re-lecture des trois points sur l'instance locale, puis l'intégration —
git switch main && git merge --ff-only feat/issue-10-member-colors.