# 001 — HistoMétéo MVP — Spécification Technique v7

> **Itération** : v7 — intègre le feedback Reviewer (`feedback-to-architect-001-v6.md`).

---

## 0) Contract

- **Source of truth** : ce document (`001-histometeo-mvp.tech.v7.md`)
- **Functional integrity** : aucun critère d'acceptation de `001-histometeo-mvp.md` ne peut être modifié, ignoré ou réinterprété.
- **Scope** : fichiers et dossiers autorisés à créer/modifier :
  - `public/app.js` — correction C4 (handler clic boutons toggle par ville)
  - `public/style.css` — amélioration R20 (sélecteur bouton principal)
- **Forbidden changes** :
  - `docs/` — aucune modification des specs existantes
  - `.github/` — aucune modification du workflow agent
  - `src/` — aucune modification du backend
  - `Dockerfile`, `pyproject.toml` — pas de modification
  - `public/index.html` — pas de modification cette itération
  - `public/assets/` — pas de modification (les assets doivent être committés séparément, cf. R21)
  - `README.md` — pas de modification cette itération
- **Invariants** (tous hérités et préservés) :
  - INV-1 : Aucune donnée utilisateur stockée (ni serveur, ni client)
  - INV-2 : Heures en fuseau `Europe/Paris`
  - INV-3 : Période maximale 31 jours
  - INV-4 : Aucune clé API
  - INV-5 : Interface en français avec accents (`HistoMétéo`)
  - INV-6 : Page unique, URL reflète l'état via query parameters
  - INV-7 : Aucune injection HTML — `textContent`, `createElement`, `replaceChildren()` uniquement, jamais `innerHTML`
  - INV-8 : Le flux recherche simple fonctionne indépendamment du mode comparaison
- **Done when** :
  - Les 11 critères d'acceptation originaux (AC1–AC11) restent vérifiables
  - Toutes les fonctionnalités v2–v6 restent opérationnelles
  - La correction C4 est appliquée : les boutons « Tout déplier / Tout replier » par ville en mode comparaison réagissent au clic
  - L'amélioration R20 est appliquée : le sélecteur CSS générique `button` ne force plus le vert sur tous les boutons
  - **Tous** les 27 tests backend passent (aucun test SKIPPED, aucun DeprecationWarning projet)
  - L'application se lance via `uvicorn src.main:app` ou `docker compose up`

---

## 1) Objectif technique

Corriger un bug fonctionnel en mode comparaison (boutons toggle par ville non interactifs) et améliorer la robustesse CSS des boutons pour éviter un héritage involontaire de style vert sur tous les `<button>` du DOM.

---

## 2) Analyse du brief

### Besoins principaux

| Besoin                                                                 | Source      | Complexité | Impact                      |
| ---------------------------------------------------------------------- | ----------- | ---------- | --------------------------- |
| C4 — Boutons « Tout déplier / replier » non fonctionnels (comparaison) | Feedback v6 | Faible     | Bug UX — interaction cassée |
| R20 — Sélecteur CSS bouton principal trop générique                    | Feedback v6 | Faible     | Maintenabilité / Prévention |

### Contexte C4

En mode comparaison, `renderComparisonHourlyResults` appelle `buildCityBlock` qui crée dynamiquement un `<button class="btn-secondary">` toggle par ville. Ce bouton est passé à `renderDayGroups` qui :

1. Met à jour son texte via `updateToggleAllButtonState` (label « Tout déplier » / « Tout replier »)
2. Enregistre un listener `toggle` sur chaque `<details>` pour mettre à jour le texte du bouton

**Mais** : aucun `addEventListener("click", ...)` n'est enregistré sur ce bouton dynamique. Le listener global (ligne ~1599 de `app.js`) cible uniquement `toggleAllDaysButton` (le bouton HTML statique, masqué en mode comparaison). Résultat : le bouton par ville est visible, son texte se met à jour, mais cliquer dessus ne fait rien.

### Contexte R20

Le CSS actuel (ligne ~125 de `style.css`) applique `background: var(--green)` sur le sélecteur `button` générique. Tous les `<button>` du DOM héritent du vert par défaut. Les boutons secondaires (`.btn-secondary`, `.preset-buttons button`, `.btn-cancel`, `.btn-period-link`) doivent overrider ce style via des sélecteurs plus spécifiques. C'est fragile : tout nouveau bouton sera vert par défaut sans override explicite.

### Contraintes

- **2 fichiers modifiés** uniquement : `public/app.js` et `public/style.css`
- **Aucune modification backend**
- **Aucune régression visuelle** — le bouton Rechercher reste vert, les boutons secondaires restent avec le style actuel

### Risques

| #   | Risque                                                              | Mitigation                                                                                                       |
| --- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| 1   | Régression sur le bouton toggle global (mode simple)                | Le listener global sur `toggleAllDaysButton` reste inchangé. C4 ajoute un listener séparé sur les boutons locaux |
| 2   | Régression visuelle boutons après changement de sélecteur CSS (R20) | Tester visuellement tous les types de boutons : Rechercher, presets, toggle, cancel, période                     |

