bringToFront() sur un circleMarker SVG (overlayPane) ne pouvait pas passer
devant les bulles de cluster (markerPane, toujours au-dessus dans l'ordre
des panes Leaflet), et le rendu SVG se redimensionnait visuellement pendant
l'animation de zoom. Le point is_home est maintenant un L.marker + divIcon
(comme les clusters), avec zIndexOffset élevé : toujours au premier plan et
taille constante quel que soit le zoom.
Marque is_home=True sur les marqueurs du home_type (acheteur ou titulaire
selon la page) dans get_org_location_map. Côté client, pointToLayer donne
à ce point un rayon légèrement plus grand (8 vs 5) et le ramène toujours
au premier plan (bringToFront), indépendamment de l'ordre des couches.
Un fitBounds animé interrompu (deux Input sur pathname+année pouvant
re-déclencher le callback quasi simultanément) laissait le transform CSS
de zoom de Leaflet figé à une grande échelle (ex: scale(16)), faisant
apparaître les marqueurs individuels démesurément gros sur /acheteurs et
/titulaires. Constaté en vérification manuelle sur test.colibre.fr.
Le commentaire affirmait à tort que l'organisme consulté est toujours
peint au-dessus de sa contrepartie ; l'ordre est en réalité fixe
(titulaire puis acheteur), indépendant de home_type. Relevé par la
revue finale de branche.
When switching tables, the table-switch branch writes fresh data for the
new table, which re-fires the same callback with data_previous still
holding the old table's rows. find_changed_cell only checked row count
before diffing, so if the two tables happened to have the same number of
rows it would zip mismatched-schema dicts and report a spurious changed
cell (usually the PK column), producing a confusing red alert right after
switching tables.
Wires is_admin(), SUBSCRIPTION_STATUSES/get_current/set_status, and
log_action() into POST /admin/actions/subscription-status: validates
the requested status and that subscription_id matches the user's
current subscription, applies the change, and logs an audit entry.
Registers the admin blueprint in init_auth() and documents ADMIN_EMAIL
in .template.env.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implement access control function for admin panel. Returns True only if
ADMIN_EMAIL env var is set, user is authenticated, and email matches
case-insensitively.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds four new DB helpers for the admin panel:
- SUBSCRIPTION_STATUSES: tuple of valid subscription statuses
- list_by_user(): retrieve all subscriptions for a user (newest first)
- set_status(): override a subscription's status and updated_at timestamp
- get_subscriber_state(): public wrapper for internal _get_state()
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements list_users(limit: int = 1000) -> list[sqlite3.Row] in the auth
DB layer to retrieve all users ordered by creation date (most recent first).
- Returns all users, optionally capped at limit (default 1000)
- Orders by created_at DESC to show newest users first
- Follows existing DB layer patterns using get_conn().execute().fetchall()
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
hosted_page_links.payment_info (POST /v1/subscription) n'honore pas
accept_url/cancel_url malgré la doc, même passés en query string (constaté
en test sur test.colibre.fr). On génère maintenant la page de paiement via
POST /v1/session/subscription (Checkout API), qui accepte ces champs dans
son body et redirige effectivement le navigateur — même mécanisme déjà
fonctionnel pour l'ajout de moyen de paiement (create_recurring_session).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_show_active_view affichait par erreur la vue "abonnement actif" (avec un
faux "Prochaine facturation") pour un abonnement expiré. Un abonnement
expiré bascule maintenant sur l'écran de re-souscription, avec une alerte
dédiée. Jamais remarqué jusqu'ici car les webhooks Frisbii ne remontaient
pas ce changement de statut.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
accept_url/cancel_url n'existent pas dans le schéma de POST /v1/subscription
(ils y sont silencieusement ignorés) : Frisbii attend ces paramètres en
query string sur le lien hosted_page_links.payment_info retourné. Ajout
d'un warning loggé sur signature de webhook invalide, jusque-là silencieuse.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Les pages acheteur/titulaire chargeaient l'intégralité des marchés d'une
organisation dans un dcc.Store côté client (jusqu'à 16k lignes pour les
plus gros acheteurs), envoyée sur le réseau à chaque interaction. Le
tableau, le top 10 et l'histogramme des distances récupèrent maintenant
leurs données via des requêtes DuckDB scopées (pagination/agrégation
poussées en SQL), sur le modèle déjà utilisé par la page tableau.
Corrige aussi le CLS des mêmes pages en réservant l'espace des
conteneurs remplis par callback (carte, top 10, histogramme).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adjust the width of the 'S'abonner' button to match the label size
using CSS width: fit-content property.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- robots.txt déclare désormais le sitemap
- /sitemap.xml devient un index de sous-sitemaps paginés (limite 50k URLs/fichier)
générés depuis DuckDB et mis en cache 24h : pages statiques, acheteurs, titulaires
- balise canonical auto-référente (JS, car index_string Dash partagé)
- config nginx de référence : 301 decp.info → colibre.fr (chemin préservé)
- tests SEO via test client Flask
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rename project from decp.info to colibre across all codebase
- Update domain from https://decp.info to https://colibre.fr
- Update GitHub repo references to ColinMaudry/colibre
- Rename deployment files: decpinfo-backup.* → colibre-backup.*
- Update project configuration and documentation
- Rename project assets: decp.info.png → colibre.png
- Update environment variables and constants (DOMAIN_NAME, TOKEN_PREFIX, GITHUB_REPO, etc.)
- Update URLs in all pages, tests, and configuration files
- Keep DECP acronym in text (unchanged per requirements)
- Add rebrand note to README.md
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
src/db.py : le remplacement des noms d'organisation nuls utilisait
.name.keep() sur une expression concat_str référençant *_id ; Polars nommait
alors le résultat d'après *_id, écrasant la colonne titulaire_id/acheteur_id et
laissant *_nom inchangée. Remplacé par .alias(col).
Suite de tests — élimination de la pollution inter-tests (callbacks/conn
globaux Dash) qui faisait échouer ~15 tests Selenium en exécution complète :
- tests/conftest.py : DUCKDB_PATH et DATA_SCHEMA_CACHE pointent sur des chemins
de test isolés (tests/decp.duckdb, tests/schema.cache.json, gitignorés) — on
ne touche plus jamais aux fichiers versionnés decp.duckdb et
schema.fixture.json (qui étaient mutés et cassaient la collecte au run
suivant). Viewport Chrome élargi à 1600px.
- tests/test_db.py : fixture module-scoped qui recharge src.db après le module
(test_query_marches faisait importlib.reload vers une DB temporaire au schéma
réduit, polluant le conn global → ColumnNotFoundError ensuite). Assertion mise
à jour pour le nouveau libellé "[Inconnu de l'INSEE (...)]".
Tests périmés / fragiles corrigés :
- tests/auth/test_oauth_routes.py : mock LinkedIn aligné sur la route (userinfo
récupéré via .get() séparé) ; destination post-login /compte/abonnement.
- tests/test_main.py : test_002 filtre sur dateNotification (uid retiré du
tableau), scrollIntoView (colonne hors viewport), attentes de l'application du
filtre et de la restauration de la persistance ; test_015 via
wait_for_no_elements.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>