---
name: frontend-structure
description: React + Vite + Tailwind + SmartStack frontend structure (npm consumer of @atlashub/smartstack)
group: D
allowed-tools: [Read, Glob, Grep]
---

# Skill: Frontend SmartStack (React)

## Technical Stack
- React 19 + TypeScript 5.9
- Vite 7
- Tailwind CSS v4
- SmartStack npm: `@atlashub/smartstack`
- State/data: `@tanstack/react-query`
- Routing: `react-router-dom` (used by `DynamicRouter` from SmartStack)
- i18n: `react-i18next`

## Project Structure
```
web/{app-lower}-web/
├── src/
│   ├── main.tsx             # SmartStackProvider + imports every *Registry
│   ├── App.tsx              # DynamicRouter
│   ├── index.css            # Tailwind v4
│   ├── extensions/          # generated *Registry.ts files (PageRegistry.register)
│   │   ├── hrmRegistry.ts
│   │   └── catalogueRegistry.ts
│   ├── features/
│   │   └── {module}/{entity}/{pages,services,hooks,types}/
│   ├── components/
│   ├── services/
│   ├── hooks/
│   └── i18n/{fr,en,it,de}/{module}/{entity}.json
├── package.json
├── vite.config.ts
└── tsconfig.json
```

## Key Files

### `main.tsx` — entry point

All registries MUST be imported BEFORE `<App/>` renders so that
`PageRegistry.register()` calls execute before `DynamicRouter` tries to resolve
the first route. Missing imports = blank pages with no error.

```tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { SmartStackProvider } from '@atlashub/smartstack';
import App from './App';
import './index.css';

// ⚠ MANDATORY: import every generated Registry before rendering.
// scaffold-routes emits one file per module; list them all here.
import './extensions/hrmRegistry';
import './extensions/catalogueRegistry';
// ... one line per module

const config = {
  apiUrl: import.meta.env.VITE_API_URL ?? 'http://localhost:5142',
  appName: '{AppCode}',
};

createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <SmartStackProvider config={config}>
        <App />
      </SmartStackProvider>
    </BrowserRouter>
  </React.StrictMode>
);
```

### `App.tsx` — routing entry

Delegates everything to `DynamicRouter` from `@atlashub/smartstack`. No manual
`<Route>` declarations here — routes come from the menu API + PageRegistry.

```tsx
import { DynamicRouter } from '@atlashub/smartstack';

export default function App() {
  return <DynamicRouter />;
}
```

### `index.css` — Tailwind v4 + SmartStack styles

```css
@import "tailwindcss";
@source "node_modules/@atlashub/smartstack";
```

## Conventions
- One component per file
- Business hooks in `features/{module}/{entity}/hooks/`
- API clients in `features/{module}/{entity}/services/`
- `PermissionGuard` from `@atlashub/smartstack` to gate UI actions by permission
- One `*Registry.ts` per module — generated by `scaffold-routes` CLI
- i18n files per entity to avoid concurrent overwrites: `i18n/{locale}/{module}/{entity}.json`
- `useParams<{ id: string }>()` — always `:id`, never custom param names (DynamicRouter convention)

## Checklist before declaring the frontend working

1. `npm run dev` → Vite outputs `Local: http://localhost:3000/`
2. `curl http://localhost:3000/` → HTTP 200 with `<div id="root"></div>`
3. In browser DevTools console after login: `PageRegistry.list()` returns a non-empty array (if exposed)
4. Every module listed in the backend's `/api/navigation/menu` has a matching `*Registry.ts` import in `main.tsx`
5. No white-screen on any route — see `debug/frontend/SKILL.md` étape 7 if any

## Tests
- Vitest + Testing Library
- MSW for API mocks
- `renderWithProviders(ui)` helper: wraps with `QueryClientProvider` + `MemoryRouter` + `SmartStackProvider`
- One test file per page/hook: `tests/{module}/{entity}/{Entity}.test.tsx`
