Files
Lesstime/CLAUDE.md
T
tristan bbd8a38c95
Auto Tag Develop / tag (push) Successful in 9s
feat(directory) : refonte UI du Répertoire (LST-72) (#27)
Améliorations frontend de la partie **Répertoire** (Client / Prospect / Prestataire). Onglet **Rapport** retravaillé en fin de parcours ; le reste de la logique métier inchangé.

## Navigation & liste
- Onglet actif conservé au retour liste ↔ fiche (flèche app **et** navigateur) via `history.state` (hors URL) — util `historyTab.ts`
- Colonne « Action » (entête alignée) + feedback hover sur les boutons d'action
- Conversion prospect → client : modal de confirmation
- Boutons « Ajouter » : label court + taille Malio standard ; barres d'outils à hauteur homogène (plus de saut entre onglets)

## Fiches (Info / Contact / Adresse)
- Style **plat** sans box-shadow (comme Starseed)
- Champs email/téléphone : `MalioInputEmail` / `MalioInputPhone`
- Grilles en **4 colonnes** (Info + blocs)
- Boutons « Nouveau contact/adresse » en secondary ; « Enregistrer » en taille Malio ; marge form↔bouton homogène
- Bouton retour **ghost** (`mdi:arrow-left-bold`)
- **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 (centralisée dans `useDirectoryDetail`)
- Modals (suppression, conversion) basées sur `MalioModal` (design Starseed) avec nom en gras

## Onglet Rapport
- Bouton d'ajout en taille Malio (« Ajouter »)
- Suppression compte-rendu : `ConfirmModal` partagée (remplace l'ancienne modal maison)
- Suppression d'un document joint : ajout d'une modal de confirmation
- Upload via `MalioInputUpload` ; bouton supprimer document aligné (`mdi:delete-outline` ghost)

## Divers
- `fix(auth)` : cookie JWT renommé `BEARER_LESSTIME` (collision localhost avec d'autres apps Symfony)
- `fix(infra)` : target makefile `fix-uploads-perm` (volume `uploads_data` root → upload local OK)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #27
2026-06-27 13:29:56 +00:00

9.9 KiB

Lesstime

Application de gestion de projet. Monorepo Symfony 8 (API Platform 4) + Nuxt 4.

WIP — Intégration Mail (branche feat/mail-integration) : client mail OVH IMAP. Avant de toucher au mail, lire docs/mail-integration.md (section « Statut & reprise » = bugs déjà corrigés, points en suspens, commandes). Code : src/Mail/, src/Service/MailSyncService.php, src/Controller/Mail/, frontend/{services,stores,components}/mail*.

Stack

  • Backend : PHP 8.4, Symfony 8.0, API Platform 4, Doctrine ORM, PostgreSQL 16
  • Frontend : Nuxt 4 (SSR off / SPA), Vue 3, Pinia, Tailwind CSS, @malio/layer-ui, nuxt-toast, @nuxtjs/i18n, @nuxt/icon
  • Auth : JWT HTTP-only cookie (lexik/jwt-authentication-bundle), login à /login_check, cookie BEARER_LESSTIME (nommé par app pour éviter la collision avec d'autres apps Symfony sur localhost en dev)
  • Docker : PHP-FPM + Node 24, Nginx (port 8082), PostgreSQL (port 5435)

Structure

Le détail (entités, providers, services, composants…) se découvre dans le code. Carte d'orientation :

src/Entity/          # Entités Doctrine (User, Client, Project, Task + métadonnées Task*, TimeEntry, Notification, *Configuration…)
src/ApiResource/     # Ressources API Platform découplées des entités
src/State/           # Providers & Processors API Platform (Me, ActiveTimeEntry, TaskNumber, Notification, Gitea*, Zimbra*, RecurrenceHandler…)
src/Service/         # Services métier (NotificationService, CalDavService, RecurrenceCalculator)
src/Controller/      # Controllers custom (notifications, avatar, download document)
src/Mcp/Tool/        # MCP tools par domaine (Project/, Task/, TaskMeta/, TimeEntry/, Reference/)
src/Security/        # ApiTokenAuthenticator (MCP HTTP)
src/Command/  src/Repository/  src/DataFixtures/
config/              # security, api_platform, lexik_jwt, nelmio_cors, doctrine — config/jwt/ = clés
migrations/  docs/plans/  docs/superpowers/
frontend/pages/      # index, login, my-tasks, profile, projects/[id]/*, time-tracking, admin
frontend/components/ # Sous-dossiers ui/ client/ project/ task/ user/ admin/ time-tracking/ notification/
frontend/composables/# useApi, useAppVersion, useNotifications, useAvatarService
frontend/stores/     # Pinia : auth, ui, timer
frontend/services/   # 1 service par ressource API (+ services/dto/ pour les types)
frontend/i18n/locales/ # Traductions (langDir résolu depuis i18n/)

Commandes

make start           # Démarrer les containers
make stop            # Arrêter les containers
make restart         # Redémarrer les containers
make install         # Install complet (composer, migrations, fixtures, build Nuxt)
make reset           # Tout supprimer et réinstaller (supprime la BDD)
make dev-nuxt        # Dev server Nuxt (hot reload, port 3002)
make shell           # Shell dans le container PHP
make shell-root      # Shell root dans le container PHP
make cache-clear     # Vider le cache Symfony
make migration-migrate # Lancer les migrations
make fixtures        # Charger les fixtures
make db-reset        # Reset BDD + migrations + fixtures
make test            # PHPUnit
make php-cs-fixer-allow-risky # Fix code style PHP
make logs-dev        # Tail logs Symfony

Conventions

Commits

Format : <type>(<scope optionnel>) : <message> (espace avant et après :)

Types autorisés (minuscules) : build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test

Exemples : feat : add login page, fix(auth) : prevent null token crash

Tags & Versioning

  • La version de l'app est dans config/version.yaml (paramètre app.version)
  • À chaque création de tag, toujours mettre à jour config/version.yaml avec la même version
  • Faire un commit séparé de bump : chore : bump version to v<X.Y.Z>
  • Puis créer le tag et pusher : git tag v<X.Y.Z> && git push origin develop --tags

Backend

  • Toujours declare(strict_types=1) en haut des fichiers PHP
  • API Platform : utiliser ApiResource, Providers (src/State/), Processors — pas de controllers
  • Routes API préfixées /api (via config/routes/api_platform.yaml)
  • Le login (/login_check) est hors prefix /api, nginx réécrit REQUEST_URI vers /login_check
  • PHP CS Fixer : règles Symfony + PSR-12 + strict types
  • Rôles : ROLE_ADMIN, ROLE_USER — hiérarchie dans security.yaml
  • User::getRoles() ajoute toujours ROLE_USER
  • PostgreSQL : LIKE sur colonne JSON ne marche pas → utiliser roles::text LIKE via native SQL
  • Controllers custom sous /api/ : ajouter priority: 1 sur #[Route] pour éviter le conflit avec API Platform {id}
  • Serialization : pour embarquer une relation (pas IRI), ajouter le groupe du parent aux propriétés de l'entité cible
  • Upload fichiers : utiliser $file->getMimeType() (pas getClientMimeType()) pour valider côté serveur — nécessite symfony/mime
  • Endpoints ouverts à tout utilisateur authentifié : utiliser #[IsGranted('IS_AUTHENTICATED_FULLY')] au lieu d'un rôle spécifique

Frontend

  • TypeScript strict
  • Composable useApi() pour tous les appels API (gère cookies, erreurs, toasts, i18n)
  • Stores Pinia : useAuthStore (auth), useUiStore (ui), useTimerStore (timer)
  • Middleware global auth.global.ts protège les routes
  • Traductions dans frontend/i18n/locales/ (le module résout langDir depuis i18n/)
  • 4 espaces d'indentation
  • MalioSelect : options { label: string, value: string | number | null } — accepte les valeurs string (enums string OK, ex category/StatusCategory), pas seulement number (vérifié dans la source Select.vue : modelValue: string | number | null). L'option vide null n'est ajoutée que si empty-option-label est passé (ne pas le passer pour un champ requis). Largeur via group-class (pas de prop minWidth/min-width). ⚠️ Le COMPONENTS.md de la lib est inexact sur ce composant (il indique une clé text et une prop minWidth inexistantes) : la clé d'affichage réelle est label. Ne jamais modifier la lib malio-layer-ui depuis ce projet.
  • Pagination API Platform & extractHydraMembers (piège de troncature) : API Platform pagine par défaut à 30 éléments/page. Le helper extractHydraMembers() (frontend/utils/api.ts) ne lit que la première page (il ignore hydra:view.next) → toute liste > 30 éléments est tronquée silencieusement (bug LST-51/LST-52). Règle : toute collection consommée via extractHydraMembers doit soit être servie par une ressource non paginée (paginationEnabled: false sur le GetCollection, quand le volume est borné/modéré et qu'on veut tout afficher — c'est le cas des référentiels et de Client/Project/User/Task/TimeEntry), soit gérer explicitement la pagination via le helper fetchAllHydra() (suit toutes les pages, à réserver aux volumes non bornés comme /notifications), soit passer par une route dédiée bornée (ex /time_entries/range). Ne jamais lire une seule page d'une collection potentiellement > 30 éléments.

Composants UI

La librairie @malio/layer-ui fournit les composants de formulaire et d'action. La documentation complète des props, events et exemples d'utilisation se trouve dans frontend/node_modules/@malio/layer-ui/COMPONENTS.md. Toujours s'y référer avant d'utiliser un composant Malio.

MCP Server

  • 60 tools MCP exposant projets, tâches, métadonnées, time tracking, récurrences, documents et absences
  • Transport STDIO (local) : docker exec -i php-lesstime-fpm php bin/console mcp:server
  • Transport HTTP (réseau) : POST /_mcp avec header Authorization: Bearer <token>
  • Auth HTTP : ApiTokenAuthenticator vérifie le champ apiToken de l'entité User
  • Générer un token : php bin/console app:generate-api-token <username>
  • Config : config/packages/mcp.yaml, firewall dans config/packages/security.yaml
  • Attribut #[McpTool] doit être sur la classe (pas la méthode __invoke) pour la discovery SDK

Nginx

  • /_mcp → Symfony (MCP HTTP transport)
  • /api/* → Symfony (via try_files + index.php)
  • /api/login_check → location exact match, fastcgi direct avec REQUEST_URI réécrit en /login_check
  • / → SPA frontend (frontend/dist/)

Docker

  • Container PHP : php-lesstime-fpm
  • Container Nginx : nginx-lesstime
  • Container DB : PostgreSQL sur port 5435 (interne et externe)
  • Config Docker : infra/dev/.env.docker (override local : infra/dev/.env.docker.local)
  • Après modif nginx : docker restart nginx-lesstime

Déploiement (prod Docker)

  • Script : infra/prod/deploy.sh (./deploy.sh [tag]) — doc complète : doc/deployment-docker.md
  • Étapes : maintenance → pull image → up → migrations → app:seed-rbacapp:sync-permissions → cache clear/warmup
  • RBAC : les migrations créent les tables role/permission mais n'insèrent aucune donnée. Les rôles système (admin, user) viennent de app:seed-rbac (idempotent) et le catalogue des permissions de app:sync-permissions (à relancer à chaque ajout de permission). Symptôme si oubliées : page admin Rôles vide (« Aucun rôle trouvé »).

Fixtures

  • User admin : admin / admin (ROLE_ADMIN)
  • Users internes : alice / alice, bob / bob, charlie / charlie (ROLE_USER)
  • API token admin (dev) : dev-mcp-token-for-testing-only-do-not-use-in-production
  • ZimbraConfiguration : serverUrl https://mail.ovh.com, username lesstime@ovh.fr, enabled false
  • TaskRecurrence (hebdomadaire lun/mer/ven) attachée à la tâche "Réunion de suivi hebdomadaire" (SIRH)

Delegation Codex

Pour les taches mecaniques (tests, boilerplate, renommages, refacto repetitif), delegue a Codex via le plugin codex. Garde Claude pour la reflexion, l'architecture et la verification.

  • Codex = junior dev rapide et pas cher (executions mecaniques)
  • Claude = senior dev qui verifie et reflechit (design, review, decisions)

C'est le meilleur ratio qualite/credits.