@lucasvu/scope-ui 0.0.4 → 0.0.5

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/AI_SETUP.md CHANGED
@@ -5,12 +5,13 @@ Mục tiêu của file này là để AI của project dùng `@lucasvu/scope-ui`
5
5
  Nếu project consumer đã cài package, cách nhanh nhất là chạy:
6
6
 
7
7
  ```bash
8
- npx scope-ui-init
8
+ npx scope-ui-init --list-themes
9
+ npx scope-ui-init --theme forest
9
10
  ```
10
11
 
11
12
  Nếu chưa cài `@lucasvu/scope-ui`, lệnh trên sẽ báo `E404` vì npm sẽ cố tải package tên `scope-ui-init`.
12
13
 
13
- CLI này sẽ tạo `AGENTS.md` và `src/styles/ui-theme.css` theo đúng convention bên dưới.
14
+ CLI này sẽ tạo `AGENTS.md` và `src/styles/ui-theme.css` theo đúng convention bên dưới. Nếu không truyền `--theme`, preset mặc định là `ocean`.
14
15
 
15
16
  ## 1. Import style đúng thứ tự
16
17
 
@@ -23,10 +24,19 @@ import './styles/ui-theme.css'
23
24
 
24
25
  `ui-theme.css` nên là nơi duy nhất override màu, surface, border, shadow và gradient của thư viện.
25
26
 
26
- ## 2. Tạo file theme của project
27
+ ## 2. Chọn preset theme của project
27
28
 
28
29
  Đặt file tại `src/styles/ui-theme.css`.
29
30
 
31
+ Dùng `--list-themes` để xem preset được duyệt, rồi generate preset đã chọn:
32
+
33
+ ```bash
34
+ npx scope-ui-init --list-themes
35
+ npx scope-ui-init --theme graphite
36
+ ```
37
+
38
+ File generate ra sẽ là source of truth cho palette, surface, radius và shadow của project. Agent phải bám vào file này thay vì tự nghĩ màu mới.
39
+
30
40
  ```css
31
41
  :root {
32
42
  --tw-primary: 12 88% 56%;
@@ -56,11 +66,13 @@ Always:
56
66
  - import `@lucasvu/scope-ui/styles.css` once at the app entry
57
67
  - import the project theme override file after the package stylesheet
58
68
  - use root exports from `@lucasvu/scope-ui`
69
+ - stay inside the approved preset declared by `src/styles/ui-theme.css`
59
70
  - read `uiAiManifest` to choose the right component by intent
60
71
  - read `uiThemeContract` before changing colors, surfaces, borders, or shadows
61
72
 
62
73
  Do not:
63
74
  - import from `MainFe` unless the task explicitly targets a legacy screen
75
+ - invent a second palette outside the preset file
64
76
  - hardcode brand colors inside page components when a theme token already exists
65
77
  - create duplicate Button/Input/Select wrappers unless a project-specific behavior is required
66
78
 
@@ -81,11 +93,12 @@ Component choice:
81
93
  Trong code, AI có thể đọc trực tiếp:
82
94
 
83
95
  ```ts
84
- import { uiAiManifest, uiThemeContract, uiProjectAiRules } from '@lucasvu/scope-ui'
96
+ import { uiAiManifest, uiThemeContract, uiThemePresets, uiProjectAiRules } from '@lucasvu/scope-ui'
85
97
  ```
86
98
 
87
99
  - `uiAiManifest`: chọn component theo intent
88
100
  - `uiThemeContract`: biết override token nào và override ở đâu
101
+ - `uiThemePresets`: danh sách preset được duyệt để nhiều project dùng chung một visual language
89
102
  - `uiProjectAiRules`: rule ngắn gọn để inject vào agent của project
90
103
 
91
104
  ## 5. Chỗ override theme
@@ -100,4 +113,5 @@ Nếu project có nhiều brand/theme:
100
113
 
101
114
  - giữ `:root` là theme mặc định
102
115
  - tạo selector như `[data-ui-theme='brand-a']`, `[data-ui-theme='brand-b']`
116
+ - vẫn nên xuất phát từ một preset duyệt sẵn rồi mới override token cần thiết
103
117
  - chỉ override token trong file theme, không fork component
package/README.md CHANGED
@@ -19,12 +19,13 @@ import '@lucasvu/scope-ui/styles.css';
19
19
  3. Nếu muốn bootstrap rule/theme cho AI trong project consumer:
20
20
 
21
21
  ```bash
