@atlashub/smartstack-cli 4.33.0 → 4.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,339 @@
1
+ # {{ProjectName}} Web - React Frontend Memory
2
+
3
+ ## Purpose
4
+
5
+ React SPA frontend. Communicates with {{ProjectName}}.Api via REST + SignalR.
6
+
7
+ ## Tech Stack
8
+
9
+ | Technology | Version | Purpose |
10
+ |------------|---------|---------|
11
+ | React | ^18 / ^19 | UI library |
12
+ | TypeScript | ~5.9.3 | Type safety (strict mode) |
13
+ | Vite | ^7.2 | Build tool + dev server |
14
+ | React Router | ^6 / ^7 | Routing (lazy-loaded) |
15
+ | Context API | - | Client + server state |
16
+ | Axios | ^1.13 | HTTP client |
17
+ | Tailwind CSS | ^4.1 | Styling |
18
+ | i18next | ^25.7 | Internationalization |
19
+ | SignalR | ^10.0 | Real-time updates |
20
+ | Lucide React | ^0.562 | Icons |
21
+
22
+ ---
23
+
24
+ ## CRITICAL: Internationalization (i18n) - 4 Languages Required
25
+
26
+ ### Rules
27
+
28
+ - **CRITICAL**: Every page MUST use translations via `useTranslation()` hook
29
+ - **CRITICAL**: All user-visible text MUST be in translation files (fr, en, it, de)
30
+ - **NEVER** hardcode text strings in components
31
+ - **ALWAYS** add translations to ALL 4 locales: `fr/`, `en/`, `it/`, `de/`
32
+
33
+ ### Supported Languages
34
+
35
+ | Language | Code | Folder |
36
+ |----------|------|--------|
37
+ | French | `fr` | `locales/fr/` |
38
+ | English | `en` | `locales/en/` |
39
+ | Italian | `it` | `locales/it/` |
40
+ | German | `de` | `locales/de/` |
41
+
42
+ ### Structure
43
+
44
+ ```
45
+ src/i18n/
46
+ ├── config.ts → i18next configuration
47
+ └── locales/
48
+ ├── en/
49
+ │ ├── common.json → Common UI (buttons, labels, errors)
50
+ │ ├── navigation.json → Menu and navigation
51
+ │ └── {feature}.json → Feature-specific translations
52
+ ├── fr/
53
+ │ └── ... → Same structure as en/
54
+ ├── it/
55
+ │ └── ... → Same structure as en/
56
+ └── de/
57
+ └── ... → Same structure as en/
58
+ ```
59
+
60
+ ### Usage in Pages
61
+
62
+ ```tsx
63
+ import { useTranslation } from 'react-i18next';
64
+
65
+ export function ExamplePage() {
66
+ const { t } = useTranslation('feature'); // Specify namespace
67
+
68
+ return (
69
+ <div>
70
+ <h1>{t('pageTitle')}</h1>
71
+ <p>{t('description')}</p>
72
+ <button>{t('common:save')}</button> {/* Cross-namespace */}
73
+ </div>
74
+ );
75
+ }
76
+ ```
77
+
78
+ ### Adding New Translations
79
+
80
+ **ALWAYS add to ALL 4 languages** (en, fr, it, de) with the same structure.
81
+
82
+ ### Translation Keys Convention
83
+
84
+ - Use **dot notation** for nested keys: `orders.pageTitle`
85
+ - Use **camelCase** for key names
86
+ - Group by feature/section: `orders.table.headerName`
87
+ - Common actions in `common` namespace: `common:save`, `common:cancel`
88
+
89
+ ---
90
+
91
+ ## Structure
92
+
93
+ ```
94
+ {{ProjectNameLower}}-web/
95
+ ├── src/
96
+ │ ├── main.tsx → Entry point
97
+ │ ├── App.tsx → Root component + providers + DynamicRouter
98
+ │ ├── i18n/ → Internationalization
99
+ │ │ ├── config.ts → i18next setup
100
+ │ │ └── locales/ → Translation files (fr/en/it/de)
101
+ │ ├── services/ → API client layer
102
+ │ │ └── api/
103
+ │ │ ├── apiClient.ts → Axios instance + interceptors
104
+ │ │ └── {feature}Api.ts → Feature-specific API modules
105
+ │ ├── components/ → Shared & domain components
106
+ │ │ ├── ui/ → Base UI components (DataTable, Modal, etc.)
107
+ │ │ ├── layout/ → Layout components
108
+ │ │ ├── routing/ → Route guards + dynamic routing
109
+ │ │ └── {feature}/ → Feature-specific components
110
+ │ ├── pages/ → Page components
111
+ │ │ └── {application}/{module}/ → Organized by navigation hierarchy
112
+ │ ├── contexts/ → React Context providers
113
+ │ ├── hooks/ → Custom hooks
114
+ │ ├── layouts/ → Layout wrappers
115
+ │ ├── utils/ → Utility functions
116
+ │ ├── types/ → Global types
117
+ │ ├── routes/ → Route definitions
118
+ │ └── extensions/ → Extension system (PageRegistry)
119
+ ├── tests/ → Vitest tests (NOT in src/)
120
+ │ ├── utils/ → Pure utility tests
121
+ │ ├── components/ → Component tests
122
+ │ ├── hooks/ → Hook tests
123
+ │ └── services/ → Service tests
124
+ ├── vite.config.ts
125
+ ├── vitest.config.ts
126
+ ├── tsconfig.json
127
+ └── eslint.config.js
128
+ ```
129
+
130
+ ## Patterns
131
+
132
+ ### Page Template with i18n
133
+
134
+ ```tsx
135
+ // src/pages/{module}/{PageName}Page.tsx
136
+ import { useTranslation } from 'react-i18next';
137
+
138
+ export function ExamplePage() {
139
+ const { t } = useTranslation('feature');
140
+
141
+ return (
142
+ <div className="container mx-auto p-4">
143
+ <h1 className="text-2xl font-bold">{t('example.pageTitle')}</h1>
144
+ <p className="text-muted-foreground">{t('example.description')}</p>
145
+
146
+ <div className="mt-4 flex gap-2">
147
+ <button className="btn-primary">{t('common:save')}</button>
148
+ <button className="btn-secondary">{t('common:cancel')}</button>
149
+ </div>
150
+ </div>
151
+ );
152
+ }
153
+ ```
154
+
155
+ ### API Client
156
+
157
+ ```typescript
158
+ // src/services/api/apiClient.ts — uses Axios instance
159
+ import { api } from '@/services/api/apiClient';
160
+
161
+ // Request interceptors automatically add:
162
+ // - Authorization: Bearer {token}
163
+ // - X-Tenant-Slug: {currentTenantSlug}
164
+ // - Accept-Language: {i18nextLng}
165
+ // - X-Correlation-ID: {UUID}
166
+
167
+ // Usage in service files:
168
+ const orders = await api.get<Order[]>('/orders');
169
+ const order = await api.post<Order>('/orders', { name, amount });
170
+ ```
171
+
172
+ ### API Service File Pattern
173
+
174
+ ```typescript
175
+ // src/services/api/{feature}Api.ts
176
+ import { api } from './apiClient';
177
+
178
+ export interface OrderDto {
179
+ id: string;
180
+ name: string;
181
+ amount: number;
182
+ isActive: boolean;
183
+ }
184
+
185
+ export const orderApi = {
186
+ getAll: (search?: string) =>
187
+ api.get<OrderDto[]>('/orders', { params: { search } }),
188
+
189
+ getById: (id: string) =>
190
+ api.get<OrderDto>(`/orders/${id}`),
191
+
192
+ create: (data: Partial<OrderDto>) =>
193
+ api.post<OrderDto>('/orders', data),
194
+
195
+ update: (id: string, data: Partial<OrderDto>) =>
196
+ api.put<OrderDto>(`/orders/${id}`, data),
197
+
198
+ delete: (id: string) =>
199
+ api.delete(`/orders/${id}`),
200
+ };
201
+ ```
202
+
203
+ ### Custom Hook Pattern
204
+
205
+ ```typescript
206
+ // src/hooks/useOrderDetail.ts
207
+ import { useState, useCallback } from 'react';
208
+ import { orderApi, OrderDto } from '@/services/api/orderApi';
209
+
210
+ export function useOrderDetail(id: string) {
211
+ const [data, setData] = useState<OrderDto | null>(null);
212
+ const [loading, setLoading] = useState(false);
213
+ const [error, setError] = useState<string | null>(null);
214
+
215
+ const load = useCallback(async () => {
216
+ setLoading(true);
217
+ setError(null);
218
+ try {
219
+ const result = await orderApi.getById(id);
220
+ setData(result);
221
+ } catch (err: unknown) {
222
+ setError(err instanceof Error ? err.message : 'Unknown error');
223
+ } finally {
224
+ setLoading(false);
225
+ }
226
+ }, [id]);
227
+
228
+ return { data, loading, error, load };
229
+ }
230
+ ```
231
+
232
+ ### Context Provider Pattern
233
+
234
+ ```typescript
235
+ // src/contexts/ExampleContext.tsx
236
+ import { createContext, useContext, useState, type ReactNode } from 'react';
237
+
238
+ interface ExampleContextType {
239
+ value: string;
240
+ setValue: (v: string) => void;
241
+ }
242
+
243
+ const ExampleContext = createContext<ExampleContextType | null>(null);
244
+
245
+ export function ExampleProvider({ children }: { children: ReactNode }) {
246
+ const [value, setValue] = useState('');
247
+ return (
248
+ <ExampleContext.Provider value={{ value, setValue }}>
249
+ {children}
250
+ </ExampleContext.Provider>
251
+ );
252
+ }
253
+
254
+ export function useExample() {
255
+ const ctx = useContext(ExampleContext);
256
+ if (!ctx) throw new Error('useExample must be used within ExampleProvider');
257
+ return ctx;
258
+ }
259
+ ```
260
+
261
+ ## UI Styling Patterns
262
+
263
+ ### Semantic Color Usage
264
+
265
+ | Color | CSS Variables | Usage |
266
+ |-------|---------------|-------|
267
+ | `blue` | `--info-*` | Totals, informational, in progress |
268
+ | `green` | `--success-*` | Completed, resolved, active |
269
+ | `yellow` | `--warning-*` | Pending, on hold, warnings |
270
+ | `red` | `--error-*` | Errors, critical, failed |
271
+ | `neutral` | `--bg-secondary`, `--text-primary` | Inactive, closed, disabled |
272
+
273
+ ---
274
+
275
+ ## Commands
276
+
277
+ ```bash
278
+ # Development
279
+ npm run dev # Start dev server (port 6173)
280
+
281
+ # Build
282
+ npm run build # Production build
283
+
284
+ # Tests (MANDATORY before commit)
285
+ npm test # Run all tests
286
+ npm run test:watch # Watch mode
287
+ npm run test:coverage # Coverage report
288
+
289
+ # Lint
290
+ npm run lint # ESLint check
291
+ npm run typecheck # TypeScript type check
292
+ ```
293
+
294
+ ## Routing
295
+
296
+ ### Lazy Loading
297
+
298
+ All non-critical pages use `React.lazy()` for code splitting:
299
+
300
+ ```tsx
301
+ const OrdersPage = lazy(() =>
302
+ import('@/pages/{module}/OrdersPage').then(m => ({ default: m.OrdersPage }))
303
+ );
304
+ ```
305
+
306
+ ### Route Guards (Layered)
307
+
308
+ ```
309
+ Auth check → Tenant transition → Permission check → Render
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Rules
315
+
316
+ 1. **CRITICAL: i18n Required** - ALL pages must use `useTranslation()` with all 4 language files (fr/en/it/de)
317
+ 2. **Pages in `pages/`**, components in `components/`** - organized by application/module
318
+ 3. **Custom hooks for logic** - extract API calls and state management from components into `hooks/`
319
+ 4. **Context API for global state** - use `contexts/` for auth, tenant, navigation, theme, etc.
320
+ 5. **Axios API client** - all HTTP calls via `services/api/apiClient.ts`, never raw `fetch()`
321
+ 6. **TypeScript strict mode** - no `any` types
322
+ 7. **Tailwind for styling** - no CSS files, no inline styles (`style={{}}`)
323
+ 8. **Functional components only** - no class components
324
+ 9. **Composition over inheritance** - use props and children
325
+ 10. **Tests in `tests/`** - NOT in `src/`, use Vitest + Testing Library
326
+
327
+ ## CRITICAL: Tabs Must Persist in URL
328
+
329
+ When creating a page with tabs, **ALWAYS** persist the active tab in the URL query params (`?tab=xxx`).
330
+ Use the `useTabNavigation` hook.
331
+
332
+ ## When Adding New Feature
333
+
334
+ 1. Create page in `pages/{application}/{module}/`
335
+ 2. Create components in `components/{feature}/`
336
+ 3. Create API service in `services/api/{feature}Api.ts`
337
+ 4. Create custom hooks in `hooks/use{Feature}.ts`
338
+ 5. Register page via `PageRegistry.register()`
339
+ 6. **CRITICAL**: Add translations to ALL 4 locales: `i18n/locales/{fr,en,it,de}/`
@@ -2,8 +2,8 @@
2
2
 
3
3
  > These templates generate React/TypeScript code for new applications/modules.
4
4
 
5
- > **DEPRECATED (v3.7+):** Pattern A (mergeRoutes) and Pattern B (JSX Routes) below
6
- > are replaced by PageRegistry.register() + DynamicRouter.
5
+ > **v3.7+:** Pattern A (mergeRoutes) and Pattern B (JSX Routes) were removed.
6
+ > The only supported routing pattern is PageRegistry.register() + DynamicRouter.
7
7
  > See references/frontend-route-wiring-app-tsx.md for the current pattern.
8
8
 
9
9
  ---
@@ -507,117 +507,86 @@ export const $moduleApi = {
507
507
 
508
508
  ---
509
509
 
510
- ## TEMPLATE: ROUTES (App.tsx)
510
+ ## TEMPLATE: ROUTES (PageRegistry + DynamicRouter)
511
511
 
512
- ### Detect App.tsx Routing Pattern FIRST
512
+ ### Default Pattern (v3.7+): PageRegistry + DynamicRouter
513
513
 
514
- Before adding routes, **read App.tsx** and detect which pattern is used:
514
+ > **This is the ONLY supported pattern** since v3.7.
515
+ > Pages are registered in `componentRegistry.generated.ts` via `PageRegistry.register()`.
516
+ > DynamicRouter resolves routes automatically from the navigation API + PageRegistry.
517
+ > **No App.tsx wiring needed.** No manual route duplication for tenant prefixes.
515
518
 
516
- | Pattern | How to detect | Action |
517
- |---------|---------------|--------|
518
- | **Pattern A** (mergeRoutes) | `applicationRoutes: ApplicationRouteExtensions` present | Add to `applicationRoutes.{application}[]` array |
519
- | **Pattern B** (JSX Routes) | `<Route path="/{application}" element={<{Layout} />}>` present | Insert `<Route>` children inside Layout wrapper |
519
+ **Steps:**
520
520
 
521
- ### Pattern A: mergeRoutes (applicationRoutes array)
522
-
523
- > **This is the DEFAULT pattern** generated by `smartstack init`.
524
- > Routes added to `applicationRoutes` are automatically injected into BOTH standard and tenant-prefixed route trees by `mergeRoutes()`. No manual duplication needed.
521
+ 1. Create your page component(s)
522
+ 2. Register in `componentRegistry.generated.ts` (or run `scaffold_routes outputFormat="componentRegistry"`)
523
+ 3. Ensure navigation seed data has matching `ComponentKey` values
524
+ 4. DynamicRouter does the rest
525
525
 
526
526
  ```tsx
