Frise multi-projets : outil de planification macro

Une frise chronologique unique où plusieurs projets s'empilent en couloirs
pliables, pour voir d'un coup d'œil où en est chacun et comment ils se situent
les uns par rapport aux autres. Ce n'est pas un outil de suivi quotidien : une
phase se compte en semaines, et il n'y a ni sous-tâches, ni tickets, ni
dépendances entre phases.

- Manipulation directe des barres (glisser, redimensionner), accrochées au lundi
- Couloirs pliables : plié, un projet devient une barre segmentée par phase
- Planning de référence figeable, avec barre fantôme et calcul de dérive
- En-tête à trois bandes : année, mois, numéro de semaine ISO 8601
- Frise qui s'élargit au défilement, pour planifier dans un futur encore vide
- Micro-serveur Python (bibliothèque standard) exposant GET/PUT sur /api/data,
  avec sauvegarde horodatée avant chaque écriture
- Zéro build : modules ES natifs, aucune dépendance à installer

50 tests unitaires sur la logique métier (node --test, sans dépendance).
Vérifié dans Chromium et Firefox.

Les arbitrages de conception et surtout leurs raisons sont consignés dans
docs/decisions.md — notamment l'abandon des dépendances entre phases, de la
File System Access API, du SVG et des niveaux de zoom.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 06:53:57 +02:00
commit 98ae311545
16 changed files with 4766 additions and 0 deletions

123
docs/modele-donnees.md Normal file
View File

@@ -0,0 +1,123 @@
# Modèle de données
Tout le planning tient dans un seul fichier JSON, par défaut `data/projets.json`.
Le fichier est écrit indenté sur 2 espaces, avec les clés dans un ordre stable, pour rester lisible
dans un diff git et éditable à la main.
## Structure
```json
{
"version": 2,
"projects": [
{
"id": "site-web",
"name": "Refonte du site web",
"color": "#3b82f6",
"collapsed": false,
"hidden": false,
"baselineDate": "2026-06-12",
"phases": [
{
"id": "cadrage",
"name": "Cadrage",
"start": "2026-08-01",
"end": "2026-08-20",
"status": "done",
"milestone": false,
"notes": "Ateliers avec les trois pôles.",
"baseline": { "start": "2026-07-25", "end": "2026-08-10" }
},
{
"id": "livraison",
"name": "Mise en ligne",
"start": "2026-11-02",
"end": "2026-11-02",
"status": "todo",
"milestone": true,
"notes": ""
}
]
}
]
}
```
## Racine
| Champ | Type | Description |
|---|---|---|
| `version` | entier | Version du format. Vaut `2`. Sert à détecter un fichier trop ancien au chargement. |
| `projects` | tableau | Les projets, dans l'ordre d'affichage des couloirs. |
## Projet
| Champ | Type | Description |
|---|---|---|
| `id` | chaîne | Identifiant parlant, dérivé du nom (`Refonte du site web``site-web`). Unique dans le fichier. |
| `name` | chaîne | Nom affiché. Non vide. |
| `color` | chaîne | Couleur du couloir, en hexadécimal `#rrggbb`. |
| `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. |
| `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. |
## Phase
| Champ | Type | Description |
|---|---|---|
| `id` | chaîne | Identifiant parlant, unique **au sein de son projet**. |
| `name` | chaîne | Nom affiché. Non vide. |
| `start` | chaîne | Date de début, `AAAA-MM-JJ`. |
| `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. |
| `notes` | chaîne | Texte libre, é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. |
## Règles de validation
Appliquées par `js/model.js` et couvertes par `tests/model.test.js`.
- `end` est postérieure ou égale à `start`.
- Un jalon (`milestone: true`) a nécessairement `start === end`. Cocher « jalon » sur une phase de
plusieurs jours ramène `end` sur `start`.
- Les dates suivent strictement `AAAA-MM-JJ` et doivent exister réellement dans le calendrier
(`2026-02-30` est rejetée).
- `id` de projet unique dans le fichier ; `id` de phase unique dans son projet.
- Un `id` est engendré à partir du nom : minuscules, accents retirés, tout ce qui n'est ni lettre ni
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`.
- `color` est un hexadécimal `#rrggbb` valide.
Un fichier invalide n'est jamais réparé en silence : le chargement échoue avec un message qui pointe
le projet et la phase fautifs, pour que le fichier puisse être corrigé à la main.
## Dates : conventions
Les dates sont manipulées comme des **chaînes `AAAA-MM-JJ`**, pas comme des objets `Date`. Cela évite
tous les pièges de fuseau horaire : `new Date("2026-08-01")` est interprétée en UTC alors que
`new Date(2026, 7, 1)` l'est en heure locale, ce qui décale d'un jour selon le fuseau. Les
conversions n'ont lieu que dans les helpers de calcul de `model.js`.
`end` est **incluse** : une phase du `2026-08-01` au `2026-08-01` dure un jour.
Tout se compte en **jours calendaires**, week-ends et jours fériés compris.
Les numéros de semaine affichés dans l'en-tête suivent l'**ISO 8601** : la semaine
appartient à l'année où tombe son jeudi. `semaineISO()` renvoie donc le numéro *et*
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.
- **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.
- La **dérive** d'un projet est l'écart, en jours, entre la plus grande `end` actuelle et la plus
grande `end` de référence. Elle est affichée arrondie en semaines. Une phase créée après le figeage
n'a pas de `baseline` et ne compte pas dans la référence, mais compte dans les dates actuelles :
ajouter une phase en fin de projet crée donc bien une dérive, ce qui est le comportement voulu.