← Tous les manuels ·
PortailMANUAL — moa-tasks
Manuel d'instructions de cette app. À LIRE avant toute intervention, et à METTRE À JOUR après chaque modification (ajouter une ligne au CHANGELOG en bas).
Dernière génération/MAJ : 2026-08-02 · Statut : brouillon auto (à relire)
1. Identité
- Rôle (à quoi sert l'app, pour qui) : TODO — décrire à quoi sert l'app et pour qui- URL publique : https://yacare.panelbay.com- Entité : MOA
2. Exécution / infra
- Dossier : /root/workspace/apps/moa-tasks- Stack : Node.js, Python, HTML/statique- Service systemd : moa-tasks.service (systemctl restart moa-tasks.service)- Port : 8300 (voir backend/.env PORT=8300)- Reverse proxy (nginx) : yacare.panelbay.com → 127.0.0.1:8300 (HTTPS Let's Encrypt)- Redémarrer : systemctl restart moa-tasks.service
3. Structure & fichiers clés
- backend/server.js- frontend/index.html- backend/package.json- backend/db/schema.sql
4. Données
- Base / stockage : - data/moa-tasks.db- Modèle / tables : TODO — tables / structure des données
5. API / endpoints
- DELETE /:id- DELETE /:id/apikey/:kid- DELETE /attachments/:aid- DELETE /comments/:cid- DELETE /subtasks/:sid- DELETE /tasks/:id- DELETE /tasks/:id/collaborators/:uid- GET /- GET /:id/apikeys- GET /api/health- GET /api/version- GET /attachments/:aid/download- GET /board- GET /delegated- GET /history- GET /me- GET /my-turns- GET /public-key- GET /search- GET /tasks/:id- GET /tasks/:id/activity- PATCH /:id- PATCH /subtasks/:sid- PATCH /tasks/:id- POST /- POST /:id/apikey- POST /:id/avatar- POST /login- POST /me/password- POST /subscribe- POST /tasks- POST /tasks/:id/attachments- POST /tasks/:id/collaborators- POST /tasks/:id/comments- POST /tasks/:id/delegate- POST /tasks/:id/handoff- POST /tasks/:id/subtasks- POST /test- POST /unsubscribe
6. Intégrations & secrets
- Intégrations (n8n, Google Sheets, Airtable, webhooks, autres apps) : TODO — n8n, Google Sheets, Airtable, webhooks, autres apps- Secrets / clés (indiquer OÙ elles sont, JAMAIS leur valeur) : toutes dans backend/.env — JWT_SECRET (signature JWT, 64 hex forts ; le code retombe sur un repli dev moa-tasks-dev-secret-change-me si absent, à NE PAS laisser en prod), ADMIN_PASSWORD (login admin de secours au seed ; le hash réel vit en base), VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY (web-push). Manifeste de rotation : /root/workspace/Audit-apps/_secret_rotation_moa-tasks.tsv.- Dépendances de sécurité : helmet (headers + CSP), express-rate-limit (anti-brute-force login).
7. Pièges connus / à savoir
- Autorisation par tâche = isStakeholder() (backend/lib/serialize.js). Toute route qui lit ou modifie une tâche, ses sous-tâches, commentaires, collaborateurs ou pièces jointes DOIT passer ce garde en tête (admin, ou créateur / assigné / collaborateur). Ne pas retirer ces gardes : sans eux, n'importe lequel des 22 users peut lire / éditer la tâche d'un autre en connaissant son id (IDOR). Modèle de référence : la route handoff.- CSP (helmet) tunée pour une PWA same-origin : tous les scripts sont servis depuis l'origine (aucun <script> inline), styles inline autorisés car l'UI pose element.style. Si on ajoute un script/CDN externe, il faut élargir la CSP dans backend/server.js sinon il sera bloqué.- trust proxy = 1 : requis derrière nginx pour que le rate-limiter voie l'IP réelle. Ne pas passer à true (express-rate-limit refuse une confiance trop permissive).- Rate-limit login : 20 tentatives / IP / 15 min (échecs seulement). Un test massif de login en localhost (sans en-tête X-Forwarded-For) peut bloquer 127.0.0.1 15 min ; les vrais users passent par nginx (IP par utilisateur).- Mot de passe temporaire + changement forcé. Colonne users.must_change_password. Tout nouveau compte (POST /api/users) ou reset admin (PATCH /api/users/:id avec password) met le flag à 1 -> popup non fermable au prochain login qui impose un mot de passe personnel. Pour créer un compte SANS forcer le changement : passer must_change_password:false. Le seul endroit qui efface le flag est POST /api/users/me/password (qui, si le flag est à 1, autorise le changement sans current_password). Ne pas retirer ce garde : c'est ce qui rend le mot de passe temporaire à usage unique. Pour réinitialiser un compte à la main (comme Younes le 2026-08-05) : hash bcrypt via backend/node_modules + UPDATE users SET password_hash=?, must_change_password=1.- Brouillon de nouvelle tâche = 100% local (localStorage), jamais serveur. À la création d'une tâche, le contenu du formulaire est auto-sauvegardé dans localStorage (clé yacare_taskdraft_<userId>_<colId>) à chaque frappe et restauré à la réouverture, pour ne rien perdre si la fenêtre se ferme. Un brouillon n'est pas une tâche : rien n'est envoyé au serveur tant que « Créer » n'est pas cliqué (donc aucune tâche « fantôme » côté board/DB). Le brouillon est effacé à la création réussie, via « Repartir de zéro », ou quand il redevient vide. Conséquence : un brouillon est par navigateur / appareil (pas synchronisé entre desktop et mobile). Code : openTaskEditor dans frontend/js/app.js (helpers loadDraft/saveDraft/clearDraft/draftIsEmpty/collectDraft/persistDraft).
8. CHANGELOG
- 2026-08-27 — Vue Agenda (calendrier jour/semaine) + drag-and-drop (Phase 1 du chantier synchro Google Agenda). Nouvel onglet Agenda entre « Tableau » et « Ce que j'ai délégué ». Objectif du chantier global (demande Matt) : synchro Google Agenda deux sens façon TickTick. Décisions cadrées : synchro par utilisateur (chacun connecte SON Google, = Phase 2, pas encore faite) ; sens Google -> Yacaré = affichage seul (les événements Google s'afficheront en fond, ne deviendront PAS des tâches) ; sens Yacaré -> Google = push des tâches planifiées (Phase 2). Phase 1 livrée ici = tout le socle calendaire interne, sans OAuth. Données : 2 nouvelles colonnes tasks.start_at / tasks.end_at (migration idempotente addColumn dans backend/db/index.js, + index idx_tasks_start_at), ISO 8601 UTC avec heure (distinctes des start_date/deadline au jour près qui pilotent le board). Une tâche apparaît dans l'agenda dès que start_at est renseigné. Ce sont ces champs qui seront poussés vers Google en Phase 2. Backend : serializeTask (backend/lib/serialize.js) expose start_at/end_at ; POST /api/tasks et PATCH /api/tasks/:id (backend/routes/tasks.js) les acceptent (null = déplanifier). Aucune nouvelle route : l'agenda dérive ses données de /api/board déjà chargé côté client. Frontend (frontend/js/app.js, frontend/styles.css, frontend/index.html) : module renderAgenda + helpers ag*. Vue jour/semaine (lun->dim), grille horaire 0-24h (46px/h, snap 15 min), colonne du jour surlignée + ligne « maintenant » rouge, gouttière d'heures, layout multi-lignes pour événements qui se chevauchent. Interactions pointer events (pas de lib externe, compatible CSP same-origin) : glisser un bloc = déplacer (change start_at, garde la durée, peut changer de jour) ; glisser la poignée du bas = redimensionner (change end_at) ; glisser une tâche du panneau « À planifier » (tâches sans start_at) sur un créneau = planifier (défaut 1h) ; clic sur un bloc = ouvre le détail. Commit optimiste puis PATCH (rollback via loadBoard() en cas d'échec). Fuseau : tous les users sont en America/Asuncion (-03 fixe, pas de DST) ; on stocke en UTC et on s'appuie sur le fuseau local du navigateur (helpers toLocalInput/fromLocalInput/addMinISO). Le détail de tâche gagne 2 champs datetime-local « 🗓 Agenda début / fin » (chemin non-drag + déplanifier en vidant le début). setView/renderBoard/loadBoard gèrent la classe agenda-mode sur #board. Cache SW v42->v43. Migration vérifiée (colonnes présentes), cycle API testé de bout en bout (create planifiée -> board -> patch/déplacer -> déplanifier -> delete), rendu vérifié au navigateur (semaine + détail, 0 erreur console). Restart service fait. Reste Phase 2 : OAuth Google par user (table tokens + refresh), push des tâches planifiées vers l'agenda Google de l'user, et récupération read-only des événements Google pour affichage en fond dans cette vue.- 2026-08-25 — Incident 502 (site down) + durcissement du redémarrage auto. Matt a signalé un 502 Bad Gateway Cloudflare sur yacare.panelbay.com (host en erreur, Cloudflare OK). Cause : moa-tasks.service s'était arrêté proprement la veille (2026-08-24 18:37, « Deactivated successfully », exit 0, pas un crash) et n'est jamais remonté car l'unit avait Restart=on-failure, qui ne relance PAS un arrêt propre. Backend (127.0.0.1:8300) donc éteint → nginx renvoyait 502. Fix immédiat : systemctl start moa-tasks.service (backend 200, site 200). Fix préventif : Restart=on-failure → Restart=always dans /etc/systemd/system/moa-tasks.service (+ daemon-reload), pour que le service remonte seul quel que soit le motif d'arrêt (RestartSec=3). Service enabled au boot (déjà OK). Cause exacte de l'arrêt du 24 non déterminée (aucune erreur dans le journal, sortie 0). À surveiller : si ça se reproduit, chercher qui envoie l'arrêt (déploiement, OOM, script externe).- 2026-08-21 — Recherche : badge visuel « Terminée » sur les tâches archivées. Demande Matt : la recherche cherche bien partout (y compris les tâches terminées, archived: 'all'), mais rien n'indiquait visuellement qu'un résultat était une tâche terminée (seule une opacité .8 discrète). renderFlatRow (frontend/js/app.js) affichait déjà un status-pill « Terminée » (vert) / « En cours », mais uniquement quand opts.showAssignee (vue Délégué). Ajout d'un flag opts.showStatus qui déclenche le même pill sans avoir besoin de showAssignee, et le libellé terminé devient « ✓ Terminée ». La boucle de rendu de la recherche (openSearchModal → run()) passe désormais { showCompleted: t.archived, showStatus: true }. Renforcement CSS (frontend/styles.css) : .flat-row.is-archived reçoit un liseré vert gauche (border-left:3px solid var(--p-medium)) + fond #f6fdfa en plus de l'opacité. Aucune modif backend (l'endpoint /search renvoyait déjà archived/archived_at). sw.js cache v40→v41. Restart service fait, healthcheck OK. La vue Historique (toutes terminées) est inchangée (badge redondant non ajouté là-bas).- 2026-08-20 — Fix mobile : le header debordait a droite (+ Tache / avatar hors ecran). Rapporte par Matt (capture) : sur telephone le bandeau du haut depassait, le bouton « + Tache » et l'avatar etaient coupes a droite. Cause : .topbar en flex space-between avec un contenu (menu + marque « Yacare » + selecteur « Mon tableau » jusqu'a 42vw + drapeau + cloche + loupe + « + Tache » + avatar) plus large que l'ecran, et rien ne se compressait -> le groupe de droite etait pousse hors cadre. Correctif CSS (frontend/styles.css) : .topbar-left devient compressible (min-width:0; flex:1 1 auto) et .board-switcher min-width:0 pour que le titre s'ellipse, .topbar-right fige (flex:0 0 auto) donc les controles restent toujours a l'ecran ; en @media (max-width:540px) le mot « Yacare » est masque (icone 🐊 gardee), titre du board reduit a 34vw, gaps/paddings resserres. Aucune modif backend/JS. Restart service fait (fingerprint recalcule -> sessions rechargees) ; SW network-first non bumpe.- 2026-08-20 — Fix mobile : cocher une case pouvait cocher une AUTRE tache (ghost click). Symptome rapporte par Matt sur telephone : en cochant la case d'une carte, la case restait visible, la carte disparaissait brievement et une autre tache se cochait parfois. Cause : toggleDoneQuick (frontend/js/app.js) fait une mutation optimiste puis renderBoard() synchrone qui reconstruit tout le board ; la carte quitte donc l'endroit du doigt, et le navigateur mobile envoie ~300 ms plus tard le click differe du tap tactile, qui retombe sur la carte desormais a ces coordonnees -> mauvaise tache cochee. Correctif : avaler ce clic fantome synthetise, exactement comme le fait deja le chemin drag (ptrUp) : ajout d'un listener click en phase capture (stopPropagation+preventDefault, auto-retire) juste avant renderBoard(), avec timeout 350 ms. Aucune modif backend. node --check OK ; restart service fait (fingerprint /api/version recalcule -> sessions ouvertes se rechargent seules, cf. [[yacare-auto-reload-deploy]]).- 2026-08-08 — Aperçu de la description sur les cartes du tableau. Demande Matt : en voyant une carte dans le pipeline il ne se rappelait plus le contexte et oubliait qu'une description existait dans la tâche. Ajout d'un aperçu de la description directement sur la carte, entre les étiquettes et la ligne de méta (renderCard, frontend/js/app.js) : élément .card-desc affiché uniquement si t.description non vide, tronqué à 2 lignes en CSS (-webkit-line-clamp, frontend/styles.css), texte complet en title au survol. Aucune modif backend : serializeTask (backend/lib/serialize.js) renvoyait déjà description dans le payload /api/board, le front l'avait donc déjà sous la main. Restart backend fait, healthcheck OK. SW non bumpé (network-first, cf. entrée 2026-08-07). Déploiement par fingerprint auto → sessions ouvertes se rechargent seules ([[yacare-auto-reload-deploy]]).- 2026-08-07 — Fix : un brouillon de nouvelle tâche ressuscitait après publication. Symptôme (rapporté par Matt) : après avoir cliqué « Créer », rouvrir « Nouvelle tâche » affichait « 📝 Brouillon restauré » avec le contenu de la tâche déjà publiée. Cause = race condition dans l'autosave débounced du brouillon (openTaskEditor, frontend/js/app.js) : persistDraft() programme un saveDraft à +350 ms à chaque frappe/blur ; au clic sur « Créer », save() faisait createTask() puis clearDraft(), mais le timer en attente se déclenchait ensuite et réécrivait le brouillon tout juste effacé (le blur du textarea au clic sur le bouton programmait encore un save). Correctif : (1) drapeau submitting posé en tête de save() + clearTimeout(draftTimer) → plus aucun autosave tardif ; le callback du timer et persistDraft() bail-out si submitting. submitting remis à false en cas d'échec réseau (retry possible). (2) Filet de sécurité draftMatchesExistingTask() : à la restauration, si une tâche du tableau porte déjà le même titre (à la casse près), le brouillon est considéré comme déjà publié → clearDraft() + pas de bannière (nettoie aussi les brouillons orphelins déjà présents dans les navigateurs). Aucun changement backend. node --check OK ; déployé par restart (fingerprint auto → toutes les sessions se rechargent, voir mémoire [[yacare-auto-reload-deploy]]). SW CACHE non bumpé (network-first no-store, inutile).- 2026-08-06 — Helper CLI scripts/add_task.py : créer une tâche en un POST (pour l'AIOS). Matt trouvait la création de tâche par l'AIOS lente et coûteuse (l'assistant re-explorait schéma DB + API + clé à chaque fois, ~10 appels d'outils pour un INSERT). Nouveau script scripts/add_task.py : résout créateur (défaut Matt / 1er admin) et colonne par nom via lecture SQLite locale, puis appelle le vrai endpoint POST /api/tasks (log d'activité, position, notifications gratuites). Auth : nouvelle clé API globale (label AIOS-cli, user_id NULL dans api_keys), plaintext stocké dans /root/aios/.env sous YACARE_API_KEY ; port via YACARE_PORT (défaut 8300). Usage : python3 scripts/add_task.py --title "..." [--desc ...] [--column "Claude code"] [--priority ...] [--deadline AAAA-MM-JJ] [--creator ...] [--assignee ...]. Mesuré : ~0,1 s / tâche. Testé (création + suppression d'une tâche de test). Aucune modif du serveur ni du frontend. Note : la clé globale préexistante labellisée "AIOS" a son plaintext perdu (non réutilisable), d'où la nouvelle.- 2026-08-06 — Fix : le scroll d'une colonne remontait tout seul en haut. Cause : chaque rafraîchissement automatique du board (poll toutes les 10 s via autoRefreshTick, echo socket temps réel, retour de focus/onglet visible) appelle loadBoard() → renderBoard(), qui fait board.innerHTML = '' et reconstruit toutes les colonnes de zéro, remettant le scrollTop de chaque .column-body à 0 pendant que l'utilisateur lisait (symptôme rapporté : « ça me remonte en haut après ~5 s »). Correctif dans renderBoard() (frontend/js/app.js) : avant le innerHTML='', on mémorise le scrollTop de chaque colonne (clé = data-col-id) et le scrollLeft horizontal du board ; après reconstruction, on les restaure. Aucun changement de comportement pour le rafraîchissement des données, juste la position de lecture préservée. frontend/sw.js cache v39→v40. Restart backend fait, healthcheck OK (yacare.panelbay.com → 200).- 2026-08-05 — Mot de passe temporaire + changement forcé à la première connexion (popup). Nouvelle colonne users.must_change_password (migration idempotente addColumn dans backend/db/index.js). Quand elle vaut 1, l'utilisateur voit au login une popup non fermable (pas de clic backdrop, pas de ✕, pas d'annulation : openForcedPasswordChange() dans frontend/js/app.js, déclenchée par maybeForcePasswordChange() après renderMe() au boot ET au login) qui lui impose de définir un mot de passe personnel avant d'utiliser l'app. Backend : POST /api/users/me/password accepte désormais un changement sans current_password UNIQUEMENT si must_change_password=1 (l'utilisateur est déjà authentifié par JWT et n'a pas de mot de passe « actuel » à retaper) ; sinon le mot de passe actuel reste exigé. Le flag est remis à 0 après le changement. Le flag est exposé par login (routes/auth.js) et /api/users/me (via lib/auth.js, ajouté aux SELECT actor). Automatique pour tout nouveau compte : POST /api/users met must_change_password=1 par défaut (opt-out avec must_change_password:false) ; un reset admin PATCH /api/users/:id avec password force aussi le changement (même opt-out). frontend/js/api.js changePassword(current, new) peut être appelé avec current undefined (flux forcé). Testé de bout en bout (login temp → flag → change sans mot de passe actuel → flag effacé). Fichiers : backend/db/index.js, backend/lib/auth.js, backend/routes/auth.js, backend/routes/users.js, frontend/js/app.js, frontend/sw.js (cache v38→v39). Restart backend fait, healthcheck OK. Appliqué à Younes (younes@moa.fr) : mot de passe réinitialisé en temporaire + changement forcé au prochain login.- 2026-08-05 — Brouillon automatique à la création d'une tâche (ne rien perdre + « pas publiée si on ne veut pas »). Demande Matt : quand on écrit une nouvelle tâche et que la fenêtre se ferme par erreur, ne rien perdre ; et pouvoir garder une tâche en brouillon sans la publier. Implémenté 100% côté frontend, en localStorage (aucune tâche fantôme côté serveur) : dans openTaskEditor (frontend/js/app.js), le formulaire de nouvelle tâche est auto-sauvegardé à chaque frappe (debounce 350ms) sous la clé yacare_taskdraft_<userId>_<colId>, couvrant titre, description, priorité, dates début/échéance, délégation, étiquettes et récurrence (délégation d'événements input/change sur la modale + appels explicites sur les chips d'étiquettes et les boutons de jours). À la réouverture de « Nouvelle tâche » sur la même colonne, le brouillon est restauré et une bannière « 📝 Brouillon restauré » s'affiche avec un bouton « Repartir de zéro » (efface le brouillon et rouvre un formulaire vierge). Nouveau bouton « 💾 Brouillon » dans les actions (création uniquement) : enregistre et ferme sans créer la tâche. Le brouillon est effacé à la création réussie (clearDraft après API.createTask), via « Repartir de zéro », ou automatiquement quand il redevient vide (draftIsEmpty). Rien n'est envoyé au serveur tant que « Créer » n'est pas cliqué → une tâche n'est jamais publiée par accident. Limite assumée : brouillon par navigateur/appareil (localStorage, non synchronisé). Helpers ajoutés : loadDraft/saveDraft/clearDraft/draftIsEmpty/collectDraft/persistDraft. Style .draft-banner ajouté dans frontend/styles.css. Fichiers : frontend/js/app.js, frontend/styles.css. Restart backend fait (fingerprint /api/version recalculé → sessions ouvertes se rechargent seules) ; healthcheck OK, node --check OK.- 2026-08-05 — Correctifs d'audit sécurité (P1 + P2). Chantier principal : fermeture des IDOR. Nouveau garde d'autorisation isStakeholder(taskId, actor) dans backend/lib/serialize.js (admin, ou créateur / assigné / collaborateur ; couvre aussi la clé API globale qui a le rôle admin). Appliqué en tête des routes de backend/routes/tasks.js : GET /tasks/:id, PATCH /tasks/:id, GET /tasks/:id/activity, POST /tasks/:id/subtasks, PATCH /subtasks/:sid, DELETE /subtasks/:sid, POST /tasks/:id/comments, POST /tasks/:id/collaborators (l'acteur doit être un stakeholder existant) et DELETE /tasks/:id/collaborators/:uid. Dans backend/routes/attachments.js : GET /api/attachments/:aid/download et POST /tasks/:id/attachments vérifient la participation à la tâche (un contrat confidentiel n'est plus récupérable en itérant les id). Les cas légitimes restent permissifs (créateur édite, assigné coche, admin partout, délégation / collaboration OK). Anti-brute-force : express-rate-limit sur POST /api/auth/login (20 tentatives / IP / 15 min, les logins réussis ne comptent pas), app.set('trust proxy', 1) pour l'IP réelle derrière nginx. Durcissement : helmet + CSP (tunée pour cette PWA same-origin : scripts same-origin, socket.io same-origin, styles inline autorisés car l'UI pose element.style), x-powered-by désactivé. Garde-fous longueur serveur : titre 200, description / commentaire / sous-tâche 5000 (capText dans tasks.js) + maxlength sur tous les inputs (frontend/js/app.js). Accessibilité : glyphe de forme par priorité en plus de la couleur sur la carte (PRIORITY_SHAPES, WCAG 1.4.1) ; carte rendue focusable clavier (tabindex/role=button/Enter-Espace) ; menu « ↔ Déplacer vers… » dans le kebab du détail (fallback clavier au drag & drop, WCAG 2.1.1). UX : skeleton de colonnes au premier chargement du board + état d'erreur avec bouton « Réessayer » (renderBoardSkeleton / renderBoardError, styles dans frontend/styles.css). Secrets : JWT_SECRET était déjà présent dans backend/.env avec une valeur forte (64 hex, pas le repli dev) — inchangé ; manifeste de rotation créé (/root/workspace/Audit-apps/_secret_rotation_moa-tasks.tsv). Deps ajoutées : helmet, express-rate-limit. Reportés (risque sur app vivante à 22 users, cf. cahier des charges « si faisable ») : #9 undo / soft-delete deleted_at (surface de requêtes trop large : board×3, history, delegated, collaborating, recurring, search, my-turns, archive_sweep — un oubli = tâche fantôme ; la suppression garde déjà son autorisation créateur/admin) et #8 pagination /api/board + /delegated (risque de casser le rendu Kanban / temps réel pour un gain nul au volume actuel). Sauvegarde DB avant intervention : data/moa-tasks.db.bak-2026-08-05-audit(+wal/+shm). Fichiers : backend/lib/serialize.js, backend/routes/tasks.js, backend/routes/attachments.js, backend/routes/auth.js, backend/server.js, backend/package.json, frontend/js/app.js, frontend/styles.css. Restart backend fait, healthcheck OK, 12/12 tests de non-régression + IDOR au vert.- 2026-08-05 — Icône PWA agrandie (crocodile plus gros dans la bulle). Le personnage était trop petit une fois installé (il ne remplissait que ~49% de la largeur / ~43% de la hauteur du cadre, énorme marge crème autour). Régénéré tout le jeu d'icônes en recadrant le crocodile depuis la source frontend/icons/yacare-logo.png (bbox du personnage détectée automatiquement, hors crème/blanc) : le personnage remplit maintenant ~70% de la largeur / ~61% de la hauteur, centré sur fond crème #f7f7ed, tout en gardant une zone de sécurité pour l'affichage « maskable » (Android/iOS ne rognent pas le perso). Tailles régénérées : 512/192/180/48/32/16 + favicon.ico. Originaux sauvegardés dans frontend/icons/_backup/. Cache-busting : sw.js v36→v37, ?v=2 ajouté aux src d'icônes du manifest.webmanifest et aux <link rel=icon/apple-touch-icon> de index.html. Restart backend fait. Note : une PWA déjà installée peut garder l'ancienne icône en cache OS jusqu'à réinstallation (désinstaller/réinstaller pour forcer).- 2026-08-05 — Édition inline du détail de tâche (suppression du bouton « Modifier »). Le bouton « ✏️ Modifier » du menu kebab (⋯) du détail de tâche est retiré : tous les champs sont désormais éditables directement dans le détail, sans ouvrir d'éditeur séparé. Rendus éditables en place : titre (input stylé en titre, save au blur/Entrée), priorité (select), date de début + échéance (inputs date), délégation (select « Déléguer à », si canDelegate()), étiquettes (label-picker à chips toujours affiché + « Gérer »), récurrence (fréquence + intervalle/jours), description (auto-save au blur, bouton « Enregistrer » retiré). Nouveau helper patchField(patch, okMsg) dans renderTaskDetail : persiste via PATCH /tasks/:id (ou POST /tasks/:id/delegate pour l'assignation) et rafraîchit le tableau en arrière-plan sans fermer/rouvrir la modale (pas de perte de focus/scroll). openTaskEditor est conservé (toujours utilisé pour la création de tâche). Fichiers : frontend/js/app.js (renderTaskDetail + openTaskKebab), frontend/styles.css (.detail-title-input, .inline-select, .inline-date, .field-mini, .detail-fields), frontend/sw.js (cache v35→v36). Restart backend fait ; les sessions ouvertes se rechargent seules (fingerprint /api/version).- 2026-08-04 — Migration panelbay.com complète (bascule sèche, à la demande de Matt : les 22 users réinstallent la PWA). Nouvelle et seule URL : https://yacare.panelbay.com (HTTPS Cloudflare + Let's Encrypt). backend/.env CORS_ORIGIN = https://yacare.panelbay.com uniquement ; server.js sait parser une liste CSV d'origines (dual-run) mais on est repassé à une seule origine. Lien du recap quotidien (jobs/daily_recap.py) basculé sur yacare.panelbay.com. Ancienne URL taches.bouthors-m.tenga.run retirée (record DNS broker + cert + vhost nginx supprimés via unexpose.py). Impact : chaque utilisateur doit ré-installer la PWA sur la nouvelle URL et ré-autoriser les notifications (le push web est lié à l'origine). Restart backend fait.- 2026-08-03 — 3 évolutions UI/UX (Yacaré). (1) Colonnes réorganisables : poignée « ⠿ » dans l'en-tête de colonne, drag & drop natif pour changer l'ordre (indicateur de dépôt gauche/droite), persisté via PATCH /api/columns/:id { position } (déjà supporté côté serveur). Fallback tactile : boutons « Déplacer à gauche/droite » dans le menu colonne (⋯), y compris pour la colonne « Terminé » (repositionnable). (2) Onglet « Récurrentes » : nouvelle vue listant les tâches actives portant une règle de récurrence (nouvel endpoint GET /api/recurring). (3) Onglet délégué adaptatif : pour les utilisateurs qui ne peuvent pas déléguer (canDelegate() faux = membre sans can_delegate), l'onglet « Ce que j'ai délégué » (toujours vide pour eux) devient « En collaboration » et liste leurs tâches en collaboration (nouvel endpoint GET /api/collaborating). Fichiers touchés : backend/routes/tasks.js (2 routes), frontend/js/api.js, frontend/index.html (onglets), frontend/js/app.js (setView/loadBoard/renderMe/renderDelegated + renderCollaborating/renderRecurring + DnD colonnes + columnMenu), frontend/styles.css (styles poignée/dépôt), frontend/sw.js (cache v34→v35). Restart backend requis (fait).- 2026-08-02 — Refonte de l'icône PWA / dock. L'ancienne icône (icon-192/512) était l'emoji crocodile Twemoji sur fond bleu pâle (placeholder), qui rendait mal une fois installée. Régénéré tout le jeu d'icônes depuis la vraie mascotte frontend/icons/yacare-logo.png (1024px) : mascotte centrée plein cadre sur fond crème #f7f7ed, tailles 512/192/180/48/32/16 + favicon.ico. Retiré le favicon SVG emoji (icons/croc.svg supprimé, plus référencé). manifest.webmanifest et <meta theme-color> passés au crème. Aucun restart requis (frontend statique).- 2026-08-02 — Manuel initialisé (bootstrap auto).
- 2026-08-26 : création compte Yacaré "Melanie" (melanie@moa-realestate.com, member, mdp temp + changement forcé). Compte existant "Giannina" migré de giannina@moa.fr vers son gmail gianninagonzalezmencia@gmail.com + mdp temp réarmé (changement forcé). PATCH users ne gère pas email -> update SQL direct.
- 2026-08-26 : nouveau logo/favicon Yacaré (caïman vert). Régénéré depuis image source : favicon.ico (16/32/48), icons/favicon-16/32/48/180, icon-192/512, yacare-logo.png (1024) + branding/yacare-mascot.png. Recadrage 6% pour enlever marge grise + ombre. Logo in-app (emoji 🐊 -> <img>) sur login (.brand-logo) et en-tête (.brand-mark-icon), fond bleu retiré. Cache-busting ?v=2 -> ?v=3 (index.html + manifest), SW CACHE v41 -> v42. Service redémarré.