2129da4f55
- Audio : minWidth:0 + onLoadedMetadata volume=0.5 (plus de débordement) - Grille : repeat(3,1fr) sur laptop, 1fr sur mobile (était auto-fill) - Header laptop : bouton "Nouvelle note" (fa-plus + accent) visible lg:flex - SideNav : DbStatusBar en bas — LED verte/rouge + taille BDD, polling 30s - docs/plan.md : Phase 4c documentée v0.5.7 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
351 lines
17 KiB
Markdown
351 lines
17 KiB
Markdown
# HomeHub — Plan de développement
|
||
|
||
> Approche itérative : chaque phase livre quelque chose d'utilisable.
|
||
> Stack : FastAPI · React/Vite/TS · PostgreSQL 16 · Docker Compose
|
||
|
||
---
|
||
|
||
## Phase 1 — Socle technique ✅
|
||
|
||
**Objectif** : environnement opérationnel, rien de métier, mais tout tourne.
|
||
|
||
### Backend
|
||
- [x] Structure projet FastAPI (`app/api/`, `app/core/`, `app/models/`, `app/schemas/`, `app/services/`)
|
||
- [x] Configuration SQLAlchemy 2.0 async + pool de connexions
|
||
- [x] Création des schémas PostgreSQL (`todos`, `shopping`, `notes`) via Alembic
|
||
- [x] Migration initiale : toutes les tables (voir spec section 3)
|
||
- [x] Endpoint santé : `GET /api/health`
|
||
- [x] Module media (`app/services/media.py`) :
|
||
- [x] Validation des formats acceptés (JPG, PNG, SVG, WebP, WebM, M4A)
|
||
- [x] Génération miniature Pillow : 150×150 (shopping), 300×300 (notes), 400×300 (inline)
|
||
- [x] Sauvegarde `originals/` + `thumbnails/` sur le volume
|
||
- [x] Endpoint `POST /api/media/upload` → retourne `{ file_path, thumbnail_path }`
|
||
- [x] Endpoint `DELETE /api/media/{uuid}` → supprime original + miniature
|
||
- [x] Middleware CORS pour réseau local `10.0.0.0/22`
|
||
- [x] Configuration via variables d'environnement (`.env`)
|
||
- [x] Dockerfile backend (Python 3.12 slim)
|
||
|
||
### Frontend
|
||
- [x] Scaffold Vite + React 18 + TypeScript
|
||
- [x] Tailwind CSS configuré avec les tokens Gruvbox (via `tokens.json`)
|
||
- [x] Intégration `design_system/components/ui-kit.jsx`
|
||
- [x] `@vite-pwa/plugin` configuré (Service Worker + manifest)
|
||
- [x] `manifest.json` avec icônes iOS + Android
|
||
- [x] Routage React Router v6
|
||
- [x] Layout de base : navigation mobile (bottom bar) + navigation laptop (sidebar)
|
||
- [x] Dockerfile frontend (Nginx + proxy `/api/` → backend)
|
||
- [x] Favicon maison (SVG Gruvbox orange)
|
||
|
||
### Infra
|
||
- [x] `docker-compose.yml` avec 3 services (frontend, backend, db)
|
||
- [x] Volume PostgreSQL persistant
|
||
- [x] Volume uploads persistant (`/uploads/`)
|
||
- [x] Fichier `.env.example`
|
||
- [x] Script de seed (`backend/app/data/seed.py`) : 113 produits + 9 magasins
|
||
|
||
---
|
||
|
||
## Phase 2 — Module Todos ✅
|
||
|
||
**Objectif** : créer, lister, modifier, terminer et reporter des tâches depuis mobile et laptop.
|
||
|
||
### Backend
|
||
- [x] `GET /api/todos` — liste avec filtres (domaine, statut, priorité, tags, période)
|
||
- [x] `POST /api/todos` — création
|
||
- [x] `PATCH /api/todos/{id}` — mise à jour partielle
|
||
- [x] `DELETE /api/todos/{id}` — suppression
|
||
- [x] `POST /api/todos/{id}/postpone` — incrémente postponed_count + décale due_date
|
||
- [x] Schémas Pydantic : TodoCreate, TodoUpdate, TodoResponse
|
||
- [x] Tests d'intégration (9 tests)
|
||
|
||
### Frontend Mobile
|
||
- [x] Page Todos : liste des tâches en cours, groupées par domaine
|
||
- [x] Bouton "+ Tâche" flottant → Modal création (titre + domaine + date + priorité + tags)
|
||
- [x] Double-tap → Modal édition pré-rempli
|
||
- [x] Swipe droite → marquer done
|
||
- [x] Swipe gauche → actions (reporter 1j / reporter 1 semaine / supprimer)
|
||
- [x] Badge compteur par domaine
|
||
|
||
### Frontend Laptop
|
||
- [x] Vue tableau avec filtres : domaine, statut, priorité, période
|
||
- [x] Formulaire complet (tous les champs) via Modal
|
||
- [x] Double-clic sur titre → Modal édition
|
||
- [x] Actions inline : ✓ done / +1j / +1S / ✕ supprimer
|
||
|
||
---
|
||
|
||
## Phase 3 — Module Shopping (liste de courses) ✅
|
||
|
||
**Objectif** : liste de courses fonctionnelle en magasin depuis smartphone, avec génération automatique.
|
||
|
||
### Backend
|
||
- [x] `GET /api/shopping/stores` — liste magasins
|
||
- [x] `GET /api/shopping/products` — catalogue avec recherche (param `q`)
|
||
- [x] `GET /api/shopping/lists` — listes de courses avec compteurs
|
||
- [x] `POST /api/shopping/lists` — création liste
|
||
- [x] `GET /api/shopping/lists/{id}` — détail liste avec articles
|
||
- [x] `PATCH /api/shopping/lists/{id}` — mise à jour liste
|
||
- [x] `DELETE /api/shopping/lists/{id}` — suppression
|
||
- [x] `POST /api/shopping/lists/{id}/items` — ajout article (produit catalogue ou custom)
|
||
- [x] `PATCH /api/shopping/lists/{id}/items/{item_id}` — cocher / modifier
|
||
- [x] `DELETE /api/shopping/lists/{id}/items/{item_id}` — suppression article
|
||
- [x] `POST /api/shopping/lists/{id}/finish` — terminer les courses (report articles non cochés → nouvelle liste draft avec `carried_over=True`)
|
||
- [x] `POST /api/shopping/lists/generate` — liste magique V1 (score = retard / intervalle moyen, articles cochés comme historique)
|
||
- [x] Schémas Pydantic complets (listes, articles, produits, magasins)
|
||
- [x] Tests d'intégration (10 tests)
|
||
- [x] Champ `tags TEXT[]` sur les produits du catalogue (migration 006)
|
||
|
||
### Frontend
|
||
- [x] Page Shopping avec 3 vues : liste des listes / détail / mode magasin
|
||
- [x] Vue "listes" : cartes par liste (statut, compteur articles), FAB + bouton "Liste magique"
|
||
- [x] Bouton "Liste magique" : désactivé si une liste `draft`/`active` existe
|
||
- [x] Vue "détail" : articles avec swipe-to-delete, FAB ajout article, bouton ✏️ modal gestion
|
||
- [x] Modal ✏️ : ajout rapide d'article + bouton rouge "Supprimer la liste en cours"
|
||
- [x] Vue "Mode magasin" : plein écran, grands boutons (48px+), section cochés/non cochés
|
||
- [x] Wake Lock API activée automatiquement en mode magasin
|
||
- [x] Composant `Modal` générique réutilisable (overlay + Escape + stopPropagation)
|
||
- [x] Composant `ItemRow` : checkbox circulaire + swipe-to-delete + mode magasin
|
||
- [x] Hook `useWakeLock` avec fallback gracieux (mode économie d'énergie)
|
||
- [x] Client API TypeScript typé (`frontend/src/api/shopping.ts`)
|
||
- [x] Migration TodoForm vers Modal (plus de panneau inline)
|
||
- [x] Bottom sheet multi-select pour l'ajout d'articles depuis le catalogue
|
||
- [x] Stats d'achat dans le catalogue : dernier achat + intervalle moyen par produit
|
||
- [x] Swipe gauche → modal édition (TodoPage + ShoppingPage)
|
||
- [x] Tags sur les produits : chip-input dans CatalogueModal, recherche étendue aux tags
|
||
- [x] Recherche article : `type="search"` supprime la suggestion URL iOS, `autoFocus` automatique
|
||
- [x] Upload photo produit : support HEIC/HEIF, redimensionnement 500×500 max (ratio conservé), miniature auto
|
||
- [x] Collage Ctrl+V photo dans CatalogueModal et TodoForm
|
||
|
||
---
|
||
|
||
## Phase 4 — Module Notes ✅
|
||
|
||
**Objectif** : saisie rapide de notes avec photo, audio et GPS depuis smartphone.
|
||
|
||
### Backend
|
||
- [x] `GET /api/notes` — liste avec search full-text + filtres tags/catégorie
|
||
- [x] `POST /api/notes` — création
|
||
- [x] `PATCH /api/notes/{id}` — mise à jour
|
||
- [x] `DELETE /api/notes/{id}` — suppression
|
||
- [x] `POST /api/notes/{id}/attachments` — upload fichier (image/audio)
|
||
- [x] `DELETE /api/notes/{id}/attachments/{att_id}` — suppression pièce jointe
|
||
- [x] Compression image Pillow → WebP (service media partagé)
|
||
- [x] Recherche FTS PostgreSQL français
|
||
|
||
### Frontend Mobile
|
||
- [x] Page Notes : liste chronologique avec aperçu
|
||
- [x] Bouton "+ Note" → formulaire rapide (contenu + tags) via Modal
|
||
- [x] Bouton 📷 → Camera API (capture directe ou import galerie)
|
||
- [x] Bouton 🎤 → MediaRecorder (enregistrement audio inline)
|
||
- [x] Bouton 📍 → Geolocation API → affichage coordonnées
|
||
- [x] Visionneuse photo inline + lecteur audio inline
|
||
- [ ] Compression WebP côté client (Canvas API) — différé Phase 5+
|
||
- [ ] Waveform visuel enregistrement — différé Phase 5+
|
||
|
||
### Frontend Laptop
|
||
- [x] Grille notes avec vignettes photo
|
||
- [x] Recherche full-text
|
||
- [x] Filtres rapides : avec photo, avec audio, avec GPS
|
||
- [ ] Vue carte pour les notes avec GPS (Leaflet.js) — différé Phase 5+
|
||
|
||
---
|
||
|
||
## Phase 4c — Notes v2 : états de tuile + médias ✅
|
||
|
||
**Objectif** : refonte de l'interface Notes avec tuiles à 3 états, support vidéo, transcodage audio universel.
|
||
|
||
### Backend
|
||
- [x] `ffmpeg` dans le Dockerfile backend — transcodage audio et vidéo
|
||
- [x] `save_audio()` : transcode toute entrée (webm/ogg/m4a) → AAC `.m4a` — lecture Safari iOS garantie
|
||
- [x] `save_video()` : stockage mp4/quicktime direct, webm → H.264/mp4 via ffmpeg
|
||
- [x] `ALLOWED_VIDEO_TYPES` : mp4, webm, quicktime, m4v, 3gpp
|
||
- [x] `GET /api/notes` : filtre `has_video` ajouté
|
||
- [x] `POST /api/notes/{id}/attachments` : gère `file_type = "video"`
|
||
- [x] `GET /api/admin/stats` : section `media.video` (count + size_bytes)
|
||
- [x] Schémas Pydantic notes : `gps_lat/gps_lon` passés en `float | None` (fix `Decimal` sérialisé en string → TypeError JS)
|
||
|
||
### Frontend
|
||
- [x] NoteCard à 3 états : **semi** (défaut, 3 lignes + actions) / **expanded** (markdown complet + médias) / **collapsed** (titre + date)
|
||
- [x] Bouton toggle `fa-chevron-down / fa-minus / fa-chevron-right` dans le coin haut-droit de chaque tuile
|
||
- [x] Renderer pseudo-markdown : `# ## ###`, `- * 1.` listes, `> citations`, `---`, `` **gras** *italique* `code` ``, ` ``` ` blocs
|
||
- [x] Icônes méta sur la tuile : `fa-image` / `fa-microphone` / `fa-video` / `fa-location-dot`
|
||
- [x] Bouton vidéo `fa-video` dans les actions de chaque note
|
||
- [x] Lecteur `<video playsInline>` inline dans l'état expanded
|
||
- [x] Audio : `onLoadedMetadata` règle le volume à 50% + fix overflow (`minWidth: 0`)
|
||
- [x] Filtres rapides : Photo / Audio / Vidéo / GPS (avec icônes Font Awesome)
|
||
- [x] Grille : 3 colonnes max sur laptop (`repeat(3, 1fr)`), 1 colonne sur mobile
|
||
- [x] Bouton "Nouvelle note" dans le header, visible sur laptop uniquement
|
||
- [x] Sidebar laptop : indicateur de statut BDD (LED verte/rouge + taille) avec polling 30s sur `/api/health`
|
||
- [x] ConfigPage : grille médias 3 colonnes (Photos / Audio / Vidéos)
|
||
- [x] `client_max_body_size nginx` : 15m → 200m pour les vidéos
|
||
- [x] `docker-compose.yml` : `user: "1000:1000"` sur backend et backend-worker
|
||
|
||
---
|
||
|
||
## Phase 4b — UX transversale ✅
|
||
|
||
**Objectif** : améliorations d'expérience applicables à tous les modules.
|
||
|
||
### Thème et apparence
|
||
- [x] `ThemeContext` : gestion dark / light / system avec persistance `localStorage`
|
||
- [x] Script anti-flash dans `index.html` : thème et zoom appliqués avant le premier rendu
|
||
- [x] Page `/config` : boutons thème + slider police (0.8–1.4, pas de 0.05)
|
||
- [x] Aperçu temps réel du texte sur le slider
|
||
- [x] Zoom CSS sur `<html>` : scale global compatible avec les tailles en pixels fixes
|
||
- [x] `TopBar` : en-tête fixe 44px avec bouton de thème cyclique (lune / soleil / demi-cercle)
|
||
- [x] Tuile "Paramètres" sur la page d'accueil (`/config`, icône `fa-sliders`)
|
||
|
||
### Mobile — clavier iOS
|
||
- [x] `BottomSheet` : `visualViewport` API pour remonter le panneau au-dessus du clavier virtuel
|
||
- [x] Transition fluide (0.15s) sur `bottom`, `max-height`, `border-radius` à l'ouverture du clavier
|
||
|
||
### Todos
|
||
- [x] Case "Pas de date" pré-cochée par défaut dans `TodoForm` (champ date masqué)
|
||
- [x] Collage Ctrl+V pour coller une image directement dans le formulaire Todo
|
||
|
||
### Infra / Media
|
||
- [x] nginx : `location ^~ /media/` pour éviter que la règle regex WebP prenne la priorité
|
||
- [x] nginx : `client_max_body_size 15m` pour les photos de smartphone (3–8 Mo)
|
||
|
||
---
|
||
|
||
## Phase 5 — Export Markdown notes + Backup BDD ✅
|
||
|
||
**Objectif** : persistance double des notes (BDD source de vérité + fichiers Markdown partagés), infrastructure Redis, backup/restore depuis l'interface.
|
||
|
||
### Architecture
|
||
- Volume Docker `./data/` (bind mount) remplace le volume nommé `uploads`
|
||
- `data/notes/` — fichiers `.md` auto-générés
|
||
- `data/uploads/` — médias (photos, audio)
|
||
- `data/backup/` — dumps PostgreSQL
|
||
|
||
### Backend
|
||
- [x] Service Redis : `redis:7-alpine` dans docker-compose
|
||
- [x] Worker ARQ (`backend-worker`) : consomme la queue `notes:markdown`, même image que backend
|
||
- [x] `app/core/redis.py` — pool ARQ, `enqueue()` best-effort (silence si Redis down)
|
||
- [x] `app/workers/notes_worker.py` — tâches `export_note_markdown` + `remove_note_markdown`
|
||
- [x] `app/services/notes_markdown.py` — génère le frontmatter YAML + contenu + pièces jointes (liens relatifs `../uploads/…`)
|
||
- Nom de fichier : `YYYY-MM-DD_slug-du-titre_shortid.md`
|
||
- Rotation automatique si titre modifié (recherche par `*_{shortid}.md`)
|
||
- [x] `app/api/notes.py` — publie sur Redis après create / update / delete / add_attachment / delete_attachment
|
||
- [x] `app/api/admin.py` — `POST /api/admin/backup`, `GET /api/admin/backups`, `POST /api/admin/restore/{filename}`
|
||
- [x] `backend/Dockerfile` — ajout `postgresql-client` pour pg_dump / pg_restore
|
||
- [x] `requirements.txt` — ajout `arq==0.26.1`
|
||
|
||
### Frontend
|
||
- [x] `frontend/src/api/admin.ts` — client TypeScript backup/restore
|
||
- [x] Page Config — section "Base de données" : bouton sauvegarde + liste des dumps + bouton Restaurer par fichier
|
||
|
||
---
|
||
|
||
## Phase 6 — Scan produits + enrichissement catalogue
|
||
|
||
**Objectif** : scan code-barres depuis mobile, auto-remplissage depuis OpenFoodFacts.
|
||
|
||
### Services Docker
|
||
- [ ] Dockerfile `product-search/` : Python slim + `requests` + client OpenFoodFacts
|
||
- [ ] Ajout service `product-search` dans `docker-compose.yml` (port 8002)
|
||
- [ ] Ajout service `searxng` dans `docker-compose.yml` (images uniquement)
|
||
|
||
### Backend (endpoints proxy)
|
||
- [ ] `GET /api/products/lookup?barcode=` → proxifie vers product-search
|
||
- [ ] `GET /api/products/search?q=` → proxifie vers product-search
|
||
- [ ] `POST /api/products/import` → crée produit depuis données OpenFoodFacts
|
||
|
||
### Frontend Mobile — scan
|
||
- [ ] Intégration `zxing-js` (BarcodeReader via flux caméra)
|
||
- [ ] Bouton "Scanner" dans le formulaire d'ajout de produit
|
||
- [ ] Sur scan réussi → auto-remplissage formulaire
|
||
|
||
### Frontend Laptop — enrichissement catalogue
|
||
- [ ] CRUD complet produits dans catalogue
|
||
- [ ] Recherche texte → OpenFoodFacts → import 1 clic
|
||
- [ ] Gestion magasins
|
||
|
||
---
|
||
|
||
## Phase 7 — Service OCR (conteneur dédié)
|
||
|
||
**Objectif** : OCR partagé, prérequis pour shopping avancé et notes avancées.
|
||
|
||
- [ ] Dockerfile `ocr/` : Python slim + Tesseract 5 + langues (fra, eng) + Pillow
|
||
- [ ] `POST /extract` : reçoit image (multipart), retourne `{ text, confidence }`
|
||
- [ ] Endpoint backend `POST /api/ocr/extract` : proxifie vers `ocr:8001`
|
||
- [ ] Ajout du service `ocr` dans `docker-compose.yml`
|
||
|
||
---
|
||
|
||
## Phase 8 — Shopping avancé (OCR + suivi prix)
|
||
|
||
### Backend
|
||
- [ ] `POST /api/shopping/ocr/price-tag` — photo étiquette → extraction prix
|
||
- [ ] `POST /api/shopping/ocr/receipt` — photo ticket → reconciliation liste
|
||
- [ ] `GET /api/shopping/products/{id}/price-history` — historique prix par produit + magasin
|
||
|
||
### Frontend
|
||
- [ ] Bouton 📷 sur chaque article → OCR étiquette → pré-remplissage prix
|
||
- [ ] Graphique d'évolution du prix par produit (Recharts)
|
||
- [ ] Comparaison prix par magasin
|
||
|
||
---
|
||
|
||
## Phase 9 — MCP Server
|
||
|
||
**Objectif** : exposer les outils HomeHub aux agents IA (Hermes, Claude, etc.).
|
||
|
||
- [ ] Intégration SDK MCP Python (`mcp` package)
|
||
- [ ] Endpoint SSE `/mcp`
|
||
- [ ] Implémentation des outils : `get_todos()`, `add_todo()`, `add_shopping_item()`, `search_notes()`
|
||
- [ ] Documentation OpenAPI des outils MCP
|
||
|
||
---
|
||
|
||
## Phases futures (hors scope initial)
|
||
|
||
### Phase 9 — Authentification multi-utilisateurs
|
||
- Auth JWT (login / refresh token)
|
||
- Activation de `owner_id` dans toutes les tables
|
||
- Page connexion React
|
||
|
||
### Phase 10 — Calendrier + intégrations
|
||
- Sync Google Calendar (OAuth2 + worker)
|
||
- Endpoint CalDAV pour iOS
|
||
- Redis pour la queue de synchronisation
|
||
- Webhooks Gitea → Kanban
|
||
- Home Assistant : capteur "tâches en retard"
|
||
|
||
### Phase 11 — Vision IA
|
||
- Endpoint analyse frigo (photo → suggestions liste de courses)
|
||
- Amélioration OCR via modèle Vision local (Ollama)
|
||
|
||
### Phase 12 — Éditeur Markdown pour les Notes
|
||
|
||
Refonte du module Notes autour d'un vrai éditeur Markdown orienté mobile.
|
||
|
||
**Concept** : une seule interface unifiée — champ titre + corps éditeur — avec une barre d'outils flottante au-dessus du clavier.
|
||
|
||
**Barre d'outils (ordre d'affichage)** :
|
||
- `H` — Titre (`# `)
|
||
- `•` — Liste à puces (`- `)
|
||
- `1.` — Liste numérotée (`1. `)
|
||
- `</>` — Bloc code (`` ` `` inline ou ` ``` ` bloc)
|
||
- 📷 — Insérer image (upload direct, syntaxe ``)
|
||
- 🎤 — Enregistrement audio (MediaRecorder, lien `[enregistrement.m4a](path)`)
|
||
- 📍 — Position GPS (insère `[GPS](geo:lat,lon)` ou coordonnées brutes)
|
||
|
||
**Rendu** : aperçu Markdown en lecture (pas d'édition WYSIWYG pure — mode split ou toggle édit/aperçu).
|
||
|
||
**Contraintes techniques** :
|
||
- Bibliothèque candidate : `@uiw/react-md-editor` (légère, compatible mobile) ou éditeur custom avec `textarea` + parsing `marked`/`micromark`
|
||
- La barre d'outils doit rester au-dessus du clavier virtuel iOS/Android (même logique que `BottomSheet` avec `visualViewport`)
|
||
- Compatibilité avec le format des fichiers `.md` déjà exportés en Phase 5 (frontmatter YAML conservé)
|
||
- Le backend ne change pas : `content` reste un champ texte libre (Markdown brut stocké tel quel)
|
||
|
||
---
|
||
|
||
## Ordre de développement
|
||
|
||
```
|
||
Phase 1 ✅ → Phase 2 ✅ → Phase 3 ✅ → Phase 4 ✅ → Phase 4b ✅ → Phase 5 ✅ → Phase 6 (Scan) → Phase 7 (OCR) → Phase 8 (Shopping avancé) → Phase 9 (MCP)
|
||
```
|