---
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`.
