diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 27ed768..ba0a278 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -131,13 +131,24 @@ jobs: python-version: "3.11" - name: Install dependencies - # semgrep épinglé (BUG-091) : la série 1.175+ embarque des libs natives - # compilées pour x86-64-v2, refusées par le CPU du runner Gitea - # (« CPU ISA level is lower than required » → exit 127). 1.174.0 est la - # dernière version qui s'exécute sur ce runner. + # BUG-091 : semgrep vit dans un venv isolé, pour deux raisons. + # 1) CPU du runner : la dernière version publiée en wheel + # manylinux2014 (baseline x86-64 v1) est 1.157.0 ; la série 1.158+ + # ne publie plus que manylinux_2_34/2_35, dont les libs natives + # exigent x86-64-v2 → « CPU ISA level is lower than required » + # (exit 127) au moindre appel. + # 2) Dépendances incompatibles avec l'env principal : semgrep 1.157.0 + # fixe tomli~=2.0.1 (pip-audit 2.10+ exige >=2.2.1) et + # pyjwt~=2.12.0 (PYSEC-2026-178) — le plancher pyjwt>=2.13.0 de + # requirements.txt resterait insatisfaisable s'il était co-installé. run: | - pip install bandit pip-audit semgrep==1.174.0 + # setuptools récent : l'image de base peut embarquer une version + # couverte par un advisory fraîchement publié (PYSEC-2026-3447). + pip install -U pip setuptools + pip install bandit pip-audit pip install -r backend/requirements.txt + python -m venv /tmp/semgrep-venv + /tmp/semgrep-venv/bin/pip install --quiet semgrep==1.157.0 - name: Bandit (SAST, bloquant — #87) # B105 est exclu (aligné avec [tool.bandit] de pyproject.toml : @@ -148,7 +159,8 @@ jobs: - name: Semgrep (SAST local, bloquant — #87) # Règles 100 % locales (semgrep-rules/, 8 règles) : aucun # téléchargement de registre (runner au réseau fragile). - run: semgrep --config semgrep-rules/ backend/ + # Venv isolé (BUG-091) : cf. l'étape « Install dependencies ». + run: /tmp/semgrep-venv/bin/semgrep --config semgrep-rules/ backend/ - name: Pip-audit (bloquant — #87) # Bloquant depuis T6 (#87) : dépendances qualifiées (mistune 3.3.3, @@ -157,7 +169,8 @@ jobs: # Seule exception documentée : PYSEC-2026-1325 (ecdsa, Minerva) — # aucun correctif upstream ET ObsiGate ne signe/vérifie qu'en HS256 # (backend/auth/jwt_handler.py), les chemins ECDSA P-256 ne - # s'exécutent jamais. + # s'exécutent jamais. PYSEC-2026-178 (pyjwt) est, lui, corrigé par le + # plancher pyjwt>=2.13.0 de backend/requirements.txt (BUG-091). # NOTE runner Gitea Act (BUG-083) : aucun `#` dans le `run:`. run: pip-audit --ignore-vuln PYSEC-2026-1325 diff --git a/CHANGELOG.md b/CHANGELOG.md index 355d5bb..4401958 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.2**. +> [Unreleased](#unreleased). La dernière version livrée est **2.39.3**. --- @@ -14,17 +14,28 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.39.3] — 2026-09-28 + +--- + ## [2.39.2] — 2026-09-28 ### Correction -- **BUG-091 — le job CI `security` refusait de démarrer semgrep.** Le binaire - `semgrep` non épinglé s'est mis à jour vers la série 1.175+, dont les - bibliothèques natives sont compilées pour x86-64-v2 : le CPU du runner - Gitea les refuse (« CPU ISA level is lower than required », exit 127) et - le job échoue sans rapport avec le code analysé. `semgrep==1.174.0` est - désormais épinglé dans le job (dernière version exécutable sur ce - runner) ; bandit et pip-audit restent flottants. +- **BUG-091 — le job CI `security` refusait de démarrer semgrep, puis + échouait à l'audit des dépendances.** Depuis 1.158.0, semgrep ne publie + plus que des wheels `manylinux_2_34`/`2_35` dont les bibliothèques + natives exigent un CPU x86-64-v2 : le runner Gitea les refuse (« CPU ISA + level is lower than required », exit 127). semgrep est désormais isolé + dans un venv jetable du job, épinglé à **1.157.0** (dernière publication + `manylinux2014`, baseline v1) — un venv, aussi, parce que ses + dépendances contredisent l'environnement principal (`tomli~=2.0.1` vs + pip-audit ≥ 2.10, `pyjwt~=2.12.0` vulnérable). Dans la foulée : + plancher `pyjwt[crypto]>=2.13.0` dans `backend/requirements.txt` + (PYSEC-2026-178, pyjwt est transitif de mcp) et mise à jour de + pip/setuptools dans le job (PYSEC-2026-3721 / PYSEC-2026-3447, apparus + récemment dans la base d'advisories). Validé en environnement frais : + résolution sans conflit, pip-audit et semgrep verts. --- diff --git a/README.fr.md b/README.fr.md index b6bc9ac..3b84b93 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. -[![Version](https://img.shields.io/badge/Version-2.39.2-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.39.3-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.39.2). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.39.3). --- -*Projet : ObsiGate | Version : 2.39.2 | Dernière mise à jour : Septembre 2026* +*Projet : ObsiGate | Version : 2.39.3 | Dernière mise à jour : Septembre 2026* diff --git a/README.md b/README.md index 1e6e5f7..e5f178a 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. -[![Version](https://img.shields.io/badge/Version-2.39.2-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.39.3-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.39.2). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.39.3). --- -*Project: ObsiGate | Version: 2.39.2 | Last updated: September 2026* +*Project: ObsiGate | Version: 2.39.3 | Last updated: September 2026* diff --git a/VERSION b/VERSION index 8f5a39a..a69af57 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.39.2 +2.39.3 diff --git a/backend/requirements.txt b/backend/requirements.txt index c827c84..2ca0c8e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -20,6 +20,9 @@ webauthn==2.6.0 psutil>=5.9 pywebpush>=2.3.0 mcp==1.28.1 +# Plancher de sécurité (BUG-091) : pyjwt est une dépendance transitive (mcp) ; +# 2.12.x est vulnérable (PYSEC-2026-178, fix 2.13.0) et pip-audit bloque sinon. +pyjwt[crypto]>=2.13.0 sse-starlette==2.1.3 openpyxl>=3.1 xlrd==2.0.2 diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index 610adc3..92a68fb 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.39.2" +version = "2.39.3" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index f1c9ab1..eefd652 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.39.2" +version = "2.39.3" 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 fb73237..a74c78a 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.2", + "version": "2.39.3", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ISSUES_TODOLIST.md b/docs/ISSUES_TODOLIST.md index ea8b0ba..2d859cd 100644 --- a/docs/ISSUES_TODOLIST.md +++ b/docs/ISSUES_TODOLIST.md @@ -197,7 +197,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | *BUG-088* | Injection de formule dans un `.xlsx` : une saisie `=cmd\|'/c calc'!A1` est stockée comme formule et s'exécute à l'ouverture dans Excel (DDE) | 🟢 corrigé | P0 | tableur Excel / sécurité | IA | `backend/services/mutations.py::_write_cell`, `backend/routers/files_write.py`, `frontend/js/viewer.js::renderXlsxViewer` | `PUT /api/file/V/xlsx/save` avec `{"sheet": "S", "cells": {"A1": "=1+1"}}` → la cellule sort en `data_type == "f"` | `cell.data_type = "s"` après affectation : le texte est stocké comme chaîne, aucun `` n'est écrit. Opt-in via `allow_formula: true` (endpoint) et le bouton `f(x)` de la visionneuse (session, jamais persisté). Test : `TestXlsxFormulaGuard` (4) + `xlsx-viewer.test.mjs` (toggle) | #153 A4. `+`/`-` ne sont pas neutralisés : ils sont déjà convertis en nombre par `_coerce_xlsx_value`. Le handler global `ServiceError` expose désormais `code` + `details` (le client en a besoin pour le 409), et `api()` (frontend) les propage sur l'Error. Vérifié : cf. BUG-085 | | *BUG-089* | Un reindex manuel ne reconstruisait pas l'index inversé : la recherche TF-IDF continuait de servir un index périmé | 🟢 corrigé | P1 | ⚙️ backend / recherche | IA | `backend/indexer.py::reload_index`, `backend/indexer.py::reload_single_vault`, `backend/search.py` | Modifier le contenu d'un fichier, puis `GET /api/index/reload` → la recherche renvoie encore l'ancien contenu (ou rien pour un fichier nouveau) | `reload_index()` / `reload_single_vault()` appellent `init_inverted_index()` après le rebuild (le remplacement wholesale d'une entrée de vault n'émet pas les notifications incrémentales). En prime, `backend/search.py` lisait l'index via `from backend.indexer import index` (liaison **par valeur** du dict) : un `importlib.reload(backend.indexer)` recréait le dict côté indexer tandis que la recherche écrivait encore dans l'ancien — l'index inversé n'indexait alors plus rien. Tous les accès passent désormais par `_indexer.index`. Contre-preuve : `TestXlsxSearchable::test_search_finds_a_word_stored_in_a_cell` échoue sans le correctif | #153 A5. Trouvé en écrivant le test de recherche d'A5 : il passait isolément et échouait en suite complète selon l'ordre. Le reload incrémental par fichier (watcher, edition) n'est pas concerné : il passe par le hook `_on_index_change`. Vérifié : suite 1402 passed / 6 skipped, ruff/mypy 0 | | *BUG-090* | Troncature silencieuse d'une feuille `.xlsx` au-delà de 500 lignes × 40 colonnes : l'utilisateur voit une table courte sans aucun indice que la suite existe | 🟢 corrigé | P1 | tableur Excel / UX | IA | `backend/xlsx_reader.py::render_sheets`, `backend/routers/files_read.py`, `frontend/js/viewer.js::renderXlsxViewer`, `frontend/style.css` | Ouvrir `test_vault/sample-xlsx-large.xlsx` (520 lignes) → la feuille s'arrête à la ligne 500 sans aucun message | `render_sheets()` renvoie désormais `total_rows`/`total_cols` (dimensions déclarées par la feuille), `max_rows`/`max_cols` (plafonds du moteur) et `truncated` ; la visionneuse affiche un bandeau « Feuille tronquée — 500 lignes affichées sur 520 » (i18n `xlsx.truncated_*` FR/EN, axe des colonnes inclus). Contre-preuve : neutraliser `truncated` → `TestXlsxTruncationNotice` (2 tests) échoue | #153 A8/R5. La ligne d'en-têtes est aussi `sticky` au défilement vertical (`thead th { top: 0 }` + `top: auto` sur les numéros de ligne pour éviter l'empilement en haut à gauche). L'endpoint `GET …/xlsx/sheet` (#153 A9) sert les fenêtres au-delà du plafond, mais le chargement paresseux complet (défilement virtuel, « charger tout ») reste à faire — le bandeau dit la vérité en attendant. Vérifié : `test_xlsx_viewer.py` 58 passed, E2E 7/7 (dont 3 nouveaux), suite 1417 passed / 6 skipped, ruff/mypy 0, i18n parity | -| *BUG-091* | Le job CI `security` échoue depuis la mise à jour de semgrep : le binaire 1.175+ refuse de démarrer sur le runner (`CPU ISA level is lower than required`, exit 127) | 🟢 corrigé | P1 | CI / sécurité | IA | `.gitea/workflows/ci.yml` (job `security`) | Run Gitea #1641 (push docs #153) : étape « Semgrep (SAST local, bloquant — #87) » → `libs/libresolv.so.2: CPU ISA level is lower than required`, `exitcode '127'` | Épinglage `semgrep==1.174.0` dans l'étape « Install dependencies » du job security (dernière version exécutable sur le CPU du runner ; les libs natives de la série 1.175+ sont compilées pour x86-64-v2). Bandit et pip-audit inchangés — les trois étapes repassées localement ✅ | #153, run #1641. Les 7 commits de features v2.33.0→v2.39.0 n'ont déclenché aucun run (Gitea ne lance le workflow que sur le commit de tête du push) ; le CI n'avait donc encore vu ni xlrd/odfpy ni le code A6→A17. Trouvé via le log fourni par l'utilisateur. À surveiller : toute montée de semgrep devra être retestée sur le runner | +| *BUG-091* | Le job CI `security` échoue : le binaire semgrep refuse de démarrer sur le runner (`CPU ISA level is lower than required`, exit 127) | 🟢 corrigé | P1 | CI / sécurité | IA | `.gitea/workflows/ci.yml` (job `security`), `backend/requirements.txt` | Run Gitea #1641 : étape « Semgrep » → `libs/libresolv.so.2: CPU ISA level is lower required, exitcode '127'` ; rechute identique sur #1642 avec `semgrep==1.174.0` | semgrep isolé dans un venv jetable du job (`/tmp/semgrep-venv`), épinglé à **1.157.0** — la dernière version publiée en wheel `manylinux2014` (baseline x86-64 **v1**) ; la série 1.158+ ne publie plus que `manylinux_2_34/2_35`, dont les libs natives exigent x86-64-**v2** que le CPU du runner ne fournit pas. Le venv évite aussi ses dépendances incompatibles avec l'env principal (`tomli~=2.0.1` vs pip-audit ≥ 2.10 ; `pyjwt~=2.12.0` couvert par PYSEC-2026-178). Plancher `pyjwt[crypto]>=2.13.0` dans requirements.txt (transitif de mcp) + `pip install -U pip setuptools` dans le job (pip PYSEC-2026-3721, setuptools PYSEC-2026-3447, apparus dans la base entre-temps). Les 3 étapes repassées en local dans un environnement frais ✅ | #153. security échouait déjà avant ce push (tags v2.31.0/v2.32.0 rouges) ; les 7 commits de features v2.33.0→v2.39.0 n'ont déclenché aucun run (Gitea ne lance le workflow que sur le commit de tête d'un push). Le 1ᵉʳ pin 1.174.0 (v2.39.2) était erroné : wheel `manylinux_2_34` uniquement, refusé par le CPU. À surveiller : tout semgrep > 1.157.0 exigera un runner x86-64-v2 | ### TODOs techniques (améliorations / nouvelles tâches) @@ -216,7 +216,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | Date | ID(s) traité(s) | Action | Fichiers modifiés | Résumé | Statut après | |---|---|---|---|---|---| | 2026-09-28 | BUG-090 (#153 A8 + A9) | Correction + feature | `backend/xlsx_reader.py`, `backend/routers/files_read.py`, `backend/schemas.py`, `backend/openapi_docs.py`, `frontend/js/viewer.js`, `frontend/style.css`, `frontend/locales/{fr,en}.json`, `tests/test_xlsx_viewer.py`, `tests/frontend/xlsx-viewer.test.mjs`, `tests/e2e/xlsx-viewer.spec.js`, `test_vault/sample-xlsx-large.xlsx` | **La troncature d'une feuille est annoncée et les lignes cachées restent accessibles** : (BUG-090/A8) `render_sheets()` renvoie `total_rows`/`total_cols`/`max_rows`/`max_cols`/`truncated`, la visionneuse affiche un bandeau « Feuille tronquée » (i18n FR/EN, axes lignes et colonnes) et la ligne d'en-têtes devient `sticky` (`top: auto` sur les numéros de ligne pour éviter l'empilement) ; (A9) `GET /api/file/{vault}/xlsx/sheet?sheet=&offset=&limit=` (`XlsxSheetWindowResponse`, plafond 1 000 lignes/requête, 404 feuille inconnue, 415 non-xlsx) sert une fenêtre avec les **vraies** coordonnées A1 et le `has_more` de pagination. Contre-preuves : neutraliser `truncated` → 2 tests échouent ; neutraliser l'offset → 3 tests échouent. Vérifié : `test_xlsx_viewer.py` 58 passed, xlsx-viewer.test.mjs 14/14, E2E 7/7 (3 nouveaux + fixture `sample-xlsx-large.xlsx` 520 lignes), suite 1417 passed / 6 skipped, ruff 0, mypy 0, i18n parity, validate-imports 40 modules | 🟢 corrigé (en attente vérif utilisateur) | -| 2026-09-28 | BUG-091 (#153, run CI #1641) | Correction CI | `.gitea/workflows/ci.yml`, `docs/ISSUES_TODOLIST.md`, `CHANGELOG.md` | **Le job `security` est réparé** : le log CI (`libs/libresolv.so.2: CPU ISA level is lower than required`, exit 127) montre que le binaire semgrep non épinglé s'était mis à jour (1.178) vers un build exigeant x86-64-v2, que le CPU du runner Gitea ne fournit pas — aucun lien avec le code livré (bandit/semgrep/pip-audit tous verts en local, et le commit fautif ne touchait que la documentation). Correctif : `semgrep==1.174.0` épinglé dans l'étape « Install dependencies » avec le raisonnement en commentaire. Au passage documenté : les 7 commits de features v2.33.0→v2.39.0 n'ont déclenché aucun run CI (seul le commit de tête d'un push déclenche le workflow). Vérifié localement : semgrep 1.174.0 exit 0 sur `semgrep-rules/`, bandit 0, pip-audit « No known vulnerabilities » | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-09-29 | BUG-091 (#153, runs CI #1641-#1642) | Correction CI | `.gitea/workflows/ci.yml`, `backend/requirements.txt`, `docs/ISSUES_TODOLIST.md`, `CHANGELOG.md` | **Le job `security` est réparé définitivement** : (1) le binaire semgrep non épinglé exige depuis 1.158.0 un CPU x86-64-v2 que le runner Gitea ne fournit pas (`libs/libresolv.so.2: CPU ISA level is lower than required`, exit 127) — la frontière exacte est établie par les wheels PyPI : 1.157.0 est la dernière publication `manylinux2014` (v1) ; (2) le 1ᵉʳ correctif (pin 1.174.0, v2.39.2) échouait car cette version ne publie qu'en `manylinux_2_34` ; (3) semgrep vit désormais dans un venv isolé du job (`/tmp/semgrep-venv`, pin 1.157.0) car ses dépendances contredisent l'env principal (`tomli~=2.0.1` vs pip-audit ≥ 2.10, `pyjwt~=2.12.0` vs PYSEC-2026-178) ; (4) plancher `pyjwt[crypto]>=2.13.0` dans requirements.txt (transitif de mcp) et `pip install -U pip setuptools` dans le job (nouveaux advisories pip PYSEC-2026-3721, setuptools PYSEC-2026-3447). Validation : environnement frais reconstitué en local → résolution sans conflit (pyjwt 2.15.1), pip-audit exit 0, semgrep 1.157.0 exit 0 sur `semgrep-rules/`. Au passage documenté : security échouait déjà avant ce push (v2.31.0/v2.32.0 rouges) et les commits de features n'ont déclenché aucun run (Gitea : commit de tête uniquement) | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-28 | #153 A6 → A17 (v2.33.0 → v2.39.0) | Feature + clôture documentaire (aucun bug nouveau) | `CHANGELOG.md`, `docs/features/xlsx-viewer.md`, `docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md`, `README.md`, `README.fr.md` | **Clôture du backlog #153** : entrées CHANGELOG des 7 sous-tâches, fiche `features/xlsx-viewer.md` (statut terminé, cases A6-A17 cochées, historique), section 6 du guide utilisateur étendue (barre de formule, navigation clavier, tri/filtre/recherche/export CSV, structure, styles, formats `.xlsm`/`.xls`/`.ods`/`.csv`, tableau de bord) et bullets README FR/EN. Code livré : v2.33.0 A6 (outils IA `backend/tools/spreadsheets.py`), v2.34.0 A7 (clavier + barre de formule), v2.35.0 A13 (tri/filtre/recherche/export), v2.36.0 A14 (structure `PUT …/xlsx/structure`), v2.37.0 A15 (styles/fusions/volets figés), v2.38.0 A16 (`.xlsm` éditable, `.xls`/`.ods` lecture seule, `.csv` RFC 4180), v2.39.0 A17 (dashboard `GET …/xlsx/dashboard`). Vérifié : suite xlsx 116 passed, xlsx-viewer.test.mjs 35/35, ruff/mypy 0, i18n parity, validate-imports 40 modules | ✅ livré (en attente vérif utilisateur) | | 2026-09-28 | BUG-089 (#153 A5, A10, A12) | Correction | `backend/xlsx_reader.py`, `backend/indexer.py`, `backend/search.py`, `backend/services/mutations.py`, `frontend/js/viewer.js`, `frontend/style.css`, `frontend/locales/{fr,en}.json`, `tests/test_xlsx_viewer.py` | **Les tableurs deviennent visibles ettypés** : (A5) `extract_indexable_text()` indexe noms de feuilles + 20 premières lignes (plafond 5 k caractères) dans le TF-IDF et la recherche sémantique — un mot tapé dans une cellule rend le fichier trouvable ; (A10) `_coerce_xlsx_value()` reconnaît désormais les booléens (`TRUE`/`FAUX`/`OUI`/`NON`) et les dates FR `JJ/MM/AAAA` (jour-first : `01/02/2026` = 1er février), symétrique avec l'affichage ; (A12) la valeur calculée en cache s'affiche sous la formule (``, 2ᵉ lecture `data_only=True` uniquement si l'archive contient un ``), info-bulle traduite via `xlsx.cached_value_title` FR/EN. (BUG-089) un reindex manuel reconstruisait mal l'index inversé et `backend/search.py` lisait l'index par valeur. Contre-preuves vérifiées pour A5, A10 et A12. Vérifié : `test_xlsx_viewer.py` 43 passed, suite 1402 passed / 6 skipped, ruff 0, mypy 0, i18n parity, validate-imports 40 modules, xlsx-viewer.test.mjs 10/10 | 🟢 corrigé (en attente vérif utilisateur) | | 2026-09-27 | BUG-085 → BUG-088 (#153 A1-A4) | Correction | `backend/xlsx_reader.py`, `backend/services/mutations.py`, `backend/routers/files_read.py`, `backend/routers/files_write.py`, `backend/schemas.py`, `backend/main.py`, `frontend/js/viewer.js`, `frontend/js/auth.js`, `frontend/style.css`, `frontend/locales/{fr,en}.json`, `frontend/sw.js`, `tests/test_xlsx_viewer.py`, `tests/frontend/xlsx-viewer.test.mjs`, `tests/e2e/xlsx-viewer.spec.js`, `test_vault/sample-xlsx-lossy.xlsx`, `.gitea/workflows/ci.yml` | **Garde-fous d'écriture des classeurs Excel** : (BUG-085) `inspect_workbook()` détecte ce qu'un round-trip openpyxl perd (valeurs calculées, slicers, contrôles, connexions, custom XML, signature) → la lecture expose `xlsx_lossy_features`, la visionneuse affiche une bannière et `PUT xlsx/save` refuse sans `force` (**409** `xlsx_lossy_content`, confirmation explicite puis reprise) ; (BUG-086) écriture atomique `.tmp` + `os.replace` ; (BUG-087) verrou par fichier (409 `conflict`, endpoint sync pour le threadpool) ; (BUG-088) une saisie `=`/`@` est stockée en texte (`data_type = "s"`), sauf opt-in `allow_formula` / bouton `f(x)`. Le handler `ServiceError` expose désormais `code` + `details` et `api()` les propage. Périmètre de perte revalidé empiriquement sur openpyxl 3.1.5 (graphiques, images et TCD sont préservés). Vérifié : `test_xlsx_viewer.py` 31 passed, suite 1390 passed / 6 skipped, ruff/mypy 0, validate-imports 40 modules, xlsx-viewer.test.mjs 10/10, E2E 3/3 | 🟢 corrigé (en attente vérif utilisateur) | diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index f4a0190..a795ca4 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ObsiGate — Roadmap -> **Version :** 2.39.2 | **Dernière mise à jour :** 2026-09-28 +> **Version :** 2.39.3 | **Dernière mise à jour :** 2026-09-28 > **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)** diff --git a/package.json b/package.json index 8d60103..a1b4cba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsigate", - "version": "2.39.2", + "version": "2.39.3", "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": {