@atlashub/smartstack-cli 4.76.0 → 4.79.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.
Files changed (34) hide show
  1. package/package.json +1 -1
  2. package/templates/project/claude-md/root.CLAUDE.md.template +1 -1
  3. package/templates/skills/ai-prompt/SKILL.md +64 -0
  4. package/templates/skills/ai-prompt/references/ai-agent-modes.md +89 -0
  5. package/templates/skills/ai-prompt/references/eval-framework.md +129 -0
  6. package/templates/skills/apex/references/checks/frontend-checks.sh +97 -11
  7. package/templates/skills/apex/references/checks/seed-checks.sh +34 -0
  8. package/templates/skills/apex/references/core-seed-data.md +7 -4
  9. package/templates/skills/apex/references/domain-events-pattern.md +45 -0
  10. package/templates/skills/apex/references/entity-hooks-pattern.md +68 -0
  11. package/templates/skills/apex/references/licensing-enforcement.md +52 -0
  12. package/templates/skills/apex/references/smartstack-api.md +112 -1
  13. package/templates/skills/apex-verify/steps/step-01-nav-audit.md +4 -0
  14. package/templates/skills/application/references/contexts-cheatsheet.md +86 -0
  15. package/templates/skills/application/references/extensions-system.md +158 -0
  16. package/templates/skills/application/references/frontend-route-naming.md +7 -5
  17. package/templates/skills/application/references/frontend-verification.md +7 -5
  18. package/templates/skills/application/references/provider-template.md +4 -2
  19. package/templates/skills/application/references/smartstack-provider.md +118 -0
  20. package/templates/skills/application/references/themes-db-driven.md +484 -0
  21. package/templates/skills/application/templates-seed.md +4 -2
  22. package/templates/skills/audit-route/references/routing-pattern.md +3 -1
  23. package/templates/skills/business-analyse/react/components.md +30 -28
  24. package/templates/skills/business-analyse/templates-react.md +15 -15
  25. package/templates/skills/cli-app-sync/SKILL.md +105 -4
  26. package/templates/skills/cli-app-sync/references/comparison-map.md +13 -0
  27. package/templates/skills/cli-app-sync/references/diff-entities.md +162 -0
  28. package/templates/skills/documentation/templates.md +16 -16
  29. package/templates/skills/migrate/SKILL.md +312 -0
  30. package/templates/skills/migrate/references/v3.34-to-v3.46.md +289 -0
  31. package/templates/skills/smoke-generation/SKILL.md +313 -0
  32. package/templates/skills/ui-components/SKILL.md +10 -0
  33. package/templates/skills/ui-components/references/component-catalog.md +82 -0
  34. package/templates/skills/workflow/SKILL.md +70 -1