22
- npx scope-ui-init
22
+ npx scope-ui-init --list-themes
23
+ npx scope-ui-init --theme sunset
23
24
  ```
24
25
 
25
26
  Lưu ý: lệnh này chỉ chạy được sau khi project đã cài `@lucasvu/scope-ui`. Nếu chưa cài package, `npx` sẽ đi tìm một package riêng tên `scope-ui-init` trên npm và báo `E404`.
26
27
 
27
- Lệnh này sẽ tạo `AGENTS.md` và `src/styles/ui-theme.css` trong repo hiện tại. Dùng `--force` nếu muốn overwrite file đã tồn tại.
28
+ Lệnh này sẽ tạo `AGENTS.md` và `src/styles/ui-theme.css` trong repo hiện tại theo preset đã chọn. Nếu không truyền `--theme`, preset mặc định là `ocean`. Dùng `--force` nếu muốn overwrite file đã tồn tại.
28
29
 
29
30
  4. Dùng component:
30
31
 
@@ -103,22 +104,27 @@ Nếu muốn AI render UI đúng và ổn định theo thư viện này, đừng
103
104
 
104
105
  - Import CSS global một lần: `@lucasvu/scope-ui/styles.css`
105
106
  - Import file theme override của project ngay sau package CSS, ví dụ `./styles/ui-theme.css`
106
- - Chạy `npx scope-ui-init` ở project consumer sau khi đã cài `@lucasvu/scope-ui` để tạo `AGENTS.md` `src/styles/ui-theme.css`
107
+ - Chạy `npx scope-ui-init --list-themes` để xem preset được duyệt
108
+ - Chạy `npx scope-ui-init --theme <preset>` ở project consumer sau khi đã cài `@lucasvu/scope-ui` để tạo `AGENTS.md` và `src/styles/ui-theme.css`
109
+ - Khoá project vào một preset thay vì tự bịa palette mới cho từng page
107
110
  - Chỉ dùng các component canonical ở root package; tránh `MainFe` nếu không phải legacy screen
108
111
  - Cho agent đọc `uiAiManifest` để biết component nào dùng cho intent nào, props quan trọng là gì, và khi nào không nên dùng
109
- - Cho agent đọc `uiThemeContract` để biết màu/token phải override đâu
112
+ - Cho agent đọc `uiThemeContract` và `uiThemePresets` để biết danh sách preset và token nào được phép override
110
113
 
111
114
  ```ts
112
115
  import {
113
116
  uiAiManifest,
114
117
  uiProjectAiRules,
115
118
  uiThemeContract,
119
+ uiThemePresets,
116
120
  } from '@lucasvu/scope-ui';
117
121
  ```
118
122
 
119
123
  `uiAiManifest` mô tả luật chọn component theo intent như: text input, textarea, fixed select, searchable select, async combobox, multi select, data table, alert, card và field wrapper.
120
124
 
121
- `uiThemeContract` mô tả token màu, surface, border, shadow, gradient và selector theme (`:root`, `.dark`, `[data-ui-theme='*']`) để AI không hardcode màu sai chỗ.
125
+ `uiThemeContract` mô tả token màu, surface, border, shadow, radius, gradient và selector theme (`:root`, `.dark`, `[data-ui-theme='*']`) để AI không hardcode màu sai chỗ.
126
+
127
+ `uiThemePresets` là danh sách preset được duyệt sẵn để nhiều project có thể chọn cùng một visual language mà không cần tự dựng theme từ đầu.
122
128
 
123
129
  `AI_SETUP.md` và CLI `scope-ui-init` giúp bootstrap rule/theme cho repo consumer mà không cần copy tay.
124
130
 
@@ -126,6 +132,13 @@ import {
126
132
 
127
133
  Nên để toàn bộ override theme của project vào một file riêng, ví dụ `src/styles/ui-theme.css`, rồi import file này sau `@lucasvu/scope-ui/styles.css`.
128
134
 
135
+ Cách nhanh nhất là generate file này từ preset:
136
+
137
+ ```bash
138
+ npx scope-ui-init --list-themes
139
+ npx scope-ui-init --theme ocean
140
+ ```
141
+
129
142
  ```ts
