@decantr/cli 1.7.13 → 1.7.15

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.
@@ -1,9 +1,224 @@
1
+ // src/registry.ts
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { API_CONTENT_TYPES, RegistryAPIClient } from "@decantr/registry";
5
+ var DEFAULT_API_URL = "https://api.decantr.ai/v1";
6
+ var ALL_CONTENT_TYPES = API_CONTENT_TYPES;
7
+ function loadFromCache(cacheDir, contentType, id, namespace) {
8
+ const nsDir = namespace ? join(cacheDir, namespace) : cacheDir;
9
+ const cachePath = id ? join(nsDir, contentType, `${id}.json`) : join(nsDir, contentType, "index.json");
10
+ if (!existsSync(cachePath)) return null;
11
+ try {
12
+ const data = JSON.parse(readFileSync(cachePath, "utf-8"));
13
+ return { data, source: { type: "cache" } };
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+ function saveToCache(cacheDir, contentType, id, data, namespace = "@official") {
19
+ const dir = join(cacheDir, namespace, contentType);
20
+ mkdirSync(dir, { recursive: true });
21
+ const cachePath = id ? join(dir, `${id}.json`) : join(dir, "index.json");
22
+ writeFileSync(cachePath, JSON.stringify(data, null, 2));
23
+ }
24
+ var RegistryClient = class {
25
+ cacheDir;
26
+ apiUrl;
27
+ offline;
28
+ projectRoot;
29
+ apiClient;
30
+ constructor(options = {}) {
31
+ this.projectRoot = options.projectRoot || process.cwd();
32
+ this.cacheDir = options.cacheDir || join(this.projectRoot, ".decantr", "cache");
33
+ this.apiUrl = options.apiUrl || process.env.DECANTR_API_URL || DEFAULT_API_URL;
34
+ this.offline = options.offline || false;
35
+ this.apiClient = new RegistryAPIClient({
36
+ baseUrl: this.apiUrl,
37
+ apiKey: options.apiKey || process.env.DECANTR_API_KEY || void 0
38
+ });
39
+ }
40
+ getApiUrl() {
41
+ return this.apiUrl;
42
+ }
43
+ /**
44
+ * Load content from .decantr/custom/{contentType}/{id}.json
45
+ * Works for ALL content types, not just themes.
46
+ */
47
+ loadCustomContent(contentType, id) {
48
+ const customPath = join(this.projectRoot, ".decantr", "custom", contentType, `${id}.json`);
49
+ if (!existsSync(customPath)) return null;
50
+ try {
51
+ const data = JSON.parse(readFileSync(customPath, "utf-8"));
52
+ return { data, source: { type: "custom", path: customPath } };
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ /**
58
+ * List all custom content of a given type from .decantr/custom/{type}/
59
+ */
60
+ listCustomContent(contentType) {
61
+ const dir = join(this.projectRoot, ".decantr", "custom", contentType);
62
+ if (!existsSync(dir)) return [];
63
+ try {
64
+ return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
65
+ const data = JSON.parse(readFileSync(join(dir, f), "utf-8"));
66
+ return { id: data.id || f.replace(".json", ""), ...data };
67
+ });
68
+ } catch {
69
+ return [];
70
+ }
71
+ }
72
+ /**
73
+ * Unified fetch for a content list.
74
+ * Resolution: API -> Cache. Custom items are merged into the list.
75
+ */
76
+ async fetchContentList(contentType, namespace, sort, recommended, intelligenceSource) {
77
+ let apiItems = [];
78
+ let source = { type: "cache" };
79
+ if (!this.offline) {
80
+ try {
81
+ const apiResult = await this.apiClient.listContent(contentType, {
82
+ namespace,
83
+ sort,
84
+ recommended,
85
+ intelligenceSource
86
+ });
87
+ apiItems = apiResult.items;
88
+ source = { type: "api", url: this.apiUrl };
89
+ saveToCache(this.cacheDir, contentType, null, apiResult, namespace || "@official");
90
+ } catch {
91
+ }
92
+ }
93
+ if (apiItems.length === 0) {
94
+ const cacheResult = loadFromCache(
95
+ this.cacheDir,
96
+ contentType,
97
+ void 0,
98
+ namespace
99
+ );
100
+ if (cacheResult) {
101
+ apiItems = cacheResult.data.items;
102
+ source = { type: "cache" };
103
+ }
104
+ }
105
+ const customItems = this.listCustomContent(contentType);
106
+ const allItems = [...customItems, ...apiItems];
107
+ return {
108
+ data: { items: allItems, total: allItems.length },
109
+ source
110
+ };
111
+ }
112
+ /**
113
+ * Unified fetch for a single content item.
114
+ * Resolution: Custom -> API -> Cache
115
+ */
116
+ async fetchContentItem(contentType, id, namespace = "@official") {
117
+ const customId = id.startsWith("custom:") ? id.slice(7) : id;
118
+ const customResult = this.loadCustomContent(contentType, customId);
119
+ if (customResult) return customResult;
120
+ if (id.startsWith("custom:")) return null;
121
+ if (!this.offline) {
122
+ for (let attempt = 0; attempt < 2; attempt++) {
123
+ try {
124
+ const data = await this.apiClient.getContent(
125
+ contentType,
126
+ namespace,
127
+ id
128
+ );
129
+ saveToCache(this.cacheDir, contentType, id, data, namespace);
130
+ return { data, source: { type: "api", url: this.apiUrl } };
131
+ } catch (e) {
132
+ if (process.env.DECANTR_DEBUG) {
133
+ console.error(
134
+ ` [debug] API fetch ${attempt === 0 ? "failed" : "retry failed"} for ${contentType}/${namespace}/${id}: ${e.message}`
135
+ );
136
+ }
137
+ if (attempt === 0) {
138
+ await new Promise((r) => setTimeout(r, 500));
139
+ }
140
+ }
141
+ }
142
+ } else if (process.env.DECANTR_DEBUG) {
143
+ console.error(` [debug] Skipping API (offline mode) for ${contentType}/${namespace}/${id}`);
144
+ }
145
+ return loadFromCache(this.cacheDir, contentType, id, namespace);
146
+ }
147
+ // ── Convenience methods (delegate to unified fetch) ──
148
+ async fetchArchetypes() {
149
+ return this.fetchContentList("archetypes");
150
+ }
151
+ async fetchArchetype(id) {
152
+ return this.fetchContentItem("archetypes", id);
153
+ }
154
+ async fetchBlueprints() {
155
+ return this.fetchContentList("blueprints");
156
+ }
157
+ async fetchBlueprint(id) {
158
+ return this.fetchContentItem("blueprints", id);
159
+ }
160
+ async fetchThemes() {
161
+ return this.fetchContentList("themes");
162
+ }
163
+ async fetchTheme(id) {
164
+ return this.fetchContentItem("themes", id);
165
+ }
166
+ async fetchPatterns() {
167
+ return this.fetchContentList("patterns");
168
+ }
169
+ async fetchPattern(id) {
170
+ return this.fetchContentItem("patterns", id);
171
+ }
172
+ async fetchShells() {
173
+ return this.fetchContentList("shells");
174
+ }
175
+ async fetchShell(id) {
176
+ return this.fetchContentItem("shells", id);
177
+ }
178
+ /**
179
+ * Check if API is available.
180
+ */
181
+ async checkApiAvailability() {
182
+ if (this.offline) return false;
183
+ return this.apiClient.checkHealth();
184
+ }
185
+ };
186
+ async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
187
+ const apiClient = new RegistryAPIClient({ baseUrl: apiUrl });
188
+ const synced = [];
189
+ const failed = [];
190
+ const healthy = await apiClient.checkHealth();
191
+ if (!healthy) {
192
+ return { synced: [], failed: ["API unavailable"] };
193
+ }
194
+ for (const type of ALL_CONTENT_TYPES) {
195
+ try {
196
+ const result = await apiClient.listContent(type, { namespace: "@official" });
197
+ saveToCache(cacheDir, type, null, result, "@official");
198
+ for (const item of result.items) {
199
+ const slug = item.slug;
200
+ const data = item.data;
201
+ const innerSlug = data?.id || data?.slug;
202
+ const cacheKey = slug || innerSlug || item.id;
203
+ if (cacheKey) {
204
+ saveToCache(cacheDir, type, cacheKey, item, "@official");
205
+ }
206
+ }
207
+ synced.push(type);
208
+ } catch {
209
+ failed.push(type);
210
+ }
211
+ }
212
+ return { synced, failed };
213
+ }
214
+
1
215
  // src/scaffold.ts
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "fs";
3
- import { join, dirname } from "path";
216
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
217
+ import { dirname, join as join2 } from "path";
4
218
  import { fileURLToPath } from "url";
5
- import { computeSpatialTokens } from "@decantr/essence-spec";
6
219
  import { compileExecutionPackBundle } from "@decantr/core";
220
+ import { computeSpatialTokens } from "@decantr/essence-spec";
221
+ import { API_CONTENT_TYPES as API_CONTENT_TYPES2 } from "@decantr/registry";
7
222
 
8
223
  // src/treatments.ts
9
224
  function generateTreatmentCSS(spatialTokens, treatmentOverrides, themeDecorators, themeName, themeDecoratorDefinitions) {
@@ -51,7 +266,10 @@ ${themeBody}
51
266
  ["display", "inline-flex"],
52
267
  ["align-items", "center"],
53
268
  ["gap", "0.5em"],
54
- ["padding", "calc(var(--d-interactive-py) * var(--d-density-scale, 1)) var(--d-interactive-px)"],
269
+ [
270
+ "padding",
271
+ "calc(var(--d-interactive-py) * var(--d-density-scale, 1)) var(--d-interactive-px)"
272
+ ],
55
273
  ["border", "1px solid var(--d-border)"],
56
274
  ["border-radius", "var(--d-radius)"],
57
275
  ["background", "transparent"],
@@ -59,7 +277,10 @@ ${themeBody}
59
277
  ["font", "inherit"],
60
278
  ["cursor", "pointer"],
61
279
  ["text-decoration", "none"],
62
- ["transition", "background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease"]
280
+ [
281
+ "transition",
282
+ "background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease"
283
+ ]
63
284
  ]);
64
285
  emitRule(".d-interactive:hover", [
65
286
  ["border-color", "var(--d-primary-hover)"],
@@ -87,14 +308,38 @@ ${themeBody}
87
308
  ["border-color", "transparent"],
88
309
  ["background", "transparent"]
89
310
  ]);
90
- emitRule('.d-interactive[data-variant="ghost"]:hover', [
91
- ["background", "var(--d-surface)"]
92
- ]);
311
+ emitRule('.d-interactive[data-variant="ghost"]:hover', [["background", "var(--d-surface)"]]);
93
312
  emitRule('.d-interactive[data-variant="danger"]', [
94
313
  ["background", "var(--d-error)"],
95
314
  ["color", "#fff"],
96
315
  ["border-color", "var(--d-error)"]
97
316
  ]);
317
+ emitRule('.d-interactive[data-size="sm"]', [
318
+ [
319
+ "padding",
320
+ "calc(var(--d-interactive-py) * 0.5 * var(--d-density-scale, 1)) calc(var(--d-interactive-px) * 0.75)"
321
+ ],
322
+ ["font-size", "0.875rem"],
323
+ ["gap", "0.375em"]
324
+ ]);
325
+ emitRule('.d-interactive[data-size="md"]', [
326
+ // Explicit md — same as default. Lets authors opt in without inheriting
327
+ // contextual sizing from an ancestor that may have set data-size.
328
+ [
329
+ "padding",
330
+ "calc(var(--d-interactive-py) * var(--d-density-scale, 1)) var(--d-interactive-px)"
331
+ ],
332
+ ["font-size", "1rem"],
333
+ ["gap", "0.5em"]
334
+ ]);
335
+ emitRule('.d-interactive[data-size="lg"]', [
336
+ [
337
+ "padding",
338
+ "calc(var(--d-interactive-py) * 1.5 * var(--d-density-scale, 1)) calc(var(--d-interactive-px) * 1.25)"
339
+ ],
340
+ ["font-size", "1.125rem"],
341
+ ["gap", "0.625em"]
342
+ ]);
98
343
  emitRule(".d-surface", [
99
344
  ["background", "var(--d-surface)"],
100
345
  ["border", "1px solid var(--d-border)"],
@@ -111,9 +356,7 @@ ${themeBody}
111
356
  ["box-shadow", "var(--d-shadow-lg)"],
112
357
  ["z-index", "50"]
113
358
  ]);
114
- emitRule(".d-surface[data-interactive]", [
115
- ["cursor", "pointer"]
116
- ]);
359
+ emitRule(".d-surface[data-interactive]", [["cursor", "pointer"]]);
117
360
  emitRule(".d-surface[data-interactive]:hover", [
118
361
  ["border-color", "var(--d-primary-hover, var(--d-border))"],
119
362
  ["box-shadow", "var(--d-shadow-md)"],
@@ -134,103 +377,958 @@ ${themeBody}
134
377
  ["text-transform", "uppercase"],
135
378
  ["letter-spacing", "0.05em"]
136
379
  ]);
