ADR 0106 – Minuteur sur une tâche
Accepté (2026-10-17, vague 3 du document d'idées)
ADR 0106 – Minuteur sur une tâche
Statut : Accepté (2026-10-17, vague 3 du document d’idées)
Les tâches ont une estimation (ADR 0070) mais rien ne dit combien de temps elles prennent vraiment. Un minuteur lancé depuis la fiche, visible partout, cumule le temps passé et le compare à l’estimation.
Décision
Configuration : clé timer d’un aspect
schema:
properties:
tempsPasse: { type: integer, minimum: 0, title: Temps passé (minutes) }
timer: { field: tempsPasse, estimate: estimation }
field: champ entier (minutes) où le temps s’additionne ;estimate(facultatif) : champ entier auquel on le compare. Le méta-schéma refuse une clé inconnue ; au chargement, un champ absent, non entier ou énuméré, ou deux fois le même, invalide l’aspect (signalé par/api/definitions/status).AspectDefinition.timerest exposé par l’API des définitions, le client généré porteTimer.- Aucun nom en dur : le moteur et le front ne connaissent ni « tache » ni « tempsPasse » ; tout aspect qui déclare
timerreçoit le minuteur dans sa section de fiche.config/aspects/tache.yamlajoutetempsPasse(modifiable à la main, dans l’ordre des champs aprèsestimation) et la clé.
Où vit l’état du minuteur : le navigateur
Décision : côté navigateur (TimerStore, core/timer/), pas côté serveur.
- L’état est minuscule (nœud, aspect, champ, instant de départ) et gardé dans le stockage local : il survit au rechargement et se voit dans les autres onglets (événement
storage). Un seul minuteur à la fois ; en démarrer un sur un autre nœud arrête et écrit le précédent d’abord (si cette écriture échoue, rien ne démarre, aucun temps perdu). - Pourquoi pas le serveur : il faudrait une table (migration), une API démarrer/arrêter, et une notion d’appareil ou de session pour un outil à un seul utilisateur ; le minuteur n’a de valeur qu’une fois transformé en minutes. Coût accepté : il ne se voit pas d’un autre navigateur ou appareil, et vider les données du site l’efface (le temps non arrêté est perdu).
- La seule écriture est l’arrêt, par la mise à jour d’aspect habituelle (
GETdu nœud pour lire le total courant, puisPATCH /api/nodes/{id}/aspects/{aspect}avec le nouveau total) : validation du JSON Schema, workflow, journal. Chaque arrêt écrit donc unnode_eventaspect.updatedavec l’avant et l’après detempsPasse. Aucune nouvelle route. - Les minutes écoulées sont arrondies à la minute la plus proche ; sous une demi-minute, rien n’est écrit. Un minuteur oublié ne compte pas plus de 12 h d’un coup (le champ reste corrigeable à la main) ; la confirmation le dit.
- Un échec (réseau, session expirée, aspect retiré entre-temps) laisse le minuteur en marche et affiche l’erreur : rien n’est perdu, on peut réessayer ou abandonner.
Interface
- Fiche d’un nœud : dans l’en-tête de l’aspect, « Démarrer le minuteur » ou, en marche, l’horloge, « Arrêter » et « Abandonner sans enregistrer » ; à côté, « 45 min passées sur 1 h 30 estimées » (en couleur d’alerte et « dépassé de 40 min » au-delà). Après un arrêt, la section est relue (comme après « Annuler » d’une action rapide, ADR 0049) : une saisie en cours dans cette même section est gardée et signalée.
- Barre d’état : tant qu’un minuteur tourne, titre de la tâche (lien vers la fiche), horloge et « Arrêter », sur toutes les pages. Confirmation discrète « 25 min ajoutées à « … » ».
Alternatives écartées
- Minuteur serveur : voir plus haut ; reste possible plus tard si l’usage multi-appareils le demande (le champ et l’écriture ne changeraient pas).
- Journal de séances (début, fin, par séance) : plus riche mais un nouveau modèle de données ; le total suffit pour comparer à l’estimation, et l’historique de
node_eventgarde chaque ajout daté. - Ajout atomique côté serveur (
tempsPasse += n) : évite une course entre deux arrêts simultanés sur deux onglets du même nœud, cas qui n’existe pas avec un minuteur unique ; non justifié pour l’instant.
Limites connues
- Pas de minuteur sans stockage local (navigation privée stricte) au-delà de l’onglet courant.
- Deux onglets qui arrêtent le même minuteur en même temps peuvent écrire deux fois ; le second constate un minuteur déjà arrêté seulement après son événement
storage. - Les vues ne montrent pas encore le temps passé ; le champ est un champ d’aspect ordinaire, donc ajoutable à une colonne de vue sans code.
Corrections de la revue
- Saisie en cours à l’arrêt : si la section de l’aspect est modifiée et non enregistrée quand le minuteur s’arrête, la fiche ne relit plus tout. Seul le champ du minuteur (
timer.field, nouveau total) est fusionné dans l’état enregistré et, si l’utilisateur n’y a pas touché, dans le formulaire ; le reste de la saisie est gardé. Si l’utilisateur a modifié ce champ lui-même, un message propre au minuteur l’en avertit (sa valeur est gardée). « Enregistrer » ne réécrit donc plus l’ancien total (PUTde toute la section). - « Arrêter » de la barre d’état est désactivé pendant un enregistrement de la fiche (
saveState« saving »). - Minuteur sans issue : quand
timer.error()est présent (nœud supprimé, aspect retiré), la barre d’état propose « Abandonner ». L’erreur n’est affichée qu’une fois : sur la fiche du nœud concerné si elle est ouverte, dans la barre d’état sinon. - Arrêt concurrent : à la fin d’un arrêt, le minuteur n’est vidé que s’il porte encore le même nœud et le même départ (un abandon ou un minuteur lancé depuis un autre onglet pendant l’écriture n’est pas effacé).