@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.
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +6 -293
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/project/claude-md/api.CLAUDE.md.template +315 -0
- package/templates/project/claude-md/application.CLAUDE.md.template +181 -0
- package/templates/project/claude-md/domain.CLAUDE.md.template +125 -0
- package/templates/project/claude-md/infrastructure.CLAUDE.md.template +168 -0
- package/templates/project/claude-md/root.CLAUDE.md.template +339 -0
- package/templates/project/claude-md/web.CLAUDE.md.template +339 -0
- package/templates/skills/application/templates-frontend.md +62 -93
- package/templates/skills/cli-app-sync/SKILL.md +2 -2
- package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
- package/templates/skills/documentation/steps/step-03-validate.md +12 -14
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +0 -126
|
@@ -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
|
-
> **
|
|
6
|
-
>
|
|
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 (
|
|
510
|
+
## TEMPLATE: ROUTES (PageRegistry + DynamicRouter)
|
|
511
511
|
|
|
512
|
-
###
|
|
512
|
+
### Default Pattern (v3.7+): PageRegistry + DynamicRouter
|
|
513
513
|
|
|
514
|
-
|
|
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
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
//
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
560
|
+
### How DynamicRouter resolves routes
|
|
561
561
|
|
|
562
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
**FORBIDDEN — Adding to clientRoutes[] with absolute paths:**
|
|
576
|
+
**FORBIDDEN — Registering pages without navigation seed data:**
|
|
597
577
|
```tsx
|
|
598
|
-
// ❌ WRONG —
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
585
|
+
### Legacy Patterns (DEPRECATED — removed in v3.7)
|
|
613
586
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
|
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 (`
|
|
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 `
|
|
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
|
|
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
|
|
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
|
-
####
|
|
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
|
|
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
|
|
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
|
-
####
|
|
155
|
+
#### DynamicRouter Documentation Routes
|
|
158
156
|
|
|
159
|
-
|
|
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`, `
|
|
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
|
|
263
|
-
- [x] Route
|
|
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
|