Second Brain documentation

ADR 0106 – Minuteur sur une tâche

Accepté (2026-10-17, vague 3 du document d'idées)

← Toutes les décisions

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.timer est exposé par l’API des définitions, le client généré porte Timer.
  • Aucun nom en dur : le moteur et le front ne connaissent ni « tache » ni « tempsPasse » ; tout aspect qui déclare timer reçoit le minuteur dans sa section de fiche. config/aspects/tache.yaml ajoute tempsPasse (modifiable à la main, dans l’ordre des champs après estimation) 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 (GET du nœud pour lire le total courant, puis PATCH /api/nodes/{id}/aspects/{aspect} avec le nouveau total) : validation du JSON Schema, workflow, journal. Chaque arrêt écrit donc un node_event aspect.updated avec l’avant et l’après de tempsPasse. 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_event garde 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 (PUT de 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é).