---

## 3) Design minimal proposé

### 3.1 Correction C4 — Ajout du click handler sur le toggle par ville

**Fichier** : `public/app.js`  
**Fonction** : `buildCityBlock` (dans `renderComparisonHourlyResults`, ligne ~1189)

Après la création du bouton `toggle` et avant l'appel à `renderDayGroups`, ajouter un `addEventListener("click", ...)` sur le bouton `toggle` qui :

1. Récupère tous les `<details class="day-group">` dans le `container` de la ville
2. Si aucun `<details>` → ne rien faire
3. Détermine si tous sont ouverts (`allOpen`)
4. Bascule tous les `<details>` vers l'état inverse (`!allOpen`)
5. Met à jour le label du bouton via `updateToggleAllButtonState(container, toggle)`

Code exact à ajouter dans `buildCityBlock`, après `block.appendChild(container)` et avant `renderDayGroups(...)` :

```js
toggle.addEventListener("click", () => {
  const details = container.querySelectorAll("details.day-group");
  if (!details.length) return;
  const allOpen = Array.from(details).every((d) => d.open);
  details.forEach((d) => {
    d.open = !allOpen;
  });
  updateToggleAllButtonState(container, toggle);
});
```

> Cette logique reproduit exactement le comportement du listener global `toggleAllDaysButton` (ligne ~1599) mais scopé au `container` de la ville.

### 3.2 Amélioration R20 — Sélecteur CSS bouton principal ciblé

**Fichier** : `public/style.css`

Remplacer le sélecteur générique `button` par un sélecteur ciblé sur le bouton principal. Le bloc actuel :

```css
button,
#search-button,
button[type="submit"] {
  /* ... styles vert ... */
}
```

Devient :

```css
#search-button,
button[type="submit"] {
  /* ... styles vert identiques ... */
}
```

Le sélecteur `button` nu est supprimé des **trois** blocs CSS concernés :

1. **Bloc principal** (fond vert, padding, etc.) : retirer `button,` du sélecteur
2. **Bloc hover** : `button:hover:not([disabled]),` → retirer `button:hover:not([disabled]),`
3. **Bloc disabled** : `button[disabled]` → ce sélecteur est déjà spécifique (`button[disabled]` cible les boutons désactivés), il reste inchangé car il s'applique à un comportement générique acceptable (opacité + curseur)

Effet net :

- `#search-button` / `button[type="submit"]` → restent verts (inchangé)
- `<button>` sans classe ni ID → n'héritent plus du vert automatiquement
- `.btn-secondary`, `.preset-buttons button`, `.btn-cancel`, `.btn-period-link` → **inchangés** (ils ont déjà leurs propres styles)
- Le bouton toggle créé dans `buildCityBlock` a déjà `class="btn-secondary"` → son style ne change pas

---

## 4) Plan d'implémentation

### Étape 1 — JS : Correction C4 — Handler clic toggle par ville

**Fichier** : `public/app.js`

