Compare commits

...
Author SHA1 Message Date
bruno 427df2ca92 Merge pull request 'Flowdeck 7.84.9 : barre v0.1.11 — marque monochrome, anneau rouge avatar' (#44) from barre-v0111-7-84-9 into main
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 23:09:35 -04:00
bruno 1a717ede16 Flowdeck 7.84.9 : étoile Spark monochrome pour la marque
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (pull_request) Failing after 1s
FlowDeck CI / test (pull_request) Failing after 1s
FlowDeck CI / docker (pull_request) Skipped
2026-10-29 23:09:19 -04:00
bruno d3abeb197a Flowdeck 7.84.9 : barre v0.1.11, marque monochrome, anneau avatar (CHANGELOG.md)
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 23:09:17 -04:00
bruno dee5fb5752 Flowdeck 7.84.9 : barre v0.1.11, marque monochrome, anneau avatar (VERSION)
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
2026-10-29 23:09:15 -04:00
bruno 9de1166661 Flowdeck 7.84.9 : barre v0.1.11, marque monochrome, anneau avatar (static/css/draco-barre.css)
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (push) Failing after 1m54s
2026-10-29 23:09:13 -04:00
bruno c0add7b6de Flowdeck 7.84.9 : barre v0.1.11, marque monochrome, anneau avatar (app/templates/_draco_bar.html)
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
2026-10-29 23:09:12 -04:00
bruno d29986e51d Merge pull request 'Flowdeck 7.84.8 : barre v0.1.10, rouge Spark, avatar flottant, Alertes cloche' (#43) from barre-rouge-spark-7-84-8 into main
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (push) Failing after 1m52s
2026-10-29 22:55:18 -04:00
bruno cd8969a3a2 Flowdeck 7.84.8 : avatar Spark flottant (commons v0.1.10)
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (pull_request) Failing after 1s
FlowDeck CI / test (pull_request) Failing after 0s
FlowDeck CI / docker (pull_request) Skipped
2026-10-29 22:55:00 -04:00
bruno e8cbd8e0f3 Flowdeck 7.84.8 : barre v0.1.10, rouge Spark, avatar flottant, Alertes cloche (CHANGELOG.md)
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:54:57 -04:00
bruno d40044e0c4 Flowdeck 7.84.8 : barre v0.1.10, rouge Spark, avatar flottant, Alertes cloche (VERSION)
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:54:56 -04:00
bruno 41865c702a Flowdeck 7.84.8 : barre v0.1.10, rouge Spark, avatar flottant, Alertes cloche (static/css/draco-barre.css)
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:54:54 -04:00
bruno 56b8ff2072 Flowdeck 7.84.8 : barre v0.1.10, rouge Spark, avatar flottant, Alertes cloche (app/templates/_draco_bar.html)
FlowDeck CI / docker (push) Blocked by required conditions
FlowDeck CI / test (push) In progress
FlowDeck CI / lint (push) Failing after 4m22s
2026-10-29 22:54:53 -04:00
bruno fc5431308c Merge pull request 'Flowdeck 7.84.7 : les 5 logos dans le menu et la barre commune' (#42) from barre-logos-7-84-7 into main
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:22:14 -04:00
bruno 7d31af8ec2 Flowdeck 7.84.7 : logo fourni spark.png pour la barre et le menu
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (pull_request) Failing after 0s
FlowDeck CI / test (pull_request) Failing after 0s
FlowDeck CI / docker (pull_request) Skipped
2026-10-29 22:21:59 -04:00
bruno 2e73802f5f Flowdeck 7.84.7 : logo fourni kanbante.png pour la barre et le menu
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:57 -04:00
bruno 46b876eea8 Flowdeck 7.84.7 : logo fourni tiktak.svg pour la barre et le menu
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:55 -04:00
bruno 2431bb6059 Flowdeck 7.84.7 : logo fourni flowdeck.png pour la barre et le menu
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:53 -04:00
bruno ac2f0c8cfc Flowdeck 7.84.7 : logo fourni obsigate.png pour la barre et le menu
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:51 -04:00
bruno 58ad5d9164 Flowdeck 7.84.7 : logos barre-apps v0.1.9 dans le menu et la barre (CHANGELOG.md)
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:48 -04:00
bruno 91fad4eba3 Flowdeck 7.84.7 : logos barre-apps v0.1.9 dans le menu et la barre (VERSION)
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:46 -04:00
bruno 7995dbe9d2 Flowdeck 7.84.7 : logos barre-apps v0.1.9 dans le menu et la barre (static/css/draco-barre.css)
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:44 -04:00
bruno 0e3e6ec197 Flowdeck 7.84.7 : logos barre-apps v0.1.9 dans le menu et la barre (static/js/vendor/draco-barre.js)
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:42 -04:00
bruno d6ea617ed7 Flowdeck 7.84.7 : logos barre-apps v0.1.9 dans le menu et la barre (app/templates/_draco_bar.html)
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
2026-10-29 22:21:40 -04:00
bruno f7c2ea0d34 docs: changelog 7.84.6 (PR #41 CORS sans doublons)
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-28 18:23:40 -04:00
bruno d16666be85 chore: bump version 7.84.5 -> 7.84.6
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (push) Failing after 1m58s
2026-10-28 18:23:32 -04:00
bruno fb4ce38403 Merge pull request 'fix(cors): /api/search ne doit plus empiler les en-têtes CORS (défaut 3b)' (#41) from fix-cors-search-doublons into main
FlowDeck CI / lint (push) Failing after 4s
FlowDeck CI / test (push) Failing after 12m39s
FlowDeck CI / docker (push) Skipped
2026-10-28 18:22:08 -04:00
bruno 34253c7b3f test(cors): /api/search un seul jeu d'en-tetes CORS, origine refusee inchangee (defaut 3b)
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (pull_request) Failing after 1m49s
FlowDeck CI / test (pull_request) Failing after 32m50s
FlowDeck CI / docker (pull_request) Skipped
2026-10-28 16:49:49 -04:00
bruno 4490a2ed70 fix(cors): /api/search remplace les en-tetes CORS au lieu de les empiler (defaut 3b)
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (push) Failing after 2m9s
2026-10-28 16:49:45 -04:00
bruno c9f7754f5f Changelog 7.84.5 : texte parasite, onglets recherche, CORS console (PR #37/#38/#40)
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-28 16:21:42 -04:00
bruno 43e416a889 Bump 7.84.5
FlowDeck CI / lint (push) Failing after 2s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
2026-10-28 16:21:38 -04:00
bruno fefd9b1ad3 Merge pull request 'CORS recherche : autoriser la console Spark (agent prod+dev)' (#40) from federation-recherche-console into main
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
2026-10-28 16:17:04 -04:00
bruno 3b43be3761 Merge pull request 'Recherche : onglets visibles apres deploiement (base console explicite + purge cache SW)' (#38) from flowdeck-recherche-onglets into main
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-28 16:17:01 -04:00
bruno 972d40a75f Merge pull request 'Barre : supprimer le texte parasite « Chantier 5 » rendu au-dessus du header' (#37) from flowdeck-texte-parasite into main
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-28 16:16:59 -04:00
bruno fb942bc871 Tests CORS recherche: origines agent autorisees, tiers refuse
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (pull_request) Failing after 3s
FlowDeck CI / test (pull_request) Failing after 1s
FlowDeck CI / docker (pull_request) Skipped
2026-10-28 16:11:58 -04:00
bruno b8ebea1479 CORS recherche: autoriser les origines agent (console Spark) prod+dev
FlowDeck CI / lint (push) Failing after 2s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-28 16:11:53 -04:00
bruno ee065978b2 Recherche onglets (defaut 6) : tests/test_recherche_onglets.py
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (push) Failing after 2s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (pull_request) Failing after 2s
FlowDeck CI / test (pull_request) Failing after 18m37s
FlowDeck CI / docker (pull_request) Skipped
2026-10-28 16:01:30 -04:00
bruno 71c7113a8c Recherche onglets (defaut 6) : static/sw.js
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (push) Failing after 1m52s
2026-10-28 16:01:25 -04:00
bruno c0bf0e8523 Recherche onglets (defaut 6) : app/routers/search.py
FlowDeck CI / lint (push) Failing after 2s
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
2026-10-28 16:01:21 -04:00
bruno 9ae054d1ba Recherche onglets (defaut 6) : app/templates/recherche.html
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (push) Failing after 1m51s
2026-10-28 16:01:18 -04:00
bruno 1e552a55a4 Tests : aucun texte de chantier rendu par la barre, onglets intacts
FlowDeck CI / test (push) Failing after 3s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (push) Failing after 1s
FlowDeck CI / test (pull_request) Failing after 1s
FlowDeck CI / docker (pull_request) Skipped
FlowDeck CI / lint (pull_request) Failing after 1m53s
2026-10-28 15:59:44 -04:00
bruno fd9df60788 Barre : texte de chantier 5 hors commentaire Jinja supprime du rendu (5e onglet + menu marque intacts)
FlowDeck CI / lint (push) Failing after 0s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-28 15:59:41 -04:00
bruno 984e92b593 Bump Flowdeck 7.84.4 : changelog (alignement recherche console #68)
FlowDeck CI / test (push) Failing after 41m28s
FlowDeck CI / lint (push) Failing after 2s
FlowDeck CI / docker (push) Skipped
2026-10-28 15:06:59 -04:00
bruno 8ba38a69a4 Bump Flowdeck 7.84.4 (alignement recherche console #68)
FlowDeck CI / lint (push) Failing after 2s
FlowDeck CI / test (push) Failing after 0s
FlowDeck CI / docker (push) Skipped
2026-10-28 15:06:55 -04:00
bruno ee477bf2d1 Merge pull request 'recherche : onglet Console Spark sur GET /api/v1/recherche (alignement)' (#36) from align-recherche-console into main
FlowDeck CI / test (push) Failing after 1s
FlowDeck CI / docker (push) Skipped
FlowDeck CI / lint (push) Failing after 1m51s
2026-10-28 15:04:27 -04:00
20 changed files with 461 additions and 47 deletions
+36
View File
@@ -1,3 +1,39 @@
## v7.84.9 (2026-10-29) — Barre commune : marque monochrome, anneau rouge avatar
- **Barre d'apps v0.1.11 vendangée (dracodev-commons)** : `static/css/draco-barre.css` remplacé verbatim par la v0.1.11 ; `static/js/vendor/draco-barre.js` inchangé (déjà identique à commons, JS inchangé en v0.1.11) ; fragment `app/templates/_draco_bar.html` porté à la v0.1.11, le reste de son intégration Flowdeck (URL `draco_urls()`, `onAction`, nonce CSP, `asset_version`) est inchangé.
- **Marque monochrome** : la marque n'est plus le logo rouge — c'est l'étoile Spark monochrome (`static/img/brand-spark.png`, copie de `ui/barre-apps/brand-spark.png` de commons, étoile blanche sur fond transparent, 26 px), blanche sur barre foncée et noire sur barre claire via le filtre du CSS vendangé. L'onglet agent et son entrée de menu GARDENT le logo rouge.
- **Anneau rouge avatar** : le bouton avatar flottant bas-centre gagne un anneau rouge `#c00000` (bordure 3 px du CSS vendangé, `var(--agent)`).
- **Thème** : la barre suit `data-theme` sur `<html>` (choix Flowdeck `fd_theme`, défaut light) — les règles thème de commons s'appliquent telles quelles, aucune surcharge propre n'a été ajoutée.
- **Cache** : le bump 7.84.8 → 7.84.9 change aussi `asset_version`, donc les navigateurs ne garderont pas l'ancienne barre.
## v7.84.8 (2026-10-29) — Barre commune : rouge Spark, avatar flottant, Alertes cloche seule
- **Barre d'apps v0.1.10 vendangée (dracodev-commons)** : `static/css/draco-barre.css` remplacé verbatim par la v0.1.10 ; `static/js/vendor/draco-barre.js` inchangé (déjà identique à commons entre v0.1.9 et v0.1.10) ; fragment `app/templates/_draco_bar.html` porté à la v0.1.10, le reste de son intégration Flowdeck (URL `draco_urls()`, `onAction`, nonce CSP, `asset_version`) est inchangé.
- **Rouge Spark** : `--agent` rose `#c2437e` → rouge `#c00000` (échantillonné dans le logo Spark) ; onglet agent `--app #c00000`, marque = logo Spark (26 px) au lieu du ✦.
- **Bouton Spark rose supprimé** de la barre, remplacé par le bouton avatar flottant bas-centre (cercle personnage Spark, `static/img/avatar-spark.png`, `data-act="spark"` → le panneau Spark de l'hôte, comme l'ancien bouton).
- **Alertes** : cloche seule, sans libellé — bouton moins large.
- **Cache** : le bump 7.84.7 → 7.84.8 change aussi `asset_version`, donc les navigateurs ne garderont pas l'ancienne barre.
## v7.84.7 (2026-10-29) — Barre commune : les 5 logos dans le menu et la barre
- **Barre d'apps v0.1.9 vendangée (dracodev-commons)** : `static/js/vendor/draco-barre.js` et `static/css/draco-barre.css` remplacés verbatim par la v0.1.9 (les fichiers vendangés précédents étaient identiques à l'octet près à ceux de commons avant la v0.1.9, aucune adaptation locale n'est perdue) ; fragment `app/templates/_draco_bar.html` porté à la v0.1.9, le reste de son intégration Flowdeck (URL `draco_urls()`, `onAction`, nonce CSP) est inchangé.
- **Icônes** : les 5 tuiles de la barre — et donc du menu de la marque, qui les clone — sont désormais les logos fournis, tels quels, jamais redessinés : ObsiGate (arbre), Flowdeck (cartes), TikTak (logo officiel SVG), Kanbante (cycle vert), Console Spark (étoile rouge validée). Fichiers servis depuis `static/img/logos/` (copie de `ui/barre-apps/logos/` de commons, PNG 180 px) ; l'entrée « Console Spark » du menu clone l'icône de l'onglet agent. Les glyphes texte ▤/▦/✓/▥/✦ disparaissent des onglets (la marque ✦ et le bouton Spark gardent leur ✦).
- **Cache** : le bump 7.84.6 → 7.84.7 change aussi `asset_version`, donc les navigateurs ne garderont pas les anciennes icônes.
## v7.84.6 (2026-10-28) — Recherche console : CORS Flowdeck sans doublons (PR #41)
- **CORS recherche (PR #41)** : le middleware de recherche remplace désormais les en-têtes CORS existants au lieu de les empiler — le CORSMiddleware global posait déjà Allow-Credentials: true, l'ancien middleware ajoutait les siens par-dessus (« true, true » rejeté par le navigateur) ; l'onglet Flowdeck de la recherche console agent-dev n'est plus injoignable.
## v7.84.5 (2026-10-28) — Corrections console/recherche : texte parasite, onglets, CORS console
- **Barre (PR #37)** : suppression du texte parasite « Chantier 5 » rendu au-dessus du header.
- **Recherche (PR #38)** : onglets visibles après déploiement (base console explicite + purge cache service worker).
- **CORS recherche (PR #40)** : la recherche Flowdeck autorise désormais la console Spark (agent prod+dev) en inter-origines.
## v7.84.4 (2026-10-28) — Alignement recherche console : onglet Console Spark sur /api/v1/recherche
- **Recherche (#68)** : l'onglet Console Spark de `/recherche` appelle désormais `GET /api/v1/recherche` (au lieu de `/api/search`) et reconstruit les liens profonds console (`?section=…&objet=…`, `disc=`, `activite=`) — les résultats console réels s'affichent enfin dans la page hôte.
## v7.84.3 (2026-10-28) — Corrections recherche/alertes : CORS prod+dev, recherche en onglets, alertes en onglets
- **CORS recherche/alertes (#68/#69)** : allowlist d'origines soeurs prod+dev explicites + credentials, jamais `*` — chaque serveur n'autorisait que l'environnement resolu, d'ou les echecs inter-apps constates.
+1 -1
View File
@@ -1 +1 @@
7.84.3
7.84.9
+37 -3
View File
@@ -9,7 +9,10 @@ Ce module n'autorise donc que :
- le chemin ``/api/search`` (jamais les autres routes, jamais les
écritures) ;
- les origines sœurs : les origines officielles **prod ET dev,
listées explicitement** (``PROD_ORIGINS | DEV_ORIGINS``), **plus**
listées explicitement** (``PROD_ORIGINS | DEV_ORIGINS``), **plus
les origines de la console Spark** (``AGENT_ORIGINS`` : agent prod
ET dev — fédération de la recherche depuis la console,
2026-10-28), **plus**
celles résolues par ``app.services.draco_urls.toutes()``
(explicite > hôte > dérivation > défaut officiel) — jamais ``*``.
Correctif 2026-10-28 (fusion #68/#69, matrice constatée par
@@ -49,6 +52,12 @@ DEV_ORIGINS = frozenset({
"https://kanbante-dev.dracodev.net",
})
AGENT_ORIGINS = frozenset({
"https://agent.dracodev.net",
"https://agent-dev.dracodev.net",
})
# Clés de draco_urls.toutes() qui sont des applications sœurs.
_SOEUR_KEYS = ("obsigate", "flowdeck", "tiktak", "kanbante")
@@ -113,7 +122,7 @@ def sister_origins(resolved: dict | None = None, request_host: str | None = None
# l'environnement de la requête, désormais inclus d'office).
# ``resolved`` n'ajoute que des origines de configuration sans
# jamais retirer une origine officielle.
origins |= PROD_ORIGINS | DEV_ORIGINS
origins |= PROD_ORIGINS | DEV_ORIGINS | AGENT_ORIGINS
return frozenset(origins)
@@ -184,7 +193,32 @@ class RechercheCorsMiddleware:
if message["type"] == "http.response.start":
cors = cors_headers_for(origin, request_host=request_host)
if cors is not None:
message["headers"] = list(message.get("headers", [])) + [
# Remplacer, pas empiler (défaut 3b, 2026-10-28) :
# le CORSMiddleware global de l'application pose
# déjà Access-Control-Allow-Credentials: true sur
# TOUTES les réponses (comportement Starlette avec
# allow_credentials=True, même pour une origine
# hors allowlist et hors /api/search) ; empiler les
# en-têtes de recherche donnait, sur /api/search
# depuis agent-dev, Allow-Credentials en double
# (« true, true » ≠ « true » pour le contrôle CORS
# du navigateur → fetch rejeté → « injoignable
# (CORS ou réseau) ») et, depuis une sœur
# (origine déjà dans la allowlist globale),
# Allow-Origin en double (rejet certain). Les
# hôtes TikTak/Kanbante, sans ce cumul, répondent
# avec des en-têtes uniques — d'où l'échec
# Flowdeck seul. Une origine non autorisée
# traverse sans modification (comportement
# antérieur préservé : le seul en-tête global
# reste, sans Allow-Origin, donc bloqué).
restants = [
(k, v) for k, v in message.get("headers", [])
if k.decode("latin-1").lower() not in (
"access-control-allow-origin",
"access-control-allow-credentials",
"vary")]
message["headers"] = restants + [
(k.lower().encode("latin-1"), v.encode("latin-1"))
for k, v in cors.items()
]
+6 -1
View File
@@ -63,7 +63,12 @@ def recherche_page(request: Request, q: str = Query(default="")):
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
bases = draco_urls.toutes()
template = ENV.get_template("recherche.html")
# Defaut 6 : la base agent est exposee explicitement (data-base-console
# dans recherche.html), comme les 3 autres hotes — recherche.js ne doit
# plus la deviner (derivation -dev / href d'onglet de barre) pour
# l'onglet Console Spark (GET /api/v1/recherche, alignement #36).
return HTMLResponse(template.render(
request=request, q=q.strip(), user=user,
bases={k: bases[k] for k in ("obsigate", "flowdeck", "tiktak", "kanbante")},
bases={k: bases[k] for k in ("obsigate", "flowdeck", "tiktak",
"kanbante", "agent")},
))
+31 -16
View File
@@ -1,42 +1,61 @@
{# Barre d'apps commune dracodev — Fusion ACC-1.1 (issue #17).
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.6
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.11
(ui/barre-apps/) : ne pas modifier la structure ici ; toute évolution
passe par une nouvelle version de commons.
CSS/JS : /static/css/draco-barre.css, /static/js/vendor/draco-barre.js.
v7.82.0 (dev/prod) : les href des onglets ne sont plus figés —
l'hôte les injecte depuis sa config (draco_urls(), noms officiels
par défaut, noms en -dev déclarés dans le .env de l'environnement). #}
Chantier 5 (fusion #69/#71, sans bump) : 5e onglet Console Spark
(data-app="agent", draco_urls().agent), après Kanbante ; l'entrée
« Console Spark » du menu de la marque est conservée.
par défaut, noms en -dev déclarés dans le .env de l'environnement).
5e onglet Console Spark (data-app="agent", draco_urls().agent),
après Kanbante ; l'entrée « Console Spark » du menu de la marque
est conservée (menu construit par draco-barre.js depuis les onglets).
Icônes logos v0.1.9 (2026-10-29) : les 5 tuiles sont les logos
fournis, tels quels, servis depuis /static/img/logos/ (copie de
ui/barre-apps/logos/ de dracodev-commons) — jamais de glyphe
texte, jamais de redessin ; l'entrée « Console Spark » du menu
clone l'icône de l'onglet agent (draco-barre.js).
v0.1.10 (2026-10-29) : --agent rose → rouge Spark #c00000 ;
marque ✦ → logo Spark (26 px) ; bouton « Spark » rose SUPPRIMÉ
de la barre, remplacé par le bouton avatar flottant bas-centre
(static/img/avatar-spark.png, data-act="spark" → panneau de l'hôte) ;
Alertes = cloche seule, sans libellé. JS inchangé depuis v0.1.9.
v0.1.11 (2026-10-29) : marque = étoile Spark monochrome
(static/img/brand-spark.png, copie de ui/barre-apps/brand-spark.png
de commons — blanche sur barre foncée, noire sur barre claire
via le filtre du CSS vendangé) ; onglet agent et son entrée de
menu GARDENT le logo rouge ; anneau rouge #c00000 autour du
bouton avatar flottant (bordure du CSS vendangé). Thème de la
barre : data-theme sur <html> (fd_theme, défaut light) — les
règles thème de commons s'appliquent, aucune surcharge propre
n'est nécessaire. #}
<link rel="stylesheet" href="/static/css/draco-alertes.css?v={{ asset_version }}">
<header class="draco-bar" data-current="flowdeck">
<div class="draco-bar-zone draco-bar-gauche">
<button type="button" class="draco-icon-btn" data-act="rail" aria-label="Barre latérale">☰</button>
<a class="draco-brand" href="/" data-act="apps" aria-haspopup="true" aria-expanded="false" aria-label="dracodev — changer d&#39;application" title="Changer d&#39;application">
<span class="draco-brand-mark">✦</span><span class="draco-brand-name">dracodev</span>
<span class="draco-brand-mark"><img src="/static/img/brand-spark.png" alt="" width="26" height="26"></span><span class="draco-brand-name">dracodev</span>
</a>
<nav class="draco-apptabs" aria-label="Applications">
<a class="draco-apptab" data-app="obsigate" href="{{ draco_urls().obsigate }}/">
<span class="draco-apptab-ico" style="--app:#7a5af5">▤</span>
<span class="draco-apptab-ico" style="--app:#7a5af5" data-icon="obsigate"><img src="/static/img/logos/obsigate.png" alt="ObsiGate" width="30" height="30"></span>
<span class="draco-apptab-txt"><strong>ObsiGate</strong><small>Notes</small></span>
</a>
<a class="draco-apptab" data-app="flowdeck" href="{{ draco_urls().flowdeck }}/">
<span class="draco-apptab-ico" style="--app:#2f6fed">▦</span>
<span class="draco-apptab-ico" style="--app:#2f6fed" data-icon="flowdeck"><img src="/static/img/logos/flowdeck.png" alt="Flowdeck" width="30" height="30"></span>
<span class="draco-apptab-txt"><strong>Flowdeck</strong><small>Projets</small></span>
</a>
<a class="draco-apptab" data-app="tiktak" href="{{ draco_urls().tiktak }}/">
<span class="draco-apptab-ico" style="--app:#e8762c">✓</span>
<span class="draco-apptab-ico" style="--app:#e8762c" data-icon="tiktak"><img src="/static/img/logos/tiktak.svg" alt="TikTak" width="30" height="30"></span>
<span class="draco-apptab-txt"><strong>TikTak</strong><small>Tâches</small></span>
<span class="draco-badge" data-badge="tiktak" hidden>0</span>
</a>
<a class="draco-apptab" data-app="kanbante" href="{{ draco_urls().kanbante }}/">
<span class="draco-apptab-ico" style="--app:#0d9488">▥</span>
<span class="draco-apptab-ico" style="--app:#0d9488" data-icon="kanbante"><img src="/static/img/logos/kanbante.png" alt="Kanbante" width="30" height="30"></span>
<span class="draco-apptab-txt"><strong>Kanbante</strong><small>Kanban</small></span>
<span class="draco-badge" data-badge="kanbante" hidden>0</span>
</a>
<a class="draco-apptab" data-app="agent" href="{{ draco_urls().agent }}/">
<span class="draco-apptab-ico" style="--app:#c2437e">✦</span>
<span class="draco-apptab-ico" style="--app:#c00000" data-icon="agent"><img src="/static/img/logos/spark.png" alt="Console Spark" width="30" height="30"></span>
<span class="draco-apptab-txt"><strong>Console Spark</strong><small>Agent commun</small></span>
</a>
</nav>
@@ -57,16 +76,12 @@
</div>
<button type="button" class="draco-alertes" data-act="alertes" aria-label="Alertes unifiées">
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>
<span class="draco-alertes-txt">Alertes</span>
<span class="draco-badge" data-badge="alertes" hidden>0</span>
</button>
<button type="button" class="draco-spark" data-act="spark" aria-label="Ouvrir Spark, l'agent commun">
<span class="draco-spark-ico">✦</span><span class="draco-spark-txt">Spark</span>
<span class="draco-badge" data-badge="spark" hidden>0</span>
</button>
<button type="button" class="draco-avatar" data-act="compte" aria-label="Compte" aria-haspopup="menu" aria-expanded="false" title="{{ user.get('full_name') if user and user.get('full_name') else '' }}">{{ user.full_name[:1].upper() if user and user.get('full_name') else 'F' }}</button>
</div>
</header>
<button type="button" class="draco-spark-flottant" data-act="spark" aria-label="Ouvrir Spark, l'agent commun"><img src="/static/img/avatar-spark.png" alt="" width="64" height="64"></button>
<div class="draco-bar-rappel" data-act="rappel-entete" hidden>✦ dracodev — afficher l'en-tête</div>
<script nonce="{{ csp_nonce() }}">
/* Configuration Flowdeck de la barre commune (Fusion ACC-1.1).
+13 -8
View File
@@ -1,17 +1,22 @@
{% extends "base.html" %}
{% block title_prefix %}Rechercher partout{% endblock %}
{% block content %}
<link rel="stylesheet" href="/static/css/recherche.css">
{# Console : pas de data-base-console ici — la route Flowdeck de /recherche
n'expose pas la base agent dans son contexte `bases` ; recherche.js
dérive donc la base console (hôte -dev -> agent-dev, sinon prod) ou
reprend le href d'un onglet de barre console/spark quand il existe.
Les 3 autres hôtes rendent data-base-console explicitement. #}
{# Versionnes (?v=asset_version) comme les autres feuilles/scripts de
base.html : sans version, le service worker (static/sw.js, cache-first
des .js/.css) resservait indefiniment les copies pre-#33 de ces deux
fichiers aux navigateurs les ayant deja visites — la page paraissait
donc « non corrigee » malgre les PR #33/#36. #}
<link rel="stylesheet" href="/static/css/recherche.css?v={{ asset_version }}">
{# Console : data-base-console rendu explicitement, comme les 3 autres
hotes — la route Flowdeck de /recherche expose la base agent dans
son contexte `bases` (defaut 6). recherche.js garde ses replis
(derivation -dev, href d'onglet de barre) si l'attribut est vide. #}
<main class="rp-page" id="recherche" data-host="flowdeck"
data-base-obsigate="{{ bases.obsigate }}"
data-base-flowdeck="{{ bases.flowdeck }}"
data-base-tiktak="{{ bases.tiktak }}"
data-base-kanbante="{{ bases.kanbante }}">
data-base-kanbante="{{ bases.kanbante }}"
data-base-console="{{ bases.agent }}">
<h1>Rechercher partout</h1>
<p class="rp-hint">Notes ObsiGate, pages Flowdeck, tâches TikTak, cartes Kanbante — dans l'environnement courant (dev reste dev). « / » ou Ctrl+K pour rechercher, ↑/↓ puis Entrée pour ouvrir un résultat. « Tout voir » bascule vers l'onglet de l'application, sans quitter la page.</p>
<div class="rp-form">
@@ -49,5 +54,5 @@
</main>
{% endblock %}
{% block scripts %}
<script src="/static/js/recherche.js"></script>
<script src="/static/js/recherche.js?v={{ asset_version }}"></script>
{% endblock %}
+36 -11
View File
@@ -7,6 +7,11 @@
menu des applications) ; l'avatar est un vrai <button>.
Correctif fusion #70 (SANS bump) : bouton trois points et menu par
défaut supprimés ; le menu de la marque gagne « Console Spark ».
v0.1.10 : rose --agent → rouge Spark #c00000 ; marque = logo Spark ;
bouton Spark supprimé (remplacé par l'avatar flottant bas-centre) ;
Alertes icône seule.
v0.1.11 : marque = étoile Spark monochrome (blanche barre foncée,
noire barre claire) ; anneau rouge #c00000 autour de l'avatar flottant.
Jetons repris du prototype validé Console v7.2.
Thème : clair par défaut, sombre via [data-theme="dark"] sur <html>
ou prefers-color-scheme si l'app intégratrice ne force rien. */
@@ -14,7 +19,7 @@
.draco-bar {
--bg: #f2f2f0; --surface: #ffffff; --surface-2: #e9eae6; --surface-3: #f6f6f3;
--ink: #1b1d1a; --ink-2: #5c615a; --ink-3: #787d75; --line: #dedfd9;
--agent: #c2437e; --danger: #cf4444;
--agent: #c00000; --danger: #cf4444;
position: relative;
height: 60px; box-sizing: border-box;
display: grid; grid-template-columns: auto minmax(200px, 520px) auto;
@@ -37,7 +42,15 @@
.draco-brand { display: flex; align-items: center; gap: 7px; color: var(--ink); text-decoration: none; font-weight: 800; cursor: pointer; border-radius: 10px; padding: 4px 6px; margin: -4px -6px; }
.draco-brand:hover { background: var(--surface-2); }
.draco-brand:focus-visible { outline: 2px solid var(--agent); outline-offset: 2px; }
.draco-brand-mark { color: var(--agent); font-size: 24px; line-height: 1; }
.draco-brand-mark { line-height: 0; }
/* Marque monochrome v0.1.11 : étoile blanche extraite du logo rouge,
fond transparent — noire sur barre claire (filtre), blanche sur
barre foncée (filtre annulé par les règles thème ci-dessous). */
.draco-brand-mark img { width: 26px; height: 26px; object-fit: cover; display: block; filter: brightness(0); }
@media (prefers-color-scheme: dark) {
.draco-bar .draco-brand-mark img { filter: none; }
}
[data-theme="dark"] .draco-bar .draco-brand-mark img { filter: none; }
/* Menu des applications — construit par le JS depuis les onglets */
.draco-apps-menu {
@@ -94,6 +107,13 @@
width: 30px; height: 30px; flex: none; display: grid; place-items: center;
border-radius: 9px; background: var(--app); color: #fff; font-size: 15px;
}
/* Icônes logos (2026-10-29, corrigé le même jour) : l'image du logo
* fourni, telle quelle, remplit la tuile 30 px — jamais de glyphe
* texte, jamais de redessin. Le fond de la tuile est transparent :
* les logos PNG portent déjà leur fond (dégradé), le SVG TikTak
* reste sur le fond de la barre. */
.draco-apptab-ico { background: transparent; }
.draco-apptab-ico img { width: 30px; height: 30px; display: block; border-radius: 9px; object-fit: cover; }
.draco-apptab-txt { display: none; flex-direction: column; line-height: 1.15; }
.draco-apptab-txt strong { font-size: 14px; font-weight: 700; }
.draco-apptab-txt small { font-size: 11.5px; color: var(--ink-2); }
@@ -131,18 +151,23 @@
/* Alertes — la cloche ne disparaît JAMAIS (correctif v7.2) */
.draco-alertes {
position: relative; display: flex; align-items: center; gap: 7px; height: 38px;
padding: 0 11px; border-radius: 10px; border: 1px solid var(--line);
position: relative; display: flex; align-items: center; height: 38px;
padding: 0 9px; border-radius: 10px; border: 1px solid var(--line);
background: transparent; color: var(--ink); font: inherit; cursor: pointer;
}
/* Spark */
.draco-spark {
position: relative; display: flex; align-items: center; gap: 7px; height: 38px;
padding: 0 14px; border: 0; border-radius: 10px; cursor: pointer;
background: var(--agent); color: #fff; font: inherit; font-weight: 700;
/* Bouton avatar Spark flottant — bas-centre, ouvre le panneau Spark
de l'hôte via data-act="spark" (délégation globale existante).
La console Spark ne l'inclut pas : son fragment est propre. */
.draco-spark-flottant {
position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
z-index: 65; width: 64px; height: 64px; padding: 0; border: 0;
border-radius: 50%; background: transparent; cursor: pointer;
box-shadow: 0 8px 28px rgb(0 0 0 / .45);
}
.draco-spark .draco-badge { border-color: var(--agent); }
.draco-spark-flottant img { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; display: block; box-sizing: border-box; border: 3px solid var(--agent); }
.draco-spark-flottant:hover { transform: translateX(-50%) scale(1.05); }
.draco-spark-flottant:focus-visible { outline: 2px solid var(--agent); outline-offset: 3px; }
.draco-icon-btn {
width: 38px; height: 38px; border-radius: 10px; border: 1px solid transparent;
@@ -169,7 +194,7 @@
.draco-bar { grid-template-columns: auto minmax(200px, 440px) auto; }
}
@media (max-width: 1200px) {
.draco-search-txt, .draco-search kbd, .draco-alertes-txt, .draco-spark-txt { display: none; }
.draco-search-txt, .draco-search kbd { display: none; }
.draco-search { width: 38px; padding: 0; justify-content: center; }
.draco-bar { grid-template-columns: auto auto 1fr; }
.draco-bar-droite { justify-content: flex-end; }
Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<!-- TikTak — deux arcs (les deux temps), coche-flèche. Bleu #0076fb, orange #fc750c.
Géométrie mesurée sur le visuel validé par Bruno (variante B). -->
<path d="M 38.2 101.1 A 84 84 0 0 1 201.8 101.1" fill="none" stroke="#0076fb" stroke-width="28" stroke-linecap="round"/>
<path d="M 201.5 140.3 A 84 84 0 0 1 38.5 140.3" fill="none" stroke="#fc750c" stroke-width="28" stroke-linecap="round"/>
<polyline points="80,129 106,146 163,84" fill="none" stroke="#0076fb" stroke-width="24" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 128 80 L 163 84 L 167 122" fill="none" stroke="#0076fb" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 760 B

+16 -5
View File
@@ -369,7 +369,10 @@
* de l'agent commun (URL agent résolue — explicite > -dev >
* défaut prod ; aucun hôte spark-dev inventé). data-app="agent"
* l'intègre à refreshMenuHrefs/applyUrls/setCurrent comme les
* autres items ; l'icône ✦ reprend la couleur agent du CSS. */
* autres items ; son icône CLONE celle de l'onglet agent
* (2026-10-29, logos barre-apps, corrigé le même jour) : le logo
* fourni, tel quel, jamais redessiné — le CSS --agent (rose) des
* autres éléments de la barre n'est pas changé ici. */
var sep = document.createElement("div");
sep.className = "draco-menu-sep";
sep.setAttribute("aria-hidden", "true");
@@ -379,10 +382,18 @@
sparkItem.href = resolvedUrls.agent || "#";
sparkItem.dataset.app = "agent";
sparkItem.setAttribute("role", "menuitem");
var sparkIco = document.createElement("span");
sparkIco.className = "draco-apptab-ico";
sparkIco.setAttribute("style", "--app:#c2437e");
sparkIco.textContent = "✦";
var sparkIco;
var agentTab = document.querySelector('.draco-apptab[data-app="agent"]');
var agentIco = agentTab ? agentTab.querySelector(".draco-apptab-ico") : null;
if (agentIco) {
sparkIco = agentIco.cloneNode(true);
} else {
sparkIco = document.createElement("span");
sparkIco.className = "draco-apptab-ico";
sparkIco.setAttribute("style", "--app:#d21f2c");
sparkIco.setAttribute("data-icon", "agent");
sparkIco.textContent = "✦";
}
sparkItem.appendChild(sparkIco);
var sparkTxt = document.createElement("span");
sparkTxt.className = "draco-apps-item-txt";
+8 -1
View File
@@ -8,7 +8,14 @@
═══════════════════════════════════════════════════════════ */
'use strict';
const CACHE_NAME = 'flowdeck-v8'; // v8 : Alpine CSP build (A20 fini)
const CACHE_NAME = 'flowdeck-v9'; // v9 : purge des caches pre-#33/#36 —
// recherche.js/recherche.css etaient servis cache-first SANS version
// (?v= absent dans recherche.html) : les navigateurs ayant visite
// Flowdeck avant les onglets gardaient les copies pre-#33 (JS sans
// onglets/compteurs/console, CSS sans .rp-tabs) malgre les deplois
// 7.84.3/7.84.4. Le bump purge ces caches a l'activation, et les
// liens versionnes (?v=asset_version) font des futurs changements
// de nouvelles cles de cache.
const DATA_CACHE = 'flowdeck-data-v7';
// App shell (assets versionnés comme référencés dans les templates).
+48
View File
@@ -0,0 +1,48 @@
"""Barre d'apps Flowdeck : aucun texte de chantier rendu hors commentaire.
Defaut 5 : les lignes « Chantier 5 (fusion #69/#71, sans bump) : ... »
etaient placees APRES la fermeture `#}` du commentaire Jinja d'en-tete
de `app/templates/_draco_bar.html` (lignes 9-11) : elles etaient donc
rendues telles quelles, juste au-dessus du <header> de la barre, sur
toutes les pages (la barre est incluse par base.html).
Test sans dependance (lecture du gabarit seul, comme
test_draco_alertes.py) : il tourne sans serveur ni base, donc aussi
en sandbox. La note documentaire peut vivre DANS le commentaire Jinja
(jamais rendu) ; elle ne doit jamais en sortir.
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def _hors_commentaires(texte):
"""Texte du gabarit sans les commentaires Jinja {# ... #}."""
return re.sub(r"\{#.*?#\}", "", texte, flags=re.S)
def test_aucun_texte_chantier_rendu():
gabarit = (ROOT / "app/templates/_draco_bar.html").read_text()
rendu = _hors_commentaires(gabarit)
assert "Chantier 5 (fusion" not in rendu
assert "sans bump" not in rendu
# Aucune ligne de texte libre entre le commentaire d'en-tete et <link> :
# le rendu doit commencer (apres commentaires) par <link> ou <header>.
assert rendu.lstrip().startswith("<link")
def test_barre_intacte_apres_suppression():
"""Le 5e onglet Console Spark et la marque survivent a la suppression."""
gabarit = (ROOT / "app/templates/_draco_bar.html").read_text()
rendu = _hors_commentaires(gabarit)
nav = rendu.split('<nav class="draco-apptabs"')[1].split("</nav>")[0]
assert nav.index('data-app="kanbante"') < nav.index('data-app="agent"')
assert "Console Spark" in nav
assert 'data-app="agent"' in nav
assert 'class="draco-brand"' in rendu
# L'entree « Console Spark » du menu de la marque est construite par
# draco-barre.js depuis les onglets : l'onglet agent suffit a l'alimenter.
js_barre = (ROOT / "static/js/vendor/draco-barre.js").read_text()
assert "Console Spark" in js_barre
+16 -1
View File
@@ -38,7 +38,7 @@ def test_origin_of():
def test_sister_origins_dev():
# Correctif 2026-10-28 : 8 origines officielles, prod ET dev,
# Correctif 2026-10-28 : 10 origines officielles (8 sœurs + 2 agent), prod ET dev,
# quelle que soit la résolution.
assert sister_origins(RESOLVED_DEV) == {
"https://og.dracodev.net",
@@ -49,9 +49,24 @@ def test_sister_origins_dev():
"https://flowdeck-dev.dracodev.net",
"https://tiktak-dev.dracodev.net",
"https://kanbante-dev.dracodev.net",
"https://agent.dracodev.net",
"https://agent-dev.dracodev.net",
}
def test_cors_headers_agent_console_autorise():
# Fédération depuis la console (2026-10-28) : les origines agent
# prod ET dev passent, avec credentials ; un tiers reste refusé.
for origine in ("https://agent.dracodev.net",
"https://agent-dev.dracodev.net"):
headers = cors_headers_for(origine, RESOLVED_DEV)
assert headers is not None
assert headers["Access-Control-Allow-Origin"] == origine
assert headers["Access-Control-Allow-Credentials"] == "true"
assert cors_headers_for("https://agent.dracodev.net.evil.example",
RESOLVED_DEV) is None
def test_sister_origins_ajoute_env_de_la_requete():
# Correctif 2026-10-28 : configuration muette, quel que soit
# l'hôte de la requête, prod ET dev passent — l'hôte ne
+120
View File
@@ -0,0 +1,120 @@
"""Défaut 3b — /api/search Flowdeck depuis la console agent-dev.
Cause prouvée en déployé (flowdeck-dev, 2026-10-28) : sur une réponse
GET /api/search, le CORSMiddleware global pose déjà
``Access-Control-Allow-Credentials: true`` (comportement Starlette
avec ``allow_credentials=True``, même hors allowlist), et l'ancien
middleware de recherche EMPILAIT ses en-têtes par-dessus — d'où, pour
l'origine agent-dev, ``Allow-Credentials`` en double (« true, true »
≠ « true » pour le contrôle CORS du navigateur → fetch rejeté →
« injoignable (CORS ou réseau) » dans la console, alors que TikTak et
Kanbante, sans ce cumul, répondent avec des en-têtes uniques) et,
pour une origine sœur déjà dans la allowlist globale, ``Allow-Origin``
en double (rejet certain). Le correctif : le middleware de recherche
REMPLACE les en-têtes CORS existants au lieu de les empiler (même
patron que app/recherche_cors.py de la console).
Ces tests pilotent le middleware en ASGI pur, avec une application
interne qui imite le CORSMiddleware global (un en-tête
Allow-Credentials déjà posé, et, pour une sœur, Allow-Origin aussi).
``sister_origins`` est figé sur les listes officielles (les fonctions
pures sont déjà couvertes par tests/test_recherche.py) pour garder le
test hermétique (aucune résolution draco_urls, aucune base).
"""
import asyncio
import pytest
from app.middleware import recherche_cors as rc
ORIGINES = rc.PROD_ORIGINS | rc.DEV_ORIGINS | rc.AGENT_ORIGINS
def _lancer(middleware, scope):
"""Exécute le middleware ASGI et retourne les messages envoyés."""
recus = []
async def recevoir():
return {"type": "http.request", "body": b"", "more_body": False}
async def envoyer(message):
recus.append(message)
asyncio.run(middleware(scope, recevoir, envoyer))
return recus
def _application_interne(entetes):
async def appli(scope, recevoir, envoyer):
await envoyer({"type": "http.response.start", "status": 401,
"headers": list(entetes)})
await envoyer({"type": "http.response.body", "body": b"{}"})
return appli
def _scope(methode, origine):
return {"type": "http", "method": methode, "path": "/api/search",
"headers": [(b"origin", origine.encode("latin-1")),
(b"host", b"flowdeck-dev.dracodev.net")]}
def _compter(entetes, nom):
return sum(1 for k, _v in entetes if k == nom.encode("latin-1"))
@pytest.fixture(autouse=True)
def _origines_figees(monkeypatch):
monkeypatch.setattr(rc, "sister_origins", lambda *a, **k: ORIGINES)
def test_get_agent_dev_un_seul_jeu_d_entetes():
"""Cas du défaut : globale (ACAC seul) + recherche → un seul jeu."""
interne = _application_interne([
(b"content-type", b"application/json"),
(b"access-control-allow-credentials", b"true")])
recus = _lancer(rc.RechercheCorsMiddleware(interne),
_scope("GET", "https://agent-dev.dracodev.net"))
entetes = recus[0]["headers"]
assert _compter(entetes, "access-control-allow-credentials") == 1
assert _compter(entetes, "access-control-allow-origin") == 1
assert _compter(entetes, "vary") == 1
assert (b"access-control-allow-origin",
b"https://agent-dev.dracodev.net") in entetes
assert (b"content-type", b"application/json") in entetes
assert recus[0]["status"] == 401 # le statut traverse inchangé
def test_get_origine_soeur_pas_d_allow_origin_double():
"""Sœur déjà dans la allowlist globale : pas de doublon non plus."""
interne = _application_interne([
(b"access-control-allow-origin", b"https://og-dev.dracodev.net"),
(b"access-control-allow-credentials", b"true"),
(b"vary", b"Origin")])
recus = _lancer(rc.RechercheCorsMiddleware(interne),
_scope("GET", "https://og-dev.dracodev.net"))
entetes = recus[0]["headers"]
assert _compter(entetes, "access-control-allow-origin") == 1
assert _compter(entetes, "access-control-allow-credentials") == 1
assert _compter(entetes, "vary") == 1
def test_origine_non_autorisee_traverse_sans_modification():
"""Une origine refusée ne reçoit aucun Allow-Origin ajouté."""
interne = _application_interne([
(b"access-control-allow-credentials", b"true")])
recus = _lancer(rc.RechercheCorsMiddleware(interne),
_scope("GET", "https://example.com"))
entetes = recus[0]["headers"]
assert _compter(entetes, "access-control-allow-origin") == 0
assert entetes == [(b"access-control-allow-credentials", b"true")]
def test_preflight_agent_dev_toujours_unique():
"""Le preflight (court-circuit avant l'interne) reste propre."""
recus = _lancer(rc.RechercheCorsMiddleware(_application_interne([])),
_scope("OPTIONS", "https://agent-dev.dracodev.net"))
entetes = recus[0]["headers"]
assert recus[0]["status"] == 204
assert _compter(entetes, "access-control-allow-origin") == 1
assert _compter(entetes, "access-control-allow-credentials") == 1
+85
View File
@@ -0,0 +1,85 @@
"""Page /recherche Flowdeck : onglets, base console, anti-cache (defaut 6).
Audit prealable (main 7.84.4, tag v7.84.4 = 984e92b) : la route
(app/routers/search.py:51), le gabarit (app/templates/recherche.html),
le JS (static/js/recherche.js) et le CSS (static/css/recherche.css)
contenaient DEJA la page a onglets des PR #33/#36 — aucune route
fantome, aucun bloc `scripts` manquant dans base.html:869, aucune
regle CSP ne bloquant le script (script-src 'self'). Le symptome
deploie (« pas d'onglets, resultats Flowdeck seuls ») ne s'explique
donc PAS par un mauvais gabarit dans main, mais par deux causes
reelles, corrigees ici :
1. Assets recherche servis CACHE-FIRST par le service worker
(static/sw.js) SANS version (?v= absent) sous un CACHE_NAME
inchange depuis A20 ('flowdeck-v8') : un navigateur ayant visite
Flowdeck avant #33 gardait le recherche.js pre-#33 (sans onglets,
sans compteurs, sans logique console, « Tout voir » naviguant
ailleurs) et le recherche.css pre-#33 (sans .rp-tabs) — la page
paraissait donc « non corrigee » malgre les deplois 7.84.3/7.84.4,
et le repli networkFirst du HTML vers le cache couvrait aussi le
cas reseau lent. Correctif : liens versionnes (?v=asset_version,
comme tous les autres assets de base.html) + bump CACHE_NAME
(purge a l'activation du nouveau worker).
2. Flowdeck etait le seul hote a ne pas rendre data-base-console
(son propre commentaire l'admettait) : la base console de l'onglet
Console Spark etait devine (derivation -dev / href d'onglet de
barre) au lieu d'etre explicite. Correctif : la route expose
`bases.agent` et le gabarit le rend en data-base-console.
La logique d'onglets elle-meme (focus, compteurs, « Tout voir » sur
place, etats par application, liens -dev, console sur
GET /api/v1/recherche) est deja couverte par tests/recherche.test.js
(suite Node, lancee en sandbox) ; ce fichier-ci verrouille le
branchement gabarit/route/service worker, sans serveur ni base.
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
ONGLETS_ATTENDUS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante",
"console"]
def test_gabarit_recherche_onglets_complets():
gabarit = (ROOT / "app/templates/recherche.html").read_text()
trouves = re.findall(r'data-rp-tab="([^"]+)"', gabarit)
assert trouves == ONGLETS_ATTENDUS, trouves
for section in ("obsigate", "flowdeck", "tiktak", "kanbante", "console"):
assert f'data-rp-section="{section}"' in gabarit
assert 'id="rp-q"' in gabarit and "autofocus" in gabarit
def test_gabarit_recherche_base_console_explicite():
gabarit = (ROOT / "app/templates/recherche.html").read_text()
assert 'data-base-console="{{ bases.agent }}"' in gabarit
def test_gabarit_recherche_assets_versionnes():
"""Sans ?v=, le SW cache-first ressert les copies pre-#33."""
gabarit = (ROOT / "app/templates/recherche.html").read_text()
assert "/static/css/recherche.css?v={{ asset_version }}" in gabarit
assert "/static/js/recherche.js?v={{ asset_version }}" in gabarit
def test_route_recherche_expose_base_agent():
route = (ROOT / "app/routers/search.py").read_text()
assert '"agent"' in route or "'agent'" in route
assert "bases.agent" in (ROOT / "app/templates/recherche.html").read_text()
def test_service_worker_purge_anciens_caches():
"""Le bump de CACHE_NAME purge les caches pre-#33 a l'activation."""
sw = (ROOT / "static/sw.js").read_text()
assert "const CACHE_NAME = 'flowdeck-v9'" in sw
assert "flowdeck-v8" not in sw
assert "cache-first" in sw or "cacheFirst" in sw
def test_recherche_js_branche_console_v1():
"""Rappel : l'onglet console appelle GET /api/v1/recherche (cf. #36)."""
js = (ROOT / "static/js/recherche.js").read_text()
assert 'CONSOLE_SEARCH_PATH = "api/v1/recherche"' in js
assert "CONSOLE_SEARCH_LIMIT = 20" in js