# #229 — HUB-21 : consommation des réglages généraux du Hub par ObsiGate **Carte :** propagation de HUB-21 (fusion #135) — le pilote TikTak (0.61.22, en production) a posé le patron de client ; ObsiGate le consomme à son tour, comme les autres hôtes de la suite. **Version cible :** 2.85.2 (branche depuis le main 2.85.1). ## Contrat consommé (liste fermée D6(a)) - `GET {hub}/api/reglages/effectifs` — endpoint **public** (aucun cookie relayé), `Accept: application/json`, délai **2 s** ; - réponse 200, forme figée : `{"recherche_partout": bool, "banniere": str, "domaine_defaut": "pro"|"famille"|"perso"|null, "ttl_secondes": 60}`, `Cache-Control: public, max-age=60` ; - **URL du Hub** : `draco_config.app_url("hub")`, la résolution de la barre commune (#223) — variable explicite d'abord, dérivation par l'hôte sœur ensuite, défaut officiel enfin. Jamais d'URL dev figée. ## Client — `backend/hub_reglages.py` Sur le patron du pilote TikTak (`app/hub_reglages.py`, httpx → urllib comme `backend/hub_roles.py`) : - cache mémoire **60 s** ; horodatage posé **avant** l'appel : un échec conserve l'ancien cache et ne déclenche pas de rafale vers un Hub en panne ; - forme validée strictement ; toute erreur (réseau, délai, statut ≠ 200, JSON illisible, forme hors contrat) → **repli intégral** sur les défauts actuels (`recherche_partout: true`, `banniere: ""`, `domaine_defaut: null`) — le comportement d'ObsiGate avant HUB-21. Le module ne lève jamais ; aucun appel au démarrage. ## Consommation 1. **`banniere`** — `backend/main.py::_fragment_banniere_hub()` construit le fragment (texte **échappé** via `html.escape`, jamais de HTML injecté), injecté sous la barre commune par `_index_html` (page principale, juste avant le rappel de la barre) et par `serve_recherche_page` (page /recherche, après l'en-tête de la barre). Le script inline (nonce CSP ajouté par `inject_csp_nonce`) révèle le bandeau sauf si le même texte a été fermé dans la session du navigateur (`sessionStorage`, clé `obsigate_banniere_masquee`) : une nouvelle annonce réapparaît. Bannière vide = aucun fragment rendu, zéro changement visuel. Styles `.banniere-hub*` dans `frontend/style.css`, sur les variables du thème (`var(--accent)`). 2. **`recherche_partout`** — `serve_recherche_page` remplace le jeton `{{RECHERCHE_PARTOUT}}` de `frontend/recherche.html`, rendu en attribut `data-recherche-partout` du `
`. Le JS vendangé `frontend/js/recherche.js` (modification identique dans les 4 hôtes applicatifs : fonction pure `federationAutorisee`) n'interroge plus les applications sœurs ni la console quand la valeur est `false` — leurs sections affichent « Recherche fédérée désactivée… » — tandis que la recherche locale ObsiGate (même origine) continue. Attribut absent ou illisible = fédération active (repli). 3. **`domaine_defaut` — non consommé.** ObsiGate n'a pas de notion de domaine (Pro/Famille/Perso) propre à ses vues : le sélecteur de domaine de la barre commune pilote le panneau Spark, pas ObsiGate. La valeur est lue et validée par le client, sans effet local. Seul l'hôte console (agent) applique ce réglage. ## i18n Aucun texte d'interface du SPA n'est ajouté : la bannière est un contenu d'annonce servi par le Hub (texte libre de l'administrateur), et le libellé « Fermer l'annonce » est l'attribut d'accessibilité du fragment injecté côté serveur, sur le même patron que les autres hôtes. Pas de clé `frontend/locales/*` ajoutée. ## Tests - `tests/test_hub_reglages.py` (nouveau) : contrat exact de l'appel (URL, aucun cookie, délai 2 s), formes inattendues → repli, erreur HTTP / Hub injoignable / JSON illisible → repli, cache 60 s, ancien cache conservé sans rafale ; intégration TestClient : bannière présente et échappée sur `/` et `/recherche`, absente si vide ou Hub muet, `data-recherche-partout` selon la valeur du drapeau (faux / vrai / Hub muet). - `tests/frontend/recherche.test.mjs` : assertions ajoutées pour `federationAutorisee` (seules les valeurs explicitement négatives désactivent ; absent/illisible = actif).