527
- // Add to App.tsx imports at top
528
- import { $MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALPage';
529
- import { $MODULE_PASCALDetailPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
530
- import { Create$MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
531
-
532
- // Add routes to applicationRoutes.{application}[] with RELATIVE paths (no leading /)
533
- const applicationRoutes: ApplicationRouteExtensions = {
534
- '$APPLICATION_KEBAB': [
535
- // ... existing routes ...
536
- { path: '$MODULE_KEBAB', element: <$MODULE_PASCALPage /> },
537
- { path: '$MODULE_KEBAB/create', element: <Create$MODULE_PASCALPage /> },
538
- { path: '$MODULE_KEBAB/:id', element: <$MODULE_PASCALDetailPage /> },
539
- { path: '$MODULE_KEBAB/:id/edit', element: <Create$MODULE_PASCALPage /> },
540
- ],
541
- };
527
+ // 1. Create page component
528
+ // pages/$APPLICATION/$MODULE/$MODULE_PASCALPage.tsx
529
+ export function $MODULE_PASCALPage() { /* ... */ }
530
+
531
+ // 2. Register in componentRegistry.generated.ts
532
+ import { PageRegistry } from '@/extensions/PageRegistry';
533
+
534
+ PageRegistry.register(
535
+ '$APPLICATION_KEBAB.$MODULE_KEBAB',
536
+ lazy(() => import('@/pages/$APPLICATION/$MODULE/$MODULE_PASCALPage')
537
+ .then(m => ({ default: m.$MODULE_PASCALPage })))
538
+ );
539
+
540
+ // For CRUD sub-pages (detail, create):
541
+ PageRegistry.register(
542
+ '$APPLICATION_KEBAB.$MODULE_KEBAB.detail',
543
+ lazy(() => import('@/pages/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage')
544
+ .then(m => ({ default: m.$MODULE_PASCALDetailPage })))
545
+ );
546
+
547
+ PageRegistry.register(
548
+ '$APPLICATION_KEBAB.$MODULE_KEBAB.create',
549
+ lazy(() => import('@/pages/$APPLICATION/$MODULE/Create$MODULE_PASCALPage')
550
+ .then(m => ({ default: m.Create$MODULE_PASCALPage })))
551
+ );
542
552
  ```
543
553
 
544
- **mergeRoutes auto-generates redirects** for intermediate paths (e.g., `$APPLICATION` → `$APPLICATION/$DEFAULT_MODULE_KEBAB`) so you don't need to add index redirects manually.
545
-
546
- **Application-to-Layout mapping (automatic via mergeRoutes):**
547
-
548
- | Application key | Injected into Layout | Standard path | Tenant path |
549
- |-----------------|---------------------|---------------|-------------|
550
- | `administration` | `AppLayout` | `/administration/...` | `/t/:slug/administration/...` |
551
- | `{application}` | `AppLayout` | `/{application}/...` | `/t/:slug/{application}/...` |
552
- | `myspace` | `AppLayout` | `/myspace/...` | `/t/:slug/myspace/...` |
553
-
554
- ### Pattern B: JSX Routes (inside Layout wrapper)
555
-
556
- > **Legacy pattern** — only used if App.tsx was manually restructured with JSX `<Route>` elements.
557
-
558
- The unified `AppLayout` provides the application shell: **header with AvatarMenu**, sidebar, navigation. It renders child pages via React Router's `<Outlet />`.
554
+ ```sql
555
+ -- 3. Navigation seed data (ComponentKey must match PageRegistry key)
556
+ INSERT INTO core.nav_Sections (...)
557
+ VALUES (..., ComponentKey = '$APPLICATION_KEBAB.$MODULE_KEBAB', ...);
558
+ ```
559
559
 
560
- **If routes are placed OUTSIDE the layout wrapper, the shell (header, sidebar, AvatarMenu) will NOT render. The page appears "naked" without any navigation.**
560
+ ### How DynamicRouter resolves routes
561
561
 
562
- **Step-by-step insertion:**
562
+ 1. Fetches menu from `GET /api/navigation/menu`
563
+ 2. For each Application → Module → Section → Resource, builds `<Route>` elements
564
+ 3. Looks up each item's `ComponentKey` in `PageRegistry` to get the React component
565
+ 4. Wraps routes in `<ProtectedRoute>` (API-driven permission check via `isOpen` flag)
566
+ 5. Generates both standard (`/$APPLICATION/$MODULE`) and tenant-prefixed (`/t/:slug/$APPLICATION/$MODULE`) routes automatically
563
567
 
564
- 1. Open `App.tsx`
565
- 2. Find the existing layout route for the target application:
566
- - `administration` → `<Route path="/administration" element={<AppLayout />}>`
567
- - `{application}` → `<Route path="/{application}" element={<AppLayout />}>`
568
- - `myspace` → `<Route path="/myspace" element={<AppLayout />}>`
569
- 3. Add the new routes **INSIDE** that `<Route>` block
570
- 4. If a tenant-prefixed block exists (`/t/:slug/...`), add the routes there too
568
+ ### ⚠️ CRITICAL RULES
571
569
 
570
+ **FORBIDDEN — Flat routes outside DynamicRouter:**
572
571
  ```tsx
573
- // Add to App.tsx
574
-
575
- import { $MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALPage';
576
- import { $MODULE_PASCALDetailPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
577
- import { Create$MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
578
-
579
- // Find the EXISTING layout route and add routes INSIDE it:
580
- <Route path="/$APPLICATION" element={<$APPLICATION_Layout />}>
581
- {/* ... existing routes stay here ... */}
582
-
583
- {/* NEW: $MODULE routes - added as children of the layout */}
584
- <Route path="$MODULE_KEBAB">
585
- <Route index element={<Navigate to="." replace />} />
586
- <Route index element={<$MODULE_PASCALPage />} />
587
- <Route path="create" element={<Create$MODULE_PASCALPage />} />
588
- <Route path=":id" element={<$MODULE_PASCALDetailPage />} />
589
- <Route path=":id/edit" element={<Create$MODULE_PASCALPage />} />
590
- </Route>
591
- </Route>
572
+ // WRONG — bypasses layout, permission checks, and tenant handling
573
+ <Route path="/$APPLICATION_KEBAB/$MODULE_KEBAB" element={<$MODULE_PASCALPage />} />
592
574
  ```
593
575
 
594
- ### ⚠️ CRITICAL RULES (both patterns)
595
-
596
- **FORBIDDEN — Adding to clientRoutes[] with absolute paths:**
576
+ **FORBIDDEN Registering pages without navigation seed data:**
597
577
  ```tsx
598
- // ❌ WRONG — bypasses layout entirely, shell will NOT render
599
- const clientRoutes: RouteConfig[] = [
600
- { path: '/$APPLICATION_KEBAB/$MODULE_KEBAB', element: <$MODULE_PASCALPage /> },
601
- ];
578
+ // ❌ WRONG — PageRegistry.register() alone does nothing without a matching ComponentKey in the DB
579
+ PageRegistry.register('my-app.my-module', MyPage);
580
+ // Must also have INSERT INTO core.nav_Sections with ComponentKey = 'my-app.my-module'
602
581
  ```
603
582
 
604
- `clientRoutes` is ONLY for routes **outside** SmartStack locked applications (e.g., `/about`, `/pricing`).
605
-
606
- **FORBIDDEN — Flat routes outside layout:**
607
- ```tsx
608
- // ❌ WRONG (Pattern B only) — flat route bypasses layout
609
- <Route path="/$APPLICATION_KEBAB/$MODULE_KEBAB" element={<$MODULE_PASCALPage />} />
610
- ```
583
+ ---
611
584
 
612
- ### Why nested/context routes?
585
+ ### Legacy Patterns (DEPRECATED — removed in v3.7)
613
586
 
614
- | Aspect | clientRoutes (wrong) | applicationRoutes / nested (correct) |
615
- |--------|---------------------|--------------------------------------|
616
- | Shell rendered | No (bypasses layout) | Yes (Outlet pattern) |
617
- | AvatarMenu visible | No | Yes |
618
- | Tenant prefix | Manual duplication | Automatic (mergeRoutes) |
619
- | Auto-redirects | None | Generated for intermediate paths |
620
- | Permission check | Bypassed (no RouteGuard) | Enforced by RouteGuard |
587
+ > **Pattern A (mergeRoutes)** and **Pattern B (JSX Routes)** were removed in v3.7.
588
+ > `smartstackRoutes.tsx`, `RouteGuard.tsx`, and `mergeRoutes()` no longer exist.
589
+ > If you encounter a pre-v3.7 project, upgrade to PageRegistry + DynamicRouter.
621
590
 
622
591
  ---
623
592
 
@@ -113,7 +113,7 @@ COMPARISON POINTS: {total}
113
113
  [OK] index.css — @custom-variant dark present
114
114
  [OK] DependencyInjection.Application — MediatR warning present
115
115
  [OK] DependencyInjection.Infrastructure — SQL Server pattern matches
116
- [SKIP] App.tsx — Intentionally different (client uses mergeRoutes legacy or PageRegistry+DynamicRouter)
116
+ [SKIP] App.tsx — Intentionally different (client uses PageRegistry+DynamicRouter v3.7+)
117
117
  [SKIP] ExtensionsDbContext — Intentionally simplified
118
118
 
119
119
  --------------------------------------------------------------------------------
@@ -161,7 +161,7 @@ These differences are **by design** and should always be marked `[SKIP]`:
161
161
 
162
162
  | File/Pattern | Reason |
163
163
  |---|---|
164
- | `App.tsx` — routing pattern (`mergeRoutes` legacy or `PageRegistry`+`DynamicRouter` v3.7+) | Client projects use their own routing entry point, not platform routing |
164
+ | `App.tsx` — routing pattern (`PageRegistry`+`DynamicRouter` v3.7+) | Client projects use their own routing entry point, not platform routing |
165
165
  | `ExtensionsDbContext` | Intentionally simplified vs `CoreDbContext` — different architectural role |
166
166
  | `api.ts` | Re-exports SmartStack client — wrapper pattern is correct |
167
167
  | `DependencyInjection.Infrastructure.cs` | SQL Server connection pattern identical by design |
@@ -208,7 +208,7 @@ These comparisons should **always** return `[SKIP]` — differences are by desig
208
208
 
209
209
  | Item | Reason |
210
210
  |---|---|
211
- | `App.tsx` | Client uses `mergeRoutes` (legacy) or `PageRegistry`+`DynamicRouter` (v3.7+); app uses direct routing |
211
+ | `App.tsx` | Client uses `PageRegistry`+`DynamicRouter` (v3.7+); app uses DynamicRouter exclusively (mergeRoutes removed in v3.7) |
212
212
  | `api.ts` | Client re-exports SmartStack client — wrapper pattern is correct |
213
213
  | `ExtensionsDbContext` | Intentionally simplified vs `CoreDbContext` |
214
214
  | `GlobalUsings.cs` | App has platform-wide usings; client has minimal set |
@@ -25,8 +25,7 @@ Validate that generated documentation is complete and accurate, then integrate i
25
25
  - [ ] Mock UI faithfully reproduces the real page interface (not generic KPI/table)
26
26
  - [ ] Every Mock UI section has `Annotation` components explaining each visual element
27
27
  - [ ] i18n JSON is FLAT (no root key) — `t('title')` resolves directly
28
- - [ ] Route added as **child** of `/docs/business` group (not standalone) in App.tsx
29
- - [ ] Route added in `smartstackRoutes.tsx` (locked routes)
28
+ - [ ] Route added as **child** of `/docs/business` group (not standalone) in DynamicRouter.tsx
30
29
  - [ ] DocsLayout sidebar updated (if applicable)
31
30
  - [ ] Module appears in UserIndexPage.tsx (if applicable)
32
31
  - [ ] DocPanelContext.tsx mapping updated for all module routes
@@ -38,8 +37,7 @@ Validate that generated documentation is complete and accurate, then integrate i
38
37
  - [ ] i18n JSON uses root key matching namespace
39
38
  - [ ] DocRenderer wrapper (index.tsx) passes correct `i18nNamespace` prop
40
39
  - [ ] doc-data.ts contains structured data (~50 lines)
41
- - [ ] Route added as **child** of `/system/docs` group (not standalone) in App.tsx
42
- - [ ] Route added in `smartstackRoutes.tsx` (locked routes)
40
+ - [ ] Route added as **child** of `/system/docs` group (not standalone) in DynamicRouter.tsx
43
41
  - [ ] DocsLayout sidebar updated (if applicable)
44
42
 
45
43
  #### For `update` Type
@@ -113,7 +111,7 @@ detects tenant-prefixed routes (`/t/:slug/...`), and builds lookup keys as `{app
113
111
  - `/support/tickets/123` → tries `support/tickets/123`, then `support/tickets`
114
112
  - `/t/my-company/administration/users` → strips tenant prefix, then tries `administration/users`
115
113
 
116
- #### App.tsx Routing
114
+ #### DynamicRouter.tsx Routing
117
115
 
118
116
  Documentation pages use a **separate routing system** from business pages. They render inside `DocsLayout` (sidebar navigation), NOT `AppLayout`. There are two route groups:
119
117
 
@@ -122,10 +120,10 @@ Documentation pages use a **separate routing system** from business pages. They
122
120
 
123
121
  > **Note:** The `<Suspense fallback={<PageLoader />}>` is already in place at the global level (wrapping the entire Routes tree). Do NOT add per-route `<Suspense>` wrappers.
124
122
 
125
- **For `user` type:** Add lazy import and **child route** inside the existing `/docs/business` group:
123
+ **For `user` type:** Add lazy import and **child route** inside the existing `/docs/business` group in `DynamicRouter.tsx`:
126
124
 
127
125
  ```tsx
128
- // 1. Add lazy import at top of App.tsx
126
+ // 1. Add lazy import at top of DynamicRouter.tsx
129
127
  const {Module}DocPage = lazy(() =>
130
128
  import('@/pages/docs/business/{application}/{module}')
131
129
  );
@@ -138,10 +136,10 @@ const {Module}DocPage = lazy(() =>
138
136
  </Route>
139
137
  ```
140
138
 
141
- **For `developer|database|testing` types:** Add **child route** inside the existing `/system/docs` group:
139
+ **For `developer|database|testing` types:** Add **child route** inside the existing `/system/docs` group in `DynamicRouter.tsx`:
142
140
 
143
141
  ```tsx
144
- // 1. Add lazy import at top of App.tsx
142
+ // 1. Add lazy import at top of DynamicRouter.tsx
145
143
  const {Tool}DocPage = lazy(() =>
146
144
  import('@/pages/docs/{category}/{tool}')
147
145
  );
@@ -154,9 +152,9 @@ const {Tool}DocPage = lazy(() =>
154
152
  </Route>
155
153
  ```
156
154
 
157
- #### smartstackRoutes.tsx Registration
155
+ #### DynamicRouter Documentation Routes
158
156
 
159
- **MANDATORY** for all doc types: Add the route in `smartstackRoutes.tsx` under the corresponding group (`/docs/business` or `/system/docs`). Documentation routes are part of `LOCKED_PREFIXES` and cannot be overridden by client extensions.
157
+ Documentation routes are statically defined in `DynamicRouter.tsx` (not dynamically generated from the navigation API). When adding new documentation pages, add the lazy import and `<Route>` entry in the documentation section of `DynamicRouter.tsx`.
160
158
 
161
159
  #### DocsLayout Sidebar Update
162
160
 
@@ -164,7 +162,7 @@ If the new documentation page should appear in the sidebar navigation, update th
164
162
 
165
163
  #### Important Notes
166
164
 
167
- - Documentation routes do **NOT** use `AppLayout`, `RouteGuard`, or `LicenseGuard`
165
+ - Documentation routes do **NOT** use `AppLayout`, `ProtectedRoute`, or `LicenseGuard`
168
166
  - Documentation routes have **no tenant prefix** (`/t/:slug/...`) — they are globally accessible
169
167
  - Documentation routes require **no specific permission** to access
170
168
 
@@ -259,8 +257,8 @@ Add or update entry with metadata:
259
257
  - [x] Mock UI faithfully reproduces the real page interface (user type)
260
258
  - [x] Every Mock UI section has `Annotation` components (user type)
261
259
  - [x] i18n files created in ALL 4 languages (FR, EN, DE, IT) with correct format
262
- - [x] Route added as child of correct DocsLayout group in App.tsx
263
- - [x] Route registered in smartstackRoutes.tsx (locked routes)
260
+ - [x] Route added as child of correct DocsLayout group in DynamicRouter.tsx
261
+ - [x] Route added in DynamicRouter.tsx documentation section
264
262
  - [x] DocsLayout sidebar updated (if applicable)
265
263
  - [x] docs-manifest.json updated
266
264
  - [x] i18n/config.ts updated with new namespace