L'accueil ne prend pas en compte les agendas importés #1
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#1
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
Sur la page d'accueil, le bloc « À venir » n'affiche que les événements de l'agenda familial. Les agendas externes ajoutés depuis le panneau « Agendas » (D33, D34) n'y apparaissent jamais, alors qu'ils sont bien visibles dans l'emploi du temps.
Reproduction
/accueil.Cause
ex_app/lib/src/routes/summary.tsne lit qu'une seule collection :calendarByKind(family.id, CalendarKind.EVENTS). L'exclusion est délibérée pour les deux agendas projetés — les échéances de tâches et les repas ont déjà leur bloc sur l'accueil, les remonter ici mettrait la même tâche dans deux colonnes de la même page (D31). Un abonnement n'est ni l'un ni l'autre : rien d'autre sur l'accueil ne le montre.Ce qu'il faut prévoir
req.useràupcomingEvents.calendarUnreadableest aujourd'hui un booléen pour une seule source. Avec plusieurs, il faut trancher ce que veut dire « l'un des agendas n'a pas répondu » sur un accueil qui doit rester lisible.Plan d'action
La conception est dans ce commentaire ; le bilan du développement dans celui-ci.
contracts/calendar.ts: exporterisProjection, qui nomme l'exclusion de D31 au lieu de la coder en dur dans la synthèse.services/calendar.ts:upcomingEventsprendreadonly CalendarRef[]— lecture en parallèle, filtre du passé, tri global, coupe à 5 en dernier,unreadablevrai dès qu'un agenda n'a pas répondu.routes/summary.ts: lirecalendarsOf(family.id)privé de ses projections, unrefOfpar agenda (le compte de service lit les abonnements, D33/D34).contracts/summary.ts: faire voyager les agendas de la famille avec la synthèse, commeEventPeriod.calendars(D26).HomeEventsCard: avertissement au-dessus de la liste quand un agenda a échoué mais que d'autres ont répondu ; état vide réservé au cas où il n'y a rien à montrer ; texte revu (« l'agenda familial » n'existe plus au singulier).HomeEventsCard: colorer la pastille avec l'agenda d'origine de l'événement plutôt qu'avec la couleur de la famille.services/calendar.test.ts(il n'y en a pas) — tri et coupe sur l'ensemble et non agenda par agenda, lecteur imposé pour un abonnement, échec partiel qui garde les événements lus.stores/summary.test.tset la carte — échec partiel avec événements, couleur par agenda.calendar-subscription.spec.ts(hostIcsFeed,refreshSubscriptions) et vérifier l'événement de l'abonnement sur/accueil; mettreunreachable-calendar.spec.tsau nouveau texte.REPORT.Recette
make lint typecheck testmake build-frontend, puis vérifier que le bundle est bien une IIFEcd e2e && npm test— 94 tests verts/code-review, retours déposés en commentaire du ticketArchitectureD31 : l'exclusion porte sur les projections ;UX: la carte « À venir » et l'échec partielPlan de MEP
appinfo/info.xmlest inchangé.make build-frontendavant la construction de l'image.L'acceuil ne prends pas en compte les plannings importéto L'accueil ne prends pas en compte les plannings importéL'accueil ne prends pas en compte les plannings importéto L'accueil ne prend pas en compte les agendas importésConception
Ce que l'accueil lit désormais
L'agenda des événements et les abonnements, jamais les deux agendas projetés. La règle
que D31 pose n'est pas « l'accueil ne lit qu'un agenda », c'est « l'accueil ne montre pas
deux fois la même chose » : les échéances de tâches et les repas ont déjà leur bloc sur la
page, un abonnement n'en a aucun.
Le code dit aujourd'hui
calendarByKind(family.id, EVENTS), ce qui code en dur une seulesortie possible. Il dira
calendarsOf(family.id)filtré sur un prédicat exporté depuiscontracts/calendar.ts:Ainsi un cinquième type d'agenda ajouté demain est lu par défaut plutôt qu'oublié en
silence, et l'exclusion reste nommée là où elle se décide.
Qui lit
Rien de nouveau côté authentification :
refOfpose déjàreaderId = compte de servicepour un abonnement (D33), et
objectsBetweenlit aveccalendar.readerId ?? userId. Ilsuffit de construire un
CalendarRefpar agenda avecrefOf— l'agenda d'événementscontinue d'être lu comme le membre, à travers le partage Team, ce qui reste chargé de sens
(D31).
Fusion et coupe
upcomingEventspasse deCalendarRef | nullàreadonly CalendarRef[]:eventsBetween;slice(limit)en dernier.Deux conséquences dans le corps de la fonction :
nulld'aujourd'hui :{ events: [], unreadable: false };unreadabledisparaît. Avec plusieurs sources il faut garder cequi a été lu et signaler l'échec ; c'est exactement ce que
eventsBetweenfait déjà.Le coût réseau : tous les abonnements sont lus
Les lectures sont parallèles : l'accueil coûte le plus lent des
REPORT, pas leur somme —le contrat que
eventsBetweentient déjà pour les trois agendas de la grille. Le nombre estborné par ailleurs :
MAX_SUBSCRIPTIONS_PER_FAMILY = 5(D33), donc six collections au pire.Tous les abonnements, pas seulement ceux que le panneau laisse visibles. Le masquage est
une préférence de navigateur (
organisateur_familial:timetable-hidden-calendars, parfamille et par appareil, D27) : le serveur ne la connaît pas, et c'est une préférence de
l'emploi du temps, pas de l'accueil — elle répond à une grille chargée, pas à cinq
lignes. Un abonnement est de toute façon affiché par défaut (
isShownByDefault).Deux alternatives écartées :
et une réponse qui varie selon une clé du poste appelant ;
afficherait donc moins de 5 événements sans que rien ne dise pourquoi.
calendarUnreadableLe champ garde son nom et change de définition : « au moins un des agendas lus n'a pas
répondu ». C'est déjà le sens de
EventPeriod.unreadablepour une période (D26), et lequela échoué n'est pas dit : la conséquence est la même, et nommer un agenda deviendrait faux
dès qu'un second tombe.
Sur l'écran, en revanche, le message ne peut plus remplacer le contenu. Il passe
aujourd'hui par le
emptydeHomeCard, ce qui est juste tant qu'un échec veut dire zéroévénement ; avec plusieurs sources, un échec partiel coexiste avec des événements lus. Deux
cas :
Le texte « L'agenda familial est momentanément injoignable. » ne décrit plus rien de juste
(il n'y a plus « l'agenda »). Proposition alignée sur la bannière de l'emploi du temps :
« Un agenda est injoignable : il peut manquer des événements. » Le sélecteur
.home-card__empty--warningd'unreachable-calendar.spec.tsteste le texte actuel et suit.La pastille de couleur
HomeEventsCardpeint son marqueur avec--family-color. Un événement venu d'un abonnements'afficherait donc aux couleurs de la famille, alors que la grille le distingue.
Proposition : faire voyager les agendas avec la synthèse (
HomeSummary.calendars, commeEventPeriod.calendarsle fait pour la grille — même raison qu'en D26 : un client qui lescacherait dessinerait une légende périmée) et peindre le marqueur avec la couleur de
l'agenda de l'événement, que
event.calendarIddésigne déjà. Coût : une lecture SQLite,aucun appel réseau de plus.
C'est la seule partie du ticket qui dépasse la correction stricte du bug. Si elle est
écartée, il faut l'assumer : un événement externe restera indiscernable d'un événement
familial sur l'accueil.
Ce qui ne bouge pas
appinfo/info.xmlest inchangé.X-NC-CalDAV-Webcal-Caching: Onest déjà envoyé sur toutes les requêtes DAV(D33) — sans lui un
REPORTsur un abonnement répond 207 vide, indiscernable de « riendans cette période ».
RefreshWebcalJobest vide, pasinjoignable. L'accueil dira « rien de prévu », ce qui est correct.
Développement terminé — branche
fix/accueil-agendas-importesCinq commits, dans cet ordre :
refactor(calendars): name the payload conversion oncepresentCalendar, au lieu d'unfamilyIdretiré à la main dans trois payloadsfix(summary): the home reads the family's external agendas tooisProjection,upcomingEventssur plusieurs agendas, la route,services/calendar.test.ts(11 tests, le fichier n'existait pas)fix(summary): an event on the home says which agenda it came fromdocs(skill): a REPORT starting mid-day loses that day's all-day eventsreferences/icalendar.mdchore(e2e): format offline.spec.tsmake lintéchouait surmainavant cette brancheCe qui a été mesuré en route, et corrigé ici
L'accueil n'affichait aucun événement « toute la journée » du jour même, y compris ceux
de l'agenda de la famille. Nextcloud 34 indexe un
VEVENTtoute-la-journée sansDTEND—ce dont un flux externe est plein — comme une occurrence de durée nulle, et le
REPORTnegarde un objet que tant que sa dernière occurrence est strictement après le début de la
période demandée. Demander à partir de « maintenant » répondait donc 207 sans les
événements du jour, sans erreur ni avertissement. Mesuré sur la même collection :
2026-08-24T00:00Z → 2026-08-30T23:59Z(la semaine)2026-08-30T00:00Z → 2026-09-29T16:00Z(l'accueil)L'accueil lit maintenant à partir de la veille et filtre le passé lui-même. Une journée
entière de marge et non un instant : cette occurrence est datée dans le fuseau du serveur et
peut tomber de part et d'autre de minuit UTC.
Retour de revue à trancher
services/calendar.ts— un événement toute-la-journée sur plusieurs jours, commencé avantaujourd'hui, n'apparaît pas sur l'accueil alors qu'il est en cours. Le filtre compare la
date de début de l'événement au début du jour, pas sa fin. Un flux de vacances scolaires
portant un seul
VEVENTdu 17/10 au 02/11 est donc absent de « À venir » le 20/10 — etc'est exactement la forme qu'a un flux d'établissement scolaire.
Le comportement est antérieur à ce ticket (le commentaire du code prétend déjà l'inverse :
« an all-day event in progress is not past. It is kept until it ends »), mais il devient
visible maintenant que l'accueil lit des flux externes. Deux lectures défendables :
leur place, et il faut corriger le commentaire plutôt que le code ;
event.endpour un événement toute-la-journée, et les vacances en cours s'affichent.Rien n'est changé pour l'instant : c'est un choix de produit, pas un correctif évident.
Portes mécaniques
make lint typecheck test— vert (378 tests backend, 342 front)make build-frontend+ vérification IIFE du bundle — vertcd e2e && npm test— 94 tests, tous verts (dont les deux nouvelles étapes decalendar-subscription.spec.ts)/code-reviewniveau high — un seul retour, celui ci-dessusWiki
Architecture/ D31 : la règle d'exclusion devient « rien que la page montre déjà sousson propre titre », avec les conséquences de la lecture multi-agendas et la mesure
ci-dessus.
UX: ce que montre la carte « À venir », la couleur par agenda, et le fait qu'un échecpartiel s'affiche au-dessus de la liste sans la remplacer.
La branche n'est ni rebasée sur
mainni poussée.Retour de revue tranché : un événement en cours reste sur l'accueil
Arbitrage retenu : « À venir » veut dire ce qui occupe les trente prochains jours. Le
filtre compare donc la fin de l'événement, et non plus son début.
Commit
fix(summary): an event under way is still what the family has on.on dispose pour le juger, et la plupart des entrées toute-la-journée d'un flux n'ont pas
de
DTEND.eventEndest explicite sur ce que vaut la valeur : leDTENDd'un événementtoute-la-journée désigne le lendemain du dernier jour. La comparaison est stricte, sinon
les vacances disparaissent le matin de leur dernier jour. Trois tests bornent exactement
ça : la veille du dernier jour, le dernier jour, le lendemain.
— même règle, appliquée sans exception.
août » sous « Les 30 prochains jours » se lirait comme un bug.
Le flux servi par la suite e2e portait un seul événement d'une journée, que l'ancienne règle
traitait correctement — il ne pouvait donc rien détecter ici. Il porte maintenant une
seconde entrée commencée il y a trois jours et finissant demain, vérifiée sur l'accueil.
Portes rejouées :
make lint typecheck testvert (382 tests backend, 342 front),bundle IIFE vérifié,
cd e2e && npm test94/94 vert.Wiki : la règle est dans D31 (
Architecture) et dansUX.Revue de la branche complète, avant fusion
/code-reviewniveau high surmain...HEAD(6 commits). Deux retours, un seul demandantune décision.
1. Le wiki mentait sur les rappels — corrigé
Le paragraphe de D31 réécrit par cette branche s'intitulait « The home and the reminders
leave out the projections ». Faux : seul l'accueil a changé.
services/reminder.ts:117littoujours
calendarByKind(family.id, CalendarKind.EVENTS), donc un événement venu d'unabonnement ne déclenche aucun rappel. Quelqu'un consultant D31 pour le savoir aurait eu la
mauvaise réponse.
D31 est corrigé : l'accueil exclut les projections, les rappels lisent l'agenda d'événements
seul — et pour une autre raison, qui vaut aussi pour un abonnement : personne ici n'a planifié
ce que porte un flux, et un flux de quelques centaines d'entrées notifierait une famille pour
chacune d'elles. La page dit désormais que c'est une question ouverte, pas un oubli.
Si tu veux qu'un abonnement notifie, c'est un ticket à part — il faudra choisir quoi
notifier dans un flux qu'on ne contrôle pas.
2. Un événement long en cours peut occuper les cinq places — à trancher, plus tard
Conséquence directe de l'arbitrage d'hier : les événements sont triés par date de début et
l'accueil en garde cinq. Un événement commencé il y a longtemps et pas terminé passe donc
devant tout le reste, pour toute sa durée.
Un flux d'établissement portant un unique
VEVENT« Année scolaire » du 1ᵉʳ septembre au31 décembre trusterait la première ligne de « À venir » pendant quatre mois, avec « En cours »
pour seule information. Deux entrées de ce genre et il ne reste que trois places pour ce qui
arrive vraiment.
Rien n'est changé : trier ce qui est en cours après ce qui est à venir, ou borner la durée
au-delà de laquelle un événement ne remonte plus, sont deux choix de produit et aucun n'est
évidemment le bon. À voir si le cas se présente sur un vrai flux.
Ce que la revue a vérifié sans rien trouver
refOf), inchangé et déjà couvert par D33.eventEndsur des valeurs mal formées : unDTENDhoraire sur un événementtoute-la-journée produit
NaN, ce que le garde intercepte pour retomber sur le début.filterpuisslice: la coupe à 5 se fait bien après l'élimination du passé.unreadableremonte quand même..home-events__notice) ne sont partagées avec aucune autrecolonne, conformément à l'invariant d'
AGENTS.md.État
Branche prête à être fusionnée en fast-forward sur
main(qui n'a pas bougé). Portes toutesvertes au dernier passage :
make lint typecheck test, bundle IIFE,cd e2e && npm test94/94.
Fusionné sur
mainFast-forward des 6 commits, branche
fix/accueil-agendas-importessupprimée en local.mainest 6 commits en avance surorigin/main: rien n'est poussé.Ticket en
step/done, jalon1.0.x. Reste la revue fonctionnelle dans l'application et lalivraison — le
Plan de MEPla décrit : rien à migrer, image reconstruite avec le bundlefrontend, effet au premier chargement de l'accueil.