ADR 0046 – Tâches récurrentes, heures et récurrences dans la capture
Accepté (2026-10-16, mode autonome)
ADR 0046 – Tâches récurrentes, heures et récurrences dans la capture
Statut : Accepté (2026-10-16, mode autonome)
Tâche lancée par le fil coordinateur, à la suite de l’ADR 0042 : récurrence sur la tâche, déclarée en YAML (tous les N jours, semaines ou mois, jours de la semaine précis, à partir de l’échéance ou de la fin) ; terminer une occurrence crée la suivante, l’historique est gardé. Puis la capture en une ligne reconnaît les heures (« à 14 h ») et les récurrences (« tous les lundis », « chaque mois le 5 »).
Le cahier range la récurrence au jalon 3, sur le moteur de règles. Ce moteur n’existe pas encore : la récurrence est ici un comportement déclaré d’aspect, comme le workflow (ADR 0023) ou deferUntil (ADR 0039). Le jour où les règles arriveront, elle pourra devenir une règle « à l’état final, créer la suite » sans changer les données.
Décision
La règle : une valeur de champ au format recurrence
La règle d’une tâche est la valeur d’un champ texte format: recurrence, sous-ensemble du RRULE de l’iCalendar (RFC 5545) :
| Valeur | Sens |
|---|---|
FREQ=DAILY |
chaque jour |
FREQ=DAILY;INTERVAL=3 |
tous les 3 jours |
FREQ=WEEKLY;BYDAY=MO,TH |
chaque semaine le lundi et le jeudi |
FREQ=WEEKLY;INTERVAL=2 |
toutes les 2 semaines |
FREQ=MONTHLY;BYMONTHDAY=5 |
chaque mois le 5 (-1 : le dernier jour) |
FREQ=YEARLY |
chaque année |
…;X-FROM=COMPLETION |
à compter de la fin plutôt que de l’échéance |
BYDAYseulement avecWEEKLY,BYMONTHDAYseulement avecMONTHLY,INTERVALde 1 à 999. Le format est vérifié à l’écriture commenode-ref(format personnalisé du validateur) ; la forme enregistrée est canonique (ordre des parties,INTERVAL=1omis).- Pourquoi RRULE plutôt qu’une phrase : une forme exacte et connue, exportable, que le cahier citait déjà. Le français est produit à l’affichage (fiche, aperçu de la capture).
- Pourquoi un champ plutôt qu’un aspect : la règle se lit, se filtre et se journalise comme les autres champs de la tâche.
Le même ADR ajoute format: time (heure locale HH:mm, champ heure de la tâche). Le format time du JSON Schema exige un fuseau (RFC 3339) : le validateur de l’application le remplace par l’heure locale, plus utile pour une tâche.
Le comportement : bloc recurrence de l’aspect
recurrence:
field: recurrence # champ format: recurrence qui porte la règle
date: echeance # champ date avancé d'une occurrence à l'autre
shift: [debut] # autres champs date décalés du même nombre de jours
on: [fait] # états du workflow qui créent la suite
reset: { statut: a-faire } # valeurs de la nouvelle occurrence
link: occurrence-suivante # lien de l'occurrence terminée vers la suivante
Contrôles au chargement : champ au format recurrence, date et shift sur des champs date, on parmi les états du workflow (qui doit exister), reset sur des champs connus, conforme au schéma et donnant l’état de la nouvelle occurrence (hors on), link déclaré dans config/links/.
Terminer une occurrence
Quand un aspect porté passe d’un état hors on à un état de on (fiche, vue, kanban, action rapide, lot) et que le champ de règle est rempli, dans la même transaction :
- Date suivante. Le calcul part juste avant la borne : une échéance très ancienne (faute de frappe sur l’année) ne ralentit ni ne bloque rien ; une date qui sortirait du calendrier ne crée rien. « Chaque mois » sans jour précis se fixe sur le jour de l’échéance (la règle de la nouvelle occurrence porte
BYMONTHDAY) : une échéance au 31 raccourcie au 30 novembre revient au 31 décembre. À partir de l’échéance : première date de la grille de la règle (ancrée sur l’échéance) postérieure à l’échéance et à aujourd’hui. Les occurrences manquées sont sautées : une tâche quotidienne en retard de dix jours revient demain, pas dix fois. À compter de la fin, ou sans échéance : première date de la grille ancrée sur aujourd’hui, après aujourd’hui. « Aujourd’hui » est celui du fuseau du serveur (variableTZ), comme les valeurs par défaut dynamiques. - Nouvelle occurrence. Même titre, type, parent, contenu (cases des listes de contrôle décochées), tags saisis et aspects ; date avancée, champs
shiftdécalés, valeursresetappliquées. Elle passe parNodeService.create(validation, valeurs par défaut, liens dérivés comme le projet, journalnode.created). Ni inbox, ni fichiers, ni liens manuels, ni dépendances. Terminer ne doit jamais échouer à cause de la copie : ce que la création refuserait est écarté avant (champ en violation du schéma, donnée ancienne tolérée à la lecture ; aspect ou type disparu deconfig/; parent dans la corbeille, la suite va à la racine ; référence vers un nœud purgé, dans la corbeille ou horsx-target). - Historique. L’occurrence terminée reste telle quelle, avec sa règle. Le lien
occurrence-suivante(une seule cible) la relie à la nouvelle, et son journal reçoitrecurrence.next(identifiant et date de la suivante). - Pas de doublon. Si l’occurrence a déjà un lien
occurrence-suivante(rouverte puis terminée de nouveau), rien n’est créé, même si la suite est dans la corbeille : la série s’arrête là. Si la définition du lien est indisponible (fichier invalide), rien n’est créé non plus, faute de garde contre les doublons.
Annuler un lot (ADR 0041) remet les états mais ne supprime pas les occurrences créées. Retirer la règle d’une occurrence arrête la série.
Capture
La syntaxe de capture déclare deux champs de plus :
time: tache.heure # « à 14 h », « vers 14h30 », « 9:15 », « à midi »
recurrence: tache.recurrence # « tous les lundis », « chaque mois le 5 »…
- Récurrences : « chaque jour », « tous les jours », « tous les 3 jours », « chaque semaine », « toutes les 2 semaines », « tous les lundis », « chaque lundi et jeudi », « tous les lundis, mercredis et vendredis », « chaque semaine le mardi », « chaque mois », « tous les 2 mois », « chaque mois le 5 », « tous les mois le 1er », « le 5 de chaque mois », « chaque mois le dernier jour », « chaque année », « tous les ans ».
- Les heures en « h » exigent « à » ou « vers » devant : « travailler 2h » est une durée, pas une heure. « 14:30 » seul est une heure.
- Elles sont lues avant les dates (« tous les lundis » n’est pas « lundi »). Sans date, l’échéance est la première occurrence à partir d’aujourd’hui compris ; une heure sans date donne l’échéance d’aujourd’hui.
- L’aperçu montre « Récurrence : chaque semaine le lundi » et « Heure : 14 h ».
Front
- La forme enregistrée est canonique : toute écriture réécrit la règle (
freq=daily ; interval=2→FREQ=DAILY;INTERVAL=2). - Champ
format: recurrence: un éditeur (aucune, chaque jour, semaine, mois, année ; intervalle ; jours de la semaine ; jour du mois ; « à compter de la fin ») avec la phrase française.format: time: champ heure natif. - Après l’écriture qui termine une occurrence, l’annonce dit « Occurrence suivante créée pour le lundi 26 octobre » avec « Ouvrir », d’après le journal (dernier événement
recurrence.next) : rien n’est annoncé si la suite existait déjà.
Alternatives écartées
- Attendre le moteur de règles : la demande vient maintenant, et le format des données ne dépend pas du moteur.
- Modifier la tâche en place (avancer l’échéance) : perd l’historique des occurrences faites, que la revue et les vues « terminées » exploitent.
- Créer toutes les occurrences à l’avance : encombre les vues, et une règle modifiée laisse des orphelins.
- Bibliothèque RRULE complète (ical4j, lib-recur) : lourde pour cinq parties ; le sous-ensemble se calcule en quelques lignes.
Conséquences
- La tâche gagne deux champs facultatifs (
heure,recurrence) ; les données existantes restent valides. - Les règles plus riches (« le premier lundi du mois », jours ouvrés, fin de série) et les rappels à l’heure dite sont reportés.