@salesforce/webapp-template-app-react-sample-b2e-experimental 1.82.0 → 1.83.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 (47) hide show
  1. package/dist/.a4drules/skills/webapp-csp-trusted-sites/SKILL.md +90 -0
  2. package/dist/.a4drules/skills/webapp-csp-trusted-sites/implementation/metadata-format.md +281 -0
  3. package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/SKILL.md +1 -1
  4. package/dist/.a4drules/skills/webapp-react-data-visualization/SKILL.md +72 -0
  5. package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/dashboard-layout.md +189 -0
  6. package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/donut-chart.md +181 -0
  7. package/dist/.a4drules/skills/webapp-react-data-visualization/implementation/stat-card.md +150 -0
  8. package/dist/.a4drules/skills/webapp-react-interactive-map/SKILL.md +92 -0
  9. package/dist/.a4drules/skills/webapp-react-interactive-map/implementation/geocoding.md +245 -0
  10. package/dist/.a4drules/skills/webapp-react-interactive-map/implementation/leaflet-map.md +279 -0
  11. package/dist/.a4drules/skills/webapp-react-weather-widget/SKILL.md +65 -0
  12. package/dist/.a4drules/skills/webapp-react-weather-widget/implementation/weather-hook.md +258 -0
  13. package/dist/.a4drules/skills/webapp-react-weather-widget/implementation/weather-ui.md +216 -0
  14. package/dist/.a4drules/skills/webapp-ui-ux/SKILL.md +268 -0
  15. package/dist/.a4drules/skills/webapp-ui-ux/data/charts.csv +26 -0
  16. package/dist/.a4drules/skills/webapp-ui-ux/data/colors.csv +97 -0
  17. package/dist/.a4drules/skills/webapp-ui-ux/data/icons.csv +101 -0
  18. package/dist/.a4drules/skills/webapp-ui-ux/data/landing.csv +31 -0
  19. package/dist/.a4drules/skills/webapp-ui-ux/data/products.csv +97 -0
  20. package/dist/.a4drules/skills/webapp-ui-ux/data/react-performance.csv +45 -0
  21. package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/html-tailwind.csv +56 -0
  22. package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/react.csv +54 -0
  23. package/dist/.a4drules/skills/webapp-ui-ux/data/stacks/shadcn.csv +61 -0
  24. package/dist/.a4drules/skills/webapp-ui-ux/data/styles.csv +68 -0
  25. package/dist/.a4drules/skills/webapp-ui-ux/data/typography.csv +58 -0
  26. package/dist/.a4drules/skills/webapp-ui-ux/data/ui-reasoning.csv +101 -0
  27. package/dist/.a4drules/skills/webapp-ui-ux/data/ux-guidelines.csv +100 -0
  28. package/dist/.a4drules/skills/webapp-ui-ux/data/web-interface.csv +31 -0
  29. package/dist/.a4drules/skills/webapp-ui-ux/scripts/core.js +255 -0
  30. package/dist/.a4drules/skills/webapp-ui-ux/scripts/design_system.js +861 -0
  31. package/dist/.a4drules/skills/webapp-ui-ux/scripts/search.js +98 -0
  32. package/dist/.a4drules/skills/webapp-unsplash-images/SKILL.md +71 -0
  33. package/dist/.a4drules/skills/webapp-unsplash-images/implementation/usage.md +159 -0
  34. package/dist/.a4drules/webapp-no-node-e.md +54 -15
  35. package/dist/.a4drules/webapp-react.md +9 -10
  36. package/dist/.a4drules/webapp-skills-first.md +26 -0
  37. package/dist/.a4drules/webapp.md +8 -0
  38. package/dist/CHANGELOG.md +11 -0
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2e/package.json +4 -4
  40. package/dist/package.json +1 -1
  41. package/package.json +3 -3
  42. package/dist/.a4drules/webapp-images.md +0 -15
  43. /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/component.md +0 -0
  44. /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/header-footer.md +0 -0
  45. /package/dist/.a4drules/skills/{webapp-add-react-component → webapp-react-add-component}/implementation/page.md +0 -0
  46. /package/dist/.a4drules/{webapp-code-quality.md → webapp-react-code-quality.md} +0 -0
  47. /package/dist/.a4drules/{webapp-typescript.md → webapp-react-typescript.md} +0 -0
