Simpl-Resultat/docs/architecture.md
le king fu cd0a2b826f feat(balance): starter accounts + opt-in modal + ADR 0012
Part 1 — New profiles: seed 4 starter accounts in
consolidated_schema.sql (Compte chèque/CELI/REER/Compte
non-enregistré, currency CAD, is_active=1) right after the
balance_categories seeds. Categories resolved via SELECT subquery
on the seeded `key` values for robustness.

Part 2 — Existing profiles: StarterAccountsModal proposes the same
4 starters at first /balance visit. Default-checked checkboxes,
collision rule (case-insensitive trim name + matching category)
disables matches with a "Déjà présent" tooltip. The atomic helper
`proposeStarterAccounts` wraps the inserts in BEGIN/COMMIT (rolls
back on error). user_preferences.balance_starter_proposed records
{shown_at, accepted} so the modal never reappears, dismissed or
confirmed.

Part 3 — docs/adr/0012-balance-two-level-model.md (Proposed):
captures the future vehicles × compositions model for reflection,
no code change. Numbered 0012 because 0011 was already taken by
the providers-best-effort-yahoo ADR. Linked from architecture.md
ADR table and Bilan section.

Tests: StarterAccountsModal.test.tsx covers STARTER_ACCOUNTS shape,
getStarterCollisions (case-insensitive trim, category-scoped) and
proposeStarterAccounts (insert order, COMMIT, ROLLBACK on failure).
No render tests — mirrors the BalanceOnboardingCard pattern (no
jsdom configured).

Resolves #179
2026-05-02 11:59:45 -04:00

30 KiB
Raw Blame History

Architecture technique — Simpl'Résultat

Document mis à jour le 2026-04-25 — Version 0.8.x (Bilan)

Stack technique

Couche Technologie Version
Framework desktop Tauri v2
Frontend React 19.1
Langage frontend TypeScript 5.8
Bundler Vite 6.4
CSS Tailwind CSS v4
Backend Rust (via Tauri) stable
Base de données SQLite (tauri-plugin-sql)
Graphiques Recharts 3.7
Icônes Lucide React 0.563
i18n i18next + react-i18next 25.8 / 16.5
Drag & Drop @dnd-kit 6.3 / 10.0
CSV PapaParse 5.5
Chiffrement aes-gcm (Rust) 0.10
Hachage PIN Argon2 (Rust) 0.5

Structure du projet

simpl-resultat/
├── src/                          # Frontend React/TypeScript
│   ├── components/               # 58 composants organisés par domaine
│   │   ├── adjustments/          # 3 composants
│   │   ├── balance/              # 8 composants Bilan (AccountForm, BalanceAccountsTable, BalanceEvolutionChart, BalanceOnboardingCard, BalanceOverviewCard, LinkTransfersModal, SnapshotEditor, SnapshotLineRow)
│   │   ├── budget/               # 5 composants
│   │   ├── categories/           # 5 composants
│   │   ├── dashboard/            # 2 composants
│   │   ├── import/               # 13 composants (wizard d'import)
│   │   ├── layout/               # AppShell, Sidebar
│   │   ├── profile/              # 3 composants (PIN, formulaire, switcher)
│   │   ├── reports/              # ~25 composants (hub, faits saillants, tendances, comparables, zoom catégorie)
│   │   ├── settings/             # 5 composants (+ LogViewerCard, LicenseCard, AccountCard)
│   │   ├── shared/               # 6 composants réutilisables
│   │   └── transactions/         # 5 composants
│   ├── contexts/                 # ProfileContext (état global profil)
│   ├── hooks/                    # 18+ hooks custom (useReducer, 5 hooks rapports par domaine)
│   ├── pages/                    # 14 pages (dont 4 sous-pages rapports)
│   ├── services/                 # 14 services métier
│   ├── shared/                   # Types et constantes partagés
│   ├── utils/                    # 4 utilitaires (parsing, CSV, charts)
│   ├── i18n/                     # Config i18next + locales FR/EN
│   ├── App.tsx                   # Router principal
│   └── main.tsx                  # Point d'entrée
├── src-tauri/                    # Backend Rust
│   ├── src/
│   │   ├── commands/             # 6 modules de commandes Tauri
│   │   │   ├── fs_commands.rs
│   │   │   ├── export_import_commands.rs
│   │   │   ├── profile_commands.rs
│   │   │   ├── license_commands.rs
│   │   │   ├── auth_commands.rs
│   │   │   └── entitlements.rs
│   │   ├── database/             # Schémas SQL et migrations
│   │   │   ├── schema.sql
│   │   │   ├── seed_categories.sql
│   │   │   └── consolidated_schema.sql
│   │   ├── lib.rs                # Point d'entrée, migrations, plugins
│   │   └── main.rs
│   ├── capabilities/             # Permissions Tauri
│   └── Cargo.toml
├── .github/workflows/            # CI/CD
│   └── release.yml
├── docs/                         # Documentation technique
└── config/                       # Configuration

