Files
Matthieu 93cbd48bf5
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
chore : rename Coltura to Starseed
- Rename project name across code, configs, docs, dev/prod infra
- Dev: DOCKER_APP_NAME + POSTGRES_DB switched to starseed, containers become php-starseed-fpm / nginx-starseed / starseed-db-1
- Dev: mount nginx.conf on default.conf instead of starseed.conf to avoid alphabetical-order clash with image's default site
- Makefile: export CURRENT_UID/CURRENT_GID at top level so docker compose builds (db-reset etc.) get them
- Prod: image registry path, container_name, volumes, vhost server_name + paths, DATABASE_URL, CORS, CI workflow
- Add doc/prompt-rename-prod.md with the migration runbook for the prod server (DB rename, FS move, vhost, Let's Encrypt)
2026-05-19 08:24:19 +02:00

209 lines
9.1 KiB
Markdown

---
name: create-module
description: Scaffold a new Starseed 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 Starseed module
Scaffolds a new module across backend and frontend following Starseed'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
<?php
declare(strict_types=1);
namespace App\Module\{Pascal};
final class {Pascal}Module
{
public const string ID = '{id}';
public const string LABEL = '{label}';
public const bool REQUIRED = false;
}
```
**2. Edit `config/modules.php`** — append the module class:
```php
return [
\App\Module\Core\CoreModule::class,
// ... existing modules
\App\Module\{Pascal}\{Pascal}Module::class,
];
```
### Backend — sidebar config (only if the user wants sidebar entries)
**3. Edit `config/sidebar.php`** — either add items to an existing section or append a new section:
```php
[
'label' => '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
<template>
<div>
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('{camel}.title') }}</h1>
<p class="mt-4 text-neutral-500">{{ $t('{camel}.welcome') }}</p>
</div>
</template>
<script setup lang="ts">
const { t } = useI18n()
useHead({ title: t('{camel}.title') })
</script>
```
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-starseed-fpm chown -R www-data:www-data /var/www/html/var` (avoid permission issues)
- `docker exec -t -u www-data php-starseed-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`.