feat: completude de l'editeur Excel - mise en forme, sortie, concurrence, cache, outils IA #156
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:
+129
-1
@@ -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é
|
||||
|
||||
Reference in New Issue
Block a user