Revoir l'ergonomie des panneaux et de la colonne des projets

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>
This commit is contained in:
2026-08-01 10:42:35 +02:00
parent a2a5c5cb9c
commit eaca2f7813
9 changed files with 1032 additions and 333 deletions

View File

@@ -92,15 +92,20 @@ Gérer les jours ouvrés impose de faire passer chaque calcul de date par un hel
aussitôt la question des congés et des jours fériés — donc un calendrier à tenir à jour. Sur des
phases qui se comptent en semaines et en mois, l'écart est dans le bruit de l'estimation.
## 8. Le mode plié est en lecture seule
## 8. La barre cumulative est en lecture seule
Un couloir plié affiche une barre unique segmentée par phase. On aurait pu rendre cette barre
glissable pour décaler tout le projet en bloc.
La ligne d'un projet porte une barre unique segmentée par phase, qui donne sa forme d'ensemble. 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. Le mode plié sert à regarder ; pour modifier, on
déplie.
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
@@ -239,15 +244,17 @@ 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 par l'œil (`hidden`), lui, est bien enregistré —
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 | Œil (`hidden`) |
| | 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é.
@@ -261,3 +268,168 @@ appliquée en normalisant, ce qui la rend vraie par construction — aussi bien
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.