130
143
  import '@lucasvu/scope-ui/styles.css';
131
144
  import './styles/ui-theme.css';
@@ -437,7 +450,8 @@ import { LineClampTooltip } from '@lucasvu/scope-ui';
437
450
 
438
451
  ## Tuỳ chỉnh theme
439
452
 
440
- - Sửa tông màu, radius, shadow trong `packages/ui/src/styles.css` (biến `--primary`, `--radius`, `--shadow-*`).
453
+ - Ưu tiên chọn một preset duyệt sẵn qua `npx scope-ui-init --theme <preset>` để giữ UI đồng bộ giữa các project.
454
+ - Nếu cần tinh chỉnh thêm, chỉ sửa token trong `src/styles/ui-theme.css`, không restyle từng component riêng lẻ.
441
455
  - Các lớp `ui-*` đã được namespaced để tránh va chạm với CSS hiện tại.
442
456
 
443
457
  ## Gợi ý tích hợp
@@ -5,30 +5,264 @@ import { dirname, resolve } from 'node:path'
5
5
  import process from 'node:process'
6
6
 
7
7
  const PACKAGE_NAME = '@lucasvu/scope-ui'
8
+ const DEFAULT_THEME_PRESET = 'ocean'
9
+
10
+ const THEME_PRESETS = {
11
+ ocean: {
12
+ id: 'ocean',
13
+ label: 'Ocean Glass',
14
+ description:
15
+ 'Blue-cyan preset with clean glass surfaces. Matches the current default visual language.',
16
+ recommendedFor: [
17
+ 'Admin dashboards',
18
+ 'SaaS CRUD screens',
19
+ 'Projects that want the existing package look',
20
+ ],
21
+ tokens: {
22
+ light: {
23
+ '--tw-background': '0 0% 100%',
24
+ '--tw-foreground': '222.2 47.4% 11.2%',
25
+ '--tw-primary': '221.2 83.2% 53.3%',
26
+ '--tw-accent': '199 89% 48%',
27
+ '--tw-success': '142.1 76.2% 36.3%',
28
+ '--tw-destructive': '0 84.2% 60.2%',
29
+ '--tw-border': '214.3 31.8% 91.4%',
30
+ '--radius': '0.75rem',
31
+ '--surface': 'rgba(255, 255, 255, 0.92)',
32
+ '--surface-strong': 'rgba(241, 245, 249, 0.96)',
33
+ '--grey': 'rgba(248, 250, 252, 0.96)',
34
+ '--grey-strong': 'rgba(241, 245, 249, 0.98)',
35
+ '--shadow-sm': '0 10px 28px -18px rgba(15, 23, 42, 0.22)',
36
+ '--shadow': '0 24px 60px -28px rgba(15, 23, 42, 0.3)',
37
+ '--primary-grad-from': '199 89% 48%',
38
+ '--primary-grad-to': '221.2 83.2% 53.3%',
39
+ },
40
+ dark: {
41
+ '--tw-background': '222.2 84% 4.9%',
42
+ '--tw-foreground': '210 40% 98%',
43
+ '--tw-primary': '217.2 91.2% 59.8%',
44
+ '--tw-accent': '199 89% 48%',
45
+ '--tw-success': '142.1 70.6% 45.3%',
46
+ '--tw-destructive': '0 62.8% 30.6%',
47
+ '--tw-border': '217.2 32.6% 17.5%',
48
+ '--radius': '0.75rem',
49
+ '--surface': 'rgba(15, 23, 42, 0.78)',
50
+ '--surface-strong': 'rgba(30, 41, 59, 0.92)',
51
+ '--grey': 'rgba(30, 41, 59, 0.88)',
52
+ '--grey-strong': 'rgba(51, 65, 85, 0.92)',
53
+ '--shadow-sm': '0 16px 32px -18px rgba(2, 6, 23, 0.48)',
54
+ '--shadow': '0 28px 80px -32px rgba(2, 6, 23, 0.7)',
55
+ '--primary-grad-from': '198.6 88.7% 48.4%',
56
+ '--primary-grad-to': '221.2 83.2% 53.3%',
57
+ },
58
+ },
59
+ },
60
+ sunset: {
61
+ id: 'sunset',
62
+ label: 'Sunset Ember',
63
+ description:
64
+ 'Warm orange-coral preset with softer cream surfaces for branded landing or growth products.',
65
+ recommendedFor: [
66
+ 'Growth products',
67
+ 'Commerce backoffices',
68
+ 'Projects that want a warmer visual tone',
69
+ ],
70
+ tokens: {
71
+ light: {
72
+ '--tw-background': '30 100% 98%',
73
+ '--tw-foreground': '20 24% 14%',
74
+ '--tw-primary': '14 90% 56%',
75
+ '--tw-accent': '29 100% 58%',
76
+ '--tw-success': '145 63% 38%',
77
+ '--tw-destructive': '0 78% 58%',
78
+ '--tw-border': '24 45% 89%',
79
+ '--radius': '0.85rem',
80
+ '--surface': 'rgba(255, 247, 240, 0.92)',
81
+ '--surface-strong': 'rgba(255, 237, 223, 0.96)',
82
+ '--grey': 'rgba(255, 243, 231, 0.96)',
83
+ '--grey-strong': 'rgba(255, 232, 214, 0.98)',
84
+ '--shadow-sm': '0 12px 30px -18px rgba(194, 65, 12, 0.18)',
85
+ '--shadow': '0 28px 64px -30px rgba(154, 52, 18, 0.24)',
86
+ '--primary-grad-from': '29 100% 58%',
87
+ '--primary-grad-to': '14 90% 56%',
88
+ },
89
+ dark: {
90
+ '--tw-background': '20 24% 8%',
91
+ '--tw-foreground': '40 33% 96%',
92
+ '--tw-primary': '18 100% 62%',
93
+ '--tw-accent': '35 100% 58%',
94
+ '--tw-success': '145 60% 47%',
95
+ '--tw-destructive': '0 73% 52%',
96
+ '--tw-border': '18 24% 22%',
97
+ '--radius': '0.85rem',
98
+ '--surface': 'rgba(41, 24, 18, 0.84)',
99
+ '--surface-strong': 'rgba(59, 34, 24, 0.9)',
100
+ '--grey': 'rgba(70, 42, 29, 0.88)',
101
+ '--grey-strong': 'rgba(92, 54, 38, 0.9)',
102
+ '--shadow-sm': '0 18px 36px -20px rgba(67, 20, 7, 0.48)',
103
+ '--shadow': '0 30px 84px -34px rgba(67, 20, 7, 0.62)',
104
+ '--primary-grad-from': '35 100% 58%',
105
+ '--primary-grad-to': '18 100% 62%',
106
+ },
107
+ },
108
+ },
109
+ forest: {
110
+ id: 'forest',
111
+ label: 'Forest Mist',
112
+ description:
113
+ 'Emerald-teal preset with soft botanical surfaces for calmer productivity products.',
114
+ recommendedFor: [
115
+ 'Operations tools',
116
+ 'Internal platforms',
117
+ 'Products that want a calmer green tone',
118
+ ],
119
+ tokens: {
120
+ light: {
121
+ '--tw-background': '138 40% 98%',
122
+ '--tw-foreground': '160 25% 14%',
123
+ '--tw-primary': '158 64% 40%',
124
+ '--tw-accent': '173 58% 44%',
125
+ '--tw-success': '145 63% 36%',
126
+ '--tw-destructive': '0 78% 58%',
127
+ '--tw-border': '143 21% 88%',
128
+ '--radius': '0.8rem',
129
+ '--surface': 'rgba(245, 252, 249, 0.92)',
130
+ '--surface-strong': 'rgba(232, 245, 239, 0.96)',
131
+ '--grey': 'rgba(240, 248, 244, 0.96)',
132
+ '--grey-strong': 'rgba(225, 240, 232, 0.98)',
133
+ '--shadow-sm': '0 12px 28px -18px rgba(5, 86, 66, 0.18)',
134
+ '--shadow': '0 26px 62px -30px rgba(6, 78, 59, 0.24)',
135
+ '--primary-grad-from': '173 58% 44%',
136
+ '--primary-grad-to': '158 64% 40%',
137
+ },
138
+ dark: {
139
+ '--tw-background': '164 35% 8%',
140
+ '--tw-foreground': '144 35% 96%',
141
+ '--tw-primary': '160 70% 46%',
142
+ '--tw-accent': '174 72% 45%',
143
+ '--tw-success': '145 68% 46%',
144
+ '--tw-destructive': '0 70% 52%',
145
+ '--tw-border': '160 20% 20%',
146
+ '--radius': '0.8rem',
147
+ '--surface': 'rgba(16, 36, 31, 0.84)',
148
+ '--surface-strong': 'rgba(21, 51, 44, 0.9)',
149
+ '--grey': 'rgba(24, 61, 52, 0.88)',
150
+ '--grey-strong': 'rgba(31, 77, 65, 0.9)',
151
+ '--shadow-sm': '0 18px 34px -20px rgba(1, 44, 34, 0.48)',
152
+ '--shadow': '0 30px 82px -34px rgba(1, 44, 34, 0.6)',
153
+ '--primary-grad-from': '174 72% 45%',
154
+ '--primary-grad-to': '160 70% 46%',
155
+ },
156
+ },
157
+ },
158
+ graphite: {
159
+ id: 'graphite',
160
+ label: 'Graphite Pulse',
161
+ description:
162
+ 'Neutral slate preset with restrained blue accents for products that need a steadier enterprise tone.',
163
+ recommendedFor: [
164
+ 'Enterprise admin panels',
165
+ 'B2B internal tools',
166
+ 'Projects that want a more neutral interface',
167
+ ],
168
+ tokens: {
169
+ light: {
170
+ '--tw-background': '220 18% 97%',
171
+ '--tw-foreground': '222 24% 14%',
172
+ '--tw-primary': '221 24% 32%',
173
+ '--tw-accent': '198 83% 44%',
174
+ '--tw-success': '160 56% 38%',
175
+ '--tw-destructive': '0 72% 54%',
176
+ '--tw-border': '218 17% 86%',
177
+ '--radius': '0.7rem',
178
+ '--surface': 'rgba(248, 250, 252, 0.94)',
179
+ '--surface-strong': 'rgba(226, 232, 240, 0.96)',
180
+ '--grey': 'rgba(241, 245, 249, 0.98)',
181
+ '--grey-strong': 'rgba(226, 232, 240, 0.99)',
182
+ '--shadow-sm': '0 12px 30px -20px rgba(15, 23, 42, 0.18)',
183
+ '--shadow': '0 28px 66px -30px rgba(15, 23, 42, 0.24)',
184
+ '--primary-grad-from': '198 83% 44%',
185
+ '--primary-grad-to': '221 24% 32%',
186
+ },
187
+ dark: {
188
+ '--tw-background': '222 32% 8%',
189
+ '--tw-foreground': '210 25% 96%',
190
+ '--tw-primary': '210 24% 82%',
191
+ '--tw-accent': '192 92% 52%',
192
+ '--tw-success': '158 64% 45%',
193
+ '--tw-destructive': '0 72% 56%',
194
+ '--tw-border': '217 19% 24%',
195
+ '--radius': '0.7rem',
196
+ '--surface': 'rgba(15, 23, 42, 0.82)',
197
+ '--surface-strong': 'rgba(30, 41, 59, 0.92)',
198
+ '--grey': 'rgba(30, 41, 59, 0.9)',
199
+ '--grey-strong': 'rgba(51, 65, 85, 0.92)',
200
+ '--shadow-sm': '0 18px 38px -22px rgba(2, 6, 23, 0.48)',
201
+ '--shadow': '0 32px 88px -34px rgba(2, 6, 23, 0.68)',
202
+ '--primary-grad-from': '192 92% 52%',
203
+ '--primary-grad-to': '210 24% 82%',
204
+ },
205
+ },
206
+ },
207
+ }
208
+
209
+ function exitWithMissingValue(optionName) {
210
+ console.error(`Missing value for ${optionName}`)
211
+ process.exit(1)
212
+ }
8
213
 
