Adds pending_email column to users table with idempotent migration.
Implements set_pending_email() and promote_pending_email() for email-change workflow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement CLI interface for the backup module with three subcommands:
- backup: create a backup and apply rotation
- list: list available backups
- restore: restore a specific backup
Also add __main__.py to enable 'python -m src.backup'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps temp file operations in try/except to ensure the temporary database file is always cleaned up, even if an exception occurs during write_snapshot or verify_integrity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implémente les trois fonctions de gestion de snapshots SQLite:
- make_snapshot: crée un snapshot gzippé cohérent via sqlite3.Connection.backup()
- write_snapshot: écrit le snapshot décompressé à destination
- verify_integrity: valide l'intégrité d'une base avec PRAGMA integrity_check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implémente la fonction select_retained() pour la rétention multi-paliers:
- Paliers fixes: horaire/12h, 12h/72h, quotidien/21j
- Palier mensuel: 12 mois calendaires
- Fonction pure, pas de dépendances sur d'autres modules backup
Fixes#89
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ajoute les fonctions make_key() et parse_timestamp() pour gérer
le format des clés S3 avec horodatage UTC.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
La connexion singleton partagée entre threads causait sqlite3.InterfaceError
lors de requêtes concurrentes sur la page compte.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remplace les 7 callbacks CSRF individuels (un par formulaire, avec IDs string
page-spécifiques) par un seul dcc.Store(id="csrf-token") dans le layout principal
et un callback pattern-matching Output({"type": "csrf-input", "index": ALL}).
Évite les erreurs Dash "id non trouvé dans le layout" sans recourir à
suppress_callback_exceptions=True.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Le lien de vérification pointait vers la page Dash /verification-email
(qui se contente de rediriger vers /connexion) au lieu de la route
backend /auth/verify-email qui consomme réellement le token et marque
l'email comme vérifié. Conséquence : l'email n'était jamais validé et le
login renvoyait email_not_verified en boucle.
Ajoute aussi MAIL_SUPPRESS_SEND=true au bloc env de pytest : sans cela,
le load_dotenv() de src.app injectait MAIL_SUPPRESS_SEND=false depuis le
.env local, désactivant la suppression d'envoi et faisant échouer les
tests d'auth qui envoient un email (selon l'ordre d'import).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute la variable MAIL_SUPPRESS_SEND pour pouvoir tester l'envoi d'emails
en mode développement sans toucher au flag DEVELOPMENT. Un warning est loggé
à chaque tentative d'envoi supprimée avec la cause et les détails du message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Barre de défilement JS orange (12px) injectée en haut de chaque
.marches_table, collée avec position:sticky, masquée si le tableau
tient dans le viewport
- Scroll contenu dans .dash-spreadsheet-container (overflow-x:hidden)
→ plus de scrollbar navigateur en bas de page
- Drag souris + tactile, clic sur le track, molette/trackpad
- Recalcul automatique au resize et aux re-renders Dash (MutationObserver)
- Test Selenium de non-régression (overflow-x:hidden sur le conteneur)
Sticky headers abandonnés (incompatibles avec scroll contenu).
Portée : 4 pages via la classe partagée .marches_table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le retrait de position:sticky sur .dt-hscroll avait cassé le layout
(bouton Remettre à zéro visible au mauvais endroit, z-index perdu).
La barre redevient sticky top:0 z-index:11 — les th.dash-header restent
sans sticky ni z-index (ils ne fonctionnaient de toute façon pas en
scroll contenu).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Retire position:sticky/z-index sur th.dash-header (abandonnés avec
le scroll contenu : overflow-x:hidden crée un scroll container qui
empêche le sticky page-level, et le z-index provoquait un recouvrement
des champs de filtre)
- Retire la règle :has() devenue inutile
- Barre de scroll réduite à 12px
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace le scroll de page (window.scrollTo) par dashContainer.scrollLeft :
- overflow-x:hidden sur .dash-spreadsheet-container → la table ne déborde
plus la page → scrollbar navigateur en bas éliminée
- overflow-y:clip évite la conversion CSS visible→auto (pas de scrollbar
vertical parasite sur le conteneur)
- metrics(), syncThumb, drag et clic utilisent tous dashContainer
- wheel handler capture les gestures trackpad horizontaux et les redirige
vers dashContainer.scrollLeft
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- adjustBarWidth() brise le padding Bootstrap (margin-left négatif +
width: 100vw) pour que la barre s'étende jusqu'au bord de la fenêtre
- metrics() utilise window.innerWidth comme largeur de piste (cohérent
avec la barre pleine largeur)
- hasOverflow mesure la largeur de .cell-table de CETTE table via
getBoundingClientRect(), plutôt que document.documentElement.scrollWidth
→ les petits tableaux (top10 /acheteur, /titulaire, /observatoire)
n'affichent pas de barre s'ils ne débordent pas individuellement
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remplace le scrollbar natif (invisible en mode overlay sous Linux) par
un thumb div orange toujours visible. Track gris (#e0e0e0) 16px.
Drag souris + toucher + clic sur le track supportés.
Supprime la dépendance aux pseudo-éléments webkit et scrollbar-color.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- overflow-x:scroll force le thumb orange toujours visible (pas overlay)
- hauteur 16px pour le drag-and-drop
- :has(.dt-hscroll:not(.is-hidden)) décale les th à top:16px quand la
barre est présente, évitant le recouvrement
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Abandonne le wrapper overflow-x (incompatible avec position:sticky des
en-têtes). La barre contrôle désormais le scroll horizontal de la page
via window.scrollTo/scrollX, ce qui préserve le sticky des th.
Scrollbar orange 12px, styling webkit + Firefox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Si la garde est posée avant de vérifier que dashContainer existe, un
déclenchement prématuré de rootObs (avant le rendu Dash) marque le
wrapper comme traité sans jamais injecter le scroll wrapper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Évite la boucle infinie : le rootObs se déclenchait sur les insertions
DOM de setup() avant que la garde ne soit posée, relançant setup() en
boucle et faisant monter le CPU.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- I-1: Add explicit lower bounds to width assertions (>= 15 for autre, >= 40 for objet)
- I-2: Wrap write_excel() and close() in try/finally to ensure cleanup even on exception
- M-2: Add openpyxl to dev dependencies in pyproject.toml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>