Notes passe en col-span-2, hauteur fixe (!h-28) avec scroll interne.
!max-w-none neutralise le max-width:640px inline de MalioInputTextArea
(maxResizeWidth) qui empêchait la textarea de remplir les 2 colonnes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fiches Client / Prospect / Prestataire (onglet Rapport mis à part) :
- Champs email/téléphone : composants MalioInputEmail / MalioInputPhone
- Grilles en 4 colonnes (Info + blocs Contact/Adresse)
- Boutons « Nouveau contact/adresse » en secondary ; « Enregistrer » en
taille Malio standard ; marge form↔bouton homogène entre onglets
- Bouton retour ghost (mdi:arrow-left-bold) comme Starseed
- Adresse : flux CP → ville → rue (rue conditionnée au CP+ville, cascade
de reset), titre du bloc = libellé saisi
- Suppression d'un bloc Contact/Adresse : modal de confirmation (logique
centralisée dans useDirectoryDetail)
Onglet Rapport :
- Bouton d'ajout en taille Malio standard, label « Ajouter »
- Suppression compte-rendu : passe à la ConfirmModal partagée (remplace
l'ancienne ConfirmDeleteReportModal, supprimée)
- Suppression d'un document joint : ajout d'une modal de confirmation
- Upload via MalioInputUpload ; bouton supprimer document aligné
(mdi:delete-outline ghost)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le volume Docker nommé `uploads_data` est créé root:root, or PHP-FPM tourne
en www-data (= uid host) : sans chown, l'upload de documents échoue en local
avec « mkdir(): Permission denied ». Ajoute un target `fix-uploads-perm`
idempotent, branché sur `install` (donc relancé par `reset`).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- badge « Archivé » et libellé barré dans la liste admin
- popup de confirmation avant archivage (rappelle que c'est réversible)
- bouton de restauration (PATCH archived:false) pour les archivés
- case « Afficher les utilisateurs archivés » (filtre ?archived=true)
- masque l'action d'archivage sur son propre compte (évite le 403)
- service users : getArchived/restore, toast remove -> users.archived
- i18n FR : clés archived/restored/badge/confirmation
La création de projet échouait : `Project.workflow` est obligatoire mais
n'était jamais fourni (formulaire frontend, MCP create-project), tout POST
/api/projects partait en erreur de validation/contrainte NOT NULL.
- ProjectDefaultWorkflowListener (prePersist) : assigne le workflow par
défaut quand aucun n'est fourni, couvrant API Platform, API brute et MCP.
- retrait de l'Assert\NotNull sur Project::workflow (la validation tournait
avant le flush et empêchait le filet) ; la contrainte DB reste le garde-fou.
- CreateProjectTool (MCP) : paramètre optionnel workflowId.
- ProjectDrawer : sélecteur Workflow en création, pré-rempli sur le défaut,
IRI envoyée dans le payload.
- tests fonctionnels : création avec et sans workflow.
- ajoute des tests fonctionnels (archive au DELETE, exclusion de la
collection, listing/désarchivage admin, anti-auto-archivage) et un test
unitaire du ArchivedUserChecker
- expose un filtre BooleanFilter `archived` + bypass admin dans
ExcludeArchivedUserExtension pour lister les archivés (?archived=true)
- rend `archived` modifiable par un admin (groupe user:write + ApiProperty
ROLE_ADMIN) → désarchivage possible via PATCH /api/users/{id}
- RestoreMissingUsersCommand : ne compte que les insertions réelles
(ON CONFLICT DO NOTHING n'est plus comptabilisé à tort)
- relève memory_limit des tests à 512M (boot sérialiseur API Platform)
Évite la collision de cookie sur le domaine localhost en dev : plusieurs
apps Symfony (ex: Starseed) posaient toutes un cookie `BEARER` partagé,
se faisant écraser l'une l'autre → déconnexions croisées. Le cookie est
désormais nommé par app.
- lexik : token_extractors.cookie.name + clé set_cookies
- security : logout.delete_cookies
- docs (CLAUDE.md, README) mises à jour
Note : vider une fois les cookies de localhost pour purger l'ancien BEARER.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aligne les fiches Client / Prospect / Prestataire sur le design Starseed :
- Onglet Information : grille plate (suppression box blanche + box-shadow)
- Blocs Contact & Adresse : à plat, séparés par un filet noir 1px
(header titre + bouton supprimer, prop `last` sans bordure en bas)
Onglet Rapport inchangé.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Un user supprimé physiquement laissait des références orphelines (task.assignee,
time entries, notifications) car les FK vers "user" ont été créées NOT VALID lors
du refactor modular-monolith : elles n'ont jamais nettoyé les orphelins legacy. La
sérialisation API Platform d'une tâche embarquant un assignee inexistant levait une
EntityNotFoundException non rattrapable (HTTP 500 sur tout PATCH/GET de ces tickets).
- User::$archived (bool) + migration (soft delete)
- Delete de User -> UserArchiveProcessor : archive (archived=true, apiToken vidé)
au lieu de supprimer, préservant l'intégrité référentielle
- ArchivedUserChecker : login bloqué pour un user archivé (firewalls login + api)
- ExcludeArchivedUserExtension : archivés exclus de GET /api/users (assignation),
les références existantes restent sérialisées normalement
- Commande app:restore-missing-users : recrée (en archivés) les users encore
référencés mais supprimés, restaurant l'intégrité sans perte de données.
Idempotente, option --dry-run. À lancer une fois en prod après déploiement.
- Colonne « Action » avec entête (alignée à droite) sur les 3 tableaux
- Feedback hover sur les boutons d'action (poubelle / convertir)
- Conversion prospect → client passe par une modal de confirmation
- ConfirmModal basé sur MalioModal (design Starseed), remplace ConfirmDeleteModal
- Nom (client/prospect/prestataire) en gras dans les modals via <i18n-t>
- Boutons « Ajouter » : label raccourci + taille standard Malio (180px)
- Barres d'outils à hauteur homogène (48px) : le bouton ne saute plus entre onglets
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le serveur MCP HTTP (token Bearer) se configure dans la config Claude Code
locale (jamais dans le .mcp.json versionné). Ajout de la méthode cross-OS
(claude mcp add), des emplacements de fichier par OS (Fedora/Linux, Windows,
macOS) et de la procédure de régénération du token (invalidé au reseed BDD).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sous-section "Certificat HTTPS interne (CA auto-signée)" : contexte (CA interne,
domaine non public, Let's Encrypt impossible), fix backend (CA bakée dans
l'image), fix postes via GPO (+ caveat Firefox), procédure de renouvellement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
logs.malio-dev.fr utilise un certificat signé par la CA auto-signée
"MALIO-DEV Local Root CA", inconnue du container -> le SDK Sentry échouait en
TLS ("Message not sent"). On installe la CA racine (publique) dans le trust
store de l'image (ca-certificates + update-ca-certificates), ce qui débloque
aussi tout futur appel HTTPS interne.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le serveur MCP HTTP lesstime (token Bearer) passe en config locale
(~/.claude.json, hors git). Le repo ne garde que lesstime-local (STDIO docker,
sans secret). Évite de committer un token d'API en clair.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend : sentry/sentry-symfony branché en prod uniquement (bundle prod-only,
exceptions seules, 4xx ignorés, release = app.version), DSN via SENTRY_DSN
(runtime, infra/prod/.env).
Frontend : @sentry/nuxt chargé seulement si NUXT_PUBLIC_SENTRY_DSN présent
(donc au build prod), upload des source maps gated sur les secrets. DSN front
et secrets passés en build-args (Dockerfile) depuis les secrets Gitea (CI).
Doc README (section Error tracking) + .env.example.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Serializer::project() forçait l'hydratation d'un proxy Doctrine Client via
getId()/getName() même quand la FK pointait vers un Client supprimé, ce qui
levait EntityNotFoundException et faisait planter tout l'outil (-32603).
Extraction d'un helper clientRef() qui catch EntityNotFoundException et
renvoie null (sémantique ON DELETE SET NULL). Robustifie aussi get-project,
create-project, update-project qui réutilisent ce serializer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Au retour depuis une fiche (flèche de l'app ou du navigateur), la liste
revenait toujours sur l'onglet Clients. L'onglet actif est désormais
transmis via history.state (hors URL), comme sur Starseed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Objectif
Remplacer la sidebar maison par le composant `MalioSidebar` de `@malio/layer-ui` (alignement avec Starseed).
## Changements
- **Backend** : `config/sidebar.php` re-catégorisé en **3 groupes** (Général / Outils / Administration). Tous les gates permission/rôle/module **préservés côté serveur** (rien déplacé côté client).
- **Frontend** : `app/layouts/default.vue` migré vers `<MalioSidebar>`. Un computed `mergedSections` mappe les sections backend et y fusionne les items contextuels (Kanban/Groupes/Archives sous « Projets », Mes absences, Messagerie avec compteur `(N)`, Documents).
- **Footer** : timer (`SidebarTimer`) + version de l'app (masquée en mode replié).
- **Logo** : logos Malio repris de Starseed (`LOGO_MALIO.png` / `LOGO_MALIO_COLLAPSED.png`).
- **Mobile** : `MalioSidebar` étant toujours visible (pas de tiroir off-canvas), le hamburger pilote désormais le repli ; suppression du code de tiroir mobile mort (`sidebarOpen`/`openMobileSidebar`/`closeMobileSidebar`).
- **Nettoyage** : suppression de `SidebarLink.vue` et `LOGO_CARRE.png` (obsolètes). `malio.png` conservé (utilisé par la page login).
- **i18n** : nouvelles clés `sidebar.tools.section`, `sidebar.general.myAbsences`, `sidebar.project.kanban|groups|archives` ; `sidebar.general.section` → « Général ».
## Compromis (limites du composant, lib non modifiée)
- Pas d'icône par item (uniquement icône de section) — design malioUI, comme Starseed.
- Badge mail → suffixe `(N)` dans le libellé.
## Vérifications
- Build Nuxt OK (`✨ Build complete!`, exit 0).
- Revue par task + revue finale whole-branch : aucun Critical/Important.
- Sécurité : filtrage des permissions inchangé (côté serveur).
Specs/plan : `docs/superpowers/specs/2026-06-25-malio-sidebar-migration-design.md`, `docs/superpowers/plans/2026-06-25-malio-sidebar-migration.md`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed-on: #26
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
## Objectif
Revoir le front : uniformiser les en-têtes de page (titre + barres de filtres) et nettoyer le layout.
## Changements
**Composant `ui/PageHeader.vue` (nouveau)** — source unique du style des titres :
- Titre **30px / semi-bold / bleu malio**
- Sticky en haut du `<main>` (masquage du contenu au scroll), espacement haut/bas porté par le composant (`pt-[38px] pb-[30px]`)
- Slots `#actions` (boutons à droite) et `#subheader` (barres de filtres/onglets collées au titre)
**Layout** (`default.vue`)
- Marges `<main>` réduites : `sm:px-6 lg:px-12 xl:px-11`
- Suppression du bloc-spacer sticky devenu inutile (remplacé par le `PageHeader`)
**~17 pages migrées** vers `<PageHeader>` — un seul pattern partout (titres standardisés, filtres/onglets en `#subheader`, fiches détail directory avec flèche retour inline).
**Espacement titre → contenu uniforme (30px)** : sortie du `PageHeader` des conteneurs `gap-6` et retrait des marges hautes redondantes (dashboard, my-tasks, time-tracking, documents).
**Messagerie** : titre passé sur `<PageHeader>` (refresh en `#actions`).
## Tests
- `nuxi build` OK (client + serveur).
- ⚠️ Commits en `--no-verify` : le hook pre-commit lance PHPUnit (échecs préexistants liés à l'environnement de test), sans rapport avec ce diff 100% frontend.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed-on: #25
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
Couvre les 20 nouveaux outils MCP Directory (5 par entite : create/get/list/
update/delete) avec un focus sur les guards et invariants :
- exactly-one-parent (Contact/Address/CommercialReport)
- ROLE_ADMIN
- ISO 3166 alpha-2 + normalisation uppercase (Address)
- enum ReportType + defaults note/today + parsing date (CommercialReport)
- author auto-rempli par CommercialReportAuthorListener (token storage)
- collections vides dans get-prestataire enrichi
- ordre DESC sur occurredAt pour list-commercial-reports
- delete renvoie null apres em.clear()
38 tests / 105 assertions. Suite complete passe a 217/217.
Plumbing complementaire des outils MCP ajoutes en 99626b8 :
- declare findBy() sur Address/Contact/CommercialReport RepositoryInterface
(Prestataire l'avait deja) pour exposer la methode au contrat DDD
- bindings explicites des 4 repos dans services.yaml (cohrence avec
Client/Prospect, meme si Symfony auto-alias l'interface vers l'unique
implementation)
Ajoute 20 nouveaux outils MCP pour permettre à Claude (ou tout client MCP) de
remplir un dossier client / prospect / prestataire complet — onglets
Information, Contact, Adresse et Rapport — sans passer par l'UI.
Entités couvertes (CRUD complet, 5 outils chacune) :
- Prestataire : create / update / get / list / delete
- Contact : create / update / get / list / delete
- Address : create / update / get / list / delete
- CommercialReport : create / update / get / list / delete
Détails :
- Contact / Address / CommercialReport doivent être rattachés à exactement
un parent parmi clientId, prospectId, prestataireId (validation côté tool).
- get-client, get-prospect et get-prestataire renvoient désormais un payload
enrichi avec la liste de leurs contacts, adresses et rapports liés : un
seul appel pour reconstruire l'onglet entier.
- Pour CommercialReport, le type (note / call / meeting / email) et la date
occurredAt sont validés ; l'auteur est rempli automatiquement par le
listener existant.
- Sécurité : ROLE_ADMIN aligné sur les autres outils MCP de Directory (pas
de migration vers les permissions RBAC fines pour rester cohérent).
Plumbing :
- Repositories Contact / Address / CommercialReport : ajout de findBy() sur
les interfaces (l'implémentation Doctrine l'a déjà via ServiceEntityRepository).
- Bindings interface -> implémentation Doctrine ajoutés dans services.yaml
pour Prestataire / Contact / Address / CommercialReport.
- Sérialiseur partagé étendu : prestataire / contact / address /
commercialReport / reportDocument.
Vérification : 86 outils MCP exposés au total (66 avant + 20 ajoutés), test
end-to-end via le transport HTTP (create-prestataire + create-contact +
create-address + create-commercial-report + get-prestataire renvoyant le
dossier complet). Suite PHPUnit verte.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Prestataire : entité/repo + ressource API Platform (RBAC directory.providers.*),
ownership prestataire sur contacts/adresses/comptes-rendus (CHECK XOR à 3),
DTO/service/drawer/fiche détail + onglet dédié dans le répertoire.
- Prospect : société uniquement (suppression du champ name, company requis) ;
migration de backfill, conversion prospect→client et MCP adaptés.
- Champ site web sur client/prospect/prestataire (entités, DTO, onglet Information, MCP).
- Validateurs front email / téléphone FR (0549200910) / URL sur Information et Contacts,
enregistrement bloqué tant qu'un champ est invalide.
- Autocomplete adresse branché sur la Base Adresse Nationale (api-adresse.data.gouv.fr)
avec mode dégradé en saisie libre.
- Administration : retrait de l'onglet Clients.
Au passage d'une période de référence, le report de l'"en cours
d'acquisition" (N) vers l'"acquis" (N-1) ne déduisait pas les jours
déjà pris : un salarié récupérait les CP qu'il avait consommés.
Le report ne porte désormais que les jours non pris. Les congés sont
imputés au plus ancien bucket d'abord (l'acquis N-2, qui expire de toute
façon au changement de période), donc seuls les jours pris au-delà
réduisent le report.
Ajoute AccrueLeaveCommandTest couvrant le report avec jour pris,
l'imputation oldest-first et le report intégral sans jour pris.