Files
Omika/docs/RECETTE_ANDROID_AUDIT.md
T
2026-10-23 18:46:50 +02:00

26 KiB
Raw Blame History

RECETTE ANDROID — AUDIT AVANT MODIFICATION (OBJECTIF 0 / §17)

Projet : D:\Avalonia\Omika — Base OMIKA réelle. Aucun fichier modifié au stade d'audit. Ce document est le livrable de l'audit préalable. Les correctifs ne sont appliqués qu'après validation.

Sommaire

  1. Architecture rappelée
  2. Tableau de synthèse des problèmes
  3. Cause exacte par problème
  4. Classification
  5. Preuve de non-régression Batik
  6. Routes HTTP appelées
  7. Requêtes SQL concernées
  8. Inventaire des icônes à remplacer
  9. Propositions de SVG monochrome

1. Architecture rappelée

  • Mobile (src/Omika.App.Local) : offline-first sur SQLite, synchronisé avec l'API via MobileApiClient.
  • API (src/Omika.Api) : endpoints REST, sélection de base Batik/Omika via ApplicationRuntimeContext.Current.DetectedVersion.
  • Données (src/Omika.Data) : repositories Adaptive* qui délèguent à Batik*Repository (schéma historique) ou Omika*Repository (schéma physique Omika). Les mappers (Mapping/Omika, Mapping/Batik) produisent le modèle canonique.
  • Identité publique : GUID. Omika → GUID physique (colonne GUID). Batik → GUID synthétique réversible (MobileEntityIdentityResolver).
  • UI (src/Omika.App) : Avalonia, thèmes clair/sombre via DesignTokens.axaml (clés DynamicResource).

2. Tableau de synthèse

