- Premier lancement : %USERPROFILE%\ObsiGate créé et monté comme vault de
démarrage « ObsiGate » (remplace voute_obsidian), vault_path dessus,
racine home nommée d'après son dernier segment (fin du « bruno » codé en
dur), champs de fenêtre préservés.
- Document « Prise en main.md » embarqué dans le binaire
(include_str!) et écrit dans ce répertoire si absent — jamais écrasé.
- Section « 🖥️ Vaults & dossiers (Desktop) » refondue : markup à classes
(zéro style inline), bloc CSS desktop-roots-* sur variables (motif
webauthn-key-item), boutons .config-btn-sm primaire/secondaire,
cibles tactiles 44px en mobile.
- Tests : test_default_first_run_config, test_welcome_doc_embedded
(cargo test 28 passed), suites frontend vertes, E2E 123/123.
- le helper choisissait le premier bloc media contenant la chaine 'mobile-toolbar' ; un commentaire citant ce selecteur (correctif BUG-103) le faisait retomber sur le mauvais bloc, sans la clearance .main-body (BUG-073)
- les commentaires sont maintenant retires avant la recherche de bloc
- en mobile le sidebar de navigation (z-index 200) passait sous .mobile-toolbar (900, 64 px) : les dernieres entrees de l'arbre etaient masquees
- regle mobile .sidebar : z-index 200 -> 950 ; test de non-regression 'mobile sidebar over toolbar' dans unit.test.mjs
- clic sur le titre: la page d'accueil s'ouvre dans un onglet Accueil (icone Maison) epingle en tete de barre, reactivable d'un clic (onglet propre au panneau actif en split)
- page de navigation: menus contextuels des repertoires et des fichiers identiques a ceux de la sidebar, clic sur la racine d'un vault qui ouvre la vue, icone dossier a la couleur de l'arbre de navigation
- BUG-101: bloc « Keyboard shortcuts for tabs » duplique dans ui.js (un seul Ctrl+W fermait deux onglets)
- BUG-102: le menu contextuel se refermait 10 ms apres son ouverture (scroll de reflow des icones lucide)
- clic sur un repertoire de l'arbre (modes focus vault et All) ouvre un onglet de navigation dedie qui coexiste avec les onglets de fichiers ouverts
- vue: sous-repertoires cliquables, facettes Vaults/Tags/Extensions (meme mecanisme que la recherche), tris Pertinence/Date, bouton Sauver; tags indexes exposes par GET /api/vault/{v}/files
- BUG-100: showWelcome re-injecte la section Raccourcis & Astuces et goHome efface la recherche globale + le filtre de la sidebar
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
pip-audit bloquait sur PYSEC-2026-3910 (outlines) et PYSEC-2026-3911 (XForm),
toutes deux atteignables via backend/pdf_reader.py. Le plancher pypdf>=4.0 ne
protégeait rien : l'image Act du runner embarque 6.16.0 dans sa toolcache
Python, donc pip répondait « already satisfied » sans jamais aligner.
Au passage, le garde-fou TestSemgrepStep était en régression depuis la
désactivation de semgrep (v2.39.9) et aurait rougi le job `test` : il vérifie
désormais que l'étape n'exécute que son avertissement et que bandit et
pip-audit restent bloquants. Nouveau TestDependencySecurityFloors pour
verrouiller les planchers de sécurité (contre-preuve : pypdf remis à >=4.0).
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
A17 — nouveau endpoint GET /api/file/{vault}/xlsx/dashboard (read_workbook_
dashboard : plages nommees avec portee depuis defined_names read_only,
comptage graphiques/TCD par parts OPC, stats par feuille bornées 500x40 :
cellules/lignes/colonnes/formules/numerique + 8 premieres valeurs en cartes
KPI) et panneau frontend toggled depuis la toolbar (table des plages,
cartes KPI par feuille, hint actions IA). Bouton absent pour .csv et
formats en lecture seule ; le menu structure est saute quand le bouton
n'existe pas. i18n FR/EN (xlsx.dashboard_*), 8 tests backend + 2 tests
JSDOM + contre-preuve (5 echecs sur neutralisation), ruff/mypy 0.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
A16 — la visionneuse tableur accepte quatre formats de plus : .xlsm est
servi et sauvegarde comme un .xlsx avec keep_vba=True (les macros
survivent, la porte lossy est levée pour ce format) ; .xls (xlrd) et .ods
(odfpy) sont rendus en lecture seule (xlsx_readonly, wiring d'édition
désactivé) ; .csv devient éditable via render_csv_table (grille A1
identique au viewer) et PUT /api/file/{vault}/csv/save (réécriture csv
RFC 4180, extension de grille, valeurs stockées telles quelles). 12 tests
backend + contre-preuve (4 échecs sur neutralisation du service CSV),
JSDOM 33/33, ruff/mypy 0.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
A15 — le rendu tableur lit les metadonnees du classeur (une charge en mode
normal par feuille) et les restitue : styles inline data-driven (couleur de
police en premier, fond, gras/italique, format numerique signale en mono),
alignements non-defaut, plages fusionnees (rowSpan/colSpan cote client) et
ancrage des volets figes (sticky rows/cols). Le chargement lazy replie les
metadonnees de chaque fenetre. Contrat API : styles/aligns = {ref: css},
merges = [ranges], freeze = ancre. 9 tests backend + contre-preuve (5 echecs
sur neutralisation), JSDOM 33/33.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
A14 : service mutate_xlsx_structure (batch ordonné d'actions
sheet_add/sheet_rename/sheet_delete/sheet_duplicate et
row/col_insert/delete, une seule réécriture verrouillée et atomique,
409 lossy sans force, backup, dernier sheet protégé) ; endpoint
PUT /api/file/{vault}/xlsx/structure (1-50 actions/requête) ;
menu « structure » dans la visionneuse : ajout/renommage/duplication/
suppression de feuilles et insertion/suppression de ligne/colonne à
partir de la cellule active, confirmations explicites pour les
destructions, re-rendu serveur après succès, reprise force après
confirmation 409.
Vérifié : test_xlsx_structure.py 11 (ops, gardes, atomicité du batch,
409 lossy + force) + contre-preuve (garde dernier sheet neutralisée ->
1 échec), xlsx-viewer.test.mjs 33/33 (3 nouveaux), ruff 0, mypy 0,
i18n parity, validate-imports.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
A13 (affichage seul, le classeur n'est jamais réécrit) : clic sur un
en-tête pour trier la colonne (asc/desc, numérique si toutes les valeurs
le sont, localeCompare fr sinon — les cellules modifiées suivent leur
ligne) ; l'unique champ de recherche filtre les lignes ET surligne les
occurrences (marque <mark>, navigation ↑/↓/Entrée, compteur i18n,
insensible à la casse par défaut, bouton Aa) ; bouton « Réinitialiser »
qui re-rend le fichier (vérité serveur) ; export CSV de la feuille
visible (BOM UTF-8, échappement ;/" et retrait des ombres de valeurs
calculées).
Vérifié : xlsx-viewer.test.mjs 30/30 (5 nouveaux) + contre-preuves
(tri neutralisé -> 1 échec, filtre neutralisé -> 1 échec), validate-imports,
i18n parity (xlsx.find_*, xlsx.sort_*, xlsx.csv_export).
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
A7 : barre de formule sous la barre d'outils (nom de cellule active A1 +
miroir du contenu, édition live, Entrée valide, Échap annule puis
refocalise) ; flèches et Tab déplacent la cellule active dans les 4
directions (Shift inverse Tab), outline persistant sur la cellule active
quand le focus passe à la barre ; Maj+Entrée ne valide plus (réservé au
multiligne A7+). td.tabIndex=0 pour la focalisation clavier.
Vérifié : xlsx-viewer.test.mjs 25/25 (6 nouveaux), E2E 9/9 (barre +
flèches + Échap sur fixture réelle), validate-imports, i18n parity
(xlsx.formula_bar_placeholder, xlsx.active_cell).
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
A9bis : sous une feuille tronquée, un pied de page « N lignes affichées
sur M · Charger la suite » fetch la fenêtre suivante (limit=500) au clic
ou à l'approche du bas du tableau (sentinelle de défilement, marge 120px).
Les lignes ajoutées passent par le même pipeline d'édition que le rendu
initial (setupCell factorisé) : éditables et sauvegardables immédiatement.
Fetch échoué → bouton restauré (retry) + toast ; feuille complète → pied
de page masqué (class done).
Vérifié : xlsx-viewer.test.mjs 19/19 (5 nouveaux, contre-preuve
wireLazyRows désactivé → 5 échecs), E2E 8/8 (A520 visible et éditable
après clic), validate-imports 40 modules, unit.test.mjs 12/12, i18n parity.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
- BUG-090 (#153 A8) : render_sheets() expose total_rows/total_cols,
max_rows/max_cols et truncated ; la visionneuse affiche un bandeau
« Feuille tronquée » (i18n FR/EN) au lieu de couper en silence, et la
ligne d'en-têtes devient sticky (top:auto sur les numéros de ligne).
- #153 A9 : GET /api/file/{vault}/xlsx/sheet?sheet&offset&limit sert une
fenêtre de 1 à 1000 lignes avec les vraies coordonnées A1, has_more de
pagination et valeurs calculées A12 ; 404 feuille inconnue, 415 non-xlsx.
- Tests : TestXlsxTruncationNotice (4) + TestXlsxSheetWindow (11) avec
contre-preuves, xlsx-viewer.test.mjs 14/14, E2E 7/7 (fixture
sample-xlsx-large.xlsx 520 lignes), suite 1417 passed / 6 skipped,
ruff/mypy 0, i18n parity.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>