Commit Graph

1170 Commits

Author SHA1 Message Date
Colin Maudry f84ca73b04 fix(abonnement): erreurs console JS et re-render en boucle sur la page mes-infos
Le label du checklist CGU mélangeait des chaînes brutes et un composant
html.A dans une liste, ce que le rendu compound-label de dcc.Checklist
ne supporte pas (chaque item passe par ExternalWrapper qui suppose un
vrai composant Dash). Ça crashait en boucle et déclenchait des dizaines
de re-render, ce qui faisait aussi clignoter le lien Abonnement dans le
menu latéral. Fix : tout regrouper dans un seul html.Span.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 23:38:29 +02:00
Colin Maudry a225dbb0ef Normalisation du style des boutons 2026-07-13 23:24:27 +02:00
Colin Maudry 34f10f2283 Tooltips abonnement sur Sauvegarder vue et Mes vues 2026-07-13 23:04:48 +02:00
Colin Maudry 8b9433b7f2 Amélioration de la modale de renommage des vues 2026-07-13 22:50:23 +02:00
Colin Maudry c0d8a40c92 Couleur hover dans les dropdowns 2026-07-13 22:49:56 +02:00
Colin Maudry bee401430b feat(tableau): lien « Gérer mes vues » en tête du dropdown + fix hover dropdown (#112)
- Dropdown « Mes vues » : ajoute en tête un lien « Gérer mes vues » (couleur
  primary) vers /compte/vues, séparé de la liste des vues.
- Corrige l'override de --bs-dropdown-link-hover-bg : Bootstrap 5.3 scope cette
  variable sur .dropdown-menu, donc un override sur :root est masqué. Déplacé
  sur .dropdown-menu.
2026-07-13 22:38:57 +02:00
Colin Maudry 31427a8b3b feat(vues): jeton de partage en minuscules (base36, #112)
Alphabet du jeton passé de base62 (a-z A-Z 0-9) à base36 (a-z 0-9), longueur
inchangée (6). Lien plus lisible/dictable et cohérent avec le slug déjà en
minuscules. Les jetons déjà générés (à casse mixte) restent valides (lookup par
match exact).
2026-07-13 22:17:26 +02:00
Colin Maudry e0828a7e13 feat(vues): bouton « Copier le lien » + lien de partage en texte stylé (#112)
- Bouton de copie explicite (icône copy.svg + « Copier le lien » + retour
  « ✓ Copié ») sur /tableau et /compte/vues, via dcc.Clipboard (children /
  copied_children), à la place de l'icône seule.
- Le lien direct s'affiche en texte (span#share-url-text) plutôt qu'un
  <input> : évite la troncature d'affichage du champ Bootstrap form-control en
  flex (zone de texte bloquée ~218px), montre tout le lien avec retour à la
  ligne, et prend la largeur du lien. Stylé comme un champ (bordure, coins
  arrondis, texte 90%).
- Instructions /compte/vues simplifiées ; E2E adapté (#share-url-text).
2026-07-13 22:08:00 +02:00
Colin Maudry 770b7cbdf8 fix(entity_grid): sortie MATCH unique pour le datasource (dash.set_props)
Le datasource _get_rows des grilles acheteur/titulaire mélangeait une sortie
MATCH (getRowsResponse de la grille pattern-matching) avec deux sorties fixes
(<org>-total / -total-unique). Dash l'interdit : le dev-renderer (mode debug de
run.py) lève « MATCH wildcards must be on the same keys for all Outputs » (4
erreurs : 2 par page). Le callback échoue au dispatch et la grille reste vide.

Correctif : _get_rows n'a plus qu'une sortie MATCH (getRowsResponse) ; les
stores totaux (id fixes) sont alimentés impérativement via dash.set_props.

Test de régression en debug=True (dev-renderer) : sans le correctif la grille ne
charge aucune ligne ; avec, lignes + compteur « N marchés » présents.
2026-07-13 19:40:26 +02:00
Colin Maudry f0a891b73f fix(tableau): griser/désactiver les boutons vues pour les non-abonnés
La barre des vues sauvegardées restait masquée pour les non-abonnés via un
style inline display:none — inopérant car .d-inline-flex est
display:inline-flex !important, la barre était donc toujours visible.

La barre reste désormais visible pour tous ; « Sauvegarder la vue » et
« Mes vues » sont grisés et désactivés (disabled) pour les non-abonnés. Le
gating serveur de save_view (prepare_view_to_save) reste inchangé.
2026-07-13 19:23:03 +02:00
Colin Maudry 3034e911e7 feat(tableau): masquage du bloc de partage piloté par événements AG Grid (#112)
Remplace le verrou de dérive à compteur (suppress-next) par une détection
événementielle : eventListeners AG Grid (filterChanged/sortChanged) →
dashAgGridFunctions.hideShareOnUserAction, qui efface active-view sur action
utilisateur (source != 'api') et ignore l'écho de l'application programmatique.

Le compteur suppress-next souffrait d'une course d'ordonnancement Dash (l'écho
lisait la valeur pré-batch) et le diff de columnState était impraticable (état
réémis bruité). La visibilité du bloc bascule via la classe (d-flex/d-none) et
non le style inline, car .d-flex est display:flex !important.

Task 7 : test E2E de bout en bout (application + masquage sur action).
2026-07-13 18:41:36 +02:00
Colin Maudry 0e0fb32222 feat(compte/vues): Ouvrir via URL courte + bouton copier (#112) 2026-07-13 17:35:53 +02:00
Colin Maudry 2a22951829 feat(tableau): verrou de derive + affichage du bloc de partage (#112) 2026-07-13 17:34:29 +02:00
Colin Maudry 301a8668b3 feat(tableau): resolution ?vue= + bloc URL de partage (#112) 2026-07-13 17:32:48 +02:00
Colin Maudry 439b6f9cdc feat(vues): resolveur pur du parametre ?vue= (#112) 2026-07-13 15:28:54 +02:00
Colin Maudry 3a0d868e93 feat(vues): slugify + build_view_url + parsing du jeton (#112) 2026-07-13 15:23:47 +02:00
Colin Maudry a66af0190d feat(vues): jeton public + get_by_token + backfill (#112) 2026-07-13 15:19:32 +02:00
Colin Maudry 940f0de0a7 Tirets pour le slug, _ pour le token (spec) #112 2026-07-13 15:14:14 +02:00
Colin Maudry ac73f6efc1 docs(vues): plan d'implementation partage par URL courte (#112)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 15:13:30 +02:00
Colin Maudry d2fa17761d feat(mcp): durcir le consentement OAuth (remember-cookie SameSite + CSRF) (#114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 15:07:59 +02:00
Colin Maudry bb2aee85a2 Libellés français pour la pagination #47 2026-07-13 15:02:47 +02:00
Colin Maudry 10770c5d14 docs(vues): design partage de vues par URL courte (#112)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 15:02:06 +02:00
Colin Maudry cc412f459f docs(mcp): documenter APP_BASE_URL/egress pour le connecteur OAuth (#114) 2026-07-13 14:47:41 +02:00
Colin Maudry 03afb8347c feat(mcp): instructions OAuth Claude.ai/ChatGPT sur /compte/mcp (#114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:43:01 +02:00
Colin Maudry 34a8470792 feat(mcp): migrations OAuth/usage + câblage app.py (#114)
Ajoute les migrations 0008-0011 (oauth_clients, oauth_codes,
oauth_tokens, mcp_usage) et câble le serveur d'autorisation OAuth 2.1
dans le bloc DASH_MCP_ENABLED de app.py : init des schémas oauth/usage,
purge périodique, enregistrement des routes /oauth et /.well-known, et
exemption CSRF de ces routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:38:33 +02:00
Colin Maudry 8258b92f98 feat(mcp): garde /_mcp unifié statique+OAuth, audience & usage (#114)
Le garde before_request de /_mcp accepte désormais les jetons OAuth
opaques (store.get_token_by_access) en plus des jetons statiques
colibre_*, avec vérification d'audience (resource == mcp_resource)
et d'expiration. Le 401 porte resource_metadata pour le découverte
RFC 9728. Sur succès, incrémente le compteur du bon store et
journalise dans mcp_usage (best-effort).

Corrige aussi une régression d'assertion stricte sur le header
WWW-Authenticate dans test_app_wiring.py, cassée par le nouveau
suffixe resource_metadata.
2026-07-13 14:32:49 +02:00
Colin Maudry 4e3c8517ad feat(mcp): flux OAuth authorize+token avec gate abonnement (#114)
Remplace le stub 501 de src/mcp/oauth/authorize.py par le flux réel
authlib (GET consentement, POST émission de code, échange de token),
avec gate d'abonnement avant tout affichage du consentement.

Deux ajustements de compatibilité authlib 1.7.2 dans server.py,
découverts en exécutant le flux bout-en-bout pour la première fois :
AUTHLIB_INSECURE_TRANSPORT en mode DEVELOPMENT (le client de test
Flask ne sert pas en HTTPS) et OAUTH2_REFRESH_TOKEN_GENERATOR (off
par défaut côté Flask, requis pour émettre les refresh_token du scope
offline_access). Détails dans .superpowers/sdd/task-9-report.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:27:58 +02:00
Colin Maudry 633462f18b feat(mcp): routes découverte + DCR + révocation OAuth (#114) 2026-07-13 14:19:49 +02:00
Colin Maudry ca2f6746f8 fix(mcp): refresh renvoie invalid_grant si abonnement perdu (#114) 2026-07-13 14:17:23 +02:00
Colin Maudry 0970803e5e feat(mcp): configuration authlib (grants + PKCE + DCR, #114)
- AuthorizationCodeGrant avec PKCE requis (CodeChallenge), RefreshTokenGrant
  avec rotation + re-vérification d'abonnement, DCR (ClientRegistrationEndpoint)
  et révocation (RevocationEndpoint) câblés sur le store sqlite (Tasks 1-3),
  metadata (Task 5) et consent.subscription_ok (Task 6).
- _Token (dict + check_client/get_scope) fait le pont entre les rows sqlite
  bruts et le protocole TokenMixin qu'authlib 1.7.2 attend pour le refresh
  et la révocation.
2026-07-13 14:12:03 +02:00
Colin Maudry 7dea07a30f feat(mcp): gate abonnement + écrans de consentement OAuth (#114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:04:22 +02:00
Colin Maudry c693992041 feat(mcp): documents de découverte OAuth (RFC 9728/8414, #114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:00:30 +02:00
Colin Maudry db0550a170 feat(mcp): journal d'usage mcp_usage (détection niveau 1, #114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:56:08 +02:00
Colin Maudry 13538a4f41 feat(mcp): store OAuth — tokens access/refresh + usage (#114)
Implements OAuth token management functions for Task 3:
- save_token: persists access/refresh tokens with hashing
- get_token_by_access/refresh: retrieves tokens by their hashes
- increment_usage: tracks token usage count and last_used_at
- revoke_token: sets revoked_at timestamp

All 3 tests passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:51:55 +02:00
Colin Maudry adbaffe545 feat(mcp): store OAuth — codes d'autorisation (#114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:48:42 +02:00
Colin Maudry f6132d41a2 feat(mcp): store OAuth — schéma + clients DCR (#114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:45:12 +02:00
Colin Maudry 490ecf940d docs(mcp): plan d'implémentation OAuth 2.1 connecteur MCP (scope B2, #114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:40:56 +02:00
Colin Maudry f63c08f381 docs(mcp): design serveur OAuth 2.1 pour connecteur MCP (scope B2, #114)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:30:32 +02:00
Colin Maudry 8b3e72d20a MCP uniquemnet pour les clients CLI 2026-07-13 12:53:14 +02:00
Colin Maudry 341dd84686 test(acheteur): couverture E2E du défilement horizontal AG Grid (#41) 2026-07-13 12:03:37 +02:00
Colin Maudry 87d59c2ce0 test(acheteur/titulaire): sélecteurs AG Grid pour la grille des marchés (#41)
test_002_filter_persistence et test_015_org_pages_filter_date ciblaient
encore le DataTable historique (.marches_table th[data-dash-column] input)
sur /acheteurs et /titulaires ; ces deux pages rendent désormais une
grille AG Grid (src/utils/entity_grid.py). Adapte les deux tests au
filtre flottant AG Grid (col-id + .ag-floating-filter-input), sur la
colonne texte "objet" (le filtre de date AG Grid utilise un <input
type="date"> natif peu fiable à piloter via Selenium). test_002 vérifie
en plus que la persistance du filtre est bien scopée par fiche
(entity_id/year) en rechargeant la même URL.

test_marches_table_hscroll_bar_present (tests/test_tableau_hscroll.py)
ciblait aussi /acheteurs/123 pour couvrir .marches_table + table_hscroll.js
: ce comportement n'est plus exercé par aucune page migrée. Le seul repli
restant (observatoire.py, hors périmètre #41/Lot 2b) a un bug préexistant
sans rapport qui empêche son rendu en Selenium sur ce jeu de données ;
le test est donc marqué skip avec la justification détaillée, en attendant
soit la correction de ce bug séparé, soit le retrait de cette couverture.

test_003_tableau_download n'a pas nécessité de changement (callbacks
"toutes les données" inchangés).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:03:37 +02:00
Colin Maudry 7d413ccda4 feat(titulaire): migration du tableau des marchés vers AG Grid (#41) 2026-07-13 12:03:37 +02:00
Colin Maudry 91aeca6f04 docs(plan): allow_duplicate store columnState + smoke test forward-safe (Task 5/6) 2026-07-13 12:03:37 +02:00
Colin Maudry 639c2eb986 feat(acheteur): migration du tableau des marchés vers AG Grid (#41) 2026-07-13 12:03:37 +02:00
Colin Maudry 0d2ad33d6c fix(entity_grid): allow_duplicate sur le store columnState partagé (collision acheteur/titulaire) (#41)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:03:37 +02:00
Colin Maudry 2454c8109f feat(entity_grid): register_entity_grid_callbacks (datasource/reset/export/meta) (#41) 2026-07-13 12:03:37 +02:00
Colin Maudry 76c1096f4c feat(entity_grid): fonctions pures de grille scopée acheteur/titulaire (#41) 2026-07-13 12:03:37 +02:00
Colin Maudry 94addf7fae feat(figures): get_top_org_ag_grid (top 10 en AG Grid client-side) (#41) 2026-07-13 12:03:37 +02:00
Colin Maudry 0c22138e19 feat(figures): ag_grid() paramétrable (id dict + persisted_props) (#41)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:03:37 +02:00
Colin Maudry 495fe153b1 feat(grid): export_dataframe scopable par base_where_sql/base_params (#41) 2026-07-13 12:03:37 +02:00