- Dans la fonction `buildCityBlock` (à l'intérieur de `renderComparisonHourlyResults`), ajouter un `click` listener sur le bouton `toggle` après `block.appendChild(container)` et avant l'appel à `renderDayGroups`
- Le listener utilise `container` (la div locale) comme scope pour les `<details>` et `toggle` comme bouton à mettre à jour
- Code exact : voir section 3.1

**Testable** : en mode comparaison (2 communes), chaque ville a un bouton « Tout déplier » / « Tout replier ». Cliquer sur ce bouton bascule l'état ouvert/fermé de tous les jours de cette ville uniquement.

---

### Étape 2 — CSS : Amélioration R20 — Sélecteur bouton principal

**Fichier** : `public/style.css`

- Retirer `button,` du sélecteur de style principal (background vert)
- Retirer `button:hover:not([disabled]),` du sélecteur hover
- Conserver `button[disabled]` tel quel (comportement générique acceptable)

**Testable** : le bouton « Rechercher » est toujours vert. Les boutons secondaires (presets, toggle, cancel, période) conservent leur apparence actuelle. Aucune régression visuelle.

---

## 5) Guide pour le Développeur

### Pièges fréquents

1. **Scope du listener C4** : le `container` capturé par la closure est la `<div>` locale de la ville, PAS `dayGroups` (le conteneur global). Ne pas utiliser `dayGroups.querySelectorAll(...)` — cela ciblerait les détails des deux villes.

2. **Ordre dans `buildCityBlock`** : le listener doit être enregistré **avant** `renderDayGroups()`. Raison : `renderDayGroups` remplit `container` avec les `<details>`, mais le listener sera actif même si enregistré avant (les `<details>` seront dans le DOM quand l'utilisateur cliquera). Alternativement, l'enregistrer **après** `renderDayGroups` fonctionne aussi — l'important est qu'il soit enregistré quelque part dans `buildCityBlock`.

3. **R20 — Ne pas supprimer `button[disabled]`** : le sélecteur `button[disabled]` reste utile comme règle de sécurité pour tous les boutons désactivés (opacité réduite, curseur not-allowed). Ce n'est pas une règle de couleur, c'est un comportement UX.

### Zones de dérive

- **Ne pas refactoriser le listener global** `toggleAllDaysButton` pour unifier avec le listener local C4. Les deux mécanismes sont distincts (statique vs. dynamique) et n'ont pas besoin d'être mutualisés.
- **Ne pas ajouter de classes CSS supplémentaires** pour le fix R20 — retirer le sélecteur `button` suffit.
- **Ne pas toucher aux fichiers hors scope** (`index.html`, `src/`, etc.).

### Décisions explicitement interdites

- Ajouter un event delegation global pour les boutons toggle (ce serait une refactorisation non demandée)
- Modifier la structure HTML pour ajouter des `id` aux boutons toggle dynamiques
- Ajouter une classe `.btn-primary` dans le HTML sur le bouton Rechercher (le sélecteur `#search-button` est suffisant et déjà en place)

---

## 6) Stratégie de tests

### Tests unitaires backend

Les 27 tests existants restent inchangés et doivent continuer à passer. Aucun nouveau test backend n'est nécessaire.

### Tests manuels frontend (checklist)

#### C4 — Toggle par ville en mode comparaison

| #   | Scénario                                                 | Résultat attendu                                                                                           |
| --- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| T1  | Mode comparaison, 2 communes, période > 1 jour           | Chaque ville affiche un bouton « Tout déplier » / « Tout replier »                                         |
| T2  | Clic sur « Tout déplier » (ville 1)                      | Tous les `<details>` de ville 1 s'ouvrent. Ville 2 reste inchangée. Bouton passe à « Tout replier »        |
| T3  | Clic sur « Tout replier » (ville 1)                      | Tous les `<details>` de ville 1 se ferment. Ville 2 reste inchangée. Bouton passe à « Tout déplier »       |
| T4  | Déplier manuellement un jour (ville 2), puis clic toggle | Le bouton toggle de ville 2 bascule tous les jours de ville 2 vers l'état inverse                          |
| T5  | Mode comparaison, période 1 jour (1 seul `<details>`)    | Le bouton toggle est masqué (comportement existant via `updateToggleAllButtonState` quand ≤ 1 `<details>`) |

#### R20 — Sélecteur CSS bouton

| #   | Scénario                                  | Résultat attendu                                               |
| --- | ----------------------------------------- | -------------------------------------------------------------- |
| T6  | Bouton « Rechercher »                     | Fond vert `#6DBE45`, texte blanc, hover foncé — identique à v6 |
| T7  | Boutons presets (« Hier », « 7 jours »…)  | Style conforme (pas de fond vert non voulu)                    |
| T8  | Boutons toggle (simple + comparaison)     | Style `.btn-secondary` — bordure bleue, pas de fond vert       |
| T9  | Bouton « Annuler la comparaison »         | Style `.btn-cancel` — pas de fond vert                         |
| T10 | Boutons « Période précédente / suivante » | Style `.btn-period-link` — pas de fond vert                    |

#### Régression (non exhaustif)

| #   | Scénario                           | Résultat attendu                                                   |
| --- | ---------------------------------- | ------------------------------------------------------------------ |
| R1  | Recherche simple → tableau horaire | Fonctionne, toggle global « Tout déplier/replier » fonctionne      |
| R2  | Mode comparaison complet           | Synthèse delta, deux blocs horaires, graphiques — tout fonctionnel |
| R3  | URL partageable                    | Query params préservés, rechargement restaure la recherche         |
| R4  | Mobile 360px                       | Lisible, pas de débordement                                        |

---

## 7) Risques techniques

| #   | Risque                                                                                  | Mitigation                                                                                                                                          |
| --- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | **Régression toggle mode simple** — modification accidentelle du listener global        | Le listener global `toggleAllDaysButton` (ligne ~1599) n'est PAS touché. Seul un nouveau listener local est ajouté dans `buildCityBlock`.           |
| 2   | **Régression visuelle boutons** — suppression de `button` du sélecteur CSS              | Tous les boutons du site ont déjà un sélecteur spécifique (`#search-button`, `.btn-secondary`, `.preset-buttons button`, etc.). Impact net : aucun. |
| 3   | **Oubli de scope dans le click handler C4** — cibler `dayGroups` au lieu de `container` | Le code exact est fourni en section 3.1 — il utilise explicitement `container` (scope local).                                                       |

---

## Annexe — Notes non bloquantes (informatives)

Les points suivants du feedback v6 ne nécessitent pas de modification dans cette itération mais sont documentés pour traçabilité :

- **R21** — Les fichiers `public/assets/logo-histometeo.png` et `public/assets/favicon.png` existent sur disque mais ne sont pas encore committés. Penser à `git add public/assets/` au prochain commit.
- **R22** — Les 102 `DeprecationWarning` dans la sortie de tests proviennent des dépendances (`pytest-asyncio`, `fastapi`) sur Python 3.14+, pas du code projet. Un `pip install --upgrade pytest-asyncio fastapi` les résoudra quand les packages seront mis à jour.
