feat: inspecteur droit pour le tableau de bord XLSX #154
CI / lint (push) Successful in 2m30s
CI / security (push) Failing after 1m46s
CI / build (push) Canceled after 0s
CI / e2e (push) Canceled after 0s
CI / test (push) Canceled after 1h32m51s

This commit is contained in:
2026-09-29 14:56:08 -04:00
parent 8da65611cb
commit e94af0369b
16 changed files with 183 additions and 31 deletions
+15 -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.41.0**.
> [Unreleased](#unreleased). La dernière version livrée est **2.42.0**.
---
@@ -14,6 +14,20 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [2.42.0] — 2026-09-29
### Ajouté
- **#154 — Refonte UI/UX de la visionneuse XLSX (Lot 3) : inspecteur droit.**
Le **Tableau de bord** quitte le flux de la grille pour un **panneau latéral droit repliable**
(`.xlsx-inspector`) : la grille reste visible à côté. L'en-tête de l'inspecteur porte le titre,
une entrée **Assistant IA** (ouvre le panneau latéral global existant) et un bouton de fermeture.
Sous 900 px, l'inspecteur se place sous la grille. Tests JSDOM
`tests/frontend/xlsx-viewer.test.mjs` (44) et E2E `tests/e2e/xlsx-viewer.spec.js` (9) verts.
Aucun changement backend.
---
## [2.41.0] — 2026-09-29
### Ajouté
+3 -3
View File
@@ -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.
[![Version](https://img.shields.io/badge/Version-2.41.0-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.42.0-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](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.41.0).
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.42.0).
---
*Projet : ObsiGate | Version : 2.41.0 | Dernière mise à jour : Septembre 2026*
*Projet : ObsiGate | Version : 2.42.0 | Dernière mise à jour : Septembre 2026*
+3 -3
View File
@@ -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.
[![Version](https://img.shields.io/badge/Version-2.41.0-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.42.0-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](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.41.0).
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.42.0).
---
*Project: ObsiGate | Version: 2.41.0 | Last updated: September 2026*
*Project: ObsiGate | Version: 2.42.0 | Last updated: September 2026*
+1 -1
View File
@@ -1 +1 @@
2.41.0
2.42.0
+1 -1
View File
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]]
name = "obsigate-desktop"
version = "2.41.0"
version = "2.42.0"
dependencies = [
"chrono",
"env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "obsigate-desktop"
version = "2.41.0"
version = "2.42.0"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"]
edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate",
"version": "2.41.0",
"version": "2.42.0",
"identifier": "com.obsigate.desktop",
"build": {
"frontendDist": "../frontend",
+5 -4
View File
@@ -236,13 +236,14 @@ en **lecture seule** ; un `.csv` s'ouvre dans la même grille et se réécrit
conformément à la RFC 4180 (les guillemets et séparateurs sont
échappés).
**Tableau de bord** — le bouton **Tableau de bord** liste les plages
**Tableau de bord** — le bouton **Tableau de bord** ouvre un **inspecteur
latéral droit** (la grille reste visible à côté) qui liste les plages
nommées du classeur (nom, référence, portée), signale les feuilles
contenant des graphiques ou des tableaux croisés, et donne pour chaque
feuille un résumé (cellules, lignes, colonnes, formules, valeurs
numériques) avec quelques chiffres clés. C'est une aide à la lecture :
les plages nommées peuvent ensuite être exploitées avec l'assistant IA
(outils `list_xlsx_sheets`, `xlsx_to_markdown`, `update_xlsx_cells`,
numériques) avec quelques chiffres clés. L'en-tête de l'inspecteur offre
aussi un accès direct à l'**assistant IA**, qui peut ensuite exploiter ces
plages (outils `list_xlsx_sheets`, `xlsx_to_markdown`, `update_xlsx_cells`,
`append_xlsx_rows`).
### Limites
+5 -5
View File
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap
> **Version :** 2.41.0 | **Dernière mise à jour :** 2026-09-29
> **Version :** 2.42.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)**
@@ -97,11 +97,11 @@
### 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** — **Lots 1 & 2 livrés le 2026-09-29** (ruban de commandes groupé,
- **Statut :** 🔵 **en cours** — **Lots 1, 2 & 3 livrés 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 ; dialogues thémés `showConfirm`/`showPrompt`,
bandeau de conflit 409 non bloquant, indicateur *dirty*). Lots suivants : inspecteur droit
(dashboard + IA), découpage modulaire + undo/redo.
bandeau de conflit 409 non bloquant, indicateur *dirty* ; **inspecteur droit repliable** pour le
tableau de bord avec entrée Assistant IA). Lot suivant : 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
@@ -112,7 +112,7 @@
- [x] **A1** Coquille : barre de commandes groupée, onglets feuilles permanents + « + »,
badges d'état, tokens de grille et affordances visuelles (Lot 1)
- [x] **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)
- [x] **A3** Inspecteur droit repliable : Tableau de bord + entrée Assistant IA (Lot 3)
- [ ] **A4** Découpage `frontend/js/xlsx/*` + undo/redo + défilement via `IntersectionObserver` (Lot 4)
---
+11 -6
View File
@@ -2,8 +2,8 @@
> **Item de roadmap :** [#154 — Refonte UI/UX tableur](../ROADMAP.md)
> **Origine :** #152 / #153 (visionneuse XLSX fonctionnelle mais peu conviviale)
> **Statut :** 🔵 **en cours** — Lots 1 & 2 livrés le 2026-09-29
> **Effort estimé :** 6-9 jours (Lot 1 ✅ · Lot 2 ✅ · Lot 3 · Lot 4)
> **Statut :** 🔵 **en cours** — Lots 1, 2 & 3 livrés 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.**
@@ -79,11 +79,13 @@ la grille ni le backend.
avec bouton **Réessayer** — les modifications sont conservées.
- **A2.4** Indicateur *dirty* sur le bouton **Enregistrer** et sur l'onglet de la feuille concernée.
### Lot 3 — Inspecteur droit (à venir)
### Lot 3 — Inspecteur droit ✅ *(2026-09-29)*
- 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()`.
- **A3.1** Le **Tableau de bord** quitte le flux de la grille pour un **panneau droit repliable**
(`.xlsx-inspector`) : la grille reste visible à côté (`.xlsx-body` = `.xlsx-main` + inspecteur).
- **A3.2** En-tête d'inspecteur : titre, bouton **Assistant IA** (ouvre le panneau latéral global
existant) et bouton de fermeture.
- **A3.3** Responsive : sous 900 px, l'inspecteur passe sous la grille.
### Lot 4 — Découpage & finitions (à venir)
@@ -91,6 +93,8 @@ la grille ni le backend.
`grid`, `sheet-tabs`, `inspector`, `cell-editor`, `api`.
- **Undo/redo** local (pile de commandes), défilement paresseux via `IntersectionObserver`,
accessibilité ARIA (`role="grid"`, *roving tabindex*).
- Lien **dashboard → grille** (clic sur KPI/plage = scroll + sélection), inspecteur
**redimensionnable**, détachement en split via `PaneManager.splitRight()`.
## 5. Recommandations techniques (contrainte « zéro build »)
@@ -121,3 +125,4 @@ la grille ni le backend.
|---|---|
| 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) |
| 2026-09-29 | **Lot 2** livré : dialogues thémés (`showConfirm`/`showPrompt`) pour la structure et la confirmation de perte, bandeau de conflit 409 non bloquant avec réessai, indicateur *dirty* (bouton + onglet) |
| 2026-09-29 | **Lot 3** livré : le Tableau de bord passe dans un **inspecteur droit repliable** (grille toujours visible), en-tête d'inspecteur avec entrée **Assistant IA** et fermeture, responsive < 900 px |
+37 -4
View File
@@ -15,6 +15,7 @@ import { cacheViewedFile, getCachedFile } from './offline.js';
import { t } from './i18n.js';
import { onFileRender } from './plugins.js';
import { NowPlaying } from './now-playing.js';
import { openAssistant } from './ai-fab.js';
// ── Multi-format export ────────────────────────────────────────────────────
// Downloads a file export (HTML / MD bundle / ePub) via the authenticated
@@ -1198,7 +1199,23 @@ export function renderXlsxViewer(area, data) {
</button>
</span>
</div>
<div class="xlsx-panels">${panels}</div>
<div class="xlsx-body">
<div class="xlsx-main">
<div class="xlsx-panels">${panels}</div>
</div>
<aside class="xlsx-inspector" id="xlsx-inspector" aria-hidden="true">
<div class="xlsx-inspector-head">
<span class="xlsx-inspector-title">${escapeHtml(t("xlsx.inspector_title"))}</span>
<span class="xlsx-inspector-actions">
<button class="btn-action xlsx-inspector-ai" id="xlsx-inspector-ai" type="button" title="${escapeHtml(t("xlsx.inspector_ai"))}">
<i data-lucide="bot" style="width:14px;height:14px"></i> ${escapeHtml(t("xlsx.inspector_ai_short"))}
</button>
<button class="btn-action xlsx-inspector-close" id="xlsx-inspector-close" type="button" aria-label="${escapeHtml(t("common.cancel"))}">✕</button>
</span>
</div>
<div class="xlsx-inspector-body" id="xlsx-inspector-body"></div>
</aside>
</div>
</div>`;
// ── #153 A17 — dashboard panel: named ranges, objects, per-sheet KPI ──
@@ -1212,14 +1229,26 @@ export function renderXlsxViewer(area, data) {
title: t("xlsx.dashboard_btn"),
}, [icon("layout-dashboard", 14), document.createTextNode(t("xlsx.dashboard_btn"))]);
(area.querySelector("#xlsx-view-group") || area.querySelector(".xlsx-toolbar-actions")).appendChild(dashBtn);
const inspector = area.querySelector("#xlsx-inspector");
const inspectorBody = area.querySelector("#xlsx-inspector-body");
const closeInspector = () => {
const panel = inspectorBody.querySelector(".xlsx-dashboard");
if (panel) panel.remove();
inspector.classList.remove("open");
inspector.setAttribute("aria-hidden", "true");
dashBtn.classList.remove("active");
};
dashBtn.addEventListener("click", async () => {
const old = area.querySelector(".xlsx-dashboard");
if (old) { old.remove(); dashBtn.classList.remove("active"); return; }
// #154-A3 — the dashboard lives in a right-hand inspector so the grid
// stays visible beside it (it used to be prepended into the grid flow).
if (inspectorBody.querySelector(".xlsx-dashboard")) { closeInspector(); return; }
dashBtn.classList.add("active");
inspector.classList.add("open");
inspector.setAttribute("aria-hidden", "false");
const panel = document.createElement("div");
panel.className = "xlsx-dashboard";
panel.innerHTML = `<div class="xlsx-dashboard-loading">…</div>`;
area.querySelector(".xlsx-panels").prepend(panel);
inspectorBody.prepend(panel);
try {
const dash = await api(
`/api/file/${encodeURIComponent(data.vault)}/xlsx/dashboard?path=${encodeURIComponent(data.path)}`,
@@ -1261,6 +1290,10 @@ export function renderXlsxViewer(area, data) {
panel.innerHTML = `<p class="xlsx-kpi-empty">${escapeHtml(t("xlsx.load_error"))}: ${escapeHtml(String(err.message || err))}</p>`;
}
});
area.querySelector("#xlsx-inspector-close").addEventListener("click", closeInspector);
// #154-A3 — the assistant is the existing global side panel; the inspector
// just gives it a contextual entry point from the spreadsheet.
area.querySelector("#xlsx-inspector-ai").addEventListener("click", () => { openAssistant(); });
}
const saveBtn = area.querySelector("#xlsx-save-btn");
+3
View File
@@ -1877,6 +1877,9 @@
"xlsx.structure_error": "Could not change the structure",
"xlsx.last_sheet": "The last sheet cannot be deleted",
"xlsx.dashboard_btn": "Dashboard",
"xlsx.inspector_title": "Inspector",
"xlsx.inspector_ai": "AI assistant",
"xlsx.inspector_ai_short": "AI",
"xlsx.dashboard_title": "Workbook dashboard",
"xlsx.dashboard_stats": "{{cells}} cells · {{rows}} rows · {{cols}} columns · {{formulas}} formulas · {{numeric}} numeric values",
"xlsx.dashboard_charts": "{{n}} chart(s)",
+3
View File
@@ -1877,6 +1877,9 @@
"xlsx.structure_error": "Modification de la structure impossible",
"xlsx.last_sheet": "Impossible de supprimer la dernière feuille",
"xlsx.dashboard_btn": "Tableau de bord",
"xlsx.inspector_title": "Inspecteur",
"xlsx.inspector_ai": "Assistant IA",
"xlsx.inspector_ai_short": "IA",
"xlsx.dashboard_title": "Tableau de bord du classeur",
"xlsx.dashboard_stats": "{{cells}} cellules · {{rows}} lignes · {{cols}} colonnes · {{formulas}} formules · {{numeric}} valeurs numériques",
"xlsx.dashboard_charts": "{{n}} graphique(s)",
+65
View File
@@ -11289,6 +11289,71 @@ body.desktop-mode .editor-container {
color: var(--text-primary);
}
/* #154-A3 — right-hand inspector (dashboard, assistant) beside the grid. */
.xlsx-body {
display: flex;
align-items: stretch;
gap: 10px;
}
.xlsx-main {
flex: 1 1 auto;
min-width: 0;
}
.xlsx-inspector {
flex: 0 0 340px;
width: 340px;
max-width: 45%;
display: none;
flex-direction: column;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
max-height: 70vh;
}
.xlsx-inspector.open {
display: flex;
}
.xlsx-inspector-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 10px;
border-bottom: 1px solid var(--border);
}
.xlsx-inspector-title {
font-weight: 600;
font-size: 0.85rem;
color: var(--text-primary);
}
.xlsx-inspector-actions {
display: inline-flex;
align-items: center;
gap: 6px;
}
.xlsx-inspector-body {
flex: 1 1 auto;
overflow: auto;
padding: 10px;
}
.xlsx-inspector .xlsx-dashboard {
margin: 0;
padding: 0;
border: none;
border-radius: 0;
background: transparent;
}
@media (max-width: 900px) {
.xlsx-body {
flex-direction: column;
}
.xlsx-inspector {
flex-basis: auto;
width: auto;
max-width: none;
}
}
/* #153 A17 — workbook dashboard panel: named ranges table + KPI cards.
Colors come from the existing CSS variables (no hardcoded values). */
.xlsx-dashboard {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "obsigate",
"version": "2.41.0",
"version": "2.42.0",
"description": "**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.",
"main": "patch.js",
"directories": {
+28
View File
@@ -719,6 +719,34 @@ await test("the dashboard button fetches the metadata and renders named ranges +
assert.equal(area.querySelector(".xlsx-dashboard"), null, "panel toggles closed");
});
await test("the dashboard opens in the right-hand inspector, not inline in the grid", async () => {
const area = mount();
apiQueue.push({
ok: true, status: 200,
body: { vault: "V", path: "data.xlsx", named_ranges: [], objects: { charts: 0, pivots: 0 }, sheets: [{ name: "Feuille1", cells: 1, rows: 1, cols: 1, formulas: 0, numeric: 0, kpi: [] }] },
});
area.querySelector("#xlsx-dashboard-btn").click();
await new Promise((r) => setTimeout(r, 5));
const inspector = area.querySelector("#xlsx-inspector");
assert.equal(inspector.classList.contains("open"), true, "the inspector opens");
assert.equal(inspector.getAttribute("aria-hidden"), "false");
assert.ok(inspector.querySelector("#xlsx-inspector-body .xlsx-dashboard"), "the dashboard lives in the inspector");
// The grid is still present beside it (not replaced).
assert.ok(area.querySelector(".xlsx-main .xlsx-table"), "the grid stays visible");
// Closing removes the panel and closes the inspector.
area.querySelector("#xlsx-inspector-close").click();
assert.equal(area.querySelector(".xlsx-dashboard"), null);
assert.equal(inspector.classList.contains("open"), false);
assert.equal(inspector.getAttribute("aria-hidden"), "true");
});
await test("the inspector exposes an AI assistant entry point", () => {
const area = mount();
assert.ok(area.querySelector("#xlsx-inspector-ai"), "AI button in the inspector head");
const title = area.querySelector("#xlsx-inspector .xlsx-inspector-title");
assert.equal(title.textContent, FR["xlsx.inspector_title"]);
});
await test("a csv mounts without the dashboard button", () => {
const area = document.getElementById("content-area");
area.innerHTML = "";