diff --git a/README.md b/README.md index dd363ae..7a4b927 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Une fois un projet en place : | Clic sur une barre ou son nom | Ouvre le panneau de détail | | Clic sur une autre barre, panneau ouvert | Bascule le panneau sur cette phase | | Clic sur un nom de phase hors écran | Ramène la frise sur elle | -| `←` `→` | Décale d'un jour la phase sélectionnée | +| `←` `→` | Décale d'un jour la phase du curseur | | `Maj` + `←` `→` | Allonge ou raccourcit d'un jour | | `Échap` | Annule le glisser en cours, ou ferme le panneau | | Clic dans une zone vide | Ferme le panneau ouvert | @@ -111,6 +111,49 @@ Une fois un projet en place : Faire défiler jusqu'au bord droit élargit la frise vers le futur, et jusqu'au bord gauche vers le passé. Le bouton **Aujourd'hui** ramène la vue sur la date du jour. +### Au clavier, à la vim + +La frise a un **curseur** : la ligne sur laquelle portent les raccourcis. Il se pose en cliquant une +barre ou un nom, ou à la première frappe de `j`, et il se voit à la ligne bleutée dans la colonne de +gauche. Il tient aussi sur la ligne d'un **projet**, position d'où l'on plie, renomme et ajoute. + +Le bouton **`?`** de la barre d'outils, ou la touche `?`, ouvre la liste complète. En résumé : + +| Touche | Effet | +|---|---| +| `j` `k` | Ligne suivante, précédente — projets traversés | +| `h` `l` | Phase précédente, suivante **du même projet**, sans déborder sur le voisin | +| `{` `}` | Projet précédent, suivant | +| `gg` `G` | Première, dernière ligne | +| `Entrée` `e` | Ouvre le volet sur la ligne du curseur et saisit le nom | +| `Espace` | Statut suivant : à venir, en cours, terminé, bloqué | +| `A` `a` | Ajoute une phase à la fin, au début du projet | +| `o` `O` | En ajoute une juste après, juste avant celle du curseur | +| `P` | Ajoute un projet | +| `dd` | Supprime la phase — ou le projet depuis sa ligne | +| `za` | Plie ou déplie le projet | +| `zR` `zM` | Déplie tout, plie tout | +| `zz` | Recentre la frise sur le curseur | +| `w` `b` | Un mois plus tard, plus tôt | +| `W` `B` | Un an plus tard, plus tôt | +| `ga` | Revient à aujourd'hui | +| `gm` | Bascule entre la frise et la vue par mois | + +Les phases étant rangées par date, `o` et `O` insèrent **dans le temps** et non dans une liste : la +nouvelle phase se pose bord à bord avec sa voisine. `P`, lui, ne dépend pas du curseur : un projet +naît hors de toute ligne, et la vue revient à la frise si on était dans la liste par mois. + +Deux garde-fous. Ces touches **se taisent pendant une saisie** — dans un champ, une note, un +dialogue ou le menu contextuel, elles écrivent la lettre qu'elles portent. Et `dd` demande toujours +confirmation, comme le bouton **Supprimer**. + +Le volet ouvert **suit le curseur** : `j` et `k` font alors défiler les phases une à une dans le +panneau de détail, ce qui est le moyen le plus rapide de relire tout un projet. Pour cela, ouvrir le +volet d'un **clic sur une barre** — `Entrée` et `e`, eux, placent la frappe dans le champ « Nom », où +les raccourcis se taisent. + +`Échap` referme le volet sans déplacer le curseur, et la frappe suivante repart de là. + ### Les notes Un projet **et** une phase portent chacun des notes, en bas de leur panneau. Elles y prennent toute @@ -278,10 +321,11 @@ données. Aucune dépendance de part ni d'autre, les lanceurs intégrés à Node | `build.sh` | Compile les binaires des cinq plateformes dans `dist/` | | `js/model.js` | Données et règles métier : CRUD, validation, bornes, référence, dérive, tags, regroupement par mois | | `js/storage.js` | Dialogue avec le serveur, sauvegarde debouncée, indicateur d'état | -| `js/timeline.js` | Rendu de la frise : échelle, couloirs, barres, jalons, pli/dépli | +| `js/timeline.js` | Rendu de la frise : échelle, couloirs, barres, jalons, pli/dépli, curseur | | `js/mois.js` | Rendu de la vue par mois : groupes, lignes, ancrage sur le mois courant | | `js/tags.js` | Pastilles de tags, partagées par les deux vues | | `js/drag.js` | Glisser et redimensionner les barres | +| `js/clavier.js` | Table des raccourcis à la vim, préfixes `g` `z` `d`, garde-fou de saisie | | `js/markdown.js` | Analyse du markdown des notes, aplatissement pour les infobulles. Sans DOM, donc testable sous Node | | `js/notes.js` | Rendu du markdown en DOM et bloc de notes — aperçu, barre d'outils, saisie — partagé par les deux panneaux | | `js/detail.js` | Panneau de détail d'une phase | diff --git a/css/style.css b/css/style.css index 2bbf8ad..1cd702d 100644 --- a/css/style.css +++ b/css/style.css @@ -577,6 +577,49 @@ button[aria-pressed="true"] { opacity: 0.45; } +/* --- curseur clavier ------------------------------------------------------ */ + +/* Le curseur des raccourcis vim se marque ici, et pas seulement sur la barre : + il se pose aussi sur des lignes qui n'ont aucune barre en face — celle d'un + projet, un projet plié, un projet masqué. Sans cette marque, `j` et `k` + déplaceraient quelque chose d'invisible. + + Un trait sur le bord gauche, posé en `box-shadow` : une vraie bordure + pousserait le contenu de trois pixels à chaque déplacement du curseur. + + Les deux sortes de ligne prennent aussi une teinte d'accent, mais pas sur le + même fond : celui d'un projet est déjà coloré à ses couleurs, et le mélange + part donc de là. Le trait seul ne suffisait pas sur cette ligne-là, où son + fond propre et le triangle de pli le noyaient. */ +.libelle-projet--curseur, +.libelle-phase--curseur { + color: var(--texte); + box-shadow: inset 3px 0 0 var(--accent); +} + +.libelle-phase--curseur { + background: color-mix(in srgb, var(--accent) 12%, var(--surface)); +} + +/* On redéfinit `--fond-libelle` au lieu de peindre le fond : le dégradé qui + donne un fond aux commandes s'appuie sur cette même variable, et un fond posé + en direct laisserait une couture visible sous elles. La valeur reprend donc + celle de `.libelle-projet`, teintée d'accent — on ne peut pas se citer + soi-même dans une variable. */ +.libelle-projet--curseur { + --fond-libelle: color-mix( + in srgb, + var(--accent) 16%, + color-mix(in srgb, var(--couleur-projet) 8%, var(--surface)) + ); +} + +/* Le curseur désigne une ligne de projet : ses commandes doivent se voir, sans + quoi rien ne dirait ce que cette ligne sait faire. */ +.libelle-projet--curseur .libelle-projet__commandes { + opacity: 1; +} + /* --- couloirs et barres -------------------------------------------------- */ .couloir { @@ -589,6 +632,13 @@ button[aria-pressed="true"] { background: color-mix(in srgb, var(--couleur-projet) 5%, transparent); } +/* Curseur clavier posé sur la ligne d'un projet. Elle n'a que sa barre + cumulative, trop fine pour porter un contour : c'est le couloir entier qui + prend la teinte. Après `.couloir--projet`, dont elle remplace le fond. */ +.couloir--curseur { + background: color-mix(in srgb, var(--accent) 10%, transparent); +} + .barre { position: absolute; top: calc((var(--hauteur-ligne) - var(--hauteur-barre)) / 2); @@ -1392,6 +1442,85 @@ button[aria-pressed="true"] { padding: 0; } +/* --- aide des raccourcis -------------------------------------------------- */ + +/* Le dialogue de confirmation tient en 400 px parce qu'il pose une question ; + celui-ci est une table de référence qu'on parcourt du regard. Il prend donc + la largeur qu'il faut, plafonnée par la fenêtre, et ses sections se rangent + en colonnes tant qu'elles y tiennent. */ +.dialogue--aide { + max-width: min(920px, calc(100vw - 32px)); + max-height: calc(100vh - 32px); +} + +.aide__colonnes { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 4px 28px; +} + +.aide__colonnes h3 { + margin: 10px 0 6px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--texte-doux); +} + +/* Deux colonnes dans un `
` : la colonne des touches se cale sur la plus + large de la section — fixée en `em`, elle creusait un couloir vide devant les + raccourcis d'une seule lettre ; laissée libre, elle mettrait les libellés en + escalier d'une ligne à l'autre. */ +dl.aide { + display: grid; + grid-template-columns: max-content 1fr; + gap: 3px 16px; + margin: 0; + font-size: 13px; +} + +dl.aide dt { + display: flex; + flex-wrap: wrap; + gap: 3px; + align-items: baseline; +} + +dl.aide dd { + margin: 0; + color: var(--texte-doux); + line-height: 1.45; +} + +kbd { + padding: 1px 5px; + border: 1px solid var(--bordure-forte); + border-bottom-width: 2px; + border-radius: 4px; + background: var(--fond); + color: var(--texte); + font-family: inherit; + font-size: 12px; + font-weight: 600; + white-space: nowrap; +} + +.aide__note { + padding-top: 4px; + border-top: 1px solid var(--bordure); + font-size: 12px; +} + +/* Le point d'interrogation de la barre d'outils. Rond et discret : c'est un + recours, pas une commande qu'on utilise en travaillant. */ +.outils__aide { + width: 28px; + padding: 0; + border-radius: 50%; + font-weight: 700; +} + /* --- vue par mois -------------------------------------------------------- */ /* Rien n'est positionné en absolu ici, contrairement à la frise : la liste diff --git a/docs/decisions.md b/docs/decisions.md index 5f9f17b..cb87858 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -718,3 +718,112 @@ reconstruit chaque projet champ par champ et laisse tomber ce qu'il ne connaît 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 `` **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. diff --git a/index.html b/index.html index 5807e4d..499a891 100644 --- a/index.html +++ b/index.html @@ -35,6 +35,20 @@ + +

@@ -253,6 +267,106 @@
+ + +
+

Raccourcis clavier

+ +
+
+

Déplacer le curseur

+
+
j k
+
Ligne suivante, précédente
+
h l
+
Phase précédente, suivante du même projet
+
{ }
+
Projet précédent, suivant
+
gg G
+
Première, dernière ligne
+
+
+ +
+

Modifier la phase du curseur

+
+
+
Décale d'un jour
+
Maj+
+
Allonge, raccourcit d'un jour
+
Espace
+
Statut suivant : à venir, en cours, terminé, bloqué
+
Entrée e
+
Ouvre le volet et saisit le nom
+
dd
+
Supprime la phase, ou le projet depuis sa ligne
+
+
+ +
+

Ajouter

+
+
A
+
Une phase à la fin du projet
+
a
+
Une phase au début du projet
+
o
+
Une phase juste après celle du curseur
+
O
+
Une phase juste avant elle
+
P
+
Un projet
+
+
+ +
+

Plier, cadrer, naviguer

+
+
za
+
Plie ou déplie le projet
+
zR zM
+
Déplie tout, plie tout
+
zz
+
Recentre la frise sur le curseur
+
w b
+
Un mois plus tard, plus tôt
+
W B
+
Un an plus tard, plus tôt
+
ga
+
Revient à aujourd'hui
+
gm
+
Bascule frise et vue par mois
+
Maj+molette
+
Fait défiler la frise horizontalement
+
+
+
+ +

+ Toutes ces touches se taisent pendant une saisie : dans un champ, une + note ou un dialogue, elles écrivent la lettre qu'elles portent. + Échap referme le volet ouvert sans déplacer le curseur. +

+ + + + +
+
+