261 lines
22 KiB
Markdown
261 lines
22 KiB
Markdown
# #153 — Visionneuse & édition XLSX — état des lieux et backlog
|
||
|
||
> **Item de roadmap :** [#153 — Visionneuse & édition XLSX — complétude](../ROADMAP.md)
|
||
> **Origine :** #152 (visionneuse XLSX, livrée en 2.27.0 — voir
|
||
> [archive/COMPLETED_v1-v2.md](../archive/COMPLETED_v1-v2.md))
|
||
> **Statut :** ✅ **Backlog terminé et livré le 2026-09-28** — P0 le 2026-09-27 (BUG-085 → BUG-088), A5/A10/A12 le 2026-09-28 (avec BUG-089), A8/A9/A9bis le 2026-09-28 (avec BUG-090), puis A6→A17 en v2.33.0 → v2.39.0 (A11 étant au CI depuis A5/A8)
|
||
> **Effort estimé :** 8-13 jours au total (P0 ✅ 2-3 j · P1 4-6 j · P2 2-4 j)
|
||
> **Règle de maintenance :** la Roadmap porte les cases à cocher (suivi), cette fiche porte
|
||
> l'analyse, les risques et les critères d'acceptation. **Ne pas dupliquer le détail.**
|
||
|
||
---
|
||
|
||
## 1. Périmètre et architecture
|
||
|
||
| Couche | Fichier | Rôle |
|
||
|---|---|---|
|
||
| Lecture | `backend/xlsx_reader.py` | `render_sheets()` → un tableau HTML par feuille (openpyxl `read_only=True`, `data_only=False`) |
|
||
| Endpoint lecture | `backend/routers/files_read.py:241-265` | `GET /api/file/{vault}?path=…` → `is_xlsx: true` + `xlsx_sheets: [{name, html, rows, cols, total_*, max_*, truncated}]` |
|
||
| Endpoint fenêtre | `backend/routers/files_read.py` | `GET /api/file/{vault}/xlsx/sheet?path=&sheet=&offset=&limit=` (#153 A9) — une fenêtre de lignes, vraies coordonnées A1 |
|
||
| Schéma API | `backend/schemas.py:286-290` | `is_xlsx`, `xlsx_sheets`, `XlsxSheetWindowResponse` |
|
||
| Écriture | `backend/services/mutations.py:227-320` | `edit_xlsx_cells()` (backup, refs A1 validées, coercion `str`→`int`/`float`) |
|
||
| Endpoint écriture | `backend/routers/files_write.py:116-148` | `PUT /api/file/{vault}/xlsx/save` (1 à 500 cellules / requête) |
|
||
| Documentation API | `backend/openapi_docs.py:184-187` | exemple d'appel `xlsx/save` |
|
||
| UI | `frontend/js/viewer.js:998-1100` | `renderXlsxViewer()` (onglets, cellules sales, Entrée/Échap, collage monoligne) |
|
||
| CSS | `frontend/style.css:10927-10988` | `.xlsx-*` (variables CSS, colonne A `sticky`) |
|
||
| Indexation | `backend/indexer.py:68, 563-568, 957-960` | `.xlsx` supporté, **métadonnées seules** (`content=""`) |
|
||
| Outils IA | `backend/tools/documents.py:66-89` + `schemas.py:296-305` | `create_xlsx` (WRITE + confirmation) — **création seule** |
|
||
| Tests | `tests/test_xlsx_viewer.py` (58) + `test_xlsx_styles.py` (9) + `test_xlsx_formats.py` (12) + `test_xlsx_dashboard.py` (8) + `test_xlsx_structure.py` (11) + `test_spreadsheet_tools.py` (17) · `tests/frontend/xlsx-viewer.test.mjs` (35) · `tests/e2e/xlsx-viewer.spec.js` (9) | Backend, JSDOM et E2E (chromium-desktop) |
|
||
|
||
## 2. Ce qui est supporté aujourd'hui (livré, non concerné par #153 sauf mention)
|
||
|
||
**Lecture** — multi-feuilles avec onglets ; en-têtes A1/A2/B1 et numéros de ligne ; valeurs
|
||
`_fmt()` (dates `YYYY-MM-DD` / `YYYY-MM-DD HH:MM`) ; lignes et colonnes de fin élaguées
|
||
(`_trim`) ; feuille vide affichée ; `html.escape()` sur chaque valeur.
|
||
|
||
**Édition** — `contentEditable` par `<td>`, classe `xlsx-dirty`, bouton Save actif seulement si
|
||
modification ; `Entrée` → blur, `Échap` → restauration, collage forcé en monoligne ; un `PUT` par
|
||
feuille sale ; coercion automatique des nombres (`"250"` → int `250`) ; chaîne vide → cellule
|
||
vidée ; backup `.bak` avant écriture ; garde-fou vault read-only (403) ; `resolve_safe_path()`
|
||
(anti path-traversal) ; `check_vault_access()` + `require_auth` ; journalisation d'audit
|
||
(`log_file_save`).
|
||
|
||
**Divers** — téléchargement de l'original ; refresh de l'arborescence via le watcher après
|
||
écriture ; rafraîchissement de la visionneuse après une action IA (`create_xlsx` →
|
||
`obsigate:file-written`, BUG-076).
|
||
|
||
## 3. Limites connues (par couche)
|
||
|
||
> **Note (2026-09-28)** : les limites ci-dessous décrivent l'état du jour de l'audit
|
||
> (2026-09-27). La quasi-totalité a été levée depuis par le backlog §5 (styles, navigation
|
||
> clavier, tri/filtre/recherche, structure, formats `.xlsm`/`.xls`/`.ods`/`.csv`, indexation,
|
||
> outils IA) — se reporter aux cases cochées et à l'historique §7 ; ne pas relire cette
|
||
> section comme l'état actuel.
|
||
|
||
### 3.1 Fidélité du round-trip — risque n°1
|
||
|
||
`load_workbook()` → `wb.save()` : ce qui est **réellement** perdu a été mesuré sur
|
||
openpyxl 3.1.5 (2026-09-27), et non repris de la documentation :
|
||
|
||
| Élément | Round-trip openpyxl 3.1.5 |
|
||
|---|---|
|
||
| Graphiques, images, dessins | ✅ **préservés** (mesuré : `xl/charts/`, `xl/drawings/`, `xl/media/` intacts) |
|
||
| Tableaux croisés (pivot) + caches | ✅ **préservés** (`reader/excel.py` relit les `TableDefinition`, `workbook/_writer.py` les réécrit) |
|
||
| Styles, formats, fusions, validation de données, mise en forme conditionnelle, commentaires | ✅ préservés |
|
||
| **Valeurs calculées en cache** (`<f>…</f><v>…</v>`) | ❌ **perdues** → tout lecteur `data_only=True` (pandas, script tiers, convertisseur) renvoie `None` tant qu'Excel n'a pas recalculé |
|
||
| Slicers / chronologies, contrôles de formulaire (`ctrlProps`/`activeX`), connexions & requêtes, custom XML, signature numérique, commentaires enrichis, macros | ❌ **perdus** (parties absentes de l'archive après écriture) |
|
||
|
||
La liste fait foi dans le code : [`LOSSY_PARTS`](../backend/xlsx_reader.py) + la sonde
|
||
`<f>…</f><v>[^<]` pour les valeurs en cache (openpyxl écrivant lui-même un `<v></v>` vide).
|
||
|
||
**Ce qui reste ouvert** (non mesuré, prudence) : types de graphiques exotiques (treemap,
|
||
sunburst, funnel…), `sparklines`, `xl/queryTables` en lecture Excel. Un classeur qui en contient
|
||
peut sortir dégradé, voire échouer au chargement — d'où le refus par défaut (A1).
|
||
|
||
### 3.2 Lecture
|
||
|
||
- Aucun style, format de nombre, devise, pourcentage, largeur de colonne, ligne figée, cellule
|
||
fusionnée, commentaire, lien hypertexte, validation de données, mise en forme conditionnelle.
|
||
- Plafonds durs `MAX_ROWS = 500`, `MAX_COLS = 40` par feuille, **sans indicateur dans l'UI** : au-delà,
|
||
contenu silencieusement tronqué et **non éditable**.
|
||
- Pas de pagination ni de chargement à la demande : toutes les feuilles sont rendues d'un bloc
|
||
dans le JSON (20 feuilles × 20 000 cellules = payload énorme, UI gelée).
|
||
- Formules affichées **en texte** (`=B1*2`), jamais recalculées ; après édition, les cellules
|
||
dépendantes ne se mettent pas à jour à l'écran.
|
||
|
||
### 3.3 UI (`viewer.js`)
|
||
|
||
Navigation clavier (Tab/flèches) absente ; pas de barre de formule, pas de nom de cellule actif,
|
||
pas d'undo/redo global, pas de recherche dans la feuille, pas de tri/filtre, pas d'export CSV,
|
||
pas d'ajout/renommage/suppression de feuille, pas d'insertion/suppression de ligne ou colonne,
|
||
pas de sélection de plage, pas de copie d'une plage, pas de retour ligne dans une cellule
|
||
(`Maj+Entrée`) ; seul le retour de l'API est signalé (plafond 500 cellules) ; seule la
|
||
**colonne A** est `sticky` (le `thead` ne l'est pas → les en-têtes de colonnes disparaissent au
|
||
défilement vertical). **Couverture de test** : `tests/frontend/xlsx-viewer.test.mjs` (10) et
|
||
`tests/e2e/xlsx-viewer.spec.js` (3) depuis #153 P0 — la navigation clavier et la barre de formule
|
||
restent à faire (A7).
|
||
|
||
### 3.4 Recherche, IA et knowledge base
|
||
|
||
- **Indexation** : `content=""` → un `.xlsx` est totalement **invisible** à la recherche TF-IDF, à
|
||
la recherche sémantique, au remplacement global, aux tags et aux statistiques de contenu.
|
||
- **Outils IA** : seul `create_xlsx` existe (crée un fichier neuf, une seule feuille,
|
||
`overwrite=True` par défaut) ; `read_file` fait un `read_text()` sur l'archive ZIP → **bruit
|
||
binaire** envoyé au LLM ; pas de `update_xlsx_cells` pourtant le service existe déjà, pas
|
||
d'ajout de lignes, pas de `xlsx → markdown` pour le contexte.
|
||
|
||
## 4. Risques de sécurité / robustesse
|
||
|
||
| # | Risque | Où | Traitement | État |
|
||
|---|---|---|---|---|
|
||
| R1 | Perte silencieuse (valeurs calculées, slicers, contrôles, connexions, custom XML, signature) | `mutations.edit_xlsx_cells` | **A1** — bandeau + **409** `xlsx_lossy_content` sans `force` | 🟢 livré (BUG-085) |
|
||
| R2 | Écriture non atomique (`wb.save()` en place) → classeur corrompu si crash | `mutations.edit_xlsx_cells` | **A2** — `.tmp` + `os.replace` | 🟢 livré (BUG-086) |
|
||
| R3 | Concurrence : deux éditions (onglets, watcher + IA) → dernier écrivain gagne | `mutations.edit_xlsx_cells` | **A3** — verrou par chemin, **409** `conflict` | 🟢 livré (BUG-087) |
|
||
| R4 | **Injection de formule** : une saisie `=cmd\|…`, `=HYPERLINK(…)` est stockée comme formule par openpyxl → DDE à l'ouverture dans Excel | `mutations._write_cell` | **A4** — forçage texte (`data_type="s"`), opt-in `allow_formula` | 🟢 livré (BUG-088) |
|
||
| R5 | Troncature silencieuse au-delà de 500×40 | `xlsx_reader.MAX_ROWS/MAX_COLS` | A8 / A9 | 🟢 bandeau + dimensions exposées (BUG-090) ; le chargement paresseux par fenêtres sert les lignes au-delà du plafond |
|
||
|
||
## 5. Backlog #153 — sous-tâches
|
||
|
||
Légende : 🔴 P0 (sécurité / perte de données) · 🟡 P1 (valeur immédiate) · 🟢 P2 (confort /
|
||
couverture) · effort en jours-homme de développement + tests.
|
||
|
||
### P0 — Garde-fous d'écriture (2-3 j) — 🟢 livré le 2026-09-27
|
||
|
||
- [x] **A1 — Alerte de fidélité avant écriture (R1).** `inspect_workbook()` liste ce qu'un
|
||
round-trip perd (`LOSSY_PARTS` + sonde valeurs en cache) ; la lecture renvoie
|
||
`xlsx_lossy_features` ; la visionneuse affiche un bandeau listant les éléments ; `PUT
|
||
…/xlsx/save` répond **409** `xlsx_lossy_content` (avec `details.features`) tant que `force` n'est
|
||
pas passé, le client demande confirmation puis réémet avec `force: true` (une seule fois par
|
||
session). *Vérifié :* `TestXlsxLossyGuard` (5), `xlsx-viewer.test.mjs` (10), E2E (3).
|
||
- [x] **A2 — Écriture atomique (R2).** `wb.save(<nom>.<pid>.tmp)` puis `os.replace()` ; `.tmp`
|
||
supprimé sur échec ; backup `.bak` inchangé. Le `.tmp` est ignoré par le watcher. *Vérifié :*
|
||
`TestXlsxAtomicWrite` (2) — les octets d'origine sont intacts après un `save` en échec.
|
||
- [x] **A3 — Verrou par fichier (R3).** Verrou `threading.Lock` par chemin (registre + garde,
|
||
timeout 15 s) autour du cycle load → edit → replace ; **409** `conflict` si le délai est dépassé.
|
||
L'endpoint est passé en `def` (sync) pour que l'attente s'exécute dans le threadpool. *Vérifié :*
|
||
`TestXlsxWriteLock` (2). *Limite :* verrou en mémoire, par processus (suffisant pour un serveur
|
||
ObsiGate, y compris desktop).
|
||
- [x] **A4 — Neutralisation de l'injection de formule (R4).** `cell.data_type = "s"` après
|
||
affectation : une saisie `=`/`@` est stockée en texte. Opt-in `allow_formula: true` côté API et
|
||
bouton `f(x)` dans la visionneuse (état de session, jamais persisté). `+`/`-` restent des
|
||
nombres. Au passage : le handler `ServiceError` expose `code` + `details` et `api()` les
|
||
propage sur l'Error. *Vérifié :* `TestXlsxFormulaGuard` (4) + test du toggle côté UI.
|
||
|
||
### P1 — Recherche, IA, UX (4-6 j) — 🟢 livré le 2026-09-28 (A5 → A12)
|
||
|
||
- [x] **A5 — Indexation du contenu des feuilles.** `extract_indexable_text()` (noms de feuilles +
|
||
20 premières lignes, `MAX_INDEX_CHARS = 5 000`, 20 feuilles max) alimente le TF-IDF et la
|
||
recherche sémantique ; la lecture binaire reste inchangée pour l'affichage. Un classeur
|
||
chiffré/corrompu s'indexe par son seul nom (jamais d'exception). Au passage : **BUG-089**,
|
||
un reindex manuel ne reconstruisait pas l'index inversé. *Vérifié :* `TestXlsxSearchable` (4)
|
||
+ `TestXlsxIndexing`, **contre-preuve** (neutraliser l'extraction → 3 tests échouent).
|
||
- [x] **A6 — Outils IA sur classeur.** `update_xlsx_cells` (enveloppe du service existant),
|
||
`append_xlsx_rows`, `xlsx_to_markdown` (contexte LLM, plafonné), `list_xlsx_sheets` — risque
|
||
WRITE + confirmation pour les mutations, libellés i18n dans `backend/tools/labels.py`,
|
||
refresh viewer via `obsigate:file-written`. *Livré (v2.33.0) :* `backend/tools/spreadsheets.py`.
|
||
*Vérifié :* `tests/test_spreadsheet_tools.py` (17).
|
||
- [x] **A7 — Navigation clavier & barre de formule.** `Tab`/`Maj+Tab`/`Entrée`/flèches, cellule
|
||
active affichée (nom A1), `Maj+Entrée` pour le multiligne, copier une plage, focus visible
|
||
et compatible mobile (≥ 44 px, `tests/e2e/mobile-editor.spec.js`). *Livré (v2.34.0).*
|
||
- [x] **A8 — `thead` sticky + indicateur de troncature (R5) — livré 2026-09-28 (BUG-090).**
|
||
Ligne d'en-têtes figlée au défilement vertical (`thead th { top: 0 }` ; `top: auto` sur les
|
||
numéros de ligne, sans quoi ils s'empilent en haut à gauche) ; `render_sheets()` expose
|
||
`total_rows`/`total_cols` (dimensions déclarées), `max_rows`/`max_cols` (plafonds) et
|
||
`truncated` — le bandeau « feuille tronquée » annonce le **plafond atteint** et non la
|
||
taille élaguée (une feuille creuse rend 1×1 tout en couvrant 500 lignes) ; libellés
|
||
`xlsx.truncated_*` FR/EN. *Vérifié :* `TestXlsxTruncationNotice` (4), `xlsx-viewer.test.mjs`
|
||
(4 nouveaux), E2E sur `test_vault/sample-xlsx-large.xlsx` (520 lignes).
|
||
- [x] **A9 — Chargement paresseux par feuille (côté API).** Endpoint
|
||
`GET /api/file/{vault}/xlsx/sheet?sheet=&offset=&limit=` (`XlsxSheetWindowResponse`,
|
||
exemple dans `backend/openapi_docs.py`) : une fenêtre de 1 à 1 000 lignes (plafond
|
||
`MAX_WINDOW_ROWS`, `limit>1000` → 422), `has_more` pour paginer, valeurs calculées A12
|
||
incluses. Les numéros de ligne et `data-cell` restent les coordonnées A1 réelles de la
|
||
feuille (`_table(..., row_offset=offset)`) : une fenêtre est indistinguishable d'un rendu
|
||
complet et une édition dans la fenêtre cible la bonne cellule. Erreurs : 404 feuille
|
||
inconnue / fichier absent, 415 non-`.xlsx`. *Vérifié :* `TestXlsxSheetWindow` (11),
|
||
**contre-preuve** (neutraliser l'offset → 3 tests échouent), E2E « l'endpoint de fenêtre
|
||
sert les lignes au-delà du plafond ».
|
||
- [x] **A9bis — Chargement à la demande côté UI.** Sous une feuille tronquée, un pied de page
|
||
« N lignes affichées sur M · Charger la suite » apparaît : cliquer — ou approcher du bas
|
||
du tableau (sentinelle de défilement, marge 120 px) — fetch la fenêtre suivante
|
||
(`limit=500`) et l'insère dans la table. Les lignes ajoutées passent par le **même**
|
||
pipeline d'édition que le rendu initial (`setupCell` factorisé : contenteditable, dirty,
|
||
Échap, collage monoligne, info-bulle valeurs calculées) et sont donc sauvegardables
|
||
immédiatement. Un fetch échoué restore le libellé du pied de page (retry possible) et
|
||
toast l'erreur ; feuille complète → pied de page masqué (`class="done"`).
|
||
*Vérifié :* `xlsx-viewer.test.mjs` 19/19 (5 nouveaux), **contre-preuve** (désactiver
|
||
`wireLazyRows` → 5 tests échouent), E2E « le bouton charger la suite ajoute les lignes
|
||
cachées » sur `sample-xlsx-large.xlsx` (A520 visible et éditable après clic).
|
||
- [x] **A10 — Types et formats de saisie.** `_coerce_xlsx_value()` reconnait les booléens
|
||
(`true`/`vrai`/`oui`/`yes` et leurs négatifs) et les dates FR `JJ/MM/AAAA` (+ `HH:MM`),
|
||
jour-first comme Excel en locale française : `01/02/2026` = 1ᵉʳ février. Une saisie
|
||
ressemblant à une formule n'est jamais convertie (BUG-088 préservé) ; un code postal
|
||
numérique ou une version restent ce qu'ils sont. *Vérifié :* `TestXlsxValueCoercion` (5),
|
||
**contre-preuve** (neutraliser la coercion → 2 tests échouent).
|
||
- [x] **A11 — Tests frontend + E2E.** `tests/frontend/xlsx-viewer.test.mjs` (dirty, Échap,
|
||
collage, 1 PUT par feuille, bouton désactivé) et `tests/e2e/xlsx-viewer.spec.js`
|
||
(ouverture, onglets, édition, sauvegarde, rechargement) ; intégration au CI. *Vérifié :*
|
||
35 tests JSDOM (le job CI `lint` lance `node xlsx-viewer.test.mjs`) et 9 E2E
|
||
chromium-desktop ; la couverture a grandi avec chaque sous-tâche (A5/A8 → P2).
|
||
- [x] **A12 — Valeurs calculées.** La valeur en cache s'affiche sous la formule dans un
|
||
`<span class="xlsx-cached">`. La 2ᵉ lecture `data_only=True` n'a lieu que si l'archive
|
||
contient réellement un `<f>…</f><v>…</v>` (sonde déjà présente pour A1) : le cas courant
|
||
reste à un seul chargement, et toute erreur retombe sur l'affichage formules seul.
|
||
L'info-bulle est traduite côté client (`xlsx.cached_value_title` FR/EN) — aucun texte
|
||
d'interface n'est émis par le backend. *Vérifié :* `TestXlsxCachedValues` (3),
|
||
**contre-preuve** (neutraliser la 2ᵉ lecture → 2 tests échouent).
|
||
|
||
### P2 — Étendu (2-4 j) — 🟢 livré le 2026-09-28
|
||
|
||
- [x] **A13 — Tri / filtre / recherche dans la feuille + export CSV de la sélection.**
|
||
*Livré (v2.35.0) :* tout en manipulation d'affichage, le classeur n'est jamais réécrit
|
||
(info-bulle `xlsx.sort_applied`).
|
||
- [x] **A14 — CRUD de feuilles et de lignes/colonnes** (renommer, insérer, supprimer, dupliquer).
|
||
*Livré (v2.36.0) :* `PUT …/xlsx/structure` + menu Structure, mêmes garde-fous que
|
||
l'édition de cellules. *Vérifié :* `tests/test_xlsx_structure.py` (11).
|
||
- [x] **A15 — Styles minimaux en écriture et lecture fidèle** (gras, fond, format
|
||
devise/pourcentage/date, cellules fusionnées, volets figés) ; conserver `csv-table` comme
|
||
socle de rendu. *Livré (v2.37.0) en lecture :* couleurs, gras/italique/souligné,
|
||
alignements, fusions, ancre de volets figés ; un format de nombre personnalisé est signalé
|
||
en police mono (pas de rendu devise/pourcentage). L'application de styles **depuis la
|
||
visionneuse** (écriture) reste hors périmètre. *Vérifié :* `tests/test_xlsx_styles.py` (9).
|
||
- [x] **A16 — Formats additionnels.** `.xlsm` (`keep_vba=True`), `.xls`, `.ods`, `.csv` éditable
|
||
comme tableur — dépendances à qualifier (`xlrd`/`odfpy`) ou conversion. *Livré (v2.38.0) :*
|
||
`.xlsm` éditable macros préservées, `.xls`/`.ods` lecture seule (xlrd/odfpy), `.csv`
|
||
éditable et réécrit RFC 4180. *Vérifié :* `tests/test_xlsx_formats.py` (12).
|
||
- [x] **A17 — Vue « tableau de bord ».** Détection des plages nommées, TCD et graphiques ; vue
|
||
résumée (KPI par feuille) et proposal d'actions IA sur ces plages. *Livré (v2.39.0) :*
|
||
panneau Tableau de bord (`GET …/xlsx/dashboard`) — plages nommées avec portée, comptage
|
||
graphiques/TCD par analyse des parties OPC, stats par feuille, 8 KPI ; le volet IA se
|
||
limite à un conseil contextuel (pas d'appel IA dédié sur les plages).
|
||
*Vérifié :* `tests/test_xlsx_dashboard.py` (8).
|
||
|
||
## 6. Règles de livraison (rappel `AGENTS.md` / `DELIVERY_WORKFLOW.md`)
|
||
|
||
- Chaque sous-tâche démarre par un **ID stable** : nouvelle feature = `#153-A<n>` dans la
|
||
Roadmap ; si la sous-tâche est un **défaut** (A1, A2, A3, A4, A8), l'ouvrir aussi comme
|
||
`BUG-NNN` dans `docs/ISSUES_TODOLIST.md` au moment du démarrage.
|
||
- Backend : docstrings, `response_model` pour tout endpoint ajouté, exemple dans
|
||
`backend/openapi_docs.py`, chemin utilisateur via `resolve_safe_path()`.
|
||
- Frontend : vanilla JS sans build, `safeCreateIcons()`, **variables CSS** (jamais de couleur
|
||
hardcodée), **i18n FR + EN** pour chaque nouveau texte (`test_i18n_parity.py` vert).
|
||
- Tests : `pytest tests/test_xlsx_viewer.py`, `ruff`, `mypy`, `validate-imports`, suite frontend
|
||
ciblée, E2E si l'UI change — puis CI verte.
|
||
- Documentation : `CHANGELOG.md` `[Unreleased]`, Roadmap (case cochée), cette fiche (résultat),
|
||
guide utilisateur i18n + README si impact utilisateur.
|
||
|
||
## 7. Historique
|
||
|
||
| Date | Événement |
|
||
|---|---|
|
||
| 2.27.0 | #152 livré : affichage multi-feuilles, édition des cellules, téléchargement (`docs/archive/COMPLETED_v1-v2.md`) |
|
||
| 2026-09-27 | Audit complet → création de #153 : limites, risques R1-R5, backlog A1-A17 |
|
||
| 2026-09-27 | Périmètre de perte **remesuré** sur openpyxl 3.1.5 : graphiques / images / TCD sont préservés, seules les valeurs en cache et quelques parties exotiques sont perdues |
|
||
| 2026-09-27 | **P0 livré** (BUG-085 → BUG-088) : `xlsx_lossy_features` + 409 `xlsx_lossy_content`, écriture atomique, verrou par fichier, formules stockées en texte par défaut |
|
||
| 2026-09-28 | **A5 + A10 + A12 livrés** : le contenu des cellules est indexé (recherche), la saisie est typée (booléens, dates FR), la valeur calculée s'affiche sous la formule. **BUG-089** corrigé au passage (reindex manuel ≠ reconstruction de l'index inversé ; `backend/search.py` lisait l'index par valeur) |
|
||
| 2026-09-28 | **A8 + A9 livrés** (BUG-090) : la troncature d'une feuille est annoncée (bandeau + dimensions dans la réponse de lecture), les en-têtes restent visibles au défilement, et `GET …/xlsx/sheet` sert une fenêtre de lignes avec les vraies coordonnées A1 — les lignes au-delà du plafond redeviennent accessibles aux clients API. Défilement virtuel côté UI à suivre |
|
||
| 2026-09-28 | **A9bis livré** : « Charger la suite » + sentinelle de défilement sous une feuille tronquée ; les lignes ajoutées sont éditables et sauvegardables immédiatement (même pipeline que le rendu initial) |
|
||
| 2026-09-28 | **A6 + A7 livrés** (v2.33.0, v2.34.0) : l'assistant IA lit et modifie les classeurs (`list_xlsx_sheets`, `xlsx_to_markdown`, `update_xlsx_cells`, `append_xlsx_rows`) et la visionneuse gagne navigation clavier complète + barre de formule |
|
||
| 2026-09-28 | **A13 + A14 livrés** (v2.35.0, v2.36.0) : tri, filtre, recherche et export CSV côté affichage ; structure du classeur éditable (feuilles, lignes, colonnes) via `PUT …/xlsx/structure` |
|
||
| 2026-09-28 | **A15 + A16 + A17 livrés** (v2.37.0 → v2.39.0) : styles/fusions/volets figés rendus, formats `.xlsm`/`.xls`/`.ods`/`.csv` gérés, panneau Tableau de bord (plages nommées, graphiques/TCD, stats, KPI) — **backlog #153 terminé** |
|