Base de données

Tables (18)

Table Description
import_sources Configuration des sources d'import CSV
imported_files Suivi des fichiers importés (hash anti-doublons)
categories Catégories hiérarchiques (dépenses/revenus)
suppliers Fournisseurs avec auto-catégorisation
keywords Mots-clés pour catégorisation automatique
transactions Transactions individuelles
adjustments Ajustements manuels (ponctuels ou récurrents)
adjustment_entries Montants par catégorie pour chaque ajustement
budget_entries Allocations budgétaires mensuelles par catégorie
budget_templates Modèles de budget réutilisables
budget_template_entries Catégories et montants dans les modèles
import_config_templates Modèles prédéfinis de config d'import
user_preferences Préférences applicatives (clé-valeur)
balance_categories Taxonomie des types d'actifs (cash, TFSA, RRSP, fund, stock, crypto, other) — kind ∈ {simple, priced}, 7 seedées (is_seed = 1)
balance_accounts Comptes de bilan (rattachés à une catégorie). currency hardcodée à CAD au MVP via CHECK. archived_at pour soft-delete. Issue #179 : 4 comptes de départ (Compte chèque, CELI, REER, Compte non-enregistré) seedés pour les nouveaux profils via consolidated_schema.sql, et proposés aux profils existants via StarterAccountsModal (one-shot, pref balance_starter_proposed). Le futur passage à un modèle véhicule × composition est décrit dans ADR 0012 (Proposed)
balance_snapshots Snapshots datés (snapshot_date UNIQUE) — éditer = mettre à jour les lignes, pas dupliquer
balance_snapshot_lines Une ligne par (snapshot, compte). Stockage denormalisé : pour simple value seul, pour priced quantity + unit_price + value. CHECK kind invariants côté SQL
balance_account_transfers Liaison transactions ↔ balance_accounts avec direction ∈ {in, out}. Utilisée par le calcul Modified Dietz pour séparer apports et gains

Index (16)

Index existants (9) : transactions (date, category, supplier, source, file, parent), categories (parent, type), suppliers (category, normalized_name), keywords (category, keyword), budget_entries (year, month), adjustment_entries (adjustment_id), imported_files (source).

