Files
siop2/docs/journal/journal.md
pr-daaif ac8b92e9c5 feat(r6): R6.5 — Assistant mobile (chat sourcé + dictée)
Maquette dédiée validée (docs/02-design/maquettes/maquette-mobile-assistant.html,
3 écrans) — la seule sous-étape de R6 à avoir eu son propre tour de
design-first plutôt qu'une exécution directe de la maquette R6 initiale,
un chat sourcé étant un patron d'écran neuf. D1 un échange à la fois ; D2
"Voir le document" → métadonnées seules (R6.3), "Ouvrir l'OT" → fiche R4 ;
D3 refus chiffré + avertissement permanents, identiques au web ; D4
Assistant rejoint Pilotage pour tout rôle avec WORK_ORDERS.view ; D5
(ajoutée en revue) — la question peut être tapée OU dictée, même pipeline
que la dictée déjà livrée en clôture (ADR-004 §5, faster-whisper local,
audio jamais persisté), la transcription remplit le champ, jamais d'envoi
automatique.

api/assistant.ts (useAskAssistant, 503 géré comme le web). Écran Assistant :
chat un échange à la fois, citations numérotées, sources avec extrait
exact, refus honnête chiffré + Reformuler. Micro dans la barre de saisie :
réutilise telle quelle la mécanique de cloture.tsx (useAudioRecorder,
setAudioModeAsync, useTranscription, purge quoi qu'il arrive) — pipeline
déjà recetté sur iPhone physique (01/08), aucun nouveau bug à découvrir.

Fiche document (bibliotheque/[id].tsx, nouveau) — la liste R6.3 y mène
aussi désormais. Menu : Assistant route réellement — le Menu R6 n'a plus
d'entrée "à venir" dans les 4 groupes (Catégories exceptée, admin, hors
périmètre mobile).

R6 fonctionnellement complet (Exploitation/Parc/Ressources/Pilotage,
Assistant inclus, pour tous les rôles) — reste une recette complète avant
de considérer R6 close au sens R0→R5.

Typecheck propre, 17 tests Jest, lint 5/5 paquets. Contrat non touché.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 12:18:25 +01:00

97 KiB
Raw Blame History

Journal de bord — SIOP V2

Trace chronologique des sessions (la plus récente en premier). Le playbook (docs/0X-*) est le livre ; ici, c'est le quotidien.


2026-08-02 — Pr. Daaif (+ Claude) — R6.5 : Assistant mobile (chat sourcé + dictée)

