Les panneaux d'édition étaient modaux et la colonne de gauche tronquait tout ce qu'elle portait. Sept corrections, mesurées sur data/exemple.json. Panneaux - Le voile qui recouvrait la page disparaît : la frise reste manipulable pendant l'édition, et cliquer une autre barre y bascule la sélection. Il rendait aussi le raccourci flèches inatteignable, le focus partant dans le champ « Nom » sans qu'on puisse cliquer ailleurs pour en sortir. - Un clic dans une zone inerte referme le panneau : ce que rendait le voile, sans le calque qui figeait la frise. - Les paramètres d'un projet quittent le <dialog> modal pour le même panneau qu'une phase, avec la même édition au fil de la saisie. « Nouveau projet » crée donc le projet aussitôt, comme le fait déjà l'ajout d'une phase : plus aucune création ne passe par un formulaire. Colonne des projets - Le nom ne disposait que de 56 px sur 250, et « client » s'affichait « c. ». Les commandes passent en surimpression au survol, les tags cèdent en bloc derrière un compteur, et le nom cède en dernier. - La colonne se redimensionne, largeur conservée dans localStorage. - L'œil disparaît : il faisait doublon avec l'entrée « Masquer » du menu. Un « + » le remplace, l'ajout d'une phase étant l'action la plus fréquente, et le nom du projet devient cliquable — il portait déjà un curseur « main » sans aucune action derrière. - Plus de commande « Ajouter un jalon » : un jalon est une phase dont on coche la case, pas un autre objet. Frise - Le nom d'une phase reste accroché au bord visible quand sa barre s'enfonce sous la colonne. - Cliquer un nom de phase hors écran ramène la frise sur elle. - La barre cumulative d'un projet s'affiche aussi quand il est déplié, amincie pour ne pas se faire passer pour une barre de phase. Arbitrages consignés dans docs/decisions.md, sections 8 et 17 à 21. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
436 lines
24 KiB
Markdown
436 lines
24 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.
|
||
|
||
Sauf que cette API exige un **contexte sécurisé** : elle ne fonctionne 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 à chaque écriture, ce que
|
||
la File System Access API ne permettait pas simplement.
|
||
|
||
## 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. Dépliée, elle voisine avec les barres de phases : elle s'amincit alors pour qu'on
|
||
ne la prenne pas pour l'une d'elles, et le fantôme de référence s'amincit avec elle.
|
||
|
||
## 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.
|
||
|
||
## 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.
|