@atlashub/smartstack-cli 4.76.0 → 4.80.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/package.json +1 -1
- package/templates/project/claude-md/root.CLAUDE.md.template +1 -1
- package/templates/skills/ai-prompt/SKILL.md +64 -0
- package/templates/skills/ai-prompt/references/ai-agent-modes.md +89 -0
- package/templates/skills/ai-prompt/references/eval-framework.md +129 -0
- package/templates/skills/apex/references/checks/frontend-checks.sh +97 -11
- package/templates/skills/apex/references/checks/seed-checks.sh +34 -0
- package/templates/skills/apex/references/core-seed-data.md +7 -4
- package/templates/skills/apex/references/domain-events-pattern.md +45 -0
- package/templates/skills/apex/references/entity-hooks-pattern.md +68 -0
- package/templates/skills/apex/references/licensing-enforcement.md +52 -0
- package/templates/skills/apex/references/smartstack-api.md +112 -1
- package/templates/skills/apex-verify/steps/step-01-nav-audit.md +4 -0
- package/templates/skills/application/references/contexts-cheatsheet.md +86 -0
- package/templates/skills/application/references/extensions-system.md +158 -0
- package/templates/skills/application/references/frontend-route-naming.md +7 -5
- package/templates/skills/application/references/frontend-verification.md +7 -5
- package/templates/skills/application/references/provider-template.md +4 -2
- package/templates/skills/application/references/smartstack-provider.md +118 -0
- package/templates/skills/application/references/themes-db-driven.md +484 -0
- package/templates/skills/application/templates-seed.md +4 -2
- package/templates/skills/audit-route/references/routing-pattern.md +3 -1
- package/templates/skills/business-analyse/react/components.md +30 -28
- package/templates/skills/business-analyse/templates-react.md +15 -15
- package/templates/skills/cli-app-sync/SKILL.md +105 -4
- package/templates/skills/cli-app-sync/references/comparison-map.md +13 -0
- package/templates/skills/cli-app-sync/references/diff-entities.md +162 -0
- package/templates/skills/documentation/templates.md +16 -16
- package/templates/skills/migrate/SKILL.md +312 -0
- package/templates/skills/migrate/references/v3.34-to-v3.46.md +289 -0
- package/templates/skills/smoke-generation/SKILL.md +313 -0
- package/templates/skills/ui-components/SKILL.md +10 -0
- package/templates/skills/ui-components/references/component-catalog.md +82 -0
- 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
|
-
|
|
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
|
-
- `
|
|
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 (
|
|
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':
|
|
41
|
-
'framed':
|
|
42
|
-
'analysed':
|
|
43
|
-
'decomposed':
|
|
44
|
-
'specified':
|
|
45
|
-
'consolidated': { color: 'bg-
|
|
46
|
-
'approved':
|
|
47
|
-
'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-
|
|
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:
|
|
61
|
-
Should: 'bg-
|
|
62
|
-
Could:
|
|
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-
|
|
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-
|
|
244
|
-
risk.severity === 'medium' ? 'text-
|
|
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-
|
|
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:
|
|
398
|
-
POST:
|
|
399
|
-
PUT:
|
|
400
|
-
DELETE: 'bg-
|
|
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-
|
|
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-
|
|
470
|
-
s.accepted === false ? 'border-
|
|
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-
|
|
480
|
-
s.accepted === false ? 'bg-
|
|
481
|
-
'bg-
|
|
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>
|