9
214
  function parseArgs(argv) {
10
215
  const args = {
11
216
  force: false,
12
217
  agentsFile: 'AGENTS.md',
13
218
  themeFile: 'src/styles/ui-theme.css',
219
+ theme: DEFAULT_THEME_PRESET,
220
+ listThemes: false,
14
221
  }
15
222
 
16
223
  for (let index = 0; index < argv.length; index += 1) {
17
224
  const arg = argv[index]
225
+
18
226
  if (arg === '--force') {
19
227
  args.force = true
20
228
  continue
21
229
  }
230
+
22
231
  if (arg === '--agents-file') {
23
- args.agentsFile = argv[index + 1] ?? args.agentsFile
232
+ const value = argv[index + 1]
233
+ if (!value || value.startsWith('--')) {
234
+ exitWithMissingValue('--agents-file')
235
+ }
236
+ args.agentsFile = value
24
237
  index += 1
25
238
  continue
26
239
  }
240
+
27
241
  if (arg === '--theme-file') {
28
- args.themeFile = argv[index + 1] ?? args.themeFile
242
+ const value = argv[index + 1]
243
+ if (!value || value.startsWith('--')) {
244
+ exitWithMissingValue('--theme-file')
245
+ }
246
+ args.themeFile = value
29
247
  index += 1
30
248
  continue
31
249
  }
250
+
251
+ if (arg === '--theme') {
252
+ const value = argv[index + 1]
253
+ if (!value || value.startsWith('--')) {
254
+ exitWithMissingValue('--theme')
255
+ }
256
+ args.theme = value
257
+ index += 1
258
+ continue
259
+ }
260
+
261
+ if (arg === '--list-themes') {
262
+ args.listThemes = true
263
+ continue
264
+ }
265
+
32
266
  if (arg === '--help' || arg === '-h') {
33
267
  printHelp()
34
268
  process.exit(0)
@@ -47,38 +281,83 @@ Usage:
47
281
  npx scope-ui-init
48
282
  Run this after @lucasvu/scope-ui has been installed in the consumer project.
49
283
 
50
- npx scope-ui-init --force
284
+ npx scope-ui-init --list-themes
285
+ npx scope-ui-init --theme sunset
286
+ npx scope-ui-init --theme forest --force
51
287
  npx scope-ui-init --agents-file AGENTS.md --theme-file src/styles/ui-theme.css
52
288
 
53
289
  Options:
54
290
  --force overwrite existing files
55
291
  --agents-file target path for the generated AGENTS.md file
56
292
  --theme-file target path for the generated theme override file
293
+ --theme theme preset id (${Object.keys(THEME_PRESETS).join(', ')})
294
+ --list-themes show the approved preset list
57
295
  --help, -h show this help
58
296
  `)
59
297
  }
60
298
 
61
- function createAgentsTemplate({ themeFile }) {
299
+ function printThemeList() {
300
+ console.log('Approved theme presets:\n')
301
+
302
+ for (const themePreset of Object.values(THEME_PRESETS)) {
303
+ console.log(`${themePreset.id} - ${themePreset.label}`)
304
+ console.log(` ${themePreset.description}`)
305
+ console.log(` Recommended for: ${themePreset.recommendedFor.join(', ')}`)
306
+ console.log('')
307
+ }
308
+ }
309
+
310
+ function getThemePreset(themeId) {
311
+ return THEME_PRESETS[themeId]
312
+ }
313
+
314
+ function resolveThemePreset(themeId) {
315
+ const themePreset = getThemePreset(themeId)
316
+
317
+ if (themePreset) {
318
+ return themePreset
319
+ }
320
+
321
+ console.error(`Unknown theme preset: ${themeId}\n`)
322
+ printThemeList()
323
+ process.exit(1)
324
+ }
325
+
326
+ function renderTokenBlock(tokens) {
327
+ return Object.entries(tokens)
328
+ .map(([token, value]) => ` ${token}: ${value};`)
329
+ .join('\n')
330
+ }
331
+
332
+ function createAgentsTemplate({ themeFile, themePreset }) {
62
333
  return `# Agent Rules
63
334
 
64
335
  This repo uses \`${PACKAGE_NAME}\` as the default UI library.
336
+ This repo is locked to the \`${themePreset.label}\` theme preset (\`${themePreset.id}\`).
65
337
 
66
338
  Primary references:
67
339
  - \`node_modules/${PACKAGE_NAME}/README.md\`
68
340
  - \`node_modules/${PACKAGE_NAME}/AI_SETUP.md\`
69
341
  - \`${themeFile}\`
70
342
 
343
+ Theme preset summary:
344
+ - ${themePreset.description}
345
+ - Recommended for: ${themePreset.recommendedFor.join(', ')}
346
+
71
347
  Always:
72
348
  - import \`${PACKAGE_NAME}/styles.css\` once at the app entry
73
349
  - import the project theme override file after the package stylesheet
74
350
  - use root exports from \`${PACKAGE_NAME}\`
351
+ - use \`${themeFile}\` as the only source of truth for colors, radius, surfaces, and shadows
352
+ - keep layouts and component choices aligned with the shared preset-driven UI used across projects
75
353
  - read \`uiAiManifest\` before choosing components
76
- - read \`uiThemeContract\` before changing colors, surfaces, borders, or shadows
354
+ - read \`uiThemeContract\` before changing colors, surfaces, borders, radius, or shadows
77
355
  - keep UI token-driven and theme-driven
78
356
 
79
357
  Do not:
80
358
  - import from \`MainFe\` unless the task explicitly targets a legacy screen
81
- - hardcode brand colors inside page components when a theme token already exists
359
+ - invent a second palette or one-off brand colors outside \`${themeFile}\`
360
+ - restyle individual pages if the preset tokens can solve it globally
82
361
  - create duplicate Button/Input/Select wrappers unless a project-specific behavior is required
83
362
 
84
363
  Component choice:
@@ -94,20 +373,18 @@ Component choice:
94
373
  `
95
374
  }
96
375
 
97
- function createThemeTemplate() {
98
- return `:root {
99
- /* Override only the tokens you need for the project brand. */
100
- /* --tw-primary: 221.2 83.2% 53.3%; */
101
- /* --tw-accent: 199 89% 48%; */
102
- /* --primary-grad-from: 199 89% 48%; */
103
- /* --primary-grad-to: 221.2 83.2% 53.3%; */
104
- /* --surface: rgba(255, 255, 255, 0.92); */
376
+ function createThemeTemplate({ themePreset }) {
377
+ return `/* Generated by scope-ui-init. */
378
+ /* Theme preset: ${themePreset.label} (${themePreset.id}) */
379
+ /* Re-run \`npx scope-ui-init --theme <preset> --force\` to switch preset. */
380
+
381
+ :root {
382
+ ${renderTokenBlock(themePreset.tokens.light)}
105
383
  }
106
384
 
107
- .dark {
108
- /* --tw-primary: 217.2 91.2% 59.8%; */
109
- /* --tw-accent: 199 89% 48%; */
110
- /* --surface: rgba(15, 23, 42, 0.78); */
385
+ .dark,
386
+ [data-ui-theme='dark'] {
387
+ ${renderTokenBlock(themePreset.tokens.dark)}
111
388
  }
112
389
  `
113
390
  }
@@ -127,19 +404,27 @@ function writeFile(targetPath, content, force) {
127
404
 
128
405
  const options = parseArgs(process.argv.slice(2))
129
406
 
407
+ if (options.listThemes) {
408
+ printThemeList()
409
+ process.exit(0)
410
+ }
411
+
412
+ const themePreset = resolveThemePreset(options.theme)
413
+
130
414
  const agentsResult = writeFile(
131
415
  options.agentsFile,
132
- createAgentsTemplate({ themeFile: options.themeFile }),
416
+ createAgentsTemplate({ themeFile: options.themeFile, themePreset }),
133
417
  options.force,
134
418
  )
135
419
 
136
420
  const themeResult = writeFile(
137
421
  options.themeFile,
138
- createThemeTemplate(),
422
+ createThemeTemplate({ themePreset }),
139
423
  options.force,
140
424
  )
141
425
 
142
426
  console.log(`Initialized ${PACKAGE_NAME}`)
427
+ console.log(`- theme preset: ${themePreset.label} (${themePreset.id})`)
143
428
  console.log(`- ${agentsResult.path}: ${agentsResult.status}`)
144
429
  console.log(`- ${themeResult.path}: ${themeResult.status}`)
145
430
  console.log('')
@@ -147,3 +432,4 @@ console.log('Next steps:')
147
432
  console.log(`1. Import \`${PACKAGE_NAME}/styles.css\` once at your app entry.`)
148
433
  console.log(`2. Import \`${options.themeFile}\` right after the package stylesheet.`)
149
434
  console.log(`3. Let your agent read \`${options.agentsFile}\` before generating UI.`)
435
+ console.log(`4. Re-run this command with \`--theme <preset> --force\` if you want another approved preset.`)