# Overriding a CORE page (HR, administration, support…)

SmartStack is an SDK: the `@atlashub/smartstack` package ships **core pages**
(the HR module, administration, support, myspace…). A client can **replace**
one of those pages with its own — without forking the package.

> **The point that traps everyone: there are TWO override mechanisms, and they
> are mutually exclusive.** A business page is NOT overridden the way the login
> page is. Pick the wrong channel and the override is silently ignored.

| Core page family | Resolved by | Override through | ❌ Does NOT work through |
|---|---|---|---|
| **Business / module pages** (`hr.*`, `administration.*`, `support.*`, every page born from the nav DB) | `PageRegistry.resolve(componentKey)` → `DynamicRouter` (desktop = rendered directly) | **Re-registering the EXACT core componentKey in `PageRegistry`** (last-write-wins) | `extensions.pages` (never consulted on desktop) |
| **Public / auth pages** (`home`, `login`, `register`, `confirm-email`, `forgot-password`, `reset-password`, `force-change-password`, `auth.callback`, `auth.onboarding`) | Static import + `withPageOverride` inside `DynamicRouter` | **`extensions.pages[PAGE_KEYS.X]`** (ExtensionConfig / `SmartStackProvider`) | re-registering in `PageRegistry` (ignored) |

This file covers **the top row** — overriding a core business page such as HR.
For login / home, see the `login-config` and `site-vitrine` skills.

---

## Why re-registering is enough — the contract

`PageRegistry.register(key, component)` is a `Map.set`: **re-registering an
existing key replaces the previous component** ("last-write-wins"). There is no
numeric priority and no whitelist: any core key can be overridden, *provided
your re-registration runs after the core's*.

The import order guarantees it. The package's `SmartStackProvider` imports the
core registry (`componentRegistry.generated`) **when its module loads**. So any
`import './extensions/…Registry'` placed **after**
`import { SmartStackProvider } from '@atlashub/smartstack'` in `main.tsx` runs
later → your page wins.

```
main.tsx (execution order of the side effects):
  1. import { SmartStackProvider } from '@atlashub/smartstack'
        └─ (inside the package) registers the CORE pages: hr.employees.list → CorePage
  2. import './extensions/myapp-overridesRegistry'
        └─ re-registers hr.employees.list → MyPage   ← wins (last-write-wins)
```

---

## Recipe (4 steps)

### 1. Get the EXACT core componentKey

The override only works when the key is **identical to the ComponentKey seeded**
in the nav DB (that key is what `DynamicRouter` resolves for the route).
Sources, by reliability:

- **`PAGE_KEYS`** exported by the package (typo-safe): `PAGE_KEYS.HR_EMPLOYEES_LIST`.
- **`GET /api/navigation/menu`** → the `ComponentKey` field of each node.
- **DevTools** after login: `PageRegistry.keys()` (when exposed) lists every registered key.

Examples of core HR keys: `hr.employees.list`, `hr.employees.org-chart`,
`hr.organization.departments`, `hr.absences.requests`, `hr.time.inbox`,
`hr.reporting.overview`.

> ⚠ **Never invent** a key (`hr.employees.list-custom`, …). A new key creates a
> NEW route, it overrides nothing — the core page stays on screen. The key must
> be *byte for byte* the one in the nav DB.

### 2. Write your replacement page

A normal React component (same `PageProps` as the core page). Keep the same
permission gating as the original (`PermissionGuard` on the same `{path}.*`),
otherwise the menu / route behaviour diverges from what the nav DB expects.

```tsx
// src/pages/myapp/hr/MyEmployeesListPage.tsx
export function MyEmployeesListPage() {
  return <div>{/* my own employee list */}</div>;
}
```

### 3. Re-register the core key in an overrides registry

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

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

// SAME key as the core → last-write-wins
PageRegistry.register(PAGE_KEYS.HR_EMPLOYEES_LIST, MyEmployeesListPage);
// (literal equivalent: PageRegistry.register('hr.employees.list', MyEmployeesListPage);)
```

**Always** use `lazyWithRetry` (never a bare `React.lazy` nor a static import) —
the same rule as for a new page (retry on `ChunkLoadError`).

### 4. Import it in `main.tsx` AFTER the provider

```tsx
import { SmartStackProvider } from '@atlashub/smartstack';  // registers the core
import './extensions/hrmRegistry';                          // "new pages" registries
import './extensions/myapp-overridesRegistry';              // ← overrides, AFTER the provider

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

The order IS the whole mechanism: the overrides import must come after the
`@atlashub/smartstack` import. The standard `main.tsx` already lists the
registries after the provider import — just add the overrides registry to that
list.

---

## Pitfalls

- **`extensions.pages[PAGE_KEYS.HR_…]` does NOT override a business page on
  desktop.** `extensions.pages` is read only by `withPageOverride`, which wraps
  only the 9 public / auth pages. On desktop a business page is rendered by
  `LazyRouteComponent`, which never consults `usePageOverride`.
  → For a business page, **PageRegistry only**.

- **The DEV warning is normal.** In dev, re-registering a key logs
  `[PageRegistry] Key "hr.employees.list" is already registered — the previous
  component is replaced`. That is **expected** for a deliberate override, not a
  bug.

- **Mobile variant.** The desktop override through `PageRegistry` does not
  automatically cover the mobile shell when the core declares a distinct mobile
  page. To override the mobile variant, go through
  `extensions.mobilePages[componentKey]` (ExtensionConfig), not `PageRegistry`.

- **Key format unchanged.** `validateComponentKey` still applies (kebab-case,
  2–5 segments). A core key already honours that contract — you copy it, you
  never rewrite it.

- **Stay an override, not a fork.** You replace the rendered component, not the
  route nor the permission: the nav node, the `{path}.access` and the URL stay
  the core's. Touch neither the nav seed nor the permissions for a plain page
  override.