@@ -0,0 +1,484 @@
1
+ # Themes & Branding (DB-driven, Tailwind v4) — Canonical Reference
2
+
3
+ > **Reference for `application`, `ui-components`, `apex` skills** — the COMPLETE token map any generated UI must respect. SmartStack v3.46+ is theme-driven: every color, radius, shadow, and font in a generated component MUST come from one of the CSS variables below. Hardcoded Tailwind colors break dark mode and per-tenant branding.
4
+
5
+ ## When This Reference Applies
6
+
7
+ - The client wants several themes (light/dark + custom)
8
+ - The client wants per-tenant branding (logo, favicon, colors)
9
+ - You generate UI code that needs to consume theme tokens
10
+ - The user asks "how do I add a custom theme", "how do I change the logo per tenant", "what about Tailwind colors"
11
+
12
+ ---
13
+
14
+ ## TL;DR — 5 canonical rules
15
+
16
+ 1. **Always use CSS variables for color/border/radius/shadow.** `bg-[var(--bg-card)]` — not `bg-white`.
17
+ 2. **Never use `dark:` prefix.** SmartStack toggles a `.dark` class on `<html>`; CSS vars adapt automatically.
18
+ 3. **Status badges use the 4 status token sets** (`--success-*`, `--warning-*`, `--error-*`, `--info-*`).
19
+ 4. **Action buttons use the accent tokens** (`--color-accent-50..950` or `--accent-*`).
20
+ 5. **Reusable component classes go in `@layer components`** (one definition, not duplicated in every component).
21
+
22
+ ---
23
+
24
+ ## Section 1 — CSS Variables (canonical token map)
25
+
26
+ All variables are defined on `:root` (light defaults) and overridden by `.dark` for dark mode. They are exported by `@atlashub/smartstack/theme.css` (auto-imported when you import the SDK).
27
+
28
+ ### 1.1 Backgrounds
29
+
30
+ | Variable | Light | Dark | Use case |
31
+ |---|---|---|---|
32
+ | `--bg-app` | `#fafafa` | `#161618` | App root background, `<body>` |
33
+ | `--bg-primary` | `#ffffff` | `#1c1c1f` | Main containers, cards, page bodies |
34
+ | `--bg-secondary` | `#f4f4f5` | `#232326` | Secondary zones, hover surfaces |
35
+ | `--bg-tertiary` | `#e4e4e7` | `#2a2a2e` | Tertiary layers, distinct from secondary |
36
+ | `--bg-card` | `#ffffff` | `#1c1c1f` | Explicit card wrappers |
37
+ | `--bg-hover` | `#f4f4f5` | `#27272a` | Hover state on surfaces |
38
+ | `--bg-active` | `#e4e4e7` | `#3f3f46` | Active / selected state |
39
+
40
+ ### 1.2 Text
41
+
42
+ | Variable | Light | Dark | Use case |
43
+ |---|---|---|---|
44
+ | `--text-primary` | `#18181b` | `#e4e4e7` | Body text, headings |
45
+ | `--text-secondary` | `#52525b` | `#a1a1aa` | Subtitles, helper text |
46
+ | `--text-tertiary` | `#71717a` | `#71717a` | Hints, metadata |
47
+ | `--text-muted` | `#a1a1aa` | `#52525b` | Very muted text |
48
+ | `--text-inverse` | `#fafafa` | `#e4e4e7` | Text on dark surface (fallback) |
49
+
50
+ ### 1.3 Borders
51
+
52
+ | Variable | Light | Dark | Use case |
53
+ |---|---|---|---|
54
+ | `--border-color` | `#e4e4e7` | `#3f3f46` | Standard border |
55
+ | `--border-subtle` | `#f4f4f5` | `#27272a` | Very light divider |
56
+ | `--border-strong` | `#d4d4d8` | `#52525b` | Strong divider |
57
+ | `--border-width` | `1px` | `1px` | Border thickness (overridable per theme) |
58
+
59
+ ### 1.4 Status (4 sets — canonical for badges/banners/toasts)
60
+
61
+ | Variable | Light | Dark | Use case |
62
+ |---|---|---|---|
63
+ | `--success-bg` | `#f0fdfa` | `rgba(20,184,166,0.15)` | Success badge / banner background |
64
+ | `--success-text` | `#0f766e` | `#5eead4` | Success label text |
65
+ | `--success-border` | `#99f6e4` | `rgba(20,184,166,0.3)` | Success border |
66
+ | `--success-dot` | `#14b8a6` | `#14b8a6` | Status dot (always vivid) |
67
+ | `--warning-bg` | `#fffbeb` | `rgba(245,158,11,0.15)` | Warning bg |
68
+ | `--warning-text` | `#b45309` | `#fbbf24` | Warning text |
69
+ | `--warning-border` | `#fde68a` | `rgba(245,158,11,0.3)` | Warning border |
70
+ | `--warning-dot` | `#f59e0b` | `#f59e0b` | Warning dot |
71
+ | `--error-bg` | `#fff1f2` | `rgba(244,63,94,0.15)` | Error bg |
72
+ | `--error-text` | `#be123c` | `#fb7185` | Error text |
73
+ | `--error-border` | `#fecdd3` | `rgba(244,63,94,0.3)` | Error border |
74
+ | `--error-dot` | `#f43f5e` | `#f43f5e` | Error dot |
75
+ | `--info-bg` | `#f0f9ff` | `rgba(14,165,233,0.15)` | Info bg |
76
+ | `--info-text` | `#0369a1` | `#7dd3fc` | Info text |
77
+ | `--info-border` | `#bae6fd` | `rgba(14,165,233,0.3)` | Info border |
78
+ | `--info-dot` | `#0ea5e9` | `#0ea5e9` | Info dot |
79
+
80
+ ### 1.5 Accent (dynamic — set by ThemeContext from `selectedPreset.accentColorKey`)
81
+
82
+ | Variable | Default light (indigo) | Default dark (indigo) | Use case |
83
+ |---|---|---|---|
84
+ | `--color-accent-50` | `#eef2ff` | `#1e1b4b` | Lightest accent (badge bg) |
85
+ | `--color-accent-100` | `#e0e7ff` | `#312e81` | |
86
+ | `--color-accent-200` | `#c7d2fe` | `#3730a3` | |
87
+ | `--color-accent-300` | `#a5b4fc` | `#4338ca` | |
88
+ | `--color-accent-400` | `#818cf8` | `#4f46e5` | |
89
+ | `--color-accent-500` | `#6366f1` | `#6366f1` | Primary accent (icons, links) |
90
+ | `--color-accent-600` | `#4f46e5` | `#818cf8` | Action button bg |
91
+ | `--color-accent-700` | `#4338ca` | `#a5b4fc` | Hover for action button |
92
+ | `--color-accent-800` | `#3730a3` | `#c7d2fe` | |
93
+ | `--color-accent-900` | `#312e81` | `#e0e7ff` | |
94
+ | `--color-accent-950` | `#1e1b4b` | `#eef2ff` | Darkest accent |
95
+ | `--accent-bg` | `#eef2ff` | `rgba(99,102,241,0.15)` | Accent badge / hover background (semantic alias) |
96
+ | `--accent-text` | `#4338ca` | `#a5b4fc` | Accent label text |
97
+ | `--accent-border` | `#c7d2fe` | `rgba(99,102,241,0.3)` | Accent border |
98
+
99
+ ### 1.6 Border-radius (overridable per theme)
100
+
101
+ | Variable | Default | Use case |
102
+ |---|---|---|
103
+ | `--radius-card` | `12px` | Card corners |
104
+ | `--radius-button` | `8px` | Button corners |
105
+ | `--radius-badge` | `8px` | Badge / chip corners |
106
+ | `--radius-input` | `8px` | Input field corners |
107
+ | `--radius-modal` | `16px` | Modal corners |
108
+ | `--radius-menu-item` | `8px` | Sidebar / menu item corners |
109
+
110
+ ### 1.7 Item palette (depends on `selectedPreset.itemPaletteKey`)
111
+
112
+ | Variable | neutral light | neutral dark | Use case |
113
+ |---|---|---|---|
114
+ | `--item-color-light` | `#ffffff` | `#1c1c1f` | Item lightest tint |
115
+ | `--item-color-medium` | `#f4f4f5` | `#27272a` | Item medium tint |
116
+ | `--item-color-dark` | `#e4e4e7` | `#3f3f46` | Item darkest tint |
117
+ | `--item-color-border` | `#d4d4d8` | `#52525b` | Item border |
118
+
119
+ ### 1.8 Typography & shadows
120
+
121
+ | Variable | Default | Use case |
122
+ |---|---|---|
123
+ | `--font-sans` | `'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif` | Default font stack |
124
+ | `--shadow-sm` | `0 1px 2px 0 rgba(0,0,0,0.05)` | Subtle elevation |
125
+ | `--shadow-md` | `0 4px 6px -1px rgba(0,0,0,0.1)` | Medium elevation |
126
+ | `--shadow-lg` | `0 10px 15px -3px rgba(0,0,0,0.1)` | Large elevation |
127
+ | `--shadow-xl` | `0 20px 25px -5px rgba(0,0,0,0.1)` | Extra-large elevation |
128
+
129
+ ### 1.9 Tooltip variants
130
+
131
+ | Variable | Light | Dark | Use case |
132
+ |---|---|---|---|
133
+ | `--tooltip-info-bg` | `#eff6ff` | `#1a2c3b` | Info tooltip bg |
134
+ | `--tooltip-success-bg` | `#f0fdf4` | `#1a3329` | Success tooltip bg |
135
+ | `--tooltip-warning-bg` | `#fefce8` | `#3b2f1a` | Warning tooltip bg |
136
+ | `--tooltip-error-bg` | `#fef2f2` | `#3b1c1e` | Error tooltip bg |
137
+
138
+ ---
139
+
140
+ ## Section 2 — TypeScript tokens
141
+
142
+ ### 2.1 `ACCENT_COLORS` (13 palettes)
143
+
144
+ Defined in `web/smartstack-web/src/config/themePresets.ts`. Each palette exposes 11 shades (50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950).
145
+
146
+ **Neutrals:** `slate`, `gray`, `zinc`, `stone`
147
+ **Vibrants:** `indigo` (default primary), `violet`, `blue`, `cyan`, `teal`, `emerald`, `amber`, `orange`, `rose`, `pink`
148
+
149
+ ```ts
150
+ ACCENT_COLORS: Record<string, { name: string; shades: ColorShades }>
151
+ ColorShades = { 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950 }
152
+ ```
153
+
154
+ The user-selected key is stored in `UiPreset.accentColorKey` and applied at runtime by `ThemeContext.applyAccent()`, which writes the 11 shades to `--color-accent-50..950`.
155
+
156
+ ### 2.2 `ITEM_PALETTES` (6 palettes)
157
+
158
+ | Key | Label FR | Use case |
159
+ |---|---|---|
160
+ | `neutral` | Neutre | Default — CRUD tables, cards |
161
+ | `ocean` | Océan | Soft blues |
162
+ | `forest` | Forêt | Natural greens |
163
+ | `sunset` | Coucher de soleil | Warm oranges/amber |
164
+ | `lavender` | Lavande | Elegant purples |
165
+ | `rose` | Rose | Soft pinks |
166
+
167
+ Each palette ships `light` and `dark` color sets `{ light, medium, dark, border }`.
168
+
169
+ ### 2.3 `BORDER_RADIUS_VALUES` (6 presets)
170
+
171
+ | Key | px | Label FR |
172
+ |---|---|---|
173
+ | `none` | `0px` | Carré |
174
+ | `small` | `4px` | Léger |
175
+ | `medium` | `8px` | Moyen (default for buttons/badges) |
176
+ | `large` | `12px` | Arrondi (default for cards) |
177
+ | `xlarge` | `16px` | Très arrondi (default for modals) |
178
+ | `xxlarge` | `24px` | Pill |
179
+
180
+ ### 2.4 `UiTheme` JSON shape (backend → frontend contract)
181
+
182
+ ```ts
183
+ interface UiTheme {
184
+ backgroundsJson: string; // { app, primary, secondary, tertiary, card, hover, active }
185
+ textColorsJson: string; // { primary, secondary, tertiary, muted, inverse }
186
+ borderColorsJson: string; // { default, subtle, strong, width: number }
187
+ statusColorsJson: string; // { success/warning/error/info: { bg, text, border, dot } }
188
+ defaultBorderRadiusJson: string; // { card, button, badge, input, modal, menuItem }
189
+ defaultAccentColorKey: string; // e.g. "indigo" | hex like "#ff00aa"
190
+ defaultItemPaletteKey: string; // "neutral" | "ocean" | ...
191
+ }
192
+ interface UiPreset {
193
+ accentColorKey: string; // overrides theme.defaultAccentColorKey
194
+ itemPaletteKey: string | null; // overrides theme.defaultItemPaletteKey
195
+ borderRadiusJson: string | null; // overrides theme.defaultBorderRadiusJson
196
+ backgroundOverrideJson: string | null;
197
+ }
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Section 3 — Tailwind v4 setup (mandatory in client projects)
203
+
204
+ ### 3.1 `package.json`
205
+
206
+ ```json
207
+ {
208
+ "dependencies": {
209
+ "tailwindcss": "^4.1.18",
210
+ "@tailwindcss/vite": "^4.1.18"
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### 3.2 `vite.config.ts`
216
+
217
+ ```ts
218
+ import tailwindcss from '@tailwindcss/vite';
219
+ import react from '@vitejs/plugin-react';
220
+ import { defineConfig } from 'vite';
221
+
222
+ export default defineConfig({
223
+ plugins: [react(), tailwindcss()],
224
+ });
225
+ ```
226
+
227
+ ### 3.3 `index.css` (minimum)
228
+
229
+ ```css
230
+ @import "tailwindcss";
231
+ @source "../node_modules/@atlashub/smartstack";
232
+ @custom-variant dark (&:where(.dark, .dark *));
233
+
234
+ @theme {
235
+ --color-primary-50: #eef2ff;
236
+ --color-primary-100: #e0e7ff;
237
+ /* ... 9 shades total ... */
238
+ --color-primary-950: #1e1b4b;
239
+
240
+ --color-accent-50: #f5f3ff;
241
+ /* ... 9 shades ... */
242
+ --color-accent-950: #2e1065;
243
+
244
+ --font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
245
+ }
246
+
247
+ /* Then optionally @layer components { .card { ... } } for shared classes */
248
+ ```
249
+
250
+ > Tailwind v4 has **no `tailwind.config.ts`** — tokens go in `@theme {}` blocks inside CSS.
251
+
252
+ ### 3.4 Dark mode
253
+
254
+ ```css
255
+ :root { /* light defaults already declared */ }
256
+
257
+ .dark {
258
+ color-scheme: dark;
259
+ --bg-app: #161618;
260
+ --text-primary: #e4e4e7;
261
+ /* ... overrides for all variables ... */
262
+ }
263
+ ```
264
+
265
+ Toggle from JS:
266
+
267
+ ```ts
268
+ document.documentElement.classList.toggle('dark', wantDark);
269
+ ```
270
+
271
+ `ThemeContext` does this automatically based on the luminance of `--bg-app` — never write the toggle yourself in components.
272
+
273
+ ---
274
+
275
+ ## Section 4 — className conventions (constated patterns)
276
+
277
+ ### Pattern A — Tailwind utility + CSS var (most common)
278
+
279
+ ```tsx
280
+ <p className="text-[var(--text-secondary)]">{subtitle}</p>
281
+ <div className="bg-[var(--bg-primary)]/80 backdrop-blur-lg border-b border-[var(--border-color)]" />
282
+ <button className="bg-[var(--color-accent-600)] hover:bg-[var(--color-accent-700)] text-white px-4 py-2 rounded-[var(--radius-button)]">Save</button>
283
+ ```
284
+
285
+ ### Pattern B — Custom @layer components class
286
+
287
+ ```css
288
+ @layer components {
289
+ .card {
290
+ background: var(--bg-card);
291
+ border: 1px solid var(--border-color);
292
+ border-radius: var(--radius-card);
293
+ box-shadow: var(--shadow-sm);
294
+ }
295
+ .badge-success {
296
+ background: var(--success-bg);
297
+ color: var(--success-text);
298
+ border: 1px solid var(--success-border);
299
+ border-radius: var(--radius-badge);
300
+ padding: 0.125rem 0.5rem;
301
+ font-size: 0.75rem;
302
+ }
303
+ }
304
+ ```
305
+
306
+ ```tsx
307
+ <div className="card">...</div>
308
+ <span className="badge-success">OK</span>
309
+ ```
310
+
311
+ ### Pattern C — Inline style for dynamic gradients
312
+
313
+ ```tsx
314
+ const accentGradient: React.CSSProperties = {
315
+ background: 'linear-gradient(to bottom right, var(--color-accent-500), var(--color-accent-600))',
316
+ };
317
+ <div style={accentGradient}>{initials}</div>
318
+ ```
319
+
320
+ ### Pattern D — Variant mapping (modal/banner color)
321
+
322
+ ```tsx
323
+ const VARIANT_CONFIG = {
324
+ danger: { iconColor: 'text-[var(--error-text)]', buttonColor: 'bg-[var(--error-text)] hover:opacity-90', iconBg: 'bg-[var(--error-bg)]' },
325
+ warning: { iconColor: 'text-[var(--warning-text)]', buttonColor: 'bg-[var(--warning-text)] hover:opacity-90', iconBg: 'bg-[var(--warning-bg)]' },
326
+ info: { iconColor: 'text-[var(--info-text)]', buttonColor: 'bg-[var(--color-accent-600)] hover:bg-[var(--color-accent-700)]', iconBg: 'bg-[var(--info-bg)]' },
327
+ };
328
+ ```
329
+
330
+ ---
331
+
332
+ ## Section 5 — Migration map (Tailwind hardcoded → CSS var)
333
+
334
+ Use this table when fixing a generated file that violates the theme.
335
+
336
+ | Hardcoded Tailwind | SmartStack CSS var |
337
+ |---|---|
338
+ | `bg-white` | `bg-[var(--bg-card)]` |
339
+ | `bg-gray-50` | `bg-[var(--bg-primary)]` |
340
+ | `bg-gray-100` | `bg-[var(--bg-secondary)]` |
341
+ | `bg-gray-200` | `bg-[var(--bg-tertiary)]` |
342
+ | `text-gray-900` | `text-[var(--text-primary)]` |
343
+ | `text-gray-700` | `text-[var(--text-secondary)]` |
344
+ | `text-gray-500` | `text-[var(--text-tertiary)]` |
345
+ | `text-gray-400` | `text-[var(--text-muted)]` |
346
+ | `border-gray-200` | `border-[var(--border-color)]` |
347
+ | `border-gray-100` | `border-[var(--border-subtle)]` |
348
+ | `bg-blue-500/10` (info badge) | `bg-[var(--info-bg)]` |
349
+ | `text-blue-600` (info text) | `text-[var(--info-text)]` |
350
+ | `bg-blue-600` (action button) | `bg-[var(--color-accent-600)]` |
351
+ | `text-blue-600` (link) | `text-[var(--color-accent-600)]` |
352
+ | `bg-red-500/10` | `bg-[var(--error-bg)]` |
353
+ | `text-red-500` / `text-red-600` | `text-[var(--error-text)]` |
354
+ | `border-red-500/30` | `border-[var(--error-border)]` |
355
+ | `bg-yellow-500/10` / `bg-amber-500/10` | `bg-[var(--warning-bg)]` |
356
+ | `text-yellow-600` / `text-amber-600` | `text-[var(--warning-text)]` |
357
+ | `bg-green-500/10` | `bg-[var(--success-bg)]` |
358
+ | `text-green-600` | `text-[var(--success-text)]` |
359
+ | `bg-purple-500/10` | `bg-[var(--accent-bg)]` |
360
+ | `text-purple-600` | `text-[var(--accent-text)]` |
361
+ | `dark:bg-gray-900` | (remove — `.dark` class on root drives `--bg-app`) |
362
+ | `dark:text-white` | (remove — CSS vars adapt automatically) |
363
+
364
+ ### Status badge recipe (canonical, theme-aware)
365
+
366
+ ```tsx
367
+ // success / warning / error / info / accent / neutral — pick one set
368
+ <span className="px-2 py-0.5 rounded text-xs font-medium border bg-[var(--success-bg)] text-[var(--success-text)] border-[var(--success-border)]">
369
+ Approved
370
+ </span>
371
+ ```
372
+
373
+ ### HTTP method badge recipe (REST conventions)
374
+
375
+ | Method | Token set |
376
+ |---|---|
377
+ | `GET` | `--info-*` |
378
+ | `POST` | `--success-*` |
379
+ | `PUT` | `--warning-*` |
380
+ | `PATCH` | `--accent-*` |
381
+ | `DELETE` | `--error-*` |
382
+
383
+ ---
384
+
385
+ ## Section 6 — Branding assets (DB-driven)
386
+
387
+ ### Architecture (since v3.45)
388
+
389
+ `BrandingAsset` entity stores uploads (logo, favicon) per scope:
390
+
391
+ ```csharp
392
+ class BrandingAsset : BaseEntity, IAuditableEntity, IOptionalTenantEntity
393
+ {
394
+ public BrandingAssetType AssetType { get; } // Logo=0, LogoDark=1, Favicon=2
395
+ public BrandingAssetScope Scope { get; } // Platform=0, Tenant=1
396
+ public Guid? TenantId { get; }
397
+ public string StorageFileName { get; } // resolved via IFileStorageService
398
+ public string ContentType { get; }
399
+ public long FileSizeBytes { get; }
400
+ public string OriginalFileName { get; }
401
+ public int? Width { get; }
402
+ public int? Height { get; }
403
+ }
404
+ ```
405
+
406
+ `UiTheme` may reference assets via `LogoAssetId`, `LogoDarkAssetId`, `FaviconAssetId` (theme-level overrides).
407
+
408
+ ### Built-in seeds (immutable)
409
+
410
+ - `UiThemeSeedData.DarkThemeId` = `f1e8a3d2-4b7c-4e5f-9a2c-6d1b8e4f3c7a`
411
+ - `UiThemeSeedData.LightThemeId` = `c2d9b4e5-3a8f-4c6d-b1e7-5f2c9a8b3d4e`
412
+
413
+ These IDs MUST be referenced by const, never re-generated.
414
+
415
+ ### API endpoints
416
+
417
+ | Endpoint | Purpose |
418
+ |---|---|
419
+ | `GET /api/ui/themes` | List effective themes for current user |
420
+ | `GET /api/ui/themes/{id}` | Get one theme |
421
+ | `POST /api/ui/themes` | Create custom theme (admin) |
422
+ | `GET /api/ui/branding/{type}` | Resolve asset for current tenant + assetType |
423
+ | `POST /api/ui/branding` | Upload asset (admin) |
424
+
425
+ The frontend `ThemeContext` calls these at startup and after a tenant switch.
426
+
427
+ ### Frontend consumption
428
+
429
+ ```tsx
430
+ import { useTheme } from '@atlashub/smartstack';
431
+
432
+ function MyComponent() {
433
+ const { mode, selectedTheme, toggleMode, setTheme } = useTheme();
434
+ // selectedTheme.BackgroundsJson is parsed and applied to :root by ThemeSync.
435
+ // Components only consume var(--xxx) and never read the JSON directly.
436
+ }
437
+ ```
438
+
439
+ ---
440
+
441
+ ## Section 7 — DO / DON'T
442
+
443
+ | ✅ DO | ❌ DON'T |
444
+ |---|---|
445
+ | Read theme via `useTheme()` | Read `localStorage` for theme directly |
446
+ | Use CSS vars (`var(--bg-card)`, `var(--text-primary)`, …) | Hardcode `bg-blue-500`, `text-gray-900`, `bg-white` |
447
+ | Toggle dark mode via `useTheme().toggleMode()` | Use `dark:` Tailwind prefix |
448
+ | Reference `DarkThemeId`/`LightThemeId` as constants | Re-generate the built-in theme GUIDs |
449
+ | Upload branding via `/api/ui/branding` | Replace logo file directly on disk |
450
+ | Use `BrandingAssetScope.Tenant` for per-tenant logos | Hardcode logo path in `App.tsx` |
451
+ | Put reusable component classes in `@layer components` | Duplicate the same className across 10 components |
452
+ | Use status tokens (`--success-*`, …) for badges | Pick a random Tailwind shade per badge |
453
+ | Rely on `IFileStorageService` for asset URLs | Hardcode `/uploads/logo.png` paths |
454
+
455
+ ---
456
+
457
+ ## Section 8 — Validation hooks
458
+
459
+ Generated UI MUST pass `apex/references/checks/frontend-checks.sh` (POST-CHECK C9 / C9b / C9c) which now BLOCK on:
460
+
461
+ - `bg|text|border-(red|blue|green|gray|white|black|slate|zinc|neutral|stone|amber|yellow|orange|purple|indigo|violet|cyan|pink|emerald|rose|sky|teal|lime|fuchsia)-\d+` in `.tsx`
462
+ - `dark:` prefix on any utility class
463
+ - Hex colors `#xxxxxx` inside `className=[…]`
464
+
465
+ Before declaring a generation complete, run:
466
+
467
+ ```bash
468
+ bash templates/skills/apex/references/checks/frontend-checks.sh
469
+ ```
470
+
471
+ Expected: `0 BLOCKING`. If C9 / C9b / C9c trigger, use the migration table in Section 5 to fix.
472
+
473
+ ---
474
+
475
+ ## Reference source files (read-only — do NOT copy verbatim)
476
+
477
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\index.css` (CSS vars)
478
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\base.css` (custom @layer classes)
479
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\config\themePresets.ts` (ACCENT_COLORS, ITEM_PALETTES, BORDER_RADIUS)
480
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\web\smartstack-web\src\contexts\ThemeContext.tsx` (apply* functions)
481
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\Platform\Administration\UiConfiguration\UiTheme.cs`
482
+ - `D:\01 - projets\SmartStack.app\features\IA-Workflow\src\SmartStack.Domain\Platform\Administration\UiConfiguration\BrandingAsset.cs`
483
+
484
+ See also [smartstack-provider.md](smartstack-provider.md) for `ThemeProvider` wrapping order, [contexts-cheatsheet.md](contexts-cheatsheet.md) for `useTheme()` API.
@@ -804,10 +804,12 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
804
804
  }
805
805
  else
806
806
  {
807
+ // v3.46+ signature : Zone removed. New flags isOpen + isPersonal at the END (optional, default false).
807
808
  app = NavigationApplication.Create(
808
- ApplicationZone.Business, "{app_code}", "{app_label_en}",
809
+ "{app_code}", "{app_label_en}",
809
810
  "{app_desc_en}", "{app_icon}", IconType.Lucide,
810
- "/{app_code}", {display_order});
811
+ "/{app_code}", {display_order},
812
+ isOpen: false, isPersonal: false);
811
813
  context.NavigationApplications.Add(app);
812
814
  await ((DbContext)context).SaveChangesAsync(ct);
813
815
 
@@ -56,7 +56,9 @@ Navigation and permissions are **entirely driven by the database**. The React fr
56
56
  - Sole routing engine — App.tsx reduced to providers + DynamicRouter
57
57
  - Handles: dynamic app routes, feature gating, auto-redirects, outlet tab routes, doc routes
58
58
  - `OUTLET_SECTIONS` config for tab-based pages
59
- - `getStaticAppRoutes()` for per-app legacy redirects (migration period only)
59
+ - `IMPLICIT_SUFFIXES` (24 suffixes : `.detail`, `.edit`, `.create`, `.new`, `.duplicate`, `.settings`, `.configure`, `.permissions`, `.members`, `.history`, `.logs`, `.analytics`, `.preview`, `.versions`, `.comments`, `.attachments`, `.audit`, `.export`, `.notifications`, `.schedule`, `.workflow`, `.summary`, `.test`, `.runs`, `.import`) auto-generate child routes (e.g. `/users/:id` for `.detail`) without seeding them in the menu
60
+ - **`mergeRoutes()` and `getStaticAppRoutes()` REMOVED in v3.7+** — clients must register pages via `PageRegistry.register()` only. Legacy migration helpers no longer exist.
61
+ - API-driven `isOpen` flag on each application bypasses permission checks (replaces hardcoded OPEN_APPS list). DTO field on `ApplicationMenuDto`.
60
62
 
61
63
  ### 4. useRouteConfig
62
64
 
@@ -34,19 +34,20 @@ import {
34
34
  import type { FeatureJson, FeatureStatus } from '@/types/docs';
35
35
  import { loadFeature, listVersions } from '@/services/docs';
36
36
 
37
- // Status Badge Component (supports both module and application statuses)
37
+ // Status Badge Component (uses SmartStack theme CSS vars — adapts to dark mode and custom themes)
38
+ // Maps the 8 BA statuses to the 4 canonical status tokens (info/warning/success) + neutral + accent.
38
39
  function StatusBadge({ status }: { status: FeatureStatus }) {
39
40
  const config: Record<FeatureStatus, { color: string; label: string }> = {
40
- 'draft': { color: 'bg-gray-500/10 text-gray-600', label: 'Draft' },
41
- 'framed': { color: 'bg-sky-500/10 text-sky-600', label: 'Framed' },
42
- 'analysed': { color: 'bg-blue-500/10 text-blue-600', label: 'Analysed' },
43
- 'decomposed': { color: 'bg-indigo-500/10 text-indigo-600', label: 'Decomposed' },
44
- 'specified': { color: 'bg-yellow-500/10 text-yellow-600', label: 'Specified' },
45
- 'consolidated': { color: 'bg-orange-500/10 text-orange-600', label: 'Consolidated' },
46
- 'approved': { color: 'bg-green-500/10 text-green-600', label: 'Approved' },
47
- 'handed-off': { color: 'bg-purple-500/10 text-purple-600', label: 'Handed Off' }
41
+ 'draft': { color: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]', label: 'Draft' },
42
+ 'framed': { color: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]', label: 'Framed' },
43
+ 'analysed': { color: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]', label: 'Analysed' },
44
+ 'decomposed': { color: 'bg-[var(--accent-bg)] text-[var(--accent-text)] border border-[var(--accent-border)]', label: 'Decomposed' },
45
+ 'specified': { color: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]', label: 'Specified' },
46
+ 'consolidated': { color: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]', label: 'Consolidated' },
47
+ 'approved': { color: 'bg-[var(--success-bg)] text-[var(--success-text)] border border-[var(--success-border)]', label: 'Approved' },
48
+ 'handed-off': { color: 'bg-[var(--accent-bg)] text-[var(--accent-text)] border border-[var(--accent-border)]', label: 'Handed Off' }
48
49
  };
49
- const { color, label } = config[status] || { color: 'bg-gray-500/10 text-gray-600', label: status };
50
+ const { color, label } = config[status] || { color: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]', label: status };
50
51
  return (
51
52
  <span className={`px-2 py-0.5 rounded text-xs font-medium ${color}`}>
52
53
  {label}
@@ -54,15 +55,15 @@ function StatusBadge({ status }: { status: FeatureStatus }) {
54
55
  );
55
56
  }
56
57
 
57
- // Priority Badge Component
58
+ // Priority Badge Component (MoSCoW → status tokens)
58
59
  function PriorityBadge({ priority }: { priority: string }) {
59
60
  const colors: Record<string, string> = {
60
- Must: 'bg-red-500/10 text-red-600',
61
- Should: 'bg-yellow-500/10 text-yellow-600',
62
- Could: 'bg-blue-500/10 text-blue-600'
61
+ Must: 'bg-[var(--error-bg)] text-[var(--error-text)] border border-[var(--error-border)]',
62
+ Should: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]',
63
+ Could: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]'
63
64
  };
64
65
  return (
65
- <span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[priority] || 'bg-gray-500/10 text-gray-600'}`}>
66
+ <span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[priority] || 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'}`}>
66
67
  {priority}
67
68
  </span>
68
69
  );
