Expo Go (App Store) bloqué par le retard d'approbation Apple (SDK 54
contre notre SDK 57) — décision : builds natifs locaux (Xcode/Android
Studio, signature Apple ID personnel gratuite) pour la vraie recette
terrain, Expo Go conservé pour l'aperçu sans installation ailleurs.
- ADR-005 : deux canaux de distribution, managed workflow conservé.
- apps/mobile/.env : EXPO_PUBLIC_USE_RN_FETCH=1 — fix permanent d'un
bug SDK 57 (fetch global expo/fetch incompatible avec l'upload
multipart natif {uri,name,type}, classé à tort « réseau
indisponible » par notre propre gestion d'erreur).
- package.json/app.json : scripts ios/android pointés sur les builds
natifs (expo run:*) plutôt que expo start (Expo Go), identifiants
de bundle générés par le prebuild.
- Recette iOS (iPhone 15 Pro physique) : connexion démo, scan caméra
réel (résolution locale + rejet QR étranger), vrai mode avion (D1),
conflit de version tranché par l'humain avec photo réellement
téléversée (D2/D5), persistance de la file à travers fermeture et
reconstruction de l'app, suggestions R5 au vrai modèle, purge de
sécurité au changement de compte (ADR-003) — 7/7 validés, la
validation terrain due depuis release/r4 est levée pour iOS.
- Android : build natif sur émulateur, passage santé complet
(connexion, navigation, écran Scanner, résolution de référence).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
74 KiB
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-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/r4a 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:iospar unxcodebuildmanuel (a produit un crashdyld: Library not loaded React.framework) ; incompatibilité des modules Expo/RN précompilés (SDK 56/57) avec la liaison statique du projet — fixEXPO_USE_PRECOMPILED_MODULES=0+RCT_USE_PREBUILT_RNCORE=0;fetchglobal (expo/fetch) incompatible avec l'upload multipart natif (Unsupported FormDataPart implementation, classé à tort « réseau indisponible » par notre propre gestion d'erreur) — fix permanentEXPO_PUBLIC_USE_RN_FETCH=1committé dansapps/mobile/.env; découverte réseau du dev client peu fiable sur ce Wi-Fi (IP du Mac changée 4 fois, reprises viaexpo 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/r4est 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/r5posé surcccfaaa(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/r5peut ê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-v2multilingue (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é, migrationr5_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 : servicesiop2-aiinterne (jamais surdokploy-network, secretAI_SERVICE_TOKENrequis, génération opt-in par variables, seuils calibrables),siop2-apibranché (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/r5attend 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) :
Documentexpose 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 leGenerateur(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/ask→AssistantAnswer(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 verssiop2-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'interfaceGenerateurd'ADR-004 §3 prend corps —GenerateurExtractif(contrat de base, aucun LLM) etGenerateurAPI(SDK officielanthropic, 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éfautclaude-opus-4-8)..env.exampleposé ;/healthzexpose 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é,
GenerateurAPIconstruit 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_SECRETet 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 (tableRagChunkpossédée par Prisma, migrationr5_ia+ état de corpus sur Document), génération opt-in (AI_GENERATION=offpar défaut : mode extractif honnête, la recette doit passer sans clé API), suggestion de bilan sans LLM (similarité sémantique, explicable). Topologie :siop2-aijamais exposé, joint par l'API NestJS seule (X-Service-Token). apps/aiposé (FastAPI + uv, python 3.11) : config validée au démarrage,/healthz,/internal/reindex(idempotent) et/internal/searchproté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 →RagChunkavec 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/r4posé et poussé (CI verte surc8b3c17: 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) :
updatedAtexposé au détail OT ;baseUpdatedAtoptionnel 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/REFde 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
closureBlockersde 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.checkedne produit PASaria-checked— utiliser la proparia-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/mobileposé (Expo SDK 57, TypeScript strict, pnpm workspace,@siop/sharedconsommé) : 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é depuisdocs/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
mobileajouté (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.changeobservé). - Leçon Playwright :
setOfflinene déclenche pas les événements réseau du navigateur — NetInfo web écoutenavigator.connectionsur 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/r3posé 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 (
monthsau contrat, libellés et barres suivent). Tiers : les syndics affichent leurs sites — nouveau lien site→client/syndic (migrationr3_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 permissionviewdu 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 ongletblob:(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-e2epurge 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
FileStoragegagne 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 à jourlastUnitPrice), 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/A1opé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 parréférence + jeton(pas de jeton, pas de lecture — aucune énumération possible). - Suivi sans compte :
Request.publicToken(migrationr2_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-idne 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) etWorkOrder.periodKeyavec 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-readings— relevé 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 exposeallowedTransitionsetclosureBlockers(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
POSTde 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.htmlcomplè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.printn'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 parcan()— 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/shared→ source 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éegeography(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
reuseExistingServerde 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/r0poussé (DoD complète : CI verte, revue pixel, production en ligne, journal). - R1 « Référentiel » ouverte — design d'abord (principe n°1) :
maquette-r1.htmlproduite dans le moule validé (CSS répliquée demaquette-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/health→ok(base, Redis, MinIOup) ; 7 comptes démo ; demo-login →/users/me(Salma Idrissi, Dispatcher, 10 lignes de matrice) ;401sans 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 jobdeploysaute 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, entrypointprisma migrate deploy→ seed optionnel (SEED_ON_START=true, compilé endist/seed) → API ; utilisateur non-root, HEALTHCHECK/health.prismapasse 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-cachesurindex.html. infra/docker-compose.dokploy.yml: 5 services préfixéssiop2-, secrets exigés (:?), profils production client / instance démo documentés dans le fichier ; seulsiop2-webrejointdokploy-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/healthtoutup, demo-login →/users/me, fallback SPA).
Leçons de la répétition (consignées pour le playbook)
- Prisma en conteneur :
binaryTargetsexplicites dansschema.prisma(debian-openssl-3.0.xx64 serveur +linux-arm64-openssl-3.0.xré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.1et nonlocalhost(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=truesansDEMO_MODE_I_KNOWrefuse 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
deployajouté au pipeline : appelle le webhook Dokploy sur pushmainuniquement si lint + contrat + api + web + e2e sont verts ; « skip » explicite tant que le secretDOKPLOY_WEBHOOK_URLn'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
lintpar paquet et job CI dédié. La règle d'architecture ADR-001 est codée :no-restricted-importsinterditminiopartout saufminio-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.webServerdémarre l'API construite + Vite ; seed idempotent en globalSetup ; localementPW_CHANNEL=chrome(pas de téléchargement). - CI : jobs
lintete2e(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èredocs/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 viacoverageThreshold), web (typecheck + vitest + build prod).- Test unitaire de
PermissionsGuardajouté (aucune route@RequirePermissionen R0 ne l'exerçait) : couverture 97,5 % stmts / 90,7 % branches, 23 tests. - Badge CI dans le README ;
ci-contractsimulé en local (diff propre) avant push.
Décisions
- CI sur PostgreSQL nu en R0 (la migration
r0_identityn'exige aucune extension) ; bascule sur l'imageinfra/postgresdès que des tests toucheront pgvector/PostGIS (R1+). - Pas de MinIO en CI :
/healthréponddegradedsans casser les tests — seuldatabase: upest 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.csscopié 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:client→src/api/schema.d.tsgénéré depuisdocs/openapi.jsonet 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 contractgénèredocs/openapi.json(committée — règle d'or ADR-001).apps/api(NestJS 11 + Prisma 6) : migrationr0_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 comptesisDemo=false. - Seed idempotent : 7 rôles, matrice complète (70 lignes), 7 comptes démo (mot de passe commun
SEED_DEMO_PASSWORDpour 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/meavec matrice, 401 sans jeton, healthok.
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.