@decantr/cli 1.5.0 → 1.5.3

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.
@@ -2,6 +2,214 @@
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
+ import { computeSpatialTokens } from "@decantr/essence-spec";
6
+
7
+ // src/treatments.ts
8
+ function generateTreatmentCSS(spatialTokens, treatmentOverrides, recipeDecorators, recipeName) {
9
+ const lines = [];
10
+ lines.push("/* Generated by @decantr/cli \u2014 Visual Treatment System */");
11
+ lines.push("");
12
+ lines.push("/* \u2500\u2500 Layer 1: Base Treatments \u2500\u2500 */");
13
+ lines.push("");
14
+ function emitRule(selector, props) {
15
+ const treatmentName = selector.replace(/^\./, "").replace(/[:[\s].+$/, "");
16
+ const overrides = treatmentOverrides?.[treatmentName];
17
+ const merged = /* @__PURE__ */ new Map();
18
+ for (const [prop, val] of props) {
19
+ merged.set(prop, val);
20
+ }
21
+ if (overrides && selector === `.${treatmentName}`) {
22
+ for (const [prop, val] of Object.entries(overrides)) {
23
+ merged.set(prop, val);
24
+ }
25
+ }
26
+ const body = Array.from(merged.entries()).map(([p, v]) => ` ${p}: ${v};`).join("\n");
27
+ lines.push(`${selector} {`);
28
+ lines.push(body);
29
+ lines.push("}");
30
+ lines.push("");
31
+ }
32
+ emitRule(".d-interactive", [
33
+ ["display", "inline-flex"],
34
+ ["align-items", "center"],
35
+ ["gap", "0.5em"],
36
+ ["padding", "var(--d-interactive-py) var(--d-interactive-px)"],
37
+ ["border", "1px solid var(--d-border)"],
38
+ ["border-radius", "var(--d-radius)"],
39
+ ["background", "transparent"],
40
+ ["color", "var(--d-text)"],
41
+ ["font", "inherit"],
42
+ ["cursor", "pointer"],
43
+ ["text-decoration", "none"],
44
+ ["transition", "background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease"]
45
+ ]);
46
+ emitRule(".d-interactive:hover", [
47
+ ["border-color", "var(--d-primary-hover)"],
48
+ ["background", "var(--d-surface)"]
49
+ ]);
50
+ emitRule(".d-interactive:focus-visible", [
51
+ ["outline", "2px solid var(--d-primary)"],
52
+ ["outline-offset", "2px"]
53
+ ]);
54
+ emitRule(".d-interactive:disabled", [
55
+ ["opacity", "0.5"],
56
+ ["cursor", "not-allowed"],
57
+ ["pointer-events", "none"]
58
+ ]);
59
+ emitRule('.d-interactive[data-variant="primary"]', [
60
+ ["background", "var(--d-primary)"],
61
+ ["color", "#fff"],
62
+ ["border-color", "var(--d-primary)"]
63
+ ]);
64
+ emitRule('.d-interactive[data-variant="primary"]:hover', [
65
+ ["background", "var(--d-primary-hover)"],
66
+ ["border-color", "var(--d-primary-hover)"]
67
+ ]);
68
+ emitRule('.d-interactive[data-variant="ghost"]', [
69
+ ["border-color", "transparent"],
70
+ ["background", "transparent"]
71
+ ]);
72
+ emitRule('.d-interactive[data-variant="ghost"]:hover', [
73
+ ["background", "var(--d-surface)"]
74
+ ]);
75
+ emitRule('.d-interactive[data-variant="danger"]', [
76
+ ["background", "var(--d-error)"],
77
+ ["color", "#fff"],
78
+ ["border-color", "var(--d-error)"]
79
+ ]);
80
+ emitRule(".d-surface", [
81
+ ["background", "var(--d-surface)"],
82
+ ["border", "1px solid var(--d-border)"],
83
+ ["border-radius", "var(--d-radius)"],
84
+ ["box-shadow", "var(--d-shadow)"],
85
+ ["padding", "var(--d-surface-p)"]
86
+ ]);
87
+ emitRule('.d-surface[data-elevation="raised"]', [
88
+ ["background", "var(--d-surface-raised)"],
89
+ ["box-shadow", "var(--d-shadow-md)"]
90
+ ]);
91
+ emitRule('.d-surface[data-elevation="overlay"]', [
92
+ ["background", "var(--d-surface-raised)"],
93
+ ["box-shadow", "var(--d-shadow-lg)"],
94
+ ["z-index", "50"]
95
+ ]);
96
+ emitRule(".d-surface[data-interactive]:hover", [
97
+ ["border-color", "var(--d-primary-hover, var(--d-border))"],
98
+ ["box-shadow", "var(--d-shadow-md)"],
99
+ ["transition", "border-color 0.15s ease, box-shadow 0.15s ease"]
100
+ ]);
101
+ emitRule(".d-data", [
102
+ ["width", "100%"],
103
+ ["border-collapse", "collapse"],
104
+ ["text-align", "left"],
105
+ ["font-size", "0.875rem"]
106
+ ]);
107
+ emitRule(".d-data-header", [
108
+ ["padding", "var(--d-data-py) var(--d-content-gap)"],
109
+ ["font-weight", "500"],
110
+ ["color", "var(--d-text-muted)"],
111
+ ["border-bottom", "1px solid var(--d-border)"],
112
+ ["font-size", "0.75rem"],
113
+ ["text-transform", "uppercase"],
114
+ ["letter-spacing", "0.05em"]
115
+ ]);
116
+ emitRule(".d-data-row", [
117
+ ["border-bottom", "1px solid var(--d-border)"],
118
+ ["transition", "background 0.1s ease"]
119
+ ]);
120
+ emitRule(".d-data-row:hover", [
121
+ ["background", "var(--d-surface)"]
122
+ ]);
123
+ emitRule(".d-data-cell", [
124
+ ["padding", "var(--d-data-py) var(--d-content-gap)"],
125
+ ["vertical-align", "middle"]
126
+ ]);
127
+ emitRule(".d-control", [
128
+ ["background", "var(--d-surface)"],
129
+ ["color", "var(--d-text)"],
130
+ ["padding", "var(--d-control-py) 0.75rem"],
131
+ ["border-radius", "var(--d-radius)"],
132
+ ["border", "1px solid var(--d-border)"],
133
+ ["width", "100%"],
134
+ ["outline", "none"],
135
+ ["font", "inherit"],
136
+ ["transition", "border-color 0.15s ease, box-shadow 0.15s ease"]
137
+ ]);
138
+ emitRule(".d-control:focus", [
139
+ ["border-color", "var(--d-primary)"],
140
+ ["box-shadow", "0 0 0 3px color-mix(in srgb, var(--d-primary) 25%, transparent)"]
141
+ ]);
142
+ emitRule(".d-control::placeholder", [
143
+ ["color", "var(--d-text-muted)"]
144
+ ]);
145
+ emitRule(".d-control:disabled", [
146
+ ["opacity", "0.5"],
147
+ ["cursor", "not-allowed"]
148
+ ]);
149
+ emitRule(".d-control[aria-invalid]", [
150
+ ["border-color", "var(--d-error)"],
151
+ ["box-shadow", "0 0 0 3px color-mix(in srgb, var(--d-error) 15%, transparent)"]
152
+ ]);
153
+ emitRule(".d-section", [
154
+ ["padding", "var(--d-section-py) 0"]
155
+ ]);
156
+ lines.push(".d-section + .d-section {");
157
+ lines.push(" border-top: 1px solid var(--d-border);");
158
+ lines.push("}");
159
+ lines.push("");
160
+ emitRule(".d-annotation", [
161
+ ["display", "inline-flex"],
162
+ ["align-items", "center"],
163
+ ["gap", "0.25em"],
164
+ ["font-size", "0.75rem"],
165
+ ["font-weight", "500"],
166
+ ["padding", "0.125rem 0.5rem"],
167
+ ["border-radius", "var(--d-radius-full)"],
168
+ ["background", "var(--d-surface)"],
169
+ ["color", "var(--d-text-muted)"],
170
+ ["white-space", "nowrap"]
171
+ ]);
172
+ emitRule('.d-annotation[data-status="success"]', [
173
+ ["background", "color-mix(in srgb, var(--d-success) 15%, transparent)"],
174
+ ["color", "var(--d-success)"]
175
+ ]);
176
+ emitRule('.d-annotation[data-status="error"]', [
177
+ ["background", "color-mix(in srgb, var(--d-error) 15%, transparent)"],
178
+ ["color", "var(--d-error)"]
179
+ ]);
180
+ emitRule('.d-annotation[data-status="warning"]', [
181
+ ["background", "color-mix(in srgb, var(--d-warning) 15%, transparent)"],
182
+ ["color", "var(--d-warning)"]
183
+ ]);
184
+ emitRule('.d-annotation[data-status="info"]', [
185
+ ["background", "color-mix(in srgb, var(--d-info) 15%, transparent)"],
186
+ ["color", "var(--d-info)"]
187
+ ]);
188
+ if (recipeDecorators && Object.keys(recipeDecorators).length > 0) {
189
+ const label = recipeName ? ` (${recipeName})` : "";
190
+ lines.push(`/* \u2500\u2500 Layer 3: Recipe Decorators${label} \u2500\u2500 */`);
191
+ lines.push("");
192
+ for (const [name, description] of Object.entries(recipeDecorators)) {
193
+ const rule = generateDecoratorRule(name, description);
194
+ lines.push(rule);
195
+ lines.push("");
196
+ }
197
+ }
198
+ lines.push("/* \u2500\u2500 Keyframes \u2500\u2500 */");
199
+ lines.push("");
200
+ lines.push("@keyframes decantr-fade-in {");
201
+ lines.push(" from { opacity: 0; transform: translateY(4px); }");
202
+ lines.push(" to { opacity: 1; transform: translateY(0); }");
203
+ lines.push("}");
204
+ lines.push("");
205
+ lines.push("@keyframes decantr-pulse {");
206
+ lines.push(" 0%, 100% { opacity: 1; }");
207
+ lines.push(" 50% { opacity: 0.5; }");
208
+ lines.push("}");
209
+ return lines.join("\n");
210
+ }
211
+
212
+ // src/scaffold.ts
5
213
  var __dirname = dirname(fileURLToPath(import.meta.url));
6
214
  function composeArchetypes(composeEntries, archetypeResults) {
7
215
  if (composeEntries.length === 0) {
@@ -251,8 +459,9 @@ function generateTopologySection(data, personality) {
251
459
  return lines.join("\n");
252
460
  }
253
461
  var CLI_VERSION = "1.0.0";
254
- function generateTokensCSS(themeData, mode) {
462
+ function generateTokensCSS(themeData, mode, spatialTokens) {
255
463
  if (!themeData) {
464
+ const spatialLines2 = spatialTokens ? "\n" + Object.entries(spatialTokens).map(([k, v]) => ` ${k}: ${v};`).join("\n") : "";
256
465
  return `/* No theme data available */
257
466
  :root {
258
467
  --d-primary: #6366f1;
@@ -263,7 +472,7 @@ function generateTokensCSS(themeData, mode) {
263
472
  --d-surface-raised: #27272a;
264
473
  --d-border: #3f3f46;
265
474
  --d-text: #fafafa;
266
- --d-text-muted: #a1a1aa;
475
+ --d-text-muted: #a1a1aa;${spatialLines2}
267
476
  }
268
477
  `;
269
478
  }
@@ -298,11 +507,11 @@ function generateTokensCSS(themeData, mode) {
298
507
  "--d-radius-lg": "0.75rem",
299
508
  "--d-radius-xl": "1rem",
300
509
  "--d-radius-full": "9999px",
301
- // Shadows
302
- "--d-shadow-sm": "0 1px 2px rgba(0,0,0,0.05)",
303
- "--d-shadow": "0 1px 3px rgba(0,0,0,0.1)",
304
- "--d-shadow-md": "0 4px 6px rgba(0,0,0,0.1)",
305
- "--d-shadow-lg": "0 10px 15px rgba(0,0,0,0.1)",
510
+ // Shadows — dark mode needs higher opacity to be visible on dark backgrounds
511
+ "--d-shadow-sm": tokenMode === "light" ? "0 1px 2px rgba(0,0,0,0.05)" : "0 1px 2px rgba(0,0,0,0.2)",
512
+ "--d-shadow": tokenMode === "light" ? "0 1px 3px rgba(0,0,0,0.1)" : "0 1px 3px rgba(0,0,0,0.25)",
513
+ "--d-shadow-md": tokenMode === "light" ? "0 4px 6px rgba(0,0,0,0.1)" : "0 4px 6px rgba(0,0,0,0.3)",
514
+ "--d-shadow-lg": tokenMode === "light" ? "0 10px 15px rgba(0,0,0,0.1)" : "0 10px 15px rgba(0,0,0,0.4)",
306
515
  // Status colors
307
516
  "--d-success": themeData.tokens?.base?.success || "#22c55e",
308
517
  "--d-error": themeData.tokens?.base?.danger || "#ef4444",
@@ -312,14 +521,27 @@ function generateTokensCSS(themeData, mode) {
312
521
  }
313
522
  const tokens = buildTokens(resolvedMode);
314
523
  const lines = Object.entries(tokens).map(([key, value]) => ` ${key}: ${value};`).join("\n");
524
+ const spatialLines = spatialTokens ? "\n" + Object.entries(spatialTokens).map(([k, v]) => ` ${k}: ${v};`).join("\n") : "";
315
525
  let css = `/* Generated by @decantr/cli */
316
526
  :root {
317
- ${lines}
527
+ ${lines}${spatialLines}
318
528
  }
319
529
  `;
320
530
  if (mode === "auto") {
321
531
  const lightTokens = buildTokens("light");
322
- const paletteKeys = ["--d-bg", "--d-surface", "--d-surface-raised", "--d-border", "--d-text", "--d-text-muted", "--d-primary-hover"];
532
+ const paletteKeys = [
533
+ "--d-bg",
534
+ "--d-surface",
535
+ "--d-surface-raised",
536
+ "--d-border",
537
+ "--d-text",
538
+ "--d-text-muted",
539
+ "--d-primary-hover",
540
+ "--d-shadow-sm",
541
+ "--d-shadow",
542
+ "--d-shadow-md",
543
+ "--d-shadow-lg"
544
+ ];
323
545
  const lightLines = Object.entries(lightTokens).filter(([key]) => paletteKeys.includes(key)).map(([key, value]) => ` ${key}: ${value};`).join("\n");
324
546
  css += `
325
547
  @media (prefers-color-scheme: light) {
@@ -331,65 +553,103 @@ ${lightLines}
331
553
  }
332
554
  return css;
333
555
  }
334
- function generateDecoratorsCSS(recipeData, themeName) {
335
- if (!recipeData?.decorators) {
336
- return `/* No recipe decorators available */`;
337
- }
338
- const decorators = recipeData.decorators;
339
- const css = [
340
- `/* Generated by @decantr/cli from recipe: ${themeName} */`,
341
- ""
342
- ];
343
- for (const [name, description] of Object.entries(decorators)) {
344
- css.push(generateDecoratorRule(name, description));
345
- css.push("");
346
- }
347
- css.push(`/* Animation keyframes */
348
- @keyframes decantr-fade-in {
349
- from { opacity: 0; transform: translateY(8px); }
350
- to { opacity: 1; transform: translateY(0); }
556
+ function generateGlobalCSS(personality) {
557
+ const personalityText = personality.join(" ").toLowerCase();
558
+ let fontBody = "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
559
+ if (personalityText.includes("inter")) {
560
+ fontBody = `'Inter', ${fontBody}`;
561
+ } else if (personalityText.includes("geist")) {
562
+ fontBody = `'Geist', ${fontBody}`;
563
+ }
564
+ return `/* Generated by @decantr/cli \u2014 global reset + body styles */
565
+
566
+ *, *::before, *::after {
567
+ box-sizing: border-box;
568
+ margin: 0;
569
+ padding: 0;
351
570
  }
352
571
 
353
- @keyframes decantr-pulse {
354
- 0%, 100% { opacity: 1; }
355
- 50% { opacity: 0.5; }
572
+ html {
573
+ color-scheme: dark;
574
+ -webkit-font-smoothing: antialiased;
575
+ -moz-osx-font-smoothing: grayscale;
576
+ text-rendering: optimizeLegibility;
356
577
  }
357
- `);
358
- return css.join("\n");
578
+
579
+ body {
580
+ font-family: ${fontBody};
581
+ background: var(--d-bg);
582
+ color: var(--d-text);
583
+ line-height: 1.6;
584
+ min-height: 100dvh;
585
+ }
586
+
587
+ img, picture, video, canvas, svg {
588
+ display: block;
589
+ max-width: 100%;
590
+ }
591
+
592
+ input, button, textarea, select {
593
+ font: inherit;
594
+ color: inherit;
359
595
  }
360
- function generateDecoratorsContext(recipeData, recipeName) {
596
+
597
+ :focus-visible {
598
+ outline: 2px solid var(--d-primary);
599
+ outline-offset: 2px;
600
+ }
601
+
602
+ .sr-only {
603
+ position: absolute;
604
+ width: 1px;
605
+ height: 1px;
606
+ padding: 0;
607
+ margin: -1px;
608
+ overflow: hidden;
609
+ clip: rect(0, 0, 0, 0);
610
+ white-space: nowrap;
611
+ border-width: 0;
612
+ }
613
+ `;
614
+ }
615
+ function generateTreatmentsContext(recipeData, recipeName) {
361
616
  const lines = [];
362
- lines.push(`# Recipe Decorators: ${recipeName}`);
617
+ lines.push(`# Visual Treatments: ${recipeName}`);
618
+ lines.push("");
619
+ lines.push("## Base Treatments");
363
620
  lines.push("");
364
- lines.push("## Available Classes");
621
+ lines.push("d-interactive, d-surface, d-data, d-control, d-section, d-annotation \u2014 see DECANTR.md for usage.");
365
622
  lines.push("");
366
623
  if (recipeData?.decorators && Object.keys(recipeData.decorators).length > 0) {
367
- lines.push("| Decorator | Description |");
368
- lines.push("|-----------|-------------|");
624
+ lines.push(`## Recipe Decorators (${recipeName}-specific)`);
625
+ lines.push("");
626
+ lines.push("| Class | Use for |");
627
+ lines.push("|-------|---------|");
369
628
  for (const [name, description] of Object.entries(recipeData.decorators)) {
370
- lines.push(`| ${name} | ${description} |`);
629
+ const useFor = description.split(".")[0].trim();
630
+ lines.push(`| ${name} | ${useFor} |`);
371
631
  }
372
- } else {
373
- lines.push("No decorators defined.");
632
+ lines.push("");
374
633
  }
634
+ lines.push("## Composition");
375
635
  lines.push("");
376
- lines.push("## Usage");
377
- lines.push("");
378
- lines.push("Decorators are plain CSS class names from `src/styles/decorators.css`. Combine with atoms:");
379
- lines.push("");
636
+ lines.push("Atoms + treatment + recipe decorator:");
380
637
  lines.push("```tsx");
381
- lines.push("<div className={css('_flex _col _gap4') + ' " + recipeName + "-card'}>");
382
- lines.push(" <pre className={css('_p3') + ' " + recipeName + "-code'}>{code}</pre>");
383
- lines.push("</div>");
638
+ lines.push(`css('_flex _col _gap4') + ' d-surface'`);
384
639
  lines.push("```");
385
640
  lines.push("");
386
- lines.push("Atoms use `css()` function. Decorators are plain class strings. Combined via string concatenation.");
387
- lines.push("");
641
+ lines.push("Atoms use `css()` function. Treatments and recipe decorators are plain class strings.");
388
642
  return lines.join("\n");
389
643
  }
390
644
  function generateDecoratorRule(name, description) {
391
645
  const rules = [];
392
646
  const descLower = description.toLowerCase();
647
+ const nameLower = name.toLowerCase();
648
+ const isCard = descLower.includes("card") || descLower.includes("panel") || nameLower.includes("card") || nameLower.includes("panel");
649
+ const isInput = descLower.includes("input") || descLower.includes("field") || descLower.includes("textarea") || nameLower.includes("input") || nameLower.includes("textarea");
650
+ const isGlass = descLower.includes("glassmorphic") || descLower.includes("glass") || nameLower.includes("glass");
651
+ const isInteractive = isCard || isInput || isGlass;
652
+ const isNonInteractive = descLower.includes("divider") || descLower.includes("skeleton") || descLower.includes("keyframe") || descLower.includes("canvas") || nameLower.includes("divider") || nameLower.includes("skeleton") || nameLower.includes("canvas");
393
653
  if (descLower.includes("monospace") || descLower.includes("mono font")) {
394
654
  rules.push("font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace");
395
655
  }
@@ -402,6 +662,16 @@ function generateDecoratorRule(name, description) {
402
662
  } else if (descLower.includes("primary-tinted") || descLower.includes("primary background")) {
403
663
  rules.push("background: color-mix(in srgb, var(--d-primary) 15%, var(--d-surface))");
404
664
  }
665
+ if (isInput) {
666
+ if (!rules.some((r) => r.startsWith("background"))) {
667
+ rules.push("background: var(--d-surface)");
668
+ }
669
+ rules.push("color: var(--d-text)");
670
+ rules.push("padding: 0.5rem 0.75rem");
671
+ rules.push("border-radius: var(--d-radius)");
672
+ rules.push("width: 100%");
673
+ rules.push("outline: none");
674
+ }
405
675
  const leftBorderMatch = descLower.match(/(\d+)px\s+left\s+border/);
406
676
  if (leftBorderMatch) {
407
677
  rules.push(`border-left: ${leftBorderMatch[1]}px solid var(--d-primary)`);
@@ -415,23 +685,40 @@ function generateDecoratorRule(name, description) {
415
685
  const radiusMatch = descLower.match(/(\d+)px radius/);
416
686
  if (radiusMatch) {
417
687
  rules.push(`border-radius: ${radiusMatch[1]}px`);
418
- } else if (descLower.includes("radius") || descLower.includes("rounded")) {
688
+ } else if ((descLower.includes("radius") || descLower.includes("rounded")) && !isInput) {
419
689
  rules.push("border-radius: var(--d-radius)");
420
690
  }
421
- if (descLower.includes("hover shadow") || descLower.includes("shadow transition")) {
422
- rules.push("transition: box-shadow 0.15s ease");
423
- }
424
691
  if (descLower.includes("elevation") || descLower.includes("shadow")) {
425
692
  rules.push("box-shadow: var(--d-shadow)");
426
693
  }
694
+ if (isInteractive && !isNonInteractive) {
695
+ rules.push("transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease");
696
+ }
427
697
  if (descLower.includes("entrance animation") || descLower.includes("fade")) {
428
698
  rules.push("animation: decantr-fade-in 0.2s ease-out");
429
699
  }
430
700
  if (descLower.includes("pulse animation") || descLower.includes("skeleton")) {
431
701
  rules.push("animation: decantr-pulse 1.5s ease-in-out infinite");
432
702
  }
433
- if (descLower.includes("blur") || descLower.includes("glass")) {
703
+ const blurMatch = descLower.match(/blur\((\d+)px\)/);
704
+ if (blurMatch) {
705
+ rules.push(`backdrop-filter: blur(${blurMatch[1]}px)`);
706
+ rules.push(`-webkit-backdrop-filter: blur(${blurMatch[1]}px)`);
707
+ } else if (isGlass) {
708
+ rules.push("backdrop-filter: blur(12px)");
709
+ rules.push("-webkit-backdrop-filter: blur(12px)");
710
+ } else if (descLower.includes("blur")) {
434
711
  rules.push("backdrop-filter: blur(8px)");
712
+ rules.push("-webkit-backdrop-filter: blur(8px)");
713
+ }
714
+ if (descLower.includes("semi-transparent") || descLower.includes("glassmorphic")) {
715
+ const bgIdx = rules.findIndex((r) => r.startsWith("background:"));
716
+ const rgbaBg = "background: rgba(31, 31, 35, 0.8)";
717
+ if (bgIdx !== -1) {
718
+ rules[bgIdx] = rgbaBg;
719
+ } else {
720
+ rules.push(rgbaBg);
721
+ }
435
722
  }
436
723
  if (descLower.includes("right-aligned")) {
437
724
  rules.push("margin-left: auto");
@@ -455,9 +742,33 @@ function generateDecoratorRule(name, description) {
455
742
  if (rules.length === 0) {
456
743
  return `/* .${name}: ${description} */`;
457
744
  }
458
- return `.${name} {
745
+ let css = `.${name} {
459
746
  ${rules.join(";\n ")};
460
747
  }`;
748
+ if (isInteractive && !isNonInteractive) {
749
+ const stateRules = [];
750
+ if (isCard || isGlass) {
751
+ stateRules.push(`.${name}:hover {
752
+ border-color: var(--d-primary-hover, var(--d-border));
753
+ box-shadow: var(--d-shadow-md);
754
+ }`);
755
+ }
756
+ if (isInput) {
757
+ stateRules.push(`.${name}:focus {
758
+ border-color: var(--d-primary);
759
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--d-primary) 25%, transparent);
760
+ }`);
761
+ stateRules.push(`.${name}::placeholder {
762
+ color: var(--d-text-muted);
763
+ }`);
764
+ stateRules.push(`.${name}:disabled {
765
+ opacity: 0.5;
766
+ cursor: not-allowed;
767
+ }`);
768
+ }
769
+ css += "\n\n" + stateRules.join("\n\n");
770
+ }
771
+ return css;
461
772
  }
462
773
  function serializeLayoutItem(item) {
463
774
  if (typeof item === "string") {
@@ -692,219 +1003,80 @@ function buildEssenceV3(options, archetypeData, themeHints, recipeHints) {
692
1003
  }
693
1004
  var CSS_APPROACH_CONTENT = `## CSS Implementation
694
1005
 
695
- This project uses **@decantr/css** for layout atoms and the generated CSS files for theme tokens and recipe decorators.
1006
+ This project uses **@decantr/css** for layout atoms, **visual treatments** for semantic styling, and **recipe decorators** for theme-specific decoration.
696
1007
 
697
- ### Setup
698
-
699
- \`\`\`javascript
700
- // 1. Import the atoms runtime
701
- import { css } from '@decantr/css';
1008
+ ### Three File Setup
702
1009
 
703
- // 2. Import generated CSS files (created by decantr init)
704
- import './styles/tokens.css'; // Theme tokens (--d-primary, --d-surface, etc.)
705
- import './styles/decorators.css'; // Recipe decorators
706
1010
  \`\`\`
707
-
708
- ### Using Atoms
709
-
710
- The \`css()\` function processes atom strings and injects CSS at runtime:
711
-
712
- \`\`\`jsx
713
- // Layout atoms
714
- <div className={css('_flex _col _gap4 _p4')}>
715
- <h1 className={css('_heading1')}>Title</h1>
716
- <p className={css('_textsm _fgmuted')}>Description</p>
717
- </div>
718
-
719
- // Responsive prefixes (mobile-first)
720
- <div className={css('_gc1 _sm:gc2 _lg:gc4')}>
721
- {/* 1 col -> 2 cols at 640px -> 4 cols at 1024px */}
722
- </div>
1011
+ src/styles/
1012
+ tokens.css # Design tokens: --d-primary, --d-surface, --d-bg, etc.
1013
+ treatments.css # Visual treatments (d-interactive, d-surface, ...) + recipe decorators
1014
+ global.css # Resets, base typography, sr-only
723
1015
  \`\`\`
724
1016
 
725
- ### Atom Reference
726
-
727
- #### Display
728
- | Atom | CSS |
729
- |------|-----|
730
- | \`_flex\` | \`display:flex\` |
731
- | \`_grid\` | \`display:grid\` |
732
- | \`_block\` | \`display:block\` |
733
- | \`_inline\` | \`display:inline\` |
734
- | \`_inlineflex\` | \`display:inline-flex\` |
735
- | \`_none\` | \`display:none\` |
736
- | \`_contents\` | \`display:contents\` |
737
-
738
- #### Flexbox
739
- | Atom | CSS |
740
- |------|-----|
741
- | \`_col\` | \`flex-direction:column\` |
742
- | \`_row\` | \`flex-direction:row\` |
743
- | \`_colrev\` | \`flex-direction:column-reverse\` |
744
- | \`_wrap\` | \`flex-wrap:wrap\` |
745
- | \`_nowrap\` | \`flex-wrap:nowrap\` |
746
- | \`_flex1\` | \`flex:1\` |
747
- | \`_flex0\` | \`flex:none\` |
748
- | \`_flexauto\` | \`flex:auto\` |
749
- | \`_grow\` | \`flex-grow:1\` |
750
- | \`_grow0\` | \`flex-grow:0\` |
751
- | \`_shrink0\` | \`flex-shrink:0\` |
752
-
753
- #### Alignment
754
- | Atom | CSS |
755
- |------|-----|
756
- | \`_aic\` | \`align-items:center\` |
757
- | \`_aifs\` | \`align-items:flex-start\` |
758
- | \`_aife\` | \`align-items:flex-end\` |
759
- | \`_aist\` | \`align-items:stretch\` |
760
- | \`_aibl\` | \`align-items:baseline\` |
761
- | \`_jcc\` | \`justify-content:center\` |
762
- | \`_jcfs\` | \`justify-content:flex-start\` |
763
- | \`_jcfe\` | \`justify-content:flex-end\` |
764
- | \`_jcsb\` | \`justify-content:space-between\` |
765
- | \`_jcsa\` | \`justify-content:space-around\` |
766
- | \`_jcse\` | \`justify-content:space-evenly\` |
767
- | \`_pic\` | \`place-items:center\` |
768
- | \`_pcc\` | \`place-content:center\` |
769
-
770
- #### Spacing (scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, ...)
771
- | Atom | CSS | Notes |
772
- |------|-----|-------|
773
- | \`_gap{n}\` | \`gap:{scale}\` | e.g. \`_gap4\` = \`gap:1rem\` |
774
- | \`_gx{n}\` | \`column-gap:{scale}\` | horizontal gap |
775
- | \`_gy{n}\` | \`row-gap:{scale}\` | vertical gap |
776
- | \`_p{n}\` | \`padding:{scale}\` | all sides |
777
- | \`_pt{n}\`, \`_pr{n}\`, \`_pb{n}\`, \`_pl{n}\` | directional padding | top/right/bottom/left |
778
- | \`_px{n}\` | \`padding-inline:{scale}\` | horizontal |
779
- | \`_py{n}\` | \`padding-block:{scale}\` | vertical |
780
- | \`_m{n}\` | \`margin:{scale}\` | same as padding variants |
781
- | \`_mx{n}\`, \`_my{n}\` | inline/block margin | horizontal/vertical |
1017
+ \`\`\`javascript
1018
+ import { css } from '@decantr/css'; // Atoms runtime
1019
+ import './styles/tokens.css'; // Theme tokens
1020
+ import './styles/treatments.css'; // Treatments + recipe decorators
1021
+ import './styles/global.css'; // Resets
1022
+ \`\`\`
782
1023
 
783
- #### Sizing
784
- | Atom | CSS |
785
- |------|-----|
786
- | \`_wfull\` / \`_w100\` | \`width:100%\` |
787
- | \`_hfull\` / \`_h100\` | \`height:100%\` |
788
- | \`_wscreen\` | \`width:100vw\` |
789
- | \`_hscreen\` | \`height:100vh\` |
790
- | \`_wfit\` | \`width:fit-content\` |
791
- | \`_hfit\` | \`height:fit-content\` |
792
- | \`_wauto\` | \`width:auto\` |
793
- | \`_minw0\` | \`min-width:0\` |
794
- | \`_minh0\` | \`min-height:0\` |
795
- | \`_w{n}\`, \`_h{n}\` | width/height from spacing scale |
796
- | \`_minw{n}\`, \`_maxw{n}\` | min/max width from scale |
1024
+ ### Visual Treatments
797
1025
 
798
- #### Text Size
799
- | Atom | Size | Line-height |
800
- |------|------|-------------|
801
- | \`_textxs\` | 0.75rem | 1rem |
802
- | \`_textsm\` | 0.875rem | 1.25rem |
803
- | \`_textbase\` | 1rem | 1.5rem |
804
- | \`_textlg\` | 1.125rem | 1.75rem |
805
- | \`_textxl\` | 1.25rem | 1.75rem |
806
- | \`_text2xl\` | 1.5rem | 2rem |
807
- | \`_text3xl\` | 1.875rem | 2.25rem |
808
- | \`_heading1\`-\`_heading6\` | Heading presets (size + weight) |
1026
+ Six base treatment classes provide semantic styling. Combine with atoms for layout:
809
1027
 
810
- #### Text Style
811
- | Atom | CSS |
812
- |------|-----|
813
- | \`_fontbold\` | \`font-weight:700\` |
814
- | \`_fontsemi\` | \`font-weight:600\` |
815
- | \`_fontmedium\` | \`font-weight:500\` |
816
- | \`_fontlight\` | \`font-weight:300\` |
817
- | \`_italic\` | \`font-style:italic\` |
818
- | \`_underline\` | \`text-decoration:underline\` |
819
- | \`_uppercase\` | \`text-transform:uppercase\` |
820
- | \`_truncate\` | overflow ellipsis + nowrap |
821
- | \`_textl\`, \`_textc\`, \`_textr\` | text-align left/center/right |
1028
+ | Treatment | Class | Variants / States |
1029
+ |-----------|-------|-------------------|
1030
+ | **Interactive Surface** | \`d-interactive\` | \`data-variant="primary\\|ghost\\|danger"\`, hover/focus-visible/disabled states |
1031
+ | **Container Surface** | \`d-surface\` | \`data-variant="raised\\|overlay"\`, optional \`data-interactive\` for hover |
1032
+ | **Data Display** | \`d-data\`, \`d-data-header\`, \`d-data-row\`, \`d-data-cell\` | Row hover highlight |
1033
+ | **Form Control** | \`d-control\` | Focus ring, placeholder, disabled, error via \`aria-invalid\` |
1034
+ | **Section Rhythm** | \`d-section\` | Auto-spacing between adjacent sections, density-aware |
1035
+ | **Inline Annotation** | \`d-annotation\` | \`data-status="success\\|error\\|warning\\|info"\` |
822
1036
 
823
- #### Color (theme variable based)
824
- | Atom | CSS |
825
- |------|-----|
826
- | \`_bgprimary\` | \`background:var(--d-primary)\` |
827
- | \`_bgsurface\` | \`background:var(--d-surface)\` |
828
- | \`_bgsurface0\`-\`_bgsurface2\` | surface elevation layers |
829
- | \`_bgmuted\` | \`background:var(--d-muted)\` |
830
- | \`_bgbg\` | \`background:var(--d-bg)\` |
831
- | \`_bgsuccess\`, \`_bgerror\`, \`_bgwarning\`, \`_bginfo\` | status backgrounds |
832
- | \`_fgprimary\` | \`color:var(--d-primary)\` |
833
- | \`_fgtext\` | \`color:var(--d-text)\` |
834
- | \`_fgmuted\` | \`color:var(--d-text-muted)\` |
835
- | \`_fgsuccess\`, \`_fgerror\`, \`_fgwarning\`, \`_fginfo\` | status text |
836
- | \`_bcborder\` | \`border-color:var(--d-border)\` |
1037
+ ### Composition
837
1038
 
838
- #### Overflow & Whitespace
839
- | Atom | CSS |
840
- |------|-----|
841
- | \`_overhidden\` | \`overflow:hidden\` |
842
- | \`_overauto\` | \`overflow:auto\` |
843
- | \`_overscroll\` | \`overflow:scroll\` |
844
- | \`_overxauto\`, \`_overyauto\` | axis-specific overflow |
845
- | \`_nowraptext\` | \`white-space:nowrap\` |
846
- | \`_prewrap\` | \`white-space:pre-wrap\` |
847
- | \`_breakword\` | \`overflow-wrap:break-word\` |
1039
+ Atoms + treatment + recipe decorator:
848
1040
 
849
- #### Cursor & Interaction
850
- | Atom | CSS |
851
- |------|-----|
852
- | \`_pointer\` | \`cursor:pointer\` |
853
- | \`_cursordefault\` | \`cursor:default\` |
854
- | \`_notallowed\` | \`cursor:not-allowed\` |
855
- | \`_grab\` | \`cursor:grab\` |
856
- | \`_selectnone\` | \`user-select:none\` |
857
- | \`_ptrnone\` | \`pointer-events:none\` |
1041
+ \`\`\`tsx
1042
+ <button className={css('_px4 _py2') + ' d-interactive'} data-variant="primary">Deploy</button>
1043
+ <div className={css('_flex _col _gap4') + ' d-surface carbon-glass'}>Card</div>
1044
+ <span className="d-annotation" data-status="success">Active</span>
1045
+ \`\`\`
858
1046
 
859
- #### Position & Layout
860
- | Atom | CSS |
861
- |------|-----|
862
- | \`_rel\` | \`position:relative\` |
863
- | \`_abs\` | \`position:absolute\` |
864
- | \`_fixed\` | \`position:fixed\` |
865
- | \`_sticky\` | \`position:sticky\` |
866
- | \`_inset0\` | \`inset:0\` |
867
- | \`_top0\`, \`_right0\`, \`_bottom0\`, \`_left0\` | edge positioning |
868
- | \`_z10\`-\`_z50\` | z-index scale |
1047
+ - **Atoms:** \`css('_flex _col _gap4')\` \u2014 processed by @decantr/css runtime
1048
+ - **Treatments:** \`d-interactive\`, \`d-surface\` \u2014 semantic base styles from treatments.css
1049
+ - **Recipe decorators:** \`carbon-glass\`, \`carbon-code\` \u2014 theme-specific decoration from treatments.css
1050
+ - **Combined:** \`css('_flex _col') + ' d-surface carbon-card'\`
869
1051
 
870
- #### Grid
871
- | Atom | CSS |
872
- |------|-----|
873
- | \`_gc1\`-\`_gc12\` | \`grid-template-columns:repeat(N,...)\` |
874
- | \`_gr1\`-\`_gr6\` | \`grid-template-rows:repeat(N,...)\` |
875
- | \`_span1\`-\`_span12\`, \`_spanfull\` | column span |
876
- | \`_rowspan1\`-\`_rowspan6\` | row span |
1052
+ ### Atoms Quick Reference
877
1053
 
878
- #### Visual
879
- | Atom | CSS |
880
- |------|-----|
881
- | \`_rounded\` | \`border-radius:var(--d-radius)\` |
882
- | \`_roundedfull\` | \`border-radius:9999px\` |
883
- | \`_roundedsm\`, \`_roundedlg\`, \`_roundedxl\` | radius variants |
884
- | \`_shadow\`, \`_shadowmd\`, \`_shadowlg\` | box-shadow presets |
885
- | \`_bordernone\` | \`border:none\` |
886
- | \`_bw{n}\` | \`border-width:{n}px\` |
887
- | \`_op0\`-\`_op100\` | opacity (0, 25, 50, 75, 100) |
888
- | \`_trans\` | \`transition:all 0.15s ease\` |
889
- | \`_visible\`, \`_invisible\` | visibility |
1054
+ | Category | Examples | Purpose |
1055
+ |----------|----------|---------|
1056
+ | Layout | \`_flex\`, \`_col\`, \`_row\`, \`_wrap\`, \`_grid\` | Flex/grid containers |
1057
+ | Spacing | \`_gap4\`, \`_p4\`, \`_px4\`, \`_py2\`, \`_m0\` | Gaps, padding, margin |
1058
+ | Sizing | \`_w100\`, \`_h100\`, \`_minw0\`, \`_maxwfull\` | Width, height |
1059
+ | Text | \`_textlg\`, \`_text2xl\`, \`_fontbold\`, \`_textc\` | Typography |
1060
+ | Alignment | \`_aic\`, \`_jcc\`, \`_jcsb\`, \`_pic\` | Flex/grid alignment |
1061
+ | Position | \`_rel\`, \`_abs\`, \`_sticky\`, \`_z10\` | Positioning |
1062
+ | Visual | \`_rounded\`, \`_shadow\`, \`_trans\`, \`_op50\` | Decoration |
1063
+ | Color | \`_bgprimary\`, \`_fgtext\`, \`_fgmuted\`, \`_bcborder\` | Theme colors |
1064
+ | Responsive | \`_md:gc2\`, \`_lg:gc4\`, \`_sm:flex\` | Breakpoint prefixes |
890
1065
 
891
- ### Using Recipe Decorators
1066
+ Scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24. Example: \`_gap4\` = \`gap:1rem\`.
892
1067
 
893
- Recipe decorators (from \`src/styles/decorators.css\`) are regular CSS class names, NOT atoms. They are applied directly as class names and combined with atoms using string concatenation:
1068
+ ### Design Tokens
894
1069
 
895
- \`\`\`tsx
896
- // Atoms use css() function, decorators are plain class names
897
- <div className={css('_flex _col _gap4') + ' carbon-card'}>
898
- <div className={css('_p4') + ' carbon-glass'}>
899
- <pre className={css('_p3') + ' carbon-code'}>{code}</pre>
900
- </div>
901
- </div>
902
- \`\`\`
903
-
904
- **Key difference:**
905
- - Atoms: \`css('_flex _col _gap4')\` \u2014 processed by @decantr/css runtime
906
- - Decorators: \`'carbon-card'\`, \`'carbon-glass'\` \u2014 plain CSS classes from decorators.css
907
- - Combined: \`css('_flex _col') + ' carbon-card'\`
1070
+ | Token | Purpose |
1071
+ |-------|---------|
1072
+ | \`--d-primary\` | Primary brand color |
1073
+ | \`--d-surface\`, \`--d-surface-raised\` | Surface backgrounds |
1074
+ | \`--d-bg\` | Page background |
1075
+ | \`--d-border\` | Border color |
1076
+ | \`--d-text\`, \`--d-text-muted\` | Text colors |
1077
+ | \`--d-success\`, \`--d-error\`, \`--d-warning\`, \`--d-info\` | Status colors |
1078
+ | \`--d-shadow\`, \`--d-shadow-lg\` | Elevation shadows |
1079
+ | \`--d-radius\`, \`--d-radius-lg\` | Border radii |
908
1080
 
909
1081
  ### Routing
910
1082
 
@@ -912,28 +1084,7 @@ Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing
912
1084
  - \`"hash"\` \u2192 use \`HashRouter\` (e.g., for static hosting, GitHub Pages)
913
1085
  - \`"history"\` \u2192 use \`BrowserRouter\` (e.g., for server-rendered apps)
914
1086
 
915
- Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.
916
-
917
- ### CSS Architecture
918
-
919
- The CSS is organized into two parts:
920
-
921
- 1. **Atoms (@decantr/css)** - Layout utilities injected at runtime into \`@layer d.atoms\`
922
- 2. **Generated CSS files** - Theme tokens and recipe decorators created during scaffold
923
-
924
- \`\`\`
925
- src/styles/
926
- tokens.css # :root { --d-primary: #...; --d-surface: #...; }
927
- decorators.css # .recipe-card { ... }
928
- \`\`\`
929
-
930
- ### Variable Naming Convention
931
-
932
- | Prefix | Purpose | Example |
933
- |--------|---------|---------|
934
- | \`--d-\` | Core Decantr tokens | \`--d-primary\`, \`--d-bg\` |
935
- | \`--d-gap-{n}\` | Spacing tokens | \`--d-gap-4\`, \`--d-gap-8\` |
936
- | \`--d-radius\` | Border radius | \`--d-radius\`, \`--d-radius-lg\` |`;
1087
+ Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.`;
937
1088
  function generateDecantrMdV31(guardMode, cssApproach) {
938
1089
  const template = loadTemplate("DECANTR.md.template");
939
1090
  return renderTemplate(template, {
@@ -1006,33 +1157,29 @@ function generateTaskContext(templateName, essence) {
1006
1157
  };
1007
1158
  return renderTemplate(template, vars);
1008
1159
  }
1009
- function generateEssenceSummary(essence) {
1010
- const template = loadTemplate("essence-summary.md.template");
1011
- const pagesTable = `| Page | Shell | Layout |
1012
- |------|-------|--------|
1013
- ${essence.structure.map((p) => `| ${p.id} | ${p.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`).join("\n")}`;
1014
- const featuresList = essence.features.length > 0 ? essence.features.map((f) => `- ${f}`).join("\n") : "- No features specified";
1015
- const dnaEnforcement = essence.guard.enforce_style ? "error" : "off";
1016
- const blueprintEnforcement = essence.guard.enforce_recipe ? "warn" : "off";
1160
+ function generateTaskContextV3(templateName, essence) {
1161
+ const template = loadTemplate(templateName);
1162
+ const pages = essence.blueprint.sections && essence.blueprint.sections.length > 0 ? essence.blueprint.sections.flatMap((s) => s.pages) : essence.blueprint.pages || [];
1163
+ const defaultShell = essence.blueprint.sections?.[0]?.shell || essence.blueprint.shell || "sidebar-main";
1164
+ const layout = pages[0]?.layout?.map(serializeLayoutItem).join(", ") || "none";
1165
+ const scaffoldStructure = pages.map((p) => {
1166
+ const patterns = p.layout.length > 0 ? `
1167
+ - Patterns: ${p.layout.map(serializeLayoutItem).join(", ")}` : "";
1168
+ return `- **${p.id}** (${defaultShell})${patterns}`;
1169
+ }).join("\n");
1170
+ const densityLevel = essence.dna.spacing?.density || "comfortable";
1171
+ const contentGap = essence.dna.spacing?.content_gap || "_gap4";
1017
1172
  const vars = {
1018
- ARCHETYPE: essence.archetype || "custom",
1019
- BLUEPRINT: essence.blueprint || "none",
1020
- PERSONALITY: essence.personality.join(", "),
1021
- TARGET: essence.target,
1022
- THEME_STYLE: essence.theme.style,
1023
- THEME_MODE: essence.theme.mode,
1024
- THEME_RECIPE: essence.theme.recipe,
1025
- SHAPE: essence.theme.shape,
1026
- PAGES_TABLE: pagesTable,
1027
- FEATURES_LIST: featuresList,
1028
- GUARD_MODE: essence.guard.mode,
1029
- ENFORCE_STYLE: String(essence.guard.enforce_style),
1030
- ENFORCE_RECIPE: String(essence.guard.enforce_recipe),
1031
- DNA_ENFORCEMENT: dnaEnforcement,
1032
- BLUEPRINT_ENFORCEMENT: blueprintEnforcement,
1033
- DENSITY: essence.density.level,
1034
- CONTENT_GAP: essence.density.content_gap,
1035
- LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
1173
+ TARGET: essence.meta.target || "react",
1174
+ THEME_STYLE: essence.dna.theme.style,
1175
+ THEME_MODE: essence.dna.theme.mode,
1176
+ THEME_RECIPE: essence.dna.theme.recipe || essence.dna.theme.style,
1177
+ DEFAULT_SHELL: defaultShell,
1178
+ GUARD_MODE: essence.meta.guard.mode,
1179
+ LAYOUT: layout,
1180
+ DENSITY: densityLevel,
1181
+ CONTENT_GAP: contentGap,
1182
+ SCAFFOLD_STRUCTURE: scaffoldStructure
1036
1183
  };
1037
1184
  return renderTemplate(template, vars);
1038
1185
  }
@@ -1116,15 +1263,6 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1116
1263
  const scaffoldPath = join(contextDir, "task-scaffold.md");
1117
1264
  writeFileSync(scaffoldPath, generateTaskContext("task-scaffold.md.template", essence));
1118
1265
  contextFiles.push(scaffoldPath);
1119
- const addPagePath = join(contextDir, "task-add-page.md");
1120
- writeFileSync(addPagePath, generateTaskContext("task-add-page.md.template", essence));
1121
- contextFiles.push(addPagePath);
1122
- const modifyPath = join(contextDir, "task-modify.md");
1123
- writeFileSync(modifyPath, generateTaskContext("task-modify.md.template", essence));
1124
- contextFiles.push(modifyPath);
1125
- const summaryPath = join(contextDir, "essence-summary.md");
1126
- writeFileSync(summaryPath, generateEssenceSummary(essence));
1127
- contextFiles.push(summaryPath);
1128
1266
  if (composedSections) {
1129
1267
  essenceV3.version = "3.1.0";
1130
1268
  essenceV3.blueprint = {
@@ -1133,7 +1271,7 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1133
1271
  routes: routeMap || {}
1134
1272
  };
1135
1273
  if (blueprintData?.personality?.length) {
1136
- essenceV3.dna.personality = blueprintData.personality;
1274
+ essenceV3.dna.personality = typeof blueprintData.personality === "string" ? [blueprintData.personality] : blueprintData.personality;
1137
1275
  }
1138
1276
  if (blueprintData?.design_constraints) {
1139
1277
  essenceV3.dna.constraints = blueprintData.design_constraints;
@@ -1146,7 +1284,7 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1146
1284
  }
1147
1285
  writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
1148
1286
  }
1149
- const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, recipeData);
1287
+ const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, recipeData, { isInitialScaffold: true });
1150
1288
  contextFiles.push(...refreshResult.contextFiles);
1151
1289
  const gitignoreUpdated = updateGitignore(projectRoot);
1152
1290
  return {
@@ -1348,7 +1486,7 @@ When available, use these tools:
1348
1486
  gitignoreUpdated
1349
1487
  };
1350
1488
  }
1351
- async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThemeData, prefetchedRecipeData) {
1489
+ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThemeData, prefetchedRecipeData, options) {
1352
1490
  const decantrDir = join(projectRoot, ".decantr");
1353
1491
  const contextDir = join(decantrDir, "context");
1354
1492
  mkdirSync(contextDir, { recursive: true });
@@ -1388,7 +1526,8 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1388
1526
  recipeData = {
1389
1527
  decorators: r.decorators,
1390
1528
  spatial_hints: r.spatial_hints,
1391
- radius_hints: r.radius_hints
1529
+ radius_hints: r.radius_hints,
1530
+ treatment_overrides: r.treatment_overrides
1392
1531
  };
1393
1532
  if (!recipeData.decorators && raw.data) {
1394
1533
  const inner = raw.data;
@@ -1412,7 +1551,8 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1412
1551
  recipeData = {
1413
1552
  decorators: inner.decorators,
1414
1553
  spatial_hints: inner.spatial_hints,
1415
- radius_hints: inner.radius_hints
1554
+ radius_hints: inner.radius_hints,
1555
+ treatment_overrides: inner.treatment_overrides
1416
1556
  };
1417
1557
  }
1418
1558
  }
@@ -1442,24 +1582,48 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1442
1582
  }
1443
1583
  const stylesDir = join(projectRoot, "src", "styles");
1444
1584
  mkdirSync(stylesDir, { recursive: true });
1585
+ const densityLevel = options.density || "comfortable";
1586
+ const spatialTokens = computeSpatialTokens(densityLevel, recipeData?.spatial_hints ? {
1587
+ section_padding: recipeData.spatial_hints.section_padding ?? void 0,
1588
+ density_bias: typeof recipeData.spatial_hints.density_bias === "number" ? recipeData.spatial_hints.density_bias : void 0,
1589
+ content_gap_shift: recipeData.spatial_hints.content_gap_shift
1590
+ } : void 0);
1445
1591
  const tokensPath = join(stylesDir, "tokens.css");
1446
1592
  const hasRealThemeData = themeData?.seed?.primary || themeData?.palette?.background;
1447
1593
  if (hasRealThemeData || !existsSync(tokensPath)) {
1448
- writeFileSync(tokensPath, generateTokensCSS(themeData, mode));
1449
- }
1450
- const decoratorsPath = join(stylesDir, "decorators.css");
1451
- const hasRealRecipeData = recipeData?.decorators && Object.keys(recipeData.decorators).length > 0;
1452
- if (hasRealRecipeData || !existsSync(decoratorsPath)) {
1453
- writeFileSync(decoratorsPath, generateDecoratorsCSS(recipeData, themeName));
1454
- }
1455
- const cssFiles = [tokensPath, decoratorsPath];
1456
- const decoratorsMdPath = join(contextDir, "decorators.md");
1457
- writeFileSync(decoratorsMdPath, generateDecoratorsContext(recipeData, recipeName));
1594
+ writeFileSync(tokensPath, generateTokensCSS(themeData, mode, spatialTokens));
1595
+ }
1596
+ const treatmentsPath = join(stylesDir, "treatments.css");
1597
+ writeFileSync(treatmentsPath, generateTreatmentCSS(
1598
+ spatialTokens,
1599
+ recipeData?.treatment_overrides,
1600
+ recipeData?.decorators,
1601
+ themeName
1602
+ ));
1603
+ const globalPath = join(stylesDir, "global.css");
1604
+ if (!existsSync(globalPath)) {
1605
+ writeFileSync(globalPath, generateGlobalCSS(personality));
1606
+ }
1607
+ const cssFiles = [tokensPath, treatmentsPath, globalPath];
1608
+ const treatmentsMdPath = join(contextDir, "treatments.md");
1609
+ writeFileSync(treatmentsMdPath, generateTreatmentsContext(recipeData, recipeName));
1458
1610
  const decantrMdPath = join(projectRoot, "DECANTR.md");
1459
1611
  writeFileSync(decantrMdPath, generateDecantrMdV31(guardMode, CSS_APPROACH_CONTENT));
1460
- const summaryPath = join(contextDir, "essence-summary.md");
1461
- writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
1462
- const contextFiles = [decoratorsMdPath, summaryPath];
1612
+ const hasSections = essence.blueprint.sections && essence.blueprint.sections.length > 0;
1613
+ const contextFiles = [treatmentsMdPath];
1614
+ if (!hasSections) {
1615
+ const summaryPath = join(contextDir, "essence-summary.md");
1616
+ writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
1617
+ contextFiles.push(summaryPath);
1618
+ }
1619
+ if (!options?.isInitialScaffold) {
1620
+ const addPagePath = join(contextDir, "task-add-page.md");
1621
+ writeFileSync(addPagePath, generateTaskContextV3("task-add-page.md.template", essence));
1622
+ contextFiles.push(addPagePath);
1623
+ const modifyPath = join(contextDir, "task-modify.md");
1624
+ writeFileSync(modifyPath, generateTaskContextV3("task-modify.md.template", essence));
1625
+ contextFiles.push(modifyPath);
1626
+ }
1463
1627
  const blueprint = essence.blueprint;
1464
1628
  const sections = blueprint.sections && blueprint.sections.length > 0 ? blueprint.sections : [];
1465
1629
  if (sections.length > 0) {
@@ -1492,21 +1656,28 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1492
1656
  slots = synthetic;
1493
1657
  }
1494
1658
  }
1495
- patternSpecs[name] = {
1659
+ const spec = {
1496
1660
  description: inner.description || "",
1497
1661
  components: inner.components || [],
1498
1662
  slots
1499
1663
  };
1664
+ if (!spec.components || spec.components.length === 0) {
1665
+ const syntheticComps = generateSyntheticComponents(name, spec.description);
1666
+ if (syntheticComps.length > 0) spec.components = syntheticComps;
1667
+ }
1668
+ patternSpecs[name] = spec;
1500
1669
  } else {
1501
1670
  const synthetic = generateSyntheticSlots(name, "");
1502
- if (Object.keys(synthetic).length > 0) {
1503
- patternSpecs[name] = { description: "", components: [], slots: synthetic };
1671
+ const syntheticComps = generateSyntheticComponents(name, "");
1672
+ if (Object.keys(synthetic).length > 0 || syntheticComps.length > 0) {
1673
+ patternSpecs[name] = { description: "", components: syntheticComps, slots: synthetic };
1504
1674
  }
1505
1675
  }
1506
1676
  } catch {
1507
1677
  const synthetic = generateSyntheticSlots(name, "");
1508
- if (Object.keys(synthetic).length > 0) {
1509
- patternSpecs[name] = { description: "", components: [], slots: synthetic };
1678
+ const syntheticComps = generateSyntheticComponents(name, "");
1679
+ if (Object.keys(synthetic).length > 0 || syntheticComps.length > 0) {
1680
+ patternSpecs[name] = { description: "", components: syntheticComps, slots: synthetic };
1510
1681
  }
1511
1682
  }
1512
1683
  }
@@ -1666,21 +1837,28 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1666
1837
  slots = synthetic;
1667
1838
  }
1668
1839
  }
1669
- patternSpecs[name] = {
1840
+ const spec = {
1670
1841
  description: inner.description || "",
1671
1842
  components: inner.components || [],
1672
1843
  slots
1673
1844
  };
1845
+ if (!spec.components || spec.components.length === 0) {
1846
+ const syntheticComps = generateSyntheticComponents(name, spec.description);
1847
+ if (syntheticComps.length > 0) spec.components = syntheticComps;
1848
+ }
1849
+ patternSpecs[name] = spec;
1674
1850
  } else {
1675
1851
  const synthetic = generateSyntheticSlots(name, "");
1676
- if (Object.keys(synthetic).length > 0) {
1677
- patternSpecs[name] = { description: "", components: [], slots: synthetic };
1852
+ const syntheticComps = generateSyntheticComponents(name, "");
1853
+ if (Object.keys(synthetic).length > 0 || syntheticComps.length > 0) {
1854
+ patternSpecs[name] = { description: "", components: syntheticComps, slots: synthetic };
1678
1855
  }
1679
1856
  }
1680
1857
  } catch {
1681
1858
  const synthetic = generateSyntheticSlots(name, "");
1682
- if (Object.keys(synthetic).length > 0) {
1683
- patternSpecs[name] = { description: "", components: [], slots: synthetic };
1859
+ const syntheticComps = generateSyntheticComponents(name, "");
1860
+ if (Object.keys(synthetic).length > 0 || syntheticComps.length > 0) {
1861
+ patternSpecs[name] = { description: "", components: syntheticComps, slots: synthetic };
1684
1862
  }
1685
1863
  }
1686
1864
  }
@@ -1810,6 +1988,27 @@ function generateSyntheticSlots(patternId, description) {
1810
1988
  }
1811
1989
  return syntheticSlots;
1812
1990
  }
1991
+ function generateSyntheticComponents(patternId, description) {
1992
+ const desc = description.toLowerCase();
1993
+ const syntheticComponents = [];
1994
+ if (patternId.includes("hero")) syntheticComponents.push("Button", "Icon", "Image");
1995
+ if (patternId.includes("feature")) syntheticComponents.push("Card", "Icon", "Text");
1996
+ if (patternId.includes("pricing")) syntheticComponents.push("Card", "Button", "Badge");
1997
+ if (patternId.includes("testimonial")) syntheticComponents.push("Card", "Avatar", "Text");
1998
+ if (patternId.includes("cta")) syntheticComponents.push("Button", "Text");
1999
+ if (patternId.includes("form") || patternId.includes("contact")) syntheticComponents.push("Input", "Textarea", "Button", "Label");
2000
+ if (patternId.includes("team")) syntheticComponents.push("Card", "Avatar", "Text");
2001
+ if (patternId.includes("settings") || patternId.includes("security")) syntheticComponents.push("Card", "Toggle", "Input", "Button");
2002
+ if (patternId.includes("message") || patternId.includes("chat")) syntheticComponents.push("Avatar", "Text", "CodeBlock");
2003
+ if (patternId.includes("input") && desc.includes("chat")) syntheticComponents.push("Textarea", "Button", "Icon");
2004
+ if (patternId.includes("header") && desc.includes("chat")) syntheticComponents.push("Button", "Icon", "Text");
2005
+ if (patternId.includes("content") || patternId.includes("legal")) syntheticComponents.push("Heading", "Text", "List");
2006
+ if (patternId.includes("how-it-works") || patternId.includes("steps")) syntheticComponents.push("Card", "Icon", "Text", "Badge");
2007
+ if (patternId.includes("values")) syntheticComponents.push("Card", "Icon", "Text");
2008
+ if (patternId.includes("story") || patternId.includes("about")) syntheticComponents.push("Text", "Image");
2009
+ if (patternId.includes("empty") || patternId.includes("new")) syntheticComponents.push("Icon", "Text", "Button");
2010
+ return [...new Set(syntheticComponents)];
2011
+ }
1813
2012
  function generateSectionContext(input) {
1814
2013
  const { section, decorators, guardConfig, personality, themeName, recipeName, zoneContext, patternSpecs, recipeHints, constraints, shellInfo } = input;
1815
2014
  const lines = [];
@@ -1836,11 +2035,10 @@ function generateSectionContext(input) {
1836
2035
  lines.push("");
1837
2036
  lines.push(`**Theme tokens:** see \`src/styles/tokens.css\` \u2014 use \`var(--d-primary)\`, \`var(--d-bg)\`, etc.`);
1838
2037
  lines.push("");
2038
+ lines.push("**Visual Treatments:** All 6 base treatments available (see DECANTR.md for usage).");
1839
2039
  if (decorators.length > 0) {
1840
2040
  const names = decorators.map((d) => d.name).join(", ");
1841
- lines.push(`**Decorators:** see \`src/styles/decorators.css\` \u2014 available classes: ${names}`);
1842
- } else {
1843
- lines.push("**Decorators:** none defined.");
2041
+ lines.push(`**Recipe decorators:** ${names}`);
1844
2042
  }
1845
2043
  lines.push("");
1846
2044
  if (recipeHints) {
@@ -1871,11 +2069,7 @@ function generateSectionContext(input) {
1871
2069
  lines.push("");
1872
2070
  }
1873
2071
  if (personality.length > 0) {
1874
- lines.push("## Personality");
1875
- lines.push("");
1876
- lines.push(personality.join(", "));
1877
- lines.push("");
1878
- lines.push("---");
2072
+ lines.push("**Personality:** See scaffold.md for personality and visual direction.");
1879
2073
  lines.push("");
1880
2074
  }
1881
2075
  if (constraints && Object.keys(constraints).length > 0) {
@@ -1888,6 +2082,34 @@ function generateSectionContext(input) {
1888
2082
  lines.push("---");
1889
2083
  lines.push("");
1890
2084
  }
2085
+ const uniquePatterns = /* @__PURE__ */ new Map();
2086
+ for (const page of section.pages) {
2087
+ const patternNames = page.layout.flatMap(extractPatternNames);
2088
+ for (const name of patternNames) {
2089
+ if (patternSpecs[name] && !uniquePatterns.has(name)) {
2090
+ uniquePatterns.set(name, patternSpecs[name]);
2091
+ }
2092
+ }
2093
+ }
2094
+ if (uniquePatterns.size > 0) {
2095
+ lines.push("## Pattern Reference");
2096
+ lines.push("");
2097
+ for (const [patternName, spec] of uniquePatterns) {
2098
+ lines.push(`### ${patternName}`);
2099
+ lines.push("");
2100
+ lines.push(spec.description);
2101
+ lines.push("");
2102
+ lines.push(`**Components:** ${spec.components.join(", ")}`);
2103
+ lines.push("");
2104
+ lines.push("**Layout slots:**");
2105
+ for (const [slot, desc] of Object.entries(spec.slots)) {
2106
+ lines.push(`- \`${slot}\`: ${desc}`);
2107
+ }
2108
+ lines.push("");
2109
+ }
2110
+ lines.push("---");
2111
+ lines.push("");
2112
+ }
1891
2113
  lines.push("## Pages");
1892
2114
  lines.push("");
1893
2115
  for (const page of section.pages) {
@@ -1908,22 +2130,6 @@ function generateSectionContext(input) {
1908
2130
  }
1909
2131
  lines.push("");
1910
2132
  }
1911
- const patternNames = page.layout.flatMap(extractPatternNames);
1912
- for (const patternName of patternNames) {
1913
- const spec = patternSpecs[patternName];
1914
- if (!spec) continue;
1915
- lines.push(`#### Pattern: ${patternName}`);
1916
- lines.push("");
1917
- lines.push(spec.description);
1918
- lines.push("");
1919
- lines.push(`**Components:** ${spec.components.join(", ")}`);
1920
- lines.push("");
1921
- lines.push("**Layout slots:**");
1922
- for (const [slot, desc] of Object.entries(spec.slots)) {
1923
- lines.push(`- \`${slot}\`: ${desc}`);
1924
- }
1925
- lines.push("");
1926
- }
1927
2133
  }
1928
2134
  return lines.join("\n");
1929
2135
  }