@@ -240,8 +241,8 @@ export function BusinessAnalyseViewer() {
240
241
  {feature.discovery.risks.map((risk) => (
241
242
  <div key={risk.id} className="flex items-start gap-2 p-2 rounded bg-[var(--bg-secondary)] mb-1">
242
243
  <AlertTriangle className={`w-4 h-4 flex-shrink-0 mt-0.5 ${
243
- risk.severity === 'high' ? 'text-red-500' :
244
- risk.severity === 'medium' ? 'text-yellow-500' : 'text-blue-500'
244
+ risk.severity === 'high' ? 'text-[var(--error-text)]' :
245
+ risk.severity === 'medium' ? 'text-[var(--warning-text)]' : 'text-[var(--info-text)]'
245
246
  }`} />
246
247
  <div>
247
248
  <span className="text-sm font-medium">{risk.description}</span>
@@ -341,7 +342,7 @@ export function BusinessAnalyseViewer() {
341
342
  </ol>
342
343
  </div>
343
344
  <div className="flex items-center gap-2 text-xs">
344
- <Shield className="w-3 h-3 text-amber-500" />
345
+ <Shield className="w-3 h-3 text-[var(--warning-text)]" />
345
346
  <code className="bg-[var(--bg-secondary)] px-2 py-0.5 rounded">{uc.permission}</code>
346
347
  {uc.linkedRules.length > 0 && (
347
348
  <span className="text-[var(--text-secondary)]">
@@ -393,11 +394,12 @@ export function BusinessAnalyseViewer() {
393
394
  </thead>
394
395
  <tbody>
395
396
  {feature.specification.apiEndpoints.map((ep, idx) => {
397
+ // HTTP methods → status tokens (theme-aware)
396
398
  const methodColors: Record<string, string> = {
397
- GET: 'bg-green-500/10 text-green-600',
398
- POST: 'bg-blue-500/10 text-blue-600',
399
- PUT: 'bg-yellow-500/10 text-yellow-600',
400
- DELETE: 'bg-red-500/10 text-red-600'
399
+ GET: 'bg-[var(--success-bg)] text-[var(--success-text)] border border-[var(--success-border)]',
400
+ POST: 'bg-[var(--info-bg)] text-[var(--info-text)] border border-[var(--info-border)]',
401
+ PUT: 'bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)]',
402
+ DELETE: 'bg-[var(--error-bg)] text-[var(--error-text)] border border-[var(--error-border)]'
401
403
  };
402
404
  return (
403
405
  <tr key={`${ep.method}-${ep.path}`} className={idx % 2 === 1 ? 'bg-[var(--bg-secondary)]/50' : ''}>
@@ -431,7 +433,7 @@ export function BusinessAnalyseViewer() {
431
433
  </h3>
432
434
  <div className="flex gap-1">
433
435
  {wf.permissionsRequired?.map((perm) => (
434
- <code key={perm} className="px-2 py-0.5 rounded bg-amber-500/10 text-amber-600 text-xs">
436
+ <code key={perm} className="px-2 py-0.5 rounded bg-[var(--warning-bg)] text-[var(--warning-text)] border border-[var(--warning-border)] text-xs">
435
437
  {perm}
436
438
  </code>
437
439
  ))}
@@ -466,8 +468,8 @@ export function BusinessAnalyseViewer() {
466
468
  <div className="space-y-2">
467
469
  {feature.suggestions.map((s) => (
468
470
  <div key={s.code} className={`flex items-center justify-between p-3 rounded-lg border ${
469
- s.accepted === true ? 'border-green-500/30 bg-green-500/5' :
470
- s.accepted === false ? 'border-red-500/30 bg-red-500/5 opacity-50' :
471
+ s.accepted === true ? 'border-[var(--success-border)] bg-[var(--success-bg)]' :
472
+ s.accepted === false ? 'border-[var(--error-border)] bg-[var(--error-bg)] opacity-50' :
471
473
  'border-[var(--border-color)]'
472
474
  }`}>
473
475
  <div>
@@ -476,9 +478,9 @@ export function BusinessAnalyseViewer() {
476
478
  <p className="text-xs text-[var(--text-secondary)]">{s.reason}</p>
477
479
  </div>
478
480
  <span className={`px-2 py-0.5 rounded text-xs ${
479
- s.accepted === true ? 'bg-green-500/10 text-green-600' :
480
- s.accepted === false ? 'bg-red-500/10 text-red-600' :
481
- 'bg-gray-500/10 text-gray-600'
481
+ s.accepted === true ? 'bg-[var(--success-bg)] text-[var(--success-text)] border border-[var(--success-border)]' :
482
+ s.accepted === false ? 'bg-[var(--error-bg)] text-[var(--error-text)] border border-[var(--error-border)]' :
483
+ 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'
482
484
  }`}>
483
485
  {s.accepted === true ? 'Accepted' : s.accepted === false ? 'Declined' : 'Pending'}
484
486
  </span>