diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a5ed14..63f2baa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.39.10**.
+> [Unreleased](#unreleased). La dernière version livrée est **2.40.0**.
---
@@ -14,6 +14,27 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
+## [2.40.0] — 2026-09-29
+
+### Ajouté
+
+- **#154 — Refonte UI/UX de la visionneuse XLSX (Lot 1) : ruban de commandes groupé,
+ onglets de feuilles permanents avec bouton « + », badges d'état.**
+ La vue tableur gagne une barre de commandes segmentée (Formules · Insertion · Vue ·
+ Fichier) avec un bouton **Enregistrer** primaire. La barre d'onglets est désormais
+ toujours affichée (même à une seule feuille) et un bouton « + » y ajoute une feuille
+ (même pipeline `PUT …/xlsx/structure`, re-rendu depuis le serveur). Deux pastilles
+ d'état annoncent les limites de la vue : **lecture seule** (`.xls`/`.ods` — plus de
+ « + », ni de structure, ni de tableau de bord, ni d'édition) et **formules non
+ recalculées**. Des tokens de grille dédiés (`--grid-bg`, `--grid-header-bg`,
+ `--grid-header-text`, `--grid-border`, `--grid-zebra`, déclinés dark/light) rendent les
+ en-têtes clairement distincts des cellules, avec zébrage, survol et cellule active
+ renforcée. Audit UX, architecture cible et plan par lots :
+ [docs/features/xlsx-ui-redesign.md](docs/features/xlsx-ui-redesign.md). Tests JSDOM
+ `tests/frontend/xlsx-viewer.test.mjs` (41, dont 6 nouveaux). Aucun changement backend.
+
+---
+
## [2.39.10] — 2026-09-29
### Sécurité
diff --git a/README.fr.md b/README.fr.md
index 2baed53..59004c5 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -4,7 +4,7 @@
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
-[]()
+[]()
[](https://opensource.org/licenses/MIT)
[](https://www.docker.com/)
[](https://www.python.org/)
@@ -976,8 +976,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
## 📝 Changelog
-Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.39.10).
+Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.40.0).
---
-*Projet : ObsiGate | Version : 2.39.10 | Dernière mise à jour : Septembre 2026*
+*Projet : ObsiGate | Version : 2.40.0 | Dernière mise à jour : Septembre 2026*
diff --git a/README.md b/README.md
index 0ff923b..768c4db 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
-[]()
+[]()
[](https://opensource.org/licenses/MIT)
[](https://www.docker.com/)
[](https://www.python.org/)
@@ -1151,8 +1151,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
## 📝 Changelog
-See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.39.10).
+See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.40.0).
---
-*Project: ObsiGate | Version: 2.39.10 | Last updated: September 2026*
+*Project: ObsiGate | Version: 2.40.0 | Last updated: September 2026*
diff --git a/VERSION b/VERSION
index 44cc0ba..770060b 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.39.10
+2.40.0
diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock
index 5b74e87..dc84c8b 100644
--- a/desktop/Cargo.lock
+++ b/desktop/Cargo.lock
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]]
name = "obsigate-desktop"
-version = "2.39.10"
+version = "2.40.0"
dependencies = [
"chrono",
"env_logger",
diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml
index cb242bf..3489b66 100644
--- a/desktop/Cargo.toml
+++ b/desktop/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "obsigate-desktop"
-version = "2.39.10"
+version = "2.40.0"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"]
edition = "2021"
diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json
index d99ad5a..ccfcc24 100644
--- a/desktop/tauri.conf.json
+++ b/desktop/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate",
- "version": "2.39.10",
+ "version": "2.40.0",
"identifier": "com.obsigate.desktop",
"build": {
"frontendDist": "../frontend",
diff --git a/docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md b/docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md
index 868947e..aea9b23 100644
--- a/docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md
+++ b/docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md
@@ -136,13 +136,22 @@ curl "http://localhost:2020/api/file/Recettes/pdf/info?path=menu.pdf"
### Affichage et édition
Un fichier `.xlsx` s'ouvre dans une visionneuse dédiée : un tableau par
-feuille, des onglets pour naviguer entre elles, les en-têtes A1/B1 et les
-numéros de ligne. Chaque cellule est modifiable directement (clic), `Entrée`
-valide, `Échap` annule la saisie. **Enregistrer** envoie les cellules
-modifiées à `PUT /api/file/{vault}/xlsx/save` : une sauvegarde par feuille,
-avec **backup automatique** du fichier avant écriture, et une écriture
+feuille, des onglets pour naviguer entre elles (toujours visibles, même à
+une seule feuille), les en-têtes A1/B1 et les numéros de ligne. La barre de
+commandes regroupe les actions en sections (Formules · Insertion · Vue ·
+Fichier) autour d'un bouton **Enregistrer** principal. Chaque cellule est
+modifiable directement (clic), `Entrée` valide, `Échap` annule la saisie.
+**Enregistrer** envoie les cellules modifiées à
+`PUT /api/file/{vault}/xlsx/save` : une sauvegarde par feuille, avec
+**backup automatique** du fichier avant écriture, et une écriture
**atomique** (le classeur n'est jamais laissé à moitié écrit).
+Le bouton **« + »** à côté des onglets ajoute une nouvelle feuille. Deux
+pastilles d'état rappellent les limites de la vue : **« Lecture seule »**
+pour les formats `.xls`/`.ods`, et **« Formules non recalculées »** — ObsiGate
+affiche la formule telle qu'elle est enregistrée, Excel la recalcule à
+l'ouverture et les cellules dépendantes ne se rafraîchissent pas à l'écran.
+
### Avertissement avant enregistrement
Certains classeurs contiennent des éléments qu'ObsiGate ne sait pas
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 60ea56f..ad915b6 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap
-> **Version :** 2.39.10 | **Dernière mise à jour :** 2026-09-29
+> **Version :** 2.40.0 | **Dernière mise à jour :** 2026-09-29
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
> vers les fonctionnalités livrées.
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
@@ -92,6 +92,30 @@
---
+## 🔵 En cours — Refonte UI/UX tableur (P2)
+
+### 154. Refonte UI/UX de la visionneuse & éditeur XLSX (ruban, grille, inspecteur)
+
+- **Effort :** 6-9 jours (Lot 1 ✅ · Lot 2 · Lot 3 · Lot 4) | **Impact :** 🟡
+- **Statut :** 🔵 **en cours** — **Lot 1 livré le 2026-09-29** (ruban de commandes groupé, onglets
+ de feuilles permanents avec bouton « + », badges d'état lecture seule / formules non
+ recalculées, tokens de grille et affordances). Lots suivants : dialogues thémés, inspecteur
+ droit (dashboard + IA), découpage modulaire + undo/redo.
+- **Analyse, architecture cible et plan par lots :** [features/xlsx-ui-redesign.md](./features/xlsx-ui-redesign.md)
+- **Description :** la visionneuse XLSX (#152/#153) est fonctionnelle mais peu conviviale :
+ commandes à plat sans hiérarchie, en-têtes de grille indistincts des cellules, états avancés
+ (tableau de bord, troncature, lecture seule, formules non recalculées, conflits) mal intégrés.
+ La refonte s'appuie sur les standards Excel/Google Sheets/Airtable **sans renier** la contrainte
+ `vanilla JS`, zéro framework, zéro build npm.
+- **Sous-tâches :**
+ - [x] **A1** Coquille : barre de commandes groupée, onglets feuilles permanents + « + »,
+ badges d'état, tokens de grille et affordances visuelles (Lot 1)
+ - [ ] **A2** Dialogues thémés (modales + toasts) et feedback non bloquant des conflits 409 (Lot 2)
+ - [ ] **A3** Inspecteur droit repliable : Tableau de bord + Assistant IA, lien dashboard ⇄ grille (Lot 3)
+ - [ ] **A4** Découpage `frontend/js/xlsx/*` + undo/redo + défilement via `IntersectionObserver` (Lot 4)
+
+---
+
## ⚪ Backlog — Priorité 4 (P4)
### 73. Synchronisation multi-appareils — Obsidian Sync compatible
@@ -219,7 +243,8 @@
| ⚪ P4 reporté | #73 Sync — **reporté (décision 2026-09-26)**, hors chemin critique | 6-8 jours si réactivé |
| ⚪ P0/P1 prioritaire | #87 CI/CD (BUG-035 → BUG-040 corrigés, #86 livré) | ~3-5 jours |
| ✅ Terminé | #153 Visionneuse & édition XLSX — complétude (A1-A17 **toutes livrées**, v2.27.0 → v2.39.0) | 0 jour restant |
-| **Total chemin critique** | **#77 fin + #87** | **~4-6 jours** |
+| 🔵 En cours | #154 Refonte UI/UX tableur (A1 ✅ Lot 1 · A2-A4 restants) | ~5-7 jours |
+| **Total chemin critique** | **#77 fin + #87 + #154** | **~9-13 jours** |
---
@@ -227,6 +252,7 @@
- **Décisions 2026-09-26 :** axe prioritaire = dette & sécurité (#85/#87) ; #73 Sync reporté (P4, hors chemin critique) ; desktop livré non signé + doc SmartScreen.
- **Ajout 2026-09-27 :** #153 ouvert à la suite de l'audit de la visionneuse XLSX (limitations, risques de perte de données, périmètre IA/recherche) — détail et critères dans [features/xlsx-viewer.md](./features/xlsx-viewer.md).
+- **Ajout 2026-09-29 :** #154 ouvert — refonte UI/UX de la visionneuse/éditeur XLSX (audit UX, architecture cible, plan par lots) dans [features/xlsx-ui-redesign.md](./features/xlsx-ui-redesign.md) ; Lot 1 livré (ruban groupé, onglets permanents + « + », badges d'état, tokens de grille).
- **Clôture #85 (v2.27.13) :** monolithe découpé (T1→T9), stores verrouillés + rate-limit SQLite (T10), fiche `docs/features/archi-refonte-85.md`.
- Les items P3/P4 ne sont pas ordonnés par priorité interne — à raffiner selon les retours utilisateurs.
- L'effort inclut le développement + tests unitaires + intégration CI, mais pas la documentation utilisateur.
diff --git a/docs/features/xlsx-ui-redesign.md b/docs/features/xlsx-ui-redesign.md
new file mode 100644
index 0000000..f2623c7
--- /dev/null
+++ b/docs/features/xlsx-ui-redesign.md
@@ -0,0 +1,118 @@
+# #154 — Refonte UI/UX de la visionneuse & éditeur XLSX (ruban, grille, inspecteur)
+
+> **Item de roadmap :** [#154 — Refonte UI/UX tableur](../ROADMAP.md)
+> **Origine :** #152 / #153 (visionneuse XLSX fonctionnelle mais peu conviviale)
+> **Statut :** 🔵 **en cours** — Lot 1 livré le 2026-09-29
+> **Effort estimé :** 6-9 jours (Lot 1 ✅ · Lot 2 · Lot 3 · Lot 4)
+> **Règle de maintenance :** la Roadmap porte les cases à cocher (suivi), cette fiche porte
+> l'analyse, l'architecture cible et le plan par lots. **Ne pas dupliquer le détail.**
+
+---
+
+## 1. Objectif
+
+Rendre la vue tableur d'ObsiGate **intuitive, moderne et hautement utilisable** en s'inspirant
+des standards du marché (Excel, Google Sheets, Airtable), **sans renier les contraintes du
+dépôt** : thème sombre, `vanilla JS`, **zéro framework, zéro build npm**
+([`AGENTS.md`](../../AGENTS.md)). La refonte est **organique** : on améliore la coquille
+existante (`frontend/js/viewer.js::renderXlsxViewer`, `frontend/style.css`), on ne réécrit pas
+la grille ni le backend.
+
+## 2. Audit UX — les 3 problèmes majeurs
+
+| # | Problème | Constat | Résolution |
+|---|---|---|---|
+| **P1** | **Aucune hiérarchie ni regroupement des commandes** | Rangée plate de boutons de poids identique (`viewer.js` toolbar historique) ; « Tableau de bord » *prependé* au runtime ; barre de formule réduite à un `input`. | **Barre de commandes groupée** (Formules · Insertion · Vue · Fichier), bouton **Enregistrer primaire**, état *dirty*. |
+| **P2** | **Grille sans affordances : en-têtes = cellules** | Contraste faible entre `th` et `td`, pas de zébrage, pas de survol lisible, cellule active peu marquée. | **Tokens de grille** + en-têtes plus clairs/interactifs, zébrage, survol, cellule active en bordure accent. |
+| **P3** | **États avancés traités comme du contenu** | Dashboard *inline* qui pousse la grille, troncature/lecture seule/formules non recalculées sans emplacement dédié, `confirm()`/`prompt()` natifs. | **Couche UI dédiée** : bandeaux d'état + **inspecteur droit** (Lot 3) + dialogues thémés (Lot 2). |
+
+## 3. Architecture cible de l'écran
+
+```
+┌──────────────────────────────────────────────────────────────────────────┐
+│ BARRE APP (globale, existante) │
+├─────────────┬────────────────────────────────────────────────────────────┤
+│ │ A. RUBAN — groupes Formules · Insertion · Vue · Fichier │
+│ EXPLORATEUR│ B. BARRE DE FORMULE — [ A1 ] fx [ … ] │
+│ DE FICHIERS│ C. BANDEAUX D'ÉTAT — lecture seule · formules non recalculées│
+│ (sidebar) ├──────────────────────────────────────────────┬─────────────┤
+│ │ D. GRILLE (en-têtes clairs, zébrage, survol) │ E. INSPECTEUR│
+│ │ │ (dashboard + │
+│ │ │ IA, repliable)│
+│ ├───────────────────────────────────────────────┤ │
+│ │ F. ONGLETS FEUILLES + « + » · 500/522 │ │
+└─────────────┴───────────────────────────────────────────────┴─────────────┘
+```
+
+- **A. Ruban** : groupes d'actions avec séparateurs ; actions de style désactivées (styles lus,
+ pas écrits). Bouton **Enregistrer** en accent, désactivé si rien de *dirty*.
+- **B. Barre de formule** : zone nom + champ + badge de session `f(x)`.
+- **C. Bandeaux d'état** : empilables, non bloquants ; portent lecture seule et
+ « formules non recalculées ».
+- **D. Grille** : rendue côté serveur (`backend/xlsx_reader.py`), habillée et câblée par le front.
+- **E. Inspecteur** : **à venir (Lot 3)** — Tableau de bord + Assistant IA dans un panneau droit
+ repliable (réutilise `PaneManager` pour le détachement), au lieu du dashboard *inline* actuel.
+- **F. Onglets feuilles** : permanents (même à une seule feuille) + bouton « + ».
+
+## 4. Plan par lots (incréments livrables)
+
+### Lot 1 — Coquille : ruban groupé, onglets permanents, badges d'état ✅ *(2026-09-29)*
+
+- **A1.1** Barre de commandes groupée (`.xlsx-cmdbar`, `.xlsx-cmd-group`, `.xlsx-cmd-sep`,
+ `.xlsx-save-primary`), IDs existants conservés (compatibilité tests JSDOM/E2E).
+- **A1.2** Onglets de feuilles **toujours rendus** (non-CSV) + bouton **`+`** `.xlsx-tab-add`
+ → `sheet_add` (même pipeline `putStructure`).
+- **A1.3** Badges d'état : `.xlsx-status-pill` **lecture seule** (`.xls`/`.ods`) et
+ **formules non recalculées** (non-CSV).
+- **A1.4** Tokens de grille (`--grid-bg`, `--grid-header-bg`, `--grid-header-text`,
+ `--grid-border`, `--grid-zebra`) déclinés dark/light + affordances (en-têtes clairs,
+ zébrage, survol, cellule active solide, cellule *dirty* prioritaire au survol).
+
+### Lot 2 — Dialogues thémés & feedback (à venir)
+
+- Remplacer `confirm()`/`prompt()` par des **modales thémées** (`.obsigate-modal-*`) et des
+ **toasts** : structure (feuille/ligne/colonne), perte (409 `xlsx_lossy_content`), conflit
+ (409 `conflict`) en bandeau non bloquant.
+- Indicateur *dirty* sur l'onglet de feuille et la barre.
+
+### Lot 3 — Inspecteur droit (à venir)
+
+- Panneau repliable hébergeant **Tableau de bord** et **Assistant IA** en onglets ; la grille
+ reste visible. Lien **dashboard → grille** (clic sur KPI/plage = scroll + sélection).
+- Détachement en split via `PaneManager.splitRight()`.
+
+### Lot 4 — Découpage & finitions (à venir)
+
+- Extraction de `renderXlsxViewer` en modules (`frontend/js/xlsx/*.js`) : `toolbar`, `formula-bar`,
+ `grid`, `sheet-tabs`, `inspector`, `cell-editor`, `api`.
+- **Undo/redo** local (pile de commandes), défilement paresseux via `IntersectionObserver`,
+ accessibilité ARIA (`role="grid"`, *roving tabindex*).
+
+## 5. Recommandations techniques (contrainte « zéro build »)
+
+| Option Data Grid | Build | Licence | Verdict |
+|---|---|---|---|
+| AG Grid Community | npm + bundler | MIT | ❌ viole « zéro build », réécrit le DOM, casse les tests |
+| Handsontable | npm + bundler | **commerciale** | ❌ licence non libre |
+| TanStack Table | headless (importable esm.sh) | MIT | ⚠ possible sans build, mais *headless* → gain limité |
+| **Grille maison sur `
`** | aucun | — | ✅ **recommandé** (conserve DOM, CSP, i18n, tests) |
+
+- **Performance** : ne pas ré-écrire tout le DOM ; réutiliser le pipeline `appendWindow` ;
+ `content-visibility:auto; contain:strict` sur les lignes ; garder la pagination serveur
+ (500 × 40 = 20 000 cellules/feuille) plutôt qu'une virtualisation client complexe.
+- **CSP** : `main.py` autorise déjà `esm.sh` — une lib *headless* reste possible en Lot 4 si
+ un vrai besoin de modèle de colonnes apparaît.
+
+## 6. Critères d'acceptation (par lot)
+
+- **Lot 1** : une feuille unique affiche son onglet + « + » ; « + » ajoute une feuille via
+ `PUT …/xlsx/structure` et re-rend ; `.xls`/`.ods` montrent le badge « lecture seule » (pas de
+ « + », pas de structure, pas de dashboard) ; un `.xlsx` montre le badge « formules non
+ recalculées », un `.csv` non ; les tests JSDOM/E2E existants restent verts + nouveaux tests.
+- Lots suivants : définis à leur ouverture.
+
+## 7. Historique
+
+| Date | Événement |
+|---|---|
+| 2026-09-29 | Audit UX (3 problèmes) + architecture cible + plan par lots ; **Lot 1** livré (ruban groupé, onglets permanents + « + », badges d'état, tokens de grille) |
diff --git a/frontend/js/viewer.js b/frontend/js/viewer.js
index 98b61db..04ce807 100644
--- a/frontend/js/viewer.js
+++ b/frontend/js/viewer.js
@@ -1047,11 +1047,18 @@ export function renderXlsxViewer(area, data) {
// per workbook, not a global preference).
let lossyConfirmed = false;
let allowFormula = false;
- const tabs = sheets.length > 1
- ? `
${sheets.map((s, i) =>
- ``
- ).join("")}
`
- : "";
+ // #154-A1 — the command bar is grouped (Formules · Insertion · Vue · Fichier)
+ // and the sheet tabs are ALWAYS rendered (a single sheet used to show no tab
+ // at all, hiding the fact that a workbook can hold several). An editable
+ // workbook also gets an explicit “+” button next to the tabs.
+ const editable = !isCsv && !readOnly;
+ const tabs = isCsv
+ ? ""
+ : `
`;
// #153 A15 — inline styles (bold/italic/colors from the file itself), merged
// ranges and the freeze anchor are applied AFTER the panels are in the DOM
// (the HTML string alone cannot carry them: col/row spans need the table).
@@ -1123,24 +1130,55 @@ export function renderXlsxViewer(area, data) {
`
: "";
+ // #154-A1 — status pills make the viewer's limits visible up front: a
+ // read-only format (.xls/.ods) and the fact that formulas are never
+ // recalculated on screen (the workbook is only written back, Excel recomputes).
+ const statusPills = [
+ readOnly
+ ? `${escapeHtml(t("xlsx.readonly_badge"))}`
+ : "",
+ !isCsv
+ ? `${escapeHtml(t("xlsx.formulas_note"))}`
+ : "",
+ ].filter(Boolean).join("");
+ const statusBar = statusPills ? `
${statusPills}
` : "";
+
+ // Command groups, separated by thin rules. The dashboard button is appended
+ // later into the dedicated (empty) “view” group.
+ const actionGroups = [];
+ if (!readOnly) {
+ actionGroups.push(`
+
+ `);
+ }
+ if (editable) {
+ actionGroups.push(`
+
+ `);
+ actionGroups.push(``);
+ }
+ actionGroups.push(`
+
+
+
+ `);
+ const actionsHtml = actionGroups.join('');
+
area.innerHTML = `