Actions

  • Maquette dédiée (docs/02-design/maquettes/maquette-mobile-assistant.html, 3 écrans) rédigée et validée par le référent : réponse sourcée, refus honnête chiffré, et — demande ajoutée en cours de revue — dictée de la question. Décisions actées : D1 un échange à la fois (pas d'historique multi-tours conservé, même limite que le web) ; D2 « Ouvrir l'OT » → fiche R4 telle quelle, « Voir le document » → métadonnées seules (bibliothèque R6.3, pas d'ouverture de fichier tant qu'expo-sharing n'est pas ajouté) ; D3 refus chiffré + avertissement permanents, identiques au web ; D4 Assistant rejoint le groupe Pilotage pour tout rôle avec WORK_ORDERS.view ; D5 la question peut être tapée OU dictée — même pipeline que la dictée déjà livrée en clôture (ADR-004 §5, faster-whisper local, audio jamais persisté) — la transcription REMPLIT le champ de saisie, éditable, jamais d'envoi automatique.
  • api/assistant.ts (useAskAssistant, 503 géré comme le web — état attendu du contrat, pas une erreur).
  • Écran Assistant (app/assistant/index.tsx) : chat à un échange à la fois, citations numérotées, cartes sources (extrait exact + « Voir le document »/« Ouvrir l'OT »), refus honnête chiffré avec « Reformuler », avertissement permanent. Micro dans la barre de saisie : réutilise telle quelle la mécanique de cloture.tsx (useAudioRecorder, setAudioModeAsync, useTranscription, purge FileSystem.deleteAsync quoi qu'il arrive) — aucun nouveau bug à découvrir, le pipeline est déjà recetté sur iPhone physique (01/08).
  • Fiche document (app/bibliotheque/[id].tsx, nouveau) : métadonnées seules, destination de « Voir le document » — la liste bibliotheque/index.tsx (R6.3) y mène aussi désormais (les cartes étaient jusque-là décoratives, sans action).
  • Menu : Assistant route réellement — le Menu R6 n'a plus aucune entrée « à venir » dans les 4 groupes (Catégories excepté, admin, toujours hors périmètre mobile).
  • Vérifié : typecheck propre, 17 tests Jest, lint 5/5 paquets. Contrat non touché.

Décisions

  • Voir D1-D5 ci-dessus (maquette dédiée, seule sous-étape de R6 à avoir eu son propre tour de design-first plutôt qu'une exécution directe de la maquette R6 initiale).

Prochaine étape : R6 fonctionnellement complet (Exploitation/Parc/Ressources/Pilotage, Assistant inclus, pour tous les rôles). Reste la recette complète avant de considérer R6 close au sens R0→R5 (chaque sous-étape n'a été vérifiée que par typecheck/tests/lint jusqu'ici, jamais en usage réel au-delà de la confirmation R6.1) ; expo-sharing/expo-clipboard en réserve pour une prochaine étape native ; restes non bloquants inchangés (Dokploy, Android physique, AI_SEUIL_*/darija, DOKPLOY_WEBHOOK_URL).


2026-08-02 — Pr. Daaif (+ Claude) — Confirmation référent : nav adaptative + déconnexion

Actions

  • Le référent confirme sur son iPhone (app déjà connectée au serveur Metro) : la barre d'onglets adaptative (R6.1) et le correctif de déconnexion (EnteteTabs, tap simple + confirmation depuis n'importe quel onglet) fonctionnent tels que livrés. Dernier point resté ouvert depuis le début de R6 — clos.

Décisions

  • Aucune.

Prochaine étape : une recette plus complète (parcourir Sites/Ascenseurs/Stock/Tiers/ Statistiques/Personnes sur au moins 2 rôles non-technicien) reste à faire avant de considérer R6 close au même sens que R0→R5 (chacune avait sa recette chiffrée avant tag) — pour l'instant chaque sous-étape n'a été vérifiée que par typecheck/tests/lint, jamais en usage réel au-delà de ce point précis. Sinon : maquette dédiée pour l'Assistant mobile, expo-sharing/expo-clipboard en réserve.


2026-08-02 — Pr. Daaif (+ Claude) — R6.4 : Pilotage sur mobile (Statistiques, Personnes)

Actions

  • EXPO_PUBLIC_WEB_URL (nouveau, .env) + WEB_URL (api/client.ts) : le lien d'activation « Personnes » pointe la page web /activation?token=… (il n'existe pas d'équivalent mobile, même page que R1) — variable par environnement comme EXPO_PUBLIC_API_URL.
  • apps/mobile/src/api/pilotage.ts (nouveau) : useAnalyticsSummary, useUsers, useRoles, useInviteUser.
  • Écran Statistiques (app/statistiques/index.tsx) : maquette écran 7 — sélecteur de période 3/6/12 mois, coût du mois, taux préventif, OT clôturés, pannes par organe (top 5), top équipements en coût. Mêmes chiffres que /analytics/summary (web), en cartes plutôt qu'en graphes denses (D4).
  • Écran Personnes & équipes (app/personnes/index.tsx) : liste avec statut (actif/invitation envoyée/désactivé) ; Inviter (app/personnes/inviter.tsx — nom, e-mail, rôle) ; lien d'activation émis (app/personnes/lien.tsx) affiché en texte sélectionnable (Text selectable, RN natif) plutôt que via presse-papiers — expo-clipboard est une dépendance native absente du projet, même raisonnement que expo-sharing en R6.3 : différée plutôt qu'ajoutée à la légère, un appui long suffit pour copier. Taux horaire, rôles, équipes restent gérés depuis le web (D4).
  • Menu : Statistiques et Personnes & équipes routent réellement. Assistant reste à venir — un chat sourcé (citations, refus honnête) est un nouveau patron d'écran, jamais maquetté sur mobile (contrairement à Sites/Ascenseurs/Stock/Tiers/Personnes qui réutilisaient des patrons déjà validés Carte/LigneInfo/EnteteFiche) : mérite son propre tour de design-first plutôt que d'être exécuté par réflexe en fin de release.
  • Vérifié : typecheck propre, 17 tests Jest, lint 5/5 paquets. Contrat non touché.

Décisions

  • Aucune nouvelle sur le fond — exécution de la maquette R6 déjà validée. Deux mêmes arbitrages que R6.3 reconduits explicitement : pas de nouvelle dépendance native au milieu d'une passe (expo-clipboard comme expo-sharing), et l'Assistant IA mobile est repoussé à un maquettage dédié plutôt que forcé dans le périmètre « actions courantes » de R6.

Prochaine étapeR6 quasi close : Exploitation/Parc/Ressources/Pilotage (hors Assistant) livrés pour tous les rôles. Restes : confirmation du référent sur iPhone (R6.1 + correctif déconnexion, toujours en attente — condition de la clôture réelle de R6) ; maquette dédiée pour l'Assistant mobile (question ouverte : l'ajouter à R6 ou une release à part) ; expo-sharing et expo-clipboard pour les flux mobiles restés en lecture seule ; restes non bloquants inchangés (Dokploy, Android physique, AI_SEUIL_*/darija, DOKPLOY_WEBHOOK_URL).


2026-08-02 — Pr. Daaif (+ Claude) — R6.3 : Ressources sur mobile (Stock, Tiers, Fichiers)

Actions

  • apps/mobile/src/api/ressources.ts (nouveau fichier, mêmes opérations que gestion.ts côté web) : usePartners, useParts/usePart, useCreatePurchaseOrder, useDocuments (bibliothèque complète, sans filtre — distincte de useDocumentsOT déjà scopée à un OT).
  • Écran Stock (app/stock/index.tsx) : pièces sous seuil en tête (même logique que stock.tsx web), badge compteur. Fiche pièce (app/stock/[id].tsx) : identité + « Commander » si PURCHASE_ORDERS.create ET fournisseur associé — sinon message explicite plutôt qu'un bouton mort. Nouveau BC (app/stock/[id]/commander.tsx) : une seule ligne pré-remplie depuis l'alerte (fournisseur figé, quantité par défaut = manquant jusqu'au seuil, prix par défaut = dernier prix connu) — le bon de commande multi-lignes détaillé reste au web (D4).
  • Écran Tiers (app/tiers/index.tsx) : liste fournisseurs/clients, lecture seule sur mobile pour cette passe (D4 — création/édition réservées au web).
  • Écran Fichiers (app/bibliotheque/index.tsx) : métadonnées seulement (nom, taille, rattachement), pas d'ouverture/téléchargement — ce flux demande expo-sharing (dépendance native absente du projet) et donc un nouveau cycle de build natif ; reporté plutôt qu'ajouté à la légère au milieu de cette passe. Le bandeau de l'écran le dit explicitement.
  • Menu : Stock & achats / Tiers / Fichiers routent réellement.
  • Vérifié : typecheck propre, 17 tests Jest, lint 5/5 paquets. Contrat (schema.d.ts) non touché — toutes les opérations utilisées existaient déjà (R3).

Décisions

  • Aucune nouvelle — Ressources exécute la maquette R6 déjà validée (écran 6, D4) ; Tiers/Fichiers n'y étaient pas maquettés en détail mais suivent la même logique de consultation déjà actée, avec les patrons visuels déjà validés (Carte/LigneInfo/EnteteFiche).
  • L'ouverture de document sur mobile est explicitement différée à une prochaine étape (nouvelle dépendance expo-sharing = nouveau build natif à valider, pas à empiler sur cette passe).

Prochaine étape : R6.4 (Pilotage — Statistiques, Personnes, Assistant) ; confirmation du référent sur iPhone pour R6.1/logout, toujours en attente.


2026-08-02 — Pr. Daaif (+ Claude) — R6.2 : Parc sur mobile (Sites, Ascenseurs)

Actions

  • useLocations() mobile (GET /locations, même liste plate que le web — le client construit l'arbre site → zone).
  • Écran Sites (app/sites/index.tsx) : sites de premier niveau (parentId === null), nom, ville/adresse, nombre d'appareils.
  • Fiche site (app/sites/[id].tsx) : identité (adresse, gardien, client/syndic), zones avec leurs appareils, liste des ascenseurs du site — même filtre siteName que fiche-site.tsx (web). Pas de carte interactive ni d'édition sur mobile (D4 — consultation seule pour cette famille, réservé au web pour l'instant).
  • Écran Ascenseurs (app/ascenseurs/index.tsx) : le parc complet, déjà préchargé (D1 lecture locale, même donnée que le Scanner), trié par référence. Ouvre la fiche appareil R4 telle quelle (ascenseur/[id].tsx, déjà générique — aucune modification nécessaire, elle dérive déjà ce qu'elle affiche du rôle appelant côté serveur).
  • Menu : « Ascenseurs » et « Sites » routent maintenant vers ces écrans réels (retirés de la liste « à venir ») ; « Catégories » reste à venir (admin, hors périmètre R6.2).
  • Vérifié : typecheck propre, 17 tests Jest, lint 5/5 paquets.

Décisions

  • Aucune nouvelle — exécution directe de la maquette R6 déjà validée (Parc = D4, consultation seule) ; pas de nouveau tour de maquette pour cette sous-étape, les patrons visuels réutilisés (Carte/LigneInfo/EnteteFiche) sont déjà validés depuis R4.

Prochaine étape : R6.3 (Ressources — Stock, Tiers, Fichiers) ; confirmation du référent sur iPhone pour R6.1 et le correctif déconnexion, toujours en attente.


2026-08-02 — Pr. Daaif (+ Claude) — R6 lancée : mobile ouvert à tous les rôles

Contexte — deux retours du référent en test réel sur iPhone : (1) une fois authentifié, impossible de revenir en arrière ou de se déconnecter ; (2) l'app mobile devrait être accessible à tous les rôles, pas seulement au Technicien, avec les mêmes actions que le web. Le premier est un bug, corrigé dans la foulée ; le second est un changement de fond — R4 avait délibérément fermé le périmètre au Technicien (décision actée dans ses maquettes). Ouvrir à tous les rôles veut dire porter sur mobile une bonne partie de ce que fait le web (référentiel, ressources, personnes, statistiques…) : plus gros que R4 dans son ensemble, donc une nouvelle release, R6 (R4 est close depuis le 17/07, R4.1/R4.2/R4.3 déjà pris par le socle mobile technicien d'origine — pas question de les réutiliser et de brouiller l'historique).

Actions

  • Correctif déconnexion : EnteteTabs (composants/ui.tsx) factorise une entête commune aux 4 onglets terrain — badge de compte + confirmation Alert au tap simple (l'ancien geste, onLongPress sur « Ma journée » seulement, n'était pas découvrable). Le rôle affiché vient du compte connecté, plus de libellé « Technicien » figé.
  • Maquette R6 (docs/02-design/maquettes/maquette-mobile-tous-roles.html, 7 écrans) rédigée et validée par le référent le jour même : synthèse par rôle, Accueil adaptatif, Menu groupé filtré par la matrice, OT côté Dispatcher, Demandes (création + approbation), Stock, Personnes & statistiques. Cinq décisions actées : D1 barre d'onglets adaptative (Technicien/Technicien limité inchangés + onglet Menu ajouté) ; D2 le Menu reprend à l'identique les 4 groupes du web (Exploitation/Parc/Ressources/Pilotage), même matrice, aucune règle de droit nouvelle ; D3 un groupe sans lien visible est masqué en entier (contrairement au web) ; D4 le mobile porte les actions courantes par famille, pas les flux de gestion les plus denses (réservés au web pour l'instant) ; D5 aucune logique de permission propre au mobile — un hook usePermissions() calqué sur celui du web.
  • R6.1 — socle : usePermissions() mobile (lit me.permissions, déjà renvoyé par /users/me) ; barre d'onglets adaptative (ongletsVisibles par rôle, href: null pour masquer sans retirer du navigateur — le Menu peut toujours y pousser) ; écrans Accueil (dashboard réel pour Administrateur/Gestionnaire/Dispatcher/Vue seule — aucun chiffre inventé, seuls WORK_ORDERS/REQUESTS sont câblés), OT (liste complète, viewOther déjà géré serveur, réutilise la fiche OT R4 telle quelle — elle dérive déjà ses actions de allowedTransitions), Menu (groupes filtrés, groupe vide masqué), Demandes (PanneauDemandes, un seul composant pour tous les rôles : création + suivi pour le Demandeur, approbation/rejet à motif pour Gestionnaire/Dispatcher/Administrateur, lecture seule pour Vue seule — même patron que le web, découverte au passage : GET /assets/options déjà ouvert à tout rôle authentifié, réutilisé tel quel). Familles pas encore portées (Sites, Ascenseurs, Stock, Tiers, Fichiers, Statistiques, Assistant, Personnes) : écran « à venir » honnête (composants/a-venir.tsx, déjà présent mais jamais câblé jusqu'ici) plutôt qu'un lien mort — chacune mérite son propre passage, sur le modèle R4.1→R4.3.
  • Vérifié : typecheck propre, 17 tests Jest verts, lint 5/5 paquets. Pas de vérification visuelle en navigateur de mon côté (aucun outil de ce type dans cet environnement) — à confirmer par le référent sur l'iPhone déjà connecté au serveur Metro.

Décisions

  • Nouvelle release R6 (pas R4.1/4.2/4.3, déjà pris ; pas de réouverture de R4, clos) pour tout le chantier « mobile ouvert à tous les rôles ». Sous-étapes à venir : R6.2 (Parc — Sites, Ascenseurs), R6.3 (Ressources — Stock, Tiers, Fichiers), R6.4 (Pilotage — Statistiques, Personnes, Assistant), sur le modèle des sous-releases R4.1→R4.3.

Prochaine étape : R6.2 (Parc sur mobile) ; confirmation du référent sur iPhone (nav adaptative et correctif déconnexion) ; restes non bloquants inchangés (recette Android physique, redéploiement Dokploy, calibrage AI_SEUIL_*/darija, DOKPLOY_WEBHOOK_URL).


2026-08-01 — Pr. Daaif (+ Claude) — Dictée validée sur iPhone physique (ADR-004 §5)

Actions

  • Reprise du reste laissé le 22/07 : le test tactile de la dictée sur iPhone, alors bloqué par une connexion USB qui ne se rétablissait pas. Diagnostic cette fois : system_profiler (l'outil utilisé pour vérifier la détection USB) était lui-même en panne et renvoyait un faux négatif silencieux — ioreg a révélé que l'iPhone était en réalité détecté. Le vrai câble/port n'a jamais été le problème.
  • Une fois l'app reconstruite (certificat gratuit expiré après plus d'une semaine — régénéré via le pipeline officiel expo run:ios, jamais par un contournement manuel), deux bugs réels trouvés et corrigés :
    1. expo-audio sur iOS refuse d'enregistrer sans un appel explicite à setAudioModeAsync({ allowsRecording: true }) avant recorder.record() — absent du code initial, jamais détecté hors d'un vrai appareil.
    2. Le correctif des modules Expo/RN précompilés (ADR-005, EXPO_USE_PRECOMPILED_MODULES=0 / RCT_USE_PREBUILT_RNCORE=0) n'avait jamais été committé dans apps/mobile/.env, contrairement au correctif FormData voisin déjà permanent — un vrai trou qui aurait refait planter l'app à la prochaine reconstruction depuis zéro. Corrigé : les deux variables vivent maintenant côte à côte dans .env.
  • Recette réelle bout en bout : micro → dictée d'une phrase → transcription fidèle (faster-whisper réel) → relecture → sauvegarde dans le bilan → clôture de l'OT → réindexation du corpus → le contenu dicté retrouvé par la recherche sémantique (score 0,476). La boucle complète de l'idée d'origine (« cette transcription devrait alimenter le corpus ») est validée sur vrai matériel, vraie voix, vrai réseau.
  • Infra locale relancée depuis zéro après une semaine d'inactivité (Docker, conteneurs siop2-*, API, service IA) — occasion de vérifier que le redémarrage à froid fonctionne proprement.

Décisions

  • Aucune — cette session referme un reste déjà décidé, sans nouvelle décision de conception.

Prochaine étape : recette Android sur appareil physique (toujours en attente d'un appareil) ; redéploiement Dokploy (AI_SERVICE_TOKEN) ; calibrage AI_SEUIL_* et qualité darija sur corpus SPELEV réel ; secret DOKPLOY_WEBHOOK_URL.


2026-07-22 — Pr. Daaif (+ Claude) — Dictée implémentée : audio → transcription locale → corpus (ADR-004 §5)

Actions

  • Suite du feu vert « on passe à l'audio » (maquette Voix amendée le 22/07) : implémentation complète de la dictée (R5 D5) — écran maquetté jamais construit jusqu'ici, ni open source ni local en LLM externe, choix du référent réaffirmé (« toujours pour l'open source et le local »).
  • apps/ai : faster-whisper (CTranslate2, CPU, MIT), AI_TRANSCRIPTION=off|locale|deterministe (défaut off), AI_TRANSCRIPTION_MODEL (défaut small) ; endpoint /internal/transcrire — l'audio ne survit JAMAIS à l'appel (fichier temporaire supprimé quoi qu'il arrive, succès ou erreur) ; indexer_bilans inclut désormais InterventionReport.note (anonymisée par le même pipeline D4) — le champ existait en base et au contrat depuis R2 mais n'avait jamais eu d'écran ; 29 pytest (dont le transcripteur déterministe pour CI).
  • Contrat (77 opérations) : POST /assistant/transcribe (multipart, TranscriptionResult).
  • API NestJS : proxy multipart vers siop2-ai (AssistantService.transcribe), WORK_ORDERS.edit — même droit que la saisie du bilan qu'elle alimente ; 2 tests e2e ajoutés (80 tests API au total).
  • Mobile : expo-audio (enregistrement) + expo-file-system (purge locale) ; carte « Décrire pour suggérer » de l'écran de clôture gagne un bouton dicter/terminer, une confirmation de purge, et « Joindre la description à l'OT » (sauvegarde dans note via la file existante — corrige au passage un bug latent : enfilerBilan ignorait silencieusement toute mise à jour de note).
  • Docker : siop2-ai embarque désormais aussi le modèle Whisper au build (image 1,54 Go → 2,19 Go) — construit et vérifié réellement (transcription en conteneur, non-root, 0 téléchargement au démarrage).
  • Vérification réelle (voix de synthèse macOS say, français) : transcription fidèle en direct (TranscripteurLocal), bout en bout via l'API NestJS, et dans le conteneur Docker construit — puis chaîne complète corpus confirmée : note sauvegardée → OT clôturé → réindexation → contenu retrouvé par recherche sémantique avec un bon score de pertinence.
  • Nettoyage : la base de dev locale, polluée par les tests manuels de la recette terrain (deux OT seedés clôturés en dehors de leur état d'origine), a été réinitialisée avec l'accord explicite du référent (prisma migrate reset --force, bloqué par défaut pour un agent IA — consentement demandé et obtenu avant exécution).

Décisions

  • Pas de prototype de mesure français/darija avant l'implémentation (confirmé une seconde fois) — seuls des contrôles d'ingénierie de base (le code tourne, avec de la vraie parole) ont été faits, pas un calibrage.
  • Reste : le test tactile sur iPhone physique (bouton dicter, permission micro) n'a pas pu se jouer — blocage USB persistant malgré câble/port/redémarrage/mode développeur essayés à plusieurs reprises. Reporté comme la recette Android, sur décision du référent — ne bloque pas la suite.

Prochaine étape : test tactile de la dictée sur iPhone dès que la connexion USB fonctionnera ; recette Android sur appareil physique ; redéploiement Dokploy (AI_SERVICE_TOKEN) ; calibrage AI_SEUIL_* et qualité darija sur corpus SPELEV réel.


2026-07-22 — Pr. Daaif (+ Claude) — Idée backlog : transcription audio → corpus (maquette amendée, pas codée)

Actions

  • Discussion de conception (aucun code) : étendre l'écran « Voix » de R5 (dictée, jamais construite — option « si budget temps ») pour que la transcription relue, une fois jointe à l'OT, alimente aussi le corpus de l'assistant à la clôture — pas seulement la description libre ponctuelle de la suggestion.
  • Écarté : Ollama local pour les embeddings du corpus (déjà 100 % locaux via fastembed, aucun gain — pertinent plutôt pour remplacer l'API Claude opt-in de génération, piste non creusée).
  • Moteur retenu pour une future transcription locale : faster-whisper (CTranslate2, MIT, CPU) — même philosophie que les embeddings. Point de vigilance identifié : Whisper transcrit mal le darija, probable en mélange avec le français sur le terrain.
  • Point d'accroche trouvé dans le schéma existant : InterventionReport.note (texte libre) existe déjà en base et en UI mais n'est pas encore indexé dans le corpus (indexer_bilans ne reprend que les libellés codés).
  • Gap identifié dans anonymisation.py : reconnaît seulement les noms CONNUS de la base, pas de NER générale — risque de fuite plus élevé sur de la parole libre que sur des libellés codés.
  • Maquette amendée et validée (maquette-r5.html, écran 6 « Voix ») : bandeau d'intention et carte « Et ensuite » disent maintenant explicitement que le texte (anonymisé) rejoint le corpus à la clôture de l'OT.

Décisions

  • Pas de prototype de mesure préalable (contrairement au calibrage des embeddings R5) — le calibrage français/darija se fera plus tard si besoin.
  • Implémentation non demandée pour l'instant — la maquette est validée, le code attend un prochain feu vert.

Prochaine étape : reprendre sur demande du référent — implémentation cohérente avec le flux esquissé (audio jamais persisté, transcription → InterventionReport.note → corpus à la clôture), moteur et calibrage déjà tranchés, pas à rediscuter.


2026-07-19 — Pr. Daaif (+ Claude) — Recette terrain mobile sur iPhone 15 Pro : les 7 points validés (ADR-005)

Actions

  • La recette « mode avion » due depuis release/r4 a enfin pu se jouer sur un vrai appareil (iPhone 15 Pro du référent). Premier obstacle immédiat : Expo Go (App Store, dernière version) refuse le projet — « supported SDK 54 » contre notre SDK 57, retard d'approbation Apple structurel, hors de notre contrôle. Décision du référent : construire des builds natifs locaux (Xcode/Android Studio, signature gratuite via Apple ID personnel) pour la vraie recette terrain, tout en conservant Expo Go comme canal léger pour un aperçu sans installation ailleurs — actée dans ADR-005.
  • Cinq obstacles techniques réels rencontrés et corrigés en route (détaillés dans ADR-005) : ciblage par UDID plutôt que nom d'appareil (apostrophe) ; ne jamais contourner expo run:ios par un xcodebuild manuel (a produit un crash dyld: Library not loaded React.framework) ; incompatibilité des modules Expo/RN précompilés (SDK 56/57) avec la liaison statique du projet — fix EXPO_USE_PRECOMPILED_MODULES=0 + RCT_USE_PREBUILT_RNCORE=0 ; fetch global (expo/fetch) incompatible avec l'upload multipart natif (Unsupported FormDataPart implementation, classé à tort « réseau indisponible » par notre propre gestion d'erreur) — fix permanent EXPO_PUBLIC_USE_RN_FETCH=1 committé dans apps/mobile/.env ; découverte réseau du dev client peu fiable sur ce Wi-Fi (IP du Mac changée 4 fois, reprises via expo run:ios --device <udid> qui transmet l'adresse de Metro par deep link plutôt que par découverte automatique).
  • Recette terrain iOS — 7/7 points validés sur iPhone physique : (1) connexion démo ; (2) scan caméra réel — résolution locale d'une étiquette réelle (D4) + rejet propre d'un QR étranger ; (3) vrai mode avion (D1) — file d'écriture visible ; (4) verrou optimiste (D2) — conflit provoqué en modifiant l'OT côté serveur pendant la coupure, écran Synchro affichant le conflit, tranché par l'humain, OT clos avec bilan complet et photo réellement téléversée (885 Ko, compression D5 confirmée) ; (5) persistance — saisie en file survivant à une fermeture complète + reconstruction de l'app, resynchronisée au retour réseau (limite documentée : un build dev client ne peut PAS démarrer à froid hors-ligne, aucun bundle embarqué — un vrai test « cold start offline » demanderait un build Release, hors périmètre aujourd'hui) ; (6) suggestions IA R5 au vrai modèle sur le téléphone, chips appliqués pré-remplissant les sélecteurs ; (7) sécurité ADR-003 — déconnexion (appui long, purement locale) puis bascule de compte démo, file et cache vides, aucune fuite entre comptes.
  • Android : build natif installé sur l'émulateur (Pixel_3a_API_34, Android Studio déjà en place) ; passage santé complet — connexion démo, navigation Ma journée → fiche appareil (résolution de référence manuelle, équivalent du scan sans caméra d'émulateur, confirmée fonctionnelle), écran Scanner sans plantage.

Décisions

  • ADR-005 actée : deux canaux de distribution mobile (builds natifs pour la vraie recette, Expo Go pour l'aperçu sans installation), managed workflow conservé (ios//android/ jamais committés).
  • La validation terrain due depuis release/r4 est levée pour iOS. La recette sur appareil Android physique (vrai mode avion, vraie caméra) reste un reste, faute d'appareil disponible aujourd'hui — même situation qu'iOS avant cette session.

Prochaine étape : recette Android sur appareil physique quand disponible ; redéploiement Dokploy (release/r3 puis r5 avec AI_SERVICE_TOKEN) ; calibrage AI_SEUIL_* sur corpus SPELEV réel ; secret DOKPLOY_WEBHOOK_URL.


2026-07-17 — Pr. Daaif (+ Claude) — 🏁 R5 CLOSE : tag release/r5

Actions

  • Tag annoté release/r5 posé sur cccfaaa (CI verte, 8/8 jobs) et poussé — sur décision du référent, après recette sans clé API et revue pixel validée (arbitrage tableau appliqué).
  • R0 → R5 : le périmètre v1 du plan de releases est couvert. La suite est de l'exploitation : déploiements, calibrage, recettes différées.

Décisions

  • R5 est la dernière release du plan — les évolutions suivantes (voix opt-in « si budget temps », backlog v2) s'ouvriront par de nouvelles maquettes, même méthode.

Prochaine étape : redéploiement Dokploy de l'instance ENSET (main = release/r5 ; nouvelles variables AI_SERVICE_TOKEN obligatoire — runbook §2 — puis « Réindexer tout »), recette R4 sur téléphone (Expo Go, due avant production client mobile), calibrage AI_SEUIL_* sur corpus SPELEV réel, secret DOKPLOY_WEBHOOK_URL.


2026-07-17 — Pr. Daaif (+ Claude) — Arbitrages de recette R5 rendus : le corpus passe en tableau

Actions

  • Arbitrage du référent sur la revue pixel : « Ça serait mieux d'utiliser un tableau car la plupart des fichiers vont être des PDF » — le reste validé (« Appliquer » à écriture directe, calibrage au runbook).
  • Bibliothèque en tableau, conforme à l'écran 5 de maquette-r5 : colonnes Document (nom + type · taille · date · auteur), Rattaché à, Indexation, Corpus (interrupteur, visible pour ASSETS.edit), actions Ouvrir/Télécharger/Supprimer. Les vignettes restent aux cartes « Documents » des fiches (photos d'OT).
  • Détail aligné sur la maquette : une image non indexable affiche son interrupteur éteint quel que soit l'état stocké — l'interrupteur montre la réalité du corpus, pas une colonne de base.
  • e2e adapté (lignes de tableau), 16/16 Playwright rejoués ; artefact de revue pixel mis à jour (même URL), arbitrages marqués rendus.

Décisions

  • Revue pixel R5 validée par le référent (1 correction appliquée). Le tag release/r5 peut être posé.

Prochaine étape : tag release/r5 sur le mot du référent. Restes : recette R4 sur téléphone (Expo Go), redéploiement Dokploy (release/r3 puis r5 avec AI_SERVICE_TOKEN), calibrage AI_SEUIL_* sur corpus SPELEV.


2026-07-17 — Pr. Daaif (+ Claude) — Recette R5 (sans clé API) + durcissement production siop2-ai

Actions

  • Recette rejouée au vrai modèle, mode extractif (aucune clé API) sur un corpus mis en scène (notice Otis Gen2 générée, 2 pages). Elle a invalidé le modèle d'embeddings choisi en R5.1 : sur la question type du plan (« quel couple de serrage pour les guides du Gen2 ? »), MiniLM-384 classait la page-réponse DERRIÈRE des passages sans rapport (0,24 contre 0,41). Banc comparatif mesuré : mpnet-base-v2 multilingue (768 d) rétablit le classement et une marge signal/bruit exploitable (pertinent ≥ 0,46, hors-corpus ≤ 0,42) ; multilingual-e5-large (1 024 d, 2,2 Go) classait bien aussi mais scores compressés (0,73-0,90) et poids rédhibitoires. Bascule vers mpnet (ADR-004 amendé, migration r5_embeddings_mpnet : pgvector 384 → 768, index vidé — re-dérivable par « Réindexer tout ») + découpage affiné (~350 caractères : la phrase-réponse ne se noie plus, l'extrait cité est lisible) + seuils par défaut recalés (0,45 / 0,40 / 0,55).
  • Recette validée après bascule : réponse sourcée p. 2 en tête ✓, refus honnête chiffré sur question hors corpus ✓ (« routeur wifi » → refus ; charabia → refus), suggestions étagées ✓, D1-D5 tenues, le tout SANS clé. 16/16 Playwright (l'embeddeur déterministe suit les 768 dims sans recalibrage), 78 tests API, 23 pytest.
  • Revue pixel : captures réelles des 6 écrans (web ×4, liseré « suggéré », mobile) face aux écrans de maquette-r5 — artefact publié pour le référent avec 3 points d'arbitrage (« Appliquer » à écriture directe, vignettes vs tableau du corpus, refus « voisin de domaine » dépendant du calibrage client).
  • Durcissement production : apps/ai/Dockerfile (uv, venv non éditable, modèle ONNX téléchargé au build — ADR-004 §1, non-root, healthcheck) ; compose Dokploy : service siop2-ai interne (jamais sur dokploy-network, secret AI_SERVICE_TOKEN requis, génération opt-in par variables, seuils calibrables), siop2-api branché (AI_SERVICE_URL) ; runbook enrichi (§2 variables, §5 service IA, §6 calibrage des seuils sur corpus client, réindexation post-déploiement).

Décisions

  • Le refus « voisin de domaine » (question ascenseur absente du corpus) reste dépendant du calibrage : jamais d'invention (extraits réels cités), mais pas toujours un refus. Les seuils sont des variables d'environnement pour être calibrés sur le corpus SPELEV réel — procédure au runbook §6.
  • Le tag release/r5 attend la validation de la revue pixel par le référent.

Prochaine étape : validation du référent (revue pixel + arbitrages) → tag release/r5. Restes : recette R4 sur téléphone (Expo Go), redéploiement Dokploy (release/r3 puis r5), secret DOKPLOY_WEBHOOK_URL.


2026-07-17 — Pr. Daaif (+ Claude) — R5.3 : les écrans de l'IA (web + mobile) et le corpus administrable

Actions

  • Contrat (76 opérations) : Document expose son état de corpus (inCorpus, indexedAt, chunkCount), PATCH /documents/{id}/corpus (bascule réversible, ASSETS.edit), POST /assistant/reindex (bilan chiffré traduit du dialecte interne). Clients web et mobile régénérés — le job ci-contract vérifie désormais les deux.
  • Web — page Assistant (sidebar Pilotage, WORK_ORDERS.view) : chat fidèle aux écrans 1-2 de maquette-r5 — réponse avec extraits exacts cités (« Ouvrir » → PDF en onglet authentifié, OT → fiche), avertissement « l'IA propose, vous validez » permanent ; refus honnête chiffré (« j'ai cherché dans N documents et M bilans ») avec l'action utile (téléverser la notice).
  • Web — Bibliothèque = corpus (écran 5) : bandeau 09-08 (l'anonymisation est DITE), statut par document (indexé · date · extraits / à indexer / exclu / image non indexable), interrupteur d'exclusion (PDF seulement), « Réindexer tout » avec bilan. Fiche OT (écran 3) : « Décrire pour suggérer » dans la carte Bilan — suggestions justifiées (« N bilans similaires », confiance), « Appliquer » = le geste humain qui écrit (cohérent avec la carte à enregistrement direct), liseré « suggéré » retiré dès qu'un choix manuel reprend la main.
  • Mobile — clôture (écran 4) : décrire au pouce → chips suggérées (un appui = un champ pré-rempli, ✓), note D1 visible ; hors-ligne le bouton dit « réseau requis » (l'IA est un service serveur — la clôture en file R4 n'en dépend pas).
  • apps/ai : seuils par env (AI_SEUIL_*) — nécessaires à la CI et au calibrage de recette.
  • CI : le job e2e démarre siop2-ai (embeddeur déterministe) et le parcours R5 se recette en vrai : upload d'un PDF généré avec xref valide → réindexation → statut → réponse sourcée (extrait « 25 Nm », p. 1) → refus sur charabia → exclusion → suggestion appliquée sur un OT créé. 16/16 Playwright (recettes R0→R5).
  • Vérifications réelles : seuils CI mesurés (vrai match 0,66 vs bruit de collisions 0,11 → pertinence 0,20 ; codes pertinents 0,14-0,16 vs parasite 0,08 → suggestion 0,10) ; chaîne complète au vrai modèle ONNX (réindexation, ask sourcé p. 8/p. 10, suggestions étagées avec vrais comptes de bilans) ; parcours mobile Expo web 6/6 et web 7/7 au vrai modèle, 0 erreur console. 78 tests API (2 nouveaux : bascule corpus gardée, reindex traduit + 403).

Décisions

  • Sur le web, « Appliquer » écrit immédiatement (comme tout le reste de la carte Bilan R2) : le clic EST la validation humaine — l'esprit de la maquette (« rien sans vous ») est porté par le geste, pas par un bouton « Enregistrer » séparé. À montrer en revue pixel.
  • Les seuils par défaut du vrai modèle (0,30/0,35/0,55) restent à calibrer en recette sur le corpus client réel : mesuré ce jour sur un corpus non-métier, le multilingual-MiniLM donne des scores plats (hors-sujet 0,35-0,43 vs pertinent 0,24-0,28) — c'est exactement pourquoi ils sont configurables.

Prochaine étape : recette R5 complète (doit passer SANS clé API), durcissement production (Dockerfile siop2-ai + compose Dokploy), puis tag release/r5. Restes : recette R4 sur appareil, redéploiement Dokploy de release/r3.


2026-07-17 — Pr. Daaif (+ Claude) — R5.2 : l'assistant au contrat + suggestion de codes de bilan

Actions

  • apps/ai : /internal/ask — recherche → seuil de pertinence → extraits sourcés, ou refus honnête portant la taille du corpus cherché (« 1 document, 3 bilans » — l'écran 2 des maquettes aura ses chiffres) ; la rédaction passe par le Generateur (opt-in — mode extractif par défaut). /internal/suggest — similarité sémantique entre la description libre et les libellés actifs des référentiels (contextualisés « anomalie constatée : … »), un seul code par champ, au-dessus du seuil, confiance FORTE/MOYENNE + « N bilans similaires sur ce parc » (comptés dans les chunks d'historique). Sans LLM : déterministe, explicable. 23 pytest.
  • Contrat (74 opérations) : POST /assistant/askAssistantAnswer (mode EXTRACTIVE/GENERATED/REFUSAL, extraits cités document/page ou bilan daté, corpus cherché) ; POST /assistant/suggest-bilan → codes existants seulement. Clients web/mobile régénérés.
  • API NestJS : module assistant — proxy vers siop2-ai (AI_SERVICE_URL/AI_SERVICE_TOKEN à l'env, ADR-004 §4), permissions par la matrice (ask = WORK_ORDERS.view ; suggest = WORK_ORDERS.edit — qui remplit des bilans), traduction du dialecte interne vers le contrat, 503 propre si le service IA est éteint (jamais un 500). 6 tests e2e sur un stub HTTP (76 tests API, 14 suites).
  • Vérifié sur la vraie chaîne (NestJS → ai → pgvector, vrai modèle) — et elle a débusqué un bug : fastembed ne renvoie pas des vecteurs normés, la similarité des suggestions dépassait 1 (pgvector normalisait dans son opérateur, ce qui masquait l'écart). Normalisation à l'encodage + réindexation : bilans du parc trouvés en tête (0.41), refus honnête hors corpus, suggestions cosinus ≤ 1 à confiances étagées.

Décisions

  • Les seuils (pertinence 0.30, suggestion 0.35, confiance forte 0.55) sont des constantes de départ — à calibrer en recette sur le corpus réel du client.

Prochaine étape : R5.3 — les écrans validés (Assistant sidebar, corpus dans la Bibliothèque, suggestions dans les fiches OT web et clôture mobile). Restes : recette R4 sur appareil, redéploiement Dokploy de release/r3.


2026-07-17 — Pr. Daaif (+ Claude) — R5.1+ : clé API de génération configurable (demande du référent)

Actions

  • generation.py : l'interface Generateur d'ADR-004 §3 prend corps — GenerateurExtractif (contrat de base, aucun LLM) et GenerateurAPI (SDK officiel anthropic, dépendance optionnelle --extra generation, absente des tests/CI). Consigne système : citations [n] obligatoires depuis les extraits fournis, « n'invente RIEN », rappel de validation humaine. Tout échec (refus du modèle, quota, réseau) retombe sur l'extractif — jamais d'erreur utilisateur imputable au LLM ; stop_reason == "refusal" traité explicitement.
  • Configuration complète et validée au boot : AI_GENERATION=off|api, AI_API_KEY (exigée en mode api — le démarrage refuse sinon, message clair), AI_MODEL (défaut claude-opus-4-8). .env.example posé ; /healthz expose le mode (off/api), jamais la clé ; le générateur rejoint l'état de l'app (R5.2 le consommera).
  • 5 tests ajoutés (19 pytest au total) : défaut extractif, refus de boot api-sans-clé, mode inconnu refusé, clé/modèle lus de l'env, invite pure numérotant les extraits. Vérifié en réel : boot refusé sans clé, GenerateurAPI construit avec le SDK, healthz sans secret.

Décisions

  • La clé vit en variable d'environnement (Dokploy secrets), pas en base ni dans l'UI — cohérent avec JWT_SECRET et ADR-002.

Prochaine étape : R5.2 — assistant au contrat (proxy NestJS), le mode extractif d'abord, la rédaction branchée sur ce Generateur. Restes : recette R4 sur appareil, redéploiement Dokploy de release/r3.


2026-07-17 — Pr. Daaif (+ Claude) — R5.1 : socle apps/ai — ingestion anonymisée + recherche sémantique

Actions

  • ADR-004 actée : embeddings locaux sur CPU (fastembed ONNX, paraphrase-multilingual-MiniLM-L12-v2, 384 dims — les textes du client ne quittent jamais le serveur), pgvector dans le Postgres existant (table RagChunk possédée par Prisma, migration r5_ia + état de corpus sur Document), génération opt-in (AI_GENERATION=off par défaut : mode extractif honnête, la recette doit passer sans clé API), suggestion de bilan sans LLM (similarité sémantique, explicable). Topologie : siop2-ai jamais exposé, joint par l'API NestJS seule (X-Service-Token).
  • apps/ai posé (FastAPI + uv, python 3.11) : config validée au démarrage, /healthz, /internal/reindex (idempotent) et /internal/search protégés par le jeton de service. Pipeline : PDF MinIO → texte par page (pypdf) → anonymisation D4 (e-mails, téléphones marocains, noms connus de la base — insensible casse/accents, fonction pure) → découpage (~900 car., chevauchement, testé) → embeddings → RagChunk avec localisateur (« p. 42 », « bilan du 17/07 »). Bilans codés clôturés ingérés aussi (« sur votre parc… »). L'exclusion de corpus (D3) s'applique à l'ingestion ET à la lecture.
  • 14 pytest verts + ruff (embeddeur déterministe en test/CI — aucun téléchargement, même interface 384 dims) ; job CI ai (uv), deploy en dépend.
  • Vérifié en réel avec le vrai modèle ONNX : réindexation du corpus seedé en 7 s (1 PDF réel → 31 extraits paginés, 3 bilans), recherche sémantique concluante (PDF trouvé par le sens, bilans du parc par « frottement des guides »), e-mails du PDF remplacés par ⟨contact⟩, 0 identité dans les chunks (contrôle SQL sur les 9 noms seedés).

Décisions

  • Convention de test R5 : pytest unitaires purs en CI (sans DB ni réseau) ; l'intégration réelle (DB + MinIO + modèle) se vérifie en local et en recette.

Prochaine étape : R5.2 — l'assistant au contrat (proxy NestJS authentifié → siop2-ai, mode extractif sourcé « sourcé ou silencieux ») + suggestion de codes de bilan. Restes : recette R4 sur appareil, redéploiement Dokploy de release/r3.


2026-07-17 — Pr. Daaif (+ Claude) — R5 ouverte : maquettes IA (design d'abord)

Actions

  • maquette-r5.html (docs/02-design/maquettes) : 6 écrans — Assistant sourcé (recette type du cadrage : « couple de serrage des guides Gen2 ? » → réponse à citations numérotées, sources document/page/extrait exact ouvrables), Sans source = refus (l'anti-hallucination visible : « je préfère ne pas inventer » + ce qui a été cherché + action utile), Suggestion de bilan web et mobile (description libre → codes EXISTANTS des référentiels, justification + confiance, « Appliquer » pré-remplit avec liseré « suggéré », rien d'enregistré sans le geste humain), Corpus & ingestion (bibliothèque R3 + bilans codés, statut par document, exclusion réversible, bandeau 09-08), Voix (option « si budget » : dictée opt-in, transcription relue, audio purgé immédiatement). Bi-thème, tokens répliqués, vérifiée en navigateur (6 onglets + sombre, zéro erreur console).
  • Artefact de validation publié (6 écrans + preuve bi-thème + 5 décisions à acter : D1 aucune écriture automatique, D2 sourcé ou silencieux, D3 corpus fermé et visible, D4 anonymisation à l'ingestion 09-08, D5 voix opt-in avec purge).

Décisions

  • → Levé le 17/07/2026 : maquettes R5 et les 5 décisions (D1-D5) VALIDÉES par le référent. Lancement R5.1 (socle apps/ai + ADR-004).

Prochaine étape : R5.1 — ADR-004 (embeddings/modèles), migration r5_ia (chunks pgvector, état de corpus), pipeline d'ingestion anonymisé et testé. Restes : recette R4 sur appareil, redéploiement Dokploy de release/r3.


2026-07-17 — Pr. Daaif (+ Claude) — R4 CLOSE : tag release/r4 (recette téléphone reportée)

Actions

  • Tag annoté release/r4 posé et poussé (CI verte sur c8b3c17 : 74 tests API, 17 jest-expo, 14 Playwright).

Décisions

  • Décision du référent : la recette sur téléphone (Expo Go — vrai mode avion, scan caméra) est REPORTÉE, il y accédera plus tard avec un appareil. Le tag est posé sur la foi de la recette « mode avion » rejouée 13/13 en Expo web piloté (toute la mécanique D1/D2 vérifiée, capteurs exclus). La validation terrain reste due avant toute mise en production client de l'app mobile — notée comme reste de release.

Prochaine étape : ouverture R5 — IA (design d'abord : maquettes assistant RAG sourcé + suggestion de codes de bilan, décisions à acter). Restes : recette R4 sur appareil, redéploiement Dokploy de release/r3.


2026-07-17 — Pr. Daaif (+ Claude) — R4.3 : file d'écriture, verrou optimiste, Synchro & conflits

Actions

  • Verrou optimiste côté serveur (D2) : updatedAt exposé au détail OT ; baseUpdatedAt optionnel sur transition/coche/bilan → 409 « Conflit de version » contextualisé (qui, quand — depuis le dernier événement). Toute écriture « secondaire » (coche, bilan, commentaire, conso, MO) fait désormais avancer la version, sinon le verrou serait aveugle. Sans version fournie, comportement web inchangé. 4 tests e2e dédiés (74 au total).
  • File d'écriture mobile (D1) : store persisté AsyncStorage (survit au redémarrage, un ENVOI interrompu redevient rejouable), rejeu dans l'ordre ; succès → sortie de file et propagation de la version fraîche aux saisies restantes du même OT (nos propres écritures ne se conflictent pas entre elles — un écart étranger reste détecté) ; coupure → tout reste en attente ; refus → CONFLIT et la file s'arrête là. Transitions, coches, bilan et photos (D5 : compressées ~1 600 px, expo-image-picker/manipulator**)** passent par la file, avec patch optimiste du cache (l'OT affiche « Terminé » localement, chips « en file » partout).
  • Écran Synchro & conflits (écran 7) : file visible et horodatée, badge tabbar (ambre → rouge si conflit), carte de conflit avec le message de l'API et les trois choix — voir l'OT, rejouer sur la version à jour, abandonner. Préchargement du parc et des référentiels à l'entrée (trou D1 débusqué par la vérif : le bilan hors-ligne n'avait pas ses vocabulaires).
  • Sécurité & routage (demande du référent) : ADR-003 — qui vit où sur l'appareil et ce qui le protège (jeton en trousseau, cache/file en sandbox, purge complète à la déconnexion), l'API seule autorité, deep links siop:// jamais suivis aveuglément (le scan extrait et résout localement). Correctif réel au passage : la file et le cache persisté n'étaient pas purgés au logout — un autre compte sur le même téléphone aurait pu rejouer les saisies du précédent.
  • Recette « mode avion » rejouée en Expo web piloté : 13/13 — OT ouvert, avion, Démarrer + bilan + clôture hors-ligne (état local immédiat, 3 en file), Salma modifie l'OT pendant ce temps, retour réseau → rejeu auto → conflit tranché par l'humain → file vide → serveur : Terminé avec bilan. 17 tests jest-expo (5 sur la file), zéro erreur console inattendue.

Décisions

  • La recette officielle « mode avion en sous-sol » reste à rejouer sur téléphone (Expo Go) avec le référent — le web a validé toute la mécanique, pas les capteurs ni le vrai mode avion.
  • Durcissements notés à l'ADR-003 : chiffrement applicatif de la file et épinglage TLS si exigence client.

Prochaine étape : recette R4 sur appareil avec le référent → tag release/r4 → R5 IA (design d'abord). Redéploiement Dokploy de release/r3 toujours en attente côté serveur.


2026-07-17 — Pr. Daaif (+ Claude) — R4.2 : scan QR, fiches terrain, grille cochable

Actions

  • Scanner réel (expo-camera, QR seulement) : analyse du code testée (analyseScan — URL portail …/q/REF de l'étiquette A6 quel que soit le domaine, ou référence tapée ; les QR étrangers sont refusés proprement, jamais d'écran blanc). Résolution D4 : le parc en cache d'abord (fonctionne en sous-sol), rafraîchissement seulement si le réseau est là ; référence inconnue → message honnête. Repli saisie manuelle (seul chemin sur web, assumé).
  • Fiche ascenseur (consultation, D3) : identité, organes, interventions visibles par le rôle (invariant « voir autre »), raccourci vers l'OT en cours. Fiche OT : un bouton principal selon la machine à états, pièces & main-d'œuvre aux coûts figés, garde de clôture alimentée par les closureBlockers de l'API (une seule source de vérité). Clôture terrain : bilan codé 6 champs (sélecteur plein écran au pouce — RN n'a pas de <select>), garde visible, clôture en ligne. Préventif : mes grilles → checklist cochable, appui long = N/A, coche individuelle à l'API.
  • En R4.2 les écritures restent en ligne (bandeaux explicites hors-ligne) — la file générale et le verrou optimiste sont le cœur de R4.3, comme prévu.
  • Vérifié en Expo web piloté : 12/12 — scan A1 → fiche → OT-0341 (505 MAD, garde), référence inconnue, coche/décoche (état restitué), et un OT créé par l'API : Démarrer → clôture bloquée bilan incomplet → 3 champs remplis → garde éteinte → Terminé ; zéro erreur console ; données de test purgées. 12 tests jest-expo (scan, progression, tri, tokens).

Décisions

  • Leçon accessibilité RN web : accessibilityState.checked ne produit PAS aria-checked — utiliser la prop aria-checked (mieux pour les lecteurs d'écran, et testable).
  • La caméra ne se recette pas sur web : scan réel à valider dans Expo Go (le repli manuel couvre le parcours en attendant).

Prochaine étape : R4.3 — file d'écriture persistée rejouée dans l'ordre, verrou optimiste tranché par l'humain (D2), écran Synchro & conflits, photos en file (D5) → recette « mode avion » sur téléphone. Redéploiement Dokploy de release/r3 toujours en attente.


2026-07-17 — Pr. Daaif (+ Claude) — R4.1 : socle mobile Expo (app du technicien)

Actions

  • apps/mobile posé (Expo SDK 57, TypeScript strict, pnpm workspace, @siop/shared consommé) : connexion e-mail/mdp + sélecteur démo ADR-002 (n'existe que si l'API l'expose), coquille tabbar de la maquette (Scanner/Préventif/Synchro visibles mais marqués R4.2/R4.3 — même patron que la sidebar web), Ma journée : OT triés priorité puis échéance (fonction pure testée), urgence « personne bloquée » en tête, strie de priorité, pastille de synchro permanente.
  • D1 en actes (lecture) : cache TanStack persisté dans AsyncStorage (7 jours), NetInfo → onlineManager + bandeau hors-ligne horodaté. Jeton en SecureStore. Client typé régénéré depuis docs/openapi.json (règle d'or). Thème : tokens light/dark répliqués (testés), Manrope embarquée.
  • API : CORS_ORIGINS (env, vide par défaut = pas de CORS) — nécessaire à Expo web/debug seulement ; le web de prod reste derrière le proxy nginx, les apps natives n'ont pas d'Origin.
  • Vérifié en navigateur réel (Expo web + Playwright) : 10/10 — connexion démo Ahmed → Ma journée (ses OT seulement : le scoping « voir autre » joue), bascule hors-ligne (bandeau + liste servie du cache) et retour, onglets à venir honnêtes ; zéro erreur console. 6 tests jest-expo, lint racine étendu (react-hooks sur mobile), job CI mobile ajouté (deploy dépend de lui).

Décisions

  • La vraie recette hors-ligne (mode avion, redémarrage, file d'écriture) se fera sur téléphone — Expo web ne prouve que le chemin de code NetInfo→UI (dispatch navigator.connection.change observé).
  • Leçon Playwright : setOffline ne déclenche pas les événements réseau du navigateur — NetInfo web écoute navigator.connection sur Chromium.

Prochaine étape : R4.2 — scan QR (expo-camera) + fiche ascenseur + fiche OT + checklist cochable ; puis R4.3 file d'écriture + verrou optimiste (D2). Redéploiement Dokploy de release/r3 toujours en attente.


2026-07-17 — Pr. Daaif (+ Claude) — R4 ouverte : maquettes mobile (design d'abord)

Actions

  • maquette-r4.html (docs/02-design/maquettes) : 7 écrans de l'app technicien — Ma journée (en ligne ET hors-ligne côte à côte), Fiche OT, Clôture terrain (bilan codé R2, garde visible ; variante « en file » hors-ligne), Scan QR (étiquettes A6 de R1, hors-ligne inclus), Fiche ascenseur (consultation devant la machine), Checklist du mois (coches individuelles en file), Synchro & conflits (file visible, conflit de verrou optimiste où l'humain tranche). Tokens répliqués, bi-thème, gabarit téléphone repris de la maquette R3, données du parc seedé. Vérifiée en navigateur (7 onglets + bascule sombre, zéro erreur console).
  • Artefact de validation publié pour le référent (7 écrans + preuve bi-thème + 5 décisions à acter : D1 offline-first lecture locale/écriture en file, D2 verrou optimiste tranché par l'humain, D3 périmètre fermé « app du technicien » sans création d'OT mobile, D4 scan local des étiquettes R1, D5 photos compressées en file — pas d'audio ni de géolocalisation en R4, loi 09-08).

Décisions

  • → Levé le 17/07/2026 : maquettes R4 et les 5 décisions (D1-D5) VALIDÉES par le référent. Lancement R4.1 (socle mobile Expo).

Prochaine étape : R4.1 — socle Expo : coquille tabbar, connexion + sélecteur démo (ADR-002), client typé du contrat, « Ma journée » en lecture avec cache hors-ligne. En parallèle, redéploiement Dokploy de release/r3 toujours à faire.


2026-07-17 — Pr. Daaif (+ Claude) — R3 CLOSE : tag release/r3

Actions

  • Recette R3 prononcée par le référent après la revue pixel corrigée (8 écarts + recherche globale, artefact à jour, CI verte sur 460ef4a).
  • Tag annoté release/r3 posé et poussé.

Décisions

  • Le tag précède le redéploiement (webhook CD toujours absent) : le déploiement Dokploy se fait manuellement depuis ce tag, puis vérification en ligne — même séquence que R1/R2.

Prochaine étape : redéployer sur Dokploy (migration r3_recette_fixes + seed au boot), vérifier en ligne (rattachements Tiers, recherche ⌘K, période des stats), puis ouvrir R4 Mobile (Expo — design d'abord, maquettes avant tout code).


2026-07-17 — Pr. Daaif (+ Claude) — R3.4 : revue pixel, corrections de recette, recherche globale

Actions

  • Revue pixel R3 (artefact partagé, captures maquette ↔ appli côte à côte, 1440 px, thème clair) : 7/7 écrans structurellement fidèles, 8 écarts relevés. Arbitrage du référent : tout corriger + activer la recherche.
  • Stock : filtre Fournisseur, « Entrée de stock » depuis la liste (choix de la pièce dans la modale), pièces sous le seuil en tête. Fiche pièce : fournisseur → lien vers Tiers. Statistiques : « Période » devient un vrai sélecteur 3/6/12 mois (months au contrat, libellés et barres suivent). Tiers : les syndics affichent leurs sites — nouveau lien site→client/syndic (migration r3_recette_fixes, Location.partnerId, garde « CLIENT seulement »), éditable sur la fiche site, seedé (Tour Atlas, Résidence Al Manar). Bibliothèque : filtre « Rattaché à » + zone de glisser-déposer (la modale s'ouvre préremplie, le rattachement reste obligatoire).
  • Recherche globale ⌘K active : GET /search (73e opération au contrat) — chaque famille (OT/ascenseurs/sites) n'est interrogée que si le rôle a la permission view du domaine, les OT respectent « voir autre » ; topbar avec debounce 200 ms, résultats groupés, navigation flèches + Entrée. 8 tests API dédiés (scoping vérifié rôle par rôle).
  • Écart « regroupement des tiers » retiré : le tri type-puis-nom existait déjà (constat d'écran, pas de code).
  • Vérifié : 18/18 contrôles en navigateur réel (zéro erreur console), 14/14 Playwright (dont nouveau parcours « recette corrigée »), 74 tests API (13 suites). Artefact de revue mis à jour (statut « corrigé », captures fraîches).

Décisions

  • « Bons de commande » reste dans l'entête du Stock en plus de la maquette : c'est le seul accès à l'écran BC — à entériner en recette (ou déplacer si le référent préfère).
  • La liste des tiers restant sous PURCHASE_ORDERS.view, le champ syndic de la fiche site n'est éditable que si le rôle voit les tiers (sinon lecture seule).

Prochaine étape : validation finale du référent sur l'artefact → déploiement Dokploy → tag release/r3 → R4 Mobile (design d'abord).


2026-07-16 — Pr. Daaif (+ Claude) — R3.3+ : aperçu et ouverture des documents (retour de pré-recette)

Actions

  • Retour du référent : les documents téléversés n'avaient ni aperçu ni ouverture (seul « Télécharger » existait). Corrigé dans la vignette (carte-documents.tsx) : les images affichent leur vrai aperçu (le fichier exige le jeton → récupération du blob puis URL objet révoquée au démontage, jamais de <img src> nu) ; les PDF gardent leur pictogramme.
  • « Ouvrir » : nouveau bouton (et clic sur la vignette) — onglet ouvert dans le geste utilisateur (sinon bloqueurs de pop-up) puis pointé sur le blob ; le navigateur affiche PDF et images dans sa visionneuse. « Télécharger » inchangé.
  • Vérifié en navigateur réel (Playwright, appli lancée) : vignette image réellement chargée (naturalWidth > 0), ouverture image et PDF en onglet blob: (PDF rendu dans la visionneuse Chrome — capture), téléchargement intact, suppression OK, zéro erreur console. Typecheck + vitest verts.

Décisions

  • L'aperçu télécharge le fichier entier (pas de miniature côté serveur) : acceptable au volume actuel ; si la bibliothèque grossit, générer des miniatures à l'upload (noté pour le durcissement).

Prochaine étape : recette R3 avec le référent (revue pixel), déploiement Dokploy, tag release/r3 → puis R4 Mobile (design d'abord).


2026-07-16 — Pr. Daaif (+ Claude) — R3.3 : les écrans web de la gestion — R3 prête pour recette

Actions

  • 7 écrans fidèles à maquette-r3.html : Stock (alerte sous seuil, strie ambre, « Préparer le BC » sur la ligne), Fiche pièce (« les mouvements SONT le stock », entrée/ajustement motivé), Bons de commande (liste + panneau, envoi/annulation/réception → entrées de stock), création de BC préremplie depuis l'alerte (fournisseur de la pièce, quantité pour repasser le seuil + 5, dernier PU), Tiers, Bibliothèque (upload rattaché à un appareil, filtre par type), Statistiques (4 KPI, pannes par organe depuis les bilans codés, coûts par mois, top équipements).
  • La fiche OT s'achève : carte « Pièces & main-d'œuvre » réelle (consommer une pièce — prix figé annoncé AVANT le clic, impact stock affiché ; saisir du temps au taux figé) + carte « Documents » réelle (photo d'intervention téléversée/retéléchargée). La fiche ascenseur gagne la même carte Documents ; le tableau de bord allume « Pannes par organe » ; Personnes affiche et édite le taux horaire.
  • Nav « Ressources »/« Statistiques » activée par la matrice (PARTS/PURCHASE_ORDERS/ASSETS/ANALYTICS.view) ; captures des 9 écrans comparées aux maquettes (bi-thème), zéro erreur console.
  • Recette R3 automatisée (2 parcours Playwright, données E2E autonomes purgées au setup) : tiers → pièce → sous seuil → BC prérempli → réception → l'alerte s'éteint ; OT : consommation + 1 h 30 → 380 MAD exacts ; upload/download MinIO réel ; statistiques lues par la Vue seule. 13/13 e2e verts, 10,7 s.

Décisions

  • Le formulaire BC prérempli attend la fin du refetch avant d'initialiser ses lignes (le cache TanStack portait un stock périmé — attrapé par la recette, pas par l'œil).
  • cleanup-e2e purge désormais les entités R3 (mouvements liés aux OT E2E supprimés AVANT les OT, sinon ils deviennent orphelins et faussent les stocks seedés d'un run à l'autre).

Prochaine étape : recette R3 avec le référent (revue pixel), déploiement Dokploy, tag release/r3 → puis R4 Mobile (design d'abord).


2026-07-16 — Pr. Daaif (+ Claude) — R3.2 : bibliothèque de documents + analytics

Actions

  • FileStorage gagne ses vraies opérations (put/stream/remove ; bucket créé au démarrage — aucune étape manuelle au déploiement) ; MinIO reste confiné à son implémentation (règle ESLint).
  • Bibliothèque : upload multipart (PDF/JPG/PNG, 20 Mo max, rattachement appareil OU OT requis, permission d'édition sur la cible), liste filtrable, téléchargement streamé par l'API (MinIO jamais exposé — topologie du runbook respectée), suppression. Test e2e : les octets téléchargés sont IDENTIQUES aux octets envoyés.
  • Analytics (GET /analytics/summary, permission ANALYTICS) : tout est dérivé du réel — coûts par mois depuis les mouvements/main-d'œuvre figés, pannes par organe depuis les bilans codés, taux de préventif (grilles terminées/générées), durée moyenne de résolution, top équipements en coût.
  • Générateur OpenAPI étendu (query params, multipart, réponse binaire) — 71 opérations. CI : service MinIO ajouté (jobs api + e2e, image bitnami).
  • 58 tests verts (92 % / 73,9 %) ; test de régression du tri des « Interventions récentes » rendu déterministe (les positions absolues étaient instables sous 12 suites parallèles) — 6 runs complets consécutifs verts.

Prochaine étape : R3.3 — écrans web (stock, fiche pièce, BC, coûts sur fiche OT, statistiques, tiers, bibliothèque, taux dans Personnes), puis recette R3 + déploiement + tag.


2026-07-16 — Pr. Daaif (+ Claude) — R3.1 : socle backend de la gestion

Actions

  • Maquettes R3 et les 4 décisions VALIDÉES par le référent → lancement du socle.
  • Modèle (migration r3_gestion, 7 tables) : Partner, Part (SANS colonne de quantité), StockMovement (signé, tracé, PU figé), PurchaseOrder/Line, LaborTime (taux figé), Document (préparé pour R3.2) ; User.hourlyRate (taux courant, administrable dans Personnes).
  • API (66 opérations au contrat) : tiers, pièces (stock = Σ mouvements calculé en groupBy, alerte sous seuil), entrée/ajustement (motif requis, stock jamais négatif — vérifié en transaction), BC (Brouillon→Envoyé→Reçu ; la réception crée les RECEIPT et met à jour lastUnitPrice), consommation sur OT (stock suffisant + prix figé) et main-d'œuvre (taux figé, refus motivé si taux non défini) ; WorkOrderDetail.costs (lignes + total immuables) ; coûts verrouillés après clôture/annulation (409).
  • Seed : 5 tiers, 5 pièces de la maquette (P-0113 sous seuil via son histoire de mouvements), BC reçu + BC envoyé, taux horaires, et la carte maquette rejouée : OT-0341 = 505 MAD (240 + 85 + 180) — vérifiée par test.
  • 55 tests verts (92 % / 74,9 %) dont la recette officielle : consommer sous seuil → alerte → BC → réception → réappro, prix d'hier intact sur l'OT pendant que le prix courant change ; hausse du taux d'Ahmed sans effet sur les OT passés.

Leçon majeure (durcissement)

  • Les références « max+1 » (OT/DEM/BC/P) étaient une course sous charge parallèle (500 sporadiques malgré les retries). Remplacées par des séquences Postgres (nextval) initialisées au max existant : la classe de bugs disparaît — 3 runs Jest complets consécutifs verts. La numérotation ne se remet pas à zéro chaque année (l'unicité prime), consigné.

Prochaine étape : R3.2 — bibliothèque de documents (upload multipart 20 Mo → FileStorage.putObject, téléchargement streamé via l'API — MinIO jamais exposé) + analytics (GET /analytics/summary), puis R3.3 écrans web.


2026-07-16 — Pr. Daaif (+ Claude) — R3 ouverte : maquettes de la gestion à valider

Actions

  • R3 « Gestion » ouverte — design d'abord : maquette-r3.html, 7 écrans dans le moule validé : Stock (dérivé des mouvements, alertes sous seuil avec « Préparer le BC »), Fiche pièce (les mouvements SONT le stock), Bons de commande (Brouillon → Envoyé → Reçu ; la réception crée les entrées), Coûts sur OT (la carte validée R0 devient réelle : consommation à prix figé, main-d'œuvre à taux figé), Statistiques (coûts, pannes par organe alimenté par les bilans codés, taux de préventif, top équipements — palette CVD), Tiers (annuaire minimal fournisseurs/syndics), Bibliothèque (documents typés rattachés appareil/OT, MinIO via FileStorage — futur corpus RAG R5).
  • Rendu vérifié (7 écrans + thème sombre, zéro erreur).

Décisions (proposées à la validation)

  • Stock strictement dérivé des mouvements (décision v1 éprouvée) : aucune saisie directe de quantité ; les ajustements d'inventaire sont des mouvements motivés.
  • Prix figé à la consommation (dernier prix d'achat) et taux horaire figé par personne (champ géré dans Personnes) : le coût d'un OT ne bouge plus jamais.
  • La réception d'un BC crée les mouvements d'entrée et fige le PU.
  • Documents : types fermés (Notice / Certificat / Photo / Autre), 20 Mo max, rattachement obligatoire (appareil ou OT).

Bloquant : validation des maquettes R3 par le référent avant toute ligne de code R3.


2026-07-16 — Pr. Daaif (+ Claude) — 🏁 R2 CLOSE : recettée, déployée, taguée

Actions

  • Recette R2 prononcée par le référent (une anomalie détectée et corrigée en cours de recette — voir entrée précédente : tri des « Interventions récentes »).
  • Déploiement vérifié en ligne : portail public /q/A1 opérationnel (résolution QR 200), 13 OT avec l'urgence en tête, préventif de juillet généré sur l'instance (8 grilles, 7 premiers contrôles).
  • DoD complète → tag release/r2. Le carnet papier est remplacé : signalement QR sans compte → OT → bilan codé → clôture gardée → suivi, plus préventif idempotent et compteurs.

Prochaine étape : R3 Gestion — design d'abord : maquettes stock/BC/tiers/coûts OT/analytics/bibliothèque à produire et faire valider.


2026-07-16 — Pr. Daaif (+ Claude) — Recette R2 : anomalie du référent corrigée

Anomalie (recette du référent) : un OT créé, assigné à Ahmed, traité et clôturé apparaissait dans le tableau de bord d'Ahmed mais PAS dans celui de Salma ni de l'administrateur.

Diagnostic : la liste API triait par statut d'abord (les Terminés relégués en queue) et le tableau de bord affiche les 5 premières lignes. Ahmed, sans « voir autre », a une liste courte → son OT clôturé restait dans le top 5 ; Salma et l'admin voient tout le parc → l'OT clôturé était éjecté des « Interventions récentes ». (La liste OT filtrait aussi « actifs » par défaut, conforme à la maquette — c'est le tableau de bord qui trompait.)

Correctif : la liste trie par dernière activité (updatedAt desc) — un OT qui vient d'être clôturé remonte en tête pour tout le monde ; les urgences « personne bloquée » actives restent épinglées au sommet (un OT d'urgence annulé ne squattait plus la tête, corrigé au passage). Reproduit avant / vérifié après sur le scénario exact du référent ; test de régression ajouté à la recette e2e (position de l'OT clôturé dans la liste de Salma ≤ nombre d'urgences actives). 50 tests API + 11 Playwright verts.


2026-07-16 — Pr. Daaif (+ Claude) — R2.4 : portail public QR — l'étiquette prend vie

Actions

  • Trois routes publiques /portal (les seules routes @Public métier de l'API), throttlées (@nestjs/throttler : 10 signalements/min, 60 lectures/min — première surface sans compte) : résolution du QR (référence → appareil), signalement (retourne un jeton de suivi opaque), suivi par référence + jeton (pas de jeton, pas de lecture — aucune énumération possible).
  • Suivi sans compte : Request.publicToken (migration r2_portail) ; le téléphone du gardien garde [référence, jeton] en localStorage (10 max) ; étapes sans jargon Reçu → Intervention → Résolu dérivées de l'OT lié ; un rejet s'affiche « Sans suite : {motif} ».
  • Page /q/{réf} fidèle à l'écran 6 validé R0 (mobile d'abord) : équipement prérempli « détecté par le QR », gros interrupteur personne bloquée, description, photo annoncée (upload → R3), messages d'erreur en français métier (QR inconnu, throttling).
  • e2e Playwright : LA boucle produit — le gardien signale sans compte sur /q/A1, l'équipe traite (approbation → démarrage → bilan → clôture via API), le gardien recharge et voit ✓ Résolu. 11 tests Playwright verts, 50 tests API, 52 opérations au contrat. Générateur OpenAPI : les paramètres non-id ne sont plus typés uuid.

Décisions

  • Le portail n'expose JAMAIS de liste : lecture uniquement par jeton individuel.
  • Throttling au niveau du contrôleur portail seulement (le reste de l'API est derrière JWT).

R2 est fonctionnellement complète (OT, bilan codé, demandes, portail QR, préventif, compteurs). Prochaine étape : recette R2 avec le référent (revue pixel ↔ maquettes R0+R2), déploiement, tag release/r2.


2026-07-16 — Pr. Daaif (+ Claude) — R2.3 : écrans web de l'exploitation

Actions

  • 7 écrans/évolutions fidèles aux maquettes validées : Liste OT (filtres, strie rouge, « immédiat »), Fiche OT (boutons de transition issus d'allowedTransitions, clôture grisée avec l'explication de la garde, bilan codé 6 selects alimentés par les référentiels, checklist Fait→N-A→à faire au clic, activité chronologique + commentaires, assignation, annulation motivée), Nouvel OT (interrupteur « personne bloquée » qui force la priorité), Demandes (table + panneau d'approbation, rejet en modale à motif), Préventif (4 tuiles réelles, bouton générer avec résumé « regénérer ne double rien », gabarits administrables), Compteurs (saisie + historique), Tableau de bord réel (bandeau urgence cliquable, KPIs, OT par statut, interventions récentes ; coûts et pannes par organe annoncés R3).
  • L'urgence traverse l'app : chip pulsante dans la topbar (rafraîchie 60 s), badges de nav (urgences OT, demandes à traiter), bandeau dashboard, tête de liste. Fiche appareil : historique réel + accès compteurs. Accueil dédié aux rôles sans exploitation (Demandeur).
  • Trou de conception débusqué par l'e2e : le Demandeur n'a pas ASSETS.view → son sélecteur d'équipement était vide. Correctif : GET /assets/options (authentification seule — les références sont affichées en cabine). 49 opérations au contrat.
  • Deux fragilités réelles corrigées dans la génération du préventif : collision de référence (course avec une création d'OT) désormais RETENTÉE au lieu de sauter silencieusement un appareil ; appareil supprimé entre lecture et écriture toléré (P2003). 3 runs Jest complets consécutifs verts.
  • 9 tests Playwright verts dont la recette R2 officielle : Karim signale → Salma approuve+assigne → OT → démarrage → clôture bloquée visible → bilan → clôture → la demande de Karim passe « Résolue » ; + préventif idempotent via l'UI, urgence bout-en-bout, compteur croissant. 50 tests API (94,6 % / 78,9 %).

Prochaine étape : R2.4 — portail public QR (/q/{ref} → signalement prérempli sans compte, suivi sans jargon — écran validé R0), puis recette R2 + déploiement + tag.


2026-07-16 — Pr. Daaif (+ Claude) — R2.2 : préventif (génération idempotente) + compteurs

Actions

  • Modèle (migration r2_preventif_compteurs) : Asset.underContract (contrat préventif, maquette fiche appareil) et WorkOrder.periodKey avec unicité [assetId, periodKey] : l'idempotence de la génération est EN BASE, pas seulement dans le code — deux générations concurrentes ne peuvent pas doubler une grille.
  • Génération mensuelle (POST /preventive/generate, 200 idempotent) : une grille par appareil sous contrat ; tâches dues par périodicité ancrée sur la mise en service (mensuelles toujours dues) ; appareil sans historique préventif → OT « Premier contrôle » avec toutes les tâches ; échéance = fin de mois ; événement GENERATED tracé. Déclenchement manuel en R2 (bouton maquette) — l'automatisation cron/BullMQ viendra avec le durcissement production.
  • Gabarits administrables (ajout/renommage/désactivation — un gabarit désactivé sort des générations suivantes) ; GET /preventive/status (générées, terminées, en retard, premiers contrôles du mois).
  • Compteurs : GET /assets/{id}/meters (les 2 compteurs, relevés récents d'abord) ; POST /assets/{id}/meter-readingsrelevé strictement croissant, refus motivé sinon.
  • Contrat : +7 opérations (48). 50 tests verts (couverture 94,7 % / 78 %) : idempotence (8 grilles seed, jamais 16), premier contrôle vs grille normale, mois anniversaire (mars 2031 pour A1 : les 3/6/12 mois tombent ensemble), gabarit désactivé exclu, compteur qui refuse de redescendre. Smoke test prod : juillet 2026 généré (9 grilles, 8 premiers contrôles), 2ᵉ appel à zéro.

Leçon

  • Les specs Jest partagent la base en parallèle : les assertions e2e doivent porter sur leurs propres données (ici les 8 appareils du seed), jamais sur des comptages globaux.

Prochaine étape : R2.3 — écrans web de l'exploitation (liste/fiche OT, demandes+approbation, nouvel OT, préventif, checklist, compteurs, chip urgence + bandeau tableau de bord).


2026-07-16 — Pr. Daaif (+ Claude) — R2.1 : socle backend de l'exploitation

Actions

  • Modèle R2 (doc + migration r2_exploitation, 10 tables) : WorkOrder (référence séquentielle, horodatages par état), WorkOrderEvent (activité), Request (1-1 vers OT, motif de rejet), ReferenceValue (référentiels du bilan par champ), InterventionReport (6 FK nommées), TaskTemplate/ChecklistItem, Meter/MeterReading.
  • Contrat : 15 opérations (41 total) — la table des transitions et les champs requis du bilan vivent dans @siop/shared (une seule loi pour l'API et l'UI) ; la fiche OT expose allowedTransitions et closureBlockers (messages métier).
  • API : machine à états stricte (transition → DONE refusée si bilan incomplet OU checklist avec tâche sans réponse — messages « quoi faire », charte §7) ; approbation de demande = création d'OT lié en 1-1 (double traitement → 409) ; rejet à motif obligatoire ; scoping « voir autre » appliqué aux listes ET aux accès directs (404, pas de fuite) ; valeurs de bilan validées champ par champ.
  • Seed : 31 valeurs de référentiels (6 champs), 8 gabarits de préventif (parachute réglementaire), 4 OT et 4 demandes rejouant la maquette, compteurs d'A1.
  • 45 tests verts (couverture 95 % / 79 % branches) dont la recette officielle rejouée : demande Karim → approbation Salma → OT assigné Ahmed → démarrage → clôture refusée sans bilan → bilan codé → clôture → Karim voit sa demande résolue. Smoke test build prod.

Décisions

  • POST de transition/rejet répondent 200 (le contrat prime sur le défaut 201 de Nest — c'est le contrat qui a raison).
  • La priorité « personne bloquée » trie en tête côté API : aucun client ne peut l'oublier.

Prochaine étape : R2.2 — génération mensuelle du préventif (BullMQ, idempotente, premier contrôle) + API compteurs, puis R2.3 écrans web.


2026-07-16 — Pr. Daaif (+ Claude) — R2 ouverte : maquettes des écrans manquants à valider

Actions

  • R2 « Exploitation » ouverte — design d'abord. Les écrans cœur sont déjà validés depuis R0 (tableau de bord, liste OT, fiche OT avec bilan codé 6 champs et garde de clôture, portail demandeur) ; maquette-r2.html complète avec les 5 écrans manquants : Demandes (file + panneau d'approbation : approuver → OT 1-1, rejeter avec motif requis), Nouvel OT (interrupteur « personne bloquée » explicite), Préventif (gabarits à périodicité — parachute marqué réglementaire — génération du mois idempotente, premiers contrôles), OT préventif (checklist Fait/N-A, clôture bloquée si tâche sans réponse), Compteurs (relevés croissants, historique).
  • Rendu vérifié (5 écrans + thème sombre, zéro erreur).

Décisions (proposées à la validation)

  • Statuts de DEMANDE distincts (Reçue / Approuvée→OT lié / Rejetée / Résolue) réutilisant la sémantique chromatique des statuts OT ; à trancher : est-ce une entorse à « réservés » de la charte ou une extension cohérente ?
  • Un rejet de demande exige un motif (lisible côté portail, sans jargon).
  • Génération mensuelle : automatique le 1ᵉʳ à 06 h + bouton manuel ; regénérer ne crée que le manquant ; appareil sans historique → OT « premier contrôle » (toutes les tâches).
  • Relevé de compteur strictement croissant (refus sinon).

Bloquant : validation des maquettes R2 par le référent avant toute ligne de code R2. → Levé le 16/07/2026 : maquettes R2 et les 4 décisions VALIDÉES par le référent (statuts de demande = extension cohérente, actée). Lancement R2.1 (socle backend exploitation).


2026-07-16 — Pr. Daaif (+ Claude) — 🏁 R1 CLOSE : recettée, déployée, taguée

Actions

  • Recette R1 prononcée par le référent (écrans ↔ maquette-r1, deux thèmes).
  • Déploiement vérifié en ligne (https://siop2.apps.enset.top) : migration r1_referentiel + seed appliqués au boot du conteneur — 5 sites, 8 appareils, écrans /sites servis ; parcours API admin vérifié (locations, assets).
  • DoD complète (recette, CI 6 jobs verts, revue pixel, production, journal) → tag release/r1.

Prochaine étape : R2 Exploitation. Maquettes R0 déjà validées pour tableau de bord, liste OT, fiche OT (bilan codé), portail demandeur ; il restera à maquetter le préventif (gabarits, grille du mois, checklist) avant tout code R2 — design d'abord.


2026-07-16 — Pr. Daaif (+ Claude) — R1.2 : écrans web du référentiel

Actions

  • 7 écrans fidèles à maquette-r1.html : Sites (liste + carte Leaflet/OSM réelle, pins maquette, création en modale avec position posée au clic), Fiche site (arbre site→zones, appareils, ajout de zone), Ascenseurs (table dense, filtres site/statut, strie rouge sur appareil à l'arrêt), Fiche appareil (écran validé R0 : identité, organes ± ajout/retrait, changement de statut, QR réel ; historique R2 et documents R3 annoncés sans simulation), Nouvel ascenseur (identité → rattachement → organes ligne à ligne), Étiquette A6 imprimable (window.print n'imprime qu'elle, objet papier même en sombre), Personnes & équipes (invitation → lien d'activation affiché à copier — pas d'email en R1, décision explicite ; renvoi de lien ; équipes), Catégories (ajout, renommage inline, désactivation).
  • Page /activation (cible du lien d'invitation, moule visuel de la connexion) — la personne choisit son mot de passe et entre directement.
  • Navigation pilotée par la matrice : les entrées livrées n'apparaissent que si canView (Catégories exige SETTINGS) ; boutons de création/édition conditionnés par can() — l'API re-vérifie de toute façon.
  • e2e Playwright : la recette R1 officielle rejouée intégralement (site → zone → appareil+organe → étiquette → invitation → activation du compte → l'invité est connecté) + un parcours « la matrice pilote l'UI » (Technicien : lecture sans boutons) ; purge idempotente des données de test en globalSetup. 5 tests verts, 4 vitest, build prod, lint.

Leçon

  • Paquet workspace CJS + Vite : les exports nommés runtime échouent (pas de pré-bundle des paquets liés) → alias Vite @siop/sharedsource TypeScript ; l'API garde le dist CJS. Symptôme vicieux : app blanche, tests e2e tous rouges.

Prochaine étape : R1.3 — recette R1 avec le référent (revue pixel écrans ↔ maquette-r1), déploiement (CI → Dokploy), tag release/r1. Puis R2 Exploitation (maquettes déjà validées pour OT/demandes/portail).


2026-07-16 — Pr. Daaif (+ Claude) — R1.1 : socle backend du référentiel

Actions

  • Modèle R1 (doc + Prisma + migration r1_referentiel) : Category (kind EQUIPMENT/COMPONENT_TYPE), Location (site → zone, lat/lng + colonne PostGIS générée geography(Point,4326) + index GIST), Asset (statut d'équipement), AssetComponent (organe sans emplacement par construction), Team (m2m User), invitation sur User (token unique + expiration). Migration autosuffisante (CREATE EXTENSION IF NOT EXISTS postgis).
  • Contrat : 21 nouvelles opérations (26 au total) — categories/locations/assets+organes/teams/users/roles/invitations/activate ; générateur OpenAPI étendu aux paramètres de chemin ; spec + client web régénérés dans le même commit.
  • API : 4 nouveaux modules + gestion des personnes, tous sous @RequirePermission (la matrice décide) ; invariants en service : profondeur 2, kinds de catégories, catégorie jamais supprimée, lien d'activation 7 jours à usage unique qui connecte directement.
  • Seed : parc de la maquette (5 sites + 8 zones, 8 appareils dont B2 à l'arrêt et M1 en maintenance, organes d'A1/B2, 9 catégories, 2 équipes).
  • 36 tests verts (couverture 96 % stmts / 85 % branches) : parcours de recette site→zone→appareil→organes, profondeur 3 refusée, matrice vivante (Technicien lit mais ne crée pas), invitation→activation complète (lien périmé/consommé/renvoyé). Smoke test sur build de prod : sites avec compteurs, fiche A1 et ses 4 organes.
  • CI : bascule sur postgis/postgis:18-3.6 (la migration R1 l'exige) — le moment anticipé dans le commentaire du workflow.

Leçons

  • Migration modifiée après application locale ⇒ réaligner son checksum dans _prisma_migrations (ou reset) — d'où la règle : rendre la migration autosuffisante AVANT de l'appliquer.
  • Un serveur reuseExistingServer de Playwright peut squatter :3000 et faire tester un dist périmé — tuer le port avant tout smoke test.

Prochaine étape : R1.2 apps/web — écrans Sites (+ carte Leaflet/OSM), Fiche site, Ascenseurs, Nouvel ascenseur, Étiquette QR, Personnes & équipes, Catégories, fidèles à maquette-r1.html.


2026-07-16 — Pr. Daaif (+ Claude) — R0 CLOSE (tag) · R1 ouverte : maquettes à valider

Actions

  • R0 close : recette prononcée par le référent, tag release/r0 poussé (DoD complète : CI verte, revue pixel, production en ligne, journal).
  • R1 « Référentiel » ouverte — design d'abord (principe n°1) : maquette-r1.html produite dans le moule validé (CSS répliquée de maquette-web.html, mêmes tokens/typo/motifs) — 7 écrans : Sites (liste + carte PostGIS), Fiche site (hiérarchie d'emplacements), Ascenseurs (liste, statuts d'équipement distincts des statuts OT), Nouvel ascenseur (identité → rattachement → organes), Étiquette QR imprimable (A6 papier, blanche même en thème sombre), Personnes & équipes (invitation par lien d'activation 7 jours, modale montrée), Catégories (référentiels administrables, jamais de suppression si utilisé).
  • Rendu vérifié en Chrome headless : 7 écrans + 2 contrôles thème sombre, zéro erreur.

Décisions (proposées à la validation)

  • Statuts d'équipement (En service / À l'arrêt / En maintenance) distincts des statuts OT ; l'appareil à l'arrêt porte la strie rouge.
  • Un organe n'a jamais d'emplacement propre : il suit son appareil (contrainte en base, comme en v1).
  • Invitation par lien d'activation (7 jours) — aucun mot de passe créé pour autrui ; compte inactif avant activation.
  • Catégorie utilisée : renommage/désactivation seulement, jamais de suppression.

Bloquant : validation des maquettes R1 par le référent avant toute ligne de code applicatif R1. → Levé le 16/07/2026 : maquettes R1 et les 4 décisions de conception VALIDÉES par le référent. Lancement R1.1 (socle backend).


2026-07-16 — Pr. Daaif (+ Claude) — 🚀 R0 EN PRODUCTION : https://siop2.apps.enset.top

Actions

  • Blocage de clone résolu (le provider « Custom » HTTPS n'avait pas d'identifiants sur dépôt privé) ; déploiement Dokploy réussi depuis GitHub, domaine posé sur siop2-web:80, certificat Let's Encrypt émis.
  • Vérification de l'instance en ligne (commit 3f9d0d8) : /api/healthok (base, Redis, MinIO up) ; 7 comptes démo ; demo-login → /users/me (Salma Idrissi, Dispatcher, 10 lignes de matrice) ; 401 sans jeton ; fallback SPA sur /design ; HTTPS valide.
  • Principe « déployer tôt » honoré : R0 est en ligne avant l'ouverture de R1.

Reste à faire

  • Poser le secret DOKPLOY_WEBHOOK_URL (runbook §4) pour activer le déploiement continu — le job deploy saute proprement tant qu'il manque.
  • Sauvegardes PostgreSQL côté Dokploy (runbook §5) à configurer.
  • Recette R0 avec le référent sur l'instance en ligne (revue pixel écrans ↔ maquettes) → clôture du jalon R0 → ouverture R1 Référentiel.

2026-07-15 — Pr. Daaif (+ Claude) — R0.13 : Dockerfiles + runbook Dokploy

Actions

  • Image API (multi-stage, node:24-slim) : pnpm deploy --legacy --prod, client Prisma régénéré dans l'arborescence déployée, entrypoint prisma migrate deploy → seed optionnel (SEED_ON_START=true, compilé en dist/seed) → API ; utilisateur non-root, HEALTHCHECK /health. prisma passe en dépendance de production (migrations au boot).
  • Image web (nginx:alpine) : statique Vite + proxy /api${API_UPSTREAM} (template envsubst) — même topologie que le proxy Vite de dev ; cache immuable sur /assets, no-cache sur index.html.
  • infra/docker-compose.dokploy.yml : 5 services préfixés siop2-, secrets exigés (:?), profils production client / instance démo documentés dans le fichier ; seul siop2-web rejoint dokploy-network (l'API n'est jamais exposée).
  • Runbook docs/06-production/runbook-dokploy.md : topologie, variables, checklist de première mise en production, releases suivantes, rollback, répétition locale.
  • Répétition locale validée : images construites, conteneurs lancés sur le réseau infra (migrate + seed au boot), parcours complet vérifié à travers nginx conteneurisé (/api/health tout up, demo-login → /users/me, fallback SPA).

Leçons de la répétition (consignées pour le playbook)

  • Prisma en conteneur : binaryTargets explicites dans schema.prisma (debian-openssl-3.0.x x64 serveur + linux-arm64-openssl-3.0.x répétition Mac) — sinon mismatch de moteur au runtime.
  • nginx : upstream résolu à la requête (resolver 127.0.0.11 + variable) et non au boot — sinon nginx refuse de démarrer si l'API n'est pas encore là.
  • Healthcheck alpine : 127.0.0.1 et non localhost (busybox wget tente ::1, nginx écoute en IPv4).
  • Le double verrou ADR-002 a été prouvé en vraie situation : l'image (NODE_ENV=production) avec DEMO_MODE=true sans DEMO_MODE_I_KNOW refuse de démarrer — comportement observé, pas seulement testé.

Décisions

  • Migrations au démarrage du conteneur (idempotentes) : la base suit toujours le code déployé ; un échec de migration arrête l'API sans servir de trafic.
  • Le web est l'unique service exposé (domaine → nginx → proxy interne /api) : mêmes chemins en dev et en prod, surface d'attaque minimale.

Statut : R0.13 prêt — la première exécution réelle attend les accès au serveur du partenaire. Reste pour clore R0 : recette avec le référent (revue pixel écrans ↔ maquettes).


2026-07-15 — Pr. Daaif (+ Claude) — R0.13 (suite) : instance ENSET + déploiement continu

Actions

  • Instance de démonstration décidée : https://siop2.apps.enset.top (Dokploy ENSET, projet compose créé par le référent) — la production client SPELEV suivra la même procédure avec le profil « production client ».
  • Job deploy ajouté au pipeline : appelle le webhook Dokploy sur push main uniquement si lint + contrat + api + web + e2e sont verts ; « skip » explicite tant que le secret DOKPLOY_WEBHOOK_URL n'est pas configuré. L'« Auto Deploy » natif de Dokploy reste désactivé (il ignorerait la CI).
  • Runbook §3 réécrit en checklist concrète (source GitHub, Compose Path, variables du profil démo, domaine → siop2-web:80) et §4 en procédure CD (secret webhook).

Prochaine étape : premier déploiement manuel (runbook §3), pose du secret DOKPLOY_WEBHOOK_URL (§4), vérification https://siop2.apps.enset.top/api/health, puis recette R0 avec le référent sur l'instance en ligne.


2026-07-15 — Pr. Daaif (+ Claude) — R0.12 (2/2) : ESLint + parcours Playwright — R0.12 CLOS

Actions

  • ESLint 10 (flat config unique à la racine, typescript-eslint, react-hooks sur le web) + scripts lint par paquet et job CI dédié. La règle d'architecture ADR-001 est codée : no-restricted-imports interdit minio partout sauf minio-storage.service.ts — violation vérifiée par un fichier-test (détectée puis retiré). Monorepo lint : 0 erreur.
  • Playwright (apps/web/e2e) : 3 tests — redirection sans jeton (fermée par défaut), parcours complet connexion démo → coquille (rail actif, chip DÉMO) → bascule de rôle chronométrée < 3 s (ADR-002) → /design, et déconnexion avec purge de session. webServer démarre l'API construite + Vite ; seed idempotent en globalSetup ; localement PW_CHANNEL=chrome (pas de téléchargement).
  • CI : jobs lint et e2e (services PostgreSQL/Redis, artefact playwright-report en cas d'échec). Vitest restreint à src/ (e2e appartient à Playwright).

Décisions

  • Une seule config ESLint à la racine (pas une par app) : les règles d'architecture sont transversales et la config est le lieu du cours.

Prochaine étape : R0.13 — Dockerfiles (api, web) + runbook Dokploy (docs/04-*), puis recette R0 avec le référent (revue pixel écrans ↔ maquettes).


2026-07-15 — Pr. Daaif (+ Claude) — R0.12 (1/2) : pipeline GitHub Actions

Actions

  • .github/workflows/ci.yml, 3 jobs sur push main + PR : ci-contract (régénère docs/openapi.json + schema.d.ts, échoue au moindre diff — la règle d'or devient bloquante), api (PostgreSQL 18 + Redis en services, prisma migrate deploy, typecheck, Jest avec couverture ≥ 70 % bloquante via coverageThreshold), web (typecheck + vitest + build prod).
  • Test unitaire de PermissionsGuard ajouté (aucune route @RequirePermission en R0 ne l'exerçait) : couverture 97,5 % stmts / 90,7 % branches, 23 tests.
  • Badge CI dans le README ; ci-contract simulé en local (diff propre) avant push.

Décisions

  • CI sur PostgreSQL nu en R0 (la migration r0_identity n'exige aucune extension) ; bascule sur l'image infra/postgres dès que des tests toucheront pgvector/PostGIS (R1+).
  • Pas de MinIO en CI : /health répond degraded sans casser les tests — seul database: up est exigé.

Prochaine étape : R0.12 (2/2) — ESLint (dont la règle « pas d'import MinIO hors FileStorage »), parcours Playwright (connexion démo → coquille → bascule de rôle), puis R0.13 Dockerfiles + runbook Dokploy.


2026-07-15 — Pr. Daaif (+ Claude) — R0.11 : apps/web (connexion + sélecteur démo, coquille, /design)

Actions

  • apps/web (React 19 + Vite + Tailwind v4) : tokens.css copié tel quel depuis 02-design ; classes composants extraites de la maquette validée ; Manrope auto-hébergée (@fontsource) ; primitives shadcn-style possédées (Button/variants cva, menu Radix).
  • Client typé : pnpm generate:clientsrc/api/schema.d.ts généré depuis docs/openapi.json et committé (règle d'or) ; openapi-fetch + injection du jeton.
  • Écrans : connexion (fidèle maquette, liste démo masquée si l'API répond 404 — ADR-002), coquille sidebar (4 groupes, rail safran actif, écrans à venir marqués R1-R3) + topbar (recherche ⌘K, bascule de thème, chip DÉMO, sélecteur de rôle dans le menu compte), /design (référence vivante : nuanciers, typo, boutons, pastilles), tableau de bord R0 minimal.
  • Compléments : tokens --nav-* ajoutés à tokens.css (valeurs issues de la maquette validée) ; seed réaligné sur les personas de la maquette (Salma Idrissi, Ahmed Benali, …) pour des revues pixel cohérentes.
  • Vérifié dans un vrai navigateur (Playwright + Chrome headless) : parcours démo-login → tableau de bord → bascule Dispatcher→Technicien en 101 ms (critère < 3 s) → /design clair & sombre ; 7 captures, zéro erreur console. Typecheck, 3 tests vitest, build prod OK.

Décisions

  • Le web ne lit pas VITE_DEMO_MODE : la présence du mode démo est déduite de la réponse de /auth/demo-accounts (404 ⇒ rien n'est affiché) — une seule source de vérité, l'API.
  • Les entrées de navigation des releases futures restent visibles mais inertes, marquées R1/R2/R3 — le périmètre est annoncé, pas simulé.

Prochaine étape : R0.12 — CI GitHub Actions (lint, typecheck, tests, couverture api ≥ 70 %, ci-contract sur openapi.json/clients, Playwright du parcours démo) puis R0.13 Dockerfiles + runbook Dokploy.


2026-07-15 — Pr. Daaif (+ Claude) — R0.10 : apps/api complète (auth, matrice, démo-login, seed)

Actions

  • packages/shared : vocabulaires (7 rôles, 10 catégories d'objets), schémas Zod (auth, profil, santé) et contrat d'API ; pnpm contract génère docs/openapi.json (committée — règle d'or ADR-001).
  • apps/api (NestJS 11 + Prisma 6) : migration r0_identity (Role/Permission/User) ; guard JWT global fermé par défaut (+ @Public() explicite) ; PermissionsGuard (@RequirePermission, matrice relue en base, cache 60 s) ; FileStorage (seul point d'import MinIO) ; /health (base, Redis, stockage).
  • Démo-login ADR-002 : module enregistré uniquement si DEMO_MODE=true (sinon routes 404), double verrou production (DEMO_MODE_I_KNOW), refus des comptes isDemo=false.
  • Seed idempotent : 7 rôles, matrice complète (70 lignes), 7 comptes démo (mot de passe commun SEED_DEMO_PASSWORD pour la connexion classique).
  • Vérifié bout-en-bout : 19 tests Jest verts (dont e2e démo on/off) ; smoke test sur build de prod — démo-login → /users/me avec matrice, 401 sans jeton, health ok.

Décisions

  • AppModule.forRoot() (module dynamique) pour rendre l'enregistrement conditionnel du module démo testable dans les deux états — le e2e « routes absentes » est l'exigence n°1 de l'ADR-002.
  • Le seed n'écrase jamais une ligne de matrice existante : la base est la source de vérité des droits, le fichier n'est que le point de départ.

Prochaine étape : R0.11 apps/web — login + sélecteur de comptes démo (fidèle à maquette-web.html), coquille sidebar/topbar avec bandeau « DÉMO », page /design, client typé généré depuis docs/openapi.json.


2026-07-15 — Pr. Daaif (+ Claude) — R0 : maquettes VALIDÉES ; architecture + squelette

Actions

  • Maquettes HD validées par le référent → le design est la loi des revues pixel.
  • 03-architecture : ADR-001 (stack), ADR-002 (démo-login DEMO_MODE, double verrou prod), vue C4, modèle de données R0 (Role/Permission/User, isDemo).
  • Racine monorepo (pnpm + turbo, Node 24) ; infra/ : compose local (PostgreSQL 18 pgvector+PostGIS via Dockerfile dédié, Redis, MinIO).

Décisions

  • Convention siop2- pour tous services/conteneurs Docker (référent — collisions sur le réseau partagé Dokploy en v1).

Prochaine étape (reprise) : R0.10 apps/api (auth JWT + matrice permissions + démo-login + seed) → R0.11 apps/web (login + sélecteur démo + coquille + /design) → R0.12 CI → R0.13 prépa Dokploy.


2026-07-15 — Pr. Daaif (+ Claude) — R0 : kickoff, vision, cadrage, design

Actions

  • Refondation décidée : nouveau dépôt siop-spelev/siop2, 100 % nouveau code ; la v1 (siop) est gelée comme référence. Jalons R0→R5 créés.
  • Playbook initialisé : 00-vision (charte projet, personas, benchmark Atlas), 01-cadrage (plan de releases avec DoD, user-story map, exigences non fonctionnelles mesurables, registre des risques).
  • 02-design : charte graphique (bleu-treuil / safran / acier, Manrope, motif « rail »), tokens.css (source de vérité, bi-thème), palettes analytics validées par script (CVD + contraste, modes clair et sombre), maquettes HD navigables (7 écrans : connexion + démo-login, tableau de bord, liste OT, fiche OT avec bilan codé, fiche ascenseur + QR, portail demandeur, mobile technicien) — publiées pour revue.

Décisions (référent, via questions)

  • Diagnostic v1 : esthétique absente, docs éparpillées, périmètre dérivant, bascule de comptes pénible → V2 design-first, playbook en livre, releases fermées, sélecteur de compte démo (DEMO_MODE) dès R0.
  • 100 % nouveau code ; périmètre v1 = web + mobile + IA par paliers ; déploiement Dokploy ; nom conservé : SIOP.

Prochaine étape : validation de la charte + maquettes par le référent (bloquant — aucun code applicatif avant), puis 03-architecture + squelette monorepo.