Seven crates in Cargo.lock accept windows-sys through a wide bound (colored, dirs-sys, errno, rustix, rustls-platform-verifier, tempfile, winapi-util), so cargo may point them at any windows-sys version already in the graph. All seven point at 0.61.2, the state every Windows release has shipped with since v0.14.0. Some updates re-resolve them (cargo update -p hyper, -p h2, -p rustls-webpki -p tar did) down to 0.48/0.52/0.59 while cargo only reports the packages it bumped: the Windows build then compiles five windows-sys versions instead of three, and nothing says so. A new blocking step, "Verify windows-sys pinning", reads the lockfile with awk right after the suppression guard and fails if any edge of the seven crates leaves 0.61.2 (a bare edge only passes when the lockfile holds a single windows-sys, at 0.61.2), if a guarded crate has no block, or if the set of windows-sys versions changes. awk's exit code is tested apart from its output, every check is logged with a summary line, and a canary (tempfile) must be read and checked by the loop. The step text was extracted from the YAML and run as is, with the runner's bash options, on copies of the lockfile: main's and the five release tags' locks pass (9 checks, exit 0); the hyper-bumped lock, an added windows-sys 0.53.0, a renamed guarded crate, a missing lockfile and a guarded list without the canary all exit 1. ADR 0023 records why 0.61.2, the measured cost (3 versions compiled for Windows instead of 5), the triggers, the exit condition (windows-sys 0.62, or convergence on a single version) and the repair when the step fails. CLAUDE.md and docs/architecture.md cite the guard next to the suppression check. Cargo.lock and .cargo/audit.toml are untouched. Resolves #345 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
13 KiB
CLAUDE.md — Simpl'Résultat
@STATE.md
Contexte du projet
Simpl'Résultat est une application de bureau desktop privacy-first pour la gestion des finances personnelles. Elle traite localement les fichiers CSV bancaires sans aucune dépendance cloud. Projet solo entrepreneurial, en développement par Max.
Stack technique : Tauri v2 + React 19 + TypeScript + Tailwind CSS v4 Backend : Rust (commandes Tauri) Stockage : SQLite local (tauri-plugin-sql) Langues supportées : Français (FR) et Anglais (EN) Plateformes : Windows, Linux Version actuelle : 0.17.1 Licence : GPL-3.0-only
Principes fondamentaux
Privacy-first — NON NÉGOCIABLE
- Zéro donnée envoyée vers un serveur tiers
- Tout le traitement CSV et toutes les données financières restent en local
- Aucune télémétrie, aucun analytics cloud
Précision financière
- Toujours valider les montants selon les règles de parsing configurables (gestion des virgules/points, espaces, symboles monétaires)
- Gérer l'encodage des fichiers CSV (UTF-8, Windows-1252, ISO-8859-15)
Internationalisation (i18n)
- Toute chaîne affichée à l'utilisateur doit passer par le système i18n (i18next + react-i18next)
- Jamais de texte en dur dans les composants React
- Fichiers de traduction :
src/i18n/locales/fr.jsonetsrc/i18n/locales/en.json
Architecture & structure du code
src/
├── components/ # 53 composants React organisés par domaine
│ ├── adjustments/ # Ajustements
│ ├── budget/ # Budget
│ ├── categories/ # Catégories hiérarchiques
│ ├── dashboard/ # Tableau de bord
│ ├── import/ # Wizard d'import (14 composants)
│ ├── layout/ # AppShell, Sidebar
│ ├── profile/ # Profils (PIN, formulaire, switcher)
│ ├── reports/ # Graphiques et rapports
│ ├── settings/ # Paramètres
│ ├── shared/ # Composants réutilisables
│ └── transactions/ # Transactions
├── contexts/ # ProfileContext (état global profil)
├── hooks/ # 13 hooks custom (useReducer)
├── pages/ # 11 pages
├── services/ # 14 services métier
├── shared/ # Types et constantes partagés
├── utils/ # Utilitaires (parsing, CSV, charts)
├── i18n/ # Config i18next + locales FR/EN
├── App.tsx # Router principal (react-router-dom)
└── main.tsx # Point d'entrée
src-tauri/
├── src/
│ ├── commands/ # 3 modules, 17 commandes Tauri
│ │ ├── fs_commands.rs # Système de fichiers (6 commandes)
│ │ ├── export_import_commands.rs # Export/import chiffré (5 commandes)
│ │ └── profile_commands.rs # Gestion des profils (8 commandes)
│ ├── database/ # Schémas SQL et migrations
│ │ ├── schema.sql # Schéma initial (v1)
│ │ ├── seed_categories.sql # Seed catégories (v2)
│ │ ├── consolidated_schema.sql # Schéma complet (nouveaux profils)
│ │ └── profile_db.rs # Rattrapage des profils secondaires (ADR 0021)
│ ├── lib.rs # Point d'entrée, 17 migrations inline, plugins
│ └── main.rs
└── Cargo.toml
Règles d'architecture :
- La logique métier va dans
services/, jamais directement dans les composants - L'état de chaque domaine est géré par un hook
useReducerdédié danshooks/ - Les composants React sont responsables de l'affichage uniquement
- Toute opération sur les fichiers système passe par les commandes Tauri (Rust)
- Les requêtes SQL passent par les services TypeScript via
tauri-plugin-sql
Fonctionnalités principales
- Import CSV : wizard multi-étapes, détection auto (encodage, délimiteur, colonnes par libellé FR/EN, formats de banques connus) avec score de confiance, aperçu obligatoire à récap signé avant tout import, templates de config, déduplication par fichier. Le format d'import est persisté en entier sur la source et jamais re-deviné — ADR 0019
- Catégorisation : automatique (mots-clés avec priorité) et manuelle, drag-and-drop pour réorganiser
- Transactions : filtrage, tri, split sur plusieurs catégories, notes
- Budget : grille 12 mois, templates réutilisables, budget vs réel
- Rapports : tendances mensuelles, répartition par catégorie, évolution dans le temps, graphiques interactifs (SVG patterns, menu contextuel)
- Multi-profils : bases de données séparées, protection par PIN (Argon2), switching rapide
- Export/Import : JSON/CSV avec chiffrement AES-256-GCM optionnel (format SREF)
- Mises à jour : auto-updater intégré (tauri-plugin-updater)
- Changelog bilingue : page
/changelogavec historique complet, notes de version dynamiques FR/EN depuisCHANGELOG.md/CHANGELOG.fr.md(bundlés danspublic/)
Conventions de code
React / TypeScript
- Un composant = un fichier
.tsx, nommé en PascalCase - Hooks custom dans
hooks/, services dansservices/ - État local via
useReducerdans les hooks de domaine
Rust / Tauri
- Toutes les commandes Tauri retournent
Result<T, String>pour la gestion d'erreurs - Documenter chaque commande avec un commentaire sur son rôle
Général
- Commits en anglais, commentaires de code en anglais
- Messages d'interface en français ET anglais (via i18n)
- Tester les cas limites de parsing CSV (montants négatifs, cellules vides, formats inattendus)
Base de données
- 20 tables SQLite, 24 index (voir
docs/architecture.mdpour le détail). Le module Bilan en représente 7 tables (balance_categories,balance_accounts,balance_snapshots,balance_snapshot_lines,balance_account_transfers, puisbalance_securities+balance_snapshot_holdingsajoutées en Étape 2 — détail par titre) et 9 index. Ces deux comptes sont inchangés depuis v13 : v14-v16 ajoutent 2 tables + 2 index (déjà comptés), v17 est unALTER TABLEpur - 17 migrations inline dans
lib.rs(v1→v17, viatauri_plugin_sql::Migration). Étape 2 du Bilan (détail par titre) : v14 (balance_securities+balance_snapshot_holdings+ 2 index), v15 (balance_accounts.kind+detailed_since+ backfill), v16 (conversion des comptes cotés existants en détaillés 1-position) — voir ADR 0015. Import : v17 (import_sources.amount_mode+sign_convention, les deux avecCHECK, +header_signature+template_id) — voir ADR 0019 - Schéma consolidé (
consolidated_schema.sql) pour l'initialisation des nouveaux profils, suivi de l'historique complet des migrations. tauri-plugin-sql ne migre que le profil par défaut : les profils secondaires rattrapent leurs migrations à chaque ouverture (prepare_profile_db, copie préalable, clés étrangères actives) — voir ADR 0021. Les bases vivent sousapp_config_dir()(profile_db_path), qui n'est pasapp_data_dir()sous Linux - Les migrations appliquées sont protégées par checksum — ne jamais modifier une migration existante, toujours en créer une nouvelle
Documentation technique
La documentation technique est centralisée dans docs/ :
docs/architecture.md— Architecture technique complète (stack, BDD, services, hooks, commandes Tauri, routing, i18n, CI/CD)docs/adr/— Architecture Decision Records (décisions techniques structurantes)docs/guide-utilisateur.md— Guide utilisateurdocs/archive/— Anciennes spécifications archivées
Règle : quand un changement touche l'architecture, mettre à jour la documentation :
- Nouveau service, hook, commande Tauri, page/route, ou table SQL → mettre à jour
docs/architecture.md - Décision technique structurante (choix de librairie, pattern architectural, changement de stratégie) → créer un nouvel ADR dans
docs/adr/ - Changement affectant l'utilisation de l'app → mettre à jour
docs/guide-utilisateur.mdet les traductions i18n correspondantes (src/i18n/locales/fr.json,src/i18n/locales/en.json, clés sousdocs.*)
Règle CHANGELOG : tout changement affectant le comportement utilisateur → ajouter une entrée sous ## [Unreleased] dans les deux fichiers :
CHANGELOG.md(anglais) — source principaleCHANGELOG.fr.md(français) — traduction- Catégories : Added/Ajouté, Changed/Modifié, Fixed/Corrigé, Removed/Supprimé
- Format Keep a Changelog. Le contenu est extrait automatiquement par le CI pour les release notes et affiché dans l'app selon la langue de l'utilisateur.
- The
public/copies are synced automatically: Vite copies them ondev/buildstart viasyncChangelogs()invite.config.ts. No manual sync needed.
Points d'attention RS&DE / CRIC
Pour maintenir l'éligibilité aux crédits d'impôt R&D (RS&DE fédéral + CRIC Québec) :
- Documenter les incertitudes technologiques rencontrées pendant le développement
- Noter les expérimentations et les approches alternatives testées
- Garder un journal des avancées techniques (dans
/docs/rnd-journal/) - Les algorithmes de catégorisation automatique et le parsing multi-format sont des activités R&D éligibles
CI/CD
Workflows Forgejo Actions dans .forgejo/workflows/. Le runner est à capacité 1 — les jobs se suivent, ils ne tournent pas en parallèle.
check-rust.yml— déclenché sur les PR touchantsrc-tauri/**ou.cargo/**, plus chaque lundi à 06:30 UTC surmainet parworkflow_dispatch. Lancecargo check, une vérification bloquante que les advisories acceptées restent non atteignables, une autre que les sept arêteswindows-sysà borne large du lock restent sur0.61.2(ADR 0023),cargo testet uncargo auditinformatif, avec le cacheSwatinem/rust-cache. Doit être vert avant tout merge. Uncargo updatequi re-résout ces arêtes (-p hyperet-p h2l'ont fait) fait rougir le gardewindows-sys: remettre les arêtes à la main selon l'ADR, ne jamais modifier la liste gardée pour le faire passer.check-frontend.yml— déclenché sur les PR, sauf si tous les fichiers modifiés sont du Rust, de la doc ou du markdown. Lancenpm run build(tsc + vite) etnpm test(vitest). Doit être vert avant tout merge. Aucune étapenpm audit: les advisories npm ne sont pas un gate de CI.npm auditremonte 2 high en permanence — une seule advisoryreact-router(mode RSC, inatteignable dans une app de bureau sans serveur), acceptée et suivie en #317.audit.yml— audit RustSec quotidien (06:00 UTC) +workflow_dispatch, pour couvrir les avis de sécurité entre deux PR Rust. Échec bloquant. Un run vert signifie « zéro advisory hors de la liste acceptée », pas « zéro advisory » : cette liste est dans.cargo/audit.toml(preuve de non-atteignabilité et condition de retrait par entrée), encadrée par l'ADR 0018. Ne jamais y ajouter une advisory atteignable ni élargir une entrée à un crate entier.release.yml— déclenché par les tagsv*. Build Windows (NSIS.exe) + Linux (.deb,.rpm), signe les binaires et publie le JSON d'updater pour les mises à jour automatiques.
Aucun workflow check-* ne filtre sur branches: : une PR stackée sur une autre branche de feature déclenche donc bien la CI. Le cache Rust passe par Swatinem/rust-cache (#234) : le runner garde ce qu'écrit un run de PR pour cette seule PR, donc c'est le run du lundi (ou un workflow_dispatch) qui alimente le cache partagé que lisent les autres PR. Un run à froid reste normal après une nouvelle version stable de Rust ou une modification de l'env: du job, quand une entrée expire (7 jours sans usage, 30 jours d'âge), ou quand une PR sauvegarde sous la clé de l'entrée partagée et l'évince, le runner ne gardant qu'une entrée par clé. La liste complète est en tête de check-rust.yml ; seul un délai dépassé signale un retour de #234. Pas de cache npm : check-frontend ne tourne que sur des PR, et npm ci y prend 9 s. Le miroir .github/workflows/ est dormant (aucune PR côté GitHub).
Ressources clés
- Tauri v2 Docs
- React Docs
- SQLite via Tauri
- Architecture détaillée :
docs/architecture.md - Décisions techniques :
docs/adr/