Rapport d'Audit & Suivi des Sprints - Plateforme MJY Holding
Ce document présente l'analyse technique des interactions entre l'application et Supabase, vérifie les schémas de base de données, les politiques RLS (Row Level Security), et fait le point sur l'état d'avancement des sprints (passés et futurs).
🔍 1. Analyse des Problèmes de Synchronisation App ⇄ Supabase
Les dysfonctionnements ou latences de réponses parfois constatés entre le frontend et la base de données proviennent de plusieurs causes architecturales et logiques identifiées dans la structure actuelle :
A. Écritures Séquentielles Lourdes (Goulot d'Étranglement HTTP)
Dans la fonction writeToSupabase, lors de la synchronisation des tâches et de leurs notes, l'application effectue des boucles asynchrones pour exécuter un upsert unitaire sur chaque ligne :
for (const t of tasksInDb) {
await supabase.from("tasks").upsert(t);
}
- Problème : Si un projet contient 20 tâches et 15 notes, cela déclenche plus de 30 requêtes HTTP individuelles vers Supabase. Les navigateurs limitant le nombre de connexions simultanées, cela crée une file d'attente (waterfall), provoquant des ralentissements et des échecs de requêtes réseau (timeouts).
- Solution recommandée : Regrouper les écritures en envoyant des tableaux d'objets pour faire un seul
upsertgroupé (ex:await supabase.from('tasks').upsert(tasksInDb)).
B. Risque d'Écrasement Concurrent ("Last-Write-Wins" global)
L'application frontend manipule les tâches comme un document global par projet (ws_{projectId}_tasks).
- Problème : Lorsqu'un utilisateur modifie le statut d'une tâche, le cache déclenche la réécriture de l'intégralité du tableau des tâches sur Supabase. Si l'Utilisateur A modifie la Tâche 1 et que l'Utilisateur B modifie la Tâche 2 au même moment sans avoir encore reçu la mise à jour par WebSocket, la sauvegarde de l'Utilisateur B écrasera les modifications de l'Utilisateur A.
- Solution recommandée : Modifier storage.js pour n'envoyer que la ligne modifiée (mise à jour granulaire) plutôt que de réécrire toute la liste.
C. Rejet des Notifications Réseau Temps Réel
Dans la fonction subscribeToRealtimeChanges, une condition bloque le rafraîchissement si une synchronisation locale est en cours :
.on('postgres_changes', { event: '*', schema: 'public' }, async (payload) => {
if (_syncing || _flushing || _userWritingBatch) return;
// ...
})
- Problème : Si un autre utilisateur effectue une action sur la base de données pendant que le client local écrit ses données (ce qui dure plusieurs secondes à cause du goulot d'étranglement mentionné en A), l'événement Realtime est purement et simplement ignoré. Le client local ne sera jamais mis à jour avec les modifications distantes, à moins de rafraîchir manuellement ou de changer d'onglet (visibilité).
- Solution recommandée : Gérer un comparateur de timestamps ou de numéros de version de données pour ne filtrer que les événements issus de notre propre session, au lieu d'ignorer globalement toutes les mises à jour pendant nos écritures.
D. Sensibilité à la Casse (Case Sensitivity) des Noms dans les Politiques RLS
La politique RLS "Employee accède uniquement à ses tâches assignées" dans supabase_schema.sql utilise une comparaison directe :
tasks.owner = (select name from members m where m.project_id = tasks.project_id and lower(m.email) = lower(public.jwt_user_email()) limit 1)
- Problème : Si le nom du membre dans la table
membersest"Alice Dupont"et que le champownerde la tâche est saisi en"alice dupont", la comparaison SQL échouera et Supabase refusera l'accès en lecture ou en écriture à cette tâche (erreur 401/403). - Solution recommandée : Uniformiser la comparaison en passant les deux côtés en lettres minuscules (
lower(tasks.owner) = lower(m.name)) ou en utilisant des identifiants (UUID) plutôt que des chaînes de caractères de noms complets.
E. Réassignation du Client Globale
Dans supabaseClient.js, le client global supabase est écrasé lors de l'appel à setSessionToken :
export let supabase = createClient(supabaseUrl, supabaseAnonKey);
export const setSessionToken = (jwt) => { ... supabase = createClient(...) }
- Problème : Les modules qui importent
supabaseau démarrage (comme les gestionnaires d'événements Websocket lancés dans les hooks de montage de App.jsx) risquent de conserver une référence vers l'ancien client anonyme, transmettant des requêtes non autorisées. - Solution recommandée : Ne pas recréer le client, mais plutôt utiliser les fonctionnalités natives de gestion de session de Supabase ou mettre à jour dynamiquement les en-têtes sans ré-instancier l'objet client complet.
🗄️ 2. Vérification des Politiques, Tables et Modèles
Les Tables PostgreSQL (supabase_schema.sql)
Le schéma est au niveau SQL très robuste, avec clés étrangères et contraintes d'intégrité :
projects: Clé primaireid(text). Gère le nom, couleur et emoji.phases: Liée à un projet. Ordonnée via un champorder.members: Profils de l'équipe avec contrôle du format depin(Regex 4 chiffres).tasks: Contient l'état opérationnel d'une tâche. Le statut est contrôlé (todo,in_progress,pending_validation,done).task_notes: Journal collaboratif lié à une tâche.
Les Politiques RLS (Row Level Security)
Les politiques de sécurité implémentent les règles suivantes avec succès grâce aux fonctions is_project_manager et is_project_member configurées avec SECURITY DEFINER (ce qui évite la récursion infinie de lecture RLS) :
- Super Manager : Accès complet (
all) sans restrictions sur toutes les tables. - Manager : Accès restreint à ses projets et à tous les membres, phases, tâches et notes rattachés aux projets dont il est manager.
- Collaborateur (Employee) : Lecture seule sur les projets, phases et membres de son projet. Pour les tâches et notes, il peut lire/modifier uniquement s'il est désigné comme
owner(responsable) oumanager(suivi).
🏃 3. Bilan des Sprints
🏁 Sprints Terminés
- Sprint 01 : Fondations & Cadrage
- Objectif : Initialisation Git, arborescence documentaire, configuration CI/CD Netlify, structure React, audit et plan de découpage du monolithe historique.
- Statut : Complété à 100% (8 SP).
- Sprint 02 : Modernisation React & Persistance Cloud
- Objectif : Intégration de l'application sous Vite, mise en place des suites de tests unitaires (Vitest) et E2E (Playwright), création du service de cache
storage.jsavec persistance via Netlify Blobs. - Statut : Complété à 100% (10 SP).
- Objectif : Intégration de l'application sous Vite, mise en place des suites de tests unitaires (Vitest) et E2E (Playwright), création du service de cache
- Sprint 03 : Migration Supabase & RLS
- Objectif : Remplacement de Netlify Blobs par la base relationnelle Supabase, configuration du schéma SQL, synchronisation temps réel par WebSockets, sécurisation RLS et authentification sécurisée par PIN et JWT personnalisé.
- Statut : Complété à 100% (11 SP).
📋 Sprints Suivants (À planifier et exécuter)
- Sprint 04 : Saisie Assistée & Intelligence Artificielle (Priorité Haute)
- US4.1 (Création Vocale) : Intégration et validation du module de dictée vocale (
VocalCreator.jsx) avec parsing temporel natif de l'échéance. - US4.2 (Import IA) : Branchement de la modale d'import sur la Netlify Function
api/importexploitant Claude d'Anthropic pour analyser le texte brut et en extraire tâches, responsables et phases (avec le fallback regex en cas d'erreur API).
- US4.1 (Création Vocale) : Intégration et validation du module de dictée vocale (
- Sprint 05 : Expérience Utilisateur & Métriques (Priorité Moyenne)
- US5.1 (Sauvegarde/Restauration locale) : Finalisation des fonctions d'exportation/importation JSON locales pour conserver des sauvegardes hors ligne.
- US5.2 (Tableau de bord de Statistiques) : Rendu interactif des filtres KPIs globaux (retard, statut, avancement par phase) pour les managers.
- Sprint 6 : Fiabilité & Optimisations Réseau (Recommandé)
- Optimisation des requêtes Supabase (remplacement des boucles d'upserts par des requêtes de masse).
- Résolution des cas de blocage d'événements Realtime.
- Refactoring de la détection de changement de session pour éviter la recréation du client global.