Index Bilan (7, ajoutés en migration v9) :

  • idx_balance_accounts_category (FK lookup catégorie → comptes)
  • idx_balance_accounts_active partiel WHERE is_active = 1 (filtre liste active)
  • idx_balance_snapshot_lines_snapshot (chargement d'un snapshot)
  • idx_balance_snapshot_lines_account (historique par compte)
  • idx_balance_account_transfers_account (cash flows Modified Dietz par compte)
  • idx_balance_account_transfers_transaction (lookup icône d'attribution dans TransactionTable)
  • idx_balance_snapshots_date (sélecteur de période + agrégation chronologique)

Invariants Bilan (CHECK + FK)

  • balance_categories.kind('simple','priced')
  • balance_accounts.currency = 'CAD' (verrou MVP — v2 lèvera ce CHECK avec table de taux)
  • balance_snapshot_lines : (quantity, unit_price) doivent être tous deux NULL (kind simple) OU tous deux NOT NULL (kind priced)
  • balance_account_transfers.direction('in','out') ; UNIQUE (transaction_id, account_id) (une transaction ne peut pas être liée deux fois au même compte)
  • FK balance_accounts.balance_category_idbalance_categories(id) ON DELETE RESTRICT (empêche suppression de catégorie avec comptes liés)
  • FK balance_snapshot_lines.snapshot_idbalance_snapshots(id) ON DELETE CASCADE (supprimer un snapshot supprime ses lignes)
  • FK balance_snapshot_lines.account_idbalance_accounts(id) ON DELETE RESTRICT (préserve l'historique)
  • FK balance_account_transfers.account_idbalance_accounts(id) ON DELETE CASCADE
  • FK balance_account_transfers.transaction_idtransactions(id) ON DELETE RESTRICT — décision structurante pour la reproductibilité Modified Dietz, voir ADR 0010

Système de migrations

Les migrations sont définies inline dans src-tauri/src/lib.rs via tauri_plugin_sql::Migration :

# Version Description
1 v1 Schéma initial (13 tables)
2 v2 Seed des catégories et mots-clés
3 v3 Ajout has_header sur import_sources
4 v4 Ajout is_inputable sur categories
5 v5 Création de import_config_templates
6 v6 Changement contrainte unique imported_files (hash → filename)
7 v7 Ajout sous-catégories d'assurance (niveau 3)
8 v8 Migration de catégories (cf. release 0.8.x)
9 v9 Schéma Bilan : 5 tables + 7 index + seed des 7 catégories standard (cash, TFSA, RRSP, fund, stock, crypto, other)

Pour les nouveaux profils, le fichier consolidated_schema.sql contient le schéma complet avec toutes les migrations pré-appliquées (pas besoin de rejouer les migrations).

Services TypeScript (18)

Service Responsabilité
db.ts Wrapper de connexion (tauri-plugin-sql)
profileService.ts Gestion des profils
categoryService.ts CRUD catégories hiérarchiques
transactionService.ts CRUD et filtrage des transactions ; détection d'erreurs FK RESTRICT pour transactions liées à un compte de bilan (typed TransactionLinkedToBalanceError)
importSourceService.ts Configuration des sources d'import
importedFileService.ts Suivi des fichiers importés
importConfigTemplateService.ts Modèles de configuration d'import
categorizationService.ts Catégorisation automatique + helpers édition de mot-clé (validateKeyword, previewKeywordMatches, applyKeywordWithReassignment)
adjustmentService.ts Gestion des ajustements
budgetService.ts Gestion budgétaire
dashboardService.ts Agrégation données tableau de bord
reportService.ts Génération de rapports : getMonthlyTrends, getCategoryOverTime, getHighlights, getCompareMonthOverMonth, getCompareYearOverYear, getCategoryZoom (CTE récursive bornée anti-cycle), getCartesSnapshot (snapshot dashboard Cartes, requêtes parallèles)
dataExportService.ts Export de données (chiffré)
userPreferenceService.ts Stockage préférences utilisateur
logService.ts Capture des logs console (buffer circulaire, sessionStorage)
licenseService.ts Validation et gestion de la clé de licence (appels commandes Tauri)
authService.ts OAuth2 PKCE / Compte Maximus (appels commandes Tauri auth_*)
balance.service.ts Domaine Bilan — service unique avec 4 sections logiques (voir détail ci-dessous)

Service Bilan — balance.service.ts

Un seul service par convention projet (1 service par domaine, splitter seulement > ~400 lignes). Quatre sections logiques distinctes :

  1. CRUD catégories + compteslistBalanceCategories, createBalanceCategory, updateBalanceCategory, archiveBalanceCategory (refus si comptes liés via FK RESTRICT, refus si is_seed = 1), listBalanceAccounts, createBalanceAccount, updateBalanceAccount, archiveBalanceAccount. Le service garde une BalanceServiceError typée (BalanceErrorCode) pour permettre à la UI d'afficher des messages i18n distincts (currency_unsupported, category_seed_protected, category_has_accounts, etc.).
  2. Snapshots + lineslistBalanceSnapshots, getBalanceSnapshotByDate, upsertSnapshot (création + édition par date), upsertSnapshotLines (rewrite-all : DELETE WHERE snapshot_id puis INSERT par ligne — choix simple pour < 20 comptes/snapshot), deleteSnapshot, helper validateLineKindInvariants exporté pour les tests (kind invariants TS en complément du CHECK SQL ; tolérance PRICED_VALUE_TOLERANCE = 0.01 pour le match value ≈ quantity × unit_price).
  3. Returns + transferslinkTransfer, unlinkTransfer, listAccountTransfers, listAllLinkedTransfersForTooltip (un coup pour la Map.has(txId) consommée par l'icône d'attribution dans TransactionTable), computeAccountReturn (wrapper sur la commande Tauri compute_account_return qui lit db_filename du profil actif via loadProfiles()).
  4. Prices(Phase 5, livraison reportée à l'Issue #143). La forme prévue : fetchPrice(symbol, date) invoquant fetch_price (Tauri), avec rate-limit client (1/2s), backoff exponentiel et dedup in-flight. Voir ADR 0009 pour l'architecture proxy.

Le CRUD passe par getDb() + tauri-plugin-sql direct, jamais via une commande Tauri — convention projet. Les commandes Rust sont réservées au filesystem, OAuth, license, profils, feedback et au seul calcul Modified Dietz (qui a besoin d'arithmétique de dates chrono).

Hooks (17+)

Chaque hook encapsule la logique d'état via useReducer :

Hook Domaine
useCategories Catégories avec hiérarchie
useTransactions Transactions et filtrage
useDataImport Import de données
useImportWizard Assistant d'import multi-étapes
useImportHistory Historique des imports
useAdjustments Ajustements
useBudget Budget
useDashboard Métriques du tableau de bord
useReportsPeriod Période de reporting synchronisée via query string (bookmarkable)
useHighlights Panneau de faits saillants du hub rapports
useTrends Rapport Tendances (sous-vue flux global / par catégorie)
useCompare Rapport Comparables (mode actual/budget, sous-toggle MoM ↔ YoY, mois de référence explicite avec wrap-around janvier)
useCategoryZoom Rapport Zoom catégorie avec rollup sous-catégories
useCartes Rapport Cartes (snapshot KPI + sparklines + top movers + budget + saisonnalité via getCartesSnapshot)
useBalanceAccounts Bilan — état de la page /balance/accounts : CRUD comptes ET catégories (un seul hook pour les deux onglets, aligné sur la convention "1 hook par page")
useSnapshotEditor Bilan — cycle de vie d'un snapshot unique (/balance/snapshot) : valeurs simple (string) + valeurs priced ({quantity, unit_price} strings), prefill depuis snapshot précédent, save (rewrite-all), delete avec double-confirmation par re-saisie de la date
useBalanceOverview Bilan — page /balance : sélecteur de période (3M / 6M / 1A / 3A / Tout), série temporelle agrégée, mode chart (line / stacked), tableau des comptes avec valeurs courantes et Δ% sur la période. Les rendements multi-horizons sont chargés lazily dans BalanceAccountsTable (un appel compute_account_return par cellule)
useDataExport Export de données
useTheme Thème clair/sombre
useUpdater Mise à jour de l'application (gated par entitlement licence)
useLicense État de la licence et entitlements
useAuth Authentification Compte Maximus (OAuth2 PKCE, subscription status)

Commandes Tauri (36)

fs_commands.rs — Système de fichiers (6)

  • scan_import_folder — Scan récursif de dossier pour fichiers CSV/TXT
  • read_file_content — Lecture avec gestion de l'encodage
  • hash_file — Hash SHA-256 (détection de doublons)
  • detect_encoding — Détection auto (UTF-8, Windows-1252, ISO-8859-15)
  • get_file_preview — Aperçu des N premières lignes
  • pick_folder — Dialogue de sélection de dossier

export_import_commands.rs — Export/Import de données (5)

  • pick_save_file — Dialogue de sauvegarde
  • pick_import_file — Dialogue de sélection de fichier
  • write_export_file — Écriture fichier chiffré (format SREF)
  • read_import_file — Lecture fichier chiffré
  • is_file_encrypted — Vérification magic SREF

profile_commands.rs — Gestion des profils (7)

  • load_profiles — Chargement depuis profiles.json
  • save_profiles — Sauvegarde de la configuration
  • delete_profile_db — Suppression du fichier de base de données
  • get_new_profile_init_sql — Récupération du schéma consolidé
  • hash_pin — Hachage Argon2id du PIN (format argon2id:salt:hash)
  • verify_pin — Vérification du PIN (supporte Argon2id et legacy SHA-256 pour rétrocompatibilité)
  • repair_migrations — Réparation des checksums de migration (rusqlite)

license_commands.rs — Licence et activation machine (10)

  • validate_license_key — Validation offline d'une clé de licence (JWT Ed25519)
  • store_license — Stockage de la clé dans le répertoire app data
  • store_activation_token — Stockage du token d'activation
  • read_license — Lecture de la licence stockée
  • get_edition — Détection de l'édition active (free/base/premium)
  • get_machine_id — Génération d'un identifiant machine unique
  • activate_machine — Activation en ligne (appel API serveur de licences, issue #49)
  • deactivate_machine — Désactivation d'une machine enregistrée
  • list_activated_machines — Liste des machines activées pour la licence
  • get_activation_status — État d'activation de la machine courante

auth_commands.rs — Compte Maximus / OAuth2 PKCE (5)

  • start_oauth — Génère un code verifier PKCE et retourne l'URL d'authentification Logto
  • refresh_auth_token — Rafraîchit l'access token via le refresh token
  • get_account_info — Lecture du cache d'affichage (via account_cache::load_unverified, accepte les payloads legacy)
  • check_subscription_status — Vérifie l'abonnement (max 1×/jour, fallback cache gracieux). Déclenche aussi la migration tokens.json → keychain via token_store::load
  • logout — Efface tokens (token_store) + cache signé (account_cache) + clé HMAC du keychain

Note : handle_auth_callback n'est PAS exposée comme commande — elle est appelée depuis le handler deep-link on_open_url dans lib.rs. Voir section "OAuth2 et deep-link" plus bas.

token_store.rs — Stockage des tokens OAuth (1)

  • get_token_store_mode — Retourne "keychain", "file" ou null. Utilisé par la bannière de sécurité TokenStoreFallbackBanner dans Settings pour alerter l'utilisateur quand les tokens sont dans le fallback fichier.

Module non-command : save, load, delete, store_mode — toute la logique de persistance passe par ce module, auth_commands.rs ne touche jamais directement tokens.json. Voir l'ADR 0006 pour la conception complète.

account_cache.rs — Cache d'abonnement signé (aucune commande)

Module privé appelé uniquement par auth_commands.rs et license_commands.rs. Expose :

  • save(app, &AccountInfo) — écrit l'enveloppe signée {data, sig} dans account.json, avec clé HMAC-SHA256 stockée dans le keychain.
  • load_unverified(app) — lecture pour affichage UI (accepte legacy et signé).
  • load_verified(app) — lecture pour gating licence (refuse legacy, tampering, absence de clé). Utilisé par license_commands::check_account_edition.
  • delete(app) — efface le fichier et la clé HMAC du keychain.

entitlements.rs — Entitlements (1)

  • check_entitlement — Vérifie si une feature est autorisée selon l'édition
    • Source de vérité : FEATURE_TIERS dans entitlements.rs. Modifier cette constante pour changer les gates, jamais ailleurs dans le code
    • Temporaire : auto-update est ouvert à free en attendant le serveur de licences (issue #49). À re-gater à [base, premium] quand l'activation payante sera live

balance_commands.rs — Bilan (1)

  • compute_account_return(account_id, period_start, period_end, db_filename) — Calcul Modified Dietz d'un compte sur une période. Ouvre une connexion rusqlite courte sur le fichier DB du profil actif, lit le snapshot ≤ period_start, le snapshot ≥ period_end et tous les balance_account_transfers JOIN transactions dans la fenêtre, puis appelle return_calculator::modified_dietz. Retourne AccountReturn { value_start, value_end, net_contributions, return_pct, annualized_pct, is_partial, has_no_transfers_warning }. Voir ADR 0008.

Le module privé return_calculator.rs (déclaré dans commands/mod.rs mais non exposé comme commande) contient la logique pure Modified Dietz et ses tests #[cfg(test)] mod tests co-localisés (TDD, 7 cas : nominal / pas de snapshot début / partial / créé en cours / vidé / sans transferts / annualisation).

À venir Phase 5 (Issue #143, BLOCKED par maximus-api Phase 2) : commande fetch_price(symbol, date) pour le price-fetching premium via proxy maximus-api. L'architecture est documentée dans l'ADR 0009 ; la livraison est différée jusqu'à ce que le serveur de licences (maximus-api) expose l'endpoint GET /v1/prices.

Plugins Tauri

Ordre d'initialisation dans lib.rs (certains plugins ont des contraintes d'ordre) :

Plugin Rôle Contrainte
tauri-plugin-single-instance Empêche les doubles lancements et forwarde les URLs deep-link au processus existant Doit être le premier plugin ; feature deep-link requise pour le forwarding d'URL
tauri-plugin-opener Ouverture d'URLs externes et de fichiers
tauri-plugin-dialog Dialogues de sélection de fichier/dossier
tauri-plugin-process Relaunch après mise à jour
tauri-plugin-deep-link Gère le scheme custom simpl-resultat:// Doit être initialisé avant setup() pour que on_open_url soit disponible
tauri-plugin-updater Mise à jour auto (gated par entitlement auto-update) Initialisé dans setup() derrière #[cfg(desktop)]
tauri-plugin-sql SQLite + migrations Doit être initialisé avec les migrations pour que le schéma soit prêt

Flow complet (v0.7.3+) :

  1. Frontend appelle start_oauth → génère un code verifier PKCE (64 chars), le stocke dans OAuthState (Mutex en mémoire du processus), retourne l'URL Logto
  2. Frontend ouvre l'URL via tauri-plugin-opener → le navigateur système affiche la page Logto
  3. L'utilisateur s'authentifie (ou Logto auto-consent si session existante) → redirection 303 vers simpl-resultat://auth/callback?code=...
  4. L'OS route le custom scheme vers une nouvelle instance de l'app → tauri-plugin-single-instance (feature deep-link) détecte l'instance existante, ne démarre PAS un nouveau processus, et forwarde l'URL à l'instance vivante
  5. Le callback app.deep_link().on_open_url(...) enregistré via DeepLinkExt reçoit les URLs. Pour chaque URL :
    • Si un param code est présent → appelle handle_auth_callback (token exchange vers /oidc/token, fetch /oidc/me, écriture des tokens via token_store::save (keychain OS, fallback fichier 0600) + cache signé via account_cache::save (HMAC-SHA256), émission de l'event auth-callback-success)
    • Si un param error est présent → émission de l'event auth-callback-error avec error: error_description
  6. Le hook useAuth (frontend) écoute auth-callback-success / auth-callback-error et met à jour l'état

Pourquoi cet enchaînement est critique :

  • Sans tauri-plugin-single-instance : une nouvelle instance démarre à chaque callback, le OAuthState est vide (pas de verifier), le token exchange échoue
  • Sans on_open_url : l'ancien listener app.listen("deep-link://new-url", ...) ne recevait pas les URLs forwardées par single-instance. L'API canonique v2 via DeepLinkExt est nécessaire
  • Sans gestion des erreurs : un callback ?error=... laissait l'UI bloquée en état "loading" infini

Fichiers : src-tauri/src/lib.rs (wiring), src-tauri/src/commands/auth_commands.rs (PKCE + token exchange), src-tauri/src/commands/token_store.rs (persistance keychain + fallback), src-tauri/src/commands/account_cache.rs (cache signé HMAC), src/hooks/useAuth.ts (frontend), src/components/settings/TokenStoreFallbackBanner.tsx (UI de l'état dégradé).

Pages et routing

Le routing est défini dans App.tsx. Toutes les pages sont englobées par AppShell (sidebar + layout). L'accès est contrôlé par ProfileContext (gate).

Gestion d'erreurs

  • ErrorBoundary (class component) : wrape <App /> dans main.tsx, attrape les crashs React et affiche ErrorPage en fallback
  • ErrorPage : page d'erreur réutilisable avec détails techniques (collapsible), bouton "Actualiser", vérification de mises à jour, et liens de contact/issues
  • Timeout au démarrage : App.tsx applique un timeout de 10 secondes sur connectActiveProfile() — affiche ErrorPage au lieu d'un spinner infini si la connexion DB échoue
  • Retry au démarrage : connectActiveProfile() réessaie jusqu'à 3 fois avec 1s de délai avant d'afficher l'erreur
  • Réparation de migrations : repair_migrations (Rust/rusqlite) supprime les checksums invalides de _sqlx_migrations avant le chargement de la DB
  • Log viewer : logService.ts capture les console.log/warn/error dans un buffer circulaire (500 entrées, persisté en sessionStorage), affiché dans la page Paramètres via LogViewerCard
Route Page Description
/ DashboardPage Tableau de bord (résumé, pie chart, budget vs réel, dépenses dans le temps)
/import ImportPage Assistant d'import CSV
/transactions TransactionsPage Liste avec filtres
/categories CategoriesPage Gestion hiérarchique
/adjustments AdjustmentsPage Ajustements manuels
/budget BudgetPage Planification budgétaire
/reports ReportsPage Hub des rapports : panneau faits saillants + 4 cartes de navigation
/reports/highlights ReportsHighlightsPage Faits saillants détaillés (soldes, top mouvements, top transactions)
/reports/trends ReportsTrendsPage Tendances (flux global + par catégorie)
/reports/compare ReportsComparePage Comparables (MoM / YoY / Réel vs budget)
/reports/category ReportsCategoryPage Zoom catégorie avec rollup + édition contextuelle de mots-clés
/reports/cartes ReportsCartesPage Tableau de bord KPI avec sparklines, top movers, budget et saisonnalité
/balance BalancePage Bilan — vue d'ensemble : carte "Aujourd'hui" + Δ% + avertissement bilan pas à jour > 60j, graphique d'évolution (toggle ligne / aire empilée par catégorie), tableau des comptes avec rendements multi-horizons (3M / 1A / depuis création — Modified Dietz) côte-à-côte avec rendement non-ajusté
/balance/snapshot SnapshotEditPage Saisie / édition d'un snapshot daté. Mode ?date=today (création) ou ?date=YYYY-MM-DD (édition, date immutable). Lignes groupées par catégorie : simple = champ valeur, priced = quantity × unit_price (value calculé read-only). Bouton "Pré-remplir depuis le snapshot précédent". Suppression à double-confirmation par re-saisie de la date
/balance/accounts AccountsPage CRUD comptes + catégories de bilan (deux onglets). Catégories seedées (is_seed = 1) renommables mais non-supprimables ; refus de suppression d'une catégorie avec comptes liés (FK RESTRICT)
/settings SettingsPage Paramètres
/docs DocsPage Documentation in-app
/changelog ChangelogPage Historique des versions (bilingue FR/EN)

Page spéciale : ProfileSelectionPage (affichée quand aucun profil n'est actif).

Internationalisation

  • Librairie : i18next + react-i18next
  • Langue par défaut : Français (fr)
  • Langue de fallback : Anglais (en)
  • Fichiers : src/i18n/locales/fr.json, src/i18n/locales/en.json
  • Clés organisées hiérarchiquement par domaine (nav.*, dashboard.*, import.*, etc.)

CI/CD

Deux workflows Forgejo Actions (avec miroir GitHub) dans .forgejo/workflows/ :

check.yml — Vérifications sur branches et PR

Déclenché sur chaque push de branche (sauf main) et chaque PR vers main. Lance en parallèle :

  • cargo check + cargo test (Rust)
  • npm run build (tsc + vite)
  • npm test (vitest)

Doit être vert avant tout merge. Évite de découvrir des régressions au moment du tag de release.

release.yml — Build et publication

Déclenché par les tags v*. Deux jobs :

  1. build-windows (windows-latest) → Installeur .exe (NSIS)
  2. build-linux (ubuntu-22.04) → .deb + .rpm

Fonctionnalités :

  • Signature des binaires (clés TAURI_SIGNING_PRIVATE_KEY)
  • JSON d'updater publié sur https://git.lacompagniemaximus.com/api/packages/maximus/generic/simpl-resultat/latest/latest.json
  • Release Forgejo automatique avec assets et release notes extraites du CHANGELOG.md

Architecture Decision Records (ADRs)

Les ADRs documentent les décisions techniques structurantes. Ils vivent dans docs/adr/.

# Titre Date Statut
0001 Choix de Tauri v2 comme framework desktop 2024-01-01 Accepted
0002 useReducer plutôt que Redux 2024-01-01 Accepted
0003 Migrations SQL inline via tauri-plugin-sql 2024-01-01 Accepted
0004 Chiffrement AES-256-GCM pour l'export 2024-01-01 Accepted
0005 Multi-profils avec bases SQLite séparées 2024-01-01 Accepted
0006 Stockage des tokens OAuth via keychain 2024-01-01 Accepted
0007 Refactorisation du hub de rapports 2024-01-01 Accepted
0008 Modified Dietz pour le calcul de rendement 2025-01-01 Accepted
0009 Proxy price-fetching via maximus-api 2025-01-01 Accepted
0010 FK RESTRICT sur balance_account_transfers 2025-01-01 Accepted
0011 Providers best-effort Yahoo 2026-04-26 Accepted
0012 Modèle à deux niveaux pour le Bilan (véhicules × compositions) 2026-05-01 Proposed