# Administration, Utilitaires & Connexion _48 constats vérifiés — 4 haute, 25 moyenne, 19 basse._ ## Pages analysées ### `/utilities (+ routes de compatibilité /backup, /nocodb-config, /database-debug, /weather-settings ; aussi exposée sur mobile)` **Rôle :** Conteneur d'administration (admin uniquement) qui regroupe en 6 onglets : sauvegardes, configuration NocoDB, debug base de données, météo, webhook BAP, exécution SQL. **Tâches principales de l'utilisateur :** - Choisir un outil d'administration via les onglets - Accéder directement à un onglet via une ancienne URL (/backup, /nocodb-config...) **Appels API :** | Endpoint | Déclencheur | Handler serveur | Notes | |---|---|---|---| | GET /api/user | au montage (useAuthUnified : état local par instance, fetch cache:'no-cache') | server/localAuth.ts:204 → storage.getUserWithGroups (server/storage.ts:371) ; + passport.deserializeUser localAuth.ts:141 → getUserWithGroups | Refait aussi par RouterProduction, Layout, Sidebar (useAuthSimple) et chaque onglet monté : ≥5 appels par affichage, 4 requêtes SQL chacun. Tant qu'il n'a pas répondu, user=null → carte « Accès refusé » affichée. | **Lisibilité / simplicité :** Six onglets plats (grid-cols-6 fixe) mélangent outils quotidiens (sauvegardes, météo) et outils dangereux pour développeur (SQL brut, scan de schéma), avec des libellés jargon (NocoDB, BAP, Debug). Inutilisable sur téléphone (6 colonnes forcées). Double en-tête de page (Utilitaires + titre de chaque onglet), largeurs/titres/états de chargement différents d'un onglet à l'autre, onglet actif non reflété dans l'URL. Flash « Accès refusé » à chaque ouverture à cause du hook d'auth non partagé. Tous les onglets sont importés statiquement dans le bundle principal téléchargé par tous les employés. ### `/utilities › onglet Sauvegardes` **Rôle :** Voir les sauvegardes de la base, en créer une manuellement, les télécharger ou les supprimer, activer/désactiver la sauvegarde automatique nocturne. **Tâches principales de l'utilisateur :** - Vérifier que la dernière sauvegarde est récente et réussie - Lancer une sauvegarde manuelle - Télécharger une sauvegarde - Supprimer une sauvegarde - Activer/désactiver la sauvegarde automatique **Appels API :** | Endpoint | Déclencheur | Handler serveur | Notes | |---|---|---|---| | GET /api/backups | au montage (enabled après le /api/user propre au composant) + polling toutes les 30 s | server/routes.ts:5173 → backupService.getBackupList (server/backupService.ts:159, ORDER BY created_at DESC LIMIT 10) | Requête légère mais précédée de getUserWithGroups (2 requêtes) en plus de la désérialisation ; polling inconditionnel. | | GET /api/utilities | au montage | server/routes.ts:333 → storage.getUser + storage.getUtilities (server/storage.ts:3015) | Lu pour l'interrupteur et la carte « Statut Système ». | | POST /api/utilities | clic sur l'interrupteur sauvegarde automatique | server/routes.ts:354 → getUtilities puis updateUtilities/createUtilities (server/storage.ts:3020-3031) | La réponse (config à jour) n'est pas réutilisée : invalidation + refetch ; toast calculé sur l'ancienne valeur. | | POST /api/backups | clic « Sauvegarde Manuelle » | server/routes.ts:5188 → backupService.createBackup (server/backupService.ts:98) | Requête HTTP ouverte pendant tout le pg_dump ; lecture synchrone du dump complet (readFileSync) pour compter les tables ; un échec laisse le statut « creating ». | | GET /api/backups/:filename/download | clic « Télécharger » (lien créé dynamiquement) | server/routes.ts:5203 → backupService.downloadBackup (server/backupService.ts:202) | Nom de fichier non validé (path.join avec le paramètre). | | DELETE /api/backups/:filename | clic sur l'icône corbeille (sans confirmation) | server/routes.ts:5225 → backupService.deleteBackup (server/backupService.ts:173) | Suppression physique immédiate du fichier + enregistrement. | | GET /api/user | au montage (useAuthUnified) | server/localAuth.ts:204 | Bloque les deux requêtes ci-dessus (enabled: canManageBackups) → cascade. | **Lisibilité / simplicité :** Information répétée trois fois (sous-titre, carte interrupteur, carte « Statut Système »), nom de fichier technique en titre de chaque ligne, unités anglaises (Bytes), description serveur franglaise (« Manuel backup du… »). Suppression en un clic sans confirmation via un bouton icône sans libellé. Ligne de liste non responsive (pas de flex-wrap ni troncature) coupée par l'overflow-x-hidden du Layout. Bouton principal orange alors que les autres onglets utilisent le bleu. Sauvegardes ratées affichées « En cours » à vie et comptées comme « Dernière sauvegarde ». Pas de restauration proposée (à décider côté produit). ### `/utilities › onglet Configuration NocoDB` **Rôle :** Gérer les connexions à la base NocoDB externe utilisée pour vérifier les factures lors du rapprochement BL/factures. **Tâches principales de l'utilisateur :** - Créer une configuration NocoDB - Modifier une configuration (URL, jeton, projet, actif) - Supprimer une configuration - Afficher le jeton API **Appels API :** | Endpoint | Déclencheur | Handler serveur | Notes | |---|---|---|---| | GET /api/nocodb-config | au montage (enabled: user?.role === 'admin', donc après le /api/user propre au composant) | server/routes.ts:5090 → storage.getUserWithGroups + storage.getNocodbConfigs (server/storage.ts:1655) | Renvoie le jeton API déchiffré au navigateur. Même queryKey réutilisée par Groups.tsx:85. | | POST /api/nocodb-config | soumission de la modale de création | server/routes.ts:5105 → storage.createNocodbConfig (server/storage.ts:1682) | Aucune contrainte « une seule config active ». | | PUT /api/nocodb-config/:id | soumission de la modale d'édition | server/routes.ts:5124 → storage.updateNocodbConfig (server/storage.ts:1688) | Le jeton en clair est renvoyé dans le formulaire puis ré-envoyé. | | DELETE /api/nocodb-config/:id | confirmation dans la modale de suppression | server/routes.ts:5141 → storage.deleteNocodbConfig (server/storage.ts:1701) | Pas de vérification des magasins (groups.nocodb_config_id) qui l'utilisent. | | GET /api/user | au montage (useAuthUnified) | server/localAuth.ts:204 | Flash « Accès restreint » tant qu'il n'a pas répondu. | **Lisibilité / simplicité :** Libellés anglais/jargon (« Personal API Token », placeholders xc-token / your-nocodb-instance), pas d'aide ni de bouton « Tester la connexion » (présent sur Météo et BAP). Jeton visible en clair avec un bouton œil. Plusieurs configurations peuvent être « Actif » sans indiquer laquelle sert réellement. Boutons Modifier/Supprimer icône seule, sans libellé ni style destructif ; suppression sans avertissement sur les magasins liés. Spinner géant (128 px) au chargement, h1 text-3xl dans un conteneur différent des autres onglets. Formulaire de création non réinitialisé, formulaires création/édition dupliqués, console.log de debug à chaque rendu. ### `/utilities › onglet Debug Base de Données` **Rôle :** Outil développeur : scanner le schéma de la base de production, l'écrire dans les logs serveur et télécharger un rapport texte. **Tâches principales de l'utilisateur :** - Lancer le scan du schéma - Télécharger le rapport **Appels API :** | Endpoint | Déclencheur | Handler serveur | Notes | |---|---|---|---| | GET /api/debug/log-schema | clic « Démarrer le Scan du Schéma » (fetch manuel) | server/routes.ts:4776 → pool.query en boucle (colonnes + COUNT(*) par table, l.4813-4846) | N+1 séquentiel, COUNT(*) en scan complet, centaines de lignes console ; refuse de fonctionner hors production. | | GET /api/debug/download-schema | clic « Télécharger » (window.open dans un nouvel onglet) | server/routes.ts:4904 → même balayage complet (l.4971, 4984) | Refait intégralement le travail du scan précédent. | | GET /api/user | au montage (useAuthUnified) | server/localAuth.ts:204 | Flash « Accès refusé » tant qu'il n'a pas répondu. | **Lisibilité / simplicité :** Page entièrement orientée développeur (« logs de votre application Node.js », « Relations FK », marqueur de log à chercher), émojis dans les textes, fonctionne uniquement en production, parcours en deux temps (scan puis téléchargement qui relance le scan). N'a pas sa place au même niveau que les Sauvegardes pour un admin non technicien. ### `/utilities › onglet Météo` **Rôle :** Configurer la clé API Visual Crossing et la ville du widget météo affiché dans l'en-tête de toutes les pages. **Tâches principales de l'utilisateur :** - Saisir la clé API et la ville - Détecter la ville par géolocalisation - Tester la connexion - Activer/désactiver le widget **Appels API :** | Endpoint | Déclencheur | Handler serveur | Notes | |---|---|---|---| | GET /api/weather/settings | au montage | server/routes.ts:5596 → storage.getWeatherSettings (server/storage.ts:2891) | Renvoie la clé API en clair (mise en defaultValue d'un champ password). | | PUT /api/weather/settings/:id (POST si aucun réglage) | soumission du formulaire (fetch manuel) | server/routes.ts:5630 / 5611 → storage.updateWeatherSettings (server/storage.ts:2901) / createWeatherSettings (2896) | Ne vide pas le cache météo même si la ville change ; le client n'invalide pas /api/weather/current. | | POST /api/weather/test-connection | clic « Tester la connexion » (fetch manuel) | server/routes.ts:5650 → weatherService.testApiConnection (server/weatherService.ts:220) | Messages serveur en français ; erreur client en anglais « Test connection failed ». | | POST /api/weather/geolocation | clic sur le bouton localisation | server/routes.ts:6094 → weatherService.getCityFromCoordinates + storage.updateWeatherSettings + storage.clearWeatherCache | Aucun contrôle de rôle admin ; le client met à jour l'input par manipulation DOM. | | GET /api/weather/current | WeatherWidget du Layout : montage + toutes les 30 min (non rafraîchi par cette page) | server/routes.ts:5671 → getWeatherSettings + getWeatherData×2 séquentiels (server/storage.ts:2910) + getNearestWeatherData (2930) + API externe si absent | Cache BD indexé par date seulement (pas par ville) ; pas de cache mémoire ; console.log à chaque appel. | **Lisibilité / simplicité :** Formulaire simple et plutôt clair, mais : après changement manuel de ville le widget continue d'afficher l'ancienne ville jusqu'au lendemain (cache non vidé), messages d'erreur en anglais, champs non contrôlés manipulés via document.getElementById / querySelector('form'), largeur max-w-2xl incohérente avec les autres onglets, texte « À propos » inexact (le widget est à gauche, le sélecteur de magasin à droite). ### `/utilities › onglet Configuration BAP` **Rôle :** Configurer l'URL du webhook n8n qui reçoit les PDF « BAP » envoyés depuis le bouton BAP de la barre latérale. **Tâches principales de l'utilisateur :** - Saisir/modifier l'URL du webhook - Tester le webhook - Activer/désactiver l'envoi - Sauvegarder **Appels API :** | Endpoint | Déclencheur | Handler serveur | Notes | |---|---|---|---| | GET /api/webhook-bap-config | au montage | server/routes.ts:186 → storage.getUser + storage.getWebhookBapConfig (server/storage.ts:2992) | Si la table manque, renvoie une config factice avec l'URL de production codée en dur. | | POST /api/webhook-bap-config | clic « Sauvegarder » | server/routes.ts:222 → getWebhookBapConfig puis updateWebhookBapConfig/createWebhookBapConfig (server/storage.ts:2997-3012) | Erreur de validation Zod renvoyée en 500. | | POST /api/webhook-bap-config/test | clic « Tester le Webhook » (fetch manuel) | server/routes.ts:264 → fetch POST vers l'URL saisie (timeout 10 s) | Teste l'URL saisie, pas celle enregistrée. | **Lisibilité / simplicité :** Jargon (webhook, n8n, BAP non expliqué) et prénoms codés en dur (« Laurie et Jeremy ») ; interrupteur « Configuration active » sans explication de l'effet. Bouton Sauvegarder isolé en bas après la carte de test, statut « Connexion réussie » qui reste affiché après modification de l'URL, pas d'indication de modifications non enregistrées. Bouton copier icône seule. Pas de titre de section cohérent avec les autres onglets. ### `/utilities › onglet Exécution SQL` **Rôle :** Exécuter du SQL brut sur la base de production (collage ou fichier .sql). **Tâches principales de l'utilisateur :** - Coller ou charger un script SQL - Exécuter - Lire les résultats/logs **Appels API :** | Endpoint | Déclencheur | Handler serveur | Notes | |---|---|---|---| | POST /api/admin/execute-sql | clic « Exécuter SQL » (fetch manuel dans useMutation) | server/routes.ts:6151 → storage.getUser + db.execute(sql.raw(sqlQuery)) (l.6178) | Aucune transaction ni mode lecture seule ; renvoie toutes les lignes (results: result.rows) + un échantillon dupliqué dans logs. | **Lisibilité / simplicité :** Outil extrêmement dangereux exposé à un clic, sans confirmation ni sauvegarde préalable. Résultats non bornés rendus comme un bloc JSON géant sans retour à la ligne (whitespace non préservé) dans une zone sans hauteur max. Bouton obsolète « SQL Webhook BAP » qui écrase l'éditeur et contient une URL de production. Message « Début de l'exécution » effacé aussitôt. ### `/auth (et toute URL quand l'utilisateur n'est pas connecté)` **Rôle :** Écran de connexion (identifiant + mot de passe) avec visuel de présentation sur grand écran. **Tâches principales de l'utilisateur :** - Se connecter - Voir les identifiants par défaut lors de la première installation **Appels API :** | Endpoint | Déclencheur | Handler serveur | Notes | |---|---|---|---| | GET /api/user | au montage, deux fois (RouterProduction puis AuthPage, chacun via useAuthUnified) | server/localAuth.ts:204 → storage.getUserWithGroups (server/storage.ts:371) | Renvoie 401 pour un visiteur ; provoque deux spinners plein écran successifs. | | GET /api/default-credentials-check | au montage | server/localAuth.ts:234 → storage.getUserByUsername('admin') (server/storage.ts:351) | Endpoint public ; carte admin/admin affichée par défaut avant la réponse. | | POST /api/login | soumission du formulaire (fetch manuel en « production », useMutation sinon) | server/localAuth.ts:153 → LocalStrategy (getUserByUsername + scrypt) puis await backupService.checkAndPerformDailyBackup (server/backupService.ts:240) | Peut attendre un pg_dump complet avant de répondre ; puis côté client attente artificielle de 500 ms + rechargement complet. | **Lisibilité / simplicité :** Formulaire simple et lisible, mais : deux spinners avant l'affichage, chargement initial lourd (~1,7 Mo JS non compressé + script tiers replit.com bloquant, lang=en déclenchant la traduction Chrome, zoom mobile désactivé), champs sans autoComplete/autoCapitalize (majuscule automatique sur mobile → échec de connexion, comparaison sensible à la casse), identifiants admin/admin affichés par défaut, messages d'erreur trompeurs en cas de blocage (rate-limit) ou de CSRF, anglicisme « across tous vos magasins », 500 ms d'attente + rechargement complet après connexion, sessions en mémoire perdues à chaque redémarrage. ### `(aucune — page non routée) Landing` **Rôle :** Ancienne page d'accueil marketing (héritage Replit Auth), jamais importée. **Tâches principales de l'utilisateur :** - Aucune (code mort) **Lisibilité / simplicité :** Code mort : non importée par le routeur, ses boutons redirigent vers GET /api/login qui n'existe pas (seul POST existe). Branding « La Foir'Fouille » codé en dur, classes Tailwind personnalisées. À supprimer. ### `* (404, desktop dans Layout et mobile dans MobileApp)` **Rôle :** Page affichée pour une URL inconnue quand l'utilisateur est connecté. **Tâches principales de l'utilisateur :** - Comprendre que la page n'existe pas et revenir à l'application **Lisibilité / simplicité :** Message de développeur en anglais (« 404 Page Not Found », « Did you forget to add the page to the router? »), aucun bouton de retour, min-h-screen à l'intérieur du Layout (double hauteur d'écran). ## Constats | ID | Sév. | Catégorie | Effort | Titre | Fichier | |---|---|---|---|---|---| | [ADMIN-01](#admin-01) | haute | bug | M | Hook d'auth non partagé : flash « Accès refusé » et cascade de /api/user à chaque onglet | `client/src/hooks/useAuthUnified.ts:23` | | [AUTH-01](#auth-01) | haute | perf-bundle | S | Script tiers replit.com bloquant en production, lang="en" et zoom désactivé | `client/index.html:19` | | [BACKUP-01](#backup-01) | haute | ux-simplicite | S | Suppression d'une sauvegarde en un clic, sans confirmation | `client/src/pages/BackupManager.tsx:142` | | [SQL-01](#sql-01) | haute | ux-simplicite | M | Exécution de SQL brut en production sans confirmation ni filet de sécurité | `client/src/pages/SQLExecutor.tsx:97` | | [ADMIN-02](#admin-02) | moyenne | perf-api | S | /api/user exécute 4 requêtes SQL alors que req.user est déjà chargé | `server/localAuth.ts:210` | | [AUTH-02](#auth-02) | moyenne | perf-serveur | S | Fichiers statiques servis sans compression ni cache long | `server/index.production.ts:143` | | [AUTH-03](#auth-03) | moyenne | perf-client | M | Double vérification d'auth, double spinner, puis 500 ms d'attente et rechargement complet | `client/src/pages/AuthPage.tsx:69` | | [AUTH-04](#auth-04) | moyenne | bug | S | Identifiants admin/admin affichés par défaut avant (et sans) vérification | `client/src/pages/AuthPage.tsx:25` | | [AUTH-05](#auth-05) | moyenne | accessibilite | S | Champs de connexion sans autoComplete/autoCapitalize (échecs sur mobile) | `client/src/pages/AuthPage.tsx:224` | | [AUTH-06](#auth-06) | moyenne | bug | S | Sessions stockées en mémoire en production (fichier d'auth PostgreSQL jamais utilisé) | `server/routes.ts:119` | | [AUTH-07](#auth-07) | moyenne | bug | S | CSRF activé en production (npm start) mais jamais envoyé par le client | `server/index.ts:40` | | [BACKUP-02](#backup-02) | moyenne | bug | S | Une sauvegarde ratée reste « En cours » indéfiniment | `server/backupService.ts:153` | | [BACKUP-03](#backup-03) | moyenne | perf-serveur | S | Le dump complet est relu en mémoire de façon synchrone pour compter les tables | `server/backupService.ts:134` | | [BACKUP-04](#backup-04) | moyenne | perf-api | S | La connexion attend la vérification (voire l'exécution) de la sauvegarde quotidienne | `server/localAuth.ts:165` | | [BACKUP-06](#backup-06) | moyenne | lisibilite | S | Informations répétées trois fois et libellés techniques | `client/src/pages/BackupManager.tsx:205` | | [BACKUP-07](#backup-07) | moyenne | accessibilite | S | Ligne de sauvegarde non responsive et bouton supprimer sans nom accessible | `client/src/pages/BackupManager.tsx:366` | | [BACKUP-09](#backup-09) | moyenne | bug | S | Téléchargement de sauvegarde vulnérable au path traversal | `server/backupService.ts:203` | | [BAP-01](#bap-01) | moyenne | lisibilite | S | Jargon technique et prénoms codés en dur | `client/src/pages/WebhookBAPConfig.tsx:166` | | [DEBUG-01](#debug-01) | moyenne | perf-serveur | S | Scan de schéma en N+1 avec COUNT(*) complet, refait pour le téléchargement | `server/routes.ts:4831` | | [DEBUG-02](#debug-02) | moyenne | ux-simplicite | S | Outil développeur incompréhensible pour un admin non technicien | `client/src/pages/DatabaseDebug.tsx:73` | | [ERR-01](#err-01) | moyenne | lisibilite | M | Messages d'erreur bruts (code HTTP + JSON, en anglais) affichés dans les toasts | `client/src/lib/queryClient.ts:6` | | [NF-01](#nf-01) | moyenne | lisibilite | S | Page 404 en anglais avec message de développeur et sans retour | `client/src/pages/not-found.tsx:11` | | [NOCO-01](#noco-01) | moyenne | bug | S | Jeton API NocoDB déchiffré envoyé au navigateur et affiché en clair | `server/storage.ts:1657` | | [NOCO-03](#noco-03) | moyenne | ux-simplicite | M | Plusieurs configurations « Actif » possibles, une seule utilisée au hasard | `server/storage.ts:1665` | | [SQL-02](#sql-02) | moyenne | perf-api | S | Résultats SQL non bornés, dupliqués et rendus en un bloc illisible | `server/routes.ts:6197` | | [UTIL-01](#util-01) | moyenne | ux-simplicite | M | Six onglets plats mêlant outils quotidiens et outils dangereux, inutilisables sur mobile | `client/src/pages/Utilities.tsx:81` | | [UTIL-03](#util-03) | moyenne | perf-bundle | M | Pages admin importées statiquement dans le bundle principal de tous les utilisateurs | `client/src/components/RouterProduction.tsx:17` | | [WEATHER-01](#weather-01) | moyenne | bug | S | Changer la ville ne met pas à jour le widget météo (cache non vidé, requête non invalidée) | `server/routes.ts:5639` | | [WEATHER-02](#weather-02) | moyenne | bug | S | Géolocalisation météo modifiable par n'importe quel utilisateur connecté | `server/routes.ts:6096` | | [ADMIN-03](#admin-03) | basse | perf-serveur | S | Chaque handler admin relit l'utilisateur en BD pour vérifier le rôle | `server/routes.ts:5175` | | [AUTH-08](#auth-08) | basse | bug | S | Limite de 5 connexions/15 min par IP et message d'erreur trompeur | `client/src/pages/AuthPage.tsx:76` | | [AUTH-09](#auth-09) | basse | dette-code | S | Deux implémentations de connexion, hook appelé hors rendu, double soumission | `client/src/pages/AuthPage.tsx:171` | | [BACKUP-05](#backup-05) | basse | perf-client | S | Polling 30 s inconditionnel de la liste des sauvegardes | `client/src/pages/BackupManager.tsx:61` | | [BACKUP-08](#backup-08) | basse | bug | S | Toast de l'interrupteur calculé sur l'ancienne valeur et bascule différée | `client/src/pages/BackupManager.tsx:78` | | [BACKUP-10](#backup-10) | basse | coherence-design | S | Double en-tête de page et bouton principal orange | `client/src/pages/BackupManager.tsx:196` | | [BAP-02](#bap-02) | basse | ux-simplicite | S | Bouton Sauvegarder isolé et statut de test périmé | `client/src/pages/WebhookBAPConfig.tsx:251` | | [BAP-03](#bap-03) | basse | bug | S | Validation renvoyée en 500 et configuration factice avec URL de production | `server/routes.ts:236` | | [LAND-01](#land-01) | basse | dette-code | S | Page Landing morte qui pointe vers une route inexistante | `client/src/pages/Landing.tsx:7` | | [NOCO-02](#noco-02) | basse | dette-code | S | Log de debug à chaque rendu et code défensif opaque | `client/src/pages/NocoDBConfig.tsx:66` | | [NOCO-04](#noco-04) | basse | accessibilite | S | Boutons Modifier/Supprimer icône seule et suppression sans avertissement d'impact | `client/src/pages/NocoDBConfig.tsx:300` | | [NOCO-05](#noco-05) | basse | bug | S | Formulaire de création non réinitialisé après succès | `client/src/pages/NocoDBConfig.tsx:84` | | [NOCO-06](#noco-06) | basse | dette-code | S | Formulaires de création et d'édition dupliqués | `client/src/pages/NocoDBConfig.tsx:371` | | [NOCO-07](#noco-07) | basse | lisibilite | M | Libellés anglais/jargon et aucun test de connexion | `client/src/pages/NocoDBConfig.tsx:406` | | [SQL-03](#sql-03) | basse | dette-code | S | Bouton « SQL Webhook BAP » obsolète, état dupliqué et log de démarrage effacé | `client/src/pages/SQLExecutor.tsx:100` | | [UTIL-02](#util-02) | basse | ux-simplicite | S | Onglet actif non reflété dans l'URL | `client/src/pages/Utilities.tsx:28` | | [UTIL-04](#util-04) | basse | coherence-design | M | Conteneurs, titres et états de chargement différents dans chaque onglet | `client/src/pages/NocoDBConfig.tsx:231` | | [WEATHER-03](#weather-03) | basse | dette-code | S | Formulaire non contrôlé manipulé via le DOM, fetch manuels et erreurs en anglais | `client/src/pages/WeatherSettings.tsx:318` | | [WEATHER-04](#weather-04) | basse | perf-serveur | S | /api/weather/current recalculé à chaque appel sans cache mémoire | `server/routes.ts:5683` | ### ADMIN-01 **Hook d'auth non partagé : flash « Accès refusé » et cascade de /api/user à chaque onglet** — bug, sévérité haute, effort M - **Fichier :** `client/src/hooks/useAuthUnified.ts:23` - **Constat :** useAuthUnified.ts:23-24 `const [productionUser, setProductionUser] = useState(null); const [productionLoading, setProductionLoading] = useState(true);` + useEffect l.148-167 `fetch('/api/user', { credentials: 'include', cache: 'no-cache' ...})` : état local par instance. Utilities.tsx:26 `const { user } = useAuthUnified();` puis l.45 `if (!canManageBackups && !canManageNocoDB && ...) return (... Accès refusé ...)` sans tenir compte d'isLoading ; même schéma BackupManager.tsx:48/54/177, NocoDBConfig.tsx:45/211 (« Accès restreint »), DatabaseDebug.tsx:8/41. Les requêtes attendent ce second appel : BackupManager.tsx:60 `enabled: canManageBackups`, NocoDBConfig.tsx:58 `enabled: user?.role === 'admin'`. - **Impact :** À l'ouverture de /utilities, l'admin voit une carte « Accès refusé » (Utilities), puis une seconde (onglet), puis un spinner : 3 allers-retours séquentiels (/api/user → /api/user → /api/backups) avant la liste. /api/user est appelé ≥5 fois par affichage (RouterProduction, Layout, Sidebar via useAuthSimple, Utilities, onglet) et de nouveau à chaque changement d'onglet. - **Recommandation :** Source unique : `useQuery({ queryKey: ['/api/user'], queryFn: getQueryFn({ on401: 'returnNull' }) sans redirection, staleTime: 5*60_000, retry: false })`. Attention : le queryFn par défaut (queryClient.ts:56-61) fait `window.location.href = '/auth'` sur un 401, ce qui changerait le comportement de RouterProduction (il affiche aujourd'hui AuthPage directement sur '/'). Il faut donc un queryFn dédié qui renvoie null sans rediriger. Garder la même forme de retour { user, isLoading, isAuthenticated, refreshAuth, forceAuthRefresh } pour les ~15 appelants, et faire de useAuthSimple un alias. Dans Utilities, afficher un skeleton tant que isLoading. Les contrôles de rôle dans les onglets peuvent ensuite être retirés (Utilities est le seul point d'entrée de ces pages). ### AUTH-01 **Script tiers replit.com bloquant en production, lang="en" et zoom désactivé** — perf-bundle, sévérité haute, effort S — vérification : partiellement confirmé - **Fichier :** `client/index.html:19` - **Constat :** client/index.html:18-19 `` (classique, sans async/defer) présent aussi dans dist/public/index.html ; l.2 `` ; l.5 `content="width=device-width, initial-scale=1.0, maximum-scale=1"`. - **Impact :** Le module de l'application (différé) ne s'exécute qu'après le téléchargement de ce script tiers : si replit.com est lent ou filtré par le réseau du magasin, l'écran reste blanc jusqu'au timeout. Chrome propose de « traduire depuis l'anglais » une interface française ; zoom bloqué sur mobile (malvoyants). - **Recommandation :** Partie mécanique : supprimer la balise replit (l.18-19) et passer `lang="fr"`. Le retrait de `maximum-scale=1` est souhaitable pour l'accessibilité, mais il faut d'abord vérifier que les champs natifs ont une police d'au moins 16px sur mobile. À traiter séparément. ### BACKUP-01 **Suppression d'une sauvegarde en un clic, sans confirmation** — ux-simplicite, sévérité haute, effort S - **Fichier :** `client/src/pages/BackupManager.tsx:142` - **Constat :** l.142-145 `const handleDelete = (filename: string) => { setDeletingBackup(filename); deleteBackupMutation.mutate(filename); };` appelé directement par l.388-391 `