# Surcharger une page CORE (HR, administration, support…)

SmartStack est un SDK : le package `@atlashub/smartstack` embarque des **pages
core** (module RH, administration, support, myspace…). Un client peut
**remplacer** l'une de ces pages par la sienne — sans forker le package.

> **Le point qui piège tout le monde : il existe DEUX mécanismes d'override,
> mutuellement exclusifs.** Une page métier NE se surcharge PAS comme la page
> login. Choisir le mauvais canal = l'override est silencieusement ignoré.

| Famille de page core | Résolue par | Override via | ❌ NE marche PAS via |
|---|---|---|---|
| **Pages métier / modules** (`hr.*`, `administration.*`, `support.*`, toute page issue de la nav DB) | `PageRegistry.resolve(componentKey)` → `DynamicRouter` (desktop = rendu direct) | **Ré-inscription du componentKey core exact dans `PageRegistry`** (last-write-wins) | `extensions.pages` (non consulté sur desktop) |
| **Pages publiques / auth** (`home`, `login`, `register`, `confirm-email`, `forgot-password`, `reset-password`, `force-change-password`, `auth.callback`, `auth.onboarding`) | Import statique + `withPageOverride` dans `DynamicRouter` | **`extensions.pages[PAGE_KEYS.X]`** (ExtensionConfig / `SmartStackProvider`) | ré-inscription `PageRegistry` (ignorée) |

Ce fichier couvre **la colonne du haut** — surcharger une page métier core comme
le RH. Pour login / home, voir les skills `login-config` et `site-vitrine`.

---

## Pourquoi la ré-inscription suffit — le contrat

`PageRegistry.register(key, component)` fait un `Map.set` : **ré-enregistrer une
clé existante remplace le composant précédent** (« last-write-wins »). Il n'y a
pas de priorité numérique ni de whitelist : n'importe quelle clé core est
surchargeable, *à condition que ta ré-inscription s'exécute après celle du core*.

C'est garanti par l'ordre d'import. Le `SmartStackProvider` du package importe le
registre core (`componentRegistry.generated`) **au chargement de son module**.
Donc tout `import './extensions/…Registry'` placé **après**
`import { SmartStackProvider } from '@atlashub/smartstack'` dans `main.tsx`
s'exécute plus tard → ta page gagne.

```
main.tsx (ordre d'exécution des side-effects) :
  1. import { SmartStackProvider } from '@atlashub/smartstack'
        └─ (interne au package) enregistre les pages CORE : hr.employees.list → CorePage
  2. import './extensions/monapp-overridesRegistry'
        └─ ré-enregistre hr.employees.list → MaPage   ← gagne (last-write-wins)
```

---

## Recette (4 étapes)

### 1. Récupérer le componentKey core EXACT

L'override ne fonctionne que si la clé est **identique au ComponentKey seedé**
côté nav DB (c'est cette clé que `DynamicRouter` résout pour la route). Sources,
par ordre de fiabilité :

- **`PAGE_KEYS`** exporté par le package (typo-safe) : `PAGE_KEYS.HR_EMPLOYEES_LIST`.
- **`GET /api/navigation/menu`** → champ `ComponentKey` de chaque nœud.
- **DevTools** après login : `PageRegistry.keys()` (si exposé) liste toutes les clés enregistrées.

Exemples de clés core RH : `hr.employees.list`, `hr.employees.org-chart`,
`hr.organization.departments`, `hr.absences.requests`, `hr.time.inbox`,
`hr.reporting.overview`.

> ⚠ Ne **jamais inventer** une clé (`hr.employees.list-custom`, …). Une clé
> nouvelle crée une route NOUVELLE, elle ne surcharge rien — la page core reste
> affichée. La clé doit être *octet pour octet* celle de la nav DB.

### 2. Écrire ta page de remplacement

Un composant React normal (mêmes `PageProps` que la page core). Respecte le même
gating de permission que l'original (`PermissionGuard` sur la même `{path}.*`),
sinon le comportement menu/route diverge de ce que la nav DB attend.

```tsx
// src/pages/monapp/hr/MyEmployeesListPage.tsx
export function MyEmployeesListPage() {
  return <div>{/* ma liste employés maison */}</div>;
}
```

### 3. Ré-enregistrer la clé core dans un registry d'overrides

```ts
// src/extensions/monapp-overridesRegistry.ts
import { PageRegistry, lazyWithRetry, PAGE_KEYS } from '@atlashub/smartstack';

const MyEmployeesListPage = lazyWithRetry(() =>
  import('@/pages/monapp/hr/MyEmployeesListPage').then((m) => ({
    default: m.MyEmployeesListPage,
  }))
);

// MÊME clé que le core → last-write-wins
PageRegistry.register(PAGE_KEYS.HR_EMPLOYEES_LIST, MyEmployeesListPage);
// (équivalent littéral : PageRegistry.register('hr.employees.list', MyEmployeesListPage);)
```

Utilise **toujours** `lazyWithRetry` (jamais `React.lazy` nu ni import statique) —
même règle que pour une page neuve (retry sur `ChunkLoadError`).

### 4. L'importer dans `main.tsx` APRÈS le provider

```tsx
import { SmartStackProvider } from '@atlashub/smartstack';  // enregistre le core
import './extensions/hrmRegistry';                          // registries « nouvelles pages »
import './extensions/monapp-overridesRegistry';             // ← overrides, APRÈS le provider

createRoot(...).render(/* … */);
```

L'ordre est **tout le mécanisme** : l'import d'overrides doit venir après
l'import de `@atlashub/smartstack`. Le `main.tsx` standard liste déjà les
registries après l'import du provider — ajoute simplement le registry d'overrides
à cette liste.

---

## Pièges

- **`extensions.pages[PAGE_KEYS.HR_…]` ne surcharge PAS une page métier sur
  desktop.** `extensions.pages` n'est lu que par `withPageOverride`, qui
  n'enveloppe que les 9 pages publiques/auth. Sur desktop une page métier est
  rendue par `LazyRouteComponent`, qui ne consulte jamais `usePageOverride`.
  → Pour une page métier, **PageRegistry uniquement**.

- **Le warning DEV est normal.** En dev, ré-enregistrer une clé déclenche
  `[PageRegistry] Key "hr.employees.list" is already registered — the previous
  component is replaced`. C'est **attendu** pour un override volontaire, pas un
  bug.

- **Variante mobile.** L'override desktop via `PageRegistry` ne couvre pas
  automatiquement la coque mobile si le core déclare une page mobile distincte.
  Pour surcharger la variante mobile, passe par `extensions.mobilePages[componentKey]`
  (ExtensionConfig), pas par `PageRegistry`.

- **Format de clé inchangé.** `validateComponentKey` s'applique toujours
  (kebab-case, 2–5 segments). Une clé core respecte déjà ce contrat — tu la
  recopies, tu ne la réécris pas.

- **Rester un override, pas un fork.** Tu remplaces le composant rendu, pas la
  route ni la permission : le nœud de nav, le `{path}.access` et l'URL restent
  ceux du core. Ne touche ni au seed nav ni aux permissions pour un simple
  override de page.