# PROBLÈME SYMPTÔME COUCHE FICHIER(S) CAUSE EXACTE SPÉCIFIQUE OMIKA ? BATIK IMPACTÉ ? CORRECTION PROPOSÉE
1 Carte OSM bloquée 403 / "volunteer-run servers" ANDROID/UI (tuiles) Controls/LieuMap.axaml.cs:97 OpenStreetMap.CreateTileLayer() (Mapsui) utilise un HttpClient sans User-Agent identifiant Omika NON NON (commun) Tile source custom avec User-Agent + cache + attribution ; fallback adresse+Itinéraire
2 Icônes hétérogènes Emoji/Unicode/couleurs UI multiples (cf. §8) Emoji/Unicode et couleurs codées en dur NON NON Standardiser en Path monochrome + DynamicResource
3 Boutons entête intervention fond blanc massif UI Views/RendezVousDetailView.axaml:120-139 Background=OnPrimaryColorBrush (blanc) + emoji ⌖ ⚒ ▧ NON NON Style translucide + SVG monochrome
4 Agenda équipe : icône groupe bleue/multicolore UI Views/AgendaView.axaml:377 (👥) emoji 👥 utilisé comme icône de groupe (pas dans AgendaEquipeView qui n'a pas d'icône) NON NON Remplacer par SVG/Path monochrome
5 RDV→intervention introuvable "Intervention introuvable." OMIKA DATA / API OmikaRendezVousRepository, MobileSnapshotService, OmikaInterventionRepository.GetDetailsAsync (voir §3.5) OUI NON Corriger le mapping/guid Omika
6A Note CreationUser=0 CreationUser=0 en base API / OMIKA DATA OmikaInterventionRepository.ResolveCreationUserAsync:1560 token CollaborateurId = PK numérique licence ; ResolveCreationUserAsync attend un GUID OUI NON Résoudre l'utilisateur depuis l'identité token (UserId/UT_PKID ou PK numérique)
6B Compteur notes = 0 "0 note(s)" LOCAL/API InterventionDetailViewModel.RefreshFromApiAsync compteur = Notes.Count chargé en arrière-plan via API ; échec/guid → vide OUI (lié #5) NON même cause que #5 + retry
7 Compteur images/documents = 0 "0 fichier(s)" API / OMIKA DATA MobileDocumentService.GetByInterventionAsync + OmikaDocumentRepository résolution guid intervention / filtre catégorie OUI NON même cause que #5 + vérif filtre
8 Écran Images/Documents titre redondant + bouton coupé UI Views/InterventionResourcesView.axaml:37,73 TextBlock "Images" redondant + StackPanel Orientation=Horizontal non responsive NON NON Supprimer titre + WrapPanel
9 Création email 404 404 modèles courriel API MobileApiClient.cs:761 + ModeleDocumentEndpoints.cs:18 Slash final manquant : mobile appelle GET /modele-mails, API enregistre GET /modele-mails/ (MapGroup("/modele-mails").MapGet("/")) NON (route commune) NON (corrigé pour les 2) MapGet("/") → MapGet("") (ou slash final côté mobile)
10 Rapports 404 404 modèles rapport API MobileApiClient.cs:771 + ModeleDocumentEndpoints.cs:28 Slash final manquant : GET /modele-rapports vs GET /modele-rapports/ NON NON idem #9
11 Bouton téléphone inopérant rien ne se lance ANDROID Services/PlatformPhoneLauncher.cs TopLevel.Launcher.LaunchUriAsync (ACTION_VIEW) ≠ ACTION_DIAL NON NON Launcher Android ACTION_DIAL (tel:)
12 Bouton email inopérant aucun client mail ANDROID Services/PlatformEmailLauncher.cs LaunchUriAsync générique ≠ ACTION_SENDTO NON NON Launcher Android ACTION_SENDTO (mailto:)
13 Fiche équipe/collaborateurs responsive email sur plusieurs lignes, cartes hautes UI CollaborateursView, CollaborateurDetailView, EquipeDetailView layout non responsive NON NON Réorganiser grille responsive
14 Messages d'erreur techniques "Response status code…404" affiché UI NouveauEmailViewModel, RapportChampsViewModel ErrorMessage = ex.Message brut NON NON Message utilisateur propre + log technique

3. Cause exacte par problème

3.1 Carte OpenStreetMap bloquée

  • LieuMap.axaml.cs:97 appelle OpenStreetMap.CreateTileLayer() (Mapsui). Le HttpTileSource par défaut de Mapsui charge https://tile.openstreetmap.org/{z}/{x}/{y}.png via un HttpClient sans User-Agent identifiant Omika → OSM rejette avec 403 (« volunteer-run servers »).
  • Le géocodeur AddressGeocoder (Nominatim) a déjà un User-Agent correct (Omika-Mobile/1.0), mais pas le chargement des tuiles.
  • Pas de cache de tuiles configuré, pas d'attribution OSM visible, pas de fallback propre : l'image 403 brute est affichée.

3.5 RDV → « Ouvrir l'intervention » → introuvable

Flux tracé (lecture de code) :

  1. RendezVousDetailViewModel.OpenIntervention → navigation avec RendezVous.InterventionGuid (GUID).
  2. AgendaItem.InterventionGuid ← AgendaService.MapToAgendaItem ← RendezVousDetailsDto.InterventionId.
  3. RendezVousDetailsDto.InterventionId ← MobileSnapshotService.MapRendezVous ← MobileIdentityMapper.ResolveIdString(Intervention, dto.InterventionSourceReference).
  4. InterventionSourceReference ← OmikaRendezVousMapper : { NumericId: RV_IdInter, EntityGuid: ResolveGuid(interventionGuids, RV_IdInter) }.
  5. Pour Omika, MobileEntityIdentityResolver.CreateOmikaIdentity retourne physical.Guid. Si EntityGuid est null (GUID intervention non résolu), CreateOmikaIdentity lève InvalidOperationException (« aucun GUID physique fourni »).
  6. Navigation → InterventionLocalService.GetInterventionByIdAsync(Guid) → API GET /interventions/{guid} → OmikaInterventionRepository.GetDetailsAsync (GUID → GUID IN (...)).

Conclusion de la trace statique (chaîne vérifiée de bout en bout) : la chaîne d'identité est correcte pour Omika :

  • OmikaRendezVousMapper résout InterventionSourceReference = { NumericId: RV_IdInter, EntityGuid: Intervention.GUID } en bulk (LoadInterventionGuidsAsync, requête IN_PKID IN (...)).
  • MobileSnapshotService.MapRendezVous → ResolveIdString → CreateOmikaIdentity retourne le GUID physique.
  • AgendaItem.InterventionGuid = ce GUID ; navigation → API GET /interventions/{guid} → GetDetailsAsync (GUID) ; repli local sur la table snapshot Intervention (peuplée par SnapshotApplier à partir de LoadInterventionsForRendezVousAsync, clé = GUID canonique).
  • PhysicalGuidParser.GetStorageCandidates couvre les formes D / N / D-tronquée.

Déclencheur exact restant = niveau données (à confirmer par les logs temporaires demandés au §5 de la recette) :

  • soit RV_IdInter pointe vers une intervention dont la colonne GUID est NULL/invalide (→ EntityGuid null → CreateOmikaIdentity lèverait, OU InterventionId null) ;
  • soit l'intervention référencée n'existe pas dans Intervention (référence orpheline) ;
  • soit un écart de format GUID entre RV_IdInter→GUID et l'Intervention.GUID réel (byte-order HFSQL).

Logs temporaires à poser (correction #5) : RDV_PKID, RDV GUID, RV_IdInter, Intervention.GUID résolu, InterventionGuid envoyé à la navigation (type + valeur), requête SQL utilisée, résultat. Aucun IN_NumIntervention ne doit être réintroduit.

Les compteurs #6B et #7 passent par les mêmes endpoints API (GET /api/interventions/{guid}/notes, GET /api/interventions/{guid}/documents) : si la résolution d'intervention échoue, listes vides → « 0 note(s) » / « 0 fichier(s) ». #7 a en plus un filtre par catégories de document (BuildAllowedDocumentCategories + IsCategoryAllowed) qui peut vider la liste si le profil n'a pas le droit A.DOC.* correspondant.

3.6A Note CreationUser = 0

  • Endpoint AddNoteForInterventionAsync (InterventionEndpoints.cs:427) transmet httpContext.GetMobileIdentity()?.CollaborateurId à InterventionService.AddNoteAsync → ResolveCreationUserAsync(collaborateurId).
  • En production, LicenseEndpoints.BuildSession (LicenseEndpoints.cs:72) pose CollaborateurId = response.CollaborateurId = licence.CollaborateurId.ToString() (PK numérique du collaborateur, cf. LicenseService.BuildSuccessAsync:404).
  • OmikaInterventionRepository.ResolveCreationUserAsync (:1560) fait Guid.TryParse(collaborateurGuid, out Guid) : une valeur numérique échoue → retour 0 → CreationUser = 0.
  • BatikInterventionRepository.ResolveCreationUserAsync (:1513) décode un GUID synthétique Batik : une valeur numérique échoue aussi (Guid.TryParse → 0).
  • Racine : incohérence de type entre la claim CollaborateurId du token (numérique en prod) et l'API de ResolveCreationUserAsync (attend un GUID).

3.9 / 3.10 Email / Rapport 404 — CAUSE EXACTE CONFIRMÉE

  • Slash final manquant. MobileApiClient.GetModeleMailsAsync appelle GET {BaseUrl}/modele-mails (sans slash), mais l'API enregistre la route GET /modele-mails/ (avec slash) via MapGroup("/modele-mails").MapGet("/") (ModeleDocumentEndpoints.cs:18). En ASP.NET Core minimal API, MapGroup("/x").MapGet("/") produit /x/ et ne matche pas /x → 404.
  • Idem GetModeleRapportsAsync → GET /modele-rapports vs GET /modele-rapports/ (ModeleDocumentEndpoints.cs:28).
  • Les autres routes auditées (/mot-clefs, /generation/email, /generation/rapport, /api/rapports/champs, /api/mails/champs, /api/mails, /api/mails/apercu, /api/rapports) matchent exactement. Le préfixe /api est cohérent, les routes sont bien enregistrées (Program.cs:243-244), et MobileAuthMiddleware ne renvoie jamais 404.
  • Correction : MapGet("/") → MapGet("") dans ModeleDocumentEndpoints (canonique, fait matcher /modele-mails sans slash) — sans impact Batik (route schéma-agnostique, corrige les deux schémas). Alternative : ajouter le slash final dans MobileApiClient (l.761/771).
  • Référence historique Mail_Modele / ModeleRapport WHERE MRP_EnvoiMob=1 → routes REST actuelles (cf. docs/modeles-email-rapport.md).

3.11 / 3.12 Téléphone / Email inopérants

  • PlatformPhoneLauncher.DialAsync et PlatformEmailLauncher.ComposeAsync utilisent TopLevel.Launcher.LaunchUriAsync(uri) (launcher générique Avalonia). Sur Android, ce launcher utilise ACTION_VIEW, qui ne lance pas le composeur natif (tel:) ni le client mail de façon fiable.
  • Aucun launcher Android spécifique n'existe (src/Omika.App.Android ne contient pas de Phone/Email launcher).
  • Les ViewModels ignorent le retour bool : aucun message si aucun handler n'est disponible.

3.14 Messages d'erreur techniques

  • NouveauEmailViewModel.LoadAsync et RapportChampsViewModel.LoadAsync font catch (Exception ex) { ErrorMessage = ex.Message; } ; EnsureSuccessStatusCode() lève HttpRequestException dont le message brut (« Response status code does not indicate success: 404 (Not Found). ») est affiché tel quel.

4. Classification

# Classification
1 ANDROID/UI (commun, non spécifique données)
2 UI
3 UI
4 UI
5 OMIKA DATA + API (résolution identité intervention)
6A API + OMIKA DATA (résolution utilisateur)
6B LOCAL/API (compteur = source API)
7 API + OMIKA DATA (documents)
8 UI
9 API (route/déploiement)
10 API (route/déploiement)
11 ANDROID (launcher)
12 ANDROID (launcher)
13 UI
14 UI

5. Preuve de non-régression Batik

  • Les corrections UI/Android (#1, #2, #3, #4, #8, #11, #12, #13, #14) ne touchent aucune requête Batik : elles portent sur les .axaml, les launchers Android et les messages d'erreur, partagés entre les deux schémas.
  • Les corrections données (#5, #6A, #7, #9, #10) seront appliquées uniquement dans les providers/repositories/mappers Omika (src/Omika.Data/Repositories/Omika/*, Mapping/Omika/*) ou dans la couche d'identité commune, sans modifier Batik*Repository ni Batik*Mapper.
  • ResolveCreationUserAsync Batik est déjà isolé dans BatikInterventionRepository ; sa correction éventuelle (acceptation du PK numérique) doit être faite symétriquement sans casser le décodage du GUID synthétique Batik (test à prévoir).
  • Les routes API (#9, #10) sont communes : l'ajout d'un alias /api/modele-mails (si retenu) n'affecte pas le comportement Batik, qui utilise la même API.

6. Routes HTTP actuellement appelées (MobileApiClient → Omika.Api)

Méthode client URL appelée Route API Match
GetModeleMailsAsync GET /modele-mails ModeleDocumentEndpoints /modele-mails ✅ (préfixe sans /api)
GetModeleRapportsAsync GET /modele-rapports ModeleDocumentEndpoints /modele-rapports ✅
GetMotClefsAsync GET /mot-clefs ModeleDocumentEndpoints /mot-clefs ✅
GetEquipesAsync GET /equipes EquipeEndpoints /equipes ✅
GetAllEquipesAsync GET /equipes/all EquipeEndpoints /equipes/all ✅
GetCollaborateurByIdAsync GET /collaborateurs/{id} CollaborateurEndpoints ✅
GetRendezVousByIdAsync GET /rendez-vous/{id} RendezVousEndpoints ✅
GetInterventionByIdAsync GET /interventions/{id} InterventionEndpoints ✅
GetDossierByIdAsync GET /dossiers/{id} DossierEndpoints ✅
GetClientByIdAsync GET /clients/{id} ClientEndpoints ✅
GetSiteByIdAsync GET /sites/{id} SiteEndpoints ✅
GetContactByIdAsync GET /contacts/{id} ContactEndpoints ✅
GetCompteRenduByRendezVousAsync GET /rendez-vous/{id}/compte-rendu CompteRenduEndpoints ✅
GetNotesByInterventionAsync GET /api/interventions/{id}/notes InterventionEndpoints mobileGroup ✅
AddNoteByInterventionAsync POST /api/interventions/{id}/notes InterventionEndpoints mobileGroup ✅
GetDocumentsByInterventionAsync GET /api/interventions/{id}/documents InterventionEndpoints mobileGroup ✅
GetRendezVousByInterventionAsync GET /api/interventions/{id}/rendez-vous InterventionEndpoints mobileGroup ✅
GetEmailsByInterventionAsync GET /api/interventions/{id}/emails InterventionEndpoints mobileGroup ✅
AddEmailByInterventionAsync POST /api/interventions/{id}/emails InterventionEndpoints mobileGroup ✅
GetSmtpConfigurationsAsync GET /api/mails/configurations-smtp RapportChampEndpoints ✅
GetRapportChampsAsync POST /api/rapports/champs RapportChampEndpoints ✅
GetMailChampsAsync POST /api/mails/champs RapportChampEndpoints ✅
CreateEmailModeleAsync POST /api/mails RapportChampEndpoints ✅
GetEmailModeleApercuAsync POST /api/mails/apercu RapportChampEndpoints ✅
CreateRapportAsync POST /api/rapports RapportChampEndpoints ✅
GenerateEmailAsync POST /generation/email ModeleDocumentEndpoints ✅
GenerateRapportAsync POST /generation/rapport ModeleDocumentEndpoints ✅
SearchInterventionsAsync POST /api/interventions/search InterventionEndpoints mobileGroup ✅
GetInterventionsByIdsAsync POST /api/interventions/by-ids InterventionEndpoints mobileGroup ✅
SearchDossiersAsync GET /api/dossiers DossierEndpoints mobileGroup ✅
SearchClientsAsync GET /api/clients ClientEndpoints mobileGroup ✅
SearchSitesAsync GET /api/sites SiteEndpoints mobileGroup ✅
SearchCollaborateursAsync GET /collaborateurs CollaborateurEndpoints ✅ (sans /api)
GetSuivisTempsAsync GET /api/suivis-temps SuiviTempsEndpoints ✅
GetBootstrapAsync GET /api/mobile/bootstrap MobileBootstrapEndpoints ✅
GetSnapshotAsync GET /api/sync/snapshot SyncEndpoints ✅
SendOperationAsync POST /api/sync/operations SyncEndpoints ✅
GetStatusAsync GET /api/sync/status/{id} SyncEndpoints ✅
ActivateAsync POST /api/license/activate LicenseEndpoints ✅
ValidateAsync POST /api/license/validate LicenseEndpoints ✅
GetDevelopmentSessionAsync POST /api/dev/session DevelopmentAuthEndpoints (Dev only) ✅

Incohérence constatée : le mobile utilise un mélange de routes avec /api et sans /api (ex. /modele-mails sans préfixe vs /api/mails). Les routes « modèle » (/modele-mails, /modele-rapports, /mot-clefs, /generation/*) sont les seules sans /api. C'est cohérent avec le code API actuel mais fragile et à uniformiser.

7. Requêtes SQL concernées (côté API, base HFSQL)

Problème Requête
#5 (intervention par GUID) SELECT … FROM Intervention WHERE GUID IN (?) (formes D/N/tronquée via PhysicalGuidParser.GetStorageCandidates) — OmikaInterventionRepository.GetDetailsAsync
#5 (RDV→intervention) SELECT … FROM RDV_Planning … LEFT JOIN Intervention I ON RV.RV_IdInter = I.IN_PKID (résolution bulk RV_IdInter → Intervention.GUID) — OmikaRendezVousRepository
#6A (user) SELECT Utilisateur.UT_PKID FROM Utilisateur INNER JOIN Collaborateur ON Collaborateur.CO_PKID = Utilisateur.UT_IDCollaborateur WHERE Collaborateur.GUID IN (?) — OmikaInterventionRepository.ResolveCreationUserAsync
#6A (Batik) SELECT UT_PKID FROM Utilisateur WHERE UT_IDCollaborateur = ? — BatikInterventionRepository.ResolveCreationUserAsync
#6B/#7 (notes) SELECT MI_PKID, GUID, MI_Message, CreationDate, CreationUser FROM MessageInterne WHERE MI_IDIntervention = ? AND (MI_IDTypeMessage IS NULL OR MI_IDTypeMessage <= 10) — OmikaInterventionRepository.GetNotesAsync
#7 (documents) résolution intervention GUID → IN_PKID puis requête documents (filtre DC_IDIntervention / catégorie / état) — OmikaDocumentRepository.GetByInterventionAsync
#7 (catégories) BuildAllowedDocumentCategories → filtrage par clés T.DOC.* selon droits A.DOC.*

8. Inventaire des icônes à remplacer (extrait — inventaire complet en cours)

Fichier Ligne Actuel Fonction Proposition
Views/RendezVousDetailView.axaml 122 ⌖ Itinéraire (emoji) Itinéraire Path "navigation/itinéraire" monochrome
Views/RendezVousDetailView.axaml 134 ⚒ Ouvrir l'intervention Ouvrir intervention Path "marteau/outil" monochrome
Views/RendezVousDetailView.axaml 139 ▧ Compte rendu Compte rendu Path "document" monochrome
Views/InterventionResourcesView.axaml 37/73 titre texte redondant (titre) supprimer
Controls/LieuMap.axaml 25 ⌖ (TextBlock) épingle Path "pin" monochrome
ViewModels/NouveauEmailViewModel.cs 144 ✉ Email vierge modèle vierge (libellé, pas d'icône)
ViewModels/NouveauEmailViewModel.cs 888 📷 {Nom} pièce jointe photo Path "image"
ViewModels/RapportChampsViewModel.cs 606 ✓ / ! (Indicateur) état rubrique Path check/alert monochrome
Views/AgendaEquipeView.axaml (filtre Équipe) icône groupe colorée filtre équipe Path "group" monochrome
Views/InterventionDocumentsView.axaml 17 ← Retour retour Path "arrow-left"

Les contrôles CallButton.axaml et EmailButton.axaml utilisent déjà des Path monochromes Fill="{DynamicResource TextPrimaryBrush}" : conformes, à conserver comme référence visuelle.

9. Propositions de SVG monochrome (famille unique)

Famille retenue : pictogrammes "Line/Outline" sobres (traits fins, style Lucide/Material Outlined), un seul style (trait 1.5–2, pas de remplissage multicolore), couleur portée par Foreground="{DynamicResource PrimaryBrush}" ou Fill="{DynamicResource …}" — jamais codée en dur.

Pictogramme Usage Path (24×24 viewBox, données type Material)
Téléphone appel M6.62,10.79c1.44,2.83 … (déjà dans CallButton)
Email courriel M20,4H4C2.9,4 … (déjà dans EmailButton)
Itinéraire/GPS itinéraire navigation arrow / pin
Intervention (marteau/outil) ouvrir intervention wrench/hammer
Rapport rapport document + lignes
Note note edit/pencil
Image photo image frame
Document document file
Collaborateurs personne person outline
Équipe groupe people/group outline
Calendrier agenda calendar
Paramètres paramètres gear
Synchronisation sync refresh/circular arrows
Retour retour arrow-left
Ajouter ajouter plus
Télécharger télécharger download arrow
Épingle carte location pin

Chaque pictogramme sera matérialisé par un Path Avalonia (monochrome) ou un StreamGeometry, avec Fill/Foreground en DynamicResource pour rester compatible clair/sombre.

8bis. Couleurs codées en dur à remplacer par DynamicResource

Fichier Ligne Valeur Remplacer par
Views/RendezVousDetailView.axaml 143 #E53935 / White (bouton Annuler) DangerBrush / OnDanger
Views/RapportChampsView.axaml 108/115/122 #22c55e / #f59e0b / #ef4444 SuccessBrush / WarningBrush / DangerBrush
Views/RapportChampsView.axaml 253 #e5e7eb SurfaceTertiaryBrush
Views/RapportChampsView.axaml 138,233,258 ThemeForegroundLowBrush (clé INEXISTANTE dans DesignTokens) TextMutedBrush
10 fichiers (badges héros) — #26FFFFFF token OnPrimaryContainer/HeroChip
Controls/ImageEditorControl.axaml — #E53935…, #FFFFFF ressources centralisées
Views/*Converter.cs (NavItemTextBrushConverter, SyncStateToBrushConverter) — Brushes.Gray fallback ressource TextMutedBrush

8ter. Correctifs appliqués (lot 1 — compilation Android 0 erreur vérifiée)

# Correctif Fichiers modifiés Vérification
9/10 MapGet("/") → MapGet("") (route sans slash final) src/Omika.Api/Endpoints/ModeleDocumentEndpoints.cs Omika.Api 0 erreur
6A ResolveCreationUserAsync accepte PK numérique + GUID (Omika & Batik) OmikaInterventionRepository.cs, BatikInterventionRepository.cs Omika.Data 0 erreur
11/12 Launchers natifs Android ACTION_DIAL/ACTION_SENDTO + hook PlatformLaunchers AndroidPhoneLauncher.cs, AndroidEmailLauncher.cs (nouveaux), PlatformLaunchers.cs (nouveau), AppServices.cs, Application.cs Android 0 erreur
1 User-Agent OSM explicite OmikaMobile/1.0 (+https://www.applilogik.fr) Controls/LieuMap.axaml.cs Omika.App 0 erreur
3 Boutons d'en-tête translucides heroAction + icônes Path monochrome Views/RendezVousDetailView.axaml, Styles/Controls.axaml Omika.App 0 erreur
8 Suppression titre redondant + WrapPanel (boutons wrappables) Views/InterventionResourcesView.axaml Omika.App 0 erreur
14 Messages utilisateur propres + log technique NouveauEmailViewModel.cs, RapportChampsViewModel.cs Omika.App 0 erreur
5 Logs temporaires de traçage RDV→intervention RendezVousDetailViewModel.cs, InterventionLocalService.cs Omika.App 0 erreur

Lot 2 — Icônes monochromes + couleurs + responsive (compilation 0 erreur)

# Correctif Fichiers
2 Bug clé ThemeForegroundLowBrush (inexistante) → TextMutedBrush RapportChampsView.axaml, NouveauEmailView.axaml
2 Couleurs sémantiques en dur → Success/Warning/Danger/SurfaceTertiaryBrush RapportChampsView.axaml
2 Icônes de tuiles emoji → Path monochrome PrimaryTextColorBrush (✎ ▧ ✉ 🛡 📄) InterventionDetailView.axaml
4 Icône groupe 👥 → Path « people » monochrome AgendaView.axaml
2 Icône calendrier 📅 → Path « calendar » monochrome Controls/DateField.axaml
2 Tokens HeroChipBrush / HeroBorderBrush ajoutés DesignTokens.axaml
13 Email non tronqué (TextWrapping) fiche collaborateur + liste collaborateurs CollaborateurDetailView.axaml, CollaborateursView.axaml

Reste (lots suivants) : #26FFFFFF → HeroChipBrush sur les badges (cosmétique), autres emoji (↻ ⌖ ⚠ ⓘ 🗑 ✎ 🖼 🔒 ★/☆ ✉ + → ←), responsive FilterGrid agenda équipe, et les correctifs de données #5/#6B/#7 (résolution intervention + filtre catégories T.DOC.*).

Build Android : dotnet build src\Omika.App.Android\Omika.App.Android.csproj -c Debug -m:1 -nodeReuse:false → 0 erreur (SDK 10.0.302 via global.json). Les builds nécessitent -m:1 -nodeReuse:false (bug de nœud MSBuild parallèle dans l'environnement) et un accès élargi pour la télémétrie Avalonia (écrit hors workspace).

9. État des sous-analyses

  • ✅ Route API (#9/#10) : confirmé — slash final manquant (/modele-mails vs /modele-mails/).
  • ✅ Icônes + responsive (#2/#3/#4/#8/#13) : confirmé — inventaire ~40 emoji/Unicode, aucune couleur codée en dur dans les Path, liste des couleurs en dur à remplacer (dont clé manquante ThemeForegroundLowBrush).
  • ✅ Trace RDV→intervention (#5/#6B/#7) : chaîne d'identité vérifiée de bout en bout (correcte) ; déclencheur exact = niveau données, à confirmer par les logs temporaires prévus en correction.