feat: completude de l'editeur Excel - mise en forme, sortie, concurrence, cache, outils IA #156
CI / lint (push) Successful in 2m36s
CI / security (push) Successful in 1m49s
CI / test (push) Successful in 4m33s
CI / build (push) Successful in 2m39s
CI / e2e (push) Successful in 15m45s

L'editeur lisait les styles mais ne les ecrivait pas, exportait la feuille
entiere et laissait le dernier-ecrivain gagner entre processus.

- A8 : bouton Mise en forme (gras/italique/souligne, alignements, couleurs via
  selecteur natif, formats de nombre, fusion, volets figes, largeur/hauteur) et
  nouvelle route PUT .../xlsx/style (verrou, backup, swap atomique, garde de
  perte, If-Match) ; A9 : decision "pas de moteur de formule" annoncee dans
  l'UI ; A10 : undo/redo unifie, piles par fichier conservees au re-rendu
- A11 : export de la selection + Markdown/HTML/impression et recherche sur
  toutes les feuilles ; A12 : concurrence optimiste (If-Match -> 409 reparable,
  retry qui relit) ; A13 : cache des metadonnees par (chemin, mtime, taille)
- A14 : outils IA .xlsm/.csv + search_workbook, analyze_range,
  edit_xlsx_structure
- tests : test_xlsx_styles.py (17), test_spreadsheet_tools.py (34),
  TestOptimisticConcurrency/TestMetaCache, JSDOM xlsx-viewer 107/107
