Une petite mairie a plus de projets imaginables que de temps pour les mener, et la crainte première est d'en oublier. La section 27 pose les quatre états — envisagé, engagé, terminé, écarté —, l'horizon en mois des envisagés, et surtout ce qu'on écarte : la matrice d'Eisenhower, un backlog séparé, une vue de rétrospective. Elle garde aussi la trace de ce que le premier essai a corrigé, dont l'immobilité de la barre d'horizon, abandonnée à l'usage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1055 lines
64 KiB
Markdown
1055 lines
64 KiB
Markdown
# Décisions de conception
|
||
|
||
Ce document garde la trace des arbitrages et surtout de leurs raisons, pour éviter de refaire les
|
||
mêmes débats dans six mois.
|
||
|
||
## 1. Pourquoi un outil sur-mesure
|
||
|
||
Les suites complètes (Redmine, Taiga, OpenProject) exigent un serveur applicatif et une base de
|
||
données à administrer — hors de proportion avec le besoin. Les mini-outils « un seul fichier HTML »
|
||
sont des Kanban sans dates.
|
||
|
||
[Markwhen](https://markwhen.com) est le concurrent sérieux : timeline en couloirs engendrée depuis du
|
||
texte markdown-ish, open source, self-hostable, avec extension VS Code et vue Gantt. Il couvre une
|
||
bonne partie du besoin.
|
||
|
||
Deux choses manquent, et ce sont elles qui justifient ce projet :
|
||
|
||
- la **manipulation directe** des barres (glisser, redimensionner) ;
|
||
- le **suivi de dérive** face à un planning de référence figé.
|
||
|
||
Si ces deux besoins disparaissaient, il faudrait sérieusement envisager d'abandonner cet outil au
|
||
profit de Markwhen.
|
||
|
||
## 2. Pas de dépendances entre phases
|
||
|
||
Une première version du cahier des charges prévoyait des dépendances fin→début avec propagation en
|
||
cascade. Abandonné.
|
||
|
||
Le coût est très supérieur à ce qu'il paraît : graphe orienté, détection de cycles, et surtout une
|
||
sémantique de propagation à trancher — la poussée est-elle une contrainte dure ou souple, la marge
|
||
existante est-elle conservée, avancer un prédécesseur tire-t-il ses successeurs en arrière, que
|
||
faire d'une date imposée de l'extérieur. Chacune de ces questions a plusieurs réponses défendables,
|
||
et se tromper produit un outil qui déplace des barres dans le dos de l'utilisateur.
|
||
|
||
En regard, le bénéfice réel en planification macro est faible. Le besoin concret — « ce projet glisse
|
||
d'un mois » — se traite en décalant les phases concernées à la main, ce qui prend quelques secondes
|
||
sur un projet de cinq ou six phases.
|
||
|
||
**Conséquence heureuse** : sans flèches à tracer entre les barres, le SVG perd sa raison d'être (voir
|
||
décision 4).
|
||
|
||
## 3. Micro-serveur Python plutôt que File System Access API
|
||
|
||
La première approche envisagée était d'écrire directement dans le fichier depuis le navigateur, via
|
||
`showOpenFilePicker` / `createWritable`, pour se passer de tout back-end.
|
||
|
||
> **Rectification (1ᵉʳ août 2026).** Le paragraphe suivant affirmait que l'API « ne fonctionne pas
|
||
> depuis `file://` ». C'est faux, et la vérification sur Chromium 150 le montre : `file://` **est**
|
||
> un contexte sécurisé (`isSecureContext === true`), `showSaveFilePicker` y ouvre bien son
|
||
> dialogue, et `localStorage` comme IndexedDB y persistent d'une session à l'autre — un handle
|
||
> peut donc y être mémorisé. Ce qui bloque réellement en `file://`, c'est autre chose : les
|
||
> **modules ES** et `fetch` y sont interdits par CORS (origine `null`), donc c'est la décision 10
|
||
> qui ferme cette voie, pas la sécurité de l'API. La conclusion ci-dessous — lancer un serveur —
|
||
> reste la bonne, mais pour ce motif-là. Voir décision 22.
|
||
|
||
Cette API exige un **contexte sécurisé**, et l'on avait cru qu'elle ne fonctionnait pas depuis
|
||
`file://`. Il fallait donc de toute façon lancer un serveur statique. Le « zéro back-end » était
|
||
perdu d'avance, tout en payant trois prix : limitation aux navigateurs Chromium, re-autorisation du
|
||
sélecteur de fichier à chaque session, et maintien d'un chemin de repli export/import pour Firefox
|
||
et Safari, doublé d'un miroir `localStorage`.
|
||
|
||
Puisqu'une commande doit être lancée dans tous les cas, autant qu'elle serve à quelque chose. Un
|
||
serveur de la bibliothèque standard, `GET` et `PUT` sur `/api/data`, tient en une centaine de lignes
|
||
— soit **moins** de code que l'API navigateur plus son repli plus son miroir. Et il fonctionne
|
||
partout, sans sélecteur à réautoriser.
|
||
|
||
Bénéfice supplémentaire : le serveur peut écrire une sauvegarde horodatée, ce que la File System
|
||
Access API ne permettait pas simplement. Elle l'était alors à chaque écriture ; son rythme a depuis
|
||
changé, voir décision 23.
|
||
|
||
## 4. DOM et CSS plutôt que SVG
|
||
|
||
Le SVG s'imposait tant qu'il fallait tracer des flèches courbes entre des barres. Les dépendances
|
||
supprimées, il ne reste que des rectangles, du texte et des losanges.
|
||
|
||
Des `div` positionnées en absolu font tout cela plus simplement : troncature du texte par
|
||
`text-overflow`, curseurs de redimensionnement, styles de survol, transitions, tout est natif en CSS.
|
||
En SVG il faudrait mesurer le texte à la main et réimplémenter des comportements que le navigateur
|
||
offre gratuitement. Les losanges de jalon se font par une rotation CSS de 45°.
|
||
|
||
## 5. Identifiants parlants plutôt qu'UUID
|
||
|
||
Le fichier de données est censé rester ouvrable dans un éditeur de texte et versionné dans git. Des
|
||
UUID rendent les deux pénibles : impossible de savoir quelle phase on lit, et un diff illisible.
|
||
|
||
`site-web` et `cadrage` coûtent la même chose à manipuler et gardent le fichier compréhensible. La
|
||
contrepartie — gérer les collisions à la création — est traitée par un suffixe numérique.
|
||
|
||
Un identifiant ne change **jamais** quand le nom est modifié : il identifie, il ne décrit pas.
|
||
Renommer une phase ne doit pas invalider les références qui pointent vers elle.
|
||
|
||
## 6. Statut plutôt que pourcentage d'avancement
|
||
|
||
Un pourcentage d'avancement sur une phase macro de plusieurs semaines est presque toujours un chiffre
|
||
inventé, et il donne une fausse impression de précision. Quatre statuts — à venir, en cours, terminé,
|
||
bloqué — se saisissent en un clic et disent l'essentiel. `blocked` en particulier porte une
|
||
information qu'un pourcentage ne peut pas exprimer.
|
||
|
||
## 7. Jours calendaires
|
||
|
||
Gérer les jours ouvrés impose de faire passer chaque calcul de date par un helper, et soulève
|
||
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
|
||
|
||
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.
|
||
|
||
É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.
|
||
|
||
**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
|
||
en train d'éditer.
|
||
|
||
**Et elle est rendue à l'identique dans les deux cas.** Une première version l'épaississait en mode
|
||
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`.
|
||
|
||
## 9. Accroche à la semaine
|
||
|
||
Au zoom trimestre, un jour représente quelques pixels : viser une date précise à la souris devient
|
||
un exercice de patience. L'accroche au lundi rend le geste franc et correspond à la granularité
|
||
réelle d'une planification macro.
|
||
|
||
Les dates exactes restent saisissables au clavier dans le panneau de détail, ce qui couvre les cas
|
||
où une phase doit démarrer un jeudi précis.
|
||
|
||
## 10. Zéro build
|
||
|
||
Modules ES natifs, chargés directement par le navigateur. Pas de `package.json`, pas de bundler, pas
|
||
de `node_modules`. Le fichier qu'on lit dans l'éditeur est exactement celui qu'exécute le navigateur,
|
||
ce qui rend le débogage direct et l'outil reprenable dans plusieurs années sans exhumer une chaîne de
|
||
compilation obsolète.
|
||
|
||
Node n'est utilisé que pour lancer les tests, via son lanceur intégré (`node --test`), sans aucune
|
||
dépendance.
|
||
|
||
**Portée après la décision 22.** Le serveur se compile désormais, mais le front, lui, ne se
|
||
compile toujours pas : aucun bundler, aucune transformation, aucun `node_modules`. Les fichiers de
|
||
`js/` sont embarqués tels quels dans le binaire et servis tels quels au navigateur — c'est bien la
|
||
ligne qu'on lit dans l'éditeur qui s'exécute, et `--dev` les sert directement depuis le disque.
|
||
Ce qui était visé ici est donc intact ; ce qui change, c'est seulement la façon dont le serveur est
|
||
distribué.
|
||
|
||
## 11. Un en-tête à trois bandes, la semaine pour grain le plus fin
|
||
|
||
L'en-tête affichait une seule rangée de graduations dont le contenu changeait selon le
|
||
zoom : des quantièmes en vue semaine, des noms de mois en vue trimestre. Il fallait
|
||
deviner l'unité affichée, et l'année n'apparaissait qu'au zoom le plus large.
|
||
|
||
Trois bandes empilées — **année**, **mois**, **numéro de semaine ISO** — lèvent
|
||
l'ambiguïté : les trois repères sont toujours là, quel que soit le zoom. Chaque cellule
|
||
est un bloc couvrant exactement son intervalle, ce qui centre le libellé sur la période
|
||
qu'il désigne au lieu de le laisser flotter après un trait.
|
||
|
||
Le jour a disparu : sur des phases qui se comptent en semaines, il n'apportait rien et
|
||
encombrait l'affichage. Le zoom ne change donc plus l'unité, seulement la largeur des
|
||
mêmes semaines.
|
||
|
||
Les numéros suivent l'**ISO 8601** : la semaine appartient à l'année où tombe son jeudi.
|
||
C'est pourquoi `semaineISO()` renvoie l'année en même temps que le numéro — afficher
|
||
« S53 » sous un bandeau « 2027 » serait faux, alors que le 1er janvier 2027 est bien en
|
||
semaine 53 de 2026.
|
||
|
||
## 12. Défilement natif sur les deux axes plutôt que synchronisé en JavaScript
|
||
|
||
La colonne des libellés était un conteneur séparé, recalé sur le défilement vertical de
|
||
la frise par un `translateY` appliqué à chaque événement `scroll`. Deux défauts : un
|
||
décalage visible d'une image sur les défilements rapides, et deux systèmes de coordonnées
|
||
à tenir cohérents.
|
||
|
||
Tout tient désormais dans un unique conteneur défilant, découpé en grille 2×2 : l'en-tête
|
||
est `sticky top`, la colonne des libellés `sticky left`, et le coin les deux à la fois. Le
|
||
navigateur gère seul le défilement des deux axes. Le code de synchronisation a disparu.
|
||
|
||
## 13. Menu contextuel plutôt que `prompt()` et `confirm()`
|
||
|
||
Les actions d'un projet passaient par un `prompt()` listant des numéros d'action à saisir
|
||
au clavier, et les suppressions par un `confirm()`. C'était rapide à écrire et pénible à
|
||
utiliser : rien n'indiquait qu'il fallait cliquer sur le nom du projet, et ces boîtes
|
||
natives bloquent le fil d'exécution.
|
||
|
||
Un bouton `⋯` explicite ouvre maintenant un vrai menu, navigable au clavier, où les
|
||
actions indisponibles sont grisées plutôt qu'absentes — « Retirer la référence » reste
|
||
visible quand aucune référence n'est figée, ce qui apprend que la fonction existe.
|
||
|
||
Les dialogues s'appuient sur l'élément `<dialog>` natif, qui fournit le voile, le piège à
|
||
focus, la fermeture par Échap et la restauration du focus sans code supplémentaire. Sur
|
||
une action destructrice, le focus initial se pose sur « Annuler » : un appui réflexe sur
|
||
Entrée ne doit rien détruire.
|
||
|
||
Une exception assumée : ajouter une phase ne demande pas son nom dans un dialogue. La
|
||
phase est créée immédiatement à la suite de la précédente, et le panneau de détail
|
||
s'ouvre avec le nom présélectionné — l'utilisateur tape simplement par-dessus. Un
|
||
dialogue de plus n'aurait rien apporté.
|
||
|
||
## 14. Une seule échelle, pas de zoom
|
||
|
||
Trois niveaux de zoom — semaine, mois, trimestre — étaient proposés. Retirés.
|
||
|
||
Depuis que l'en-tête empile l'année, le mois et le numéro de semaine (décision 11), les
|
||
trois repères utiles sont lisibles en permanence. Le zoom ne résolvait plus qu'un problème
|
||
qu'il avait lui-même créé : savoir quelle unité on regardait. Il restait un réglage de
|
||
plus à comprendre, un état de plus à gérer, et un chemin de code de plus à tester.
|
||
|
||
L'échelle unique est de **6 pixels par jour**, soit 42 pixels par semaine : assez pour que
|
||
« S32 » tienne dans sa cellule, assez serré pour qu'une année tienne dans une fenêtre de
|
||
portable.
|
||
|
||
## 15. Une frise qui s'élargit au défilement
|
||
|
||
La fenêtre affichée se déduisait des bornes des phases existantes, avec une marge fixe.
|
||
Conséquence gênante : impossible d'aller regarder un futur vide pour y planifier. La frise
|
||
s'arrêtait là où s'arrêtaient les données.
|
||
|
||
La fenêtre est désormais un **état de vue** distinct des données. Elle démarre autour des
|
||
phases existantes, puis s'élargit de six mois dès que le défilement passe à moins de
|
||
400 pixels d'un bord. On peut ainsi partir vers 2031, y poser une phase, et revenir.
|
||
|
||
Deux subtilités :
|
||
|
||
- **Élargir vers la gauche déplace l'origine de l'échelle**, donc tout le contenu se
|
||
décale vers la droite. On compense `scrollLeft` de la même quantité, sans quoi la vue
|
||
sauterait en arrière à chaque élargissement. Un drapeau empêche l'ajustement de
|
||
relancer le gestionnaire de défilement.
|
||
- **La fenêtre ne rétrécit jamais**, même si les phases se resserrent : le terrain déjà
|
||
exploré doit rester atteignable.
|
||
|
||
L'élargissement s'arrête à **dix ans de part et d'autre des données**. Ce n'est donc pas
|
||
infini au sens strict — c'est un garde-fou : rien n'est virtualisé, et un défilement
|
||
prolongé ferait autrement enfler le DOM sans limite. À l'échelle d'une planification
|
||
macro, dix ans dépassent largement l'horizon utile.
|
||
|
||
Corollaire : le recentrage est instantané, jamais animé. Sur une frise qui peut atteindre
|
||
plusieurs dizaines de milliers de pixels, une animation serait lente, et le gestionnaire
|
||
de défilement l'interromprait en élargissant la fenêtre au passage près d'un bord.
|
||
|
||
Attention en modifiant `centrerSur` : `scrollLeft` porte sur la grille entière, dont la
|
||
première colonne est celle des libellés. Comme elle est `sticky`, elle masque en
|
||
permanence les premiers pixels de la vue, et le centre utile des couloirs est décalé
|
||
d'une demi-largeur de colonne par rapport au centre de la frise.
|
||
|
||
## 16. Tags libres et filtre conjonctif
|
||
|
||
Passé une dizaine de projets, la frise devient un mur : on veut n'y voir que les projets
|
||
clients, ou que ceux d'un pôle. D'où des **tags** sur le projet, et un filtre.
|
||
|
||
**Étiquettes libres, pas de référentiel.** Aucune liste de tags autorisés n'est tenue
|
||
quelque part : les tags disponibles sont ceux que portent les projets, et un tag disparaît
|
||
de la barre de filtres dès qu'il n'orne plus rien. Un référentiel séparé aurait exigé une
|
||
interface pour le gérer, et se serait désynchronisé. La contrepartie — la dérive
|
||
orthographique, « client », « Clients », « client final » — est traitée par deux gardes :
|
||
la comparaison ignore la casse, et le dialogue d'un projet propose en un clic les tags déjà
|
||
employés ailleurs.
|
||
|
||
**Le filtre exige *tous* les tags cochés**, pas au moins un. Cocher un tag de plus resserre
|
||
donc toujours la sélection : on part du tout et on élague, sans jamais voir la frise se
|
||
repeupler en cochant. La disjonction se comporte à l'inverse, et il faut alors relire la
|
||
liste des tags actifs pour savoir dans quel sens la prochaine case va jouer.
|
||
|
||
**Le filtre n'est pas enregistré.** C'est un état de vue, comme la fenêtre temporelle
|
||
(décision 15) : un filtre qui survivrait au rechargement rouvrirait le planning amputé sans
|
||
qu'on se souvienne pourquoi. Le masquage d'un projet (`hidden`), lui, est bien enregistré —
|
||
et les deux ne font pas la même chose :
|
||
|
||
| | Filtre par tags | Masquage (`hidden`) |
|
||
|---|---|---|
|
||
| Portée | La vue entière, libellé compris | Les barres seules, le libellé reste grisé |
|
||
| Durée de vie | La session | Enregistré dans le fichier |
|
||
| Question posée | « Je ne regarde que les projets clients » | « Ce projet existe, mais il encombre ma frise » |
|
||
|
||
Le masquage n'a plus de bouton dédié dans la colonne : voir décision 20.
|
||
|
||
**La couleur d'un tag est calculée, pas choisie** : une teinte HSL dérivée du nom par
|
||
hachage. Rien à stocker, rien à saisir, et le même tag garde sa couleur partout. Des
|
||
collisions de teinte sont possibles et sans gravité — le nom est toujours écrit à côté.
|
||
|
||
**Un tag trop long est tronqué, pas refusé.** Contrairement à une date invalide, qui bloque
|
||
le chargement en nommant le fautif, la longueur d'un tag est cosmétique : refuser d'ouvrir
|
||
un planning entier pour un libellé bavard serait disproportionné. La limite est donc
|
||
appliquée en normalisant, ce qui la rend vraie par construction — aussi bien pour un fichier
|
||
édité à la main que pour une saisie dans le dialogue.
|
||
|
||
Créer un projet alors qu'un filtre est actif **préremplit ses tags** avec ceux du filtre :
|
||
sans cela, le nouveau venu naîtrait invisible, hors de la vue depuis laquelle on vient de le
|
||
demander.
|
||
|
||
## 17. Le panneau de détail n'est pas modal
|
||
|
||
Le panneau s'ouvrait derrière un voile couvrant toute la page. L'intention était
|
||
raisonnable — concentrer l'attention sur la phase en cours d'édition —, mais elle
|
||
allait contre ce que fait un outil de frise : on y compare, on passe d'une phase à
|
||
la suivante, on ajuste en regardant le voisinage.
|
||
|
||
Trois conséquences, dont deux invisibles à la lecture du code :
|
||
|
||
- passer d'une phase à l'autre imposait fermer, cliquer, rouvrir, alors que c'est
|
||
le geste le plus fréquent de l'outil ;
|
||
- le raccourci `←` `→` était **inatteignable**. Le panneau plaçait le focus dans
|
||
le champ « Nom », et `app.js` ignore les flèches quand la frappe part dans un
|
||
`input` — pour en sortir il aurait fallu cliquer ailleurs, ce que le voile
|
||
interdisait précisément ;
|
||
- le rafraîchissement du panneau depuis la frise, prévu pour qu'un glisser se
|
||
reflète dans les champs, ne pouvait jamais se produire à la souris.
|
||
|
||
Le voile a donc disparu. Le panneau reste posé sur le bord droit, la frise
|
||
continue de vivre derrière lui, et cliquer une autre barre y bascule la
|
||
sélection. Le focus se pose sur le panneau lui-même — assez pour que le clavier
|
||
le suive, pas assez pour capturer les flèches. Le champ « Nom » n'est
|
||
présélectionné que pour une phase fraîchement créée, dont le nom provisoire est
|
||
justement là pour être écrasé.
|
||
|
||
**Cliquer à côté ferme quand même.** C'était le seul service que rendait le
|
||
voile, et il ne valait pas la frise inerte qu'il coûtait. Un écouteur sur
|
||
`document` le rend sans calque : il ferme les panneaux au `pointerdown`, sauf
|
||
sur deux familles de cibles.
|
||
|
||
- Les **surfaces protégées** — un panneau, le menu, un dialogue, la poignée de
|
||
colonne — où l'on est manifestement au travail.
|
||
- Les **cibles ouvrantes** : une barre, une poignée de barre, un libellé de
|
||
phase, tout élément portant `data-action`. Leur propre gestionnaire ouvre déjà
|
||
le bon panneau, et les faire passer par la fermeture générale serait pire
|
||
qu'inutile : celle-ci redessine la frise, ce qui détacherait du DOM l'élément
|
||
visé avant que son gestionnaire — ou le glisser qui commence peut-être — n'ait
|
||
fini. C'est aussi pourquoi `fermer()` sort tout de suite quand le panneau est
|
||
déjà fermé : sans cette garde, chaque clic dans le vide relancerait un rendu
|
||
complet.
|
||
|
||
Échap et la croix restent les deux autres façons de fermer.
|
||
|
||
## 18. La colonne des libellés est redimensionnable
|
||
|
||
La colonne portait, sur 250 pixels fixes, le triangle de pli, le nom, les tags,
|
||
l'étiquette de dérive, l'œil et le menu. Sur le jeu d'exemple, il ne restait que
|
||
**56 pixels au nom** : « Refonte du site web » s'affichait « Refon… », pendant que
|
||
« client » se réduisait à « c. ». Le pire des deux mondes — des pastilles qui
|
||
occupaient la place d'un tag sans en livrer le nom.
|
||
|
||
Trois corrections, dans l'ordre de leur rendement :
|
||
|
||
- **Les commandes passent en surimpression**, révélées au survol de la ligne.
|
||
Ce sont des actions occasionnelles ; elles coûtaient soixante pixels en
|
||
permanence à une information lue en continu. Elles sont posées en `absolute`,
|
||
et non retirées du flux au repos, pour que rien ne se déplace à l'approche du
|
||
curseur. Elles restent visibles sans survol sur un projet masqué, seul chemin
|
||
pour le rétablir.
|
||
- **Les tags cèdent en bloc plutôt que de rétrécir.** Deux pastilles au plus,
|
||
puis un compteur `+N` dont l'infobulle donne les manquants. Un tag présent dans
|
||
le filtre courant passe devant : c'est lui qui explique la présence du projet à
|
||
l'écran, il ne doit pas tomber dans le compteur.
|
||
- **Le nom cède en dernier**, et pas en deçà de sept caractères.
|
||
|
||
Restait qu'aucune largeur ne convient à la fois à « Site web » et à « Refonte du
|
||
portail fournisseurs — lot 2 ». D'où une poignée dans le coin de la grille —
|
||
seul point de la colonne qui reste visible quel que soit le défilement —, avec
|
||
double-clic pour revenir au défaut et flèches au clavier.
|
||
|
||
**Cette largeur est conservée, dans `localStorage`.** C'est la première entorse à
|
||
la règle des décisions 15 et 16, où les états de vue meurent avec la session, et
|
||
elle est délibérée : la fenêtre temporelle et le filtre décrivent *ce qu'on
|
||
regarde*, une largeur de colonne décrit *l'écran sur lequel on regarde*. La
|
||
placer dans le planning imposerait à un portable la largeur choisie sur un
|
||
27 pouces ; ne pas la garder du tout obligerait à réajuster la poignée à chaque
|
||
ouverture, ce qui la rendrait inutile. `localStorage` est exactement le bon
|
||
rangement : local à la machine, hors des données.
|
||
|
||
## 19. Le nom d'une phase reste accroché au bord visible
|
||
|
||
Le libellé d'une barre était écrit à son bord gauche. Une phase de plusieurs mois
|
||
dont le début sortait de l'écran occupait donc toute la largeur de la vue sans
|
||
qu'on puisse lire son nom, parti sous la colonne des libellés.
|
||
|
||
Le nom vit désormais dans son propre élément, en `position: sticky`, calé sur la
|
||
largeur de la colonne — c'est-à-dire sur le premier pixel réellement visible de
|
||
la frise. Trois détails le rendent possible, et aucun n'est cosmétique :
|
||
|
||
- un `overflow: hidden` sur la barre en aurait fait le conteneur défilant de
|
||
référence, et le décalage n'aurait plus eu lieu. Le rognage du texte trop long
|
||
est donc porté par le nom lui-même, pas par la barre ;
|
||
- le décalage est plafonné par la boîte du parent, ce qui garantit que le nom ne
|
||
sort jamais de sa barre — d'où `inline-block`, qui laisse au texte la marge de
|
||
manœuvre qu'un bloc pleine largeur n'aurait pas ;
|
||
- quand le nom occupe presque toute sa barre, la marge s'épuise et il finit par
|
||
s'enfoncer sous la colonne. On lit alors sa fin plutôt que rien, ce qui reste
|
||
un progrès, et la colonne des libellés donne de toute façon le nom entier.
|
||
|
||
## 20. Ce que porte la ligne d'un projet
|
||
|
||
Trois commandes s'y étaient accumulées sans qu'on se demande si chacune méritait
|
||
sa place. Le tri s'est fait sur une seule question : cette action est-elle assez
|
||
fréquente pour valoir un bouton permanent ?
|
||
|
||
**L'œil disparaît.** Il faisait doublon avec l'entrée « Masquer de la frise » du
|
||
menu, qui reste. Le masquage est une décision durable sur un projet — on le pose
|
||
une fois et on l'oublie — pas un geste qu'on refait dix fois par séance. Depuis
|
||
que les tags existent (décision 16), c'est d'ailleurs le filtre qui répond au
|
||
besoin courant de dégager la vue. La fonction demeure, elle ne coûte simplement
|
||
plus de pixels à tout le monde en permanence. Le format de données est inchangé :
|
||
`hidden` reste un champ du projet.
|
||
|
||
**Le `+` apparaît.** Ajouter une phase est l'action la plus fréquente de l'outil,
|
||
et elle était enterrée dans un menu.
|
||
|
||
**Le nom du projet devient cliquable.** Il portait déjà `cursor: pointer` sans
|
||
qu'aucune action n'y réponde — un curseur qui promet et ne tient pas. Il ouvre
|
||
maintenant le dialogue des paramètres, ce que la main annonçait.
|
||
|
||
**Il n'y a plus de commande « Ajouter un jalon ».** Un jalon n'est pas un autre
|
||
objet : c'est une phase dont la fin rejoint le début, soit une case à cocher du
|
||
panneau au même titre que le statut. Deux entrées de menu obligeaient à trancher
|
||
avant d'avoir rien saisi, alors que la bascule reste possible ensuite dans les
|
||
deux sens. Une seule commande crée une phase ; on coche « Jalon » si c'en est un.
|
||
|
||
## 21. Un projet s'édite comme une phase
|
||
|
||
Les paramètres d'un projet — nom, couleur, tags — passaient par un `<dialog>`
|
||
modal à valider, quand ceux d'une phase s'éditaient dans un panneau latéral au
|
||
fil de la saisie. Deux formes pour la même opération à un niveau de granularité
|
||
près, avec deux modèles mentaux à tenir : ici on tape et ça s'applique, là on
|
||
tape et il faut confirmer.
|
||
|
||
Les projets ont rejoint le panneau (`js/projet.js`, jumeau de `js/detail.js`).
|
||
Même en-tête, même édition en direct, même bouton de suppression au bas du
|
||
formulaire. Les deux panneaux occupent le même bord de l'écran : ouvrir l'un
|
||
ferme l'autre.
|
||
|
||
**« Nouveau projet » ne demande donc plus rien.** Le projet est créé aussitôt
|
||
avec un nom provisoire, et le panneau s'ouvre avec ce nom présélectionné — on
|
||
tape par-dessus. C'est exactement le parti pris déjà retenu pour l'ajout d'une
|
||
phase (décision 13), qui cesse ainsi d'être une exception : plus aucune création
|
||
ne passe par un formulaire à valider.
|
||
|
||
Conséquences en cascade, toutes bienvenues :
|
||
|
||
- la couleur s'applique au clic sur sa pastille, donc la frise change sous les
|
||
yeux — le seul moyen de juger si la teinte se distingue de celle des couloirs
|
||
voisins, ce qu'un aperçu dans un dialogue ne montrait pas ;
|
||
- il n'y a plus de bouton « Annuler », donc plus le cas où fermer la fenêtre
|
||
perdait la saisie ;
|
||
- `creerDialogueProjet` disparaît de `menu.js`, qui ne garde que le menu
|
||
contextuel et la confirmation.
|
||
|
||
Le `<dialog>` natif reste pour cette dernière : sur une action irréversible,
|
||
interrompre franchement *est* le but, et le voile, le piège à focus et la
|
||
fermeture par Échap y sont gratuits.
|
||
|
||
Une contrepartie assumée : un projet créé par mégarde existe dans le fichier
|
||
même si l'on ferme le panneau sans rien saisir. Il porte alors le nom
|
||
« Nouveau projet », se voit sur la frise, et se supprime d'un clic depuis son
|
||
propre panneau — ce qui reste moins coûteux que d'imposer un dialogue à tout le
|
||
monde à chaque création.
|
||
|
||
## 22. Un binaire Go plutôt qu'un script Python
|
||
|
||
`serve.py` supposait Python installé et une commande tapée dans un terminal, depuis le dossier du
|
||
dépôt. C'est une friction quotidienne pour un outil qu'on veut ouvrir d'un geste, et un obstacle
|
||
net sous Windows, où Python est absent par défaut.
|
||
|
||
L'objectif visé : **poser l'outil et son planning dans un dossier synchronisé, et les retrouver
|
||
sur n'importe quelle machine**. Trois voies menaient là.
|
||
|
||
**Un `index.html` autonome en `file://`.** Séduisante — zéro exécutable, longévité maximale — mais
|
||
elle impose de renoncer aux modules ES, bloqués par CORS depuis `file://` (voir la rectification en
|
||
décision 3), donc de concaténer les sources en un fichier unique. Et surtout elle enferme dans
|
||
Chromium : Firefox et Safari n'implémentent pas la File System Access API.
|
||
|
||
**Tauri.** Accès direct au disque, sans dialogue ni permission, et le code du navigateur reste
|
||
intact. Mais un webview système à installer sous Linux (WebKitGTK), un rendu à vérifier sur trois
|
||
moteurs différents, une chaîne Rust et une intégration continue multi-OS pour produire les
|
||
binaires. C'est contradictoire avec la décision 10 : une chaîne Tauri de 2026 ne se recompilera pas
|
||
aussi sûrement dans dix ans qu'un fichier HTML s'ouvrira.
|
||
|
||
**Un binaire Go qui contient le serveur et l'interface**, retenu. Il conserve exactement
|
||
l'architecture éprouvée — mêmes routes, même fichier JSON, mêmes sauvegardes horodatées, front
|
||
inchangé — en supprimant la seule vraie friction, l'installation. `CGO_ENABLED=0` produit un
|
||
exécutable statique de 6 Mo qui ne dépend de rien, pas même de la glibc, et les cinq plateformes se
|
||
compilent depuis une seule machine sans intégration continue.
|
||
|
||
Face à `file://`, il garde **tous** les navigateurs et les modules ES. Face à Tauri, il n'exige
|
||
aucun webview et divise l'outillage par dix. Ce qu'il concède : l'interface s'ouvre dans un onglet
|
||
plutôt que dans une fenêtre d'application, et il faut un binaire par plateforme.
|
||
|
||
Deux choix de détail méritent d'être notés.
|
||
|
||
Le port n'est plus fixe : lancé par un double-clic, l'utilisateur n'a aucun moyen de passer
|
||
`--port` si 8000 est occupé. Le serveur essaie donc les dix premiers ports libres à partir de 8000.
|
||
Un `--port` explicite, lui, n'est jamais contourné — c'est un choix de l'utilisateur.
|
||
|
||
Le fichier de données est cherché **à côté de l'exécutable**, pas dans le répertoire courant, qui
|
||
est imprévisible lors d'un double-clic. Le mode `--dev` fait exception : il sert l'interface depuis
|
||
le disque et lit `data/projets.json`, pour qu'un rafraîchissement suffise à voir ses modifications
|
||
sans recompiler.
|
||
|
||
`serve.py` a été supprimé une fois le binaire validé en usage réel. Il n'y avait rien à migrer : le
|
||
format du fichier et les routes sont identiques, et son historique reste dans git si le besoin de
|
||
le relire se présentait.
|
||
|
||
## 23. Une sauvegarde par séance, pas une par modification
|
||
|
||
Le serveur copiait le planning dans `backups/` avant **chaque** écriture. Comme l'interface
|
||
enregistre après une seconde d'inactivité, une séance d'édition un peu soutenue produisait des
|
||
dizaines de fichiers quasi identiques.
|
||
|
||
Le défaut n'est pas l'espace occupé — cinquante copies d'un planning macro pèsent 250 Ko — mais ce
|
||
que la rotation en fait : les cinquante emplacements se remplissaient en une après-midi, et
|
||
chassaient précisément les états anciens qu'on cherche à retrouver. Un filet de sécurité qui ne
|
||
remonte pas plus loin que la dernière demi-heure protège de la faute de frappe, pas de la bêtise
|
||
qu'on découvre le lendemain.
|
||
|
||
La copie est donc prise **à l'ouverture**, et une seule fois. C'est le bon moment : l'état d'avant
|
||
la séance est exactement celui vers lequel on veut revenir, et il est figé avant qu'on y touche.
|
||
Deux garde-fous complètent la règle :
|
||
|
||
- si le serveur reste allumé longtemps — un serveur local qu'on ne ferme jamais est un cas
|
||
réel —, une copie est reprise au plus une fois par jour, faute de quoi une session de trois
|
||
semaines ne laisserait qu'un seul point de reprise ;
|
||
- si le planning est identique à la dernière sauvegarde, rien n'est écrit. Rouvrir l'outil sans
|
||
avoir rien modifié n'a pas à consommer un emplacement.
|
||
|
||
À raison d'une copie par séance, les cinquante conservées couvrent des mois. Pour un historique
|
||
plus long ou plus fin, la vraie réponse n'est pas d'en garder davantage mais de versionner le
|
||
planning dans git, ce que son format texte indenté rend parfaitement lisible (voir décision 5).
|
||
|
||
Ce changement a rendu concurrentes deux étapes qui ne l'étaient pas — la copie et le remplacement
|
||
dépendent maintenant d'un état partagé, la date de la dernière sauvegarde. Un verrou les sérialise,
|
||
ce qui protège au passage du cas où deux onglets enregistrent en même temps.
|
||
|
||
## 24. Une vue par mois, en lecture seule, à côté de la frise
|
||
|
||
La frise répond très bien à « comment ce projet-là s'étale-t-il », et très mal à « qu'est-ce qui
|
||
arrive le mois prochain ». La seconde question traverse les projets, alors que la frise les range
|
||
précisément en couloirs séparés : il faut balayer une colonne de dates à travers tous les couloirs,
|
||
en gardant en tête ce qu'on a vu plus haut. Avec cinq projets et une échelle de six pixels par
|
||
jour, c'est déjà pénible ; c'est la raison pour laquelle on ressortait un tableur à côté.
|
||
|
||
D'où une seconde vue, atteinte par deux boutons dans la barre d'outils. Trois arbitrages la
|
||
définissent.
|
||
|
||
**Une phase est rattachée au mois de son début, et à lui seul.** Une phase de trois mois n'apparaît
|
||
donc qu'une fois. La répéter dans chaque mois traversé aurait donné un tableau de charge —
|
||
« qu'est-ce qui tourne en octobre » —, qui est une question légitime mais une autre vue : le
|
||
doublon coûte la lecture en diagonale qui fait tout l'intérêt de celle-ci, et brouille le compte
|
||
affiché en tête de mois. La fin est écrite sur la ligne (« 31 → 13 nov. »), ce qui suffit à voir
|
||
qu'une phase déborde et jusqu'où.
|
||
|
||
**Les mois sans rien ne sont pas représentés.** Un planning à horizon de deux ans a des trous ;
|
||
intercaler « novembre 2026 — rien » ferait défiler du vide, alors que l'absence se lit déjà dans le
|
||
saut d'un titre au suivant.
|
||
|
||
**La liste ne s'édite pas.** Aucune poignée, aucun glisser, et un clic sur une ligne n'ouvre pas le
|
||
panneau de détail. Ce n'est pas une lacune à combler plus tard : déplacer une phase demande de voir
|
||
ce qu'elle chevauche et ce qu'elle repousse, c'est-à-dire le voisinage que la frise montre et que
|
||
la liste, ordonnée par date et non par projet, a justement dissous. Une date qu'on modifie sans
|
||
voir son contexte est une date qu'on modifie à l'aveugle. La vue sert donc à faire le point, la
|
||
frise à arbitrer. Seules les pastilles de tags restent cliquables — filtrer ne change pas les
|
||
données, c'est une façon de regarder.
|
||
|
||
Les **notes** d'une phase, elles, se consultent ici : un triangle en tête de ligne les déplie en
|
||
dessous. Lire n'est pas éditer, et une note est précisément ce qu'on va chercher quand on fait le
|
||
point — « pourquoi cette phase est-elle bloquée ». Elles restent repliées par défaut, pour deux
|
||
raisons. La densité d'abord : trois lignes de notes sous chaque intitulé étalent un mois sur deux
|
||
écrans et ruinent la lecture en diagonale. Et parce qu'elles ne sont pas régulières — une phase sur
|
||
trois en porte —, les afficher d'office produirait des lignes de hauteurs inégales, ce qui casse
|
||
l'alignement des colonnes sur lequel repose tout le balayage du regard.
|
||
|
||
**Toute la ligne déplie, pas seulement le triangle.** Une première version faisait de l'intitulé
|
||
lui-même le bouton, avec un chevron à sa suite : la cible était trop petite pour un geste aussi
|
||
banal, et rien ne justifiait cette parcimonie dans une vue où le clic n'a aucun autre effet. Le
|
||
triangle reste, mais comme indicateur plutôt que comme cible — c'est lui qui dit ce qui va se
|
||
passer, et il reprend le triangle qui plie un projet sur la frise, au même endroit et avec la même
|
||
rotation : ce qui s'apprend d'un côté vaut de l'autre. Le curseur et le fond au survol signalent
|
||
que la ligne réagit. Les lignes sans notes, elles, ne réagissent pas : promettre un geste qui ne
|
||
fait rien est pire que ne rien promettre.
|
||
|
||
Le triangle n'en reste pas moins un vrai `button` : la ligne, elle, n'est pas focusable, et c'est
|
||
donc lui le seul chemin clavier vers les notes. L'état déplié vit dans le DOM et nulle part
|
||
ailleurs — il ne survit pas à un changement de filtre, qui reconstruit la liste. Le remonter dans
|
||
l'état de l'application pour qu'il y survive serait payer cher une continuité que personne ne
|
||
réclame : quand la liste change, on la relit.
|
||
|
||
Le filtre par tags, lui, vaut pour les deux vues sans rien changer à sa mécanique : c'est le même
|
||
état, la même barre, au même endroit. Un filtre posé sur la frise se retrouve en passant à la
|
||
liste, ce qui rend la bascule sûre — on ne se demande jamais si l'une montre plus que l'autre. Les
|
||
deux tamis de la frise s'appliquent d'ailleurs à l'identique : l'œil (`hidden`) écarte un projet de
|
||
la liste comme il écarte ses barres.
|
||
|
||
La vue choisie n'est pas enregistrée, contrairement à la largeur de la colonne des libellés
|
||
(décision 18). Cette largeur est un réglage d'écran, la vue est un geste de consultation : on passe
|
||
par la liste pour faire le point, on revient à la frise pour travailler. L'outil s'ouvre donc
|
||
toujours sur la frise, celle où l'on modifie. Le défilement horizontal de la frise, lui, est
|
||
mémorisé le temps de l'aller-retour : masquer un élément lui fait perdre son `scrollLeft`, et
|
||
revenir des années avant ce qu'on regardait serait une punition pour avoir consulté la liste.
|
||
|
||
Deux conséquences dans le code, toutes deux de bon aloi : les noms de mois et `formaterDateLongue`
|
||
sont remontés de `timeline.js` vers `model.js`, et les pastilles de tags dans `tags.js`. Deux vues
|
||
sœurs les emploient, aucune n'a à importer l'autre pour écrire « septembre ».
|
||
|
||
## 25. Les notes sont en markdown, et le projet en a
|
||
|
||
Une phase avait des notes : un `textarea` de quatre lignes au bas du panneau, du texte brut. Un
|
||
projet n'en avait aucune. Les deux manques se tiennent.
|
||
|
||
**Le projet en avait besoin.** La frise dit quand les choses arrivent ; elle ne dit pas qui pilote,
|
||
sur quel budget, ni pourquoi le prestataire est engagé jusqu'en janvier. Ce contexte-là finissait
|
||
dans les notes de la première phase venue, où il n'a rien à faire — il survit à la phase, il ne
|
||
survit pas à sa suppression. Le champ est donc monté d'un cran, et le panneau d'un projet reçoit le
|
||
même bloc que celui d'une phase : renommer un projet et renommer une phase sont la même opération à
|
||
un niveau près (décision 21), y écrire une note aussi.
|
||
|
||
**Quatre lignes fixes étaient un plafond arbitraire.** Les autres champs du panneau ont une taille
|
||
dictée par leur contenu — une date en fait dix caractères, un statut en fait quatre. Une note n'a
|
||
aucune taille propre : elle fait ce qu'on a à dire. Elle prend donc toute la hauteur que les champs
|
||
au-dessus lui laissent, avec un plancher sous lequel le corps du panneau défile plutôt que de
|
||
l'écraser.
|
||
|
||
### Pourquoi un analyseur maison
|
||
|
||
Le markdown demandait une dépendance, ou du code. Une dépendance aurait été le premier
|
||
`node_modules` de l'outil, contre la décision 10, pour une syntaxe dont on n'emploie ici qu'une
|
||
poignée de formes. On a donc écrit `js/markdown.js` : un sous-ensemble, environ trois cents lignes,
|
||
sans DOM ni réseau — ce qui le rend testable sous Node comme `model.js`, le rendu proprement dit
|
||
vivant dans `notes.js`.
|
||
|
||
Le choix du sous-ensemble suit ce qu'on écrit dans une note de suivi : titres, emphase, code,
|
||
listes, cases à cocher, liens, citations, séparateur. Pas de tableaux ni d'images — ils ne tiennent
|
||
pas dans un volet latéral. Pas de HTML brut, jamais : le DOM est bâti nœud par nœud, sans un seul
|
||
`innerHTML`. Un planning s'échange, on en ouvre un qu'un autre a écrit, et une note est du texte
|
||
libre ; la seule façon sûre d'en afficher est de ne jamais laisser le navigateur l'interpréter comme
|
||
du balisage. Les URL passent une liste blanche de schémas — `http`, `https`, `mailto` — et un lien
|
||
refusé retombe sur son libellé plutôt que de disparaître.
|
||
|
||
Deux écarts au standard, assumés. **Un retour à la ligne en est un** : CommonMark recolle les lignes
|
||
d'un paragraphe et exige deux espaces en fin de ligne pour un vrai retour, piège invisible dans un
|
||
champ où l'on jette une petite liste à la volée. Et **ce qui n'est pas reconnu reste du texte** :
|
||
`**gras` sans fermeture s'affiche tel quel, parce qu'une note à moitié écrite est l'état normal
|
||
d'une note et que l'aperçu doit suivre la frappe sans à-coups.
|
||
|
||
### On lit d'abord, on écrit ensuite
|
||
|
||
Le bloc s'ouvre sur l'**aperçu** quand la note existe, sur la **saisie** quand elle est vide. C'est
|
||
l'usage : on ouvre une phase bloquée pour relire pourquoi, une phase vierge pour y consigner
|
||
quelque chose. Un crayon bascule d'un état à l'autre, et la barre d'outils ne paraît qu'en saisie —
|
||
en aperçu elle ne commanderait rien, et sa place revient au texte.
|
||
|
||
Six boutons, pas plus : gras, italique, titre, liste, case à cocher, lien. Le markdown entier reste
|
||
accessible au clavier puisque c'est du texte ; la barre n'est là que pour épargner la syntaxe des
|
||
formes courantes. Une barre exhaustive prendrait deux rangées dans le volet et repousserait la note
|
||
d'autant.
|
||
|
||
Les **cases à cocher se cliquent dans l'aperçu**, et c'est le seul geste de l'aperçu qui modifie les
|
||
données. Une note de suivi contient des choses à faire ; les cocher en rouvrant la saisie pour
|
||
transformer un `[ ]` en `[x]` serait une corvée absurde. La bascule réécrit **la seule ligne visée
|
||
dans le texte d'origine** plutôt que de régénérer la note depuis l'arbre : régénérer normaliserait
|
||
au passage les marqueurs, l'indentation et l'emphase de l'utilisateur, qui verrait sa mise en forme
|
||
réécrite pour avoir coché une case.
|
||
|
||
Dans la **vue par mois**, en revanche, les cases sont inertes. La liste est en lecture seule
|
||
(décision 24) et une case cliquable y serait la seule chose qu'on puisse modifier — une exception
|
||
isolée dans une vue dont toute la promesse est qu'on n'y casse rien. On les voit cochées ou non, ce
|
||
qui est justement ce qu'on vient y chercher quand on fait le point.
|
||
|
||
### `Ctrl+Z` doit continuer de marcher
|
||
|
||
Une barre d'outils écrit dans le champ à la place de l'utilisateur, et la façon évidente de le faire
|
||
— affecter `champ.value` — **vide la pile d'annulation** du navigateur. `Ctrl+Z` cesse alors de
|
||
remonter au-delà du bouton pressé, et la frappe qui précédait devient irrécupérable. C'est un prix
|
||
qu'on ne peut pas faire payer à un champ de saisie : annuler est le réflexe le plus élémentaire
|
||
qu'on ait devant du texte, et l'ancien champ de notes, lui, l'honorait sans qu'on ait rien à écrire.
|
||
|
||
Toutes les écritures passent donc par `document.execCommand('insertText')`, qui laisse le navigateur
|
||
enregistrer l'opération comme si elle avait été tapée. L'API est marquée obsolète et reste sans
|
||
remplaçant pour cet usage : les `InputEvent` synthétiques qui devaient lui succéder ne modifient
|
||
rien, un navigateur ignorant les événements qu'il n'a pas produits. Tenir notre propre historique
|
||
reviendrait à réimplémenter `Ctrl+Z`, `Ctrl+Y` et la fusion des frappes voisines — sans commune
|
||
mesure avec le service rendu par un champ de notes. Si la commande échoue, on retombe sur
|
||
l'affectation directe : on perd l'annulation, jamais la saisie.
|
||
|
||
Deux gardes vont avec. `execCommand` écrit **là où est le focus**, sans considération pour l'élément
|
||
qu'on croit viser : on vérifie donc que le champ l'a bien pris, faute de quoi la commande irait
|
||
insérer du markdown dans le champ « Nom ». Et une insertion vide n'en est pas une — c'est une
|
||
suppression, que `insertText` ne traite pas partout, d'où le passage par `delete`.
|
||
|
||
Une seule écriture y échappe : cocher une case depuis l'aperçu, où le champ est masqué et ne peut
|
||
pas prendre le focus. Le geste vide donc la pile — sans grande conséquence, puisqu'on n'était pas en
|
||
train d'y taper.
|
||
|
||
### Le volet s'élargit, les infobulles s'aplatissent
|
||
|
||
Trois cent quarante pixels suffisaient à des champs de date ; ils ne suffisent pas à une liste
|
||
imbriquée, où chaque niveau mange une indentation. Le panneau reçoit donc la **même poignée** que la
|
||
colonne des libellés (décision 18) : même geste, même double-clic de remise à zéro, même
|
||
mémorisation dans `localStorage` — c'est un réglage d'écran, pas une donnée à synchroniser. Une
|
||
largeur unique pour les deux panneaux, parce que ce qu'on élargit est le volet, pas l'un ou l'autre
|
||
de ses contenus. Elle est plafonnée à la fenêtre sans être écrasée pour autant : la largeur voulue
|
||
et la largeur appliquée sont deux variables distinctes, faute de quoi réduire un instant la fenêtre
|
||
ferait perdre le réglage.
|
||
|
||
Les **infobulles** de la frise, elles, ne peuvent pas montrer de rendu : l'attribut `title` du
|
||
navigateur ne connaît que le texte. Les notes y sont donc **aplaties** — marques retirées, et
|
||
remplacées par un caractère qui porte le même sens là où il y en a un : `•` pour un tiret, `☐` et
|
||
`☑` pour une case, `│` pour une citation. Sans cela, survoler une barre afficherait la source
|
||
markdown, astérisques comprises, soit une note *moins* lisible qu'avant qu'on ne l'enrichisse. Deux
|
||
infobulles taisaient d'ailleurs les notes et les disent maintenant : celle d'un jalon — c'est
|
||
pourtant là qu'on consigne une décision — et celle du libellé d'un projet, seul endroit de la frise
|
||
où ses notes à lui peuvent se lire, faute de barre qui lui appartienne.
|
||
|
||
### Le format passe en version 4
|
||
|
||
Ajouter un champ n'oblige à rien : une version 3 se relit sans encombre. Mais le validateur
|
||
reconstruit chaque projet champ par champ et laisse tomber ce qu'il ne connaît pas. Sans
|
||
incrémenter, un binaire antérieur ouvrirait un fichier plus riche que lui sans broncher et en
|
||
effacerait toutes les notes de projet à la première sauvegarde. Le numéro ne sert qu'à cela : faire
|
||
échouer bruyamment ce qui échouerait silencieusement.
|
||
|
||
## 26. Un curseur clavier, et des raccourcis à la vim
|
||
|
||
L'outil savait déjà décaler une phase aux flèches, mais pas la **choisir** : `selection` n'était
|
||
posée que par `ouvrirPhase`, appelée depuis un clic. Il fallait donc la souris pour commencer, et
|
||
tout raccourci ajouté par-dessus aurait hérité de cette dépendance.
|
||
|
||
### `selection` devient `curseur`, et survit à la fermeture
|
||
|
||
L'ancienne variable disait deux choses à la fois : « voici la phase que je manipule » et « le
|
||
panneau est ouvert dessus ». Les deux se séparent.
|
||
|
||
Le curseur désigne une **ligne de la frise**, et la ligne d'un projet en est une : c'est de là qu'on
|
||
plie, qu'on renomme, qu'on ajoute une phase et qu'on supprime le projet. D'où `{ projet, phase }`
|
||
avec `phase` à `null`, plutôt que deux curseurs concurrents dont l'un serait toujours à ignorer.
|
||
|
||
Il **survit à `Échap`**, alors que `selection` s'effaçait : un curseur qui disparaît à la fermeture
|
||
du volet ne peut pas servir de point de départ au déplacement suivant. En contrepartie, la marque
|
||
reste visible après un clic dans le vide — c'est le prix d'un curseur, et il se paie une fois.
|
||
|
||
Comme il peut désigner une ligne qu'un changement vient d'emporter — phase supprimée, projet replié,
|
||
filtre resserré —, `dessiner` commence par le **normaliser** : la ligne du projet sert de refuge,
|
||
puisqu'elle survit à ces trois cas, et l'effacement n'est que le dernier recours. Placer ce contrôle
|
||
dans `dessiner` plutôt qu'à chaque appelant garantit qu'aucun chemin ne l'oublie.
|
||
|
||
### Le volet suit le curseur
|
||
|
||
Ce n'est pas un agrément mais une nécessité : les raccourcis agissent sur le curseur, et un panneau
|
||
resté sur une autre phase donnerait deux cibles concurrentes à l'écran. On éditerait dans les champs
|
||
une phase que `Espace` ou `dd` n'atteindraient pas. Ouvert, le panneau est donc la vue détaillée du
|
||
curseur — ce qui fait de `j` et `k` le moyen le plus rapide de relire tout un projet.
|
||
|
||
Ce parcours passe par un **clic** sur une barre, qui ouvre le volet sans prendre le champ. Ouvrir au
|
||
clavier place la frappe dans le nom, où les raccourcis se taisent, et `Échap` referme tout : les deux
|
||
usages ne se rejoignent pas, et c'est un compromis assumé plutôt qu'un oubli. Le rendre continu
|
||
demanderait de faire d'`Échap` une sortie de champ avant d'être une fermeture — deux appuis là où il
|
||
en faut un aujourd'hui, y compris à la souris.
|
||
|
||
### Deux couches qui ne se recouvrent pas
|
||
|
||
Les **flèches modifient** la phase du curseur, les **lettres déplacent** et commandent. Aucun
|
||
raccourci documenté n'a changé de sens.
|
||
|
||
La décision 17 tient, mais son partage se déplace. Elle laissait le focus sur le panneau et non dans
|
||
un champ, pour que les flèches restent atteignables juste après avoir sélectionné une phase — au
|
||
clic, c'est toujours le cas. Ouvrir le volet **au clavier**, en revanche, est une demande explicite
|
||
d'écrire : `Entrée` et `e` saisissent donc le nom tous les deux. Ce qui protège les flèches n'est
|
||
plus le focus mais `Échap`, qui referme le volet sans déplacer le curseur — « `e`, taper, `Échap`,
|
||
flèches » enchaîne sans la souris.
|
||
|
||
Ce qui ne doit **jamais** prendre le focus, c'est le volet qui suit un déplacement du curseur : la
|
||
frappe suivante partirait dans le champ, où les raccourcis se taisent, et on ne saisirait plus jamais
|
||
qu'un nom. D'où le paramètre `saisir` de `ouvrirSousCurseur`, vrai sur commande, faux au passage.
|
||
|
||
`clavier.js` ne connaît rien du planning : il traduit des frappes en noms d'actions, que `app.js`
|
||
lui fournit. Les séquences à deux temps se déclarent dans la même table que les autres, et les
|
||
préfixes — `g` aller, `z` plis, `d` détruire — s'en déduisent au lieu d'être listés à part. Un
|
||
préfixe resté en attente s'oublie au bout de deux secondes : vim ne le fait pas, mais vim n'est pas
|
||
posé sur un écran qu'on quitte des yeux, et un `d` abandonné puis retrouvé cinq minutes plus tard
|
||
viserait une autre ligne.
|
||
|
||
Les touches **se taisent** dès que le clavier appartient à quelqu'un d'autre : une saisie en cours,
|
||
un dialogue modal, le menu contextuel — qui navigue déjà aux flèches — ou un glisser en cours. Sans
|
||
cela, `a` dans un nom de phase créerait une phase au lieu d'écrire un `a`.
|
||
|
||
`AltGr` est la seule combinaison tolérée avec `Maj`, et il n'y a pas le choix : sur un clavier
|
||
français, `{` et `}` ne s'obtiennent qu'avec lui. Windows le présente comme `Ctrl`+`Alt`, si bien
|
||
que refuser les deux ensemble rendrait ces deux touches inatteignables sur les binaires Windows.
|
||
|
||
### Insérer se dit en dates
|
||
|
||
`model.js` trie les phases par date de début. « Avant » et « après » ne peuvent donc pas s'exprimer
|
||
en rangs : `o` et `O` calculent une date et laissent le tri faire le reste. Les quatre points
|
||
d'insertion — `A` à la fin du projet, `a` au début, `o` après la phase du curseur, `O` avant — ne
|
||
diffèrent que par ce calcul, et passent tous par `ajouterUnePhase`, qui garde son comportement
|
||
d'origine quand aucune date ne lui est donnée.
|
||
|
||
`P` reste à part : un projet naît **hors de tout curseur**, sans date et sans ligne de référence.
|
||
L'agréger à la famille `a`/`o` aurait suggéré une position qu'il n'a pas.
|
||
|
||
### Le curseur se peint dans la colonne, pas seulement sur la barre
|
||
|
||
La barre portait déjà un contour. Il ne suffit pas : le curseur se pose sur des lignes qui n'ont
|
||
aucune barre en face — celle d'un projet, un projet plié, un projet masqué. La colonne des libellés
|
||
est le seul endroit qui porte une ligne pour *chaque* position atteignable, et c'est aussi ce qui
|
||
permet à `app.js` d'y compter les enfants pour amener le défilement au bon endroit. L'énumération de
|
||
`lignesFrise` et celle de `construireLibelles` doivent donc rester d'accord.
|
||
|
||
Sur la ligne d'un projet, le trait d'accent seul se noyait dans son fond coloré et son triangle de
|
||
pli : elle prend aussi une teinte, mélangée à sa couleur propre plutôt qu'au fond de la surface. La
|
||
teinte passe par une redéfinition de `--fond-libelle` et non par `background`, parce que le dégradé
|
||
qui donne un fond aux commandes du projet s'appuie sur cette même variable — un fond posé en direct
|
||
y laissait une couture.
|
||
|
||
### Le `?` n'est pas facultatif
|
||
|
||
Des raccourcis d'une lettre ne se devinent pas. Un bouton dans la barre d'outils et la touche `?`
|
||
ouvrent la même table, dans un `<dialog>` **modal** — contrairement aux deux panneaux : celui-ci ne
|
||
sert qu'à lire, et laisser `j` déplacer le curseur derrière la table qui explique `j` serait absurde.
|
||
|
||
La table est écrite à la main plutôt que bâtie depuis `SEQUENCES`, qui associe une touche à un nom
|
||
d'action : cela ne dit ni ce que l'action fait, ni dans quel ordre la présenter, ni quelles touches
|
||
vont par paires. La génération aurait produit une liste exacte et illisible.
|
||
|
||
### La vue par mois n'a pas de curseur
|
||
|
||
Elle se lit, elle ne s'édite pas (décision 24) : rien n'y aurait de sens à désigner. `j`, `k`, `gg`
|
||
et `G` y font donc ce qu'ils font dans un document — ils déroulent la liste. `gm` et `ga` marchent
|
||
dans les deux vues, le reste attend le retour à la frise.
|
||
|
||
## 27. Le cycle de vie d'un projet, et l'horizon
|
||
|
||
L'outil est employé pour piloter les chantiers d'une petite mairie : beaucoup plus de projets
|
||
imaginables que de temps pour les mener, et une crainte première — **ne rien oublier**. Une idée
|
||
qu'on ne peut pas traiter cette année ne doit pas disparaître pour autant.
|
||
|
||
Or l'outil ne connaissait que les projets déjà planifiés. Tout ce qui n'était pas encore engagé
|
||
vivait ailleurs — un carnet, une tête, un compte rendu de réunion — de sorte que l'arbitrage se
|
||
rendait **sans voir le planning**, alors que c'est précisément le planning qui dit s'il y a la
|
||
place.
|
||
|
||
### La priorité est une décision d'avant l'engagement
|
||
|
||
Le point de départ était une matrice d'Eisenhower dans le panneau d'un projet. Deux objections
|
||
l'ont déplacée.
|
||
|
||
L'urgence est **déjà dans les dates**. La resaisir crée un champ qui se périme en silence : un
|
||
projet coché « urgent » en juin ne l'est plus en septembre, et rien ne viendra le décocher.
|
||
|
||
Surtout, une fois un projet planifié, **l'arbitrage est déjà rendu** — ce sont ses dates qui le
|
||
portent. Une priorité posée sur un projet engagé ne déciderait plus rien, elle commenterait. Le
|
||
lieu de la priorisation est donc en amont, sur ce qui n'est pas encore engagé.
|
||
|
||
### Quatre états, dont deux se déduisent
|
||
|
||
Un projet traverse quatre états, et deux d'entre eux se lisent dans les données sans qu'on ait
|
||
rien à tenir à jour :
|
||
|
||
| État | Comment on le sait |
|
||
|---|---|
|
||
| **Envisagé** | il n'a qu'un horizon, pas encore de phase |
|
||
| **Engagé** | on lui a donné au moins une phase datée |
|
||
| **Terminé** | toutes ses phases sont finies **et** on l'a acté |
|
||
| **Écarté** | on a décidé de ne pas le faire |
|
||
|
||
Une règle unique les résume : **les phases commandent l'état, sauf quand on a prononcé quelque
|
||
chose.** Livré n'est pas clos, et abandonner est une décision — ces deux-là s'actent. Le reste se
|
||
déduit.
|
||
|
||
**Clore un projet termine toutes ses phases avec lui.** Sans cela la frise se contredirait : une
|
||
ligne éteinte au-dessus de segments pâles, et la vue par mois annonçant des tâches à venir dans un
|
||
projet fini. C'est donc la seule opération du cycle qui **écrase des données** — le statut d'une
|
||
phase bloquée est perdu, et rouvrir le projet ne peut pas le deviner. Elle se confirme à ce titre,
|
||
comme une suppression (décision 21), mais **seulement quand il reste quelque chose à écraser** : un
|
||
dialogue qui ne prévient de rien apprend à cliquer sans lire.
|
||
|
||
Engager n'est donc pas un bouton : c'est un geste, celui de donner ses premières dates fermes. Et
|
||
écarter n'est pas supprimer — le projet quitte la frise mais reste retrouvable, avec dans ses notes
|
||
la raison pour laquelle on a dit non. Les mêmes idées reviennent tous les deux ans, portées par
|
||
d'autres : savoir qu'on les a déjà examinées, et sur quel motif, a de la valeur.
|
||
|
||
### L'horizon est le repli des bornes
|
||
|
||
Un projet envisagé n'est pas une ligne hors du temps : il porte un **horizon**, exprimé en mois —
|
||
« mars 2027 », « de mars à septembre 2027 », « courant 2028 » — et il se pose **sur la frise**, à
|
||
cet horizon. C'est là que l'arbitrage devient concret : quand le printemps 2027 est déjà chargé et
|
||
que six projets pâles s'y superposent, on décide devant l'évidence plutôt que dans l'abstrait.
|
||
C'est ce qui remplace le calcul de charge que l'outil ne fera jamais.
|
||
|
||
La règle tient en une phrase, et elle ne mélange pas le saisi et le calculé :
|
||
|
||
> L'emprise temporelle d'un projet se calcule depuis ses phases. Un projet qui n'en a pas encore la
|
||
> déclare à la main : c'est son horizon.
|
||
|
||
Les phases gagnent toujours. `bornesProjet()` fait déjà ce calcul et retourne `null` quand il n'y a
|
||
aucune phase — l'horizon bouche ce trou, il n'ajoute pas de règle. Corollaire : **on ne réécrit
|
||
jamais une saisie par un calcul**. Un projet qui perdrait sa dernière phase retrouve l'horizon qu'il
|
||
avait déclaré, pas l'enveloppe des phases disparues.
|
||
|
||
Le grain est le mois, jamais l'année : en début de mandat, tout est à faire, et un horizon annuel ne
|
||
range plus rien. Un horizon s'exprime comme un intervalle de mois plutôt que par des crans nommés —
|
||
ni trimestre ni semestre à apprendre, un seul mécanisme, et trois propriétés qui en découlent :
|
||
|
||
- **la largeur de la barre pâle *est* l'incertitude**, l'œil la lit sans qu'on l'écrive ;
|
||
- **la précision de l'horizon suit la maturité du projet** : on resserre à mesure qu'on y voit
|
||
clair. Il n'y a pas de frontière entre envisagé et engagé, il y a un dégradé ;
|
||
- **engager, c'est préciser au jour près** ce qui était au mois près.
|
||
|
||
La barre d'un projet envisagé se dessine donc autrement : pâle, aux bords fondus, et **accrochée au
|
||
mois** — jamais au jour. C'est l'accroche au lundi de la décision 9 poussée d'un cran : le geste
|
||
existe, il ne permet simplement pas de poser une date qu'on n'a pas.
|
||
|
||
Elle fut d'abord rendue **immobile**, au motif qu'interdire physiquement valait mieux qu'une
|
||
convention. L'usage a tranché autrement, et pour une bonne raison : on ajuste un horizon *en
|
||
regardant ses voisins*, pour voir si le printemps 2027 est déjà chargé. Obliger à passer par un
|
||
panneau pour ce réglage-là, c'est le faire à l'aveugle. L'accroche au mois suffisait à protéger ce
|
||
qui devait l'être.
|
||
|
||
L'horizon **survit à l'engagement**. Non pour le comparer à ce qui a été planifié, mais parce que ne
|
||
rien effacer est ici plus simple qu'effacer, et parce qu'il sert de filet si le projet se dépeuple.
|
||
|
||
### Un horizon échu remonte à la surface
|
||
|
||
Un backlog échoue toujours de la même façon : tout ce qu'on n'ose pas refuser y atterrit, il gonfle,
|
||
plus personne ne le relit. Les deux garde-fous habituels — un plafond, une péremption qui efface —
|
||
sont ici interdits par la crainte de départ.
|
||
|
||
L'horizon en fournit un meilleur : **un projet dont l'horizon est dépassé se signale**. On est en
|
||
juin, quatre projets visaient le premier trimestre : il faut trancher, on repousse ou on écarte.
|
||
Rien ne disparaît, mais rien ne dort — et la relecture tombe d'elle-même au rythme des arbitrages
|
||
budgétaires.
|
||
|
||
### Ce qui ancre un projet écarté
|
||
|
||
La question n'a pas la même réponse selon d'où vient l'écartement.
|
||
|
||
Un projet écarté **depuis l'état engagé** garde ses phases datées, dont certaines ont réellement eu
|
||
lieu. Il a un ancrage, et un vrai : le travail fait ne doit pas s'effacer, puisque la frise est notre
|
||
mémoire.
|
||
|
||
Un projet écarté **depuis l'état envisagé** n'a rien. Son horizon a cessé d'être une prévision — on
|
||
ne le fera pas à cette date, on ne le fera pas du tout — et l'y laisser serait un mensonge sur la
|
||
frise.
|
||
|
||
D'où **la date d'écartement**, posée automatiquement, jamais saisie. C'est l'ancrage commun aux deux
|
||
cas, celui sans lequel les écartés ne forment plus qu'un tas. Il ne s'agit pas de journaliser les
|
||
décisions — les comptes rendus de réunion font ce travail, et mieux — mais d'horodater une sortie.
|
||
|
||
La clôture est horodatée de la même façon, et c'est ce qui donne au modèle sa forme définitive :
|
||
**les deux actes prononcés laissent une date, et ces deux dates sont l'unique trace de l'état dans le
|
||
fichier.** Acter, c'est dater. Il n'y a pas de champ « état » — il se lit dans ces deux dates et dans
|
||
la présence de phases.
|
||
|
||
On les retrouve d'abord en les cochant dans le filtre par état, ce qui les ramène dans la colonne
|
||
des libellés, groupés et éteints. Cela répond à « je sais que ça existe, je veux le retrouver ». Cela
|
||
ne répond pas à « relire d'un coup deux ans d'idées écartées avec leurs motifs », qui demanderait une
|
||
liste — geste assez rare pour attendre que le manque se fasse sentir.
|
||
|
||
### La réanimation ne demande aucun mécanisme
|
||
|
||
Puisque les états se déduisent, il suffit de **retirer l'écartement** : le projet retrouve tout seul
|
||
l'état que ses données commandent. Des phases, il redevient engagé ; un horizon seul, envisagé. Pas
|
||
de destination à choisir, une seule commande.
|
||
|
||
Le système se referme bien : un projet écarté en 2026 et réanimé aujourd'hui revient avec un horizon
|
||
périmé, donc se signale aussitôt comme échu. On le redate dans la foulée.
|
||
|
||
```
|
||
écarté ←──────┐
|
||
↗ ↑ │ réanimer
|
||
/ │ │ (l'état se
|
||
envisagé ──────┴─ engagé ──→ terminé recalcule)
|
||
(un horizon) (≥ 1 phase) (acté)
|
||
↑ │
|
||
└──────────────┘
|
||
s'il perd sa dernière phase
|
||
```
|
||
|
||
### L'état ne passe pas par les tags
|
||
|
||
Un tag est libre, disparaît quand plus personne ne le porte, et dérive orthographiquement
|
||
(décision 16) : trois propriétés inacceptables pour un état. Ce sont deux mécanismes indépendants
|
||
qui filtrent la même frise, et les tags restent disponibles pour ce à quoi ils servent.
|
||
|
||
Les deux filtres ne se comportent d'ailleurs pas pareil, et il faut que cela se voie : **celui des
|
||
tags est conjonctif, celui des états est disjonctif**. Un projet porte plusieurs tags mais un seul
|
||
état — cocher `client` + `urgent` resserre, cocher `envisagé` + `engagé` élargit. La même case à
|
||
cocher voudrait sinon dire deux choses opposées selon la ligne où elle se trouve.
|
||
|
||
Comme celui des tags, le filtre par état **n'est pas enregistré** : c'est un état de vue, pas un
|
||
réglage.
|
||
|
||
### Ce que le premier essai a corrigé
|
||
|
||
Trois choses ne se sont vues qu'en manipulant l'outil, et toutes disaient la même : **un projet
|
||
envisagé ne doit pas être un citoyen de seconde zone**.
|
||
|
||
**Engager démarre à l'horizon.** La première phase d'un projet envisagé naissait à la date du jour,
|
||
comme celle de n'importe quel projet vide. Elle apparaissait donc à des mois de la barre pâle qu'elle
|
||
venait remplacer, et le lien entre les deux se perdait. Elle commence maintenant au premier jour de
|
||
l'horizon : engager, c'est préciser au jour près ce qu'on avait déclaré au mois près, pas repartir
|
||
d'ailleurs.
|
||
|
||
**Les deux actes sont dans le panneau, pas seulement dans le menu.** Ils avaient été rangés dans le
|
||
menu ⋯ au nom de la décision 20 — un bouton permanent se mérite, et clore un projet est rare. Mais
|
||
c'est dans le panneau qu'on **lit** l'état du projet, donc c'est là qu'on cherche à en changer : les
|
||
y chercher et ne pas les trouver coûte plus cher que deux boutons de plus. Ils figurent aux deux
|
||
endroits, comme « plier » ou « masquer ».
|
||
|
||
**Les projets sans phase paraissent dans la vue par mois.** Ils n'y figuraient pas, faute de phase à
|
||
lister — alors qu'un projet visé pour mars 2027 *arrive* bel et bien en mars 2027. Les omettre
|
||
revenait à dire que la liste ne montre que l'engagé, quand toute cette décision pose le contraire.
|
||
Ils s'y rattachent au premier mois de leur horizon, dans les mêmes colonnes que les phases mais en
|
||
italique. C'est ce qui a fait renommer `phasesParMois` en `entreesParMois` : la liste ne montre plus
|
||
seulement des phases.
|
||
|
||
La règle porte sur **l'absence de phases et non sur l'état**, exactement comme `empriseProjet` : un
|
||
projet écarté avant d'avoir jamais été engagé n'a lui non plus que son horizon pour se situer, et
|
||
il doit se retrouver là quand on ouvre le cimetière. La distinguer par l'état aurait fait du
|
||
cimetière de la liste par mois une collection des seuls projets qui avaient démarré — soit
|
||
l'inverse de ce qu'on y cherche.
|
||
|
||
### Ce qui a été reporté, et pourquoi
|
||
|
||
Ces choix ne sont pas des refus définitifs. Ils attendent leur moment, et il vaut mieux savoir
|
||
lequel.
|
||
|
||
**La matrice d'Eisenhower.** Elle n'a pas de sens sur des projets engagés, pour les raisons
|
||
ci-dessus. Sur les seuls envisagés elle redevient cohérente, et se branche même bien : l'horizon
|
||
fournit l'axe d'urgence, il ne resterait que l'importance à saisir. À reprendre quand une liste
|
||
existera, et seulement si l'horizon seul ne suffit pas à départager.
|
||
|
||
**Une vue liste des envisagés.** La frise sait situer, elle ne sait pas ranger : on ne peut pas
|
||
ordonner six projets par priorité dans un empilement de couloirs. Cette première étape ne livre donc
|
||
**aucun mécanisme d'ordre explicite entre projets** — ce qu'elle livre, c'est l'horizon, et c'est
|
||
déjà l'arbitrage le plus décisif. Reste le départage fin entre projets visant le même trimestre.
|
||
|
||
**Un dézoom de l'axe temporel**, pour embrasser plusieurs années d'un coup. Le jour où il arrivera,
|
||
il faudra rouvrir la décision 14, « une seule échelle, pas de zoom ».
|
||
|
||
**Une vue de rétrospective.** La frise défile en arrière sans limite (décision 15) : le passé y est
|
||
déjà lisible, et un écran de plus l'aurait redit. Corollaire à tenir : **un projet terminé reste sur
|
||
la frise**, éteint mais présent, sinon on perd le bilan.
|
||
|
||
**Des dates réelles à côté des dates prévues.** Ce serait doubler chaque champ pour un gain nul :
|
||
dans cet outil on recale les barres au fil de l'eau, donc la date de fin *devient* le réel. La
|
||
contrepartie est assumée — la qualité du bilan dépend de l'entretien de la frise, et aucun champ ne
|
||
répare un planning qu'on ne tient pas.
|
||
|
||
**Un repère de fin de mandat**, comme le repère « aujourd'hui ». Pertinent sur le principe, sans
|
||
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.
|