From 4a513f9a5935e869294b67c977a783dd0752f02b Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Mon, 10 Aug 2026 18:07:31 +0200 Subject: [PATCH] =?UTF-8?q?Repousser=20un=20projet=20en=20bloc,=20sauf=20c?= =?UTF-8?q?e=20dont=20on=20ne=20d=C3=A9cide=20pas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Glisser la barre résumé décale toutes les phases du même nombre de jours, écarts conservés : un projet engagé glisse souvent, et le faire phase par phase les déformait. Restent en place ce qui est terminé — on ne réécrit pas le passé — et les phases à date imposée, nouveau booléen `fixed` pour ce qui est délégué ou tenu du dehors. D'où le format en version 6 : sans l'incrément, un binaire antérieur effacerait ces dates imposées à la première sauvegarde. Co-Authored-By: Claude Opus 5 --- README.md | 48 +++++++++--- css/style.css | 50 +++++++++++- data/exemple.json | 12 ++- docs/decisions.md | 142 +++++++++++++++++++++++++++++++--- docs/modele-donnees.md | 32 ++++++-- index.html | 36 +++++++-- js/app.js | 32 +++++++- js/clavier.js | 7 +- js/detail.js | 4 + js/drag.js | 107 +++++++++++++++++++++++++- js/model.js | 100 +++++++++++++++++++++--- js/timeline.js | 113 +++++++++++++++++++++++---- main.go | 2 +- main_test.go | 6 +- tests/model.test.js | 171 +++++++++++++++++++++++++++++++++++++++++ 15 files changed, 788 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index e1b57e2..7eb5394 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,9 @@ survol de la ligne, sur son bord droit : déclarer terminé, écarter, figer la référence, supprimer. Un clic droit sur le libellé ouvre le même menu. -Il n'y a pas de commande séparée pour créer un **jalon** : c'est une phase dont on coche la case -« Jalon » dans le panneau, ce qui ramène sa fin sur son début. La bascule joue dans les deux sens -à tout moment. +Il n'y a pas de commande séparée pour créer un **jalon** ni pour marquer une **date imposée** : ce +sont deux cases du panneau de la phase, indépendantes l'une de l'autre comme du statut. Cocher +« Jalon » ramène la fin sur le début. Les deux bascules jouent dans les deux sens à tout moment. La colonne se **redimensionne** en glissant la ligne qui la sépare de la frise, en haut à gauche : un double-clic revient à la largeur d'origine. Cette largeur est propre au navigateur, @@ -106,14 +106,15 @@ Une fois un projet en place : | Geste | Effet | |---|---| | Glisser le corps d'une barre | Décale la phase, durée conservée | +| Glisser la barre résumé d'un projet | Repousse le projet entier, écarts conservés | | Glisser la barre pâle d'un envisagé | Décale son horizon, au mois | | Glisser le bord d'une barre | Change la date de début ou de fin | | Glisser le bord gauche du panneau | Change sa largeur (double-clic : d'origine) | | Clic sur une barre ou son nom | Ouvre le panneau de détail | | Clic sur une autre barre, panneau ouvert | Bascule le panneau sur cette phase | | Clic sur un nom de phase hors écran | Ramène la frise sur elle | -| `←` `→` | Décale d'un jour la phase du curseur | -| `Maj` + `←` `→` | Allonge ou raccourcit d'un jour | +| `←` `→` | Décale d'un jour la phase du curseur — ou tout le projet, sur sa ligne | +| `Maj` + `←` `→` | Allonge ou raccourcit la phase d'un jour | | `Échap` | Annule le glisser en cours, ou ferme le panneau | | Clic dans une zone vide | Ferme le panneau ouvert | | `Maj` + molette | Fait défiler la frise horizontalement | @@ -122,6 +123,33 @@ Une fois un projet en place : Faire défiler jusqu'au bord droit élargit la frise vers le futur, et jusqu'au bord gauche vers le passé. Le bouton **Aujourd'hui** ramène la vue sur la date du jour. +### Repousser un projet en bloc + +Un projet engagé glisse dans le temps — c'est même ce qui arrive le plus souvent. Plutôt que de +reprendre ses phases une à une, on **glisse sa barre résumé** : le même nombre de jours s'applique à +toutes, jalons compris, et les écarts entre elles sont conservés au jour près. Le projet est +repoussé, il n'est pas replanifié. Les flèches `←` `→` font la même chose au jour près, curseur posé +sur la ligne du projet. + +**Ce qui est terminé ne bouge pas.** Les phases finies restent où elles sont : décaler un projet, +c'est reconnaître qu'il prendra plus de temps, pas réécrire ce qu'on a déjà vécu. Sur un projet à +moitié fait, la barre s'étire donc au lieu de se déplacer — son bord gauche tient et le reste s'en +va. Un projet dont tout est terminé n'offre aucune prise, et son infobulle le dit. + +**Ce dont on ne décide pas non plus.** Une phase peut porter une **date imposée** : elle est +déléguée à quelqu'un d'autre, ou tenue par un contrat, un créneau, une échéance réglementaire. La +case se coche dans son panneau, sous « Jalon », et la barre reçoit alors deux petits taquets à ses +extrémités — un cercle pour un jalon. Repousser le projet ne la repousse pas : si tout le reste +glisse, c'est le créneau qu'on rate, et le chevauchement qui apparaît est justement ce qu'on voulait +voir. Elle reste glissable seule, elle : « imposée » veut dire *indépendante du projet*, pas +*immuable*. + +À ne pas confondre avec le statut **bloqué**, son voisin dans le panneau : bloqué dit que ça +n'avance pas, date imposée que l'échéance ne vous appartient pas. Une phase peut être les deux. + +Le **planning de référence ne suit pas** : repousser de trois semaines affiche aussitôt une dérive +de trois semaines. C'est voulu — le geste inscrit ce qu'il coûte. + ### Le cycle de vie d'un projet On a presque toujours plus de projets imaginables que de temps pour les mener, et l'enjeu n'est pas @@ -315,7 +343,9 @@ pour les deux vues et n'est pas enregistré. ## Ce que fait l'outil - **Plusieurs projets**, chacun décomposé en **phases** ayant un nom, des dates de début et de fin, - un statut, des notes. Une phase peut être un **jalon** (une date unique, rendue en losange). + un statut, des notes. Une phase peut être un **jalon** (une date unique, rendue en losange), et + porter une **date imposée** quand elle est déléguée ou tenue du dehors — le décalage du projet ne + l'emporte alors pas. - **Un cycle de vie** — envisagé, engagé, terminé, écarté — dont deux états se déduisent des données. Ce qu'on envisage sans pouvoir encore le mener vit sur la même frise que le reste, et ne s'y perd pas. @@ -325,8 +355,8 @@ pour les deux vues et n'est pas enregistré. liens. Elles occupent toute la hauteur restante du panneau, et se cochent d'un clic dans l'aperçu. - **Une frise commune**, les projets empilés en couloirs, pour les comparer d'un coup d'œil. - **Une barre cumulative par projet**, segmentée en teintes selon le statut de chaque phase : la - forme d'ensemble, toujours visible sur la ligne du projet et toujours rendue pareil. Elle ne se - glisse pas. + forme d'ensemble, toujours visible sur la ligne du projet et toujours rendue pareil. Elle se + glisse pour repousser le projet entier. - **Couloirs pliables** : déplié, chaque phase occupe sa ligne sous la barre cumulative ; plié, il ne reste que celle-ci. - **Manipulation directe** : glisser une barre pour la décaler, tirer ses bords pour changer sa @@ -424,7 +454,7 @@ données. Aucune dépendance de part ni d'autre, les lanceurs intégrés à Node | `js/timeline.js` | Rendu de la frise : échelle, couloirs, barres, jalons, pli/dépli, curseur | | `js/mois.js` | Rendu de la vue par mois : groupes, lignes de phase et de projet envisagé, ancrage sur le mois courant | | `js/tags.js` | Pastilles de tags, partagées par les deux vues | -| `js/drag.js` | Glisser et redimensionner les barres — au lundi pour une phase, au mois pour un horizon | +| `js/drag.js` | Glisser et redimensionner les barres — au lundi pour une phase ou un projet, au mois pour un horizon | | `js/clavier.js` | Table des raccourcis à la vim, préfixes `g` `z` `d`, garde-fou de saisie | | `js/markdown.js` | Analyse du markdown des notes, aplatissement pour les infobulles. Sans DOM, donc testable sous Node | | `js/notes.js` | Rendu du markdown en DOM et bloc de notes — aperçu, barre d'outils, saisie — partagé par les deux panneaux | diff --git a/css/style.css b/css/style.css index fad8a57..1b0942b 100644 --- a/css/style.css +++ b/css/style.css @@ -829,6 +829,24 @@ button[aria-pressed="true"] { ); } +/* Une phase dont la date ne nous appartient pas — déléguée, contractuelle, + réglementaire (docs/decisions.md, section 28). + + Deux **taquets** verticaux à ses extrémités, comme les butées d'une pièce + qu'on ne peut plus faire coulisser. La marque se cumule avec les quatre + textures de statut au lieu de leur disputer la place : c'est bien une phase à + venir, en cours ou bloquée, qui se trouve en plus tenue par le dehors. + + En `inset`, donc sans toucher à la géométrie : la barre garde exactement la + largeur de ses dates, et l'ombre portée reste déclarée à côté — un + `box-shadow` ne se cumule pas d'une règle à l'autre, il se remplace. */ +.barre--imposee { + box-shadow: + var(--ombre), + inset 3px 0 0 var(--texte), + inset -3px 0 0 var(--texte); +} + /* Poignées de redimensionnement, révélées au survol de la barre. */ .poignee { position: absolute; @@ -884,6 +902,15 @@ button[aria-pressed="true"] { background: var(--danger); } +/* Un losange n'a pas d'extrémités où poser les taquets d'une barre imposée : il + se cercle. Déclarée après `.jalon`, dont elle partage la spécificité — c'est + l'ordre qui la fait gagner, un `box-shadow` se remplaçant en bloc. */ +.jalon--impose { + box-shadow: + var(--ombre), + inset 0 0 0 2px var(--texte); +} + .jalon--selectionne { outline: 2px solid var(--texte); outline-offset: 1px; @@ -907,8 +934,8 @@ button[aria-pressed="true"] { déplié, et rendue à l'identique dans les deux cas — c'est le même objet, il n'a pas à changer d'aspect quand on ouvre le couloir. Volontairement plus mince qu'une barre de phase : elle voisine avec elles - dès que le projet est déplié, et ne se glisse pas. - Lecture seule : voir docs/decisions.md, section 8. */ + dès que le projet est déplié. + Elle se glisse pour repousser le projet : voir docs/decisions.md, section 8. */ .barre-cumulative { position: absolute; top: calc((var(--hauteur-ligne) - var(--hauteur-cumulative)) / 2); @@ -921,6 +948,25 @@ button[aria-pressed="true"] { z-index: 2; } +/* La prise ne s'annonce que là où elle mène quelque part : sur un projet dont + tout est terminé, la barre n'a pas de rôle et garde le curseur par défaut — + rien ne bougerait. */ +.barre-cumulative[data-role='cumul'] { + cursor: grab; +} + +.barre-cumulative[data-role='cumul']:hover { + background: color-mix(in srgb, var(--couleur-projet) 28%, var(--surface)); +} + +/* Mince comme elle est, une barre cumulative saisie se repère mal. L'ombre la + décolle du couloir le temps du geste, sans changer sa géométrie — ce qui + décalerait ce qu'on est en train de viser. */ +.barre-cumulative.barre--glissee { + cursor: grabbing; + box-shadow: 0 0 0 2px color-mix(in srgb, var(--couleur-projet) 60%, transparent); +} + .segment { position: absolute; top: 0; diff --git a/data/exemple.json b/data/exemple.json index 60b803c..2d616aa 100644 --- a/data/exemple.json +++ b/data/exemple.json @@ -1,5 +1,5 @@ { - "version": 4, + "version": 6, "projects": [ { "id": "site-web", @@ -99,6 +99,16 @@ "milestone": false, "notes": "**Bloquée** : en attente de la fenêtre de maintenance côté hébergeur.\n\n- [x] demande déposée le 3 août\n- [ ] créneau confirmé\n" }, + { + "id": "fenetre-hebergeur", + "name": "Fenêtre de maintenance", + "start": "2026-09-12", + "end": "2026-09-13", + "status": "todo", + "milestone": false, + "fixed": true, + "notes": "Créneau imposé par l'hébergeur : un seul week-end par trimestre.\n\nRepousser le projet ne la repousse pas — si le reste glisse, c'est ce créneau qu'on rate.\n" + }, { "id": "bascule-generale", "name": "Bascule générale", diff --git a/docs/decisions.md b/docs/decisions.md index be04070..ac86fab 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -102,15 +102,62 @@ Gérer les jours ouvrés impose de faire passer chaque calcul de date par un hel aussitôt la question des congés et des jours fériés — donc un calendrier à tenir à jour. Sur des phases qui se comptent en semaines et en mois, l'écart est dans le bruit de l'estimation. -## 8. La barre cumulative est en lecture seule +## 8. La barre cumulative repousse le projet -La ligne d'un projet porte une barre unique segmentée par phase, qui donne sa forme d'ensemble. On -aurait pu la rendre glissable pour décaler tout le projet en bloc. +La ligne d'un projet porte une barre unique segmentée par phase, qui donne sa forme d'ensemble. Elle +se glisse, et le projet entier se décale avec elle. -Écarté pour une raison de lisibilité du geste : la même barre représenterait tantôt une phase, tantôt -un projet entier, et un glissement de quelques pixels déplacerait alors plusieurs mois de travail -d'un coup, sans que l'utilisateur voie ce qui bouge. Elle sert à regarder ; pour modifier, on agit -sur les phases. +**Ce n'était pas le cas au départ, et le motif du refus mérite d'être conservé** : la même barre +représenterait tantôt une phase, tantôt un projet entier, et un glissement de quelques pixels +déplacerait alors plusieurs mois de travail d'un coup, sans qu'on voie ce qui bouge. L'argument était +juste ; c'est le premier usage réel de l'outil qui l'a renversé. Un projet engagé glisse dans le +temps — c'est même la chose qui arrive le plus souvent —, et le faire glisser phase par phase oblige +à répéter le même geste cinq fois en priant pour que les écarts se conservent. Or ils ne se +conservent pas : chaque phase s'accroche à son propre lundi, et un projet ainsi déplacé se +déforme un peu à chaque passage. + +L'objection d'origine n'a pas été balayée, elle a été traitée : + +- **Les barres de phase suivent le résumé pendant le geste.** On voit exactement ce qu'on déplace + pendant qu'on le déplace, y compris plié — où les segments colorés de la barre suffisent à le + montrer. C'était le cœur du reproche, et il tombe. +- **Le delta est calculé une fois, sur la première phase qui bouge, puis appliqué tel quel + partout.** Les écarts entre phases sont conservés au jour près : le projet est repoussé, il n'est + pas replanifié. C'est précisément ce que le geste phase par phase ne savait pas faire. +- **La référence figée ne suit pas.** Repousser de trois semaines affiche aussitôt une dérive de + trois semaines. Le geste ne cache pas ce qu'il coûte, il l'inscrit. + +### Ce qui est fait est fait + +Une phase `done` ne bouge pas. Décaler un projet, c'est reconnaître qu'il prendra plus de temps que +prévu, et emporter les phases terminées réécrirait un passé qu'on a vécu — en effaçant du même coup +la dérive qu'on cherche à lire. + +Le geste ne « déplace » donc pas toujours : sur un projet à moitié fait, la barre s'**étire**, son +bord gauche tient et le reste s'en va. C'est assumé, et c'est même ce qu'on veut voir. Le résumé se +repose alors depuis le modèle à chaque mouvement de souris plutôt que d'être translaté, faute de quoi +l'aperçu et le résultat divergeraient. + +Le critère est le **statut**, jamais la position dans le calendrier : une phase terminée en avance +reste où elle est, une phase `blocked` ou `doing` déjà commencée se décale avec le reste — c'est bien +elle qui glisse. Un projet dont toutes les phases sont closes n'a rien à repousser : sa barre perd +son `data-role`, garde le curseur par défaut, et le dit dans son infobulle plutôt que d'offrir une +prise qui ne mènerait nulle part. + +Rien n'empêche un décalage vers l'arrière de faire chevaucher une phase avec une phase terminée. Il +n'y a pas de dépendances entre phases dans ce modèle (section 2) et poser ici une butée en +inventerait une par la bande. + +### Pas de poignées + +La barre cumulative se déplace, elle ne se redimensionne pas. Étirer un projet supposerait de dilater +chaque phase au prorata, avec des arrondis qui déforment les durées courtes et un résultat qu'on ne +peut pas prévoir avant de l'avoir vu. C'est un autre besoin, et il attendra de se présenter. + +Le même geste existe au clavier : sur la ligne d'un projet, `←` et `→` le repoussent d'un jour, là où +elles ne faisaient rien. `Maj` n'y ajoute rien, pour la raison qui précède. + +### Ce qui n'a pas changé **Elle est affichée que le projet soit plié ou déplié.** Elle ne l'était d'abord qu'en mode plié, ce qui obligeait à replier pour savoir où en était le projet entier — donc à perdre la vue qu'on était @@ -120,8 +167,10 @@ en train d'éditer. plié, au motif qu'elle y était seule sur sa ligne. C'était une erreur : plier un couloir ne change rien à ce que cette barre représente, et la voir changer d'aspect au pli laissait croire à deux objets différents. Elle garde donc partout la même hauteur, plus mince qu'une barre de phase — -puisqu'elle voisine avec elles dès que le projet est déplié, et qu'elle ne se glisse pas. Le fantôme -de référence se cale dessous, d'où sa variante `--cumulatif`. +puisqu'elle voisine avec elles dès que le projet est déplié. Le fantôme de référence se cale dessous, +d'où sa variante `--cumulatif`. Étant mince, elle se repère mal une fois saisie : un anneau à sa +couleur la décolle du couloir le temps du geste, sans toucher à sa géométrie, ce qui décalerait ce +qu'on est en train de viser. ## 9. Accroche à la semaine @@ -1052,3 +1101,78 @@ urgence en début de mandat. **Un retour arrière déclaré**, d'engagé vers envisagé. On déplace les dates, cela suffit. Le seul cas où il survient — retirer la dernière phase — se résout tout seul par la règle des bornes. + +## 28. Une date qu'on ne décide pas + +Toutes les phases d'un planning ne sont pas nôtres. Certaines sont **déléguées** — un prestataire les +tient, on n'a que le résultat —, d'autres portent une **contrainte extérieure** : une fenêtre de +maintenance chez l'hébergeur, une échéance réglementaire, une date de salon. Une phase reçoit donc un +booléen `fixed`, et le décalage d'un projet (section 8) ne l'emporte pas. + +Sans elle, le geste ajouté en section 8 était activement nuisible : repousser un projet de trois +semaines repoussait aussi l'audit du commissaire aux comptes, ce qui est faux et ce que rien ne +signalait. + +### Pourquoi ce n'est pas un cinquième statut + +C'était la formulation spontanée — « ajouter un état ». Elle a été écartée, parce que les quatre +statuts existants disent tous **l'avancement** (section 6), et qu'une date imposée n'est pas une +étape du travail : une phase déléguée est tour à tour à venir, en cours, terminée, et elle reste +déléguée tout du long. En faire une cinquième valeur aurait créé une exclusion mutuelle fausse — on +ne pourrait plus dire d'une phase déléguée qu'elle est bloquée, ce qui est précisément la situation +qu'on veut voir venir de loin. + +Le modèle avait déjà le patron : **`milestone` est un booléen à côté de `status`**, pas un statut. Un +jalon peut être `todo` ou `done` sans que cela pose la moindre question. `fixed` suit exactement le +même chemin, et les deux se combinent librement — un jalon à date imposée est le cas le plus courant +des deux. + +**À ne pas confondre avec `blocked`**, dont il est le voisin immédiat dans un formulaire : +`blocked` dit *ça n'avance pas*, `fixed` dit *cette date ne m'appartient pas*. Une phase peut être +les deux, l'une ou l'autre, ou aucune. Le libellé retenu — « date imposée » plutôt que « fixe » — +sert d'abord à cela : il nomme la cause, là où « fixe » n'aurait nommé que l'effet, et six mois plus +tard c'est la cause qu'on cherche en relisant. + +### Le verrou est souple + +Une phase à date imposée **se glisse toujours seule**, à la souris comme aux flèches. Seul le +décalage de son projet l'épargne. « Imposée » veut donc dire *indépendante du projet*, et non +*immuable* : quand l'hébergeur déplace son créneau, on déplace la barre, sans rien avoir à décocher. + +Le verrou dur avait été envisagé — barre non glissable, dates saisies au panneau — au motif qu'on +marque une phase pour la protéger d'un geste accidentel. Il ajoutait une friction permanente pour un +risque occasionnel, et il aurait fait diverger cette case de son modèle, `milestone`, qui n'empêche +rien non plus. + +Corollaire : un projet dont toutes les phases sont terminées **ou** imposées n'offre aucune prise, et +sa barre cumulative le dit dans son infobulle. C'est le comportement déjà écrit pour les projets +entièrement clos ; il n'a pas eu à changer, seule la liste des phases emportées s'est resserrée. + +### Ce que ça donne à l'œil + +Deux **taquets** verticaux aux extrémités de la barre, comme les butées d'une pièce qui ne coulisse +plus ; un **cercle** pour un jalon, qui n'a pas d'extrémités où en poser. La marque se cumule avec +les quatre textures de statut au lieu de leur disputer la place — c'est bien une phase à venir, en +cours ou bloquée, qui se trouve en plus tenue par le dehors. + +Elle se pose en `box-shadow` interne, donc sans toucher à la géométrie : la barre garde exactement la +largeur de ses dates. Sur une phase de deux jours, les taquets occupent presque toute la barre et la +texture de statut disparaît ; c'est accepté, une fenêtre de maintenance de deux jours se lit de toute +façon à son infobulle. + +### Le format passe en version 6 + +Un champ ajouté n'y obligerait pas — la v5 se relit sans encombre. Ce qui y oblige, c'est le sens du +numéro : le validateur reconstruit chaque phase champ par champ et laisse tomber ce qu'il ne connaît +pas. Sans incrément, un binaire antérieur ouvrirait un fichier v6 sans broncher et en effacerait +toutes les dates imposées à la première sauvegarde — les phases concernées se remettant alors à +suivre le décalage de leur projet, en silence. C'est exactement le genre de perte que ce numéro +existe pour empêcher. + +La migration ne devine rien : toute phase héritée reçoit `false`. Rien dans les données ne permettrait +d'inférer laquelle était déléguée, et un faux positif ici immobiliserait une phase sans qu'on +comprenne pourquoi. + +Au passage, `donneesInitiales` dans `main.go` a été remis d'aplomb : le serveur écrivait encore +`version 4` pour un planning vide, deux versions en retard. Sans conséquence — le validateur accepte +un fichier plus ancien et le remonte — mais un fichier neuf se déclarait d'avant le cycle de vie. diff --git a/docs/modele-donnees.md b/docs/modele-donnees.md index 2d5424f..76bd867 100644 --- a/docs/modele-donnees.md +++ b/docs/modele-donnees.md @@ -9,7 +9,7 @@ dans un diff git et éditable à la main. ```json { - "version": 5, + "version": 6, "projects": [ { "id": "site-web", @@ -29,6 +29,7 @@ dans un diff git et éditable à la main. "end": "2026-08-20", "status": "done", "milestone": false, + "fixed": false, "notes": "Ateliers avec les trois pôles.\n\nPérimètre arrêté le **8 juillet**.", "baseline": { "start": "2026-07-25", "end": "2026-08-10" } }, @@ -39,7 +40,8 @@ dans un diff git et éditable à la main. "end": "2026-11-02", "status": "todo", "milestone": true, - "notes": "" + "fixed": true, + "notes": "Date annoncée au client." } ] } @@ -51,18 +53,21 @@ dans un diff git et éditable à la main. | Champ | Type | Description | |---|---|---| -| `version` | entier | Version du format. Vaut `5`. Sert à détecter un fichier trop ancien au chargement. | +| `version` | entier | Version du format. Vaut `6`. Sert à détecter un fichier trop ancien au chargement. | | `projects` | tableau | Les projets, dans l'ordre d'affichage des couloirs. | Un fichier plus ancien se charge sans rien demander et est réécrit au format courant à la première sauvegarde : une version `2` — celle d'avant les tags — voit ses projets recevoir une liste de tags vide, une version `3` — celle d'avant les notes de projet — des notes vides, une version `4` — celle -d'avant le cycle de vie — des projets sans horizon ni date d'acte. +d'avant le cycle de vie — des projets sans horizon ni date d'acte, une version `5` — celle d'avant +les dates imposées — des phases à `fixed: false`. -Cette dernière migration ne demande rien et ne devine rien : un projet qui a des phases est *engagé*, -un projet qui n'en a pas est *envisagé sans horizon*, et il se signalera comme réclamant une date. -Aucun projet n'est déclaré terminé ni écarté au chargement — ces deux états s'actent, ils ne se -devinent pas, fût-ce d'un planning dont toutes les phases sont finies. +Aucune de ces migrations ne demande ni ne devine quoi que ce soit. Un projet qui a des phases est +*engagé*, un projet qui n'en a pas est *envisagé sans horizon*, et il se signalera comme réclamant une +date. Aucun projet n'est déclaré terminé ni écarté au chargement — ces deux états s'actent, ils ne se +devinent pas, fût-ce d'un planning dont toutes les phases sont finies. Et aucune phase n'est déclarée +à date imposée : rien dans les données ne dirait laquelle est déléguée, et un faux positif +immobiliserait une phase sans qu'on comprenne pourquoi. Un fichier écrit par une version **plus récente** est en revanche refusé. C'est la raison d'être du numéro : le validateur reconstruit chaque projet champ par champ et laisse tomber ce qu'il ne connaît @@ -96,6 +101,7 @@ effacerait les champs inconnus à la première sauvegarde. | `end` | chaîne | Date de fin **incluse**, `AAAA-MM-JJ`. | | `status` | chaîne | `todo`, `doing`, `done` ou `blocked`. | | `milestone` | booléen | `true` pour un jalon. | +| `fixed` | booléen | `true` si la date est imposée du dehors — phase déléguée, contrainte contractuelle ou réglementaire. Un décalage du projet ne l'emporte pas. | | `notes` | chaîne | Texte libre en markdown, éventuellement vide. | | `baseline` | objet ou absent | Dates de référence : `{ "start": …, "end": … }`. Absent tant que la référence n'a pas été figée. | @@ -113,6 +119,10 @@ Appliquées par `js/model.js` et couvertes par `tests/model.test.js`. chiffre remplacé par un tiret. En cas de collision, un suffixe numérique est ajouté (`cadrage-2`). Un `id` ne change jamais si le nom est modifié ensuite — il identifie, il ne décrit pas. - `status` fait partie des quatre valeurs autorisées ; toute autre valeur est ramenée à `todo`. +- `fixed` est orthogonal au reste : aucune combinaison n'est interdite. Une phase peut être à la fois + jalon et à date imposée — c'est même le cas le plus courant des deux —, ou `blocked` et imposée, ce + qui n'est pas une contradiction : `blocked` dit que ça n'avance pas, `fixed` que la date ne nous + appartient pas. - `color` est un hexadécimal `#rrggbb` valide. - `tags` est un tableau de chaînes. Le tableau lui-même et le type de ses éléments sont vérifiés — un tag mal typé serait un tag qu'on croit poser et qui ne filtre rien. Leur *contenu*, en revanche, @@ -270,6 +280,12 @@ l'année correspondante, qui peut différer de celle de la date. fenêtre du planning au premier affichage — sans quoi un planning fait de projets encore tous envisagés s'ouvrirait sur rien. Les projets écartés en sont exclus : ils ne s'affichent pas d'ordinaire, et un projet abandonné il y a trois ans n'a pas à tirer la vue en arrière. +- **Repousser un projet** (`decalerProjet()`) ajoute le même nombre de jours aux `start` et `end` des + phases qu'il emporte — `phasesDecalables()` les désigne, `ancreDecalage()` donne la date sur + laquelle le geste s'accroche. Deux sortes en sont exclues, pour des raisons sans rapport : les + phases **terminées**, parce qu'on ne réécrit pas le passé, et les phases à **date imposée**, parce + qu'on n'en décide pas. Les `baseline` ne bougent pas non plus : le décalage se lit alors + intégralement dans la dérive. - **Figer la référence** copie, pour chaque phase du projet, ses `start` et `end` courantes dans son champ `baseline`, et inscrit la date du jour dans `baselineDate`. L'opération est rejouable : la refiger après un arbitrage assumé repart d'une base propre. diff --git a/index.html b/index.html index 1dcdfa9..2ffa064 100644 --- a/index.html +++ b/index.html @@ -165,6 +165,18 @@ + + +
+ + + - -