--- name: create-module description: Scaffold a new Coltura module (backend + frontend) and optionally wire its entries into the sidebar config. Use when the user asks to create, add, scaffold, or generate a new module — e.g., "crée un module Paie", "add a Pointage module", "ajoute un module RH". The backend is the source of truth for activation and sidebar layout; the frontend scans modules automatically. --- # Create a new Coltura module Scaffolds a new module across backend and frontend following Coltura's modular monolith DDD architecture. ## Architecture reminder — read before acting The module system has **two concerns that are decoupled**: 1. **Module** = code + routes + pages. A module is a bounded context that owns feature code. Declared in `config/modules.php` (backend) and scanned automatically on frontend via `nuxt.config.ts` (any directory under `frontend/modules/` becomes a Nuxt layer). 2. **Sidebar** = navigation layout. Defined in `config/sidebar.php`. Each sidebar item references the module that owns it via the `module` key. Items whose module is not active are filtered out by the backend. **You can freely move a sidebar item from one section to another without touching module code.** Consequences: - Frontend never hardcodes module metadata — no `.module.ts`, no `modules-loader.ts`, no manual registration. - Frontend never hardcodes the sidebar — it fetches `GET /api/sidebar`. - To add a new module in the sidebar: edit `config/sidebar.php`. - To move an item between sections: edit `config/sidebar.php`. - To disable a module: remove/comment it from `config/modules.php`. Its sidebar items are auto-filtered AND its routes land in `disabledRoutes`, which the frontend middleware `modules.global.ts` uses to redirect any direct URL access back to `/`. The `/api/sidebar` response shape is: ```json { "sections": [{ "label": "...", "icon": "...", "items": [...] }], "disabledRoutes": ["/commercial", "/commercial/orders"] } ``` Any item whose `module` key matches an inactive module is moved from `sections` into `disabledRoutes` automatically by `SidebarProvider`. ## When to use User wants to create a new vendable module. Example triggers: - "Crée un module Paie" - "Ajoute un module Pointage" - "Scaffold a new Stock module" ## Prerequisites — gather before acting Ask the user (via `AskUserQuestion` if not already provided): 1. **Module name** (French, human readable, e.g., "Paie", "Gestion RH", "Pointage"). Derive: - `PascalCase` for backend folders/classes: `Paie`, `GestionRh`, `Pointage` - `kebab-case` for frontend folders/routes: `paie`, `gestion-rh`, `pointage` - `camelCase` for i18n keys: `paie`, `gestionRh`, `pointage` - `snake_case` for module ID: `paie`, `gestion_rh`, `pointage` 2. **Icon** — an mdi icon identifier for the sidebar section (e.g., `mdi:cash-multiple`). If not given, suggest a relevant one. 3. **Should it appear in the sidebar immediately?** If yes, ask which section (new one or existing) and what nav items (label + route). Don't guess these — ask. ## Placeholders used below - `{Pascal}` = PascalCase (e.g., `Paie`) - `{kebab}` = kebab-case (e.g., `paie`) - `{camel}` = camelCase i18n slug (e.g., `paie`, `gestionRh`) - `{id}` = module ID for backend + config (same as `{camel}` for single-word names, `snake_case` otherwise) - `{label}` = French display label (e.g., `Paie`) - `{icon}` = mdi icon string (e.g., `mdi:cash-multiple`) ## Files to create/edit ### Backend — module declaration **1. `src/Module/{Pascal}/{Pascal}Module.php`** ```php 'sidebar.{camel}.section', 'icon' => '{icon}', 'items' => [ [ 'label' => 'sidebar.{camel}.overview', 'to' => '/{kebab}', 'icon' => '{icon}', 'module' => '{id}', ], // more items from user input... ], ], ``` Every item must have a `module` key matching an active module ID — otherwise the backend will filter it out. ### Frontend — module code (auto-detected) **4a. `frontend/modules/{kebab}/nuxt.config.ts`** — required for Nuxt to treat the folder as a layer. Content: ```typescript export default defineNuxtConfig({}) ``` **4b. `frontend/modules/{kebab}/pages/{kebab}.vue`** — placeholder page. The filename becomes the route `/{kebab}`. ```vue ``` Create one page file per nav item the user specified. Derive filenames from the `to` paths (e.g., `/paie/bulletins` → `pages/paie/bulletins.vue`). **Do NOT create any of these — they no longer exist in this architecture:** - ❌ `frontend/modules/{kebab}/{kebab}.module.ts` - ❌ `frontend/plugins/modules-loader.ts` - ❌ Any `extends` entry in `nuxt.config.ts` — modules are auto-detected from `frontend/modules/*/`. ### Frontend — translations **5. Edit `frontend/i18n/locales/fr.json`** — add the sidebar keys and page content keys: ```json { "sidebar": { "{camel}": { "section": "{label}", "overview": "Vue d'ensemble" } }, "{camel}": { "title": "{label}", "welcome": "Module {label}" } } ``` Every `label` in the new `config/sidebar.php` entries must have a matching key here, and every page's `$t('{camel}.*')` calls must match too. ## Implementation steps Execute in this exact order: 1. **Clarify inputs** — use `AskUserQuestion` for name, icon, and sidebar decisions. 2. **Derive naming** — confirm Pascal/kebab/camel/id are sensible (e.g., "Gestion RH" → `GestionRh`/`gestion-rh`/`gestionRh`/`gestion_rh`). 3. **Read current state** — Read `config/modules.php`, `config/sidebar.php`, `frontend/i18n/locales/fr.json` so you know what exists before editing. 4. **Backend: declare the module** — create `{Pascal}Module.php`, edit `config/modules.php`. 5. **Frontend: create pages** — create the placeholder `.vue` files under `frontend/modules/{kebab}/pages/`. 6. **Backend: sidebar** — if the user wants sidebar entries, edit `config/sidebar.php`. 7. **Frontend: translations** — edit `frontend/i18n/locales/fr.json`. 8. **Verify** — run: - `docker exec -t -u root php-coltura-fpm chown -R www-data:www-data /var/www/html/var` (avoid permission issues) - `docker exec -t -u www-data php-coltura-fpm php bin/console cache:clear` (validates backend) - `cd frontend && npx nuxi prepare` (validates Nuxt auto-detection of the new layer) 9. **Report** — list files created, the route(s) to test, and the sidebar items added. ## Rules — do not violate - **Never create `.module.ts` files** — the old `ModuleDefinition` / `registerModule` pattern is gone. Activation and sidebar are 100% backend-driven. - **Never edit `extends` in `nuxt.config.ts`** — it auto-scans `frontend/modules/*/`. Adding entries manually causes duplicates. - **Never create `frontend/plugins/modules-loader.ts`** — it was deleted on purpose. - **All sidebar labels are i18n keys, not raw text** — the layout calls `t()` on every label. Raw text will display as-is and look broken. - **Every sidebar item needs a `module` key** — if omitted, the backend filters it out silently. - **Backend module must be in `config/modules.php`** — otherwise `/api/sidebar` will hide its items even though the sidebar config references them. - **No DDD scaffolding unless asked** — only create the `{Pascal}Module.php` initially. Don't create empty `Domain/`, `Application/`, `Infrastructure/` folders. Real domain code comes when the module gets features. - **Don't create a page per nav item blindly** — if the user didn't specify, create only the root `/{kebab}` page and ask whether they want stubs for subpages. - **Don't use `make cache-clear`** — it may hit permission issues. Use the docker commands directly (see Verify step). ## Naming derivation examples | User input | Pascal | kebab | camel | id | |------------|--------|-------|-------|-----| | Paie | Paie | paie | paie | paie | | Pointage | Pointage | pointage | pointage | pointage | | Gestion RH | GestionRh | gestion-rh | gestionRh | gestion_rh | | Stock & Inventaire | StockInventaire | stock-inventaire | stockInventaire | stock_inventaire | | CRM | Crm | crm | crm | crm | For accented characters (é, è, à, ç...): strip accents in folder/identifier names (`Forêt` → `Foret`/`foret`), keep them in the French `label`.