137
- emitRule(".d-data-row", [
138
- ["border-bottom", "1px solid var(--d-border)"],
139
- ["transition", "background 0.1s ease"]
380
+ emitRule(".d-data-row", [
381
+ ["border-bottom", "1px solid var(--d-border)"],
382
+ ["transition", "background 0.1s ease"]
383
+ ]);
384
+ emitRule(".d-data-row:hover", [["background", "var(--d-surface)"]]);
385
+ emitRule(".d-data-cell", [
386
+ ["padding", "calc(var(--d-data-py) * var(--d-density-scale, 1)) var(--d-content-gap)"],
387
+ ["vertical-align", "middle"]
388
+ ]);
389
+ emitRule(".d-control", [
390
+ ["background", "var(--d-surface)"],
391
+ ["color", "var(--d-text)"],
392
+ ["padding", "calc(var(--d-control-py) * var(--d-density-scale, 1)) 0.75rem"],
393
+ ["border-radius", "var(--d-radius)"],
394
+ ["border", "1px solid var(--d-border)"],
395
+ ["width", "100%"],
396
+ ["outline", "none"],
397
+ ["font", "inherit"],
398
+ ["transition", "border-color 0.15s ease, box-shadow 0.15s ease"]
399
+ ]);
400
+ emitRule(".d-control:focus", [
401
+ ["border-color", "var(--d-primary)"],
402
+ ["box-shadow", "0 0 0 3px color-mix(in srgb, var(--d-primary) 25%, transparent)"]
403
+ ]);
404
+ emitRule(".d-control::placeholder", [["color", "var(--d-text-muted)"]]);
405
+ emitRule(".d-control:disabled", [
406
+ ["opacity", "0.5"],
407
+ ["cursor", "not-allowed"]
408
+ ]);
409
+ emitRule(".d-control[aria-invalid]", [
410
+ ["border-color", "var(--d-error)"],
411
+ ["box-shadow", "0 0 0 3px color-mix(in srgb, var(--d-error) 15%, transparent)"]
412
+ ]);
413
+ emitRule(".d-section", [
414
+ ["--d-density-scale", "1"],
415
+ ["padding", "calc(var(--d-section-py) * var(--d-density-scale)) 0"]
416
+ ]);
417
+ lines.push('.d-section[data-density="compact"] {');
418
+ lines.push(" --d-density-scale: 0.65;");
419
+ lines.push("}");
420
+ lines.push("");
421
+ lines.push('.d-section[data-density="spacious"] {');
422
+ lines.push(" --d-density-scale: 1.4;");
423
+ lines.push("}");
424
+ lines.push("");
425
+ lines.push(".d-section + .d-section {");
426
+ lines.push(" border-top: 1px solid transparent;");
427
+ lines.push(
428
+ " border-image: linear-gradient(to right, transparent, var(--d-border), transparent) 1;"
429
+ );
430
+ lines.push(" margin-top: calc(var(--d-section-gap) * var(--d-density-scale, 1));");
431
+ lines.push("}");
432
+ lines.push("");
433
+ emitRule(".d-annotation", [
434
+ ["display", "inline-flex"],
435
+ ["align-items", "center"],
436
+ ["gap", "0.25em"],
437
+ ["font-size", "0.75rem"],
438
+ ["font-weight", "500"],
439
+ ["padding", "0.125rem 0.5rem"],
440
+ ["border-radius", "var(--d-radius-full)"],
441
+ ["background", "color-mix(in srgb, var(--d-surface-raised) 88%, transparent)"],
442
+ ["border", "1px solid color-mix(in srgb, var(--d-border) 72%, transparent)"],
443
+ ["color", "var(--d-text-muted)"],
444
+ ["white-space", "nowrap"]
445
+ ]);
446
+ emitRule('.d-annotation[data-status="success"]', [
447
+ ["background", "color-mix(in srgb, var(--d-success) 15%, transparent)"],
448
+ ["color", "var(--d-success)"]
449
+ ]);
450
+ emitRule('.d-annotation[data-status="error"]', [
451
+ ["background", "color-mix(in srgb, var(--d-error) 15%, transparent)"],
452
+ ["color", "var(--d-error)"]
453
+ ]);
454
+ emitRule('.d-annotation[data-status="warning"]', [
455
+ ["background", "color-mix(in srgb, var(--d-warning) 15%, transparent)"],
456
+ ["color", "var(--d-warning)"]
457
+ ]);
458
+ emitRule('.d-annotation[data-status="info"]', [
459
+ ["background", "color-mix(in srgb, var(--d-info) 15%, transparent)"],
460
+ ["color", "var(--d-info)"]
461
+ ]);
462
+ emitRule(".d-label", [
463
+ ["font-size", "0.7rem"],
464
+ ["font-weight", "600"],
465
+ ["text-transform", "uppercase"],
466
+ ["letter-spacing", "0.08em"],
467
+ ["color", "var(--d-text-muted)"],
468
+ ["font-family", "var(--d-font-mono, ui-monospace, monospace)"],
469
+ ["display", "block"],
470
+ ["margin-bottom", "calc(var(--d-label-mb) * var(--d-density-scale, 1))"]
471
+ ]);
472
+ emitRule(".d-label[data-anchor]", [
473
+ ["padding-left", "var(--d-label-px)"],
474
+ ["border-left", "2px solid var(--d-accent)"]
475
+ ]);
476
+ emitRule(".d-link", [
477
+ ["color", "var(--d-text)"],
478
+ ["text-decoration", "none"],
479
+ ["border-bottom", "1px solid transparent"],
480
+ ["transition", "color 0.15s ease, border-color 0.15s ease"],
481
+ ["cursor", "pointer"]
482
+ ]);
483
+ emitRule(".d-link:hover", [
484
+ ["color", "var(--d-primary)"],
485
+ ["border-bottom-color", "var(--d-primary)"]
486
+ ]);
487
+ emitRule(".d-link:focus-visible", [
488
+ ["outline", "2px solid var(--d-primary)"],
489
+ ["outline-offset", "2px"],
490
+ ["border-radius", "2px"]
491
+ ]);
492
+ emitRule('.d-link[data-variant="subtle"]', [["color", "var(--d-text-muted)"]]);
493
+ emitRule('.d-link[data-variant="subtle"]:hover', [
494
+ ["color", "var(--d-text)"],
495
+ ["border-bottom-color", "var(--d-text)"]
496
+ ]);
497
+ emitRule('.d-link[data-variant="strong"]', [
498
+ ["color", "var(--d-primary)"],
499
+ ["font-weight", "500"]
500
+ ]);
501
+ emitRule('.d-link[data-variant="strong"]:hover', [
502
+ ["color", "var(--d-primary-hover)"],
503
+ ["border-bottom-color", "var(--d-primary-hover)"]
504
+ ]);
505
+ emitRule('.d-link[aria-current="page"], .d-link[data-active="true"]', [
506
+ ["color", "var(--d-primary)"],
507
+ ["font-weight", "500"]
508
+ ]);
509
+ emitRule(".d-icon-btn", [
510
+ ["display", "inline-flex"],
511
+ ["align-items", "center"],
512
+ ["justify-content", "center"],
513
+ ["width", "2rem"],
514
+ ["height", "2rem"],
515
+ ["border", "none"],
516
+ ["background", "transparent"],
517
+ ["color", "var(--d-text-muted)"],
518
+ ["border-radius", "var(--d-radius-sm)"],
519
+ ["cursor", "pointer"],
520
+ ["transition", "background 0.15s ease, color 0.15s ease"]
521
+ ]);
522
+ emitRule(".d-icon-btn:hover", [
523
+ ["background", "color-mix(in srgb, var(--d-text) 8%, transparent)"],
524
+ ["color", "var(--d-text)"]
525
+ ]);
526
+ emitRule(".d-icon-btn:focus-visible", [
527
+ ["outline", "2px solid var(--d-primary)"],
528
+ ["outline-offset", "2px"]
529
+ ]);
530
+ emitRule(".d-icon-btn:disabled", [
531
+ ["opacity", "0.5"],
532
+ ["cursor", "not-allowed"],
533
+ ["pointer-events", "none"]
534
+ ]);
535
+ emitRule('.d-icon-btn[data-size="sm"]', [
536
+ ["width", "1.5rem"],
537
+ ["height", "1.5rem"]
538
+ ]);
539
+ emitRule('.d-icon-btn[data-size="lg"]', [
540
+ ["width", "2.5rem"],
541
+ ["height", "2.5rem"]
542
+ ]);
543
+ emitRule('.d-icon-btn[data-variant="primary"]', [
544
+ ["background", "var(--d-primary)"],
545
+ ["color", "#fff"]
546
+ ]);
547
+ emitRule('.d-icon-btn[data-variant="primary"]:hover', [["background", "var(--d-primary-hover)"]]);
548
+ emitRule(".d-nav-link", [
549
+ ["display", "flex"],
550
+ ["align-items", "center"],
551
+ ["gap", "0.5rem"],
552
+ ["padding", "0.5rem 0.75rem"],
553
+ ["border-radius", "var(--d-radius-sm)"],
554
+ ["color", "var(--d-text-muted)"],
555
+ ["text-decoration", "none"],
556
+ ["font-size", "0.875rem"],
557
+ ["cursor", "pointer"],
558
+ ["transition", "background 0.15s ease, color 0.15s ease"],
559
+ ["border-left", "2px solid transparent"]
560
+ ]);
561
+ emitRule(".d-nav-link:hover", [
562
+ ["color", "var(--d-text)"],
563
+ ["background", "color-mix(in srgb, var(--d-text) 6%, transparent)"]
564
+ ]);
565
+ emitRule(".d-nav-link:focus-visible", [
566
+ ["outline", "2px solid var(--d-primary)"],
567
+ ["outline-offset", "2px"]
568
+ ]);
569
+ emitRule('.d-nav-link[aria-current="page"], .d-nav-link[data-active="true"]', [
570
+ ["color", "var(--d-primary)"],
571
+ ["background", "color-mix(in srgb, var(--d-primary) 10%, transparent)"],
572
+ ["border-left-color", "var(--d-primary)"],
573
+ ["font-weight", "500"]
574
+ ]);
575
+ emitRule(".d-step-chip", [
576
+ ["display", "inline-flex"],
577
+ ["align-items", "center"],
578
+ ["justify-content", "center"],
579
+ ["width", "2rem"],
580
+ ["height", "2rem"],
581
+ ["border-radius", "50%"],
582
+ ["border", "1.5px solid var(--d-border)"],
583
+ ["background", "transparent"],
584
+ ["color", "var(--d-text-muted)"],
585
+ ["font-size", "0.875rem"],
586
+ ["font-weight", "600"],
587
+ ["font-variant-numeric", "tabular-nums"],
588
+ ["transition", "background 0.15s ease, border-color 0.15s ease, color 0.15s ease"]
589
+ ]);
590
+ emitRule('.d-step-chip[data-step-state="active"]', [
591
+ ["background", "var(--d-primary)"],
592
+ ["border-color", "var(--d-primary)"],
593
+ ["color", "#fff"]
594
+ ]);
595
+ emitRule('.d-step-chip[data-step-state="done"]', [
596
+ ["background", "color-mix(in srgb, var(--d-success) 15%, transparent)"],
597
+ ["border-color", "var(--d-success)"],
598
+ ["color", "var(--d-success)"]
599
+ ]);
600
+ emitRule(".d-agent-node", [
601
+ ["display", "flex"],
602
+ ["flex-direction", "column"],
603
+ ["gap", "0.5rem"],
604
+ ["position", "relative"],
605
+ ["min-width", "200px"],
606
+ ["max-width", "260px"],
607
+ ["padding", "0.75rem"],
608
+ ["background", "var(--d-surface)"],
609
+ ["border", "1px solid var(--d-border)"],
610
+ ["border-radius", "var(--d-radius)"],
611
+ ["cursor", "pointer"],
612
+ ["transition", "border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease"]
613
+ ]);
614
+ emitRule(".d-agent-node:hover", [
615
+ ["border-color", "var(--d-primary)"],
616
+ ["transform", "translateY(-1px)"]
617
+ ]);
618
+ emitRule('.d-agent-node[data-status="error"]', [
619
+ ["box-shadow", "0 0 12px color-mix(in srgb, var(--d-error) 25%, transparent)"],
620
+ ["border-color", "color-mix(in srgb, var(--d-error) 60%, transparent)"]
621
+ ]);
622
+ emitRule('.d-agent-node[data-status="active"]', [["border-color", "var(--d-primary)"]]);
623
+ emitRule(".d-port", [
624
+ ["position", "absolute"],
625
+ ["width", "8px"],
626
+ ["height", "8px"],
627
+ ["border-radius", "50%"],
628
+ ["background", "var(--d-border)"],
629
+ ["top", "50%"],
630
+ ["transform", "translateY(-50%)"],
631
+ ["transition", "background 0.15s ease"]
632
+ ]);
633
+ emitRule('.d-port[data-side="left"]', [["left", "-4px"]]);
634
+ emitRule('.d-port[data-side="right"]', [["right", "-4px"]]);
635
+ emitRule('.d-port[data-side="top"]', [
636
+ ["top", "-4px"],
637
+ ["left", "50%"],
638
+ ["transform", "translateX(-50%)"]
639
+ ]);
640
+ emitRule('.d-port[data-side="bottom"]', [
641
+ ["bottom", "-4px"],
642
+ ["top", "auto"],
643
+ ["left", "50%"],
644
+ ["transform", "translateX(-50%)"]
645
+ ]);
646
+ emitRule('.d-port[data-active="true"]', [["background", "var(--d-primary)"]]);
647
+ emitRule(".d-cta-banner", [
648
+ ["display", "flex"],
649
+ ["flex-direction", "column"],
650
+ ["align-items", "center"],
651
+ ["justify-content", "center"],
652
+ ["gap", "1rem"],
653
+ ["padding", "3rem 1.5rem"],
654
+ ["border-radius", "var(--d-radius-lg)"],
655
+ ["text-align", "center"],
656
+ ["position", "relative"],
657
+ ["overflow", "hidden"],
658
+ // Default gradient uses theme tokens — themes can override the stops
659
+ // via CSS variables --d-cta-gradient if desired.
660
+ [
661
+ "background",
662
+ "var(--d-cta-gradient, linear-gradient(135deg, color-mix(in srgb, var(--d-primary) 85%, transparent), color-mix(in srgb, var(--d-accent) 80%, transparent)))"
663
+ ],
664
+ ["color", "var(--d-cta-text, #ffffff)"]
665
+ ]);
666
+ emitRule('.d-cta-banner[data-size="compact"]', [
667
+ ["padding", "1.5rem 1rem"],
668
+ ["gap", "0.5rem"]
669
+ ]);
670
+ emitRule('.d-cta-banner[data-size="hero"]', [
671
+ ["padding", "5rem 2rem"],
672
+ ["gap", "1.5rem"]
673
+ ]);
674
+ emitRule('.d-interactive[data-variant="dark"]', [
675
+ ["background", "var(--d-cta-pill-bg, #18181b)"],
676
+ ["color", "var(--d-cta-pill-text, #ffffff)"],
677
+ ["border-color", "transparent"],
678
+ ["border-radius", "var(--d-radius-full)"]
679
+ ]);
680
+ emitRule('.d-interactive[data-variant="dark"]:hover', [
681
+ ["background", "var(--d-cta-pill-bg-hover, #27272a)"]
682
+ ]);
683
+ emitRule(".d-divider-top", [["border-top", "1px solid var(--d-border)"]]);
684
+ emitRule(".d-divider-bottom", [["border-bottom", "1px solid var(--d-border)"]]);
685
+ emitRule(".d-divider-left", [["border-left", "1px solid var(--d-border)"]]);
686
+ emitRule(".d-divider-right", [["border-right", "1px solid var(--d-border)"]]);
687
+ emitRule(".d-divider", [
688
+ ["border", "0"],
689
+ ["border-top", "1px solid var(--d-border)"],
690
+ ["margin", "0"],
691
+ ["width", "100%"],
692
+ ["height", "0"]
693
+ ]);
694
+ emitRule(".d-shell", [
695
+ ["display", "flex"],
696
+ ["flex-direction", "column"],
697
+ ["min-height", "100vh"],
698
+ ["background", "var(--d-bg)"],
699
+ ["color", "var(--d-text)"]
700
+ ]);
701
+ emitRule('.d-shell[data-layout="sidebar-main"]', [
702
+ ["flex-direction", "row"],
703
+ ["height", "100vh"],
704
+ ["overflow", "hidden"]
705
+ ]);
706
+ emitRule('.d-shell[data-layout="centered"]', [
707
+ ["align-items", "center"],
708
+ ["justify-content", "center"],
709
+ ["padding", "1rem"]
710
+ ]);
711
+ emitRule('.d-shell[data-layout="top-nav-footer"]', [
712
+ ["flex-direction", "column"],
713
+ ["height", "100vh"],
714
+ ["overflow", "hidden"]
715
+ ]);
716
+ emitRule('.d-shell[data-layout="sidebar-aside"]', [
717
+ ["display", "grid"],
718
+ ["grid-template-columns", "240px 1fr 320px"],
719
+ ["grid-template-rows", "1fr"],
720
+ ["height", "100vh"],
721
+ ["overflow", "hidden"]
722
+ ]);
723
+ emitRule(".d-shell-aside", [
724
+ ["display", "flex"],
725
+ ["flex-direction", "column"],
726
+ ["border-left", "1px solid var(--d-border)"],
727
+ ["background", "var(--d-surface)"],
728
+ ["overflow-y", "auto"]
729
+ ]);
730
+ lines.push("@media (max-width: 767.98px) {");
731
+ lines.push(' .d-shell[data-layout="sidebar-aside"] {');
732
+ lines.push(" grid-template-columns: 1fr;");
733
+ lines.push(" grid-template-rows: auto 1fr;");
734
+ lines.push(" }");
735
+ lines.push(' .d-shell[data-layout="sidebar-aside"] .d-shell-aside {');
736
+ lines.push(" display: none;");
737
+ lines.push(" }");
738
+ lines.push(' .d-shell[data-layout="sidebar-aside"] .d-shell-aside[data-mobile-open="true"] {');
739
+ lines.push(" display: flex;");
740
+ lines.push(" position: fixed;");
741
+ lines.push(" inset: 0 0 0 auto;");
742
+ lines.push(" width: min(320px, 100vw);");
743
+ lines.push(" z-index: 50;");
744
+ lines.push(" }");
745
+ lines.push("}");
746
+ lines.push("");
747
+ emitRule(".d-shell-sidebar", [
748
+ ["display", "flex"],
749
+ ["flex-direction", "column"],
750
+ ["width", "240px"],
751
+ ["flex-shrink", "0"],
752
+ ["border-right", "1px solid var(--d-border)"],
753
+ ["background", "var(--d-surface)"],
754
+ ["overflow-y", "auto"],
755
+ ["transition", "width 0.2s ease, transform 0.2s ease"]
756
+ ]);
757
+ emitRule('.d-shell-sidebar[data-collapsed="true"]', [["width", "64px"]]);
758
+ lines.push("@media (max-width: 767.98px) {");
759
+ lines.push(" .d-shell-sidebar {");
760
+ lines.push(" position: fixed;");
761
+ lines.push(" top: 0;");
762
+ lines.push(" left: 0;");
763
+ lines.push(" bottom: 0;");
764
+ lines.push(" z-index: 50;");
765
+ lines.push(" transform: translateX(-100%);");
766
+ lines.push(" }");
767
+ lines.push(' .d-shell-sidebar[data-mobile-open="true"] {');
768
+ lines.push(" transform: translateX(0);");
769
+ lines.push(" }");
770
+ lines.push("}");
771
+ lines.push("");
772
+ emitRule(".d-shell-main", [
773
+ ["display", "flex"],
774
+ ["flex-direction", "column"],
775
+ ["flex", "1"],
776
+ ["min-width", "0"],
777
+ ["overflow", "hidden"]
778
+ ]);
779
+ emitRule(".d-shell-header", [
780
+ ["display", "flex"],
781
+ ["align-items", "center"],
782
+ ["justify-content", "space-between"],
783
+ ["gap", "1rem"],
784
+ ["height", "52px"],
785
+ ["flex-shrink", "0"],
786
+ ["padding", "0 clamp(1rem, 2vw, 1.5rem)"],
787
+ ["border-bottom", "1px solid var(--d-border)"],
788
+ ["background", "var(--d-bg)"],
789
+ ["position", "sticky"],
790
+ ["top", "0"],
791
+ ["z-index", "10"]
792
+ ]);
793
+ emitRule(".d-shell-body", [
794
+ ["flex", "1"],
795
+ ["min-width", "0"],
796
+ ["overflow-y", "auto"],
797
+ ["overflow-x", "clip"],
798
+ ["padding", "1rem"]
799
+ ]);
800
+ emitRule('.d-shell-body[data-padding="compact"]', [["padding", "0.75rem"]]);
801
+ emitRule('.d-shell-body[data-padding="spacious"]', [["padding", "1.5rem"]]);
802
+ emitRule('.d-shell-body[data-padding="none"]', [["padding", "0"]]);
803
+ emitRule(".d-shell-footer", [
804
+ ["padding", "1rem clamp(1rem, 2vw, 1.5rem)"],
805
+ ["border-top", "1px solid var(--d-border)"],
806
+ ["background", "var(--d-surface)"],
807
+ ["flex-shrink", "0"]
808
+ ]);
809
+ emitRule(".d-shell-centered-card", [
810
+ ["width", "100%"],
811
+ ["max-width", "28rem"],
812
+ ["margin-inline", "auto"]
813
+ ]);
814
+ emitRule(".d-modal", [
815
+ ["position", "fixed"],
816
+ ["inset", "0"],
817
+ ["z-index", "100"],
818
+ ["display", "flex"],
819
+ ["align-items", "center"],
820
+ ["justify-content", "center"],
821
+ ["padding", "1rem"]
822
+ ]);
823
+ emitRule('.d-modal[data-align="top"]', [
824
+ ["align-items", "flex-start"],
825
+ ["padding-top", "15vh"]
826
+ ]);
827
+ emitRule(".d-modal-backdrop", [
828
+ ["position", "absolute"],
829
+ ["inset", "0"],
830
+ ["background", "color-mix(in srgb, var(--d-bg) 70%, transparent)"],
831
+ ["backdrop-filter", "blur(8px)"],
832
+ ["-webkit-backdrop-filter", "blur(8px)"]
833
+ ]);
834
+ emitRule(".d-modal-panel", [
835
+ ["position", "relative"],
836
+ ["z-index", "1"],
837
+ ["width", "100%"],
838
+ ["max-width", "32rem"],
839
+ ["background", "var(--d-surface-raised)"],
840
+ ["border", "1px solid var(--d-border)"],
841
+ ["border-radius", "var(--d-radius-lg)"],
842
+ ["box-shadow", "var(--d-shadow-lg)"],
843
+ ["max-height", "85vh"],
844
+ ["display", "flex"],
845
+ ["flex-direction", "column"],
846
+ ["overflow", "hidden"]
847
+ ]);
848
+ emitRule('.d-modal-panel[data-size="sm"]', [["max-width", "24rem"]]);
849
+ emitRule('.d-modal-panel[data-size="lg"]', [["max-width", "48rem"]]);
850
+ emitRule(".d-palette", [
851
+ ["width", "100%"],
852
+ ["max-width", "40rem"],
853
+ ["margin-inline", "auto"],
854
+ ["background", "var(--d-surface-raised)"],
855
+ ["border", "1px solid var(--d-border)"],
856
+ ["border-radius", "var(--d-radius-lg)"],
857
+ ["box-shadow", "var(--d-shadow-lg)"],
858
+ ["display", "flex"],
859
+ ["flex-direction", "column"],
860
+ ["overflow", "hidden"],
861
+ ["max-height", "60vh"]
862
+ ]);
863
+ emitRule(".d-palette-input", [
864
+ ["padding", "1rem 1.25rem"],
865
+ ["border", "0"],
866
+ ["border-bottom", "1px solid var(--d-border)"],
867
+ ["background", "transparent"],
868
+ ["color", "var(--d-text)"],
869
+ ["font-size", "1rem"],
870
+ ["outline", "0"],
871
+ ["width", "100%"]
872
+ ]);
873
+ emitRule(".d-palette-list", [
874
+ ["flex", "1"],
875
+ ["overflow-y", "auto"],
876
+ ["padding", "0.5rem"]
877
+ ]);
878
+ emitRule(".d-palette-row", [
879
+ ["display", "flex"],
880
+ ["align-items", "center"],
881
+ ["gap", "0.75rem"],
882
+ ["padding", "0.5rem 0.75rem"],
883
+ ["border-radius", "var(--d-radius-sm)"],
884
+ ["cursor", "pointer"],
885
+ ["color", "var(--d-text)"],
886
+ ["font-size", "0.875rem"],
887
+ ["transition", "background 0.1s ease"]
888
+ ]);
889
+ emitRule('.d-palette-row:hover, .d-palette-row[data-active="true"]', [
890
+ ["background", "color-mix(in srgb, var(--d-primary) 10%, transparent)"]
891
+ ]);
892
+ emitRule(".d-palette-section", [
893
+ ["padding", "0.5rem 0.75rem 0.25rem"],
894
+ ["font-size", "0.7rem"],
895
+ ["font-weight", "600"],
896
+ ["text-transform", "uppercase"],
897
+ ["letter-spacing", "0.08em"],
898
+ ["color", "var(--d-text-muted)"]
899
+ ]);
900
+ emitRule(".d-kbd", [
901
+ ["display", "inline-flex"],
902
+ ["align-items", "center"],
903
+ ["justify-content", "center"],
904
+ ["min-width", "1.5rem"],
905
+ ["padding", "0 0.375rem"],
906
+ ["height", "1.375rem"],
907
+ ["border", "1px solid var(--d-border)"],
908
+ ["border-radius", "var(--d-radius-sm)"],
909
+ ["background", "var(--d-surface)"],
910
+ ["color", "var(--d-text-muted)"],
911
+ ["font-family", "var(--d-font-mono, ui-monospace, monospace)"],
912
+ ["font-size", "0.75rem"],
913
+ ["font-weight", "500"],
914
+ ["line-height", "1"]
915
+ ]);
916
+ emitRule(".d-enter-fade", [
917
+ [
918
+ "animation",
919
+ "d-fade-in var(--d-motion-base, 250ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)) both"
920
+ ]
921
+ ]);
922
+ emitRule(".d-enter-slide-up", [
923
+ [
924
+ "animation",
925
+ "d-slide-up var(--d-motion-base, 250ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)) both"
926
+ ]
927
+ ]);
928
+ emitRule(".d-enter-scale", [
929
+ [
930
+ "animation",
931
+ "d-scale-in var(--d-motion-base, 250ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)) both"
932
+ ]
933
+ ]);
934
+ emitRule(".d-stagger-children > *", [
935
+ [
936
+ "animation",
937
+ "d-fade-in var(--d-motion-base, 250ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)) both"
938
+ ],
939
+ ["animation-delay", "calc(var(--d-stagger-index, 0) * var(--d-motion-stagger, 60ms))"]
940
+ ]);
941
+ emitRule(".d-pulse", [
942
+ ["animation", "d-pulse 2s var(--d-motion-ease, cubic-bezier(0.4,0,0.2,1)) infinite"]
943
+ ]);
944
+ emitRule(".d-pulse-ring", [
945
+ ["position", "relative"],
946
+ ["animation", "d-pulse-ring 1.5s var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)) infinite"]
947
+ ]);
948
+ emitRule(".d-shimmer", [
949
+ [
950
+ "background",
951
+ "linear-gradient(90deg, transparent, var(--d-surface-raised) 50%, transparent)"
952
+ ],
953
+ ["background-size", "200% 100%"],
954
+ ["animation", "d-shimmer 1.5s linear infinite"]
955
+ ]);
956
+ emitRule(".d-float", [
957
+ ["animation", "d-float 3s var(--d-motion-ease, cubic-bezier(0.4,0,0.2,1)) infinite"]
958
+ ]);
959
+ emitRule(".d-glow-hover", [
960
+ [
961
+ "transition",
962
+ "box-shadow var(--d-motion-fast, 150ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1))"
963
+ ]
964
+ ]);
965
+ emitRule(".d-glow-hover:hover", [
966
+ [
967
+ "box-shadow",
968
+ "0 0 24px var(--d-accent-glow, color-mix(in srgb, var(--d-accent) 40%, transparent))"
969
+ ]
970
+ ]);
971
+ emitRule(".d-scale-hover", [
972
+ [
973
+ "transition",
974
+ "transform var(--d-motion-fast, 150ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1))"
975
+ ]
976
+ ]);
977
+ emitRule(".d-scale-hover:hover", [["transform", "scale(1.02)"]]);
978
+ emitRule(".d-lift-hover", [
979
+ [
980
+ "transition",
981
+ "transform var(--d-motion-fast, 150ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)), box-shadow var(--d-motion-fast, 150ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1))"
982
+ ]
983
+ ]);
984
+ emitRule(".d-lift-hover:hover", [
985
+ ["transform", "translateY(-2px)"],
986
+ ["box-shadow", "var(--d-elevation-3, 0 4px 12px rgba(0,0,0,0.10))"]
987
+ ]);
988
+ emitRule(".d-ripple", [
989
+ ["position", "relative"],
990
+ ["overflow", "hidden"]
991
+ ]);
992
+ emitRule(".d-ripple::after", [
993
+ ["content", "''"],
994
+ ["position", "absolute"],
995
+ ["inset", "0"],
996
+ ["background", "radial-gradient(circle, currentColor 10%, transparent 10.01%)"],
997
+ ["opacity", "0"],
998
+ ["transform", "scale(0)"],
999
+ [
1000
+ "transition",
1001
+ "transform var(--d-motion-slow, 400ms), opacity var(--d-motion-slow, 400ms)"
1002
+ ],
1003
+ ["pointer-events", "none"]
1004
+ ]);
1005
+ emitRule(".d-ripple:active::after", [
1006
+ ["transform", "scale(1)"],
1007
+ ["opacity", "0.15"],
1008
+ ["transition", "0s"]
1009
+ ]);
1010
+ emitRule(".d-display", [
1011
+ ["font-family", "var(--d-font-display, var(--d-font-body, system-ui, sans-serif))"],
1012
+ ["font-weight", "var(--d-weight-bold, 700)"],
1013
+ ["font-size", "var(--d-text-5xl, 3rem)"],
1014
+ ["line-height", "var(--d-leading-tight, 1.1)"],
1015
+ ["letter-spacing", "var(--d-tracking-tight, -0.02em)"],
1016
+ ["margin", "0"]
1017
+ ]);
1018
+ emitRule(".d-headline", [
1019
+ ["font-family", "var(--d-font-display, var(--d-font-body, system-ui, sans-serif))"],
1020
+ ["font-weight", "var(--d-weight-semibold, 600)"],
1021
+ ["font-size", "var(--d-text-3xl, 1.875rem)"],
1022
+ ["line-height", "var(--d-leading-snug, 1.25)"],
1023
+ ["letter-spacing", "var(--d-tracking-tight, -0.01em)"],
1024
+ ["margin", "0"]
1025
+ ]);
1026
+ emitRule(".d-title", [
1027
+ ["font-family", "var(--d-font-display, var(--d-font-body, system-ui, sans-serif))"],
1028
+ ["font-weight", "var(--d-weight-semibold, 600)"],
1029
+ ["font-size", "var(--d-text-xl, 1.25rem)"],
1030
+ ["line-height", "var(--d-leading-snug, 1.25)"],
1031
+ ["margin", "0"]
1032
+ ]);
1033
+ emitRule(".d-subtitle", [
1034
+ ["font-family", "var(--d-font-body, system-ui, sans-serif)"],
1035
+ ["font-weight", "var(--d-weight-regular, 400)"],
1036
+ ["font-size", "var(--d-text-lg, 1.125rem)"],
1037
+ ["line-height", "var(--d-leading-snug, 1.375)"],
1038
+ ["color", "var(--d-text-muted)"],
1039
+ ["margin", "0"]
1040
+ ]);
1041
+ emitRule(".d-prose", [
1042
+ ["font-family", "var(--d-font-body, system-ui, sans-serif)"],
1043
+ ["font-size", "var(--d-text-base, 1rem)"],
1044
+ ["line-height", "var(--d-leading-relaxed, 1.625)"]
1045
+ ]);
1046
+ emitRule(".d-body", [
1047
+ ["font-family", "var(--d-font-body, system-ui, sans-serif)"],
1048
+ ["font-size", "var(--d-text-base, 1rem)"],
1049
+ ["line-height", "var(--d-leading-normal, 1.5)"]
1050
+ ]);
1051
+ emitRule(".d-caption", [
1052
+ ["font-family", "var(--d-font-body, system-ui, sans-serif)"],
1053
+ ["font-size", "var(--d-text-sm, 0.875rem)"],
1054
+ ["line-height", "var(--d-leading-normal, 1.5)"],
1055
+ ["color", "var(--d-text-muted)"]
1056
+ ]);
1057
+ emitRule(".d-eyebrow", [
1058
+ ["font-family", "var(--d-font-body, system-ui, sans-serif)"],
1059
+ ["font-size", "var(--d-text-xs, 0.75rem)"],
1060
+ ["line-height", "1.2"],
1061
+ ["font-weight", "var(--d-weight-semibold, 600)"],
1062
+ ["letter-spacing", "var(--d-tracking-wider, 0.08em)"],
1063
+ ["text-transform", "uppercase"],
1064
+ ["color", "var(--d-accent)"]
1065
+ ]);
1066
+ emitRule(".d-numeric", [["font-variant-numeric", "tabular-nums"]]);
1067
+ emitRule(".d-mono-text", [
1068
+ ["font-family", "var(--d-font-mono, ui-monospace, monospace)"],
1069
+ ["font-variant-numeric", "tabular-nums"]
1070
+ ]);
1071
+ emitRule(".d-card", [
1072
+ ["display", "flex"],
1073
+ ["flex-direction", "column"],
1074
+ ["gap", "0.75rem"],
1075
+ ["padding", "1rem"],
1076
+ ["background", "var(--d-surface)"],
1077
+ ["border", "1px solid var(--d-border)"],
1078
+ ["border-radius", "var(--d-radius)"],
1079
+ ["box-shadow", "var(--d-elevation-1, var(--d-shadow-sm))"]
1080
+ ]);
1081
+ emitRule(".d-card-header", [
1082
+ ["display", "flex"],
1083
+ ["align-items", "center"],
1084
+ ["justify-content", "space-between"],
1085
+ ["gap", "0.5rem"],
1086
+ ["padding-bottom", "0.5rem"],
1087
+ ["border-bottom", "1px solid var(--d-border)"]
1088
+ ]);
1089
+ emitRule(".d-card-body", [
1090
+ ["display", "flex"],
1091
+ ["flex-direction", "column"],
1092
+ ["gap", "0.5rem"],
1093
+ ["flex", "1 1 auto"],
1094
+ ["min-width", "0"]
1095
+ ]);
1096
+ emitRule(".d-card-footer", [
1097
+ ["display", "flex"],
1098
+ ["align-items", "center"],
1099
+ ["justify-content", "flex-end"],
1100
+ ["gap", "0.5rem"],
1101
+ ["padding-top", "0.5rem"],
1102
+ ["border-top", "1px solid var(--d-border)"]
1103
+ ]);
1104
+ emitRule('.d-card[data-padding="compact"]', [["padding", "0.625rem"]]);
1105
+ emitRule('.d-card[data-padding="spacious"]', [["padding", "1.5rem"]]);
1106
+ emitRule('.d-card[data-padding="none"]', [["padding", "0"]]);
1107
+ emitRule(".d-card[data-interactive]", [
1108
+ ["cursor", "pointer"],
1109
+ [
1110
+ "transition",
1111
+ "box-shadow var(--d-motion-fast, 150ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)), border-color var(--d-motion-fast, 150ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1))"
1112
+ ]
1113
+ ]);
1114
+ emitRule(".d-card[data-interactive]:hover", [
1115
+ ["box-shadow", "var(--d-elevation-2, var(--d-shadow))"],
1116
+ ["border-color", "var(--d-primary)"]
1117
+ ]);
1118
+ emitRule('.d-elevate[data-level="0"]', [["box-shadow", "var(--d-elevation-0, none)"]]);
1119
+ emitRule('.d-elevate[data-level="1"]', [
1120
+ ["box-shadow", "var(--d-elevation-1, 0 1px 2px rgba(0,0,0,0.06))"]
1121
+ ]);
1122
+ emitRule('.d-elevate[data-level="2"]', [
1123
+ ["box-shadow", "var(--d-elevation-2, 0 2px 4px rgba(0,0,0,0.08))"]
1124
+ ]);
1125
+ emitRule('.d-elevate[data-level="3"]', [
1126
+ ["box-shadow", "var(--d-elevation-3, 0 4px 12px rgba(0,0,0,0.10))"]
1127
+ ]);
1128
+ emitRule('.d-elevate[data-level="4"]', [
1129
+ ["box-shadow", "var(--d-elevation-4, 0 8px 24px rgba(0,0,0,0.14))"]
1130
+ ]);
1131
+ emitRule('.d-elevate[data-level="5"]', [
1132
+ ["box-shadow", "var(--d-elevation-5, 0 16px 48px rgba(0,0,0,0.18))"]
1133
+ ]);
1134
+ emitRule(".d-hotkey-indicator", [
1135
+ ["position", "fixed"],
1136
+ ["bottom", "1.5rem"],
1137
+ ["right", "1.5rem"],
1138
+ ["padding", "0.5rem 0.75rem"],
1139
+ ["background", "var(--d-surface-raised)"],
1140
+ ["border", "1px solid var(--d-border)"],
1141
+ ["border-radius", "var(--d-radius)"],
1142
+ ["box-shadow", "var(--d-elevation-3, 0 4px 12px rgba(0,0,0,0.10))"],
1143
+ ["font-family", "var(--d-font-mono, ui-monospace, monospace)"],
1144
+ ["font-size", "0.875rem"],
1145
+ ["color", "var(--d-text)"],
1146
+ ["opacity", "0"],
1147
+ ["transform", "translateY(8px)"],
1148
+ ["pointer-events", "none"],
1149
+ ["z-index", "60"],
1150
+ [
1151
+ "transition",
1152
+ "opacity var(--d-motion-fast, 150ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)), transform var(--d-motion-fast, 150ms) var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1))"
1153
+ ]
1154
+ ]);
1155
+ emitRule('.d-hotkey-indicator[data-visible="true"]', [
1156
+ ["opacity", "1"],
1157
+ ["transform", "translateY(0)"]
1158
+ ]);
1159
+ emitRule(".d-hotkey-indicator::before", [
1160
+ ["content", "'Chord: '"],
1161
+ ["color", "var(--d-text-muted)"],
1162
+ ["margin-right", "0.25rem"]
1163
+ ]);
1164
+ emitRule(".d-hotkey-indicator::after", [
1165
+ ["content", "attr(data-prefix) '\u2026'"],
1166
+ ["color", "var(--d-accent)"],
1167
+ ["font-weight", "600"]
1168
+ ]);
1169
+ emitRule(".d-timeline-rail", [
1170
+ ["position", "relative"],
1171
+ ["padding-left", "2rem"]
1172
+ ]);
1173
+ emitRule(".d-timeline-rail::before", [
1174
+ ["content", "''"],
1175
+ ["position", "absolute"],
1176
+ ["left", "0.5rem"],
1177
+ ["top", "0.5rem"],
1178
+ ["bottom", "0.5rem"],
1179
+ ["width", "2px"],
1180
+ ["background", "var(--d-border)"]
140
1181
  ]);
141
- emitRule(".d-data-row:hover", [
142
- ["background", "var(--d-surface)"]
1182
+ emitRule(".d-timeline-dot", [
1183
+ ["position", "relative"],
1184
+ ["margin-bottom", "1rem"]
143
1185
  ]);
144
- emitRule(".d-data-cell", [
145
- ["padding", "calc(var(--d-data-py) * var(--d-density-scale, 1)) var(--d-content-gap)"],
146
- ["vertical-align", "middle"]
1186
+ emitRule(".d-timeline-dot::before", [
1187
+ ["content", "''"],
1188
+ ["position", "absolute"],
1189
+ ["left", "-1.5rem"],
1190
+ ["top", "0.5rem"],
1191
+ ["width", "0.625rem"],
1192
+ ["height", "0.625rem"],
1193
+ ["border-radius", "50%"],
1194
+ ["background", "var(--d-border)"],
1195
+ ["border", "2px solid var(--d-bg)"],
1196
+ ["box-sizing", "border-box"]
147
1197
  ]);
148
- emitRule(".d-control", [
149
- ["background", "var(--d-surface)"],
150
- ["color", "var(--d-text)"],
151
- ["padding", "calc(var(--d-control-py) * var(--d-density-scale, 1)) 0.75rem"],
152
- ["border-radius", "var(--d-radius)"],
1198
+ emitRule('.d-timeline-dot[data-state="active"]::before', [["background", "var(--d-primary)"]]);
1199
+ emitRule('.d-timeline-dot[data-state="done"]::before', [["background", "var(--d-success)"]]);
1200
+ emitRule('.d-timeline-dot[data-state="error"]::before', [["background", "var(--d-error)"]]);
1201
+ emitRule('.d-timeline-dot[data-state="warning"]::before', [["background", "var(--d-warning)"]]);
1202
+ emitRule(".d-sparkline", [
1203
+ ["display", "inline-block"],
1204
+ ["vertical-align", "middle"],
1205
+ ["height", "var(--d-sparkline-height, 1.5rem)"],
1206
+ ["width", "var(--d-sparkline-width, 5rem)"]
1207
+ ]);
1208
+ emitRule(".d-sparkline-path", [
1209
+ ["fill", "none"],
1210
+ ["stroke", "var(--d-primary)"],
1211
+ ["stroke-width", "1.5"],
1212
+ ["stroke-linecap", "round"],
1213
+ ["stroke-linejoin", "round"]
1214
+ ]);
1215
+ emitRule(".d-sparkline-area", [
1216
+ ["fill", "color-mix(in srgb, var(--d-primary) 15%, transparent)"],
1217
+ ["stroke", "none"]
1218
+ ]);
1219
+ emitRule('.d-sparkline[data-trend="up"] .d-sparkline-path', [["stroke", "var(--d-success)"]]);
1220
+ emitRule('.d-sparkline[data-trend="up"] .d-sparkline-area', [
1221
+ ["fill", "color-mix(in srgb, var(--d-success) 15%, transparent)"]
1222
+ ]);
1223
+ emitRule('.d-sparkline[data-trend="down"] .d-sparkline-path', [["stroke", "var(--d-error)"]]);
1224
+ emitRule('.d-sparkline[data-trend="down"] .d-sparkline-area', [
1225
+ ["fill", "color-mix(in srgb, var(--d-error) 15%, transparent)"]
1226
+ ]);
1227
+ emitRule(".d-intent-radar", [
1228
+ ["position", "relative"],
1229
+ ["width", "var(--d-radar-size, 200px)"],
1230
+ ["height", "var(--d-radar-size, 200px)"]
1231
+ ]);
1232
+ emitRule(".d-intent-radar-ring", [
1233
+ ["position", "absolute"],
1234
+ ["border-radius", "50%"],
153
1235
  ["border", "1px solid var(--d-border)"],
154
- ["width", "100%"],
155
- ["outline", "none"],
156
- ["font", "inherit"],
157
- ["transition", "border-color 0.15s ease, box-shadow 0.15s ease"]
1236
+ ["inset", "0"]
158
1237
  ]);
159
- emitRule(".d-control:focus", [
160
- ["border-color", "var(--d-primary)"],
161
- ["box-shadow", "0 0 0 3px color-mix(in srgb, var(--d-primary) 25%, transparent)"]
1238
+ emitRule('.d-intent-radar-ring[data-level="2"]', [["inset", "12.5%"]]);
1239
+ emitRule('.d-intent-radar-ring[data-level="3"]', [["inset", "25%"]]);
1240
+ emitRule('.d-intent-radar-ring[data-level="4"]', [["inset", "37.5%"]]);
1241
+ emitRule('.d-intent-radar-ring[data-level="5"]', [["inset", "50%"]]);
1242
+ emitRule(".d-intent-radar-axis", [
1243
+ ["position", "absolute"],
1244
+ ["top", "50%"],
1245
+ ["left", "50%"],
1246
+ ["width", "50%"],
1247
+ ["height", "1px"],
1248
+ ["background", "var(--d-border)"],
1249
+ ["transform-origin", "0 0"],
1250
+ ["transform", "rotate(var(--d-radar-axis-angle, 0deg))"]
162
1251
  ]);
163
- emitRule(".d-control::placeholder", [
164
- ["color", "var(--d-text-muted)"]
1252
+ emitRule(".d-waveform", [
1253
+ ["display", "block"],
1254
+ ["width", "100%"],
1255
+ ["height", "var(--d-waveform-height, 3rem)"],
1256
+ ["overflow", "hidden"]
165
1257
  ]);
166
- emitRule(".d-control:disabled", [
167
- ["opacity", "0.5"],
168
- ["cursor", "not-allowed"]
1258
+ emitRule(".d-waveform-path", [
1259
+ ["fill", "color-mix(in srgb, var(--d-primary) 30%, transparent)"],
1260
+ ["stroke", "var(--d-primary)"],
1261
+ ["stroke-width", "1"]
169
1262
  ]);
170
- emitRule(".d-control[aria-invalid]", [
171
- ["border-color", "var(--d-error)"],
172
- ["box-shadow", "0 0 0 3px color-mix(in srgb, var(--d-error) 15%, transparent)"]
1263
+ emitRule('.d-waveform[data-state="active"] .d-waveform-path', [
1264
+ ["fill", "color-mix(in srgb, var(--d-success) 30%, transparent)"],
1265
+ ["stroke", "var(--d-success)"]
173
1266
  ]);
174
- emitRule(".d-section", [
175
- ["--d-density-scale", "1"],
176
- ["padding", "calc(var(--d-section-py) * var(--d-density-scale)) 0"]
1267
+ emitRule(".d-qr-placeholder", [
1268
+ ["display", "block"],
1269
+ ["width", "var(--d-qr-size, 8rem)"],
1270
+ ["height", "var(--d-qr-size, 8rem)"],
1271
+ [
1272
+ "background",
1273
+ "repeating-linear-gradient(0deg, var(--d-text) 0 4px, transparent 4px 8px), repeating-linear-gradient(90deg, var(--d-text) 0 4px, transparent 4px 8px)"
1274
+ ],
1275
+ ["background-color", "var(--d-surface)"],
1276
+ ["border-radius", "var(--d-radius-sm)"],
1277
+ ["border", "8px solid var(--d-surface)"],
1278
+ ["outline", "1px solid var(--d-border)"]
177
1279
  ]);
178
- lines.push('.d-section[data-density="compact"] {');
179
- lines.push(" --d-density-scale: 0.65;");
180
- lines.push("}");
181
- lines.push("");
182
- lines.push('.d-section[data-density="spacious"] {');
183
- lines.push(" --d-density-scale: 1.4;");
184
- lines.push("}");
185
- lines.push("");
186
- lines.push(".d-section + .d-section {");
187
- lines.push(" border-top: 1px solid transparent;");
188
- lines.push(" border-image: linear-gradient(to right, transparent, var(--d-border), transparent) 1;");
189
- lines.push(" margin-top: calc(var(--d-section-gap) * var(--d-density-scale, 1));");
190
- lines.push("}");
191
- lines.push("");
192
- emitRule(".d-annotation", [
1280
+ emitRule(".d-conic-ring", [
1281
+ ["--d-conic-value", "0.5"],
1282
+ ["width", "var(--d-conic-size, 4rem)"],
1283
+ ["height", "var(--d-conic-size, 4rem)"],
1284
+ ["border-radius", "50%"],
1285
+ [
1286
+ "background",
1287
+ "conic-gradient(var(--d-conic-color, var(--d-primary)) 0deg, var(--d-conic-color, var(--d-primary)) calc(var(--d-conic-value) * 360deg), var(--d-border) calc(var(--d-conic-value) * 360deg), var(--d-border) 360deg)"
1288
+ ],
1289
+ ["position", "relative"],
193
1290
  ["display", "inline-flex"],
194
1291
  ["align-items", "center"],
195
- ["gap", "0.25em"],
196
- ["font-size", "0.75rem"],
197
- ["font-weight", "500"],
198
- ["padding", "0.125rem 0.5rem"],
199
- ["border-radius", "var(--d-radius-full)"],
200
- ["background", "color-mix(in srgb, var(--d-surface-raised) 88%, transparent)"],
201
- ["border", "1px solid color-mix(in srgb, var(--d-border) 72%, transparent)"],
202
- ["color", "var(--d-text-muted)"],
203
- ["white-space", "nowrap"]
204
- ]);
205
- emitRule('.d-annotation[data-status="success"]', [
206
- ["background", "color-mix(in srgb, var(--d-success) 15%, transparent)"],
207
- ["color", "var(--d-success)"]
1292
+ ["justify-content", "center"]
208
1293
  ]);
209
- emitRule('.d-annotation[data-status="error"]', [
210
- ["background", "color-mix(in srgb, var(--d-error) 15%, transparent)"],
211
- ["color", "var(--d-error)"]
1294
+ emitRule(".d-conic-ring::before", [
1295
+ ["content", "''"],
1296
+ ["position", "absolute"],
1297
+ ["inset", "var(--d-conic-thickness, 0.5rem)"],
1298
+ ["border-radius", "50%"],
1299
+ ["background", "var(--d-bg)"]
212
1300
  ]);
213
- emitRule('.d-annotation[data-status="warning"]', [
214
- ["background", "color-mix(in srgb, var(--d-warning) 15%, transparent)"],
215
- ["color", "var(--d-warning)"]
1301
+ emitRule(".d-conic-ring > *", [
1302
+ ["position", "relative"],
1303
+ ["z-index", "1"]
216
1304
  ]);
217
- emitRule('.d-annotation[data-status="info"]', [
218
- ["background", "color-mix(in srgb, var(--d-info) 15%, transparent)"],
219
- ["color", "var(--d-info)"]
1305
+ emitRule('.d-conic-ring[data-state="success"]', [["--d-conic-color", "var(--d-success)"]]);
1306
+ emitRule('.d-conic-ring[data-state="warning"]', [["--d-conic-color", "var(--d-warning)"]]);
1307
+ emitRule('.d-conic-ring[data-state="error"]', [["--d-conic-color", "var(--d-error)"]]);
1308
+ emitRule(".d-heatmap-cell", [
1309
+ ["--d-heatmap-intensity", "0"],
1310
+ ["display", "inline-block"],
1311
+ ["width", "var(--d-heatmap-cell-size, 0.875rem)"],
1312
+ ["height", "var(--d-heatmap-cell-size, 0.875rem)"],
1313
+ ["border-radius", "2px"],
1314
+ [
1315
+ "background",
1316
+ "color-mix(in srgb, var(--d-primary) calc(var(--d-heatmap-intensity) * 100%), var(--d-surface))"
1317
+ ],
1318
+ ["border", "1px solid var(--d-border)"],
1319
+ ["vertical-align", "middle"]
220
1320
  ]);
221
- emitRule(".d-label", [
222
- ["font-size", "0.7rem"],
223
- ["font-weight", "600"],
224
- ["text-transform", "uppercase"],
225
- ["letter-spacing", "0.08em"],
226
- ["color", "var(--d-text-muted)"],
227
- ["font-family", "var(--d-font-mono, ui-monospace, monospace)"],
228
- ["display", "block"],
229
- ["margin-bottom", "calc(var(--d-label-mb) * var(--d-density-scale, 1))"]
1321
+ emitRule('.d-heatmap-cell[data-status="error"]', [
1322
+ [
1323
+ "background",
1324
+ "color-mix(in srgb, var(--d-error) calc(var(--d-heatmap-intensity) * 100%), var(--d-surface))"
1325
+ ]
230
1326
  ]);
231
- emitRule(".d-label[data-anchor]", [
232
- ["padding-left", "var(--d-label-px)"],
233
- ["border-left", "2px solid var(--d-accent)"]
1327
+ emitRule('.d-heatmap-cell[data-status="success"]', [
1328
+ [
1329
+ "background",
1330
+ "color-mix(in srgb, var(--d-success) calc(var(--d-heatmap-intensity) * 100%), var(--d-surface))"
1331
+ ]
234
1332
  ]);
235
1333
  if (themeOverrideRules.length > 0) {
236
1334
  lines.push("/* \u2500\u2500 Theme-scoped Treatment Overrides \u2500\u2500 */");
@@ -247,6 +1345,59 @@ ${themeBody}
247
1345
  lines.push(" 50% { opacity: 0.5; }");
248
1346
  lines.push("}");
249
1347
  lines.push("");
1348
+ lines.push("@keyframes d-fade-in {");
1349
+ lines.push(" from { opacity: 0; }");
1350
+ lines.push(" to { opacity: 1; }");
1351
+ lines.push("}");
1352
+ lines.push("");
1353
+ lines.push("@keyframes d-slide-up {");
1354
+ lines.push(" from { opacity: 0; transform: translateY(12px); }");
1355
+ lines.push(" to { opacity: 1; transform: translateY(0); }");
1356
+ lines.push("}");
1357
+ lines.push("");
1358
+ lines.push("@keyframes d-scale-in {");
1359
+ lines.push(" from { opacity: 0; transform: scale(0.96); }");
1360
+ lines.push(" to { opacity: 1; transform: scale(1); }");
1361
+ lines.push("}");
1362
+ lines.push("");
1363
+ lines.push("@keyframes d-pulse {");
1364
+ lines.push(" 0%, 100% { opacity: 1; }");
1365
+ lines.push(" 50% { opacity: 0.5; }");
1366
+ lines.push("}");
1367
+ lines.push("");
1368
+ lines.push("@keyframes d-pulse-ring {");
1369
+ lines.push(
1370
+ " 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--d-primary) 40%, transparent); }"
1371
+ );
1372
+ lines.push(" 100% { box-shadow: 0 0 0 12px transparent; }");
1373
+ lines.push("}");
1374
+ lines.push("");
1375
+ lines.push("@keyframes d-shimmer {");
1376
+ lines.push(" 0% { background-position: -200% 0; }");
1377
+ lines.push(" 100% { background-position: 200% 0; }");
1378
+ lines.push("}");
1379
+ lines.push("");
1380
+ lines.push("@keyframes d-float {");
1381
+ lines.push(" 0%, 100% { transform: translateY(0); }");
1382
+ lines.push(" 50% { transform: translateY(-4px); }");
1383
+ lines.push("}");
1384
+ lines.push("");
1385
+ lines.push("/* Respect user motion preferences \u2014 disable all declarative motion */");
1386
+ lines.push("@media (prefers-reduced-motion: reduce) {");
1387
+ lines.push(
1388
+ " .d-enter-fade, .d-enter-slide-up, .d-enter-scale, .d-stagger-children > *,"
1389
+ );
1390
+ lines.push(" .d-pulse, .d-pulse-ring, .d-shimmer, .d-float {");
1391
+ lines.push(" animation: none !important;");
1392
+ lines.push(" }");
1393
+ lines.push(" .d-glow-hover, .d-scale-hover, .d-lift-hover, .d-ripple::after {");
1394
+ lines.push(" transition: none !important;");
1395
+ lines.push(" }");
1396
+ lines.push(" .d-scale-hover:hover, .d-lift-hover:hover {");
1397
+ lines.push(" transform: none !important;");
1398
+ lines.push(" }");
1399
+ lines.push("}");
1400
+ lines.push("");
250
1401
  lines.push("} /* end @layer treatments */");
251
1402
  const decoratorRules = [];
252
1403
  if (themeDecoratorDefinitions) {
@@ -305,37 +1456,59 @@ function generatePersonalityCSS(personality, themeData) {
305
1456
  if (text.includes("neon") || text.includes("glow")) {
306
1457
  const glowColor = "var(--d-accent-glow, rgba(0, 212, 255, 0.3))";
307
1458
  rules.push(`.neon-glow { box-shadow: 0 0 20px ${glowColor}; }`);
308
- rules.push(`.neon-glow-hover:hover { box-shadow: 0 0 24px ${glowColor}; transition: box-shadow var(--d-duration-hover, 0.15s) var(--d-easing, ease); }`);
1459
+ rules.push(
1460
+ `.neon-glow-hover:hover { box-shadow: 0 0 24px ${glowColor}; transition: box-shadow var(--d-duration-hover, 0.15s) var(--d-easing, ease); }`
1461
+ );
309
1462
  rules.push(`.neon-text-glow { text-shadow: 0 0 12px ${glowColor}; }`);
310
- rules.push(`.neon-border-glow { border-color: var(--d-accent); box-shadow: 0 0 8px ${glowColor}; }`);
1463
+ rules.push(
1464
+ `.neon-border-glow { border-color: var(--d-accent); box-shadow: 0 0 8px ${glowColor}; }`
1465
+ );
311
1466
  }
312
1467
  if (text.includes("monospace") || text.includes("mono")) {
313
- rules.push(`.mono-data { font-family: var(--d-font-mono, ui-monospace, monospace); font-variant-numeric: tabular-nums; }`);
1468
+ rules.push(
1469
+ `.mono-data { font-family: var(--d-font-mono, ui-monospace, monospace); font-variant-numeric: tabular-nums; }`
1470
+ );
314
1471
  }
315
1472
  if (text.includes("pulse") || text.includes("ring") || text.includes("status")) {
316
- rules.push(`.status-ring { width: 48px; height: 48px; border-radius: 50%; border: 2px solid var(--d-border); display: flex; align-items: center; justify-content: center; position: relative; transition: border-color 0.2s ease, box-shadow 0.2s ease; }`);
1473
+ rules.push(
1474
+ `.status-ring { width: 48px; height: 48px; border-radius: 50%; border: 2px solid var(--d-border); display: flex; align-items: center; justify-content: center; position: relative; transition: border-color 0.2s ease, box-shadow 0.2s ease; }`
1475
+ );
1476
+ rules.push(`.status-ring[data-size="sm"] { width: 32px; height: 32px; border-width: 1.5px; }`);
1477
+ rules.push(`.status-ring[data-size="md"] { width: 48px; height: 48px; border-width: 2px; }`);
1478
+ rules.push(`.status-ring[data-size="lg"] { width: 64px; height: 64px; border-width: 2.5px; }`);
317
1479
  rules.push(`.status-ring[data-status="active"] { border-color: var(--d-success); }`);
318
- rules.push(`.status-ring[data-status="error"] { border-color: var(--d-error); box-shadow: 0 0 12px color-mix(in srgb, var(--d-error) 25%, transparent); }`);
1480
+ rules.push(
1481
+ `.status-ring[data-status="error"] { border-color: var(--d-error); box-shadow: 0 0 12px color-mix(in srgb, var(--d-error) 25%, transparent); }`
1482
+ );
319
1483
  rules.push(`.status-ring[data-status="warning"] { border-color: var(--d-warning); }`);
320
1484
  rules.push(`.status-ring[data-status="idle"] { border-color: var(--d-text-muted); }`);
321
- rules.push(`.status-ring[data-status="processing"] { border-color: var(--d-primary); animation: pulse-ring 2s ease-in-out infinite; }`);
322
- rules.push(`@keyframes pulse-ring { 0% { opacity: 0.6; transform: scale(1); } 100% { opacity: 0; transform: scale(1.3); } }`);
323
- rules.push(`.status-ring[data-status="active"]::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--d-success); opacity: 0; animation: pulse-ring 2s ease-out infinite; }`);
1485
+ rules.push(
1486
+ `.status-ring[data-status="processing"] { border-color: var(--d-primary); animation: pulse-ring 2s ease-in-out infinite; }`
1487
+ );
1488
+ rules.push(
1489
+ `@keyframes pulse-ring { 0% { opacity: 0.6; transform: scale(1); } 100% { opacity: 0; transform: scale(1.3); } }`
1490
+ );
1491
+ rules.push(
1492
+ `.status-ring[data-status="active"]::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--d-success); opacity: 0; animation: pulse-ring 2s ease-out infinite; }`
1493
+ );
324
1494
  }
325
1495
  if (themeData.motion?.entrance) {
326
- rules.push(`.entrance-fade { animation: decantr-entrance var(--d-duration-entrance, 0.2s) var(--d-easing, ease-out); }`);
327
- rules.push(`@keyframes decantr-entrance { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }`);
1496
+ rules.push(
1497
+ `.entrance-fade { animation: decantr-entrance var(--d-duration-entrance, 0.2s) var(--d-easing, ease-out); }`
1498
+ );
1499
+ rules.push(
1500
+ `@keyframes decantr-entrance { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }`
1501
+ );
328
1502
  }
329
1503
  if (rules.length === 0) return "";
330
1504
  return "\n@layer utilities {\n\n/* \u2500\u2500 Personality-Derived Utilities \u2500\u2500 */\n\n" + rules.join("\n\n") + "\n\n} /* end @layer utilities */\n";
331
1505
  }
332
1506
 
333
1507
  // src/scaffold.ts
334
- import { API_CONTENT_TYPES } from "@decantr/registry";
335
1508
  var __dirname = dirname(fileURLToPath(import.meta.url));
336
1509
  function getPlatformMeta(target) {
337
1510
  const normalized = (target || "react").toLowerCase();
338
- const routing = normalized === "nextjs" ? "pathname" : "hash";
1511
+ const routing = normalized === "nextjs" ? "pathname" : "history";
339
1512
  return {
340
1513
  type: "spa",
341
1514
  routing
@@ -372,19 +1545,28 @@ function collectPatternIdsFromItems(items) {
372
1545
  return [...ids];
373
1546
  }
374
1547
  function mapRegistryArchetypeToArchetypeData(archetype) {
1548
+ const registryExtras = archetype;
1549
+ const registryNavigation = registryExtras.navigation_items;
1550
+ const registryDirectives = registryExtras.directives;
375
1551
  return {
376
1552
  id: archetype.id,
377
1553
  name: archetype.name,
378
1554
  role: archetype.role,
379
1555
  description: archetype.description,
380
- pages: archetype.pages?.map((page) => ({
381
- id: page.id,
382
- shell: page.shell,
383
- default_layout: page.default_layout?.length ? page.default_layout : ["hero"],
384
- patterns: page.patterns?.map(toPatternReferenceObject)
385
- })),
1556
+ pages: archetype.pages?.map((page) => {
1557
+ const pageExtras = page;
1558
+ return {
1559
+ id: page.id,
1560
+ shell: page.shell,
1561
+ default_layout: page.default_layout?.length ? page.default_layout : ["hero"],
1562
+ patterns: page.patterns?.map(toPatternReferenceObject),
1563
+ ...Array.isArray(pageExtras.directives) && pageExtras.directives.length > 0 ? { directives: pageExtras.directives } : {}
1564
+ };
1565
+ }),
386
1566
  features: archetype.features,
387
- seo_hints: archetype.seo_hints
1567
+ seo_hints: archetype.seo_hints,
1568
+ ...Array.isArray(registryNavigation) && registryNavigation.length > 0 ? { navigation_items: registryNavigation } : {},
1569
+ ...Array.isArray(registryDirectives) && registryDirectives.length > 0 ? { directives: registryDirectives } : {}
388
1570
  };
389
1571
  }
390
1572
  function composeArchetypes(composeEntries, archetypeResults) {
@@ -409,7 +1591,9 @@ function composeArchetypes(composeEntries, archetypeResults) {
409
1591
  for (const page of data.pages) {
410
1592
  allPages.push({
411
1593
  id: page.id,
412
- layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, page.patterns)),
1594
+ layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map(
1595
+ (item) => resolvePatternAlias(item, page.patterns)
1596
+ ),
413
1597
  ...page.shell !== defaultShell ? { shell_override: page.shell } : {}
414
1598
  });
415
1599
  }
@@ -418,7 +1602,9 @@ function composeArchetypes(composeEntries, archetypeResults) {
418
1602
  for (const page of data.pages) {
419
1603
  allPages.push({
420
1604
  id: `${prefix}-${page.id}`,
421
- layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, page.patterns)),
1605
+ layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map(
1606
+ (item) => resolvePatternAlias(item, page.patterns)
1607
+ ),
422
1608
  ...page.shell !== defaultShell ? { shell_override: page.shell } : {}
423
1609
  });
424
1610
  }
@@ -439,14 +1625,16 @@ function composeArchetypes(composeEntries, archetypeResults) {
439
1625
  function composeSections(composeEntries, archetypeResults, overrides) {
440
1626
  if (composeEntries.length === 0) {
441
1627
  return {
442
- sections: [{
443
- id: "default",
444
- role: "primary",
445
- shell: "sidebar-main",
446
- features: [],
447
- description: "Default section",
448
- pages: [{ id: "home", layout: ["hero"] }]
449
- }],
1628
+ sections: [
1629
+ {
1630
+ id: "default",
1631
+ role: "primary",
1632
+ shell: "sidebar-main",
1633
+ features: [],
1634
+ description: "Default section",
1635
+ pages: [{ id: "home", layout: ["hero"] }]
1636
+ }
1637
+ ],
450
1638
  features: [],
451
1639
  defaultShell: "sidebar-main"
452
1640
  };
@@ -470,7 +1658,13 @@ function composeSections(composeEntries, archetypeResults, overrides) {
470
1658
  const overriddenPage = overrides?.pages?.[page.id];
471
1659
  pages.push({
472
1660
  id: page.id,
473
- layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, page.patterns)),
1661
+ layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map(
1662
+ (item) => resolvePatternAlias(item, page.patterns)
1663
+ ),
1664
+ // Propagate per-page directives from the archetype so cold LLMs
1665
+ // see execution-level rules in the page-pack contract instead of
1666
+ // having to read the section narrative.
1667
+ ...Array.isArray(page.directives) && page.directives.length > 0 ? { directives: page.directives } : {},
474
1668
  ...overriddenPage
475
1669
  });
476
1670
  }
@@ -480,7 +1674,14 @@ function composeSections(composeEntries, archetypeResults, overrides) {
480
1674
  shell: data.pages[0]?.shell || "sidebar-main",
481
1675
  features: data.features ?? [],
482
1676
  description: data.description ?? "",
483
- pages
1677
+ pages,
1678
+ // Propagate navigation_items from the archetype so the shell renders
1679
+ // the correct primary-nav list instead of the LLM improvising one.
1680
+ ...Array.isArray(data.navigation_items) && data.navigation_items.length > 0 ? { navigation_items: data.navigation_items } : {},
1681
+ // Propagate section-level directives from the archetype into the
1682
+ // essence so the section-pack renderer can surface them in the
1683
+ // pack contract.
1684
+ ...Array.isArray(data.directives) && data.directives.length > 0 ? { directives: data.directives } : {}
484
1685
  });
485
1686
  if (data.features) {
486
1687
  allFeatures.push(...data.features);
@@ -575,7 +1776,12 @@ function deriveTransitions(zones) {
575
1776
  const hasGateway = roles.has("gateway");
576
1777
  const hasPublic = roles.has("public");
577
1778
  if (hasPublic && hasGateway) {
578
- transitions.push({ from: "public", to: "gateway", type: "conversion", trigger: gatewayTrigger });
1779
+ transitions.push({
1780
+ from: "public",
1781
+ to: "gateway",
1782
+ type: "conversion",
1783
+ trigger: gatewayTrigger
1784
+ });
579
1785
  }
580
1786
  if (hasPublic && hasApp && !hasGateway) {
581
1787
  transitions.push({ from: "public", to: "app", type: "conversion", trigger: "navigation" });
@@ -651,7 +1857,7 @@ function mapRegistryThemeToThemeData(theme) {
651
1857
  pattern_preferences: theme.pattern_preferences
652
1858
  };
653
1859
  }
654
- function generateTokensCSS(themeData, mode, spatialTokens) {
1860
+ function generateTokensCSS(themeData, mode, spatialTokens, options) {
655
1861
  if (!themeData) {
656
1862
  const spatialLines2 = spatialTokens ? "\n" + Object.entries(spatialTokens).map(([k, v]) => ` ${k}: ${v};`).join("\n") : "";
657
1863
  return `/* No theme data available */
@@ -673,19 +1879,34 @@ function generateTokensCSS(themeData, mode, spatialTokens) {
673
1879
  const seed = themeData.seed || {};
674
1880
  const palette = themeData.palette || {};
675
1881
  const resolvedMode = mode === "auto" ? "dark" : mode;
1882
+ const FALLBACKS = {
1883
+ bg: { light: "#ffffff", dark: "#18181b" },
1884
+ surface: { light: "#f9fafb", dark: "#1f1f23" },
1885
+ "surface-raised": { light: "#ffffff", dark: "#27272a" },
1886
+ border: { light: "#e5e7eb", dark: "#3f3f46" },
1887
+ text: { light: "#111827", dark: "#fafafa" },
1888
+ "text-muted": { light: "#6b7280", dark: "#a1a1aa" },
1889
+ secondary: { light: "#6b7280", dark: "#A1A1AA" }
1890
+ };
676
1891
  function buildTokens(tokenMode) {
1892
+ const tokenModeKey = tokenMode === "light" ? "light" : "dark";
1893
+ const pickFb = (key) => {
1894
+ const fallbacks = FALLBACKS[key];
1895
+ if (!fallbacks) return tokenModeKey === "light" ? "#ffffff" : "#18181b";
1896
+ return fallbacks[tokenModeKey];
1897
+ };
677
1898
  return {
678
1899
  // Seed colors
679
1900
  "--d-primary": seed.primary || "#6366f1",
680
- "--d-secondary": palette.secondary?.[tokenMode] || palette.secondary?.dark || seed.secondary || "#A1A1AA",
1901
+ "--d-secondary": palette.secondary?.[tokenMode] || pickFb("secondary"),
681
1902
  "--d-accent": seed.accent || "#f59e0b",
682
- // Palette colors (mode-aware)
683
- "--d-bg": palette.background?.[tokenMode] || "#18181b",
684
- "--d-surface": palette.surface?.[tokenMode] || "#1f1f23",
685
- "--d-surface-raised": palette["surface-raised"]?.[tokenMode] || "#27272a",
686
- "--d-border": palette.border?.[tokenMode] || "#3f3f46",
687
- "--d-text": palette.text?.[tokenMode] || "#fafafa",
688
- "--d-text-muted": palette["text-muted"]?.[tokenMode] || "#a1a1aa",
1903
+ // Palette colors (mode-aware with mode-aware fallbacks)
1904
+ "--d-bg": palette.background?.[tokenMode] || pickFb("bg"),
1905
+ "--d-surface": palette.surface?.[tokenMode] || pickFb("surface"),
1906
+ "--d-surface-raised": palette["surface-raised"]?.[tokenMode] || pickFb("surface-raised"),
1907
+ "--d-border": palette.border?.[tokenMode] || pickFb("border"),
1908
+ "--d-text": palette.text?.[tokenMode] || pickFb("text"),
1909
+ "--d-text-muted": palette["text-muted"]?.[tokenMode] || pickFb("text-muted"),
689
1910
  "--d-primary-hover": palette["primary-hover"]?.[tokenMode] || seed.primary || "#6366f1",
690
1911
  // Spacing scale
691
1912
  "--d-gap-1": "0.25rem",
@@ -706,11 +1927,62 @@ function generateTokensCSS(themeData, mode, spatialTokens) {
706
1927
  "--d-shadow": tokenMode === "light" ? "0 1px 3px rgba(0,0,0,0.1)" : "0 1px 3px rgba(0,0,0,0.25)",
707
1928
  "--d-shadow-md": tokenMode === "light" ? "0 4px 6px rgba(0,0,0,0.1)" : "0 4px 6px rgba(0,0,0,0.3)",
708
1929
  "--d-shadow-lg": tokenMode === "light" ? "0 10px 15px rgba(0,0,0,0.1)" : "0 10px 15px rgba(0,0,0,0.4)",
1930
+ // Elevation scale (v2.1 Tier B3). Formal cross-theme depth system.
1931
+ // .d-elevate[data-level="N"] reads these. Dark themes need stronger
1932
+ // alpha to register on dark backgrounds.
1933
+ "--d-elevation-0": "none",
1934
+ "--d-elevation-1": tokenMode === "light" ? "0 1px 2px rgba(0,0,0,0.06)" : "0 1px 2px rgba(0,0,0,0.3)",
1935
+ "--d-elevation-2": tokenMode === "light" ? "0 2px 4px rgba(0,0,0,0.08)" : "0 2px 4px rgba(0,0,0,0.4)",
1936
+ "--d-elevation-3": tokenMode === "light" ? "0 4px 12px rgba(0,0,0,0.10)" : "0 4px 12px rgba(0,0,0,0.5)",
1937
+ "--d-elevation-4": tokenMode === "light" ? "0 8px 24px rgba(0,0,0,0.14)" : "0 8px 24px rgba(0,0,0,0.55)",
1938
+ "--d-elevation-5": tokenMode === "light" ? "0 16px 48px rgba(0,0,0,0.18)" : "0 16px 48px rgba(0,0,0,0.6)",
709
1939
  // Status colors
710
1940
  "--d-success": themeData.tokens?.base?.success || "#22c55e",
711
1941
  "--d-error": themeData.tokens?.base?.danger || "#ef4444",
712
1942
  "--d-warning": themeData.tokens?.base?.warning || "#f59e0b",
713
- "--d-info": "#3b82f6"
1943
+ "--d-info": "#3b82f6",
1944
+ // Motion scale (v2.1 Tier B1). Canonical durations + easings.
1945
+ // d-enter-fade, d-pulse, d-glow-hover, etc. all read these.
1946
+ // Themes can override via theme.motion.* and this picks them up
1947
+ // below. Defaults here ensure treatments work even without theme.
1948
+ "--d-motion-instant": "80ms",
1949
+ "--d-motion-fast": "150ms",
1950
+ "--d-motion-base": "250ms",
1951
+ "--d-motion-slow": "400ms",
1952
+ "--d-motion-slower": "600ms",
1953
+ "--d-motion-stagger": "60ms",
1954
+ "--d-motion-ease": "cubic-bezier(0.4, 0, 0.2, 1)",
1955
+ "--d-motion-ease-out": "cubic-bezier(0, 0, 0.2, 1)",
1956
+ "--d-motion-ease-in": "cubic-bezier(0.4, 0, 1, 1)",
1957
+ "--d-motion-ease-spring": "cubic-bezier(0.34, 1.56, 0.64, 1)",
1958
+ // Typography scale (v2.1 Tier B2). Canonical sizes + weights +
1959
+ // tracking + leading. d-display, d-headline, d-title, d-prose,
1960
+ // d-caption, d-eyebrow read these. Themes override via
1961
+ // theme.typography.* below.
1962
+ "--d-font-body": "ui-sans-serif, system-ui, -apple-system, sans-serif",
1963
+ "--d-font-display": "ui-sans-serif, system-ui, -apple-system, sans-serif",
1964
+ "--d-weight-regular": "400",
1965
+ "--d-weight-medium": "500",
1966
+ "--d-weight-semibold": "600",
1967
+ "--d-weight-bold": "700",
1968
+ "--d-tracking-tight": "-0.02em",
1969
+ "--d-tracking-normal": "0",
1970
+ "--d-tracking-wide": "0.04em",
1971
+ "--d-tracking-wider": "0.08em",
1972
+ "--d-leading-tight": "1.1",
1973
+ "--d-leading-snug": "1.25",
1974
+ "--d-leading-normal": "1.5",
1975
+ "--d-leading-relaxed": "1.625",
1976
+ "--d-text-xs": "0.75rem",
1977
+ "--d-text-sm": "0.875rem",
1978
+ "--d-text-base": "1rem",
1979
+ "--d-text-lg": "1.125rem",
1980
+ "--d-text-xl": "1.25rem",
1981
+ "--d-text-2xl": "1.5rem",
1982
+ "--d-text-3xl": "1.875rem",
1983
+ "--d-text-4xl": "2.25rem",
1984
+ "--d-text-5xl": "3rem",
1985
+ "--d-text-6xl": "4rem"
714
1986
  };
715
1987
  }
716
1988
  const tokens = buildTokens(resolvedMode);
@@ -729,6 +2001,54 @@ function generateTokensCSS(themeData, mode, spatialTokens) {
729
2001
  if (themeData?.motion?.timing) {
730
2002
  tokens["--d-easing"] = themeData.motion.timing;
731
2003
  }
2004
+ if (themeData?.typography?.display) {
2005
+ tokens["--d-font-display"] = themeData.typography.display;
2006
+ }
2007
+ if (themeData?.typography?.body) {
2008
+ tokens["--d-font-body"] = themeData.typography.body;
2009
+ }
2010
+ if (themeData?.motion?.durations?.instant) {
2011
+ tokens["--d-motion-instant"] = themeData.motion.durations.instant;
2012
+ }
2013
+ if (themeData?.motion?.durations?.fast) {
2014
+ tokens["--d-motion-fast"] = themeData.motion.durations.fast;
2015
+ }
2016
+ if (themeData?.motion?.durations?.base) {
2017
+ tokens["--d-motion-base"] = themeData.motion.durations.base;
2018
+ }
2019
+ if (themeData?.motion?.durations?.slow) {
2020
+ tokens["--d-motion-slow"] = themeData.motion.durations.slow;
2021
+ }
2022
+ if (themeData?.motion?.durations?.slower) {
2023
+ tokens["--d-motion-slower"] = themeData.motion.durations.slower;
2024
+ }
2025
+ if (themeData?.motion?.durations?.stagger) {
2026
+ tokens["--d-motion-stagger"] = themeData.motion.durations.stagger;
2027
+ }
2028
+ if (themeData?.motion?.easings?.ease) {
2029
+ tokens["--d-motion-ease"] = themeData.motion.easings.ease;
2030
+ }
2031
+ if (themeData?.motion?.easings?.easeOut) {
2032
+ tokens["--d-motion-ease-out"] = themeData.motion.easings.easeOut;
2033
+ }
2034
+ if (themeData?.motion?.easings?.easeIn) {
2035
+ tokens["--d-motion-ease-in"] = themeData.motion.easings.easeIn;
2036
+ }
2037
+ if (themeData?.motion?.easings?.spring) {
2038
+ tokens["--d-motion-ease-spring"] = themeData.motion.easings.spring;
2039
+ }
2040
+ if (themeData?.elevation) {
2041
+ for (let i = 1; i <= 5; i++) {
2042
+ const value = themeData.elevation[String(i)];
2043
+ if (typeof value === "string") {
2044
+ tokens[`--d-elevation-${i}`] = value;
2045
+ } else if (value && typeof value === "object") {
2046
+ const modeKey = resolvedMode === "light" ? "light" : "dark";
2047
+ const modeValue = value[modeKey];
2048
+ if (modeValue) tokens[`--d-elevation-${i}`] = modeValue;
2049
+ }
2050
+ }
2051
+ }
732
2052
  const lines = Object.entries(tokens).map(([key, value]) => ` ${key}: ${value};`).join("\n");
733
2053
  const spatialLines = spatialTokens ? "\n" + Object.entries(spatialTokens).map(([k, v]) => ` ${k}: ${v};`).join("\n") : "";
734
2054
  let css = `/* Generated by @decantr/cli */
@@ -737,21 +2057,26 @@ function generateTokensCSS(themeData, mode, spatialTokens) {
737
2057
  ${lines}${spatialLines}
738
2058
  }
739
2059
  `;
2060
+ const paletteKeys = [
2061
+ "--d-bg",
2062
+ "--d-surface",
2063
+ "--d-surface-raised",
2064
+ "--d-border",
2065
+ "--d-text",
2066
+ "--d-text-muted",
2067
+ "--d-primary-hover",
2068
+ "--d-shadow-sm",
2069
+ "--d-shadow",
2070
+ "--d-shadow-md",
2071
+ "--d-shadow-lg",
2072
+ "--d-elevation-1",
2073
+ "--d-elevation-2",
2074
+ "--d-elevation-3",
2075
+ "--d-elevation-4",
2076
+ "--d-elevation-5"
2077
+ ];
740
2078
  if (mode === "auto") {
741
2079
  const lightTokens = buildTokens("light");
742
- const paletteKeys = [
743
- "--d-bg",
744
- "--d-surface",
745
- "--d-surface-raised",
746
- "--d-border",
747
- "--d-text",
748
- "--d-text-muted",
749
- "--d-primary-hover",
750
- "--d-shadow-sm",
751
- "--d-shadow",
752
- "--d-shadow-md",
753
- "--d-shadow-lg"
754
- ];
755
2080
  const lightLines = Object.entries(lightTokens).filter(([key]) => paletteKeys.includes(key)).map(([key, value]) => ` ${key}: ${value};`).join("\n");
756
2081
  css += `
757
2082
  @media (prefers-color-scheme: light) {
@@ -759,6 +2084,22 @@ ${lines}${spatialLines}
759
2084
  ${lightLines}
760
2085
  }
761
2086
  }
2087
+ `;
2088
+ }
2089
+ if (options?.hasThemeToggle) {
2090
+ const opposite = resolvedMode === "light" ? "dark" : "light";
2091
+ const oppositeTokens = buildTokens(opposite);
2092
+ const oppositeLines = Object.entries(oppositeTokens).filter(([key]) => paletteKeys.includes(key)).map(([key, value]) => ` ${key}: ${value};`).join("\n");
2093
+ css += `
2094
+ /*
2095
+ * Theme-toggle variant. Blueprint declared the \`theme-toggle\` feature,
2096
+ * so the user-facing toggle sets \`data-mode\` on <html>. Apply the
2097
+ * opposite mode's palette when that attribute is set.
2098
+ */
2099
+ :root[data-mode="${opposite}"],
2100
+ [data-mode="${opposite}"] {
2101
+ ${oppositeLines}
2102
+ }
762
2103
  `;
763
2104
  }
764
2105
  css += `}
@@ -899,13 +2240,13 @@ function extractPatternNames(item) {
899
2240
  return [];
900
2241
  }
901
2242
  function loadTemplate(name) {
902
- const fromDist = join(__dirname, "..", "src", "templates", name);
903
- if (existsSync(fromDist)) {
904
- return readFileSync(fromDist, "utf-8");
2243
+ const fromDist = join2(__dirname, "..", "src", "templates", name);
2244
+ if (existsSync2(fromDist)) {
2245
+ return readFileSync2(fromDist, "utf-8");
905
2246
  }
906
- const fromSrc = join(__dirname, "templates", name);
907
- if (existsSync(fromSrc)) {
908
- return readFileSync(fromSrc, "utf-8");
2247
+ const fromSrc = join2(__dirname, "templates", name);
2248
+ if (existsSync2(fromSrc)) {
2249
+ return readFileSync2(fromSrc, "utf-8");
909
2250
  }
910
2251
  throw new Error(`Template not found: ${name}`);
911
2252
  }
@@ -940,15 +2281,15 @@ function resolvePatternAlias(item, patterns) {
940
2281
  return item;
941
2282
  }
942
2283
  function buildEssenceV3(options, archetypeData, themeHints) {
943
- let pages = [
944
- { id: "home", layout: ["hero"] }
945
- ];
2284
+ let pages = [{ id: "home", layout: ["hero"] }];
946
2285
  let features = options.features;
947
2286
  let defaultShell = options.shell || "sidebar-main";
948
2287
  if (archetypeData?.pages) {
949
2288
  defaultShell = archetypeData.pages[0]?.shell || defaultShell;
950
2289
  pages = archetypeData.pages.map((p) => {
951
- const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
2290
+ const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map(
2291
+ (item) => resolvePatternAlias(item, p.patterns)
2292
+ );
952
2293
  return {
953
2294
  id: p.id,
954
2295
  ...p.shell !== defaultShell ? { shell_override: p.shell } : {},
@@ -970,9 +2311,21 @@ function buildEssenceV3(options, archetypeData, themeHints) {
970
2311
  sharp: 2
971
2312
  };
972
2313
  const guardModeMap = {
973
- strict: { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" },
974
- guided: { mode: "guided", dna_enforcement: "error", blueprint_enforcement: "off" },
975
- creative: { mode: "creative", dna_enforcement: "off", blueprint_enforcement: "off" }
2314
+ strict: {
2315
+ mode: "strict",
2316
+ dna_enforcement: "error",
2317
+ blueprint_enforcement: "warn"
2318
+ },
2319
+ guided: {
2320
+ mode: "guided",
2321
+ dna_enforcement: "error",
2322
+ blueprint_enforcement: "off"
2323
+ },
2324
+ creative: {
2325
+ mode: "creative",
2326
+ dna_enforcement: "off",
2327
+ blueprint_enforcement: "off"
2328
+ }
976
2329
  };
977
2330
  const dna = {
978
2331
  theme: {
@@ -987,9 +2340,13 @@ function buildEssenceV3(options, archetypeData, themeHints) {
987
2340
  content_gap: densityLevelMap[options.density] || "_gap4"
988
2341
  },
989
2342
  typography: {
990
- scale: themeHints?.typography?.scale || "modular",
991
- heading_weight: themeHints?.typography?.heading_weight || 600,
992
- body_weight: themeHints?.typography?.body_weight || 400
2343
+ // Coerce: 5 historical themes stored typography.scale as a numeric
2344
+ // ratio (e.g., 1.15) instead of a string enum, which fails the v3
2345
+ // essence schema (`/dna/typography/scale: must be string`). Treat
2346
+ // any non-string as missing and fall back to the canonical 'modular'.
2347
+ scale: typeof themeHints?.typography?.scale === "string" ? themeHints.typography.scale : "modular",
2348
+ heading_weight: typeof themeHints?.typography?.heading_weight === "number" ? themeHints.typography.heading_weight : 600,
2349
+ body_weight: typeof themeHints?.typography?.body_weight === "number" ? themeHints.typography.body_weight : 400
993
2350
  },
994
2351
  color: {
995
2352
  palette: "semantic",
@@ -1068,16 +2425,179 @@ import './styles/global.css'; // Resets
1068
2425
 
1069
2426
  ### Visual Treatments
1070
2427
 
1071
- Six base treatment classes provide semantic styling. Combine with atoms for layout:
2428
+ Decantr ships semantic treatment classes that cover the recurring UI idioms. Combine with atoms for layout \u2014 don't hand-roll equivalent CSS classes.
2429
+
2430
+ **Core treatments (every app uses these):**
1072
2431
 
1073
2432
  | Treatment | Class | Variants / States |
1074
2433
  |-----------|-------|-------------------|
1075
- | **Interactive Surface** | \`d-interactive\` | \`data-variant="primary\\|ghost\\|danger"\`, hover/focus-visible/disabled states |
2434
+ | **Interactive Surface** | \`d-interactive\` | \`data-variant="primary\\|ghost\\|danger"\`, \`data-size="sm\\|md\\|lg"\`, hover/focus-visible/disabled states |
1076
2435
  | **Container Surface** | \`d-surface\` | \`data-variant="raised\\|overlay"\`, optional \`data-interactive\` for hover |
1077
2436
  | **Data Display** | \`d-data\`, \`d-data-header\`, \`d-data-row\`, \`d-data-cell\` | Row hover highlight |
1078
2437
  | **Form Control** | \`d-control\` | Focus ring, placeholder, disabled, error via \`aria-invalid\` |
1079
2438
  | **Section Rhythm** | \`d-section\` | Auto-spacing between adjacent sections, density-aware |
1080
2439
  | **Inline Annotation** | \`d-annotation\` | \`data-status="success\\|error\\|warning\\|info"\` |
2440
+ | **Section Label** | \`d-label\` | \`data-anchor\` for accent-border section headers |
2441
+
2442
+ **Common UI idioms (use these before hand-rolling):**
2443
+
2444
+ | Treatment | Class | Variants / States |
2445
+ |-----------|-------|-------------------|
2446
+ | **Text Link** | \`d-link\` | \`data-variant="subtle\\|strong"\`, active state via \`aria-current="page"\` or \`data-active="true"\` |
2447
+ | **Icon Button** | \`d-icon-btn\` | \`data-size="sm\\|lg"\`, \`data-variant="primary"\`, hover/focus-visible/disabled |
2448
+ | **Nav Link** | \`d-nav-link\` | Active state via \`aria-current="page"\` or \`data-active="true"\` (accent left-border pill) |
2449
+ | **Stepper Chip** | \`d-step-chip\` | \`data-step-state="pending\\|active\\|done"\` |
2450
+ | **Divider utilities** | \`d-divider-top\`, \`d-divider-bottom\`, \`d-divider-left\`, \`d-divider-right\`, \`d-divider\` | Single-side border rule, or standalone \`<hr className="d-divider">\` |
2451
+
2452
+ **Spatial / graph patterns (for canvases with positioned nodes):**
2453
+
2454
+ | Treatment | Class | Variants / States |
2455
+ |-----------|-------|-------------------|
2456
+ | **Agent Node** | \`d-agent-node\` | Card sized for graph canvases (200-260px wide). \`data-status="active\\|error"\` for highlights (error adds red border-glow shadow, active adds accent border). Pair with absolute positioning on the canvas parent. |
2457
+ | **Connection Port** | \`d-port\` | \`data-side="left\\|right\\|top\\|bottom"\` positions the 8px dot on the node edge. \`data-active="true"\` colors it with accent. Use as a slot inside \`d-agent-node\` so SVG connection paths can anchor to predictable element coordinates via \`getBoundingClientRect\`. |
2458
+
2459
+ **Composite card (structural companion to theme card decorators):**
2460
+
2461
+ | Treatment | Class | Variants / States |
2462
+ |-----------|-------|-------------------|
2463
+ | **Card** | \`d-card\` | \`data-padding="compact\\|spacious\\|none"\`, \`data-interactive\` for hover elevation + border accent |
2464
+ | **Card header** | \`d-card-header\` | Flex row, bottom-bordered; pair with \`d-title\` + \`d-icon-btn\` slots |
2465
+ | **Card body** | \`d-card-body\` | Content region, flex col, \`flex: 1 1 auto\` |
2466
+ | **Card footer** | \`d-card-footer\` | Right-aligned action row, top-bordered |
2467
+
2468
+ Pair \`d-card\` with a theme card decorator (e.g., \`carbon-card\`) for hover glow / gradient border. The composite handles layout; the decorator handles aesthetic polish.
2469
+
2470
+ **Data-viz primitives (do NOT hand-roll inline SVGs for these):**
2471
+
2472
+ | Treatment | Class | Purpose / Variants |
2473
+ |-----------|-------|---------------------|
2474
+ | **Timeline rail** | \`d-timeline-rail\` + \`d-timeline-dot\` | Vertical timeline. Dot has \`data-state="active\\|done\\|error\\|warning"\` controlling color. |
2475
+ | **Sparkline** | \`d-sparkline\` + \`d-sparkline-path\` + \`d-sparkline-area\` | Inline trend SVG. \`data-trend="up\\|down"\` colors stroke + area accent. |
2476
+ | **Intent radar** | \`d-intent-radar\` + \`d-intent-radar-ring[data-level]\` + \`d-intent-radar-axis\` | Concentric ring backdrop (5 levels) for confidence/score wheels. \`--d-radar-axis-angle\` for rotated axes. |
2477
+ | **Waveform** | \`d-waveform\` + \`d-waveform-path\` | Audio/signal waveform path container. \`data-state="active"\` switches to success color. |
2478
+ | **QR placeholder** | \`d-qr-placeholder\` | Pure-CSS QR-code placeholder (repeating gradients). \`--d-qr-size\` for size override. |
2479
+ | **Conic ring** | \`d-conic-ring\` | Gauge/confidence ring. Set \`--d-conic-value\` (0..1) to fill arc. \`data-state="success\\|warning\\|error"\` switches color. \`--d-conic-thickness\` for ring width. |
2480
+ | **Heatmap cell** | \`d-heatmap-cell\` | Single heatmap cell. \`--d-heatmap-intensity\` 0..1 blends primary\u2192surface. \`data-status="error\\|success"\` switches base color. |
2481
+
2482
+ **Banners / prominent CTAs:**
2483
+
2484
+ | Treatment | Class | Variants / States |
2485
+ |-----------|-------|-------------------|
2486
+ | **CTA Banner** | \`d-cta-banner\` | \`data-size="compact\\|hero"\` (default is between). Gradient wash from primary to accent. Theme can override via \`--d-cta-gradient\` / \`--d-cta-text\` CSS vars. |
2487
+ | **Dark-Pill Button** | \`d-interactive\` + \`data-variant="dark"\` | Pill-shaped dark-on-accent CTA for use inside \`d-cta-banner\`. Theme can override via \`--d-cta-pill-bg\` / \`--d-cta-pill-text\`. |
2488
+
2489
+ **Shell layouts (do NOT hand-roll these):**
2490
+
2491
+ | Treatment | Class | Purpose / States |
2492
+ |-----------|-------|------------------|
2493
+ | **Shell root** | \`d-shell\` | Full-viewport root container. \`data-layout="sidebar-main\\|centered\\|top-nav-footer\\|sidebar-aside"\` switches the layout model (default equivalent to top-nav-footer: vertical flex with sticky header). |
2494
+ | **Sidebar** | \`d-shell-sidebar\` | Left 240px nav column. \`data-collapsed="true"\` switches to a 64px rail. Below \`_mdmax:\` auto-becomes an off-canvas drawer \u2014 toggle via \`data-mobile-open="true"\`. |
2495
+ | **Main** | \`d-shell-main\` | Remaining-width column to the right of the sidebar (or the full content area in top-nav shells). Handles scroll internally. |
2496
+ | **Aside** | \`d-shell-aside\` | Right 320px auxiliary panel for inspector / timeline / minimap in \`sidebar-aside\` layouts. Below \`_mdmax:\` hides by default; toggle with \`data-mobile-open="true"\`. |
2497
+ | **Header** | \`d-shell-header\` | 52px sticky top bar with horizontal flex layout. Use inside \`d-shell-main\` (sidebar-main shells) or at the top of \`d-shell\` (top-nav shells). |
2498
+ | **Body** | \`d-shell-body\` | Scrollable main region. \`data-padding="compact\\|spacious\\|none"\` overrides the default 1rem padding. |
2499
+ | **Footer** | \`d-shell-footer\` | Narrow band below the body with top border. |
2500
+ | **Centered card** | \`d-shell-centered-card\` | The content parent inside \`d-shell[data-layout="centered"]\`. Caps width at 28rem. |
2501
+
2502
+ **Shell layout recipes:**
2503
+ - **Auth / confirmation:** \`d-shell[data-layout="centered"] + d-shell-centered-card\`.
2504
+ - **Dashboard with sidebar:** \`d-shell[data-layout="sidebar-main"] + d-shell-sidebar + d-shell-main (> d-shell-header + d-shell-body)\`.
2505
+ - **Dashboard with inspector / timeline / minimap:** \`d-shell[data-layout="sidebar-aside"] + d-shell-sidebar + d-shell-main + d-shell-aside\` (3-column grid; aside collapses off-canvas below md).
2506
+ - **Marketing / public pages:** \`d-shell[data-layout="top-nav-footer"]\` (or bare \`d-shell\`) with \`d-shell-header\` at the top and \`d-shell-body\` + \`d-shell-footer\`.
2507
+
2508
+ Do NOT hand-roll \`.shell-sidebar\`, \`.shell-centered\`, \`.shell-tnf\`, \`.shell-aside\`, \`.sidebar-main-layout\`, or similar class names. They exist as treatments.
2509
+
2510
+ ### Theme toggle
2511
+
2512
+ If the blueprint declares the \`theme-toggle\` feature, \`tokens.css\` includes a \`[data-mode="<opposite>"]\` selector block. Flip the visible mode by setting \`data-mode\` on \`<html>\` (or any ancestor):
2513
+
2514
+ \`\`\`tsx
2515
+ // Toggle between the blueprint's primary mode and its opposite.
2516
+ function ThemeToggle() {
2517
+ const toggle = () => {
2518
+ const html = document.documentElement;
2519
+ const current = html.getAttribute('data-mode');
2520
+ html.setAttribute('data-mode', current === 'dark' ? 'light' : 'dark');
2521
+ };
2522
+ return <button className="d-icon-btn" onClick={toggle}><SunMoon /></button>;
2523
+ }
2524
+ \`\`\`
2525
+
2526
+ Do NOT branch component code on the current mode via JS to re-style elements \u2014 the token switch handles it CSS-side.
2527
+
2528
+ **Modal / palette chrome:**
2529
+
2530
+ | Treatment | Class | Purpose / States |
2531
+ |-----------|-------|------------------|
2532
+ | **Modal root** | \`d-modal\` | Fixed-position overlay covering the viewport. \`data-align="top"\` shifts content to top 15vh (common for command palettes). |
2533
+ | **Modal backdrop** | \`d-modal-backdrop\` | Scrim with backdrop-blur. Place as a sibling inside \`d-modal\` with \`onClick\` to close. |
2534
+ | **Modal panel** | \`d-modal-panel\` | The actual dialog content. \`data-size="sm\\|lg"\` adjusts max-width (default 32rem). |
2535
+ | **Command palette** | \`d-palette\` | Specialized modal-panel variant for command palettes \u2014 40rem wide, 60vh max-height. |
2536
+ | **Palette input** | \`d-palette-input\` | Search input at top of palette. |
2537
+ | **Palette list** | \`d-palette-list\` | Scrollable command list. |
2538
+ | **Palette row** | \`d-palette-row\` | Individual command row. \`data-active="true"\` for keyboard-highlighted row. |
2539
+ | **Palette section** | \`d-palette-section\` | Uppercase section label inside palette (e.g., "Navigation"). |
2540
+ | **Keyboard chip** | \`d-kbd\` | Mono-font key hint. Use inside \`<kbd>\` for accessibility. |
2541
+ | **Hotkey indicator** | \`d-hotkey-indicator\` | Corner badge shown while a chord hotkey prefix is armed. Apply \`data-visible={isArmed}\` and \`data-prefix="g"\` when the prefix is pressed; clear on timeout/resolve. Required when \`hotkey_semantics.show_chord_indicator !== false\`. |
2542
+
2543
+ Composition pattern for a command palette (REQUIRED \u2014 palette MUST be wrapped in \`d-modal\` + \`d-modal-backdrop\`, otherwise it renders as a top-level full-width strip):
2544
+ \`\`\`tsx
2545
+ {open && (
2546
+ <div className="d-modal" data-align="top">
2547
+ <div className="d-modal-backdrop" onClick={close} />
2548
+ <div className="d-palette">
2549
+ <input className="d-palette-input" placeholder="Type a command..." />
2550
+ <ul className="d-palette-list">
2551
+ <li className="d-palette-section">Navigation</li>
2552
+ <li className="d-palette-row" data-active={i === selectedIndex}>
2553
+ <Bot /> Go to Agents
2554
+ <kbd className="d-kbd">g a</kbd>
2555
+ </li>
2556
+ </ul>
2557
+ </div>
2558
+ </div>
2559
+ )}
2560
+ \`\`\`
2561
+
2562
+ **Hard rules for the palette:**
2563
+ - The palette MUST be inside \`d-modal\` (positions/centers it as overlay) AND have a \`d-modal-backdrop\` sibling (provides scrim + click-to-close).
2564
+ - Group commands by section using \`d-palette-section\` (Uppercase eyebrow label) \u2014 never render a flat list. The blueprint's \`navigation.command_palette.commands\` already has \`section\` fields; honor them.
2565
+ - Each row should have an icon on the LEFT (Lucide), label in the center, and a \`d-kbd\` shortcut hint on the RIGHT \u2014 even when the command has no hotkey, leave the right slot empty for visual rhythm.
2566
+
2567
+ Composition pattern for an auth page (REQUIRED \u2014 must use \`d-shell[data-layout="centered"]\` + \`d-shell-centered-card\`, not a hand-rolled centering wrapper):
2568
+ \`\`\`tsx
2569
+ <div className="d-shell" data-layout="centered">
2570
+ <div className="d-shell-centered-card">
2571
+ {/* Logo + form go here. Card caps at 28rem and self-centers. */}
2572
+ </div>
2573
+ </div>
2574
+ \`\`\`
2575
+
2576
+ **Hard rule for centered/auth pages:**
2577
+ The \`d-shell-centered-card\` element provides the 28rem-max-width box. Do NOT render auth forms directly as children of \`d-shell\` \u2014 they will span full viewport width. Always wrap the form in \`d-shell-centered-card\`.
2578
+
2579
+ **Guidance for cold scaffolds:**
2580
+ - If your component is an icon-only action trigger, it's a \`d-icon-btn\`, not a stripped-down \`d-interactive\`.
2581
+ - Breadcrumb / footer / inline body-copy links use \`d-link\`.
2582
+ - Sidebar and top-nav route links use \`d-nav-link\`. Match active state by setting \`aria-current="page"\` (preferred \u2014 accessible) or \`data-active="true"\`.
2583
+ - Checkout / onboarding stepper position indicators use \`d-step-chip\`.
2584
+ - Horizontal rules between card sections use \`d-divider-top\` / \`d-divider-bottom\` as a container modifier, or \`<hr className="d-divider">\` as a standalone element.
2585
+ - Do NOT create \`.nav-link\`, \`.icon-btn\`, \`.sidebar-link\`, \`.step-chip\`, \`.divider-top\` (or similar) as custom classes. They exist as treatments.
2586
+
2587
+ ### Icons \u2014 use Lucide
2588
+
2589
+ Decantr scaffolds ship with \`lucide-react\` pre-installed. When personality prose says "Lucide icons" (or the section/pattern contract references icon names), import them from there:
2590
+
2591
+ \`\`\`tsx
2592
+ import { Bot, ShoppingBag, Settings, Activity, Gauge, Cpu } from 'lucide-react';
2593
+
2594
+ <Bot className={css('_w5 _h5')} aria-hidden="true" />
2595
+ \`\`\`
2596
+
2597
+ - Tree-shaking keeps the bundle at ~1.5-3 KB per icon used.
2598
+ - Do NOT inline SVGs or import an alternative icon library without an explicit contract directive.
2599
+ - When a navigation item declares an \`icon\` field (see section \`navigation_items\`), the value is the Lucide icon name in kebab-case \u2014 e.g., \`"shopping-bag"\` \u2192 \`import { ShoppingBag } from 'lucide-react'\`.
2600
+ - Default sizing: \`_w5 _h5\` (20px) for inline icons, \`_w4 _h4\` (16px) inside dense chrome, \`_w6 _h6\` (24px) for primary slots.
1081
2601
 
1082
2602
  ### Composition
1083
2603
 
@@ -1109,6 +2629,42 @@ css('_bgprimary _h:bgprimary/80')
1109
2629
  - Arbitrary values use square brackets when the standard scale is not enough: \`_w[512px]\`, \`_h[100vh]\`, \`_p[clamp(1rem,3vw,2rem)]\`, \`_z[40]\`.
1110
2630
  - When you see bracket atoms in shell or page contracts, treat them as first-class Decantr syntax, not as an error or a cue to fall back to inline styles.
1111
2631
 
2632
+ ### Responsive Breakpoint Atoms
2633
+
2634
+ Decantr ships two families of responsive prefixes. Use them directly inside \`css(...)\` \u2014 no \`matchMedia\` JS needed for simple responsive switches.
2635
+
2636
+ **Mobile-first (min-width):**
2637
+ | Prefix | Breakpoint | Meaning |
2638
+ |--------|-----------|---------|
2639
+ | \`_sm:\` | \u2265 640px | small tablet / large phone landscape and up |
2640
+ | \`_md:\` | \u2265 768px | tablet portrait and up |
2641
+ | \`_lg:\` | \u2265 1024px | tablet landscape / small desktop and up |
2642
+ | \`_xl:\` | \u2265 1280px | desktop and up |
2643
+
2644
+ **Desktop-first (max-width, for "hide below" / "swap at small" expressions):**
2645
+ | Prefix | Breakpoint | Meaning |
2646
+ |--------|-----------|---------|
2647
+ | \`_smmax:\` | < 640px | phone only |
2648
+ | \`_mdmax:\` | < 768px | phone + small tablet |
2649
+ | \`_lgmax:\` | < 1024px | below tablet-landscape |
2650
+ | \`_xlmax:\` | < 1280px | below desktop |
2651
+
2652
+ Pseudo-class stacking works with both (e.g., \`_mdmax:h:bgmuted\`, \`_sm:fv:ring2\`).
2653
+
2654
+ **Example:**
2655
+ \`\`\`
2656
+ // 1-column on phone, 2-column from tablet, 3-column from desktop
2657
+ css('_grid _gc1 _sm:gc2 _lg:gc3')
2658
+
2659
+ // Hide the minimap below tablet portrait
2660
+ css('_block _mdmax:none')
2661
+
2662
+ // Show the hamburger below tablet portrait, hide it above
2663
+ css('_none _mdmax:block')
2664
+ \`\`\`
2665
+
2666
+ Prefer these atoms over \`window.matchMedia\` in JS. Reserve JS responsive checks for cases where the component tree ITSELF must change shape (e.g., rendering a different React component), not just styling.
2667
+
1112
2668
  ### Atom Reference
1113
2669
 
1114
2670
  #### Display
@@ -1166,6 +2722,10 @@ css('_bgprimary _h:bgprimary/80')
1166
2722
  | \`_py{n}\` | \`padding-block:{scale}\` | vertical |
1167
2723
  | \`_m{n}\` | \`margin:{scale}\` | same as padding variants |
1168
2724
  | \`_mx{n}\`, \`_my{n}\` | inline/block margin | horizontal/vertical |
2725
+ | \`_mauto\` | \`margin:auto\` | center in flex/grid |
2726
+ | \`_mtauto\`, \`_mbauto\` | \`margin-top:auto\` / \`margin-bottom:auto\` | pin to bottom/top of flex column |
2727
+ | \`_mlauto\`, \`_mrauto\` | \`margin-left:auto\` / \`margin-right:auto\` | pin to right/left in a row |
2728
+ | \`_mxauto\`, \`_myauto\` | inline/block margin: auto | center horizontally/vertically |
1169
2729
 
1170
2730
  #### Sizing
1171
2731
  | Atom | CSS |
@@ -1325,10 +2885,13 @@ Missing declared navigation features are contract drift, not optional polish.
1325
2885
 
1326
2886
  ### Routing
1327
2887
 
1328
- Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing strategy:
1329
- - \`"hash"\` \u2192 use \`HashRouter\` (e.g., for static hosting, GitHub Pages)
1330
- - \`"history"\` \u2192 use \`BrowserRouter\` (e.g., for server-rendered apps)
1331
- - \`"pathname"\` \u2192 use pathname-based routing (e.g., Next.js App Router or React apps using \`BrowserRouter\`)
2888
+ Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing strategy. The value is also rendered at the top of \`.decantr/context/scaffold-pack.md\` with a mechanical router-name hint \u2014 trust the pack.
2889
+
2890
+ - \`"history"\` (modern SPA default) \u2192 use \`BrowserRouter\` from \`react-router-dom\`. Regular URLs like \`/login\`, \`/agents\`. Works on Vite dev, Vercel, Netlify, Cloudflare Pages, and most modern hosts (SPA fallback is automatic on those platforms).
2891
+ - \`"hash"\` \u2192 use \`HashRouter\` from \`react-router-dom\`. URLs are prefixed with \`/#\` (e.g., \`/#/login\`). Only needed when deploying to a static host without SPA fallback (e.g., vanilla GitHub Pages).
2892
+ - \`"pathname"\` \u2192 framework-native file-based routing (Next.js App Router).
2893
+
2894
+ Do **not** pick a router based on personal preference. Match the declared \`routing\` value exactly \u2014 it's the contract.
1332
2895
 
1333
2896
  Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.
1334
2897
 
@@ -1336,7 +2899,7 @@ Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and l
1336
2899
 
1337
2900
  - For hash-routed SPA scaffolds, focus SEO work on the root document: document title, description, Open Graph/Twitter meta, and any root-level JSON-LD that the contract calls for.
1338
2901
  - Do **not** invent SSR-only per-route metadata systems for a clearly hash-routed scaffold.
1339
- - For history/SSR-style projects, per-route metadata can be richer, but it still needs to follow the declared route contract instead of introducing off-contract marketing pages.
2902
+ - For history-mode SPAs and SSR-style projects, per-route metadata can be richer (set \`document.title\` and meta tags via a route-level effect on SPA; use framework primitives on SSR), but it still needs to follow the declared route contract instead of introducing off-contract marketing pages.
1340
2903
 
1341
2904
  ### Layout Rules
1342
2905
 
@@ -1346,6 +2909,20 @@ Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and l
1346
2909
  4. **d-section spacing is self-contained.** Each d-section owns its padding. The d-section + d-section rule adds a separator. Do NOT add extra margin between adjacent sections.
1347
2910
  5. **Responsive nav rules.** Hamburger menus appear ONLY below the shell collapse breakpoint. Full nav shows above it.
1348
2911
 
2912
+ ### Responsive Breakpoints
2913
+
2914
+ The \`@decantr/css\` atom breakpoints are the canonical defaults. See the "Responsive Breakpoint Atoms" section below for the full table. Shell-level guidance:
2915
+
2916
+ - **\`_smmax:\` (< 640px \u2014 phone):** hamburger drawer, single-column stack, full-bleed content. Pattern-level content stacks vertically unless the pattern explicitly declares otherwise.
2917
+ - **\`_mdmax:\` (< 768px \u2014 phone + small tablet):** most patterns should use this as the "stack to a single column / hide secondary chrome" breakpoint. This is the level where \`top-nav-footer\` mid-nav links should collapse to a hamburger.
2918
+ - **\`_lgmax:\` (< 1024px \u2014 below tablet-landscape):** \`sidebar-main\` shells should collapse the persistent sidebar into a drawer here. Do **not** keep the sidebar open below \`_lg:\` \u2014 at 768-1023px it leaves the main canvas too cramped for data-dense mission-control content.
2919
+ - **\`_lg:\` (\u2265 1024px \u2014 tablet-landscape / small desktop):** full \`sidebar-main\` layout; responsive multi-column grids.
2920
+ - **\`_xl:\` (\u2265 1280px \u2014 desktop):** canonical layout.
2921
+
2922
+ Implementation: prefer the \`@decantr/css\` breakpoint atoms (\`_sm:\`, \`_md:\`, \`_lg:\`, \`_xl:\`, \`_smmax:\`, \`_mdmax:\`, \`_lgmax:\`, \`_xlmax:\`) or structured \`responsive\` fields on patterns. Use \`window.matchMedia\` only when the React component tree itself must change shape per viewport (e.g., rendering a different component), not just styling.
2923
+
2924
+ **High-density content patterns** (swarm canvases, trace-waterfall, data tables with 8+ columns) should declare explicit mobile-reflow behavior \u2014 stack vertically, collapse to a list, or define a \`desktop-only\` directive and render a lighter alternative pattern below \`_md:\`. Without this, horizontal overflow on phone viewports is the default failure mode.
2925
+
1349
2926
  ### Accessibility Defaults
1350
2927
 
1351
2928
  - If \`dna.accessibility.skip_nav = true\`, add a visible-on-focus skip link such as \`<a href="#main-content" className="skip-link">Skip to content</a>\`.
@@ -1353,29 +2930,104 @@ Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and l
1353
2930
  - Keep keyboard focus visible with \`:focus-visible\` treatments on custom interactive surfaces, not just browser defaults.
1354
2931
  - Implement shell-level accessibility and routing behaviors as reusable structure or shared helpers, not one-off inline patches. Compact header sizing, responsive sidebar collapse, and skip-nav targets should be consistent across the shell, not re-solved page by page.
1355
2932
 
1356
- ### Motion Philosophy
1357
-
1358
- Every interaction should feel responsive and polished. Apply motion by default, not as an afterthought:
2933
+ ### Motion Treatments
1359
2934
 
1360
- - **Page transitions:** Apply entrance-fade (or the personality entrance animation) to the main content area on route change
1361
- - **Stagger children:** Lists, grids, and card groups should stagger-animate on mount (50-100ms delay per item)
1362
- - **Data visualization:** Charts, gauges, progress bars, and counters should animate to their values on mount \u2014 never render static
1363
- - **Micro-interactions:** All interactive elements (buttons, toggles, cards, nav items) need hover/press transitions. Use the motion tokens (--d-duration-hover, --d-easing) for consistency.
1364
- - **Scroll reveals:** Sections below the fold should fade-in on scroll intersection (IntersectionObserver, once)
1365
- - **Reduced motion:** Wrap all animations in \`prefers-reduced-motion\` media query \u2014 skip animation, keep state changes instant
2935
+ **Hard rule:** Every animation MUST use one of the treatments below. Do **not** hand-roll \`@keyframes\` or inline \`transition\` rules \u2014 the treatments ship tuned durations, easings, and \`prefers-reduced-motion\` handling.
1366
2936
 
1367
- Never leave this to implication when \`dna.motion.reduce_motion = true\`. The scaffold should include a reviewed reduced-motion path in project CSS, even when the app initially runs on mock data.
2937
+ | Treatment | Class | Intent | When to use |
2938
+ |-----------|-------|--------|-------------|
2939
+ | Fade entrance | \`d-enter-fade\` | Soft mount | Cards, sections, modals on mount |
2940
+ | Slide-up entrance | \`d-enter-slide-up\` | Forceful mount | Hero blocks, primary content |
2941
+ | Scale entrance | \`d-enter-scale\` | Spring mount | Dialogs, popovers, callouts |
2942
+ | Stagger children | \`d-stagger-children > *\` | Sequential reveal | Lists, grids \u2014 set \`style={{ '--d-stagger-index': i }}\` on each child |
2943
+ | Status pulse | \`d-pulse\` | Opacity cycle | Live indicators, processing badges |
2944
+ | Ring pulse | \`d-pulse-ring\` | Expanding halo | Notification dots, focus attractors |
2945
+ | Shimmer | \`d-shimmer\` | Loading skeleton | Skeleton screens on surface-raised |
2946
+ | Float | \`d-float\` | Idle vertical drift | Decorative elements, empty-state graphics |
2947
+ | Glow on hover | \`d-glow-hover\` | Accent glow | Primary CTAs, feature cards |
2948
+ | Scale on hover | \`d-scale-hover\` | 1.02\xD7 pop | Clickable cards, tiles |
2949
+ | Lift on hover | \`d-lift-hover\` | Translate + elevate | Product cards (elevation jumps to 3) |
2950
+ | Click ripple | \`d-ripple\` | Material ripple | Buttons inside disclosure surfaces |
1368
2951
 
1369
- ### Interactivity Philosophy
2952
+ **Motion tokens** (theme-tunable via \`theme.motion.durations\` / \`theme.motion.easings\`):
1370
2953
 
1371
- Build for wow factor. When a pattern describes a canvas, graph, map, or spatial visualization, implement it as a **fully interactive surface**, not a static illustration:
1372
-
1373
- - **Drag and drop:** Nodes, cards, and items on spatial canvases should be draggable. Use pointer events with proper grab/grabbing cursors.
1374
- - **Pan and zoom:** Canvases and large visualizations should support pan (click-drag on background) and zoom (scroll wheel or pinch). Show zoom level indicator.
1375
- - **Connections:** When nodes exist in a graph/topology view, they should have visible connection lines. Implement click-to-select + click-target for connecting nodes.
1376
- - **Live state:** Data-driven visualizations should update in real-time with simulated data. Status changes should animate (color transitions, pulse effects).
1377
- - **Direct manipulation:** Prefer drag-to-reorder over dropdown menus. Prefer inline editing over modal forms. Prefer resize handles over fixed layouts.
1378
- - **Hover reveals:** Show contextual information (tooltips, expanded cards, action menus) on hover \u2014 don't require clicks to discover functionality.`;
2954
+ | Token | Default | Meaning |
2955
+ |-------|---------|---------|
2956
+ | \`--d-motion-instant\` | 80ms | Color swaps, focus rings |
2957
+ | \`--d-motion-fast\` | 150ms | Hover transitions, button press |
2958
+ | \`--d-motion-base\` | 250ms | Entrances, section reveals |
2959
+ | \`--d-motion-slow\` | 400ms | Modals, page transitions |
2960
+ | \`--d-motion-slower\` | 600ms | Hero reveals |
2961
+ | \`--d-motion-stagger\` | 60ms | Per-child stagger delay |
2962
+ | \`--d-motion-ease\` | cubic-bezier(0.4, 0, 0.2, 1) | Balanced ease in/out |
2963
+ | \`--d-motion-ease-out\` | cubic-bezier(0, 0, 0.2, 1) | Decelerate (entrances) |
2964
+ | \`--d-motion-ease-in\` | cubic-bezier(0.4, 0, 1, 1) | Accelerate (exits) |
2965
+ | \`--d-motion-ease-spring\` | cubic-bezier(0.34, 1.56, 0.64, 1) | Bounce overshoot |
2966
+
2967
+ **Reduced motion is handled inside the treatments themselves** \u2014 do NOT wrap each usage in a media query. Do NOT branch React/TS code on \`dna.motion.reduce_motion\`. The treatments' \`@media (prefers-reduced-motion: reduce)\` block hands control to the user's OS preference automatically.
2968
+
2969
+ ### Typography Treatments
2970
+
2971
+ **Hard rule:** Every text node with a distinct visual role MUST use one of these treatments. Do **not** set \`font-size\` / \`font-weight\` / \`letter-spacing\` / \`line-height\` via inline styles or hand-rolled classes.
2972
+
2973
+ | Treatment | Class | Role | Default size / weight |
2974
+ |-----------|-------|------|----------------------|
2975
+ | Display | \`d-display\` | Hero headings | 3rem / 700 / tight leading / tight tracking |
2976
+ | Headline | \`d-headline\` | Section H1/H2 | 1.875rem / 600 / snug leading |
2977
+ | Title | \`d-title\` | Card titles, dialog headers | 1.25rem / 600 |
2978
+ | Subtitle | \`d-subtitle\` | Under-title explainer | 1.125rem / 400 / muted |
2979
+ | Prose | \`d-prose\` | Long-form reading copy | 1rem / 1.625 leading |
2980
+ | Body | \`d-body\` | UI body text | 1rem / 1.5 leading |
2981
+ | Caption | \`d-caption\` | Help text, fine print | 0.875rem / muted |
2982
+ | Eyebrow | \`d-eyebrow\` | Category kicker above headline | 0.75rem / 600 / uppercase / wider tracking / accent |
2983
+ | Numeric modifier | \`d-numeric\` | Adds tabular-nums to any text | Mix with other treatments |
2984
+ | Monospace | \`d-mono-text\` | Code, IDs, timestamps, metric values | Mono font + tabular nums |
2985
+
2986
+ ### Elevation Scale
2987
+
2988
+ **Hard rule:** When a surface needs a shadow, use \`d-elevate[data-level="1..5"]\`. Do **not** hand-roll \`box-shadow\` values.
2989
+
2990
+ | Level | Token | Typical use |
2991
+ |-------|-------|-------------|
2992
+ | 0 | \`--d-elevation-0\` (none) | Flat surfaces (default) |
2993
+ | 1 | \`--d-elevation-1\` | Subtle \u2014 resting cards |
2994
+ | 2 | \`--d-elevation-2\` | Raised \u2014 default cards |
2995
+ | 3 | \`--d-elevation-3\` | Hover / active |
2996
+ | 4 | \`--d-elevation-4\` | Floating panels, popovers |
2997
+ | 5 | \`--d-elevation-5\` | Modals, overlays |
2998
+
2999
+ Dark themes emit stronger alpha values automatically.
3000
+
3001
+ ### Interaction Requirements
3002
+
3003
+ **Hard rule:** Every pattern declares its required interactions in its page-pack \`Interactions\` checklist. **A pattern that declares \`interactions: [...]\` MUST implement each one in source.** \`decantr check --strict\` fails when a declared interaction has no matching treatment or handler in the generated code.
3004
+
3005
+ | Declared interaction | Canonical implementation |
3006
+ |----------------------|-------------------------|
3007
+ | \`animate-on-mount\` | \`d-enter-fade\` / \`d-enter-slide-up\` / \`d-enter-scale\` on the pattern root |
3008
+ | \`stagger-children\` | \`d-stagger-children\` on parent + \`style={{ '--d-stagger-index': i }}\` on each child |
3009
+ | \`status-pulse\` | \`d-pulse\` on the indicator |
3010
+ | \`glow-hover\` | \`d-glow-hover\` on the interactive surface |
3011
+ | \`lift-hover\` | \`d-lift-hover\` on the interactive surface |
3012
+ | \`scale-hover\` | \`d-scale-hover\` on the interactive surface |
3013
+ | \`drag-nodes\` | \`pointerdown\` \u2192 \`pointermove\` with 4px threshold before drag engages. \`cursor: grab\` default, \`cursor: grabbing\` during. |
3014
+ | \`pan-background\` | Pointer handlers on canvas background only (not nodes); translate the viewport transform |
3015
+ | \`zoom-scroll\` | Wheel handler adjusting a \`scale\` transform, clamped [0.25, 4]; show zoom indicator |
3016
+ | \`click-connect\` | Two-click state machine: select a port, click another port to create a connection |
3017
+ | \`inline-edit\` | Replace static text with controlled \`<input>\` on click; commit on blur or Enter |
3018
+ | \`hover-tooltip\` | \`data-tooltip\` attribute + hover handler positioning a popover (mount with \`d-enter-scale\`) |
3019
+ | \`live-simulation\` | \`setInterval\` updating mock state every 2-4 seconds; animate changes with \`d-pulse\` |
3020
+ | \`drag-reorder\` | \`pointerdown\` \u2192 \`pointermove\` with 4px threshold + \`cursor: grab/grabbing\`. Reorder list state on drop. (Same handler shape as \`drag-nodes\`; different state model \u2014 list reorder vs free placement.) |
3021
+ | \`scroll-reveal\` | \`IntersectionObserver\` with \`once: true\` triggering \`d-enter-fade\`/\`d-enter-slide-up\` on entry |
3022
+ | \`real-time-updates\` | \`setInterval\` (2-8s) updating mock state OR \`WebSocket\`/\`EventSource\` for live data; animate changes with \`d-pulse\` on the changed element |
3023
+ | \`float-idle\` | \`d-float\` on decorative elements (illustrations, empty-state graphics) |
3024
+ | \`hover-reveal\` | \`onMouseEnter\`/\`onMouseLeave\` toggling visibility, OR group-hover via the \`:hover\` pseudo on a parent revealing a child (e.g., row actions appear on row hover) |
3025
+ | \`click-select\` | Controlled selection state via \`onClick\`; reflect via \`aria-selected\` / \`aria-pressed\` / \`data-active="true"\` and toggle visual via the matching treatment data-attribute |
3026
+ | \`keyboard-navigation\` | \`onKeyDown\` arrow-key handlers (ArrowUp/Down/Left/Right + Enter/Space). For lists/grids: roving tabindex pattern. Always pair with \`tabIndex={0}\` on focusable items. |
3027
+ | \`focus-trap\` | Tab-key interception inside modal/dialog cycles focus to first/last focusable element; restore focus on close |
3028
+ | \`shimmer-skeleton\` | \`d-shimmer\` on skeleton placeholders during loading |
3029
+ | \`zoom-pinch\` | Touch handlers (\`touchstart\`/\`touchmove\`) tracking pinch distance, OR \`gestureend\` on Safari; same scale transform as \`zoom-scroll\` |
3030
+ | \`ripple-click\` | \`d-ripple\` on the interactive surface |`;
1379
3031
  function generateDecantrMdV31(params) {
1380
3032
  const template = loadTemplate("DECANTR.md.template");
1381
3033
  const body = renderTemplate(template, {
@@ -1404,7 +3056,9 @@ Start implementation from the shell layouts and shared route structure before fi
1404
3056
  briefLines.push(`- **Blueprint:** ${params.blueprintId || "custom"}`);
1405
3057
  const themeDesc = `${params.themeName || "default"} (${params.themeMode || "dark"} mode${params.themeShape ? `, ${params.themeShape} shape` : ""})`;
1406
3058
  briefLines.push(`- **Theme:** ${themeDesc}`);
1407
- briefLines.push(`- **Workflow:** ${params.workflowMode === "brownfield-attach" ? "brownfield attach" : "greenfield scaffold"}`);
3059
+ briefLines.push(
3060
+ `- **Workflow:** ${params.workflowMode === "brownfield-attach" ? "brownfield attach" : "greenfield scaffold"}`
3061
+ );
1408
3062
  if (params.personality && params.personality.length > 0) {
1409
3063
  briefLines.push(`- **Personality:** ${params.personality.join(". ")}`);
1410
3064
  }
@@ -1423,8 +3077,18 @@ Start implementation from the shell layouts and shared route structure before fi
1423
3077
  briefLines.push("|-------|--------|---------|");
1424
3078
  for (const [name, def] of Object.entries(params.decoratorDefinitions)) {
1425
3079
  const intent = def.intent || "";
1426
- const cssProps = def.css ? Object.entries(def.css).map(([p, v]) => `${p}: ${v}`).join("; ") : "";
1427
- briefLines.push(`| \`.${name}\` | ${intent} | ${cssProps} |`);
3080
+ const props = def.suggested_properties ?? def.css ?? {};
3081
+ const base = Object.entries(props).map(([p, v]) => `${p}: ${v}`).join("; ");
3082
+ const hasHover = def.hover_properties && Object.keys(def.hover_properties).length > 0;
3083
+ const hasFocus = def.focus_properties && Object.keys(def.focus_properties).length > 0;
3084
+ const hasActive = def.active_properties && Object.keys(def.active_properties).length > 0;
3085
+ const stateMarkers = [
3086
+ hasHover && ":hover",
3087
+ hasFocus && ":focus-visible",
3088
+ hasActive && ":active"
3089
+ ].filter((m) => Boolean(m));
3090
+ const stateSuffix = stateMarkers.length > 0 ? ` _(+ ${stateMarkers.join(", ")})_` : "";
3091
+ briefLines.push(`| \`.${name}\` | ${intent} | ${base}${stateSuffix} |`);
1428
3092
  }
1429
3093
  briefLines.push("");
1430
3094
  } else if (params.decorators && params.decorators.length > 0) {
@@ -1438,7 +3102,9 @@ Start implementation from the shell layouts and shared route structure before fi
1438
3102
  }
1439
3103
  briefLines.push("## Development Workflow");
1440
3104
  briefLines.push("");
1441
- briefLines.push("The essence file (`decantr.essence.json`) is the source of truth for your project's structure. Context files in `.decantr/context/` are derived from it. When you need to add, remove, or modify pages, sections, or features:");
3105
+ briefLines.push(
3106
+ "The essence file (`decantr.essence.json`) is the source of truth for your project's structure. Context files in `.decantr/context/` are derived from it. When you need to add, remove, or modify pages, sections, or features:"
3107
+ );
1442
3108
  briefLines.push("");
1443
3109
  briefLines.push("**1. Update the essence** (use CLI commands for consistency):");
1444
3110
  briefLines.push("- `decantr add page {section}/{page} --route /{path}`");
@@ -1571,9 +3237,7 @@ function generateScaffoldTaskContext(essence, scaffoldPack, manifest) {
1571
3237
  const sectionRefs = manifest?.sections.map(
1572
3238
  (section) => `Section \`${section.id}\` -> \`.decantr/context/${section.markdown}\``
1573
3239
  ) ?? [];
1574
- const pageRefs = manifest?.pages.map(
1575
- (page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``
1576
- ) ?? [];
3240
+ const pageRefs = manifest?.pages.map((page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``) ?? [];
1577
3241
  return `# Task Context: Scaffolding
1578
3242
 
1579
3243
  **Enforcement Tier: Creative** \u2014 Guard rules are advisory during initial scaffolding.
@@ -1627,9 +3291,7 @@ function generateAddPageTaskContext(essence, scaffoldPack, manifest) {
1627
3291
  const sectionRefs = manifest?.sections.map(
1628
3292
  (section) => `Section \`${section.id}\` -> \`.decantr/context/${section.markdown}\``
1629
3293
  ) ?? [];
1630
- const pageRefs = manifest?.pages.map(
1631
- (page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``
1632
- ) ?? [];
3294
+ const pageRefs = manifest?.pages.map((page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``) ?? [];
1633
3295
  return `# Task Context: Adding Pages
1634
3296
 
1635
3297
  **Enforcement Tier: Guided**
@@ -1682,9 +3344,7 @@ function generateModifyTaskContext(essence, scaffoldPack, manifest) {
1682
3344
  const patternSummary = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
1683
3345
  return `- \`${route.path}\` -> \`${route.pageId}\` [${patternSummary}]`;
1684
3346
  }).join("\n") : "- No routes declared";
1685
- const pageRefs = manifest?.pages.map(
1686
- (page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``
1687
- ) ?? [];
3347
+ const pageRefs = manifest?.pages.map((page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``) ?? [];
1688
3348
  const successChecks = scaffoldPack.successChecks.map((check) => `- [${check.severity}] ${check.label}`).join("\n");
1689
3349
  return `# Task Context: Modifying Code
1690
3350
 
@@ -1729,14 +3389,18 @@ function generateEssenceSummaryV3(essence) {
1729
3389
  let pagesTable;
1730
3390
  if (sections.length > 0) {
1731
3391
  const rows = sections.flatMap(
1732
- (s) => s.pages.map((p) => `| ${p.id} | ${s.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`)
3392
+ (s) => s.pages.map(
3393
+ (p) => `| ${p.id} | ${s.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`
3394
+ )
1733
3395
  );
1734
3396
  pagesTable = `| Page | Shell | Layout |
1735
3397
  |------|-------|--------|
1736
3398
  ${rows.join("\n")}`;
1737
3399
  } else {
1738
3400
  const shell = blueprint.shell ?? "sidebar-main";
1739
- const rows = flatPages.map((p) => `| ${p.id} | ${shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`);
3401
+ const rows = flatPages.map(
3402
+ (p) => `| ${p.id} | ${shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`
3403
+ );
1740
3404
  pagesTable = `| Page | Shell | Layout |
1741
3405
  |------|-------|--------|
1742
3406
  ${rows.join("\n")}`;
@@ -1765,10 +3429,10 @@ ${rows.join("\n")}`;
1765
3429
  return renderTemplate(template, vars);
1766
3430
  }
1767
3431
  function updateGitignore(projectRoot) {
1768
- const gitignorePath = join(projectRoot, ".gitignore");
3432
+ const gitignorePath = join2(projectRoot, ".gitignore");
1769
3433
  const cacheEntry = ".decantr/cache/";
1770
- if (existsSync(gitignorePath)) {
1771
- const content = readFileSync(gitignorePath, "utf-8");
3434
+ if (existsSync2(gitignorePath)) {
3435
+ const content = readFileSync2(gitignorePath, "utf-8");
1772
3436
  if (!content.includes(cacheEntry)) {
1773
3437
  appendFileSync(gitignorePath, `
1774
3438
  # Decantr cache
@@ -1778,7 +3442,7 @@ ${cacheEntry}
1778
3442
  }
1779
3443
  return false;
1780
3444
  } else {
1781
- writeFileSync(gitignorePath, `# Decantr cache
3445
+ writeFileSync2(gitignorePath, `# Decantr cache
1782
3446
  ${cacheEntry}
1783
3447
  `);
1784
3448
  return true;
@@ -1786,20 +3450,20 @@ ${cacheEntry}
1786
3450
  }
1787
3451
  async function scaffoldProject(projectRoot, options, detected, registry, archetypeData, registrySource = "cache", themeData, topologyMarkdown, composedSections, routeMap, patternSpecs, blueprintData) {
1788
3452
  const essenceV3 = buildEssenceV3(options, archetypeData, themeData);
1789
- const decantrDir = join(projectRoot, ".decantr");
1790
- const contextDir = join(decantrDir, "context");
1791
- const cacheDir = join(decantrDir, "cache");
1792
- mkdirSync(contextDir, { recursive: true });
1793
- mkdirSync(cacheDir, { recursive: true });
1794
- const essencePath = join(projectRoot, "decantr.essence.json");
1795
- writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
1796
- const projectJsonPath = join(decantrDir, "project.json");
3453
+ const decantrDir = join2(projectRoot, ".decantr");
3454
+ const contextDir = join2(decantrDir, "context");
3455
+ const cacheDir = join2(decantrDir, "cache");
3456
+ mkdirSync2(contextDir, { recursive: true });
3457
+ mkdirSync2(cacheDir, { recursive: true });
3458
+ const essencePath = join2(projectRoot, "decantr.essence.json");
3459
+ writeFileSync2(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
3460
+ const projectJsonPath = join2(decantrDir, "project.json");
1797
3461
  const projectJsonStr = generateProjectJson(detected, options, registrySource);
1798
3462
  const projectJsonObj = JSON.parse(projectJsonStr);
1799
3463
  if (blueprintData?.voice) {
1800
3464
  projectJsonObj.voice = blueprintData.voice;
1801
3465
  }
1802
- writeFileSync(projectJsonPath, JSON.stringify(projectJsonObj, null, 2));
3466
+ writeFileSync2(projectJsonPath, JSON.stringify(projectJsonObj, null, 2));
1803
3467
  const contextFiles = [];
1804
3468
  if (composedSections) {
1805
3469
  essenceV3.version = "3.1.0";
@@ -1820,9 +3484,12 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1820
3484
  if (blueprintData?.navigation) {
1821
3485
  essenceV3.meta.navigation = blueprintData.navigation;
1822
3486
  }
1823
- writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
3487
+ writeFileSync2(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
1824
3488
  }
1825
- const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, { isInitialScaffold: true, patternSpecs });
3489
+ const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, {
3490
+ isInitialScaffold: true,
3491
+ patternSpecs
3492
+ });
1826
3493
  contextFiles.push(...refreshResult.contextFiles);
1827
3494
  const gitignoreUpdated = updateGitignore(projectRoot);
1828
3495
  return {
@@ -1835,11 +3502,11 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1835
3502
  };
1836
3503
  }
1837
3504
  function scaffoldMinimal(projectRoot) {
1838
- const decantrDir = join(projectRoot, ".decantr");
1839
- const customDir = join(decantrDir, "custom");
1840
- const contentTypes = API_CONTENT_TYPES;
3505
+ const decantrDir = join2(projectRoot, ".decantr");
3506
+ const customDir = join2(decantrDir, "custom");
3507
+ const contentTypes = API_CONTENT_TYPES2;
1841
3508
  for (const type of contentTypes) {
1842
- mkdirSync(join(customDir, type), { recursive: true });
3509
+ mkdirSync2(join2(customDir, type), { recursive: true });
1843
3510
  }
1844
3511
  const essence = {
1845
3512
  version: "3.0.0",
@@ -1887,9 +3554,7 @@ function scaffoldMinimal(projectRoot) {
1887
3554
  },
1888
3555
  blueprint: {
1889
3556
  shell: "sidebar-main",
1890
- pages: [
1891
- { id: "home", layout: ["hero"] }
1892
- ],
3557
+ pages: [{ id: "home", layout: ["hero"] }],
1893
3558
  features: []
1894
3559
  },
1895
3560
  meta: {
@@ -1903,8 +3568,8 @@ function scaffoldMinimal(projectRoot) {
1903
3568
  }
1904
3569
  }
1905
3570
  };
1906
- const essencePath = join(projectRoot, "decantr.essence.json");
1907
- writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
3571
+ const essencePath = join2(projectRoot, "decantr.essence.json");
3572
+ writeFileSync2(essencePath, JSON.stringify(essence, null, 2) + "\n");
1908
3573
  const now = (/* @__PURE__ */ new Date()).toISOString();
1909
3574
  const projectJson = {
1910
3575
  detected: {
@@ -1936,9 +3601,9 @@ function scaffoldMinimal(projectRoot) {
1936
3601
  workflowMode: "greenfield-scaffold"
1937
3602
  }
1938
3603
  };
1939
- const projectJsonPath = join(decantrDir, "project.json");
1940
- writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2));
1941
- const decantrMdPath = join(projectRoot, "DECANTR.md");
3604
+ const projectJsonPath = join2(decantrDir, "project.json");
3605
+ writeFileSync2(projectJsonPath, JSON.stringify(projectJson, null, 2));
3606
+ const decantrMdPath = join2(projectRoot, "DECANTR.md");
1942
3607
  const decantrMdContent = `# DECANTR.md
1943
3608
 
1944
3609
  > This file was generated by \`decantr init\` in offline/minimal mode.
@@ -2009,7 +3674,7 @@ When available, use these tools:
2009
3674
 
2010
3675
  *Generated by @decantr/cli v${CLI_VERSION}*
2011
3676
  `;
2012
- writeFileSync(decantrMdPath, decantrMdContent);
3677
+ writeFileSync2(decantrMdPath, decantrMdContent);
2013
3678
  const gitignoreUpdated = updateGitignore(projectRoot);
2014
3679
  return {
2015
3680
  essencePath,
@@ -2023,40 +3688,46 @@ When available, use these tools:
2023
3688
  function writeExecutionPackArtifacts(basePathWithoutExtension, pack) {
2024
3689
  const markdownPath = `${basePathWithoutExtension}.md`;
2025
3690
  const jsonPath = `${basePathWithoutExtension}.json`;
2026
- writeFileSync(markdownPath, pack.renderedMarkdown);
2027
- writeFileSync(jsonPath, JSON.stringify(pack, null, 2) + "\n");
3691
+ writeFileSync2(markdownPath, pack.renderedMarkdown);
3692
+ writeFileSync2(jsonPath, JSON.stringify(pack, null, 2) + "\n");
2028
3693
  return markdownPath;
2029
3694
  }
2030
3695
  function writeExecutionPackBundleArtifacts(contextDir, bundle) {
2031
- mkdirSync(contextDir, { recursive: true });
3696
+ mkdirSync2(contextDir, { recursive: true });
2032
3697
  const outputPaths = [];
2033
- const scaffoldPackPath = writeExecutionPackArtifacts(join(contextDir, "scaffold-pack"), bundle.scaffold);
3698
+ const scaffoldPackPath = writeExecutionPackArtifacts(
3699
+ join2(contextDir, "scaffold-pack"),
3700
+ bundle.scaffold
3701
+ );
2034
3702
  outputPaths.push(scaffoldPackPath);
2035
- const reviewPackPath = writeExecutionPackArtifacts(join(contextDir, "review-pack"), bundle.review);
3703
+ const reviewPackPath = writeExecutionPackArtifacts(
3704
+ join2(contextDir, "review-pack"),
3705
+ bundle.review
3706
+ );
2036
3707
  outputPaths.push(reviewPackPath);
2037
3708
  for (const sectionPack of bundle.sections) {
2038
3709
  const sectionPackPath = writeExecutionPackArtifacts(
2039
- join(contextDir, `section-${sectionPack.data.sectionId}-pack`),
3710
+ join2(contextDir, `section-${sectionPack.data.sectionId}-pack`),
2040
3711
  sectionPack
2041
3712
  );
2042
3713
  outputPaths.push(sectionPackPath);
2043
3714
  }
2044
3715
  for (const pagePack of bundle.pages) {
2045
3716
  const pagePackPath = writeExecutionPackArtifacts(
2046
- join(contextDir, `page-${pagePack.data.pageId}-pack`),
3717
+ join2(contextDir, `page-${pagePack.data.pageId}-pack`),
2047
3718
  pagePack
2048
3719
  );
2049
3720
  outputPaths.push(pagePackPath);
2050
3721
  }
2051
3722
  for (const mutationPack of bundle.mutations) {
2052
3723
  const mutationPackPath = writeExecutionPackArtifacts(
2053
- join(contextDir, `mutation-${mutationPack.data.mutationType}-pack`),
3724
+ join2(contextDir, `mutation-${mutationPack.data.mutationType}-pack`),
2054
3725
  mutationPack
2055
3726
  );
2056
3727
  outputPaths.push(mutationPackPath);
2057
3728
  }
2058
- const manifestPath = join(contextDir, "pack-manifest.json");
2059
- writeFileSync(manifestPath, JSON.stringify(bundle.manifest, null, 2) + "\n");
3729
+ const manifestPath = join2(contextDir, "pack-manifest.json");
3730
+ writeFileSync2(manifestPath, JSON.stringify(bundle.manifest, null, 2) + "\n");
2060
3731
  outputPaths.push(manifestPath);
2061
3732
  return {
2062
3733
  paths: outputPaths,
@@ -2071,10 +3742,10 @@ async function generatePackContexts(projectRoot, contextDir, essence) {
2071
3742
  scaffoldPack: null,
2072
3743
  manifest: null
2073
3744
  };
2074
- const cacheRoot = join(projectRoot, ".decantr", "cache", "@official");
2075
- if (!existsSync(cacheRoot)) return emptyResult;
2076
- const customRoot = join(projectRoot, ".decantr", "custom");
2077
- const overridePaths = existsSync(customRoot) ? [customRoot] : void 0;
3745
+ const cacheRoot = join2(projectRoot, ".decantr", "cache", "@official");
3746
+ if (!existsSync2(cacheRoot)) return emptyResult;
3747
+ const customRoot = join2(projectRoot, ".decantr", "custom");
3748
+ const overridePaths = existsSync2(customRoot) ? [customRoot] : void 0;
2078
3749
  try {
2079
3750
  const bundle = await compileExecutionPackBundle(essence, {
2080
3751
  contentRoot: cacheRoot,
@@ -2086,7 +3757,22 @@ async function generatePackContexts(projectRoot, contextDir, essence) {
2086
3757
  scaffoldPack: bundle.scaffold,
2087
3758
  manifest: bundle.manifest
2088
3759
  };
2089
- } catch {
3760
+ } catch (err) {
3761
+ const YELLOW = "\x1B[33m";
3762
+ const DIM = "\x1B[2m";
3763
+ const RESET = "\x1B[0m";
3764
+ const message = err instanceof Error ? err.message : String(err);
3765
+ const short = message.length > 240 ? message.slice(0, 220) + "\u2026 (truncated)" : message;
3766
+ console.warn(
3767
+ `${YELLOW}\u26A0 Execution pack compilation failed \u2014 scaffold will ship narrative-only context.${RESET}`
3768
+ );
3769
+ console.warn(`${DIM} Reason: ${short}${RESET}`);
3770
+ console.warn(
3771
+ `${DIM} Cold-scaffolding LLMs won't get scaffold-pack.md / section-*-pack.md / page-*-pack.md.${RESET}`
3772
+ );
3773
+ console.warn(
3774
+ `${DIM} This is a known drift source \u2014 fix the underlying issue and re-run \`decantr refresh\`.${RESET}`
3775
+ );
2090
3776
  return emptyResult;
2091
3777
  }
2092
3778
  }
@@ -2105,7 +3791,11 @@ async function resolvePatternSpec(name, registry, prefetched, includeExtendedFie
2105
3791
  try {
2106
3792
  const patResult = await registry.fetchPattern(name);
2107
3793
  if (patResult?.data) {
2108
- return mapRegistryPatternToPatternSpecSummary(patResult.data, prefetched, includeExtendedFields);
3794
+ return mapRegistryPatternToPatternSpecSummary(
3795
+ patResult.data,
3796
+ prefetched,
3797
+ includeExtendedFields
3798
+ );
2109
3799
  }
2110
3800
  } catch {
2111
3801
  }
@@ -2128,20 +3818,21 @@ async function resolvePatternSpec(name, registry, prefetched, includeExtendedFie
2128
3818
  return null;
2129
3819
  }
2130
3820
  async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThemeData, options) {
2131
- const decantrDir = join(projectRoot, ".decantr");
2132
- const contextDir = join(decantrDir, "context");
2133
- mkdirSync(contextDir, { recursive: true });
3821
+ const decantrDir = join2(projectRoot, ".decantr");
3822
+ const contextDir = join2(decantrDir, "context");
3823
+ mkdirSync2(contextDir, { recursive: true });
2134
3824
  let storedBlueprintId;
2135
3825
  let storedVoice;
2136
3826
  let storedWorkflowMode;
2137
- const projectJsonFilePath = join(decantrDir, "project.json");
3827
+ const projectJsonFilePath = join2(decantrDir, "project.json");
2138
3828
  let projectJsonData = {};
2139
- if (existsSync(projectJsonFilePath)) {
3829
+ if (existsSync2(projectJsonFilePath)) {
2140
3830
  try {
2141
- projectJsonData = JSON.parse(readFileSync(projectJsonFilePath, "utf-8"));
3831
+ projectJsonData = JSON.parse(readFileSync2(projectJsonFilePath, "utf-8"));
2142
3832
  if (projectJsonData.blueprintId) storedBlueprintId = projectJsonData.blueprintId;
2143
3833
  if (projectJsonData.voice) storedVoice = projectJsonData.voice;
2144
- if (projectJsonData.initialized?.workflowMode) storedWorkflowMode = projectJsonData.initialized.workflowMode;
3834
+ if (projectJsonData.initialized?.workflowMode)
3835
+ storedWorkflowMode = projectJsonData.initialized.workflowMode;
2145
3836
  } catch {
2146
3837
  }
2147
3838
  }
@@ -2152,7 +3843,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2152
3843
  if (bpResult.data.voice) {
2153
3844
  storedVoice = bpResult.data.voice;
2154
3845
  projectJsonData.voice = bpResult.data.voice;
2155
- writeFileSync(projectJsonFilePath, JSON.stringify(projectJsonData, null, 2));
3846
+ writeFileSync2(projectJsonFilePath, JSON.stringify(projectJsonData, null, 2));
2156
3847
  }
2157
3848
  }
2158
3849
  } catch {
@@ -2168,13 +3859,14 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2168
3859
  };
2169
3860
  const personality = essence.dna.personality || [];
2170
3861
  let themeData = prefetchedThemeData;
2171
- if (!themeData) try {
2172
- const themeResult = await registry.fetchTheme(themeName);
2173
- if (themeResult?.data) {
2174
- themeData = mapRegistryThemeToThemeData(themeResult.data);
3862
+ if (!themeData)
3863
+ try {
3864
+ const themeResult = await registry.fetchTheme(themeName);
3865
+ if (themeResult?.data) {
3866
+ themeData = mapRegistryThemeToThemeData(themeResult.data);
3867
+ }
3868
+ } catch {
2175
3869
  }
2176
- } catch {
2177
- }
2178
3870
  if (!themeData?.seed?.primary) {
2179
3871
  try {
2180
3872
  const apiUrl = registry.getApiUrl();
@@ -2204,20 +3896,60 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2204
3896
  } catch {
2205
3897
  }
2206
3898
  }
2207
- const stylesDir = join(projectRoot, "src", "styles");
2208
- mkdirSync(stylesDir, { recursive: true });
3899
+ const stylesDir = join2(projectRoot, "src", "styles");
3900
+ mkdirSync2(stylesDir, { recursive: true });
2209
3901
  const densityLevel = essence.dna?.spacing?.density || "comfortable";
2210
- const spatialTokens = computeSpatialTokens(densityLevel, themeData?.spatial ? {
2211
- section_padding: themeData.spatial.section_padding ?? void 0,
2212
- density_bias: typeof themeData.spatial.density_bias === "number" ? themeData.spatial.density_bias : void 0,
2213
- content_gap_shift: themeData.spatial.content_gap_shift
2214
- } : void 0);
2215
- const tokensPath = join(stylesDir, "tokens.css");
3902
+ const spatialTokens = computeSpatialTokens(
3903
+ densityLevel,
3904
+ themeData?.spatial ? {
3905
+ section_padding: themeData.spatial.section_padding ?? void 0,
3906
+ density_bias: typeof themeData.spatial.density_bias === "number" ? themeData.spatial.density_bias : void 0,
3907
+ content_gap_shift: themeData.spatial.content_gap_shift
3908
+ } : void 0
3909
+ );
3910
+ const tokensPath = join2(stylesDir, "tokens.css");
2216
3911
  const hasRealThemeData = themeData?.seed?.primary || themeData?.palette?.background;
2217
- if (hasRealThemeData || !existsSync(tokensPath)) {
2218
- writeFileSync(tokensPath, generateTokensCSS(themeData, mode, spatialTokens));
3912
+ if (hasRealThemeData || !existsSync2(tokensPath)) {
3913
+ if (themeData?.palette && mode && mode !== "auto") {
3914
+ const paletteEntries = Object.values(themeData.palette);
3915
+ const modeDefined = paletteEntries.some(
3916
+ (entry) => entry && typeof entry === "object" && entry[mode]
3917
+ );
3918
+ if (!modeDefined) {
3919
+ const supportedModes = Array.from(
3920
+ new Set(
3921
+ paletteEntries.flatMap(
3922
+ (entry) => entry && typeof entry === "object" ? Object.keys(entry) : []
3923
+ )
3924
+ )
3925
+ ).sort();
3926
+ const YELLOW = "\x1B[33m";
3927
+ const RESET = "\x1B[0m";
3928
+ console.warn(
3929
+ `${YELLOW}\u26A0 Theme "${themeName}" does not define a "${mode}" palette variant.${RESET}`
3930
+ );
3931
+ console.warn(
3932
+ `${YELLOW} Supported modes in palette: ${supportedModes.join(", ") || "none"}.${RESET}`
3933
+ );
3934
+ console.warn(
3935
+ `${YELLOW} Tokens will use mode-aware defaults so the scaffold still renders a legible "${mode}" UI,`
3936
+ );
3937
+ console.warn(
3938
+ `${YELLOW} but the theme's personality may not land. Consider picking a different theme or adding`
3939
+ );
3940
+ console.warn(
3941
+ `${YELLOW} "${mode}" keys to the theme's palette in decantr-content.${RESET}`
3942
+ );
3943
+ }
3944
+ }
3945
+ const features = essence.blueprint?.features ?? [];
3946
+ const hasThemeToggle = features.includes("theme-toggle") || features.includes("theme_toggle");
3947
+ writeFileSync2(
3948
+ tokensPath,
3949
+ generateTokensCSS(themeData, mode, spatialTokens, { hasThemeToggle })
3950
+ );
2219
3951
  }
2220
- const treatmentsPath = join(stylesDir, "treatments.css");
3952
+ const treatmentsPath = join2(stylesDir, "treatments.css");
2221
3953
  let treatmentCSS = generateTreatmentCSS(
2222
3954
  spatialTokens,
2223
3955
  themeData?.treatments,
@@ -2227,10 +3959,10 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2227
3959
  );
2228
3960
  const personalityCSS = generatePersonalityCSS(personality || [], themeData || {});
2229
3961
  treatmentCSS += personalityCSS;
2230
- writeFileSync(treatmentsPath, treatmentCSS);
2231
- const globalPath = join(stylesDir, "global.css");
2232
- if (!existsSync(globalPath)) {
2233
- writeFileSync(globalPath, generateGlobalCSS(personality, essence));
3962
+ writeFileSync2(treatmentsPath, treatmentCSS);
3963
+ const globalPath = join2(stylesDir, "global.css");
3964
+ if (!existsSync2(globalPath)) {
3965
+ writeFileSync2(globalPath, generateGlobalCSS(personality, essence));
2234
3966
  }
2235
3967
  const cssFiles = [tokensPath, treatmentsPath, globalPath];
2236
3968
  const earlyDecoratorList = [];
@@ -2256,44 +3988,47 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2256
3988
  if (!allFeatures.includes(f)) allFeatures.push(f);
2257
3989
  }
2258
3990
  }
2259
- const decantrMdPath = join(projectRoot, "DECANTR.md");
2260
- writeFileSync(decantrMdPath, generateDecantrMdV31({
2261
- guardMode,
2262
- cssApproach: CSS_APPROACH_CONTENT,
2263
- workflowMode: storedWorkflowMode,
2264
- blueprintId: storedBlueprintId || getLegacyBlueprintId(essence.meta) || void 0,
2265
- themeName,
2266
- themeMode: mode,
2267
- themeShape: essence.dna.theme.shape || void 0,
2268
- personality,
2269
- sections: sectionSummaries.length > 0 ? sectionSummaries : void 0,
2270
- features: allFeatures.length > 0 ? allFeatures : void 0,
2271
- decorators: earlyDecoratorList.length > 0 ? earlyDecoratorList : void 0,
2272
- decoratorDefinitions: themeData?.decorator_definitions
2273
- }));
3991
+ const decantrMdPath = join2(projectRoot, "DECANTR.md");
3992
+ writeFileSync2(
3993
+ decantrMdPath,
3994
+ generateDecantrMdV31({
3995
+ guardMode,
3996
+ cssApproach: CSS_APPROACH_CONTENT,
3997
+ workflowMode: storedWorkflowMode,
3998
+ blueprintId: storedBlueprintId || getLegacyBlueprintId(essence.meta) || void 0,
3999
+ themeName,
4000
+ themeMode: mode,
4001
+ themeShape: essence.dna.theme.shape || void 0,
4002
+ personality,
4003
+ sections: sectionSummaries.length > 0 ? sectionSummaries : void 0,
4004
+ features: allFeatures.length > 0 ? allFeatures : void 0,
4005
+ decorators: earlyDecoratorList.length > 0 ? earlyDecoratorList : void 0,
4006
+ decoratorDefinitions: themeData?.decorator_definitions
4007
+ })
4008
+ );
2274
4009
  const hasSections = essence.blueprint.sections && essence.blueprint.sections.length > 0;
2275
4010
  const contextFiles = [];
2276
4011
  if (!hasSections) {
2277
- const summaryPath = join(contextDir, "essence-summary.md");
2278
- writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
4012
+ const summaryPath = join2(contextDir, "essence-summary.md");
4013
+ writeFileSync2(summaryPath, generateEssenceSummaryV3(essence));
2279
4014
  contextFiles.push(summaryPath);
2280
4015
  }
2281
4016
  const packContexts = await generatePackContexts(projectRoot, contextDir, essence);
2282
- const scaffoldTaskPath = join(contextDir, "task-scaffold.md");
2283
- writeFileSync(
4017
+ const scaffoldTaskPath = join2(contextDir, "task-scaffold.md");
4018
+ writeFileSync2(
2284
4019
  scaffoldTaskPath,
2285
4020
  generateScaffoldTaskContext(essence, packContexts.scaffoldPack, packContexts.manifest)
2286
4021
  );
2287
4022
  contextFiles.push(scaffoldTaskPath);
2288
4023
  if (!options?.isInitialScaffold) {
2289
- const addPagePath = join(contextDir, "task-add-page.md");
2290
- writeFileSync(
4024
+ const addPagePath = join2(contextDir, "task-add-page.md");
4025
+ writeFileSync2(
2291
4026
  addPagePath,
2292
4027
  generateAddPageTaskContext(essence, packContexts.scaffoldPack, packContexts.manifest)
2293
4028
  );
2294
4029
  contextFiles.push(addPagePath);
2295
- const modifyPath = join(contextDir, "task-modify.md");
2296
- writeFileSync(
4030
+ const modifyPath = join2(contextDir, "task-modify.md");
4031
+ writeFileSync2(
2297
4032
  modifyPath,
2298
4033
  generateModifyTaskContext(essence, packContexts.scaffoldPack, packContexts.manifest)
2299
4034
  );
@@ -2346,7 +4081,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2346
4081
  }
2347
4082
  };
2348
4083
  const topologyMarkdown = generateTopologySection(topologyData, personality);
2349
- const themeTokensCss = existsSync(tokensPath) ? readFileSync(tokensPath, "utf-8") : "";
4084
+ const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
2350
4085
  const decoratorList = [];
2351
4086
  if (themeData?.decorators) {
2352
4087
  for (const [name, desc] of Object.entries(themeData.decorators)) {
@@ -2418,8 +4153,8 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2418
4153
  voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0,
2419
4154
  spatialHints: sectionSpatialHints
2420
4155
  });
2421
- const sectionContextPath = join(contextDir, `section-${section.id}.md`);
2422
- writeFileSync(sectionContextPath, contextContent);
4156
+ const sectionContextPath = join2(contextDir, `section-${section.id}.md`);
4157
+ writeFileSync2(sectionContextPath, contextContent);
2423
4158
  contextFiles.push(sectionContextPath);
2424
4159
  }
2425
4160
  const routes = blueprint.routes || {};
@@ -2436,8 +4171,8 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2436
4171
  navigation: essence.meta.navigation,
2437
4172
  voice: storedVoice
2438
4173
  });
2439
- const scaffoldMdPath = join(contextDir, "scaffold.md");
2440
- writeFileSync(scaffoldMdPath, scaffoldContent);
4174
+ const scaffoldMdPath = join2(contextDir, "scaffold.md");
4175
+ writeFileSync2(scaffoldMdPath, scaffoldContent);
2441
4176
  contextFiles.push(scaffoldMdPath);
2442
4177
  } else {
2443
4178
  const pages = blueprint.pages || [{ id: "home", layout: ["hero"] }];
@@ -2465,7 +4200,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2465
4200
  }
2466
4201
  }
2467
4202
  }
2468
- const themeTokensCss = existsSync(tokensPath) ? readFileSync(tokensPath, "utf-8") : "";
4203
+ const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
2469
4204
  const decoratorList = [];
2470
4205
  if (themeData?.decorators) {
2471
4206
  for (const [name, desc] of Object.entries(themeData.decorators)) {
@@ -2507,8 +4242,8 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2507
4242
  voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0,
2508
4243
  spatialHints: v30SpatialHints
2509
4244
  });
2510
- const sectionContextPath = join(contextDir, `section-${syntheticSection.id}.md`);
2511
- writeFileSync(sectionContextPath, contextContent);
4245
+ const sectionContextPath = join2(contextDir, `section-${syntheticSection.id}.md`);
4246
+ writeFileSync2(sectionContextPath, contextContent);
2512
4247
  contextFiles.push(sectionContextPath);
2513
4248
  }
2514
4249
  if (packContexts.paths.length > 0) {
@@ -2594,17 +4329,26 @@ function generateSyntheticComponents(patternId, description) {
2594
4329
  if (patternId.includes("pricing")) syntheticComponents.push("Card", "Button", "Badge");
2595
4330
  if (patternId.includes("testimonial")) syntheticComponents.push("Card", "Avatar", "Text");
2596
4331
  if (patternId.includes("cta")) syntheticComponents.push("Button", "Text");
2597
- if (patternId.includes("form") || patternId.includes("contact")) syntheticComponents.push("Input", "Textarea", "Button", "Label");
4332
+ if (patternId.includes("form") || patternId.includes("contact"))
4333
+ syntheticComponents.push("Input", "Textarea", "Button", "Label");
2598
4334
  if (patternId.includes("team")) syntheticComponents.push("Card", "Avatar", "Text");
2599
- if (patternId.includes("settings") || patternId.includes("security")) syntheticComponents.push("Card", "Toggle", "Input", "Button");
2600
- if (patternId.includes("message") || patternId.includes("chat")) syntheticComponents.push("Avatar", "Text", "CodeBlock");
2601
- if (patternId.includes("input") && desc.includes("chat")) syntheticComponents.push("Textarea", "Button", "Icon");
2602
- if (patternId.includes("header") && desc.includes("chat")) syntheticComponents.push("Button", "Icon", "Text");
2603
- if (patternId.includes("content") || patternId.includes("legal")) syntheticComponents.push("Heading", "Text", "List");
2604
- if (patternId.includes("how-it-works") || patternId.includes("steps")) syntheticComponents.push("Card", "Icon", "Text", "Badge");
4335
+ if (patternId.includes("settings") || patternId.includes("security"))
4336
+ syntheticComponents.push("Card", "Toggle", "Input", "Button");
4337
+ if (patternId.includes("message") || patternId.includes("chat"))
4338
+ syntheticComponents.push("Avatar", "Text", "CodeBlock");
4339
+ if (patternId.includes("input") && desc.includes("chat"))
4340
+ syntheticComponents.push("Textarea", "Button", "Icon");
4341
+ if (patternId.includes("header") && desc.includes("chat"))
4342
+ syntheticComponents.push("Button", "Icon", "Text");
4343
+ if (patternId.includes("content") || patternId.includes("legal"))
4344
+ syntheticComponents.push("Heading", "Text", "List");
4345
+ if (patternId.includes("how-it-works") || patternId.includes("steps"))
4346
+ syntheticComponents.push("Card", "Icon", "Text", "Badge");
2605
4347
  if (patternId.includes("values")) syntheticComponents.push("Card", "Icon", "Text");
2606
- if (patternId.includes("story") || patternId.includes("about")) syntheticComponents.push("Text", "Image");
2607
- if (patternId.includes("empty") || patternId.includes("new")) syntheticComponents.push("Icon", "Text", "Button");
4348
+ if (patternId.includes("story") || patternId.includes("about"))
4349
+ syntheticComponents.push("Text", "Image");
4350
+ if (patternId.includes("empty") || patternId.includes("new"))
4351
+ syntheticComponents.push("Icon", "Text", "Button");
2608
4352
  return [...new Set(syntheticComponents)];
2609
4353
  }
2610
4354
  function mapRegistryPatternToPatternSpecSummary(pattern, prefetched, includeExtendedFields = true) {
@@ -2612,7 +4356,10 @@ function mapRegistryPatternToPatternSpecSummary(pattern, prefetched, includeExte
2612
4356
  const preset = pattern.presets?.[defaultPreset];
2613
4357
  let slots = preset?.layout?.slots || pattern.default_layout?.slots || prefetched?.slots || {};
2614
4358
  if (Object.keys(slots).length === 0) {
2615
- const synthetic = generateSyntheticSlots(pattern.id, pattern.description || prefetched?.description || "");
4359
+ const synthetic = generateSyntheticSlots(
4360
+ pattern.id,
4361
+ pattern.description || prefetched?.description || ""
4362
+ );
2616
4363
  if (Object.keys(synthetic).length > 0) slots = synthetic;
2617
4364
  }
2618
4365
  const spec = {
@@ -2677,9 +4424,15 @@ function generateShellImplementation(shellId, shellInfo) {
2677
4424
  }
2678
4425
  lines.push("### Anti-patterns");
2679
4426
  lines.push("");
2680
- lines.push("- Do NOT nest `overflow-y-auto` inside another `overflow-y-auto` \u2014 one scroll container per region.");
2681
- lines.push("- Do NOT apply `d-surface` to shell frame regions (sidebar, header). Use `var(--d-surface)` or `var(--d-bg)` directly.");
2682
- lines.push("- Do NOT add wrapper `<div>` elements around shell regions \u2014 the grid areas handle placement.");
4427
+ lines.push(
4428
+ "- Do NOT nest `overflow-y-auto` inside another `overflow-y-auto` \u2014 one scroll container per region."
4429
+ );
4430
+ lines.push(
4431
+ "- Do NOT apply `d-surface` to shell frame regions (sidebar, header). Use `var(--d-surface)` or `var(--d-bg)` directly."
4432
+ );
4433
+ lines.push(
4434
+ "- Do NOT add wrapper `<div>` elements around shell regions \u2014 the grid areas handle placement."
4435
+ );
2683
4436
  lines.push("");
2684
4437
  } else {
2685
4438
  if (shellInfo.layout) {
@@ -2766,31 +4519,70 @@ function generateQuickStart(input) {
2766
4519
  function generateSpacingGuide(density, spatialHints) {
2767
4520
  const lines = [];
2768
4521
  const level = density === "compact" || density === "spacious" ? density : "comfortable";
2769
- const tokens = computeSpatialTokens(level, spatialHints);
4522
+ const tokens = computeSpatialTokens(
4523
+ level,
4524
+ spatialHints
4525
+ );
2770
4526
  lines.push("## Spacing Guide");
2771
4527
  lines.push("");
2772
4528
  lines.push("| Context | Token | Value | Usage |");
2773
4529
  lines.push("|---------|-------|-------|-------|");
2774
- lines.push(`| Content gap | \`--d-content-gap\` | \`${tokens["--d-content-gap"]}\` | Gap between sibling elements |`);
2775
- lines.push(`| Section padding | \`--d-section-py\` | \`${tokens["--d-section-py"]}\` | Vertical padding on d-section |`);
2776
- lines.push(`| Surface padding | \`--d-surface-p\` | \`${tokens["--d-surface-p"]}\` | Inner padding for d-surface |`);
2777
- lines.push(`| Interactive V | \`--d-interactive-py\` | \`${tokens["--d-interactive-py"]}\` | Vertical padding on buttons |`);
2778
- lines.push(`| Interactive H | \`--d-interactive-px\` | \`${tokens["--d-interactive-px"]}\` | Horizontal padding on buttons |`);
2779
- lines.push(`| Control | \`--d-control-py\` | \`${tokens["--d-control-py"]}\` | Vertical padding on inputs |`);
2780
- lines.push(`| Data row | \`--d-data-py\` | \`${tokens["--d-data-py"]}\` | Vertical padding on table rows |`);
2781
- lines.push(`| Label gap | \`--d-label-mb\` | \`${tokens["--d-label-mb"]}\` | Gap below d-label section headers |`);
2782
- lines.push(`| Label indent | \`--d-label-px\` | \`${tokens["--d-label-px"]}\` | Anchor indent for d-label[data-anchor] |`);
2783
- lines.push(`| Section gap | \`--d-section-gap\` | \`${tokens["--d-section-gap"]}\` | Gap between adjacent d-sections |`);
2784
- lines.push(`| Annotation gap | \`--d-annotation-mt\` | \`${tokens["--d-annotation-mt"]}\` | Top margin on d-annotation |`);
4530
+ lines.push(
4531
+ `| Content gap | \`--d-content-gap\` | \`${tokens["--d-content-gap"]}\` | Gap between sibling elements |`
4532
+ );
4533
+ lines.push(
4534
+ `| Section padding | \`--d-section-py\` | \`${tokens["--d-section-py"]}\` | Vertical padding on d-section |`
4535
+ );
4536
+ lines.push(
4537
+ `| Surface padding | \`--d-surface-p\` | \`${tokens["--d-surface-p"]}\` | Inner padding for d-surface |`
4538
+ );
4539
+ lines.push(
4540
+ `| Interactive V | \`--d-interactive-py\` | \`${tokens["--d-interactive-py"]}\` | Vertical padding on buttons |`
4541
+ );
4542
+ lines.push(
4543
+ `| Interactive H | \`--d-interactive-px\` | \`${tokens["--d-interactive-px"]}\` | Horizontal padding on buttons |`
4544
+ );
4545
+ lines.push(
4546
+ `| Control | \`--d-control-py\` | \`${tokens["--d-control-py"]}\` | Vertical padding on inputs |`
4547
+ );
4548
+ lines.push(
4549
+ `| Data row | \`--d-data-py\` | \`${tokens["--d-data-py"]}\` | Vertical padding on table rows |`
4550
+ );
4551
+ lines.push(
4552
+ `| Label gap | \`--d-label-mb\` | \`${tokens["--d-label-mb"]}\` | Gap below d-label section headers |`
4553
+ );
4554
+ lines.push(
4555
+ `| Label indent | \`--d-label-px\` | \`${tokens["--d-label-px"]}\` | Anchor indent for d-label[data-anchor] |`
4556
+ );
4557
+ lines.push(
4558
+ `| Section gap | \`--d-section-gap\` | \`${tokens["--d-section-gap"]}\` | Gap between adjacent d-sections |`
4559
+ );
4560
+ lines.push(
4561
+ `| Annotation gap | \`--d-annotation-mt\` | \`${tokens["--d-annotation-mt"]}\` | Top margin on d-annotation |`
4562
+ );
2785
4563
  lines.push("");
2786
4564
  return lines;
2787
4565
  }
2788
4566
  function generateSectionContext(input) {
2789
- const { section, decorators, guardConfig, personality, themeName, zoneContext, patternSpecs, themeHints, constraints, shellInfo, spatialHints } = input;
4567
+ const {
4568
+ section,
4569
+ decorators,
4570
+ guardConfig,
4571
+ personality,
4572
+ themeName,
4573
+ zoneContext,
4574
+ patternSpecs,
4575
+ themeHints,
4576
+ constraints,
4577
+ shellInfo,
4578
+ spatialHints
4579
+ } = input;
2790
4580
  const lines = [];
2791
4581
  lines.push(`# Section: ${section.id}`);
2792
4582
  lines.push("");
2793
- lines.push(`**Role:** ${section.role} | **Shell:** ${section.shell} | **Archetype:** ${section.id}`);
4583
+ lines.push(
4584
+ `**Role:** ${section.role} | **Shell:** ${section.shell} | **Archetype:** ${section.id}`
4585
+ );
2794
4586
  lines.push(`**Description:** ${section.description}`);
2795
4587
  if (section.dna_overrides) {
2796
4588
  const parts = [];
@@ -2818,9 +4610,15 @@ function generateSectionContext(input) {
2818
4610
  }
2819
4611
  lines.push("- Density-responsive bottom gap via `--d-label-mb` x `--d-density-scale`");
2820
4612
  if (sectionDensity) {
2821
- const scaleMap = { compact: "0.65", comfortable: "1", spacious: "1.4" };
4613
+ const scaleMap = {
4614
+ compact: "0.65",
4615
+ comfortable: "1",
4616
+ spacious: "1.4"
4617
+ };
2822
4618
  lines.push("");
2823
- lines.push(`Section density: ${sectionDensity} (--d-density-scale: ${scaleMap[sectionDensity] || "1"})`);
4619
+ lines.push(
4620
+ `Section density: ${sectionDensity} (--d-density-scale: ${scaleMap[sectionDensity] || "1"})`
4621
+ );
2824
4622
  }
2825
4623
  lines.push("");
2826
4624
  }
@@ -2834,94 +4632,54 @@ function generateSectionContext(input) {
2834
4632
  }
2835
4633
  lines.push("");
2836
4634
  }
2837
- const density = section.dna_overrides?.density || "comfortable";
2838
- lines.push(...generateSpacingGuide(density, spatialHints));
2839
- lines.push("---");
4635
+ const sectionDensityOverride = section.dna_overrides?.density;
4636
+ const effectiveDensity = sectionDensityOverride || "comfortable";
4637
+ lines.push("## Theme Reference");
2840
4638
  lines.push("");
2841
- lines.push(`**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`);
2842
- lines.push("");
2843
- lines.push("**Key palette tokens:**");
4639
+ lines.push(
4640
+ `**Theme:** ${themeName} (${input.themeMode || "dark"}) \xB7 **Density:** ${effectiveDensity}${sectionDensityOverride ? " _(DNA override)_" : ""}`
4641
+ );
2844
4642
  lines.push("");
2845
- lines.push("| Token | Value | Role |");
2846
- lines.push("|-------|-------|------|");
2847
- const semanticRoles = {
2848
- background: "Page canvas / base layer",
2849
- surface: "Cards, panels, containers",
2850
- "surface-raised": "Elevated containers, modals, popovers",
2851
- border: "Dividers, card borders, separators",
2852
- text: "Body text, headings, primary content",
2853
- "text-muted": "Secondary text, placeholders, labels",
2854
- primary: "Brand color, key interactive, selected states",
2855
- "primary-hover": "Hover state for primary elements",
2856
- secondary: "Secondary brand color, supporting elements",
2857
- "accent-glow": "Ambient glow effect for accent-colored elements"
2858
- };
2859
- const paletteToTokenName = {
2860
- "background": "bg"
2861
- };
2862
- const addedTokens = /* @__PURE__ */ new Set();
2863
- if (input.themeData?.palette) {
2864
- const modeKey = input.themeMode || "dark";
2865
- for (const [name, values] of Object.entries(input.themeData.palette)) {
2866
- if (!addedTokens.has(name)) {
2867
- addedTokens.add(name);
2868
- const tokenName = paletteToTokenName[name] || name;
2869
- const val = values[modeKey] || values.dark || values.light || Object.values(values)[0];
2870
- lines.push(`| \`--d-${tokenName}\` | \`${val}\` | ${semanticRoles[name] || ""} |`);
2871
- }
2872
- }
2873
- }
2874
- if (input.themeData?.seed?.accent && !addedTokens.has("accent")) {
2875
- addedTokens.add("accent");
2876
- lines.push(`| \`--d-accent\` | \`${input.themeData.seed.accent}\` | CTAs, links, active states, glow effects |`);
2877
- }
2878
- if (!addedTokens.has("accent-glow")) {
2879
- const accentGlowVal = input.themeData?.palette?.["accent-glow"]?.[input.themeMode || "dark"] || input.themeData?.tokens?.base?.["accent-glow"];
2880
- if (accentGlowVal) {
2881
- addedTokens.add("accent-glow");
2882
- lines.push(`| \`--d-accent-glow\` | \`${accentGlowVal}\` | Ambient glow effect around accent elements |`);
2883
- }
4643
+ lines.push(
4644
+ "Full palette tokens, spacing-guide table, and decorator reference live in `DECANTR.md` (project root). These values are identical across sections in this scaffold unless a DNA override above changes density."
4645
+ );
4646
+ if (sectionDensityOverride) {
4647
+ lines.push("");
4648
+ lines.push("Because this section overrides density, the spacing guide is emitted below:");
4649
+ lines.push("");
4650
+ lines.push(...generateSpacingGuide(effectiveDensity, spatialHints));
2884
4651
  }
2885
4652
  lines.push("");
2886
- lines.push("Full token set: `src/styles/tokens.css`");
4653
+ lines.push("---");
4654
+ lines.push("");
4655
+ lines.push(
4656
+ `**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`
4657
+ );
2887
4658
  lines.push("");
2888
- lines.push("**Visual Treatments:** All 6 base treatments available (see DECANTR.md for usage).");
2889
4659
  const decoratorDefs = input.themeData?.decorator_definitions;
2890
4660
  if (decoratorDefs && Object.keys(decoratorDefs).length > 0) {
2891
- lines.push("**Theme decorators:**");
2892
- lines.push("");
2893
- lines.push("| Class | Intent | Key CSS | Pairs with |");
2894
- lines.push("|-------|--------|---------|------------|");
2895
- for (const [name, def] of Object.entries(decoratorDefs)) {
2896
- const intent = def.intent || "";
2897
- const cssProps = def.css ? Object.entries(def.css).map(([p, v]) => `${p}: ${v}`).join("; ") : "";
2898
- const pairsWith = def.pairs_with || "";
2899
- lines.push(`| \`.${name}\` | ${intent} | ${cssProps} | ${pairsWith} |`);
2900
- }
2901
- lines.push("");
2902
- lines.push("**Decorator usage guide:**");
2903
- for (const [name, def] of Object.entries(decoratorDefs)) {
2904
- if (def.usage && def.usage.length > 0) {
2905
- lines.push(`- \`.${name}\`: ${def.usage.join(", ")}`);
4661
+ const usageEntries = Object.entries(decoratorDefs).filter(
4662
+ ([, def]) => def.usage && def.usage.length > 0
4663
+ );
4664
+ if (usageEntries.length > 0) {
4665
+ lines.push("**Section decorators (usage hints):**");
4666
+ for (const [name, def] of usageEntries) {
4667
+ lines.push(`- \`.${name}\`: ${(def.usage || []).join(", ")}`);
2906
4668
  }
4669
+ lines.push("");
2907
4670
  }
2908
- lines.push("");
2909
4671
  } else if (decorators.length > 0) {
2910
- lines.push("**Theme decorators:**");
2911
- lines.push("");
2912
- lines.push("| Class | Usage |");
2913
- lines.push("|-------|-------|");
4672
+ lines.push("**Section decorators:**");
2914
4673
  for (const d of decorators) {
2915
- lines.push(`| \`.${d.name}\` | ${d.description} |`);
4674
+ lines.push(`- \`.${d.name}\` \u2014 ${d.description}`);
2916
4675
  }
2917
4676
  lines.push("");
2918
- } else {
2919
- lines.push("No theme decorators defined.");
2920
- lines.push("");
2921
4677
  }
2922
4678
  if (themeHints) {
2923
4679
  if (themeHints.preferred && themeHints.preferred.length > 0) {
2924
- const sectionPatterns = new Set(section.pages.flatMap((p) => p.layout.flatMap(extractPatternNames)));
4680
+ const sectionPatterns = new Set(
4681
+ section.pages.flatMap((p) => p.layout.flatMap(extractPatternNames))
4682
+ );
2925
4683
  const relevant = themeHints.preferred.filter((p) => sectionPatterns.has(p));
2926
4684
  if (relevant.length > 0) {
2927
4685
  lines.push(`**Preferred:** ${relevant.join(", ")}`);
@@ -2937,7 +4695,9 @@ function generateSectionContext(input) {
2937
4695
  }
2938
4696
  const themePrefix = themeName.split("-")[0] || themeName;
2939
4697
  lines.push("");
2940
- lines.push(`Usage: \`className={css('_flex _col _gap4') + ' d-surface ${themePrefix}-glass'}\` \u2014 atoms via css(), treatments and theme decorators as plain class strings.`);
4698
+ lines.push(
4699
+ `Usage: \`className={css('_flex _col _gap4') + ' d-surface ${themePrefix}-glass'}\` \u2014 atoms via css(), treatments and theme decorators as plain class strings.`
4700
+ );
2941
4701
  lines.push("");
2942
4702
  lines.push("---");
2943
4703
  lines.push("");
@@ -2963,11 +4723,15 @@ function generateSectionContext(input) {
2963
4723
  const pLower = personalityText.toLowerCase();
2964
4724
  const utils = [];
2965
4725
  if (pLower.includes("neon") || pLower.includes("glow"))
2966
- utils.push("`neon-glow`, `neon-glow-hover`, `neon-text-glow`, `neon-border-glow` \u2014 Apply to elements needing accent emphasis");
4726
+ utils.push(
4727
+ "`neon-glow`, `neon-glow-hover`, `neon-text-glow`, `neon-border-glow` \u2014 Apply to elements needing accent emphasis"
4728
+ );
2967
4729
  if (pLower.includes("mono") || pLower.includes("monospace"))
2968
4730
  utils.push("`mono-data` \u2014 Monospace + tabular-nums for metrics, IDs, timestamps");
2969
4731
  if (pLower.includes("pulse") || pLower.includes("ring") || pLower.includes("status"))
2970
- utils.push('`status-ring` with `data-status="active|idle|error|processing"` \u2014 Color-coded status with pulse animation');
4732
+ utils.push(
4733
+ '`status-ring` with `data-status="active|idle|error|processing"` \u2014 Color-coded status with pulse animation'
4734
+ );
2971
4735
  if (utils.length > 0) {
2972
4736
  lines.push("**Personality utilities available in treatments.css:**");
2973
4737
  for (const u of utils) lines.push(`- ${u}`);
@@ -2978,7 +4742,9 @@ function generateSectionContext(input) {
2978
4742
  lines.push("## Constraints");
2979
4743
  lines.push("");
2980
4744
  for (const [key, value] of Object.entries(constraints)) {
2981
- lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
4745
+ lines.push(
4746
+ `- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`
4747
+ );
2982
4748
  }
2983
4749
  lines.push("");
2984
4750
  lines.push("---");
@@ -2996,8 +4762,12 @@ function generateSectionContext(input) {
2996
4762
  if (uniquePatterns.size > 0) {
2997
4763
  lines.push("## Pattern Reference");
2998
4764
  lines.push("");
2999
- lines.push("Scaffold-tier rule: implement the core visual structure, states, and required slots first.");
3000
- lines.push("Treat advanced capabilities such as drag/drop, force-layout, minimaps, or simulated live streaming as optional unless the slot guidance or section contract makes them explicitly required.");
4765
+ lines.push(
4766
+ "Scaffold-tier rule: implement the core visual structure, states, and required slots first."
4767
+ );
4768
+ lines.push(
4769
+ "Treat advanced capabilities such as drag/drop, force-layout, minimaps, or simulated live streaming as optional unless the slot guidance or section contract makes them explicitly required."
4770
+ );
3001
4771
  lines.push("");
3002
4772
  for (const [patternName, spec] of uniquePatterns) {
3003
4773
  lines.push(`### ${patternName}`);
@@ -3032,12 +4802,17 @@ function generateSectionContext(input) {
3032
4802
  if (spec.motion) {
3033
4803
  const entries = [];
3034
4804
  const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3035
- if (isObj(spec.motion.micro)) for (const [k, v] of Object.entries(spec.motion.micro)) entries.push([k, v]);
4805
+ if (isObj(spec.motion.micro))
4806
+ for (const [k, v] of Object.entries(spec.motion.micro)) entries.push([k, v]);
3036
4807
  else if (typeof spec.motion.micro === "string") entries.push(["micro", spec.motion.micro]);
3037
- if (isObj(spec.motion.transitions)) for (const [k, v] of Object.entries(spec.motion.transitions)) entries.push([k, v]);
3038
- else if (typeof spec.motion.transitions === "string") entries.push(["transitions", spec.motion.transitions]);
3039
- if (isObj(spec.motion.ambient)) for (const [k, v] of Object.entries(spec.motion.ambient)) entries.push([k, v]);
3040
- else if (typeof spec.motion.ambient === "string") entries.push(["ambient", spec.motion.ambient]);
4808
+ if (isObj(spec.motion.transitions))
4809
+ for (const [k, v] of Object.entries(spec.motion.transitions)) entries.push([k, v]);
4810
+ else if (typeof spec.motion.transitions === "string")
4811
+ entries.push(["transitions", spec.motion.transitions]);
4812
+ if (isObj(spec.motion.ambient))
4813
+ for (const [k, v] of Object.entries(spec.motion.ambient)) entries.push([k, v]);
4814
+ else if (typeof spec.motion.ambient === "string")
4815
+ entries.push(["ambient", spec.motion.ambient]);
3041
4816
  if (entries.length > 0) {
3042
4817
  lines.push("**Motion:**");
3043
4818
  lines.push("| Interaction | Animation |");
@@ -3049,16 +4824,21 @@ function generateSectionContext(input) {
3049
4824
  if (spec.responsive) {
3050
4825
  lines.push("**Responsive:**");
3051
4826
  if (spec.responsive.mobile) lines.push(`- **Mobile (<640px):** ${spec.responsive.mobile}`);
3052
- if (spec.responsive.tablet) lines.push(`- **Tablet (640-1024px):** ${spec.responsive.tablet}`);
3053
- if (spec.responsive.desktop) lines.push(`- **Desktop (>1024px):** ${spec.responsive.desktop}`);
4827
+ if (spec.responsive.tablet)
4828
+ lines.push(`- **Tablet (640-1024px):** ${spec.responsive.tablet}`);
4829
+ if (spec.responsive.desktop)
4830
+ lines.push(`- **Desktop (>1024px):** ${spec.responsive.desktop}`);
3054
4831
  lines.push("");
3055
4832
  }
3056
4833
  if (spec.accessibility) {
3057
4834
  lines.push("**Accessibility:**");
3058
4835
  if (spec.accessibility.role) lines.push(`- Role: \`${spec.accessibility.role}\``);
3059
- if (Array.isArray(spec.accessibility.keyboard) && spec.accessibility.keyboard.length) lines.push(`- Keyboard: ${spec.accessibility.keyboard.join("; ")}`);
3060
- if (Array.isArray(spec.accessibility.announcements) && spec.accessibility.announcements.length) lines.push(`- Announcements: ${spec.accessibility.announcements.join("; ")}`);
3061
- if (spec.accessibility.focus_management) lines.push(`- Focus: ${spec.accessibility.focus_management}`);
4836
+ if (Array.isArray(spec.accessibility.keyboard) && spec.accessibility.keyboard.length)
4837
+ lines.push(`- Keyboard: ${spec.accessibility.keyboard.join("; ")}`);
4838
+ if (Array.isArray(spec.accessibility.announcements) && spec.accessibility.announcements.length)
4839
+ lines.push(`- Announcements: ${spec.accessibility.announcements.join("; ")}`);
4840
+ if (spec.accessibility.focus_management)
4841
+ lines.push(`- Focus: ${spec.accessibility.focus_management}`);
3062
4842
  lines.push("");
3063
4843
  }
3064
4844
  lines.push("");
@@ -3090,7 +4870,18 @@ function generateSectionContext(input) {
3090
4870
  return lines.join("\n");
3091
4871
  }
3092
4872
  function generateScaffoldContext(input) {
3093
- const { appName, blueprintId, themeName, personality, topologyMarkdown, sections, routes, constraints, seo, navigation } = input;
4873
+ const {
4874
+ appName,
4875
+ blueprintId,
4876
+ themeName,
4877
+ personality,
4878
+ topologyMarkdown,
4879
+ sections,
4880
+ routes,
4881
+ constraints,
4882
+ seo,
4883
+ navigation
4884
+ } = input;
3094
4885
  const lines = [];
3095
4886
  lines.push(`# Scaffold: ${appName}`);
3096
4887
  lines.push("");
@@ -3103,7 +4894,8 @@ function generateScaffoldContext(input) {
3103
4894
  lines.push("## Voice & Copy");
3104
4895
  lines.push("");
3105
4896
  if (input.voice.tone) lines.push(`**Tone:** ${input.voice.tone}`);
3106
- if (input.voice.cta_verbs?.length) lines.push(`**CTA verbs:** ${input.voice.cta_verbs.join(", ")}`);
4897
+ if (input.voice.cta_verbs?.length)
4898
+ lines.push(`**CTA verbs:** ${input.voice.cta_verbs.join(", ")}`);
3107
4899
  if (input.voice.avoid?.length) lines.push(`**Avoid:** ${input.voice.avoid.join(", ")}`);
3108
4900
  if (input.voice.empty_states) lines.push(`**Empty states:** ${input.voice.empty_states}`);
3109
4901
  if (input.voice.errors) lines.push(`**Errors:** ${input.voice.errors}`);
@@ -3115,10 +4907,18 @@ function generateScaffoldContext(input) {
3115
4907
  lines.push("");
3116
4908
  lines.push("For local development and showcases, wire all zone transitions with mock data:");
3117
4909
  lines.push("");
3118
- lines.push("- **Auth bypass:** Auth pages should accept any input and redirect to the primary section's default route");
3119
- lines.push("- **Route guards:** Check a simple localStorage flag (e.g., `decantr_authenticated`). Login sets it \u2192 redirect to app zone entry. Logout clears it \u2192 redirect to public/gateway zone.");
3120
- lines.push("- **Mock data on every page:** All pages should render with simulated data on first load \u2014 never show empty states during development");
3121
- lines.push("- **Zone transitions:** CTA links on marketing pages should route to the gateway (login/register). Successful auth should route to the primary section default page.");
4910
+ lines.push(
4911
+ "- **Auth bypass:** Auth pages should accept any input and redirect to the primary section's default route"
4912
+ );
4913
+ lines.push(
4914
+ "- **Route guards:** Check a simple localStorage flag (e.g., `decantr_authenticated`). Login sets it \u2192 redirect to app zone entry. Logout clears it \u2192 redirect to public/gateway zone."
4915
+ );
4916
+ lines.push(
4917
+ "- **Mock data on every page:** All pages should render with simulated data on first load \u2014 never show empty states during development"
4918
+ );
4919
+ lines.push(
4920
+ "- **Zone transitions:** CTA links on marketing pages should route to the gateway (login/register). Successful auth should route to the primary section default page."
4921
+ );
3122
4922
  lines.push("");
3123
4923
  lines.push(topologyMarkdown);
3124
4924
  lines.push("");
@@ -3175,7 +4975,9 @@ function generateScaffoldContext(input) {
3175
4975
  lines.push("## Design Constraints");
3176
4976
  lines.push("");
3177
4977
  for (const [key, value] of Object.entries(constraints)) {
3178
- lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
4978
+ lines.push(
4979
+ `- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`
4980
+ );
3179
4981
  }
3180
4982
  lines.push("");
3181
4983
  }
@@ -3195,7 +4997,9 @@ function generateScaffoldContext(input) {
3195
4997
  lines.push("");
3196
4998
  if (navigation.command_palette) {
3197
4999
  lines.push("- Command palette: enabled");
3198
- lines.push("- Requirement: implement a real keyboard-triggered command palette, not just placeholder UI text.");
5000
+ lines.push(
5001
+ "- Requirement: implement a real keyboard-triggered command palette, not just placeholder UI text."
5002
+ );
3199
5003
  }
3200
5004
  if (navigation.hotkeys && navigation.hotkeys.length > 0) {
3201
5005
  lines.push(`- Hotkeys: ${navigation.hotkeys.length} configured`);
@@ -3205,229 +5009,21 @@ function generateScaffoldContext(input) {
3205
5009
  lines.push(` - \`${hotkey.key}\`${target ? `: ${target}` : ""}`);
3206
5010
  }
3207
5011
  }
3208
- lines.push("- Requirement: implement these bindings as real keyboard shortcuts, not as decorative text.");
3209
- lines.push("- Presentation rule: do not append hotkey text to persistent nav labels, breadcrumbs, or page titles unless the shell or route contract explicitly requests visible shortcut hints.");
5012
+ lines.push(
5013
+ "- Requirement: implement these bindings as real keyboard shortcuts, not as decorative text."
5014
+ );
5015
+ lines.push(
5016
+ "- Presentation rule: do not append hotkey text to persistent nav labels, breadcrumbs, or page titles unless the shell or route contract explicitly requests visible shortcut hints."
5017
+ );
3210
5018
  }
3211
5019
  lines.push("");
3212
5020
  }
3213
5021
  return lines.join("\n");
3214
5022
  }
3215
5023
 
3216
- // src/registry.ts
3217
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync } from "fs";
3218
- import { join as join2 } from "path";
3219
- import { RegistryAPIClient, API_CONTENT_TYPES as API_CONTENT_TYPES2 } from "@decantr/registry";
3220
- var DEFAULT_API_URL = "https://api.decantr.ai/v1";
3221
- var ALL_CONTENT_TYPES = API_CONTENT_TYPES2;
3222
- function loadFromCache(cacheDir, contentType, id, namespace) {
3223
- const nsDir = namespace ? join2(cacheDir, namespace) : cacheDir;
3224
- const cachePath = id ? join2(nsDir, contentType, `${id}.json`) : join2(nsDir, contentType, "index.json");
3225
- if (!existsSync2(cachePath)) return null;
3226
- try {
3227
- const data = JSON.parse(readFileSync2(cachePath, "utf-8"));
3228
- return { data, source: { type: "cache" } };
3229
- } catch {
3230
- return null;
3231
- }
3232
- }
3233
- function saveToCache(cacheDir, contentType, id, data, namespace = "@official") {
3234
- const dir = join2(cacheDir, namespace, contentType);
3235
- mkdirSync2(dir, { recursive: true });
3236
- const cachePath = id ? join2(dir, `${id}.json`) : join2(dir, "index.json");
3237
- writeFileSync2(cachePath, JSON.stringify(data, null, 2));
3238
- }
3239
- var RegistryClient = class {
3240
- cacheDir;
3241
- apiUrl;
3242
- offline;
3243
- projectRoot;
3244
- apiClient;
3245
- constructor(options = {}) {
3246
- this.projectRoot = options.projectRoot || process.cwd();
3247
- this.cacheDir = options.cacheDir || join2(this.projectRoot, ".decantr", "cache");
3248
- this.apiUrl = options.apiUrl || process.env.DECANTR_API_URL || DEFAULT_API_URL;
3249
- this.offline = options.offline || false;
3250
- this.apiClient = new RegistryAPIClient({
3251
- baseUrl: this.apiUrl,
3252
- apiKey: options.apiKey || process.env.DECANTR_API_KEY || void 0
3253
- });
3254
- }
3255
- getApiUrl() {
3256
- return this.apiUrl;
3257
- }
3258
- /**
3259
- * Load content from .decantr/custom/{contentType}/{id}.json
3260
- * Works for ALL content types, not just themes.
3261
- */
3262
- loadCustomContent(contentType, id) {
3263
- const customPath = join2(
3264
- this.projectRoot,
3265
- ".decantr",
3266
- "custom",
3267
- contentType,
3268
- `${id}.json`
3269
- );
3270
- if (!existsSync2(customPath)) return null;
3271
- try {
3272
- const data = JSON.parse(readFileSync2(customPath, "utf-8"));
3273
- return { data, source: { type: "custom", path: customPath } };
3274
- } catch {
3275
- return null;
3276
- }
3277
- }
3278
- /**
3279
- * List all custom content of a given type from .decantr/custom/{type}/
3280
- */
3281
- listCustomContent(contentType) {
3282
- const dir = join2(this.projectRoot, ".decantr", "custom", contentType);
3283
- if (!existsSync2(dir)) return [];
3284
- try {
3285
- return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
3286
- const data = JSON.parse(readFileSync2(join2(dir, f), "utf-8"));
3287
- return { id: data.id || f.replace(".json", ""), ...data };
3288
- });
3289
- } catch {
3290
- return [];
3291
- }
3292
- }
3293
- /**
3294
- * Unified fetch for a content list.
3295
- * Resolution: API -> Cache. Custom items are merged into the list.
3296
- */
3297
- async fetchContentList(contentType, namespace, sort, recommended, intelligenceSource) {
3298
- let apiItems = [];
3299
- let source = { type: "cache" };
3300
- if (!this.offline) {
3301
- try {
3302
- const apiResult = await this.apiClient.listContent(contentType, {
3303
- namespace,
3304
- sort,
3305
- recommended,
3306
- intelligenceSource
3307
- });
3308
- apiItems = apiResult.items;
3309
- source = { type: "api", url: this.apiUrl };
3310
- saveToCache(this.cacheDir, contentType, null, apiResult, namespace || "@official");
3311
- } catch {
3312
- }
3313
- }
3314
- if (apiItems.length === 0) {
3315
- const cacheResult = loadFromCache(
3316
- this.cacheDir,
3317
- contentType,
3318
- void 0,
3319
- namespace
3320
- );
3321
- if (cacheResult) {
3322
- apiItems = cacheResult.data.items;
3323
- source = { type: "cache" };
3324
- }
3325
- }
3326
- const customItems = this.listCustomContent(contentType);
3327
- const allItems = [...customItems, ...apiItems];
3328
- return {
3329
- data: { items: allItems, total: allItems.length },
3330
- source
3331
- };
3332
- }
3333
- /**
3334
- * Unified fetch for a single content item.
3335
- * Resolution: Custom -> API -> Cache
3336
- */
3337
- async fetchContentItem(contentType, id, namespace = "@official") {
3338
- const customId = id.startsWith("custom:") ? id.slice(7) : id;
3339
- const customResult = this.loadCustomContent(contentType, customId);
3340
- if (customResult) return customResult;
3341
- if (id.startsWith("custom:")) return null;
3342
- if (!this.offline) {
3343
- for (let attempt = 0; attempt < 2; attempt++) {
3344
- try {
3345
- const data = await this.apiClient.getContent(contentType, namespace, id);
3346
- saveToCache(this.cacheDir, contentType, id, data, namespace);
3347
- return { data, source: { type: "api", url: this.apiUrl } };
3348
- } catch (e) {
3349
- if (process.env.DECANTR_DEBUG) {
3350
- console.error(` [debug] API fetch ${attempt === 0 ? "failed" : "retry failed"} for ${contentType}/${namespace}/${id}: ${e.message}`);
3351
- }
3352
- if (attempt === 0) {
3353
- await new Promise((r) => setTimeout(r, 500));
3354
- }
3355
- }
3356
- }
3357
- } else if (process.env.DECANTR_DEBUG) {
3358
- console.error(` [debug] Skipping API (offline mode) for ${contentType}/${namespace}/${id}`);
3359
- }
3360
- return loadFromCache(this.cacheDir, contentType, id, namespace);
3361
- }
3362
- // ── Convenience methods (delegate to unified fetch) ──
3363
- async fetchArchetypes() {
3364
- return this.fetchContentList("archetypes");
3365
- }
3366
- async fetchArchetype(id) {
3367
- return this.fetchContentItem("archetypes", id);
3368
- }
3369
- async fetchBlueprints() {
3370
- return this.fetchContentList("blueprints");
3371
- }
3372
- async fetchBlueprint(id) {
3373
- return this.fetchContentItem("blueprints", id);
3374
- }
3375
- async fetchThemes() {
3376
- return this.fetchContentList("themes");
3377
- }
3378
- async fetchTheme(id) {
3379
- return this.fetchContentItem("themes", id);
3380
- }
3381
- async fetchPatterns() {
3382
- return this.fetchContentList("patterns");
3383
- }
3384
- async fetchPattern(id) {
3385
- return this.fetchContentItem("patterns", id);
3386
- }
3387
- async fetchShells() {
3388
- return this.fetchContentList("shells");
3389
- }
3390
- async fetchShell(id) {
3391
- return this.fetchContentItem("shells", id);
3392
- }
3393
- /**
3394
- * Check if API is available.
3395
- */
3396
- async checkApiAvailability() {
3397
- if (this.offline) return false;
3398
- return this.apiClient.checkHealth();
3399
- }
3400
- };
3401
- async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
3402
- const apiClient = new RegistryAPIClient({ baseUrl: apiUrl });
3403
- const synced = [];
3404
- const failed = [];
3405
- const healthy = await apiClient.checkHealth();
3406
- if (!healthy) {
3407
- return { synced: [], failed: ["API unavailable"] };
3408
- }
3409
- for (const type of ALL_CONTENT_TYPES) {
3410
- try {
3411
- const result = await apiClient.listContent(type, { namespace: "@official" });
3412
- saveToCache(cacheDir, type, null, result, "@official");
3413
- for (const item of result.items) {
3414
- const slug = item.slug;
3415
- const data = item.data;
3416
- const innerSlug = data?.id || data?.slug;
3417
- const cacheKey = slug || innerSlug || item.id;
3418
- if (cacheKey) {
3419
- saveToCache(cacheDir, type, cacheKey, item, "@official");
3420
- }
3421
- }
3422
- synced.push(type);
3423
- } catch {
3424
- failed.push(type);
3425
- }
3426
- }
3427
- return { synced, failed };
3428
- }
3429
-
3430
5024
  export {
5025
+ RegistryClient,
5026
+ syncRegistry,
3431
5027
  collectPatternIdsFromItems,
3432
5028
  mapRegistryArchetypeToArchetypeData,
3433
5029
  composeArchetypes,
@@ -3440,7 +5036,5 @@ export {
3440
5036
  scaffoldMinimal,
3441
5037
  writeExecutionPackBundleArtifacts,
3442
5038
  refreshDerivedFiles,
3443
- mapRegistryPatternToPatternSpecSummary,
3444
- RegistryClient,
3445
- syncRegistry
5039
+ mapRegistryPatternToPatternSpecSummary
3446
5040
  };