This commit is contained in:
2026-09-30 07:04:47 -04:00
parent 1bacfd69d9
commit c5c225a68e
32 changed files with 4615 additions and 302 deletions
+129 -1
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.44.1**.
> [Unreleased](#unreleased). La dernière version livrée est **2.45.0**.
---
@@ -14,6 +14,134 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [2.45.0] — 2026-09-30
### Ajouté
- **#156-A5 — presse-papiers de plage dans l'éditeur Excel.** Copier, couper et coller un
**bloc** de cellules (TSV), au clavier (`Ctrl+C`/`Ctrl+X`/`Ctrl+V`) comme par le menu
contextuel ; le collage remplit la plage à partir de la cellule active et la laisse
sélectionnée. La grille tient son propre presse-papiers (le presse-papiers système est
synchronisé quand le navigateur l'autorise), le collage est toujours du **texte** — jamais de
HTML — et il est **annulable**. Un bloc plus large que la grille affichée est tronqué et signalé.
- **#156-A6 — raccourcis clavier complets.** `Ctrl+S` (enregistrer), `Ctrl+A` (sélectionner la
feuille rendue), `Suppr` (vider la sélection), `F2` (édition), `Ctrl+Home`/`Ctrl+End` (coins de
la feuille), `Home`/`End` (bords de la ligne), `PgUp`/`PgDn` (un écran), `Ctrl+flèches` (bout de
la plage de données) et `Maj+Entrée` (saut de ligne dans la cellule). Une cellule reste en mode
« navigation » jusqu'à la saisie (ou `F2`) : `Suppr` et `Home`/`End` ne volent donc jamais une
touche du curseur de texte.
- **#156-A7 — zone Nom éditable et aide à la saisie.** La zone Nom devient un champ
(« Atteindre ») : saisir `B12`, `A1:B3` ou `Feuille!A1` puis `Entrée` sélectionne la cellule ou
la plage (avec changement d'onglet si la feuille est nommée) ; une référence inconnue est
refusée avec un message et l'adresse précédente restaurée. La barre de formule propose les noms
de fonctions courants (liste localisée FR/EN).
- **#156-A8 — mise en forme en écriture.** Un bouton **Mise en forme** ouvre un menu qui
agit sur la **sélection courante** (une cellule ou une plage) : gras, italique, souligné,
effacement de la mise en forme, alignement gauche/centré/droite, couleur de police et de fond,
format de nombre (général, nombre, pourcentage, devise, date, texte), fusion/défusion, volets
figés/libérés et largeur de colonne / hauteur de ligne. L'écriture passe par une nouvelle route
`PUT /api/file/{vault}/xlsx/style`, sur le même modèle que `xlsx/save` et `xlsx/structure` :
verrou par fichier, écriture atomique (`.tmp` + `os.replace`), backup automatique, garde de perte
(une opération qui effacerait des éléments non préservables est refusée sans `force`) et
**concurrence optimiste** (`If-Match`, cf. A12). Les mises en forme ne sont pas proposées sur un
`.csv` (le format n'en porte pas) ni sur une grille en lecture seule. Les couleurs viennent du
sélecteur natif, jamais d'une palette codée en dur. Tests : `tests/test_xlsx_styles.py` (17) et
JSDOM (application d'un format, refus d'un CSV, garde de perte).
- **#156-A9 — le calcul est explicitement hors périmètre, et annoncé.** L'éditeur n'embarque
aucun moteur de formule : ObsiGate affiche et écrit les formules telles qu'Excel les a
enregistrées, les cellules dépendantes ne se recalculent pas à l'écran. Le choix est désormais
**dit à l'utilisateur** : un enregistrement contenant des valeurs commençant par `=` ou `@`
affiche « enregistrée comme texte » tant que le bouton `f(x)` n'est pas activé (la garde
anti-DDE de BUG-088 reste en place). La pastille « Formules non recalculées » et la fiche #156
portent la décision. Tests JSDOM : l'avertissement apparaît, et disparaît une fois `f(x)`
activé.
- **#156-A10 — historique annuler/rétablir unifié, conservé au re-rendu.** La pile ne couvrait
que les éditions de cellules : « Effacer le contenu », le tri, le filtre et les actions de
**structure** y entrent désormais, avec des entrées typées (`cell`, `order`, `hidden`,
`structure`). Les piles sont **conservées par fichier** (8 fichiers, `_xlsxHistory`) au lieu
d'être vidées par un re-rendu ; `Suppr` passe par le même chemin (donc annulable) ; le filtre
est **coalescé** en une seule entrée par session de frappe ; un rejeu de structure appelle
l'endpoint en mode silencieux (pas de doublon d'historique ni de bandeau parasite) et une
action destructrice (suppression de ligne/colonne/feuille) n'est **pas** annulable, faute
d'inverse. Tests JSDOM : édition → effacement → tri annulés successivement, insertion de ligne
rejouée par l'endpoint, action destructive non annulable, filtre annulé par un seul `Ctrl+Z`.
- **#156-A11 — sortie (CSV, Markdown, HTML, impression) et recherche sur tout le classeur.**
L'export ne se limite plus à la feuille entière : le bouton **CSV** exporte la **sélection**
quand une plage de plusieurs cellules est active (nom de fichier `Feuille-A1B2.csv`), sinon la
feuille ; un menu **Exporter** ajoute le **Markdown** (tableau), le **HTML** (document
autonome) et l'**impression** (la feuille seule, sans le ruban, via un cadre hors écran). Les
quatre formats suivent le **contenu affiché** (la valeur calculée en cache reste une
infobulle). L'écart doc/code de #153-A13 (« export de la sélection ») est tranché : la
sélection est le défaut quand elle existe. La recherche (`Ctrl+F`) parcourt désormais **toutes
les feuilles** : compteur « n/m · k feuilles » et activation automatique de l'onglet de la
correspondance atteinte.
- **#156-A14 — l'assistant IA couvre les trois formats édités et gagne trois outils.** Les
outils existants acceptent `.xlsm` (écriture avec macros préservées) et `.csv` (lecture et
écriture, délimiteur conservé) au lieu du seul `.xlsx` ; `update_xlsx_cells` écrit un `.csv`
via `save_csv_cells`. Nouveaux outils : `search_workbook` (recherche dans toutes les feuilles,
comptée par feuille), `analyze_range` (nombre, somme, moyenne, min, max d'une plage A1, avec
échantillon borné) et `edit_xlsx_structure` (ajouter/renommer/dupliquer/supprimer une feuille,
insérer/supprimer lignes et colonnes) — les deux premiers en risque READ, le dernier conservant
la confirmation obligatoire des outils mutants. Tests `tests/test_spreadsheet_tools.py` (34).
### Modifié
- **#156 — Éditeur Excel : audit de complétude, roadmap et 4 défauts enregistrés (documentation
seule).** Nouvelle fiche `docs/features/xlsx-editor-completeness.md` (audit par couche, manques
fonctionnels, risques, critères d'acceptation) et item **#156** dans `docs/ROADMAP.md`
(P0 défauts · P1 presse-papiers/clavier · P2 mise en forme/calcul/undo · P3
sortie/robustesse/perf). **BUG-096 → BUG-099** ouverts dans `docs/ISSUES_TODOLIST.md` :
enregistrer un `.csv` depuis la visionneuse lève un `TypeError` (reproduit en JSDOM, aucun
`PUT …/csv/save` émis) ; « Charger la suite » d'une feuille `.xlsm` tronquée répond **415** ;
le délimiteur CSV est figé sur `,` alors que l'export écrit `;` ; la sonde de perte plafonnée à
8 Mo peut laisser passer une écriture qui détruit les valeurs calculées en cache. L'audit
lui-même ne changeait aucun code ; les correctifs correspondants suivent ci-dessous.
- **#156-A12 — concurrence optimiste entre processus (`If-Match`, 409 réparable).** Chaque
lecture renvoie la **version** du fichier (`xlsx_revision` : `mtime_ns` + taille) et les trois
routes d'écriture (`xlsx/save`, `xlsx/structure`, `csv/save`) acceptent l'en-tête `If-Match` ou
le champ `if_match` : si le fichier a changé depuis, l'écriture est refusée en **409**
`conflict` (`details.reason = "stale_revision"`, versions attendue et courante) au lieu
d'écraser l'autre écrivain. Le bandeau « Réessayer » **relit** le fichier puis rejoue la
sauvegarde avec la version courante ; les réponses de succès renvoient la nouvelle version, que
le client enchaîne sur les écritures suivantes. Tests : `TestOptimisticConcurrency` (6) et
JSDOM (version lue, version rafraîchie par la réponse, relecture avant le retry).
- **#156-A13 — cache des métadonnées par `mtime`.** `read_workbook_meta()` rechargeait le
classeur entier à chaque fenêtre servie ; les métadonnées (styles, fusions, volets, largeurs)
sont désormais mémoïsées par `(chemin, mtime_ns, taille)` (LRU de 8) et invalidées à chaque
écriture (cellules, structure, CSV). Charger une fenêtre ne rescanne plus le classeur. L'axe
des colonnes au-delà de `MAX_COLS` reste hors périmètre (voir la fiche #156). Tests :
`TestMetaCache` (4).
### Corrigé
- **BUG-096 — enregistrer un `.csv` depuis la visionneuse échoue (`TypeError`, aucun
`PUT …/csv/save` émis).** Le job de sauvegarde lisait le nom de la feuille dans
`xlsx_sheets` — absent du payload d'un CSV — donc `sheets[0].name` levait un `TypeError`
**avant** toute requête : aucune modification de CSV ne pouvait être enregistrée. Le nom est
désormais résolu avec repli (feuille → nom du fichier) ; l'endpoint `csv/save` est inchangé.
Tests : JSDOM (un CSV monté **sans** `xlsx_sheets` émet un unique `PUT …/csv/save`).
- **BUG-097 — « Charger la suite » d'une feuille `.xlsm` renvoyait **415**.** Un `.xlsm`
s'édite dans la même visionneuse que le `.xlsx`, mais `GET …/xlsx/sheet` refusait tout autre
format : les lignes au-delà du plafond de 500 étaient inaccessibles. L'endpoint accepte
désormais `.xlsx` **et** `.xlsm` (les autres formats restent en **415**). Tests :
`test_xlsx_formats.py` (fenêtre servie pour un `.xlsm`, refus maintenu pour un `.csv`).
- **BUG-098 — un CSV français (`;`) s'affichait en une seule colonne.** Le délimiteur était
figé à la virgule, en lecture comme en réécriture. `sniff_csv_delimiter()` détecte `;`, `,`
ou tabulation (repli : décompte sur la première ligne, puis `,`) et `save_csv_cells()`
**réutilise le délimiteur détecté** — un CSV point-virgule s'affiche en colonnes distinctes et
le reste après édition. Tests : `test_xlsx_formats.py` (lecture, écriture, champs entre
guillemets contenant le séparateur).
- **BUG-099 — perte silencieuse possible des valeurs calculées en cache (sonde plafonnée).**
La détection des `<f>…</f><v>…</v>` disposait d'un budget **unique** de 8 Mo partagé entre les
feuilles : au-delà, un résultat négatif était pris pour une preuve et l'écriture détruisait
les valeurs mises en cache sans avertissement. Le budget est désormais **par feuille** (4 Mo)
avec un plafond global (32 Mo), et un budget épuisé est **signalé** (`cached_values_unverified`,
libellé FR/EN) : la garde de sauvegarde demande une confirmation au lieu de laisser passer.
Tests : `TestXlsxCachedValueProbe` (budget épuisé → signalé, feuille volumineuse → la suivante
reste scannée, scan complet propre → aucun faux positif).
---
## [2.44.1] — 2026-09-29
### Sécurité