@decantr/cli 1.4.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2 -2
- package/dist/{chunk-OTVIAUQG.js → chunk-66RIAQLH.js} +1405 -279
- package/dist/{chunk-ZQ5FTYKG.js → chunk-6RJSFLT4.js} +537 -132
- package/dist/heal-54MKDDSQ.js +155 -0
- package/dist/index.js +2 -2
- package/dist/{upgrade-EHMDEZGC.js → upgrade-25IURU4X.js} +1 -1
- package/package.json +6 -6
- package/LICENSE +0 -21
- package/dist/heal-ZG5VJZ5J.js +0 -60
|
@@ -120,6 +120,12 @@ function composeSections(composeEntries, archetypeResults, overrides) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
const primaryShell = sections.find((s) => s.role === "primary")?.shell || defaultShell;
|
|
124
|
+
for (const section of sections) {
|
|
125
|
+
if (section.shell === "inherit") {
|
|
126
|
+
section.shell = primaryShell;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
123
129
|
let features = [...new Set(allFeatures)];
|
|
124
130
|
if (overrides?.features_add) {
|
|
125
131
|
for (const f of overrides.features_add) {
|
|
@@ -292,11 +298,11 @@ function generateTokensCSS(themeData, mode) {
|
|
|
292
298
|
"--d-radius-lg": "0.75rem",
|
|
293
299
|
"--d-radius-xl": "1rem",
|
|
294
300
|
"--d-radius-full": "9999px",
|
|
295
|
-
// Shadows
|
|
296
|
-
"--d-shadow-sm": "0 1px 2px rgba(0,0,0,0.05)",
|
|
297
|
-
"--d-shadow": "0 1px 3px rgba(0,0,0,0.1)",
|
|
298
|
-
"--d-shadow-md": "0 4px 6px rgba(0,0,0,0.1)",
|
|
299
|
-
"--d-shadow-lg": "0 10px 15px rgba(0,0,0,0.1)",
|
|
301
|
+
// Shadows — dark mode needs higher opacity to be visible on dark backgrounds
|
|
302
|
+
"--d-shadow-sm": tokenMode === "light" ? "0 1px 2px rgba(0,0,0,0.05)" : "0 1px 2px rgba(0,0,0,0.2)",
|
|
303
|
+
"--d-shadow": tokenMode === "light" ? "0 1px 3px rgba(0,0,0,0.1)" : "0 1px 3px rgba(0,0,0,0.25)",
|
|
304
|
+
"--d-shadow-md": tokenMode === "light" ? "0 4px 6px rgba(0,0,0,0.1)" : "0 4px 6px rgba(0,0,0,0.3)",
|
|
305
|
+
"--d-shadow-lg": tokenMode === "light" ? "0 10px 15px rgba(0,0,0,0.1)" : "0 10px 15px rgba(0,0,0,0.4)",
|
|
300
306
|
// Status colors
|
|
301
307
|
"--d-success": themeData.tokens?.base?.success || "#22c55e",
|
|
302
308
|
"--d-error": themeData.tokens?.base?.danger || "#ef4444",
|
|
@@ -313,7 +319,19 @@ ${lines}
|
|
|
313
319
|
`;
|
|
314
320
|
if (mode === "auto") {
|
|
315
321
|
const lightTokens = buildTokens("light");
|
|
316
|
-
const paletteKeys = [
|
|
322
|
+
const paletteKeys = [
|
|
323
|
+
"--d-bg",
|
|
324
|
+
"--d-surface",
|
|
325
|
+
"--d-surface-raised",
|
|
326
|
+
"--d-border",
|
|
327
|
+
"--d-text",
|
|
328
|
+
"--d-text-muted",
|
|
329
|
+
"--d-primary-hover",
|
|
330
|
+
"--d-shadow-sm",
|
|
331
|
+
"--d-shadow",
|
|
332
|
+
"--d-shadow-md",
|
|
333
|
+
"--d-shadow-lg"
|
|
334
|
+
];
|
|
317
335
|
const lightLines = Object.entries(lightTokens).filter(([key]) => paletteKeys.includes(key)).map(([key, value]) => ` ${key}: ${value};`).join("\n");
|
|
318
336
|
css += `
|
|
319
337
|
@media (prefers-color-scheme: light) {
|
|
@@ -351,17 +369,132 @@ function generateDecoratorsCSS(recipeData, themeName) {
|
|
|
351
369
|
`);
|
|
352
370
|
return css.join("\n");
|
|
353
371
|
}
|
|
372
|
+
function generateGlobalCSS(personality) {
|
|
373
|
+
const personalityText = personality.join(" ").toLowerCase();
|
|
374
|
+
let fontBody = "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
375
|
+
if (personalityText.includes("inter")) {
|
|
376
|
+
fontBody = `'Inter', ${fontBody}`;
|
|
377
|
+
} else if (personalityText.includes("geist")) {
|
|
378
|
+
fontBody = `'Geist', ${fontBody}`;
|
|
379
|
+
}
|
|
380
|
+
return `/* Generated by @decantr/cli \u2014 global reset + body styles */
|
|
381
|
+
|
|
382
|
+
*, *::before, *::after {
|
|
383
|
+
box-sizing: border-box;
|
|
384
|
+
margin: 0;
|
|
385
|
+
padding: 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
html {
|
|
389
|
+
color-scheme: dark;
|
|
390
|
+
-webkit-font-smoothing: antialiased;
|
|
391
|
+
-moz-osx-font-smoothing: grayscale;
|
|
392
|
+
text-rendering: optimizeLegibility;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
body {
|
|
396
|
+
font-family: ${fontBody};
|
|
397
|
+
background: var(--d-bg);
|
|
398
|
+
color: var(--d-text);
|
|
399
|
+
line-height: 1.6;
|
|
400
|
+
min-height: 100dvh;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
img, picture, video, canvas, svg {
|
|
404
|
+
display: block;
|
|
405
|
+
max-width: 100%;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
input, button, textarea, select {
|
|
409
|
+
font: inherit;
|
|
410
|
+
color: inherit;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
:focus-visible {
|
|
414
|
+
outline: 2px solid var(--d-primary);
|
|
415
|
+
outline-offset: 2px;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.sr-only {
|
|
419
|
+
position: absolute;
|
|
420
|
+
width: 1px;
|
|
421
|
+
height: 1px;
|
|
422
|
+
padding: 0;
|
|
423
|
+
margin: -1px;
|
|
424
|
+
overflow: hidden;
|
|
425
|
+
clip: rect(0, 0, 0, 0);
|
|
426
|
+
white-space: nowrap;
|
|
427
|
+
border-width: 0;
|
|
428
|
+
}
|
|
429
|
+
`;
|
|
430
|
+
}
|
|
431
|
+
function generateDecoratorsContext(recipeData, recipeName) {
|
|
432
|
+
const lines = [];
|
|
433
|
+
lines.push(`# Recipe Decorators: ${recipeName}`);
|
|
434
|
+
lines.push("");
|
|
435
|
+
lines.push("## Available Classes");
|
|
436
|
+
lines.push("");
|
|
437
|
+
if (recipeData?.decorators && Object.keys(recipeData.decorators).length > 0) {
|
|
438
|
+
lines.push("| Decorator | Description |");
|
|
439
|
+
lines.push("|-----------|-------------|");
|
|
440
|
+
for (const [name, description] of Object.entries(recipeData.decorators)) {
|
|
441
|
+
lines.push(`| ${name} | ${description} |`);
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
lines.push("No decorators defined.");
|
|
445
|
+
}
|
|
446
|
+
lines.push("");
|
|
447
|
+
lines.push("## Usage");
|
|
448
|
+
lines.push("");
|
|
449
|
+
lines.push("Decorators are plain CSS class names from `src/styles/decorators.css`. Combine with atoms:");
|
|
450
|
+
lines.push("");
|
|
451
|
+
lines.push("```tsx");
|
|
452
|
+
lines.push("<div className={css('_flex _col _gap4') + ' " + recipeName + "-card'}>");
|
|
453
|
+
lines.push(" <pre className={css('_p3') + ' " + recipeName + "-code'}>{code}</pre>");
|
|
454
|
+
lines.push("</div>");
|
|
455
|
+
lines.push("```");
|
|
456
|
+
lines.push("");
|
|
457
|
+
lines.push("Atoms use `css()` function. Decorators are plain class strings. Combined via string concatenation.");
|
|
458
|
+
lines.push("");
|
|
459
|
+
return lines.join("\n");
|
|
460
|
+
}
|
|
354
461
|
function generateDecoratorRule(name, description) {
|
|
355
462
|
const rules = [];
|
|
356
463
|
const descLower = description.toLowerCase();
|
|
357
|
-
|
|
464
|
+
const nameLower = name.toLowerCase();
|
|
465
|
+
const isCard = descLower.includes("card") || descLower.includes("panel") || nameLower.includes("card") || nameLower.includes("panel");
|
|
466
|
+
const isInput = descLower.includes("input") || descLower.includes("field") || descLower.includes("textarea") || nameLower.includes("input") || nameLower.includes("textarea");
|
|
467
|
+
const isGlass = descLower.includes("glassmorphic") || descLower.includes("glass") || nameLower.includes("glass");
|
|
468
|
+
const isInteractive = isCard || isInput || isGlass;
|
|
469
|
+
const isNonInteractive = descLower.includes("divider") || descLower.includes("skeleton") || descLower.includes("keyframe") || descLower.includes("canvas") || nameLower.includes("divider") || nameLower.includes("skeleton") || nameLower.includes("canvas");
|
|
470
|
+
if (descLower.includes("monospace") || descLower.includes("mono font")) {
|
|
471
|
+
rules.push("font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace");
|
|
472
|
+
}
|
|
473
|
+
if (descLower.includes("surface-raised") || descLower.includes("surface raised")) {
|
|
474
|
+
rules.push("background: var(--d-surface-raised)");
|
|
475
|
+
} else if (descLower.includes("surface background") || descLower.includes("surface elevation")) {
|
|
358
476
|
rules.push("background: var(--d-surface)");
|
|
359
477
|
} else if (descLower.includes("background") && descLower.includes("theme")) {
|
|
360
478
|
rules.push("background: var(--d-bg)");
|
|
361
479
|
} else if (descLower.includes("primary-tinted") || descLower.includes("primary background")) {
|
|
362
480
|
rules.push("background: color-mix(in srgb, var(--d-primary) 15%, var(--d-surface))");
|
|
363
481
|
}
|
|
364
|
-
if (
|
|
482
|
+
if (isInput) {
|
|
483
|
+
if (!rules.some((r) => r.startsWith("background"))) {
|
|
484
|
+
rules.push("background: var(--d-surface)");
|
|
485
|
+
}
|
|
486
|
+
rules.push("color: var(--d-text)");
|
|
487
|
+
rules.push("padding: 0.5rem 0.75rem");
|
|
488
|
+
rules.push("border-radius: var(--d-radius)");
|
|
489
|
+
rules.push("width: 100%");
|
|
490
|
+
rules.push("outline: none");
|
|
491
|
+
}
|
|
492
|
+
const leftBorderMatch = descLower.match(/(\d+)px\s+left\s+border/);
|
|
493
|
+
if (leftBorderMatch) {
|
|
494
|
+
rules.push(`border-left: ${leftBorderMatch[1]}px solid var(--d-primary)`);
|
|
495
|
+
} else if (descLower.includes("left border")) {
|
|
496
|
+
rules.push("border-left: 3px solid var(--d-primary)");
|
|
497
|
+
} else if (descLower.includes("1px border") || descLower.includes("subtle border")) {
|
|
365
498
|
rules.push("border: 1px solid var(--d-border)");
|
|
366
499
|
} else if (descLower.includes("border") && !descLower.includes("radius")) {
|
|
367
500
|
rules.push("border: 1px solid var(--d-border)");
|
|
@@ -369,23 +502,40 @@ function generateDecoratorRule(name, description) {
|
|
|
369
502
|
const radiusMatch = descLower.match(/(\d+)px radius/);
|
|
370
503
|
if (radiusMatch) {
|
|
371
504
|
rules.push(`border-radius: ${radiusMatch[1]}px`);
|
|
372
|
-
} else if (descLower.includes("radius") || descLower.includes("rounded")) {
|
|
505
|
+
} else if ((descLower.includes("radius") || descLower.includes("rounded")) && !isInput) {
|
|
373
506
|
rules.push("border-radius: var(--d-radius)");
|
|
374
507
|
}
|
|
375
|
-
if (descLower.includes("hover shadow") || descLower.includes("shadow transition")) {
|
|
376
|
-
rules.push("transition: box-shadow 0.15s ease");
|
|
377
|
-
}
|
|
378
508
|
if (descLower.includes("elevation") || descLower.includes("shadow")) {
|
|
379
509
|
rules.push("box-shadow: var(--d-shadow)");
|
|
380
510
|
}
|
|
511
|
+
if (isInteractive && !isNonInteractive) {
|
|
512
|
+
rules.push("transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease");
|
|
513
|
+
}
|
|
381
514
|
if (descLower.includes("entrance animation") || descLower.includes("fade")) {
|
|
382
515
|
rules.push("animation: decantr-fade-in 0.2s ease-out");
|
|
383
516
|
}
|
|
384
517
|
if (descLower.includes("pulse animation") || descLower.includes("skeleton")) {
|
|
385
518
|
rules.push("animation: decantr-pulse 1.5s ease-in-out infinite");
|
|
386
519
|
}
|
|
387
|
-
|
|
520
|
+
const blurMatch = descLower.match(/blur\((\d+)px\)/);
|
|
521
|
+
if (blurMatch) {
|
|
522
|
+
rules.push(`backdrop-filter: blur(${blurMatch[1]}px)`);
|
|
523
|
+
rules.push(`-webkit-backdrop-filter: blur(${blurMatch[1]}px)`);
|
|
524
|
+
} else if (isGlass) {
|
|
525
|
+
rules.push("backdrop-filter: blur(12px)");
|
|
526
|
+
rules.push("-webkit-backdrop-filter: blur(12px)");
|
|
527
|
+
} else if (descLower.includes("blur")) {
|
|
388
528
|
rules.push("backdrop-filter: blur(8px)");
|
|
529
|
+
rules.push("-webkit-backdrop-filter: blur(8px)");
|
|
530
|
+
}
|
|
531
|
+
if (descLower.includes("semi-transparent") || descLower.includes("glassmorphic")) {
|
|
532
|
+
const bgIdx = rules.findIndex((r) => r.startsWith("background:"));
|
|
533
|
+
const rgbaBg = "background: rgba(31, 31, 35, 0.8)";
|
|
534
|
+
if (bgIdx !== -1) {
|
|
535
|
+
rules[bgIdx] = rgbaBg;
|
|
536
|
+
} else {
|
|
537
|
+
rules.push(rgbaBg);
|
|
538
|
+
}
|
|
389
539
|
}
|
|
390
540
|
if (descLower.includes("right-aligned")) {
|
|
391
541
|
rules.push("margin-left: auto");
|
|
@@ -397,12 +547,45 @@ function generateDecoratorRule(name, description) {
|
|
|
397
547
|
rules.push("border-radius: var(--d-radius-lg)");
|
|
398
548
|
rules.push("max-width: 80%");
|
|
399
549
|
}
|
|
550
|
+
if (descLower.includes("monospace") || descLower.includes("code")) {
|
|
551
|
+
if (!rules.some((r) => r.startsWith("padding"))) {
|
|
552
|
+
rules.push("padding: 0.75rem 1rem");
|
|
553
|
+
}
|
|
554
|
+
if (!rules.some((r) => r.startsWith("border-radius"))) {
|
|
555
|
+
rules.push("border-radius: var(--d-radius-sm)");
|
|
556
|
+
}
|
|
557
|
+
rules.push("overflow-x: auto");
|
|
558
|
+
}
|
|
400
559
|
if (rules.length === 0) {
|
|
401
560
|
return `/* .${name}: ${description} */`;
|
|
402
561
|
}
|
|
403
|
-
|
|
562
|
+
let css = `.${name} {
|
|
404
563
|
${rules.join(";\n ")};
|
|
405
564
|
}`;
|
|
565
|
+
if (isInteractive && !isNonInteractive) {
|
|
566
|
+
const stateRules = [];
|
|
567
|
+
if (isCard || isGlass) {
|
|
568
|
+
stateRules.push(`.${name}:hover {
|
|
569
|
+
border-color: var(--d-primary-hover, var(--d-border));
|
|
570
|
+
box-shadow: var(--d-shadow-md);
|
|
571
|
+
}`);
|
|
572
|
+
}
|
|
573
|
+
if (isInput) {
|
|
574
|
+
stateRules.push(`.${name}:focus {
|
|
575
|
+
border-color: var(--d-primary);
|
|
576
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--d-primary) 25%, transparent);
|
|
577
|
+
}`);
|
|
578
|
+
stateRules.push(`.${name}::placeholder {
|
|
579
|
+
color: var(--d-text-muted);
|
|
580
|
+
}`);
|
|
581
|
+
stateRules.push(`.${name}:disabled {
|
|
582
|
+
opacity: 0.5;
|
|
583
|
+
cursor: not-allowed;
|
|
584
|
+
}`);
|
|
585
|
+
}
|
|
586
|
+
css += "\n\n" + stateRules.join("\n\n");
|
|
587
|
+
}
|
|
588
|
+
return css;
|
|
406
589
|
}
|
|
407
590
|
function serializeLayoutItem(item) {
|
|
408
591
|
if (typeof item === "string") {
|
|
@@ -833,6 +1016,32 @@ The \`css()\` function processes atom strings and injects CSS at runtime:
|
|
|
833
1016
|
| \`_trans\` | \`transition:all 0.15s ease\` |
|
|
834
1017
|
| \`_visible\`, \`_invisible\` | visibility |
|
|
835
1018
|
|
|
1019
|
+
### Using Recipe Decorators
|
|
1020
|
+
|
|
1021
|
+
Recipe decorators (from \`src/styles/decorators.css\`) are regular CSS class names, NOT atoms. They are applied directly as class names and combined with atoms using string concatenation:
|
|
1022
|
+
|
|
1023
|
+
\`\`\`tsx
|
|
1024
|
+
// Atoms use css() function, decorators are plain class names
|
|
1025
|
+
<div className={css('_flex _col _gap4') + ' carbon-card'}>
|
|
1026
|
+
<div className={css('_p4') + ' carbon-glass'}>
|
|
1027
|
+
<pre className={css('_p3') + ' carbon-code'}>{code}</pre>
|
|
1028
|
+
</div>
|
|
1029
|
+
</div>
|
|
1030
|
+
\`\`\`
|
|
1031
|
+
|
|
1032
|
+
**Key difference:**
|
|
1033
|
+
- Atoms: \`css('_flex _col _gap4')\` \u2014 processed by @decantr/css runtime
|
|
1034
|
+
- Decorators: \`'carbon-card'\`, \`'carbon-glass'\` \u2014 plain CSS classes from decorators.css
|
|
1035
|
+
- Combined: \`css('_flex _col') + ' carbon-card'\`
|
|
1036
|
+
|
|
1037
|
+
### Routing
|
|
1038
|
+
|
|
1039
|
+
Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing strategy:
|
|
1040
|
+
- \`"hash"\` \u2192 use \`HashRouter\` (e.g., for static hosting, GitHub Pages)
|
|
1041
|
+
- \`"history"\` \u2192 use \`BrowserRouter\` (e.g., for server-rendered apps)
|
|
1042
|
+
|
|
1043
|
+
Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.
|
|
1044
|
+
|
|
836
1045
|
### CSS Architecture
|
|
837
1046
|
|
|
838
1047
|
The CSS is organized into two parts:
|
|
@@ -925,33 +1134,29 @@ function generateTaskContext(templateName, essence) {
|
|
|
925
1134
|
};
|
|
926
1135
|
return renderTemplate(template, vars);
|
|
927
1136
|
}
|
|
928
|
-
function
|
|
929
|
-
const template = loadTemplate(
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
1137
|
+
function generateTaskContextV3(templateName, essence) {
|
|
1138
|
+
const template = loadTemplate(templateName);
|
|
1139
|
+
const pages = essence.blueprint.sections && essence.blueprint.sections.length > 0 ? essence.blueprint.sections.flatMap((s) => s.pages) : essence.blueprint.pages || [];
|
|
1140
|
+
const defaultShell = essence.blueprint.sections?.[0]?.shell || essence.blueprint.shell || "sidebar-main";
|
|
1141
|
+
const layout = pages[0]?.layout?.map(serializeLayoutItem).join(", ") || "none";
|
|
1142
|
+
const scaffoldStructure = pages.map((p) => {
|
|
1143
|
+
const patterns = p.layout.length > 0 ? `
|
|
1144
|
+
- Patterns: ${p.layout.map(serializeLayoutItem).join(", ")}` : "";
|
|
1145
|
+
return `- **${p.id}** (${defaultShell})${patterns}`;
|
|
1146
|
+
}).join("\n");
|
|
1147
|
+
const densityLevel = essence.dna.spacing?.density || "comfortable";
|
|
1148
|
+
const contentGap = essence.dna.spacing?.content_gap || "_gap4";
|
|
936
1149
|
const vars = {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
GUARD_MODE: essence.guard.mode,
|
|
948
|
-
ENFORCE_STYLE: String(essence.guard.enforce_style),
|
|
949
|
-
ENFORCE_RECIPE: String(essence.guard.enforce_recipe),
|
|
950
|
-
DNA_ENFORCEMENT: dnaEnforcement,
|
|
951
|
-
BLUEPRINT_ENFORCEMENT: blueprintEnforcement,
|
|
952
|
-
DENSITY: essence.density.level,
|
|
953
|
-
CONTENT_GAP: essence.density.content_gap,
|
|
954
|
-
LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
|
|
1150
|
+
TARGET: essence.meta.target || "react",
|
|
1151
|
+
THEME_STYLE: essence.dna.theme.style,
|
|
1152
|
+
THEME_MODE: essence.dna.theme.mode,
|
|
1153
|
+
THEME_RECIPE: essence.dna.theme.recipe || essence.dna.theme.style,
|
|
1154
|
+
DEFAULT_SHELL: defaultShell,
|
|
1155
|
+
GUARD_MODE: essence.meta.guard.mode,
|
|
1156
|
+
LAYOUT: layout,
|
|
1157
|
+
DENSITY: densityLevel,
|
|
1158
|
+
CONTENT_GAP: contentGap,
|
|
1159
|
+
SCAFFOLD_STRUCTURE: scaffoldStructure
|
|
955
1160
|
};
|
|
956
1161
|
return renderTemplate(template, vars);
|
|
957
1162
|
}
|
|
@@ -1035,15 +1240,6 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
|
|
|
1035
1240
|
const scaffoldPath = join(contextDir, "task-scaffold.md");
|
|
1036
1241
|
writeFileSync(scaffoldPath, generateTaskContext("task-scaffold.md.template", essence));
|
|
1037
1242
|
contextFiles.push(scaffoldPath);
|
|
1038
|
-
const addPagePath = join(contextDir, "task-add-page.md");
|
|
1039
|
-
writeFileSync(addPagePath, generateTaskContext("task-add-page.md.template", essence));
|
|
1040
|
-
contextFiles.push(addPagePath);
|
|
1041
|
-
const modifyPath = join(contextDir, "task-modify.md");
|
|
1042
|
-
writeFileSync(modifyPath, generateTaskContext("task-modify.md.template", essence));
|
|
1043
|
-
contextFiles.push(modifyPath);
|
|
1044
|
-
const summaryPath = join(contextDir, "essence-summary.md");
|
|
1045
|
-
writeFileSync(summaryPath, generateEssenceSummary(essence));
|
|
1046
|
-
contextFiles.push(summaryPath);
|
|
1047
1243
|
if (composedSections) {
|
|
1048
1244
|
essenceV3.version = "3.1.0";
|
|
1049
1245
|
essenceV3.blueprint = {
|
|
@@ -1052,7 +1248,7 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
|
|
|
1052
1248
|
routes: routeMap || {}
|
|
1053
1249
|
};
|
|
1054
1250
|
if (blueprintData?.personality?.length) {
|
|
1055
|
-
essenceV3.dna.personality = blueprintData.personality;
|
|
1251
|
+
essenceV3.dna.personality = typeof blueprintData.personality === "string" ? [blueprintData.personality] : blueprintData.personality;
|
|
1056
1252
|
}
|
|
1057
1253
|
if (blueprintData?.design_constraints) {
|
|
1058
1254
|
essenceV3.dna.constraints = blueprintData.design_constraints;
|
|
@@ -1065,7 +1261,7 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
|
|
|
1065
1261
|
}
|
|
1066
1262
|
writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
|
|
1067
1263
|
}
|
|
1068
|
-
const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry);
|
|
1264
|
+
const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, recipeData, { isInitialScaffold: true });
|
|
1069
1265
|
contextFiles.push(...refreshResult.contextFiles);
|
|
1070
1266
|
const gitignoreUpdated = updateGitignore(projectRoot);
|
|
1071
1267
|
return {
|
|
@@ -1267,7 +1463,7 @@ When available, use these tools:
|
|
|
1267
1463
|
gitignoreUpdated
|
|
1268
1464
|
};
|
|
1269
1465
|
}
|
|
1270
|
-
async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
1466
|
+
async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThemeData, prefetchedRecipeData, options) {
|
|
1271
1467
|
const decantrDir = join(projectRoot, ".decantr");
|
|
1272
1468
|
const contextDir = join(decantrDir, "context");
|
|
1273
1469
|
mkdirSync(contextDir, { recursive: true });
|
|
@@ -1281,12 +1477,13 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
|
1281
1477
|
blueprint_enforcement: essence.meta.guard.blueprint_enforcement || "warn"
|
|
1282
1478
|
};
|
|
1283
1479
|
const personality = essence.dna.personality || [];
|
|
1284
|
-
let themeData;
|
|
1285
|
-
let recipeData;
|
|
1286
|
-
try {
|
|
1480
|
+
let themeData = prefetchedThemeData;
|
|
1481
|
+
let recipeData = prefetchedRecipeData;
|
|
1482
|
+
if (!themeData) try {
|
|
1287
1483
|
const themeResult = await registry.fetchTheme(themeName);
|
|
1288
1484
|
if (themeResult?.data) {
|
|
1289
|
-
const
|
|
1485
|
+
const raw = themeResult.data;
|
|
1486
|
+
const t = raw.data ?? raw;
|
|
1290
1487
|
themeData = {
|
|
1291
1488
|
seed: t.seed,
|
|
1292
1489
|
palette: t.palette,
|
|
@@ -1298,18 +1495,66 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
|
1298
1495
|
}
|
|
1299
1496
|
} catch {
|
|
1300
1497
|
}
|
|
1301
|
-
try {
|
|
1498
|
+
if (!recipeData) try {
|
|
1302
1499
|
const recipeResult = await registry.fetchRecipe(recipeName);
|
|
1303
1500
|
if (recipeResult?.data) {
|
|
1304
|
-
const
|
|
1501
|
+
const raw = recipeResult.data;
|
|
1502
|
+
const r = raw.data ?? raw;
|
|
1305
1503
|
recipeData = {
|
|
1306
1504
|
decorators: r.decorators,
|
|
1307
1505
|
spatial_hints: r.spatial_hints,
|
|
1308
1506
|
radius_hints: r.radius_hints
|
|
1309
1507
|
};
|
|
1508
|
+
if (!recipeData.decorators && raw.data) {
|
|
1509
|
+
const inner = raw.data;
|
|
1510
|
+
if (inner.decorators) {
|
|
1511
|
+
recipeData.decorators = inner.decorators;
|
|
1512
|
+
recipeData.spatial_hints = inner.spatial_hints;
|
|
1513
|
+
recipeData.radius_hints = inner.radius_hints;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1310
1516
|
}
|
|
1311
1517
|
} catch {
|
|
1312
1518
|
}
|
|
1519
|
+
if (!recipeData?.decorators || Object.keys(recipeData.decorators).length === 0) {
|
|
1520
|
+
try {
|
|
1521
|
+
const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
|
|
1522
|
+
const resp = await fetch(`${apiUrl}/recipes/@official/${recipeName}`);
|
|
1523
|
+
if (resp.ok) {
|
|
1524
|
+
const apiData = await resp.json();
|
|
1525
|
+
const inner = apiData.data ?? apiData;
|
|
1526
|
+
if (inner.decorators && Object.keys(inner.decorators).length > 0) {
|
|
1527
|
+
recipeData = {
|
|
1528
|
+
decorators: inner.decorators,
|
|
1529
|
+
spatial_hints: inner.spatial_hints,
|
|
1530
|
+
radius_hints: inner.radius_hints
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
} catch {
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (!themeData?.seed?.primary) {
|
|
1538
|
+
try {
|
|
1539
|
+
const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
|
|
1540
|
+
const resp = await fetch(`${apiUrl}/themes/@official/${themeName}`);
|
|
1541
|
+
if (resp.ok) {
|
|
1542
|
+
const apiData = await resp.json();
|
|
1543
|
+
const inner = apiData.data ?? apiData;
|
|
1544
|
+
if (inner.seed) {
|
|
1545
|
+
themeData = {
|
|
1546
|
+
seed: inner.seed,
|
|
1547
|
+
palette: inner.palette,
|
|
1548
|
+
cvd_support: inner.cvd_support,
|
|
1549
|
+
tokens: inner.tokens,
|
|
1550
|
+
typography_hints: inner.typography_hints,
|
|
1551
|
+
motion_hints: inner.motion_hints
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
} catch {
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1313
1558
|
const stylesDir = join(projectRoot, "src", "styles");
|
|
1314
1559
|
mkdirSync(stylesDir, { recursive: true });
|
|
1315
1560
|
const tokensPath = join(stylesDir, "tokens.css");
|
|
@@ -1322,15 +1567,39 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
|
1322
1567
|
if (hasRealRecipeData || !existsSync(decoratorsPath)) {
|
|
1323
1568
|
writeFileSync(decoratorsPath, generateDecoratorsCSS(recipeData, themeName));
|
|
1324
1569
|
}
|
|
1325
|
-
const
|
|
1570
|
+
const globalPath = join(stylesDir, "global.css");
|
|
1571
|
+
if (!existsSync(globalPath)) {
|
|
1572
|
+
writeFileSync(globalPath, generateGlobalCSS(personality));
|
|
1573
|
+
}
|
|
1574
|
+
const cssFiles = [tokensPath, decoratorsPath, globalPath];
|
|
1575
|
+
const decoratorsMdPath = join(contextDir, "decorators.md");
|
|
1576
|
+
writeFileSync(decoratorsMdPath, generateDecoratorsContext(recipeData, recipeName));
|
|
1326
1577
|
const decantrMdPath = join(projectRoot, "DECANTR.md");
|
|
1327
1578
|
writeFileSync(decantrMdPath, generateDecantrMdV31(guardMode, CSS_APPROACH_CONTENT));
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1579
|
+
const hasSections = essence.blueprint.sections && essence.blueprint.sections.length > 0;
|
|
1580
|
+
const contextFiles = [decoratorsMdPath];
|
|
1581
|
+
if (!hasSections) {
|
|
1582
|
+
const summaryPath = join(contextDir, "essence-summary.md");
|
|
1583
|
+
writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
|
|
1584
|
+
contextFiles.push(summaryPath);
|
|
1585
|
+
}
|
|
1586
|
+
if (!options?.isInitialScaffold) {
|
|
1587
|
+
const addPagePath = join(contextDir, "task-add-page.md");
|
|
1588
|
+
writeFileSync(addPagePath, generateTaskContextV3("task-add-page.md.template", essence));
|
|
1589
|
+
contextFiles.push(addPagePath);
|
|
1590
|
+
const modifyPath = join(contextDir, "task-modify.md");
|
|
1591
|
+
writeFileSync(modifyPath, generateTaskContextV3("task-modify.md.template", essence));
|
|
1592
|
+
contextFiles.push(modifyPath);
|
|
1593
|
+
}
|
|
1331
1594
|
const blueprint = essence.blueprint;
|
|
1332
1595
|
const sections = blueprint.sections && blueprint.sections.length > 0 ? blueprint.sections : [];
|
|
1333
1596
|
if (sections.length > 0) {
|
|
1597
|
+
const primarySectionShell = sections.find((s) => s.role === "primary")?.shell || "sidebar-main";
|
|
1598
|
+
for (const section of sections) {
|
|
1599
|
+
if (section.shell === "inherit") {
|
|
1600
|
+
section.shell = primarySectionShell;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1334
1603
|
const patternSpecs = {};
|
|
1335
1604
|
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1336
1605
|
for (const section of sections) {
|
|
@@ -1347,13 +1616,36 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
|
1347
1616
|
const inner = raw.data ?? raw;
|
|
1348
1617
|
const defaultPreset = inner.default_preset || "standard";
|
|
1349
1618
|
const preset = inner.presets?.[defaultPreset];
|
|
1350
|
-
|
|
1619
|
+
let slots = preset?.layout?.slots || {};
|
|
1620
|
+
if (Object.keys(slots).length === 0) {
|
|
1621
|
+
const synthetic = generateSyntheticSlots(name, inner.description || "");
|
|
1622
|
+
if (Object.keys(synthetic).length > 0) {
|
|
1623
|
+
slots = synthetic;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
const spec = {
|
|
1351
1627
|
description: inner.description || "",
|
|
1352
1628
|
components: inner.components || [],
|
|
1353
|
-
slots
|
|
1629
|
+
slots
|
|
1354
1630
|
};
|
|
1631
|
+
if (!spec.components || spec.components.length === 0) {
|
|
1632
|
+
const syntheticComps = generateSyntheticComponents(name, spec.description);
|
|
1633
|
+
if (syntheticComps.length > 0) spec.components = syntheticComps;
|
|
1634
|
+
}
|
|
1635
|
+
patternSpecs[name] = spec;
|
|
1636
|
+
} else {
|
|
1637
|
+
const synthetic = generateSyntheticSlots(name, "");
|
|
1638
|
+
const syntheticComps = generateSyntheticComponents(name, "");
|
|
1639
|
+
if (Object.keys(synthetic).length > 0 || syntheticComps.length > 0) {
|
|
1640
|
+
patternSpecs[name] = { description: "", components: syntheticComps, slots: synthetic };
|
|
1641
|
+
}
|
|
1355
1642
|
}
|
|
1356
1643
|
} catch {
|
|
1644
|
+
const synthetic = generateSyntheticSlots(name, "");
|
|
1645
|
+
const syntheticComps = generateSyntheticComponents(name, "");
|
|
1646
|
+
if (Object.keys(synthetic).length > 0 || syntheticComps.length > 0) {
|
|
1647
|
+
patternSpecs[name] = { description: "", components: syntheticComps, slots: synthetic };
|
|
1648
|
+
}
|
|
1357
1649
|
}
|
|
1358
1650
|
}
|
|
1359
1651
|
}
|
|
@@ -1423,8 +1715,17 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
|
1423
1715
|
}
|
|
1424
1716
|
}
|
|
1425
1717
|
for (const section of sections) {
|
|
1426
|
-
const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role
|
|
1427
|
-
|
|
1718
|
+
const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role === "gateway" ? "Gateway" : "Public";
|
|
1719
|
+
let zoneContext = `**Zone:** ${zoneLabel} (${section.role}) \u2014 ${section.shell} shell`;
|
|
1720
|
+
if (section.role === "gateway") {
|
|
1721
|
+
zoneContext += "\nAuth success \u2192 enters App zone. Sign out returns here.";
|
|
1722
|
+
} else if (section.role === "primary") {
|
|
1723
|
+
zoneContext += "\nAuthenticated users land here. Sign out \u2192 Gateway (/login).";
|
|
1724
|
+
} else if (section.role === "public") {
|
|
1725
|
+
zoneContext += "\nAnonymous visitors. CTAs lead to Gateway (/login, /register).";
|
|
1726
|
+
} else if (section.role === "auxiliary") {
|
|
1727
|
+
zoneContext += "\nSupporting section within App zone. Shares navigation with primary.";
|
|
1728
|
+
}
|
|
1428
1729
|
const sectionPatterns = {};
|
|
1429
1730
|
for (const page of section.pages) {
|
|
1430
1731
|
for (const item of page.layout) {
|
|
@@ -1496,13 +1797,36 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
|
1496
1797
|
const inner = raw.data ?? raw;
|
|
1497
1798
|
const defaultPreset = inner.default_preset || "standard";
|
|
1498
1799
|
const preset = inner.presets?.[defaultPreset];
|
|
1499
|
-
|
|
1800
|
+
let slots = preset?.layout?.slots || {};
|
|
1801
|
+
if (Object.keys(slots).length === 0) {
|
|
1802
|
+
const synthetic = generateSyntheticSlots(name, inner.description || "");
|
|
1803
|
+
if (Object.keys(synthetic).length > 0) {
|
|
1804
|
+
slots = synthetic;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
const spec = {
|
|
1500
1808
|
description: inner.description || "",
|
|
1501
1809
|
components: inner.components || [],
|
|
1502
|
-
slots
|
|
1810
|
+
slots
|
|
1503
1811
|
};
|
|
1812
|
+
if (!spec.components || spec.components.length === 0) {
|
|
1813
|
+
const syntheticComps = generateSyntheticComponents(name, spec.description);
|
|
1814
|
+
if (syntheticComps.length > 0) spec.components = syntheticComps;
|
|
1815
|
+
}
|
|
1816
|
+
patternSpecs[name] = spec;
|
|
1817
|
+
} else {
|
|
1818
|
+
const synthetic = generateSyntheticSlots(name, "");
|
|
1819
|
+
const syntheticComps = generateSyntheticComponents(name, "");
|
|
1820
|
+
if (Object.keys(synthetic).length > 0 || syntheticComps.length > 0) {
|
|
1821
|
+
patternSpecs[name] = { description: "", components: syntheticComps, slots: synthetic };
|
|
1822
|
+
}
|
|
1504
1823
|
}
|
|
1505
1824
|
} catch {
|
|
1825
|
+
const synthetic = generateSyntheticSlots(name, "");
|
|
1826
|
+
const syntheticComps = generateSyntheticComponents(name, "");
|
|
1827
|
+
if (Object.keys(synthetic).length > 0 || syntheticComps.length > 0) {
|
|
1828
|
+
patternSpecs[name] = { description: "", components: syntheticComps, slots: synthetic };
|
|
1829
|
+
}
|
|
1506
1830
|
}
|
|
1507
1831
|
}
|
|
1508
1832
|
}
|
|
@@ -1565,8 +1889,95 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
|
1565
1889
|
cssFiles
|
|
1566
1890
|
};
|
|
1567
1891
|
}
|
|
1892
|
+
function generateSyntheticSlots(patternId, description) {
|
|
1893
|
+
const desc = description.toLowerCase();
|
|
1894
|
+
const syntheticSlots = {};
|
|
1895
|
+
if (patternId.includes("feature") || desc.includes("feature")) {
|
|
1896
|
+
syntheticSlots["grid"] = "Grid of feature cards (icon + title + description)";
|
|
1897
|
+
syntheticSlots["feature-card"] = "Individual feature with icon, heading, and description text";
|
|
1898
|
+
}
|
|
1899
|
+
if (patternId.includes("pricing") || desc.includes("pricing")) {
|
|
1900
|
+
syntheticSlots["tiers"] = "Pricing tier cards (name, price, features list, CTA button)";
|
|
1901
|
+
syntheticSlots["toggle"] = "Monthly/annual billing toggle (optional)";
|
|
1902
|
+
}
|
|
1903
|
+
if (patternId.includes("testimonial") || desc.includes("testimonial")) {
|
|
1904
|
+
syntheticSlots["quotes"] = "Testimonial cards (quote text, author name, role, avatar)";
|
|
1905
|
+
}
|
|
1906
|
+
if (patternId.includes("cta") || desc.includes("call-to-action") || desc.includes("call to action")) {
|
|
1907
|
+
syntheticSlots["headline"] = "CTA headline text";
|
|
1908
|
+
syntheticSlots["description"] = "Supporting description text";
|
|
1909
|
+
syntheticSlots["actions"] = "CTA button(s)";
|
|
1910
|
+
}
|
|
1911
|
+
if (patternId.includes("how-it-works") || desc.includes("how it works") || desc.includes("timeline") || desc.includes("steps")) {
|
|
1912
|
+
syntheticSlots["steps"] = "Numbered steps (step number, title, description)";
|
|
1913
|
+
}
|
|
1914
|
+
if (patternId.includes("team") || desc.includes("team")) {
|
|
1915
|
+
syntheticSlots["members"] = "Team member cards (avatar, name, role)";
|
|
1916
|
+
}
|
|
1917
|
+
if (patternId.includes("story") || desc.includes("story") || desc.includes("about")) {
|
|
1918
|
+
syntheticSlots["content"] = "Story/about narrative text content";
|
|
1919
|
+
}
|
|
1920
|
+
if (patternId.includes("values") || desc.includes("values")) {
|
|
1921
|
+
syntheticSlots["values"] = "Value cards (icon/emoji, title, description)";
|
|
1922
|
+
}
|
|
1923
|
+
if (patternId.includes("form") || desc.includes("form") || desc.includes("contact")) {
|
|
1924
|
+
syntheticSlots["fields"] = "Form fields (name, email, message, etc.)";
|
|
1925
|
+
syntheticSlots["submit"] = "Submit button";
|
|
1926
|
+
}
|
|
1927
|
+
if (patternId.includes("content") || desc.includes("legal") || desc.includes("privacy") || desc.includes("policy")) {
|
|
1928
|
+
syntheticSlots["body"] = "Long-form text content with headings and paragraphs";
|
|
1929
|
+
syntheticSlots["toc"] = "Table of contents sidebar (optional)";
|
|
1930
|
+
}
|
|
1931
|
+
if (patternId.includes("settings") || desc.includes("settings") || desc.includes("preferences")) {
|
|
1932
|
+
syntheticSlots["sections"] = "Settings sections (label, description, input/toggle)";
|
|
1933
|
+
}
|
|
1934
|
+
if (patternId.includes("security") || desc.includes("security") || desc.includes("password")) {
|
|
1935
|
+
syntheticSlots["sections"] = "Security sections (password change, MFA toggle, session list)";
|
|
1936
|
+
}
|
|
1937
|
+
if (patternId.includes("session") || desc.includes("session")) {
|
|
1938
|
+
syntheticSlots["list"] = "Active sessions list (device, location, last active, revoke button)";
|
|
1939
|
+
}
|
|
1940
|
+
if (patternId.includes("message") || desc.includes("message") || desc.includes("chat")) {
|
|
1941
|
+
syntheticSlots["messages"] = "Message bubbles (user/assistant, content, timestamp)";
|
|
1942
|
+
}
|
|
1943
|
+
if (patternId.includes("input") && desc.includes("chat")) {
|
|
1944
|
+
syntheticSlots["textarea"] = "Auto-expanding message input";
|
|
1945
|
+
syntheticSlots["actions"] = "Attach file button, send button";
|
|
1946
|
+
}
|
|
1947
|
+
if (patternId.includes("empty") || desc.includes("empty")) {
|
|
1948
|
+
syntheticSlots["illustration"] = "Empty state illustration or icon";
|
|
1949
|
+
syntheticSlots["message"] = "Welcome/empty state message";
|
|
1950
|
+
syntheticSlots["suggestions"] = "Suggested actions or prompts";
|
|
1951
|
+
}
|
|
1952
|
+
if (patternId.includes("header") && desc.includes("chat")) {
|
|
1953
|
+
syntheticSlots["title"] = "Conversation title or model name";
|
|
1954
|
+
syntheticSlots["actions"] = "Header action buttons (new chat, settings)";
|
|
1955
|
+
}
|
|
1956
|
+
return syntheticSlots;
|
|
1957
|
+
}
|
|
1958
|
+
function generateSyntheticComponents(patternId, description) {
|
|
1959
|
+
const desc = description.toLowerCase();
|
|
1960
|
+
const syntheticComponents = [];
|
|
1961
|
+
if (patternId.includes("hero")) syntheticComponents.push("Button", "Icon", "Image");
|
|
1962
|
+
if (patternId.includes("feature")) syntheticComponents.push("Card", "Icon", "Text");
|
|
1963
|
+
if (patternId.includes("pricing")) syntheticComponents.push("Card", "Button", "Badge");
|
|
1964
|
+
if (patternId.includes("testimonial")) syntheticComponents.push("Card", "Avatar", "Text");
|
|
1965
|
+
if (patternId.includes("cta")) syntheticComponents.push("Button", "Text");
|
|
1966
|
+
if (patternId.includes("form") || patternId.includes("contact")) syntheticComponents.push("Input", "Textarea", "Button", "Label");
|
|
1967
|
+
if (patternId.includes("team")) syntheticComponents.push("Card", "Avatar", "Text");
|
|
1968
|
+
if (patternId.includes("settings") || patternId.includes("security")) syntheticComponents.push("Card", "Toggle", "Input", "Button");
|
|
1969
|
+
if (patternId.includes("message") || patternId.includes("chat")) syntheticComponents.push("Avatar", "Text", "CodeBlock");
|
|
1970
|
+
if (patternId.includes("input") && desc.includes("chat")) syntheticComponents.push("Textarea", "Button", "Icon");
|
|
1971
|
+
if (patternId.includes("header") && desc.includes("chat")) syntheticComponents.push("Button", "Icon", "Text");
|
|
1972
|
+
if (patternId.includes("content") || patternId.includes("legal")) syntheticComponents.push("Heading", "Text", "List");
|
|
1973
|
+
if (patternId.includes("how-it-works") || patternId.includes("steps")) syntheticComponents.push("Card", "Icon", "Text", "Badge");
|
|
1974
|
+
if (patternId.includes("values")) syntheticComponents.push("Card", "Icon", "Text");
|
|
1975
|
+
if (patternId.includes("story") || patternId.includes("about")) syntheticComponents.push("Text", "Image");
|
|
1976
|
+
if (patternId.includes("empty") || patternId.includes("new")) syntheticComponents.push("Icon", "Text", "Button");
|
|
1977
|
+
return [...new Set(syntheticComponents)];
|
|
1978
|
+
}
|
|
1568
1979
|
function generateSectionContext(input) {
|
|
1569
|
-
const { section,
|
|
1980
|
+
const { section, decorators, guardConfig, personality, themeName, recipeName, zoneContext, patternSpecs, recipeHints, constraints, shellInfo } = input;
|
|
1570
1981
|
const lines = [];
|
|
1571
1982
|
lines.push(`# Section: ${section.id}`);
|
|
1572
1983
|
lines.push("");
|
|
@@ -1587,40 +1998,16 @@ function generateSectionContext(input) {
|
|
|
1587
1998
|
lines.push("");
|
|
1588
1999
|
lines.push("---");
|
|
1589
2000
|
lines.push("");
|
|
1590
|
-
lines.push(
|
|
2001
|
+
lines.push(`**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`);
|
|
1591
2002
|
lines.push("");
|
|
1592
|
-
lines.push(
|
|
1593
|
-
lines.push(`|------|-------|----------|-------------|`);
|
|
1594
|
-
lines.push(`| Style guard | DNA | ${guardConfig.dna_enforcement} | Code must use the ${themeName} theme |`);
|
|
1595
|
-
lines.push(`| Recipe guard | DNA | ${guardConfig.dna_enforcement} | Visual recipe must match ${recipeName} |`);
|
|
1596
|
-
lines.push(`| Density guard | DNA | ${guardConfig.dna_enforcement} | Content gap must match essence density |`);
|
|
1597
|
-
lines.push(`| Accessibility guard | DNA | ${guardConfig.dna_enforcement} | Must meet WCAG level from essence |`);
|
|
1598
|
-
lines.push(`| Structure guard | Blueprint | ${guardConfig.blueprint_enforcement} | Pages must exist in essence structure |`);
|
|
1599
|
-
lines.push(`| Layout guard | Blueprint | ${guardConfig.blueprint_enforcement} | Pattern order must match essence layout |`);
|
|
1600
|
-
lines.push(`| Pattern existence | Blueprint | ${guardConfig.blueprint_enforcement} | All patterns must exist in registry |`);
|
|
1601
|
-
lines.push("");
|
|
1602
|
-
lines.push(`**Guard mode:** ${guardConfig.mode}`);
|
|
1603
|
-
lines.push("");
|
|
1604
|
-
lines.push("---");
|
|
1605
|
-
lines.push("");
|
|
1606
|
-
lines.push(`## Theme: ${themeName}`);
|
|
1607
|
-
lines.push("");
|
|
1608
|
-
lines.push("```css");
|
|
1609
|
-
lines.push(themeTokens);
|
|
1610
|
-
lines.push("```");
|
|
1611
|
-
lines.push("");
|
|
1612
|
-
lines.push("---");
|
|
1613
|
-
lines.push("");
|
|
1614
|
-
lines.push(`## Decorators (${recipeName} recipe)`);
|
|
2003
|
+
lines.push(`**Theme tokens:** see \`src/styles/tokens.css\` \u2014 use \`var(--d-primary)\`, \`var(--d-bg)\`, etc.`);
|
|
1615
2004
|
lines.push("");
|
|
1616
2005
|
if (decorators.length > 0) {
|
|
1617
|
-
|
|
1618
|
-
lines.push(
|
|
1619
|
-
|
|
1620
|
-
lines.push(`| ${dec.name} | ${dec.description} |`);
|
|
1621
|
-
}
|
|
2006
|
+
const names = decorators.map((d) => `\`${d.name}\``).join(", ");
|
|
2007
|
+
lines.push(`**Decorators:** ${names} (see \`src/styles/decorators.css\`)`);
|
|
2008
|
+
lines.push("Usage: `className={css('_flex _col') + ' carbon-card'}` \u2014 atoms via css(), decorators as plain class strings.");
|
|
1622
2009
|
} else {
|
|
1623
|
-
lines.push("
|
|
2010
|
+
lines.push("**Decorators:** none defined.");
|
|
1624
2011
|
}
|
|
1625
2012
|
lines.push("");
|
|
1626
2013
|
if (recipeHints) {
|
|
@@ -1638,11 +2025,8 @@ function generateSectionContext(input) {
|
|
|
1638
2025
|
lines.push("---");
|
|
1639
2026
|
lines.push("");
|
|
1640
2027
|
if (zoneContext) {
|
|
1641
|
-
lines.push("## Zone Context");
|
|
1642
|
-
lines.push("");
|
|
1643
2028
|
lines.push(zoneContext);
|
|
1644
|
-
lines.push("");
|
|
1645
|
-
lines.push("---");
|
|
2029
|
+
lines.push("For full app topology, see `.decantr/context/scaffold.md`");
|
|
1646
2030
|
lines.push("");
|
|
1647
2031
|
}
|
|
1648
2032
|
if (section.features.length > 0) {
|
|
@@ -1654,11 +2038,7 @@ function generateSectionContext(input) {
|
|
|
1654
2038
|
lines.push("");
|
|
1655
2039
|
}
|
|
1656
2040
|
if (personality.length > 0) {
|
|
1657
|
-
lines.push("
|
|
1658
|
-
lines.push("");
|
|
1659
|
-
lines.push(personality.join(", "));
|
|
1660
|
-
lines.push("");
|
|
1661
|
-
lines.push("---");
|
|
2041
|
+
lines.push("**Personality:** See scaffold.md for personality and visual direction.");
|
|
1662
2042
|
lines.push("");
|
|
1663
2043
|
}
|
|
1664
2044
|
if (constraints && Object.keys(constraints).length > 0) {
|
|
@@ -1671,6 +2051,34 @@ function generateSectionContext(input) {
|
|
|
1671
2051
|
lines.push("---");
|
|
1672
2052
|
lines.push("");
|
|
1673
2053
|
}
|
|
2054
|
+
const uniquePatterns = /* @__PURE__ */ new Map();
|
|
2055
|
+
for (const page of section.pages) {
|
|
2056
|
+
const patternNames = page.layout.flatMap(extractPatternNames);
|
|
2057
|
+
for (const name of patternNames) {
|
|
2058
|
+
if (patternSpecs[name] && !uniquePatterns.has(name)) {
|
|
2059
|
+
uniquePatterns.set(name, patternSpecs[name]);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
if (uniquePatterns.size > 0) {
|
|
2064
|
+
lines.push("## Pattern Reference");
|
|
2065
|
+
lines.push("");
|
|
2066
|
+
for (const [patternName, spec] of uniquePatterns) {
|
|
2067
|
+
lines.push(`### ${patternName}`);
|
|
2068
|
+
lines.push("");
|
|
2069
|
+
lines.push(spec.description);
|
|
2070
|
+
lines.push("");
|
|
2071
|
+
lines.push(`**Components:** ${spec.components.join(", ")}`);
|
|
2072
|
+
lines.push("");
|
|
2073
|
+
lines.push("**Layout slots:**");
|
|
2074
|
+
for (const [slot, desc] of Object.entries(spec.slots)) {
|
|
2075
|
+
lines.push(`- \`${slot}\`: ${desc}`);
|
|
2076
|
+
}
|
|
2077
|
+
lines.push("");
|
|
2078
|
+
}
|
|
2079
|
+
lines.push("---");
|
|
2080
|
+
lines.push("");
|
|
2081
|
+
}
|
|
1674
2082
|
lines.push("## Pages");
|
|
1675
2083
|
lines.push("");
|
|
1676
2084
|
for (const page of section.pages) {
|
|
@@ -1691,22 +2099,6 @@ function generateSectionContext(input) {
|
|
|
1691
2099
|
}
|
|
1692
2100
|
lines.push("");
|
|
1693
2101
|
}
|
|
1694
|
-
const patternNames = page.layout.flatMap(extractPatternNames);
|
|
1695
|
-
for (const patternName of patternNames) {
|
|
1696
|
-
const spec = patternSpecs[patternName];
|
|
1697
|
-
if (!spec) continue;
|
|
1698
|
-
lines.push(`#### Pattern: ${patternName}`);
|
|
1699
|
-
lines.push("");
|
|
1700
|
-
lines.push(spec.description);
|
|
1701
|
-
lines.push("");
|
|
1702
|
-
lines.push(`**Components:** ${spec.components.join(", ")}`);
|
|
1703
|
-
lines.push("");
|
|
1704
|
-
lines.push("**Layout slots:**");
|
|
1705
|
-
for (const [slot, desc] of Object.entries(spec.slots)) {
|
|
1706
|
-
lines.push(`- \`${slot}\`: ${desc}`);
|
|
1707
|
-
}
|
|
1708
|
-
lines.push("");
|
|
1709
|
-
}
|
|
1710
2102
|
}
|
|
1711
2103
|
return lines.join("\n");
|
|
1712
2104
|
}
|
|
@@ -1925,12 +2317,22 @@ var RegistryClient = class {
|
|
|
1925
2317
|
if (customResult) return customResult;
|
|
1926
2318
|
if (id.startsWith("custom:")) return null;
|
|
1927
2319
|
if (!this.offline) {
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2320
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2321
|
+
try {
|
|
2322
|
+
const data = await this.apiClient.getContent(contentType, namespace, id);
|
|
2323
|
+
saveToCache(this.cacheDir, contentType, id, data, namespace);
|
|
2324
|
+
return { data, source: { type: "api", url: this.apiUrl } };
|
|
2325
|
+
} catch (e) {
|
|
2326
|
+
if (process.env.DECANTR_DEBUG) {
|
|
2327
|
+
console.error(` [debug] API fetch ${attempt === 0 ? "failed" : "retry failed"} for ${contentType}/${namespace}/${id}: ${e.message}`);
|
|
2328
|
+
}
|
|
2329
|
+
if (attempt === 0) {
|
|
2330
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
1933
2333
|
}
|
|
2334
|
+
} else if (process.env.DECANTR_DEBUG) {
|
|
2335
|
+
console.error(` [debug] Skipping API (offline mode) for ${contentType}/${namespace}/${id}`);
|
|
1934
2336
|
}
|
|
1935
2337
|
return loadFromCache(this.cacheDir, contentType, id, namespace);
|
|
1936
2338
|
}
|
|
@@ -1992,9 +2394,12 @@ async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
|
|
|
1992
2394
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
1993
2395
|
saveToCache(cacheDir, type, null, result, "@official");
|
|
1994
2396
|
for (const item of result.items) {
|
|
1995
|
-
const
|
|
1996
|
-
|
|
1997
|
-
|
|
2397
|
+
const slug = item.slug;
|
|
2398
|
+
const data = item.data;
|
|
2399
|
+
const innerSlug = data?.id || data?.slug;
|
|
2400
|
+
const cacheKey = slug || innerSlug || item.id;
|
|
2401
|
+
if (cacheKey) {
|
|
2402
|
+
saveToCache(cacheDir, type, cacheKey, item, "@official");
|
|
1998
2403
|
}
|
|
1999
2404
|
}
|
|
2000
2405
|
synced.push(type);
|