@@ -0,0 +1,861 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { search, loadCsv, DATA_DIR } from './core.js';
6
+
7
+ const REASONING_FILE = 'ui-reasoning.csv';
8
+
9
+ const SEARCH_CONFIG = {
10
+ product: { maxResults: 1 },
11
+ style: { maxResults: 3 },
12
+ color: { maxResults: 2 },
13
+ landing: { maxResults: 2 },
14
+ typography: { maxResults: 2 }
15
+ };
16
+
17
+ // ============ DESIGN SYSTEM GENERATOR ============
18
+
19
+ function loadReasoning() {
20
+ try {
21
+ const filepath = path.join(DATA_DIR, REASONING_FILE);
22
+ return loadCsv(filepath);
23
+ } catch (err) {
24
+ console.error('Warning: could not load reasoning file:', err?.message || err);
25
+ return [];
26
+ }
27
+ }
28
+
29
+ function multiDomainSearch(query, stylePriority) {
30
+ const results = {};
31
+ for (const [domain, config] of Object.entries(SEARCH_CONFIG)) {
32
+ try {
33
+ if (domain === 'style' && stylePriority && stylePriority.length > 0) {
34
+ const priorityQuery = stylePriority.slice(0, 2).join(' ');
35
+ results[domain] = search(`${query} ${priorityQuery}`, domain, config.maxResults);
36
+ } else {
37
+ results[domain] = search(query, domain, config.maxResults);
38
+ }
39
+ } catch (err) {
40
+ console.error(`Warning: search failed for domain "${domain}":`, err?.message || err);
41
+ results[domain] = { results: [] };
42
+ }
43
+ }
44
+ return results;
45
+ }
46
+
47
+ function findReasoningRule(reasoningData, category) {
48
+ const catLower = category.toLowerCase();
49
+
50
+ for (const rule of reasoningData) {
51
+ if ((rule.UI_Category || '').toLowerCase() === catLower) return rule;
52
+ }
53
+ for (const rule of reasoningData) {
54
+ const uiCat = (rule.UI_Category || '').toLowerCase();
55
+ if (uiCat.includes(catLower) || catLower.includes(uiCat)) return rule;
56
+ }
57
+ for (const rule of reasoningData) {
58
+ const uiCat = (rule.UI_Category || '').toLowerCase();
59
+ const keywords = uiCat.replace(/\//g, ' ').replace(/-/g, ' ').split(/\s+/);
60
+ if (keywords.some(kw => catLower.includes(kw))) return rule;
61
+ }
62
+ return {};
63
+ }
64
+
65
+ function applyReasoning(reasoningData, category) {
66
+ const rule = findReasoningRule(reasoningData, category);
67
+
68
+ if (!rule || Object.keys(rule).length === 0) {
69
+ return {
70
+ pattern: 'Hero + Features + CTA',
71
+ stylePriority: ['Minimalism', 'Flat Design'],
72
+ colorMood: 'Professional',
73
+ typographyMood: 'Clean',
74
+ keyEffects: 'Subtle hover transitions',
75
+ antiPatterns: '',
76
+ decisionRules: {},
77
+ severity: 'MEDIUM'
78
+ };
79
+ }
80
+
81
+ let decisionRules = {};
82
+ try {
83
+ decisionRules = JSON.parse(rule.Decision_Rules || '{}');
84
+ } catch {
85
+ // invalid JSON, keep empty object
86
+ }
87
+
88
+ return {
89
+ pattern: rule.Recommended_Pattern || '',
90
+ stylePriority: (rule.Style_Priority || '').split('+').map(s => s.trim()),
91
+ colorMood: rule.Color_Mood || '',
92
+ typographyMood: rule.Typography_Mood || '',
93
+ keyEffects: rule.Key_Effects || '',
94
+ antiPatterns: rule.Anti_Patterns || '',
95
+ decisionRules,
96
+ severity: rule.Severity || 'MEDIUM'
97
+ };
98
+ }
99
+
100
+ function selectBestMatch(results, priorityKeywords) {
101
+ try {
102
+ if (!results || results.length === 0) return {};
103
+ if (!priorityKeywords || priorityKeywords.length === 0) return results[0];
104
+
105
+ for (const priority of priorityKeywords) {
106
+ const pLower = priority.toLowerCase().trim();
107
+ for (const result of results) {
108
+ const name = (result['Style Category'] || '').toLowerCase();
109
+ if (pLower.includes(name) || name.includes(pLower)) return result;
110
+ }
111
+ }
112
+
113
+ const scored = results.map(result => {
114
+ const str = JSON.stringify(result).toLowerCase();
115
+ let score = 0;
116
+ for (const kw of priorityKeywords) {
117
+ const k = kw.toLowerCase().trim();
118
+ if ((result['Style Category'] || '').toLowerCase().includes(k)) score += 10;
119
+ else if ((result.Keywords || '').toLowerCase().includes(k)) score += 3;
120
+ else if (str.includes(k)) score += 1;
121
+ }
122
+ return { score, result };
123
+ });
124
+
125
+ scored.sort((a, b) => b.score - a.score);
126
+ return scored[0] && scored[0].score > 0 ? scored[0].result : results[0];
127
+ } catch (err) {
128
+ console.error('Warning: selectBestMatch failed:', err?.message || err);
129
+ return results?.[0] ?? {};
130
+ }
131
+ }
132
+
133
+ function getDefaultDesignSystem(query, projectName) {
134
+ return {
135
+ project_name: projectName || (typeof query === 'string' ? query.toUpperCase() : 'PROJECT'),
136
+ category: 'General',
137
+ pattern: { name: 'Hero + Features + CTA', sections: 'Hero > Features > CTA', cta_placement: 'Above fold', color_strategy: '', conversion: '' },
138
+ style: { name: 'Minimalism', type: 'General', effects: '', keywords: '', best_for: '', performance: '', accessibility: '' },
139
+ colors: { primary: '#2563EB', secondary: '#3B82F6', cta: '#F97316', background: '#F8FAFC', text: '#1E293B', notes: '' },
140
+ typography: { heading: 'Inter', body: 'Inter', mood: 'Clean', best_for: '', google_fonts_url: '', css_import: '' },
141
+ key_effects: 'Subtle hover transitions',
142
+ anti_patterns: '',
143
+ decision_rules: {},
144
+ severity: 'MEDIUM'
145
+ };
146
+ }
147
+
148
+ function generate(query, projectName) {
149
+ try {
150
+ const reasoningData = loadReasoning();
151
+
152
+ let productResult = { results: [] };
153
+ try {
154
+ productResult = search(query, 'product', 1);
155
+ } catch (err) {
156
+ console.error('Warning: product search failed:', err?.message || err);
157
+ }
158
+ const productResults = productResult.results || [];
159
+ let category = 'General';
160
+ if (productResults.length > 0) category = productResults[0]['Product Type'] || 'General';
161
+
162
+ const reasoning = applyReasoning(reasoningData, category);
163
+ const stylePriority = reasoning.stylePriority || [];
164
+
165
+ const searchResults = multiDomainSearch(query, stylePriority);
166
+ searchResults.product = productResult;
167
+
168
+ const styleResults = (searchResults.style || {}).results || [];
169
+ const colorResults = (searchResults.color || {}).results || [];
170
+ const typographyResults = (searchResults.typography || {}).results || [];
171
+ const landingResults = (searchResults.landing || {}).results || [];
172
+
173
+ const bestStyle = selectBestMatch(styleResults, reasoning.stylePriority);
174
+ const bestColor = colorResults[0] || {};
175
+ const bestTypography = typographyResults[0] || {};
176
+ const bestLanding = landingResults[0] || {};
177
+
178
+ const styleEffects = bestStyle['Effects & Animation'] || '';
179
+ const reasoningEffects = reasoning.keyEffects || '';
180
+
181
+ return {
182
+ project_name: projectName || (typeof query === 'string' ? query.toUpperCase() : 'PROJECT'),
183
+ category,
184
+ pattern: {
185
+ name: bestLanding['Pattern Name'] || reasoning.pattern || 'Hero + Features + CTA',
186
+ sections: bestLanding['Section Order'] || 'Hero > Features > CTA',
187
+ cta_placement: bestLanding['Primary CTA Placement'] || 'Above fold',
188
+ color_strategy: bestLanding['Color Strategy'] || '',
189
+ conversion: bestLanding['Conversion Optimization'] || ''
190
+ },
191
+ style: {
192
+ name: bestStyle['Style Category'] || 'Minimalism',
193
+ type: bestStyle.Type || 'General',
194
+ effects: styleEffects,
195
+ keywords: bestStyle.Keywords || '',
196
+ best_for: bestStyle['Best For'] || '',
197
+ performance: bestStyle.Performance || '',
198
+ accessibility: bestStyle.Accessibility || ''
199
+ },
200
+ colors: {
201
+ primary: bestColor['Primary (Hex)'] || '#2563EB',
202
+ secondary: bestColor['Secondary (Hex)'] || '#3B82F6',
203
+ cta: bestColor['CTA (Hex)'] || '#F97316',
204
+ background: bestColor['Background (Hex)'] || '#F8FAFC',
205
+ text: bestColor['Text (Hex)'] || '#1E293B',
206
+ notes: bestColor.Notes || ''
207
+ },
208
+ typography: {
209
+ heading: bestTypography['Heading Font'] || 'Inter',
210
+ body: bestTypography['Body Font'] || 'Inter',
211
+ mood: bestTypography['Mood/Style Keywords'] || reasoning.typographyMood || '',
212
+ best_for: bestTypography['Best For'] || '',
213
+ google_fonts_url: bestTypography['Google Fonts URL'] || '',
214
+ css_import: bestTypography['CSS Import'] || ''
215
+ },
216
+ key_effects: styleEffects || reasoningEffects,
217
+ anti_patterns: reasoning.antiPatterns || '',
218
+ decision_rules: reasoning.decisionRules || {},
219
+ severity: reasoning.severity || 'MEDIUM'
220
+ };
221
+ } catch (err) {
222
+ console.error('Error generating design system:', err?.message || err);
223
+ return getDefaultDesignSystem(query, projectName);
224
+ }
225
+ }
226
+
227
+ // ============ OUTPUT FORMATTERS ============
228
+
229
+ const BOX_WIDTH = 90;
230
+
231
+ function wrapText(text, prefix, width) {
232
+ if (!text) return [];
233
+ const words = text.split(/\s+/);
234
+ const lines = [];
235
+ let cur = prefix;
236
+ for (const word of words) {
237
+ if (cur.length + word.length + 1 <= width - 2) {
238
+ cur += (cur !== prefix ? ' ' : '') + word;
239
+ } else {
240
+ if (cur !== prefix) lines.push(cur);
241
+ cur = prefix + word;
242
+ }
243
+ }
244
+ if (cur !== prefix) lines.push(cur);
245
+ return lines;
246
+ }
247
+
248
+ function formatAsciiBox(ds) {
249
+ try {
250
+ if (!ds || typeof ds !== 'object') return 'Error: invalid design system object';
251
+ const project = ds.project_name || 'PROJECT';
252
+ const pattern = ds.pattern || {};
253
+ const style = ds.style || {};
254
+ const colors = ds.colors || {};
255
+ const typo = ds.typography || {};
256
+ const effects = ds.key_effects || '';
257
+ const antiPatterns = ds.anti_patterns || '';
258
+
259
+ const sections = (pattern.sections || '').split('>').map(s => s.trim()).filter(Boolean);
260
+ const w = BOX_WIDTH - 1;
261
+ const pad = s => s.padEnd(BOX_WIDTH) + '|';
262
+ const blank = () => pad('|' + ' '.repeat(BOX_WIDTH));
263
+ const lines = [];
264
+
265
+ lines.push('+' + '-'.repeat(w) + '+');
266
+ lines.push(pad(`| TARGET: ${project} - RECOMMENDED DESIGN SYSTEM`));
267
+ lines.push('+' + '-'.repeat(w) + '+');
268
+ lines.push(blank());
269
+
270
+ lines.push(pad(`| PATTERN: ${pattern.name || ''}`));
271
+ if (pattern.conversion) lines.push(pad(`| Conversion: ${pattern.conversion}`));
272
+ if (pattern.cta_placement) lines.push(pad(`| CTA: ${pattern.cta_placement}`));
273
+ lines.push(pad('| Sections:'));
274
+ sections.forEach((s, i) => lines.push(pad(`| ${i + 1}. ${s}`)));
275
+ lines.push(blank());
276
+
277
+ lines.push(pad(`| STYLE: ${style.name || ''}`));
278
+ if (style.keywords) wrapText(`Keywords: ${style.keywords}`, '| ', BOX_WIDTH).forEach(l => lines.push(pad(l)));
279
+ if (style.best_for) wrapText(`Best For: ${style.best_for}`, '| ', BOX_WIDTH).forEach(l => lines.push(pad(l)));
280
+ if (style.performance || style.accessibility) {
281
+ lines.push(pad(`| Performance: ${style.performance || ''} | Accessibility: ${style.accessibility || ''}`));
282
+ }
283
+ lines.push(blank());
284
+
285
+ lines.push(pad('| COLORS:'));
286
+ lines.push(pad(`| Primary: ${colors.primary || ''}`));
287
+ lines.push(pad(`| Secondary: ${colors.secondary || ''}`));
288
+ lines.push(pad(`| CTA: ${colors.cta || ''}`));
289
+ lines.push(pad(`| Background: ${colors.background || ''}`));
290
+ lines.push(pad(`| Text: ${colors.text || ''}`));
291
+ if (colors.notes) wrapText(`Notes: ${colors.notes}`, '| ', BOX_WIDTH).forEach(l => lines.push(pad(l)));
292
+ lines.push(blank());
293
+
294
+ lines.push(pad(`| TYPOGRAPHY: ${typo.heading || ''} / ${typo.body || ''}`));
295
+ if (typo.mood) wrapText(`Mood: ${typo.mood}`, '| ', BOX_WIDTH).forEach(l => lines.push(pad(l)));
296
+ if (typo.best_for) wrapText(`Best For: ${typo.best_for}`, '| ', BOX_WIDTH).forEach(l => lines.push(pad(l)));
297
+ if (typo.google_fonts_url) lines.push(pad(`| Google Fonts: ${typo.google_fonts_url}`));
298
+ if (typo.css_import) lines.push(pad(`| CSS Import: ${typo.css_import.slice(0, 70)}...`));
299
+ lines.push(blank());
300
+
301
+ if (effects) {
302
+ lines.push(pad('| KEY EFFECTS:'));
303
+ wrapText(effects, '| ', BOX_WIDTH).forEach(l => lines.push(pad(l)));
304
+ lines.push(blank());
305
+ }
306
+
307
+ if (antiPatterns) {
308
+ lines.push(pad('| AVOID (Anti-patterns):'));
309
+ wrapText(antiPatterns, '| ', BOX_WIDTH).forEach(l => lines.push(pad(l)));
310
+ lines.push(blank());
311
+ }
312
+
313
+ lines.push(pad('| PRE-DELIVERY CHECKLIST:'));
314
+ [
315
+ '[ ] No emojis as icons (use SVG: Heroicons/Lucide)',
316
+ '[ ] cursor-pointer on all clickable elements',
317
+ '[ ] Hover states with smooth transitions (150-300ms)',
318
+ '[ ] Light mode: text contrast 4.5:1 minimum',
319
+ '[ ] Focus states visible for keyboard nav',
320
+ '[ ] prefers-reduced-motion respected',
321
+ '[ ] Responsive: 375px, 768px, 1024px, 1440px'
322
+ ].forEach(item => lines.push(pad(`| ${item}`)));
323
+ lines.push(blank());
324
+
325
+ lines.push('+' + '-'.repeat(w) + '+');
326
+ return lines.join('\n');
327
+ } catch (err) {
328
+ console.error('Error formatting ASCII box:', err?.message || err);
329
+ return `Error: could not format design system (${err?.message || err})`;
330
+ }
331
+ }
332
+
333
+ function formatMarkdown(ds) {
334
+ try {
335
+ if (!ds || typeof ds !== 'object') return 'Error: invalid design system object';
336
+ const project = ds.project_name || 'PROJECT';
337
+ const pattern = ds.pattern || {};
338
+ const style = ds.style || {};
339
+ const colors = ds.colors || {};
340
+ const typo = ds.typography || {};
341
+ const effects = ds.key_effects || '';
342
+ const antiPatterns = ds.anti_patterns || '';
343
+
344
+ const L = [];
345
+ L.push(`## Design System: ${project}`, '');
346
+
347
+ L.push('### Pattern');
348
+ L.push(`- **Name:** ${pattern.name || ''}`);
349
+ if (pattern.conversion) L.push(`- **Conversion Focus:** ${pattern.conversion}`);
350
+ if (pattern.cta_placement) L.push(`- **CTA Placement:** ${pattern.cta_placement}`);
351
+ if (pattern.color_strategy) L.push(`- **Color Strategy:** ${pattern.color_strategy}`);
352
+ L.push(`- **Sections:** ${pattern.sections || ''}`, '');
353
+
354
+ L.push('### Style');
355
+ L.push(`- **Name:** ${style.name || ''}`);
356
+ if (style.keywords) L.push(`- **Keywords:** ${style.keywords}`);
357
+ if (style.best_for) L.push(`- **Best For:** ${style.best_for}`);
358
+ if (style.performance || style.accessibility) {
359
+ L.push(`- **Performance:** ${style.performance || ''} | **Accessibility:** ${style.accessibility || ''}`);
360
+ }
361
+ L.push('');
362
+
363
+ L.push('### Colors');
364
+ L.push('| Role | Hex |', '|------|-----|');
365
+ L.push(`| Primary | ${colors.primary || ''} |`);
366
+ L.push(`| Secondary | ${colors.secondary || ''} |`);
367
+ L.push(`| CTA | ${colors.cta || ''} |`);
368
+ L.push(`| Background | ${colors.background || ''} |`);
369
+ L.push(`| Text | ${colors.text || ''} |`);
370
+ if (colors.notes) L.push(`\n*Notes: ${colors.notes}*`);
371
+ L.push('');
372
+
373
+ L.push('### Typography');
374
+ L.push(`- **Heading:** ${typo.heading || ''}`);
375
+ L.push(`- **Body:** ${typo.body || ''}`);
376
+ if (typo.mood) L.push(`- **Mood:** ${typo.mood}`);
377
+ if (typo.best_for) L.push(`- **Best For:** ${typo.best_for}`);
378
+ if (typo.google_fonts_url) L.push(`- **Google Fonts:** ${typo.google_fonts_url}`);
379
+ if (typo.css_import) L.push('- **CSS Import:**', '```css', typo.css_import, '```');
380
+ L.push('');
381
+
382
+ if (effects) L.push('### Key Effects', effects, '');
383
+ if (antiPatterns) L.push('### Avoid (Anti-patterns)', `- ${antiPatterns.replace(/ \+ /g, '\n- ')}`, '');
384
+
385
+ L.push('### Pre-Delivery Checklist');
386
+ L.push('- [ ] No emojis as icons (use SVG: Heroicons/Lucide)');
387
+ L.push('- [ ] cursor-pointer on all clickable elements');
388
+ L.push('- [ ] Hover states with smooth transitions (150-300ms)');
389
+ L.push('- [ ] Light mode: text contrast 4.5:1 minimum');
390
+ L.push('- [ ] Focus states visible for keyboard nav');
391
+ L.push('- [ ] prefers-reduced-motion respected');
392
+ L.push('- [ ] Responsive: 375px, 768px, 1024px, 1440px', '');
393
+
394
+ return L.join('\n');
395
+ } catch (err) {
396
+ console.error('Error formatting markdown:', err?.message || err);
397
+ return `Error: could not format design system (${err?.message || err})`;
398
+ }
399
+ }
400
+
401
+ // ============ PERSISTENCE ============
402
+
403
+ function timestamp() {
404
+ const d = new Date();
405
+ const pad = n => String(n).padStart(2, '0');
406
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
407
+ }
408
+
409
+ function formatMasterMd(ds) {
410
+ try {
411
+ if (!ds || typeof ds !== 'object') return '# Error: invalid design system object';
412
+ const project = ds.project_name || 'PROJECT';
413
+ const pattern = ds.pattern || {};
414
+ const style = ds.style || {};
415
+ const colors = ds.colors || {};
416
+ const typo = ds.typography || {};
417
+ const effects = ds.key_effects || '';
418
+ const antiPatterns = ds.anti_patterns || '';
419
+ const ts = timestamp();
420
+
421
+ const L = [];
422
+
423
+ L.push('# Design System Master File');
424
+ L.push('');
425
+ L.push('> **LOGIC:** When building a specific page, first check `design-system/pages/[page-name].md`.');
426
+ L.push('> If that file exists, its rules **override** this Master file.');
427
+ L.push('> If not, strictly follow the rules below.');
428
+ L.push('', '---', '');
429
+ L.push(`**Project:** ${project}`);
430
+ L.push(`**Generated:** ${ts}`);
431
+ L.push(`**Category:** ${ds.category || 'General'}`);
432
+ L.push('', '---', '');
433
+
434
+ // Global Rules
435
+ L.push('## Global Rules', '');
436
+
437
+ L.push('### Color Palette', '');
438
+ L.push('| Role | Hex | CSS Variable |');
439
+ L.push('|------|-----|--------------|');
440
+ L.push(`| Primary | \`${colors.primary || '#2563EB'}\` | \`--color-primary\` |`);
441
+ L.push(`| Secondary | \`${colors.secondary || '#3B82F6'}\` | \`--color-secondary\` |`);
442
+ L.push(`| CTA/Accent | \`${colors.cta || '#F97316'}\` | \`--color-cta\` |`);
443
+ L.push(`| Background | \`${colors.background || '#F8FAFC'}\` | \`--color-background\` |`);
444
+ L.push(`| Text | \`${colors.text || '#1E293B'}\` | \`--color-text\` |`);
445
+ L.push('');
446
+ if (colors.notes) L.push(`**Color Notes:** ${colors.notes}`, '');
447
+
448
+ L.push('### Typography', '');
449
+ L.push(`- **Heading Font:** ${typo.heading || 'Inter'}`);
450
+ L.push(`- **Body Font:** ${typo.body || 'Inter'}`);
451
+ if (typo.mood) L.push(`- **Mood:** ${typo.mood}`);
452
+ if (typo.google_fonts_url) L.push(`- **Google Fonts:** [${typo.heading || ''} + ${typo.body || ''}](${typo.google_fonts_url})`);
453
+ L.push('');
454
+ if (typo.css_import) L.push('**CSS Import:**', '```css', typo.css_import, '```', '');
455
+
456
+ L.push('### Spacing Variables', '');
457
+ L.push('| Token | Value | Usage |');
458
+ L.push('|-------|-------|-------|');
459
+ L.push('| `--space-xs` | `4px` / `0.25rem` | Tight gaps |');
460
+ L.push('| `--space-sm` | `8px` / `0.5rem` | Icon gaps, inline spacing |');
461
+ L.push('| `--space-md` | `16px` / `1rem` | Standard padding |');
462
+ L.push('| `--space-lg` | `24px` / `1.5rem` | Section padding |');
463
+ L.push('| `--space-xl` | `32px` / `2rem` | Large gaps |');
464
+ L.push('| `--space-2xl` | `48px` / `3rem` | Section margins |');
465
+ L.push('| `--space-3xl` | `64px` / `4rem` | Hero padding |');
466
+ L.push('');
467
+
468
+ L.push('### Shadow Depths', '');
469
+ L.push('| Level | Value | Usage |');
470
+ L.push('|-------|-------|-------|');
471
+ L.push('| `--shadow-sm` | `0 1px 2px rgba(0,0,0,0.05)` | Subtle lift |');
472
+ L.push('| `--shadow-md` | `0 4px 6px rgba(0,0,0,0.1)` | Cards, buttons |');
473
+ L.push('| `--shadow-lg` | `0 10px 15px rgba(0,0,0,0.1)` | Modals, dropdowns |');
474
+ L.push('| `--shadow-xl` | `0 20px 25px rgba(0,0,0,0.15)` | Hero images, featured cards |');
475
+ L.push('');
476
+
477
+ // Component Specs
478
+ L.push('---', '', '## Component Specs', '');
479
+
480
+ L.push('### Buttons', '');
481
+ L.push('```css');
482
+ L.push('/* Primary Button */');
483
+ L.push('.btn-primary {');
484
+ L.push(` background: ${colors.cta || '#F97316'};`);
485
+ L.push(' color: white;');
486
+ L.push(' padding: 12px 24px;');
487
+ L.push(' border-radius: 8px;');
488
+ L.push(' font-weight: 600;');
489
+ L.push(' transition: all 200ms ease;');
490
+ L.push(' cursor: pointer;');
491
+ L.push('}');
492
+ L.push('');
493
+ L.push('.btn-primary:hover {');
494
+ L.push(' opacity: 0.9;');
495
+ L.push(' transform: translateY(-1px);');
496
+ L.push('}');
497
+ L.push('');
498
+ L.push('/* Secondary Button */');
499
+ L.push('.btn-secondary {');
500
+ L.push(' background: transparent;');
501
+ L.push(` color: ${colors.primary || '#2563EB'};`);
502
+ L.push(` border: 2px solid ${colors.primary || '#2563EB'};`);
503
+ L.push(' padding: 12px 24px;');
504
+ L.push(' border-radius: 8px;');
505
+ L.push(' font-weight: 600;');
506
+ L.push(' transition: all 200ms ease;');
507
+ L.push(' cursor: pointer;');
508
+ L.push('}');
509
+ L.push('```', '');
510
+
511
+ L.push('### Cards', '');
512
+ L.push('```css');
513
+ L.push('.card {');
514
+ L.push(` background: ${colors.background || '#FFFFFF'};`);
515
+ L.push(' border-radius: 12px;');
516
+ L.push(' padding: 24px;');
517
+ L.push(' box-shadow: var(--shadow-md);');
518
+ L.push(' transition: all 200ms ease;');
519
+ L.push(' cursor: pointer;');
520
+ L.push('}');
521
+ L.push('');
522
+ L.push('.card:hover {');
523
+ L.push(' box-shadow: var(--shadow-lg);');
524
+ L.push(' transform: translateY(-2px);');
525
+ L.push('}');
526
+ L.push('```', '');
527
+
528
+ L.push('### Inputs', '');
529
+ L.push('```css');
530
+ L.push('.input {');
531
+ L.push(' padding: 12px 16px;');
532
+ L.push(' border: 1px solid #E2E8F0;');
533
+ L.push(' border-radius: 8px;');
534
+ L.push(' font-size: 16px;');
535
+ L.push(' transition: border-color 200ms ease;');
536
+ L.push('}');
537
+ L.push('');
538
+ L.push('.input:focus {');
539
+ L.push(` border-color: ${colors.primary || '#2563EB'};`);
540
+ L.push(' outline: none;');
541
+ L.push(` box-shadow: 0 0 0 3px ${colors.primary || '#2563EB'}20;`);
542
+ L.push('}');
543
+ L.push('```', '');
544
+
545
+ L.push('### Modals', '');
546
+ L.push('```css');
547
+ L.push('.modal-overlay {');
548
+ L.push(' background: rgba(0, 0, 0, 0.5);');
549
+ L.push(' backdrop-filter: blur(4px);');
550
+ L.push('}');
551
+ L.push('');
552
+ L.push('.modal {');
553
+ L.push(' background: white;');
554
+ L.push(' border-radius: 16px;');
555
+ L.push(' padding: 32px;');
556
+ L.push(' box-shadow: var(--shadow-xl);');
557
+ L.push(' max-width: 500px;');
558
+ L.push(' width: 90%;');
559
+ L.push('}');
560
+ L.push('```', '');
561
+
562
+ // Style Guidelines
563
+ L.push('---', '', '## Style Guidelines', '');
564
+ L.push(`**Style:** ${style.name || 'Minimalism'}`, '');
565
+ if (style.keywords) L.push(`**Keywords:** ${style.keywords}`, '');
566
+ if (style.best_for) L.push(`**Best For:** ${style.best_for}`, '');
567
+ if (effects) L.push(`**Key Effects:** ${effects}`, '');
568
+
569
+ L.push('### Page Pattern', '');
570
+ L.push(`**Pattern Name:** ${pattern.name || ''}`, '');
571
+ if (pattern.conversion) L.push(`- **Conversion Strategy:** ${pattern.conversion}`);
572
+ if (pattern.cta_placement) L.push(`- **CTA Placement:** ${pattern.cta_placement}`);
573
+ L.push(`- **Section Order:** ${pattern.sections || ''}`, '');
574
+
575
+ // Anti-Patterns
576
+ L.push('---', '', '## Anti-Patterns (Do NOT Use)', '');
577
+ if (antiPatterns) {
578
+ for (const a of antiPatterns.split('+').map(s => s.trim()).filter(Boolean)) {
579
+ L.push(`- ❌ ${a}`);
580
+ }
581
+ }
582
+ L.push('');
583
+ L.push('### Additional Forbidden Patterns', '');
584
+ L.push('- ❌ **Emojis as icons** — Use SVG icons (Heroicons, Lucide, Simple Icons)');
585
+ L.push('- ❌ **Missing cursor:pointer** — All clickable elements must have cursor:pointer');
586
+ L.push('- ❌ **Layout-shifting hovers** — Avoid scale transforms that shift layout');
587
+ L.push('- ❌ **Low contrast text** — Maintain 4.5:1 minimum contrast ratio');
588
+ L.push('- ❌ **Instant state changes** — Always use transitions (150-300ms)');
589
+ L.push('- ❌ **Invisible focus states** — Focus states must be visible for a11y');
590
+ L.push('');
591
+
592
+ // Pre-Delivery Checklist
593
+ L.push('---', '', '## Pre-Delivery Checklist', '');
594
+ L.push('Before delivering any UI code, verify:', '');
595
+ L.push('- [ ] No emojis used as icons (use SVG instead)');
596
+ L.push('- [ ] All icons from consistent icon set (Heroicons/Lucide)');
597
+ L.push('- [ ] `cursor-pointer` on all clickable elements');
598
+ L.push('- [ ] Hover states with smooth transitions (150-300ms)');
599
+ L.push('- [ ] Light mode: text contrast 4.5:1 minimum');
600
+ L.push('- [ ] Focus states visible for keyboard navigation');
601
+ L.push('- [ ] `prefers-reduced-motion` respected');
602
+ L.push('- [ ] Responsive: 375px, 768px, 1024px, 1440px');
603
+ L.push('- [ ] No content hidden behind fixed navbars');
604
+ L.push('- [ ] No horizontal scroll on mobile', '');
605
+
606
+ return L.join('\n');
607
+ } catch (err) {
608
+ console.error('Error formatting MASTER.md:', err?.message || err);
609
+ return `# Error: could not format design system (${err?.message || err})`;
610
+ }
611
+ }
612
+
613
+ // ============ PAGE OVERRIDES ============
614
+
615
+ function detectPageType(context, styleResults) {
616
+ const ctx = context.toLowerCase();
617
+ const patterns = [
618
+ [['dashboard', 'admin', 'analytics', 'data', 'metrics', 'stats', 'monitor', 'overview'], 'Dashboard / Data View'],
619
+ [['checkout', 'payment', 'cart', 'purchase', 'order', 'billing'], 'Checkout / Payment'],
620
+ [['settings', 'profile', 'account', 'preferences', 'config'], 'Settings / Profile'],
621
+ [['landing', 'marketing', 'homepage', 'hero', 'home', 'promo'], 'Landing / Marketing'],
622
+ [['login', 'signin', 'signup', 'register', 'auth', 'password'], 'Authentication'],
623
+ [['pricing', 'plans', 'subscription', 'tiers', 'packages'], 'Pricing / Plans'],
624
+ [['blog', 'article', 'post', 'news', 'content', 'story'], 'Blog / Article'],
625
+ [['product', 'item', 'detail', 'pdp', 'shop', 'store'], 'Product Detail'],
626
+ [['search', 'results', 'browse', 'filter', 'catalog', 'list'], 'Search Results'],
627
+ [['empty', '404', 'error', 'not found', 'zero'], 'Empty State']
628
+ ];
629
+
630
+ for (const [keywords, pageType] of patterns) {
631
+ if (keywords.some(kw => ctx.includes(kw))) return pageType;
632
+ }
633
+
634
+ if (styleResults && styleResults.length > 0) {
635
+ const bestFor = (styleResults[0]['Best For'] || '').toLowerCase();
636
+ if (bestFor.includes('dashboard') || bestFor.includes('data')) return 'Dashboard / Data View';
637
+ if (bestFor.includes('landing') || bestFor.includes('marketing')) return 'Landing / Marketing';
638
+ }
639
+
640
+ return 'General';
641
+ }
642
+
643
+ function generateIntelligentOverrides(pageName, pageQuery, _designSystem) {
644
+ try {
645
+ const pageLower = (pageName || '').toLowerCase();
646
+ const queryLower = (pageQuery || '').toLowerCase();
647
+ const combined = `${pageLower} ${queryLower}`;
648
+
649
+ let styleSearch = { results: [] };
650
+ let uxSearch = { results: [] };
651
+ let landingSearch = { results: [] };
652
+ try {
653
+ styleSearch = search(combined, 'style', 1);
654
+ } catch (err) {
655
+ console.error('Warning: style search failed in page overrides:', err?.message || err);
656
+ }
657
+ try {
658
+ uxSearch = search(combined, 'ux', 3);
659
+ } catch (err) {
660
+ console.error('Warning: ux search failed in page overrides:', err?.message || err);
661
+ }
662
+ try {
663
+ landingSearch = search(combined, 'landing', 1);
664
+ } catch (err) {
665
+ console.error('Warning: landing search failed in page overrides:', err?.message || err);
666
+ }
667
+
668
+ const styleResults = styleSearch.results || [];
669
+ const uxResults = uxSearch.results || [];
670
+ const landingResults = landingSearch.results || [];
671
+
672
+ const pageType = detectPageType(combined, styleResults);
673
+
674
+ const layout = {};
675
+ const spacing = {};
676
+ const typography = {};
677
+ const colors = {};
678
+ const components = [];
679
+ const uniqueComponents = [];
680
+ const recommendations = [];
681
+
682
+ if (styleResults.length > 0) {
683
+ const s = styleResults[0];
684
+ const keywords = (s.Keywords || '').toLowerCase();
685
+ const effects = s['Effects & Animation'] || '';
686
+
687
+ if (['data', 'dense', 'dashboard', 'grid'].some(kw => keywords.includes(kw))) {
688
+ layout['Max Width'] = '1400px or full-width';
689
+ layout['Grid'] = '12-column grid for data flexibility';
690
+ spacing['Content Density'] = 'High — optimize for information display';
691
+ } else if (['minimal', 'simple', 'clean', 'single'].some(kw => keywords.includes(kw))) {
692
+ layout['Max Width'] = '800px (narrow, focused)';
693
+ layout['Layout'] = 'Single column, centered';
694
+ spacing['Content Density'] = 'Low — focus on clarity';
695
+ } else {
696
+ layout['Max Width'] = '1200px (standard)';
697
+ layout['Layout'] = 'Full-width sections, centered content';
698
+ }
699
+
700
+ if (effects) recommendations.push(`Effects: ${effects}`);
701
+ }
702
+
703
+ for (const ux of uxResults) {
704
+ const cat = ux.Category || '';
705
+ const doText = ux.Do || '';
706
+ const dontText = ux["Don't"] || '';
707
+ if (doText) recommendations.push(`${cat}: ${doText}`);
708
+ if (dontText) components.push(`Avoid: ${dontText}`);
709
+ }
710
+
711
+ if (landingResults.length > 0) {
712
+ const l = landingResults[0];
713
+ if (l['Section Order']) layout['Sections'] = l['Section Order'];
714
+ if (l['Primary CTA Placement']) recommendations.push(`CTA Placement: ${l['Primary CTA Placement']}`);
715
+ if (l['Color Strategy']) colors['Strategy'] = l['Color Strategy'];
716
+ }
717
+
718
+ if (Object.keys(layout).length === 0) {
719
+ layout['Max Width'] = '1200px';
720
+ layout['Layout'] = 'Responsive grid';
721
+ }
722
+
723
+ if (recommendations.length === 0) {
724
+ recommendations.push('Refer to MASTER.md for all design rules', 'Add specific overrides as needed for this page');
725
+ }
726
+
727
+ return { page_type: pageType, layout, spacing, typography, colors, components, unique_components: uniqueComponents, recommendations };
728
+ } catch (err) {
729
+ console.error('Error generating page overrides:', err?.message || err);
730
+ return {
731
+ page_type: 'General',
732
+ layout: { 'Max Width': '1200px', Layout: 'Responsive grid' },
733
+ spacing: {},
734
+ typography: {},
735
+ colors: {},
736
+ components: [],
737
+ unique_components: [],
738
+ recommendations: ['Refer to MASTER.md for all design rules']
739
+ };
740
+ }
741
+ }
742
+
743
+ function formatPageOverrideMd(designSystem, pageName, pageQuery) {
744
+ try {
745
+ if (!designSystem || typeof designSystem !== 'object') return '# Error: invalid design system';
746
+ const project = designSystem.project_name || 'PROJECT';
747
+ const ts = timestamp();
748
+ const pageTitle = (pageName || 'Page').replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
749
+ const overrides = generateIntelligentOverrides(pageName, pageQuery, designSystem);
750
+
751
+ const L = [];
752
+
753
+ L.push(`# ${pageTitle} Page Overrides`, '');
754
+ L.push(`> **PROJECT:** ${project}`);
755
+ L.push(`> **Generated:** ${ts}`);
756
+ L.push(`> **Page Type:** ${overrides.page_type || 'General'}`, '');
757
+ L.push('> ⚠️ **IMPORTANT:** Rules in this file **override** the Master file (`design-system/MASTER.md`).');
758
+ L.push('> Only deviations from the Master are documented here. For all other rules, refer to the Master.');
759
+ L.push('', '---', '');
760
+
761
+ L.push('## Page-Specific Rules', '');
762
+
763
+ const section = (title, obj, fallback) => {
764
+ L.push(`### ${title}`, '');
765
+ const entries = Object.entries(obj || {});
766
+ if (entries.length > 0) {
767
+ for (const [k, v] of entries) L.push(`- **${k}:** ${v}`);
768
+ } else {
769
+ L.push(`- ${fallback}`);
770
+ }
771
+ L.push('');
772
+ };
773
+
774
+ section('Layout Overrides', overrides.layout, 'No overrides — use Master layout');
775
+ section('Spacing Overrides', overrides.spacing, 'No overrides — use Master spacing');
776
+ section('Typography Overrides', overrides.typography, 'No overrides — use Master typography');
777
+ section('Color Overrides', overrides.colors, 'No overrides — use Master colors');
778
+
779
+ L.push('### Component Overrides', '');
780
+ if (overrides.components && overrides.components.length > 0) {
781
+ for (const c of overrides.components) L.push(`- ${c}`);
782
+ } else {
783
+ L.push('- No overrides — use Master component specs');
784
+ }
785
+ L.push('');
786
+
787
+ L.push('---', '', '## Page-Specific Components', '');
788
+ if (overrides.unique_components && overrides.unique_components.length > 0) {
789
+ for (const c of overrides.unique_components) L.push(`- ${c}`);
790
+ } else {
791
+ L.push('- No unique components for this page');
792
+ }
793
+ L.push('');
794
+
795
+ L.push('---', '', '## Recommendations', '');
796
+ if (overrides.recommendations && overrides.recommendations.length > 0) {
797
+ for (const r of overrides.recommendations) L.push(`- ${r}`);
798
+ }
799
+ L.push('');
800
+
801
+ return L.join('\n');
802
+ } catch (err) {
803
+ console.error('Error formatting page override:', err?.message || err);
804
+ return `# Error: could not format page override (${err?.message || err})`;
805
+ }
806
+ }
807
+
808
+ function persistDesignSystem(designSystem, page, outputDir, pageQuery) {
809
+ try {
810
+ if (!designSystem || typeof designSystem !== 'object') {
811
+ return { status: 'error', error: 'Invalid design system object' };
812
+ }
813
+ const baseDir = outputDir || process.cwd();
814
+ const project = designSystem.project_name || 'default';
815
+ const projectSlug = String(project).toLowerCase().replace(/\s+/g, '-');
816
+
817
+ const dsDir = path.join(baseDir, 'design-system', projectSlug);
818
+ const pagesDir = path.join(dsDir, 'pages');
819
+ const createdFiles = [];
820
+
821
+ fs.mkdirSync(pagesDir, { recursive: true });
822
+
823
+ const masterFile = path.join(dsDir, 'MASTER.md');
824
+ fs.writeFileSync(masterFile, formatMasterMd(designSystem), 'utf-8');
825
+ createdFiles.push(masterFile);
826
+
827
+ if (page) {
828
+ const pageFile = path.join(pagesDir, `${String(page).toLowerCase().replace(/\s+/g, '-')}.md`);
829
+ fs.writeFileSync(pageFile, formatPageOverrideMd(designSystem, page, pageQuery), 'utf-8');
830
+ createdFiles.push(pageFile);
831
+ }
832
+
833
+ return { status: 'success', design_system_dir: dsDir, created_files: createdFiles };
834
+ } catch (err) {
835
+ console.error('Error persisting design system:', err?.message || err);
836
+ return { status: 'error', error: err?.message || String(err) };
837
+ }
838
+ }
839
+
840
+ // ============ MAIN ENTRY POINT ============
841
+
842
+ function generateDesignSystem(query, projectName, outputFormat = 'ascii', persist = false, page = null, outputDir = null) {
843
+ try {
844
+ const ds = generate(query, projectName);
845
+
846
+ if (persist) {
847
+ const persistResult = persistDesignSystem(ds, page, outputDir, query);
848
+ if (persistResult.status === 'error') {
849
+ console.error('Persist warning:', persistResult.error);
850
+ }
851
+ }
852
+
853
+ return outputFormat === 'markdown' ? formatMarkdown(ds) : formatAsciiBox(ds);
854
+ } catch (err) {
855
+ console.error('Error in generateDesignSystem:', err?.message || err);
856
+ const fallback = getDefaultDesignSystem(query, projectName);
857
+ return outputFormat === 'markdown' ? formatMarkdown(fallback) : formatAsciiBox(fallback);
858
+ }
859
+ }
860
+
861
+ export { generateDesignSystem, persistDesignSystem };