Porter le cycle de vie et l'horizon dans le modèle, en version 5
L'état n'est pas un champ : il se lit dans horizon, completedDate, discardedDate et la présence de phases. Les phases commandent, sauf quand on a prononcé quelque chose. L'emprise d'un projet retombe sur son horizon faute de phases, et une saisie n'est jamais réécrite par un calcul : un projet qui perd sa dernière phase retrouve l'horizon qu'il avait déclaré. Clore un projet termine aussi toutes ses phases, seule opération du cycle qui écrase des données. phasesParMois devient entreesParMois : elle émet aussi les projets sans phase, à leur horizon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ dans un diff git et éditable à la main.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 4,
|
||||
"version": 5,
|
||||
"projects": [
|
||||
{
|
||||
"id": "site-web",
|
||||
@@ -19,6 +19,7 @@ dans un diff git et éditable à la main.
|
||||
"collapsed": false,
|
||||
"hidden": false,
|
||||
"notes": "## Contexte\n\nPiloté par **Marie D.**\n\n- [x] cadrage validé\n- [ ] recette",
|
||||
"horizon": { "start": "2026-08", "end": "2026-11" },
|
||||
"baselineDate": "2026-06-12",
|
||||
"phases": [
|
||||
{
|
||||
@@ -50,12 +51,18 @@ dans un diff git et éditable à la main.
|
||||
|
||||
| Champ | Type | Description |
|
||||
|---|---|---|
|
||||
| `version` | entier | Version du format. Vaut `4`. Sert à détecter un fichier trop ancien au chargement. |
|
||||
| `version` | entier | Version du format. Vaut `5`. 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
@@ -73,6 +80,9 @@ effacerait les champs inconnus à la première sauvegarde.
|
||||
| `collapsed` | booléen | `true` si le couloir est plié. |
|
||||
| `hidden` | booléen | `true` si le projet est masqué de la frise. Ses données restent intactes. |
|
||||
| `notes` | chaîne | Texte libre en markdown, éventuellement vide. Ce que la frise ne sait pas dire : contexte, interlocuteurs, décisions. |
|
||||
| `horizon` | objet ou absent | Emprise déclarée, en mois : `{ "start": "2027-03", "end": "2027-06" }`. Fin **incluse**. C'est l'ancrage d'un projet qui n'a pas encore de phase. |
|
||||
| `completedDate` | chaîne ou absent | Date à laquelle le projet a été déclaré terminé. Absent tant qu'il ne l'a pas été. |
|
||||
| `discardedDate` | chaîne ou absent | Date à laquelle le projet a été écarté. Absent tant qu'il ne l'a pas été. |
|
||||
| `baselineDate` | chaîne ou absent | Date à laquelle la référence a été figée. Absent si elle ne l'a jamais été. |
|
||||
| `phases` | tableau | Les phases, triées par date de début croissante. |
|
||||
|
||||
@@ -110,6 +120,12 @@ Appliquées par `js/model.js` et couvertes par `tests/model.test.js`.
|
||||
- `notes`, de projet comme de phase, est ramenée à `""` si ce n'est pas une chaîne. Le champ n'engage
|
||||
aucun calcul, contrairement aux dates : bloquer le chargement d'un planning entier pour lui serait
|
||||
disproportionné.
|
||||
- `horizon` est facultatif, mais s'il est présent il porte deux mois `AAAA-MM` réels, et sa fin
|
||||
n'est pas antérieure à son début. Il est vérifié aussi strictement que les dates d'une phase, et
|
||||
non réparé en silence : il décide d'une position sur la frise, et un horizon avalé laisserait un
|
||||
projet envisagé invisible sans qu'on sache pourquoi.
|
||||
- `completedDate` et `discardedDate` suivent les mêmes règles que `baselineDate` : absentes, ou
|
||||
dates `AAAA-MM-JJ` réelles.
|
||||
|
||||
## Tags
|
||||
|
||||
@@ -178,6 +194,58 @@ celui qui la lit. Un lien refusé n'est pas effacé : son libellé redevient du
|
||||
|
||||
Voir [decisions.md](decisions.md), section 25.
|
||||
|
||||
## Cycle de vie et horizon
|
||||
|
||||
L'état d'un projet — envisagé, engagé, terminé, écarté — **n'est pas un champ**. Il se lit dans les
|
||||
données, et `etatProjet()` applique une règle unique : *les phases commandent l'état, sauf quand on a
|
||||
prononcé quelque chose.*
|
||||
|
||||
| État | Comment il se lit | Ce qu'il veut dire |
|
||||
|---|---|---|
|
||||
| `considered` | ni `discardedDate`, ni `completedDate`, ni phase | Envisagé : un horizon pour seul ancrage |
|
||||
| `engaged` | au moins une phase | Engagé : il a des dates fermes |
|
||||
| `completed` | `completedDate` présente | Terminé, et quelqu'un l'a prononcé |
|
||||
| `discarded` | `discardedDate` présente | Écarté : on a décidé de ne pas le faire |
|
||||
|
||||
Terminer et écarter s'actent, ils ne se devinent pas : un projet dont toutes les phases sont `done`
|
||||
reste *engagé* tant que sa clôture n'a pas été prononcée — livré n'est pas clos. Ces deux actes
|
||||
laissent une date, et ces deux dates sont l'unique trace de l'état dans le fichier. Un projet ne
|
||||
porte jamais les deux à la fois ; si un fichier retouché à la main le fait, l'écartement l'emporte.
|
||||
|
||||
**Clore un projet passe toutes ses phases à `done`**, jalons compris. C'est la seule opération du
|
||||
cycle qui écrase des données : le statut d'une phase `blocked` ou `todo` est perdu, et
|
||||
`rouvrirProjet()` ne le rétablit pas — il n'a aucun moyen de savoir ce qu'il valait.
|
||||
`clotureEcraseDesStatuts()` dit s'il reste quelque chose à écraser, ce qui permet à l'interface de
|
||||
ne demander confirmation que dans ce cas.
|
||||
|
||||
**Réanimer un projet écarté** ne demande donc rien d'autre que d'effacer `discardedDate` : il
|
||||
retrouve seul l'état que ses données commandent, engagé s'il a des phases, envisagé sinon. Son
|
||||
horizon est resté celui d'avant et se signalera aussitôt comme dépassé, ce qui est voulu.
|
||||
|
||||
### L'horizon
|
||||
|
||||
Un projet envisagé n'est jamais hors du temps : il déclare son emprise en **mois**, fin incluse. Un
|
||||
horizon d'un seul mois a `start === end`.
|
||||
|
||||
Le grain est le mois et jamais l'année, et l'horizon s'exprime comme un intervalle plutôt que par des
|
||||
crans nommés — ni trimestre ni semestre à apprendre. La largeur de l'intervalle *est* l'incertitude,
|
||||
et elle se resserre à mesure que le projet mûrit, jusqu'aux dates fermes de l'engagement.
|
||||
|
||||
L'horizon **survit à l'engagement** : il n'est ni effacé ni réécrit quand des phases arrivent. C'est
|
||||
ce qui permet à un projet ayant perdu sa dernière phase de retrouver l'horizon qu'il avait déclaré,
|
||||
plutôt que l'enveloppe de phases disparues — **on ne réécrit jamais une saisie par un calcul**.
|
||||
|
||||
Il se manœuvre au **mois** partout : les crans du panneau, le glisser sur la frise et le
|
||||
redimensionnement de ses bords passent tous par `ajouterMois()` et n'atteignent jamais le jour.
|
||||
`entreesParMois()` rattache d'ailleurs un projet envisagé au premier mois de son horizon, pour qu'il
|
||||
paraisse dans la vue par mois au même titre qu'une phase qui démarre.
|
||||
|
||||
Un projet envisagé **sans** horizon est légal : c'est l'état de tous les projets sans phase hérités
|
||||
d'un fichier en version 4. Il réclame une date, exactement comme un projet dont l'horizon est
|
||||
dépassé — `projetADater()` confond volontairement les deux cas en un seul signal.
|
||||
|
||||
Voir [decisions.md](decisions.md), section 27.
|
||||
|
||||
## Dates : conventions
|
||||
|
||||
Les dates sont manipulées comme des **chaînes `AAAA-MM-JJ`**, pas comme des objets `Date`. Cela évite
|
||||
@@ -195,8 +263,13 @@ l'année correspondante, qui peut différer de celle de la date.
|
||||
|
||||
## Bornes et dérive
|
||||
|
||||
- Les **bornes d'un projet** vont de la plus petite `start` à la plus grande `end` de ses phases. Un
|
||||
projet sans phase n'a pas de bornes et s'affiche comme un couloir vide.
|
||||
- Les **bornes d'un projet** (`bornesProjet()`) vont de la plus petite `start` à la plus grande `end`
|
||||
de ses phases. Un projet sans phase n'a pas de bornes.
|
||||
- Son **emprise** (`empriseProjet()`) est celle de ses phases, ou à défaut celle de son horizon,
|
||||
ramenée du premier jour de son premier mois au dernier jour de son dernier. C'est elle qui cadre la
|
||||
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.
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user