@decantr/cli 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-OTVIAUQG.js";
3
- import "./chunk-ZQ5FTYKG.js";
2
+ import "./chunk-SUNMRG3P.js";
3
+ import "./chunk-6K6ZPDT4.js";
@@ -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) {
@@ -351,17 +357,57 @@ function generateDecoratorsCSS(recipeData, themeName) {
351
357
  `);
352
358
  return css.join("\n");
353
359
  }
360
+ function generateDecoratorsContext(recipeData, recipeName) {
361
+ const lines = [];
362
+ lines.push(`# Recipe Decorators: ${recipeName}`);
363
+ lines.push("");
364
+ lines.push("## Available Classes");
365
+ lines.push("");
366
+ if (recipeData?.decorators && Object.keys(recipeData.decorators).length > 0) {
367
+ lines.push("| Decorator | Description |");
368
+ lines.push("|-----------|-------------|");
369
+ for (const [name, description] of Object.entries(recipeData.decorators)) {
370
+ lines.push(`| ${name} | ${description} |`);
371
+ }
372
+ } else {
373
+ lines.push("No decorators defined.");
374
+ }
375
+ lines.push("");
376
+ lines.push("## Usage");
377
+ lines.push("");
378
+ lines.push("Decorators are plain CSS class names from `src/styles/decorators.css`. Combine with atoms:");
379
+ lines.push("");
380
+ lines.push("```tsx");
381
+ lines.push("<div className={css('_flex _col _gap4') + ' " + recipeName + "-card'}>");
382
+ lines.push(" <pre className={css('_p3') + ' " + recipeName + "-code'}>{code}</pre>");
383
+ lines.push("</div>");
384
+ lines.push("```");
385
+ lines.push("");
386
+ lines.push("Atoms use `css()` function. Decorators are plain class strings. Combined via string concatenation.");
387
+ lines.push("");
388
+ return lines.join("\n");
389
+ }
354
390
  function generateDecoratorRule(name, description) {
355
391
  const rules = [];
356
392
  const descLower = description.toLowerCase();
357
- if (descLower.includes("surface background") || descLower.includes("surface elevation")) {
393
+ if (descLower.includes("monospace") || descLower.includes("mono font")) {
394
+ rules.push("font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace");
395
+ }
396
+ if (descLower.includes("surface-raised") || descLower.includes("surface raised")) {
397
+ rules.push("background: var(--d-surface-raised)");
398
+ } else if (descLower.includes("surface background") || descLower.includes("surface elevation")) {
358
399
  rules.push("background: var(--d-surface)");
359
400
  } else if (descLower.includes("background") && descLower.includes("theme")) {
360
401
  rules.push("background: var(--d-bg)");
361
402
  } else if (descLower.includes("primary-tinted") || descLower.includes("primary background")) {
362
403
  rules.push("background: color-mix(in srgb, var(--d-primary) 15%, var(--d-surface))");
363
404
  }
364
- if (descLower.includes("1px border") || descLower.includes("subtle border")) {
405
+ const leftBorderMatch = descLower.match(/(\d+)px\s+left\s+border/);
406
+ if (leftBorderMatch) {
407
+ rules.push(`border-left: ${leftBorderMatch[1]}px solid var(--d-primary)`);
408
+ } else if (descLower.includes("left border")) {
409
+ rules.push("border-left: 3px solid var(--d-primary)");
410
+ } else if (descLower.includes("1px border") || descLower.includes("subtle border")) {
365
411
  rules.push("border: 1px solid var(--d-border)");
366
412
  } else if (descLower.includes("border") && !descLower.includes("radius")) {
367
413
  rules.push("border: 1px solid var(--d-border)");
@@ -397,6 +443,15 @@ function generateDecoratorRule(name, description) {
397
443
  rules.push("border-radius: var(--d-radius-lg)");
398
444
  rules.push("max-width: 80%");
399
445
  }
446
+ if (descLower.includes("monospace") || descLower.includes("code")) {
447
+ if (!rules.some((r) => r.startsWith("padding"))) {
448
+ rules.push("padding: 0.75rem 1rem");
449
+ }
450
+ if (!rules.some((r) => r.startsWith("border-radius"))) {
451
+ rules.push("border-radius: var(--d-radius-sm)");
452
+ }
453
+ rules.push("overflow-x: auto");
454
+ }
400
455
  if (rules.length === 0) {
401
456
  return `/* .${name}: ${description} */`;
402
457
  }
@@ -833,6 +888,32 @@ The \`css()\` function processes atom strings and injects CSS at runtime:
833
888
  | \`_trans\` | \`transition:all 0.15s ease\` |
834
889
  | \`_visible\`, \`_invisible\` | visibility |
835
890
 
891
+ ### Using Recipe Decorators
892
+
893
+ Recipe decorators (from \`src/styles/decorators.css\`) are regular CSS class names, NOT atoms. They are applied directly as class names and combined with atoms using string concatenation:
894
+
895
+ \`\`\`tsx
896
+ // Atoms use css() function, decorators are plain class names
897
+ <div className={css('_flex _col _gap4') + ' carbon-card'}>
898
+ <div className={css('_p4') + ' carbon-glass'}>
899
+ <pre className={css('_p3') + ' carbon-code'}>{code}</pre>
900
+ </div>
901
+ </div>
902
+ \`\`\`
903
+
904
+ **Key difference:**
905
+ - Atoms: \`css('_flex _col _gap4')\` \u2014 processed by @decantr/css runtime
906
+ - Decorators: \`'carbon-card'\`, \`'carbon-glass'\` \u2014 plain CSS classes from decorators.css
907
+ - Combined: \`css('_flex _col') + ' carbon-card'\`
908
+
909
+ ### Routing
910
+
911
+ Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing strategy:
912
+ - \`"hash"\` \u2192 use \`HashRouter\` (e.g., for static hosting, GitHub Pages)
913
+ - \`"history"\` \u2192 use \`BrowserRouter\` (e.g., for server-rendered apps)
914
+
915
+ Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.
916
+
836
917
  ### CSS Architecture
837
918
 
838
919
  The CSS is organized into two parts:
@@ -1065,7 +1146,7 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1065
1146
  }
1066
1147
  writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
1067
1148
  }
1068
- const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry);
1149
+ const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, recipeData);
1069
1150
  contextFiles.push(...refreshResult.contextFiles);
1070
1151
  const gitignoreUpdated = updateGitignore(projectRoot);
1071
1152
  return {
@@ -1267,7 +1348,7 @@ When available, use these tools:
1267
1348
  gitignoreUpdated
1268
1349
  };
1269
1350
  }
1270
- async function refreshDerivedFiles(projectRoot, essence, registry) {
1351
+ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThemeData, prefetchedRecipeData) {
1271
1352
  const decantrDir = join(projectRoot, ".decantr");
1272
1353
  const contextDir = join(decantrDir, "context");
1273
1354
  mkdirSync(contextDir, { recursive: true });
@@ -1281,12 +1362,13 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
1281
1362
  blueprint_enforcement: essence.meta.guard.blueprint_enforcement || "warn"
1282
1363
  };
1283
1364
  const personality = essence.dna.personality || [];
1284
- let themeData;
1285
- let recipeData;
1286
- try {
1365
+ let themeData = prefetchedThemeData;
1366
+ let recipeData = prefetchedRecipeData;
1367
+ if (!themeData) try {
1287
1368
  const themeResult = await registry.fetchTheme(themeName);
1288
1369
  if (themeResult?.data) {
1289
- const t = themeResult.data;
1370
+ const raw = themeResult.data;
1371
+ const t = raw.data ?? raw;
1290
1372
  themeData = {
1291
1373
  seed: t.seed,
1292
1374
  palette: t.palette,
@@ -1298,18 +1380,66 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
1298
1380
  }
1299
1381
  } catch {
1300
1382
  }
1301
- try {
1383
+ if (!recipeData) try {
1302
1384
  const recipeResult = await registry.fetchRecipe(recipeName);
1303
1385
  if (recipeResult?.data) {
1304
- const r = recipeResult.data;
1386
+ const raw = recipeResult.data;
1387
+ const r = raw.data ?? raw;
1305
1388
  recipeData = {
1306
1389
  decorators: r.decorators,
1307
1390
  spatial_hints: r.spatial_hints,
1308
1391
  radius_hints: r.radius_hints
1309
1392
  };
1393
+ if (!recipeData.decorators && raw.data) {
1394
+ const inner = raw.data;
1395
+ if (inner.decorators) {
1396
+ recipeData.decorators = inner.decorators;
1397
+ recipeData.spatial_hints = inner.spatial_hints;
1398
+ recipeData.radius_hints = inner.radius_hints;
1399
+ }
1400
+ }
1310
1401
  }
1311
1402
  } catch {
1312
1403
  }
1404
+ if (!recipeData?.decorators || Object.keys(recipeData.decorators).length === 0) {
1405
+ try {
1406
+ const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
1407
+ const resp = await fetch(`${apiUrl}/recipes/@official/${recipeName}`);
1408
+ if (resp.ok) {
1409
+ const apiData = await resp.json();
1410
+ const inner = apiData.data ?? apiData;
1411
+ if (inner.decorators && Object.keys(inner.decorators).length > 0) {
1412
+ recipeData = {
1413
+ decorators: inner.decorators,
1414
+ spatial_hints: inner.spatial_hints,
1415
+ radius_hints: inner.radius_hints
1416
+ };
1417
+ }
1418
+ }
1419
+ } catch {
1420
+ }
1421
+ }
1422
+ if (!themeData?.seed?.primary) {
1423
+ try {
1424
+ const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
1425
+ const resp = await fetch(`${apiUrl}/themes/@official/${themeName}`);
1426
+ if (resp.ok) {
1427
+ const apiData = await resp.json();
1428
+ const inner = apiData.data ?? apiData;
1429
+ if (inner.seed) {
1430
+ themeData = {
1431
+ seed: inner.seed,
1432
+ palette: inner.palette,
1433
+ cvd_support: inner.cvd_support,
1434
+ tokens: inner.tokens,
1435
+ typography_hints: inner.typography_hints,
1436
+ motion_hints: inner.motion_hints
1437
+ };
1438
+ }
1439
+ }
1440
+ } catch {
1441
+ }
1442
+ }
1313
1443
  const stylesDir = join(projectRoot, "src", "styles");
1314
1444
  mkdirSync(stylesDir, { recursive: true });
1315
1445
  const tokensPath = join(stylesDir, "tokens.css");
@@ -1323,14 +1453,22 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
1323
1453
  writeFileSync(decoratorsPath, generateDecoratorsCSS(recipeData, themeName));
1324
1454
  }
1325
1455
  const cssFiles = [tokensPath, decoratorsPath];
1456
+ const decoratorsMdPath = join(contextDir, "decorators.md");
1457
+ writeFileSync(decoratorsMdPath, generateDecoratorsContext(recipeData, recipeName));
1326
1458
  const decantrMdPath = join(projectRoot, "DECANTR.md");
1327
1459
  writeFileSync(decantrMdPath, generateDecantrMdV31(guardMode, CSS_APPROACH_CONTENT));
1328
1460
  const summaryPath = join(contextDir, "essence-summary.md");
1329
1461
  writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
1330
- const contextFiles = [summaryPath];
1462
+ const contextFiles = [decoratorsMdPath, summaryPath];
1331
1463
  const blueprint = essence.blueprint;
1332
1464
  const sections = blueprint.sections && blueprint.sections.length > 0 ? blueprint.sections : [];
1333
1465
  if (sections.length > 0) {
1466
+ const primarySectionShell = sections.find((s) => s.role === "primary")?.shell || "sidebar-main";
1467
+ for (const section of sections) {
1468
+ if (section.shell === "inherit") {
1469
+ section.shell = primarySectionShell;
1470
+ }
1471
+ }
1334
1472
  const patternSpecs = {};
1335
1473
  const seenPatterns = /* @__PURE__ */ new Set();
1336
1474
  for (const section of sections) {
@@ -1347,13 +1485,29 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
1347
1485
  const inner = raw.data ?? raw;
1348
1486
  const defaultPreset = inner.default_preset || "standard";
1349
1487
  const preset = inner.presets?.[defaultPreset];
1488
+ let slots = preset?.layout?.slots || {};
1489
+ if (Object.keys(slots).length === 0) {
1490
+ const synthetic = generateSyntheticSlots(name, inner.description || "");
1491
+ if (Object.keys(synthetic).length > 0) {
1492
+ slots = synthetic;
1493
+ }
1494
+ }
1350
1495
  patternSpecs[name] = {
1351
1496
  description: inner.description || "",
1352
1497
  components: inner.components || [],
1353
- slots: preset?.layout?.slots || {}
1498
+ slots
1354
1499
  };
1500
+ } else {
1501
+ const synthetic = generateSyntheticSlots(name, "");
1502
+ if (Object.keys(synthetic).length > 0) {
1503
+ patternSpecs[name] = { description: "", components: [], slots: synthetic };
1504
+ }
1355
1505
  }
1356
1506
  } catch {
1507
+ const synthetic = generateSyntheticSlots(name, "");
1508
+ if (Object.keys(synthetic).length > 0) {
1509
+ patternSpecs[name] = { description: "", components: [], slots: synthetic };
1510
+ }
1357
1511
  }
1358
1512
  }
1359
1513
  }
@@ -1423,8 +1577,17 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
1423
1577
  }
1424
1578
  }
1425
1579
  for (const section of sections) {
1426
- const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role.charAt(0).toUpperCase() + section.role.slice(1);
1427
- const zoneContext = `This section is in the **${zoneLabel}** zone (${section.shell} shell).` + (topologyMarkdown ? "\n\n" + topologyMarkdown : "");
1580
+ const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role === "gateway" ? "Gateway" : "Public";
1581
+ let zoneContext = `**Zone:** ${zoneLabel} (${section.role}) \u2014 ${section.shell} shell`;
1582
+ if (section.role === "gateway") {
1583
+ zoneContext += "\nAuth success \u2192 enters App zone. Sign out returns here.";
1584
+ } else if (section.role === "primary") {
1585
+ zoneContext += "\nAuthenticated users land here. Sign out \u2192 Gateway (/login).";
1586
+ } else if (section.role === "public") {
1587
+ zoneContext += "\nAnonymous visitors. CTAs lead to Gateway (/login, /register).";
1588
+ } else if (section.role === "auxiliary") {
1589
+ zoneContext += "\nSupporting section within App zone. Shares navigation with primary.";
1590
+ }
1428
1591
  const sectionPatterns = {};
1429
1592
  for (const page of section.pages) {
1430
1593
  for (const item of page.layout) {
@@ -1496,13 +1659,29 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
1496
1659
  const inner = raw.data ?? raw;
1497
1660
  const defaultPreset = inner.default_preset || "standard";
1498
1661
  const preset = inner.presets?.[defaultPreset];
1662
+ let slots = preset?.layout?.slots || {};
1663
+ if (Object.keys(slots).length === 0) {
1664
+ const synthetic = generateSyntheticSlots(name, inner.description || "");
1665
+ if (Object.keys(synthetic).length > 0) {
1666
+ slots = synthetic;
1667
+ }
1668
+ }
1499
1669
  patternSpecs[name] = {
1500
1670
  description: inner.description || "",
1501
1671
  components: inner.components || [],
1502
- slots: preset?.layout?.slots || {}
1672
+ slots
1503
1673
  };
1674
+ } else {
1675
+ const synthetic = generateSyntheticSlots(name, "");
1676
+ if (Object.keys(synthetic).length > 0) {
1677
+ patternSpecs[name] = { description: "", components: [], slots: synthetic };
1678
+ }
1504
1679
  }
1505
1680
  } catch {
1681
+ const synthetic = generateSyntheticSlots(name, "");
1682
+ if (Object.keys(synthetic).length > 0) {
1683
+ patternSpecs[name] = { description: "", components: [], slots: synthetic };
1684
+ }
1506
1685
  }
1507
1686
  }
1508
1687
  }
@@ -1565,8 +1744,74 @@ async function refreshDerivedFiles(projectRoot, essence, registry) {
1565
1744
  cssFiles
1566
1745
  };
1567
1746
  }
1747
+ function generateSyntheticSlots(patternId, description) {
1748
+ const desc = description.toLowerCase();
1749
+ const syntheticSlots = {};
1750
+ if (patternId.includes("feature") || desc.includes("feature")) {
1751
+ syntheticSlots["grid"] = "Grid of feature cards (icon + title + description)";
1752
+ syntheticSlots["feature-card"] = "Individual feature with icon, heading, and description text";
1753
+ }
1754
+ if (patternId.includes("pricing") || desc.includes("pricing")) {
1755
+ syntheticSlots["tiers"] = "Pricing tier cards (name, price, features list, CTA button)";
1756
+ syntheticSlots["toggle"] = "Monthly/annual billing toggle (optional)";
1757
+ }
1758
+ if (patternId.includes("testimonial") || desc.includes("testimonial")) {
1759
+ syntheticSlots["quotes"] = "Testimonial cards (quote text, author name, role, avatar)";
1760
+ }
1761
+ if (patternId.includes("cta") || desc.includes("call-to-action") || desc.includes("call to action")) {
1762
+ syntheticSlots["headline"] = "CTA headline text";
1763
+ syntheticSlots["description"] = "Supporting description text";
1764
+ syntheticSlots["actions"] = "CTA button(s)";
1765
+ }
1766
+ if (patternId.includes("how-it-works") || desc.includes("how it works") || desc.includes("timeline") || desc.includes("steps")) {
1767
+ syntheticSlots["steps"] = "Numbered steps (step number, title, description)";
1768
+ }
1769
+ if (patternId.includes("team") || desc.includes("team")) {
1770
+ syntheticSlots["members"] = "Team member cards (avatar, name, role)";
1771
+ }
1772
+ if (patternId.includes("story") || desc.includes("story") || desc.includes("about")) {
1773
+ syntheticSlots["content"] = "Story/about narrative text content";
1774
+ }
1775
+ if (patternId.includes("values") || desc.includes("values")) {
1776
+ syntheticSlots["values"] = "Value cards (icon/emoji, title, description)";
1777
+ }
1778
+ if (patternId.includes("form") || desc.includes("form") || desc.includes("contact")) {
1779
+ syntheticSlots["fields"] = "Form fields (name, email, message, etc.)";
1780
+ syntheticSlots["submit"] = "Submit button";
1781
+ }
1782
+ if (patternId.includes("content") || desc.includes("legal") || desc.includes("privacy") || desc.includes("policy")) {
1783
+ syntheticSlots["body"] = "Long-form text content with headings and paragraphs";
1784
+ syntheticSlots["toc"] = "Table of contents sidebar (optional)";
1785
+ }
1786
+ if (patternId.includes("settings") || desc.includes("settings") || desc.includes("preferences")) {
1787
+ syntheticSlots["sections"] = "Settings sections (label, description, input/toggle)";
1788
+ }
1789
+ if (patternId.includes("security") || desc.includes("security") || desc.includes("password")) {
1790
+ syntheticSlots["sections"] = "Security sections (password change, MFA toggle, session list)";
1791
+ }
1792
+ if (patternId.includes("session") || desc.includes("session")) {
1793
+ syntheticSlots["list"] = "Active sessions list (device, location, last active, revoke button)";
1794
+ }
1795
+ if (patternId.includes("message") || desc.includes("message") || desc.includes("chat")) {
1796
+ syntheticSlots["messages"] = "Message bubbles (user/assistant, content, timestamp)";
1797
+ }
1798
+ if (patternId.includes("input") && desc.includes("chat")) {
1799
+ syntheticSlots["textarea"] = "Auto-expanding message input";
1800
+ syntheticSlots["actions"] = "Attach file button, send button";
1801
+ }
1802
+ if (patternId.includes("empty") || desc.includes("empty")) {
1803
+ syntheticSlots["illustration"] = "Empty state illustration or icon";
1804
+ syntheticSlots["message"] = "Welcome/empty state message";
1805
+ syntheticSlots["suggestions"] = "Suggested actions or prompts";
1806
+ }
1807
+ if (patternId.includes("header") && desc.includes("chat")) {
1808
+ syntheticSlots["title"] = "Conversation title or model name";
1809
+ syntheticSlots["actions"] = "Header action buttons (new chat, settings)";
1810
+ }
1811
+ return syntheticSlots;
1812
+ }
1568
1813
  function generateSectionContext(input) {
1569
- const { section, themeTokens, decorators, guardConfig, personality, themeName, recipeName, zoneContext, patternSpecs, recipeHints, constraints, shellInfo } = input;
1814
+ const { section, decorators, guardConfig, personality, themeName, recipeName, zoneContext, patternSpecs, recipeHints, constraints, shellInfo } = input;
1570
1815
  const lines = [];
1571
1816
  lines.push(`# Section: ${section.id}`);
1572
1817
  lines.push("");
@@ -1587,40 +1832,15 @@ function generateSectionContext(input) {
1587
1832
  lines.push("");
1588
1833
  lines.push("---");
1589
1834
  lines.push("");
1590
- lines.push("## Guard Rules");
1591
- lines.push("");
1592
- lines.push(`| Rule | Scope | Severity | Description |`);
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("---");
1835
+ lines.push(`**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`);
1613
1836
  lines.push("");
1614
- lines.push(`## Decorators (${recipeName} recipe)`);
1837
+ lines.push(`**Theme tokens:** see \`src/styles/tokens.css\` \u2014 use \`var(--d-primary)\`, \`var(--d-bg)\`, etc.`);
1615
1838
  lines.push("");
1616
1839
  if (decorators.length > 0) {
1617
- lines.push("| Decorator | Description |");
1618
- lines.push("|-----------|-------------|");
1619
- for (const dec of decorators) {
1620
- lines.push(`| ${dec.name} | ${dec.description} |`);
1621
- }
1840
+ const names = decorators.map((d) => d.name).join(", ");
1841
+ lines.push(`**Decorators:** see \`src/styles/decorators.css\` \u2014 available classes: ${names}`);
1622
1842
  } else {
1623
- lines.push("No decorators defined.");
1843
+ lines.push("**Decorators:** none defined.");
1624
1844
  }
1625
1845
  lines.push("");
1626
1846
  if (recipeHints) {
@@ -1638,11 +1858,8 @@ function generateSectionContext(input) {
1638
1858
  lines.push("---");
1639
1859
  lines.push("");
1640
1860
  if (zoneContext) {
1641
- lines.push("## Zone Context");
1642
- lines.push("");
1643
1861
  lines.push(zoneContext);
1644
- lines.push("");
1645
- lines.push("---");
1862
+ lines.push("For full app topology, see `.decantr/context/scaffold.md`");
1646
1863
  lines.push("");
1647
1864
  }
1648
1865
  if (section.features.length > 0) {
@@ -1925,12 +2142,22 @@ var RegistryClient = class {
1925
2142
  if (customResult) return customResult;
1926
2143
  if (id.startsWith("custom:")) return null;
1927
2144
  if (!this.offline) {
1928
- try {
1929
- const data = await this.apiClient.getContent(contentType, namespace, id);
1930
- saveToCache(this.cacheDir, contentType, id, data, namespace);
1931
- return { data, source: { type: "api", url: this.apiUrl } };
1932
- } catch {
2145
+ for (let attempt = 0; attempt < 2; attempt++) {
2146
+ try {
2147
+ const data = await this.apiClient.getContent(contentType, namespace, id);
2148
+ saveToCache(this.cacheDir, contentType, id, data, namespace);
2149
+ return { data, source: { type: "api", url: this.apiUrl } };
2150
+ } catch (e) {
2151
+ if (process.env.DECANTR_DEBUG) {
2152
+ console.error(` [debug] API fetch ${attempt === 0 ? "failed" : "retry failed"} for ${contentType}/${namespace}/${id}: ${e.message}`);
2153
+ }
2154
+ if (attempt === 0) {
2155
+ await new Promise((r) => setTimeout(r, 500));
2156
+ }
2157
+ }
1933
2158
  }
2159
+ } else if (process.env.DECANTR_DEBUG) {
2160
+ console.error(` [debug] Skipping API (offline mode) for ${contentType}/${namespace}/${id}`);
1934
2161
  }
1935
2162
  return loadFromCache(this.cacheDir, contentType, id, namespace);
1936
2163
  }
@@ -1992,9 +2219,12 @@ async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
1992
2219
  const result = await apiClient.listContent(type, { namespace: "@official" });
1993
2220
  saveToCache(cacheDir, type, null, result, "@official");
1994
2221
  for (const item of result.items) {
1995
- const id = item.id || item.slug;
1996
- if (id) {
1997
- saveToCache(cacheDir, type, id, item, "@official");
2222
+ const slug = item.slug;
2223
+ const data = item.data;
2224
+ const innerSlug = data?.id || data?.slug;
2225
+ const cacheKey = slug || innerSlug || item.id;
2226
+ if (cacheKey) {
2227
+ saveToCache(cacheDir, type, cacheKey, item, "@official");
1998
2228
  }
1999
2229
  }
2000
2230
  synced.push(type);
@@ -9,7 +9,7 @@ import {
9
9
  scaffoldMinimal,
10
10
  scaffoldProject,
11
11
  syncRegistry
12
- } from "./chunk-ZQ5FTYKG.js";
12
+ } from "./chunk-6K6ZPDT4.js";
13
13
 
14
14
  // src/index.ts
15
15
  import { readFileSync as readFileSync14, existsSync as existsSync19, readdirSync as readdirSync6 } from "fs";
@@ -2578,11 +2578,9 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
2578
2578
  );
2579
2579
  selectedBlueprint = selected || "default";
2580
2580
  }
2581
- const [archetypesResult, blueprintsResult, themesResult] = await Promise.all([
2582
- registryClient.fetchArchetypes(),
2583
- registryClient.fetchBlueprints(),
2584
- registryClient.fetchThemes()
2585
- ]);
2581
+ const archetypesResult = await registryClient.fetchArchetypes();
2582
+ const blueprintsResult = await registryClient.fetchBlueprints();
2583
+ const themesResult = await registryClient.fetchThemes();
2586
2584
  if (archetypesResult.source.type === "api") {
2587
2585
  registrySource = "api";
2588
2586
  }
@@ -2626,15 +2624,14 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
2626
2624
  blueprintRecipeName = blueprint.theme?.recipe;
2627
2625
  if (blueprint.compose && blueprint.compose.length > 0) {
2628
2626
  const entries = blueprint.compose;
2629
- const fetchPromises = entries.map((entry) => {
2627
+ const results = [];
2628
+ for (const entry of entries) {
2630
2629
  const id = typeof entry === "string" ? entry : entry.archetype;
2631
- return registryClient.fetchArchetype(id).then((r) => {
2632
- const raw = r?.data;
2633
- const inner = raw?.data ?? raw;
2634
- return [id, inner];
2635
- });
2636
- });
2637
- const results = await Promise.all(fetchPromises);
2630
+ const r = await registryClient.fetchArchetype(id);
2631
+ const raw = r?.data;
2632
+ const inner = raw?.data ?? raw;
2633
+ results.push([id, inner]);
2634
+ }
2638
2635
  const archetypeMap = new Map(results.map(([id, data]) => [id, data || null]));
2639
2636
  const composed = composeArchetypes(entries, archetypeMap);
2640
2637
  const primaryId = typeof entries[0] === "string" ? entries[0] : entries[0].archetype;
@@ -2677,8 +2674,7 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
2677
2674
  }
2678
2675
  patternSpecs = {};
2679
2676
  if (allPatternIds.size > 0) {
2680
- const pSpecs = patternSpecs;
2681
- const fetches = [...allPatternIds].map(async (pid) => {
2677
+ for (const pid of allPatternIds) {
2682
2678
  try {
2683
2679
  const result2 = await registryClient.fetchPattern(pid);
2684
2680
  if (result2) {
@@ -2686,17 +2682,15 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
2686
2682
  const inner = raw.data ?? raw;
2687
2683
  const defaultPreset = inner.default_preset || "standard";
2688
2684
  const preset = inner.presets?.[defaultPreset];
2689
- pSpecs[pid] = {
2685
+ patternSpecs[pid] = {
2690
2686
  description: inner.description || "",
2691
2687
  components: inner.components || [],
2692
- slots: preset?.layout?.slots || {},
2693
- code: preset?.code?.example || inner.code?.example || ""
2688
+ slots: preset?.layout?.slots || {}
2694
2689
  };
2695
2690
  }
2696
2691
  } catch {
2697
2692
  }
2698
- });
2699
- await Promise.all(fetches);
2693
+ }
2700
2694
  }
2701
2695
  const zoneInputs = [];
2702
2696
  for (const entry of entries) {
@@ -2766,6 +2760,10 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
2766
2760
  }
2767
2761
  const recipeName = blueprintRecipeName || options.theme;
2768
2762
  const recipeResult = await registryClient.fetchRecipe(recipeName);
2763
+ if (process.env.DECANTR_DEBUG && recipeResult) {
2764
+ const dbg = recipeResult.data;
2765
+ console.error(` [debug] recipe source: ${recipeResult.source.type}, keys: ${Object.keys(dbg).join(",")}, has .data: ${"data" in dbg}, has .decorators: ${"decorators" in dbg}, inner.decorators: ${!!dbg.data?.decorators}`);
2766
+ }
2769
2767
  if (recipeResult) {
2770
2768
  const rawRecipe = recipeResult.data;
2771
2769
  const recipe = rawRecipe.data ?? rawRecipe;
@@ -2816,18 +2814,21 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
2816
2814
  console.log(` ${cyan("decantr migrate")} Migrate v2 essence to v3`);
2817
2815
  const essenceContent = readFileSync14(result.essencePath, "utf-8");
2818
2816
  const essence = JSON.parse(essenceContent);
2819
- const validation = validateEssence2(essence);
2820
- if (!validation.valid) {
2821
- console.log(error(`
2817
+ if (essence.version !== "3.1.0") {
2818
+ const validation = validateEssence2(essence);
2819
+ if (!validation.valid) {
2820
+ console.log(error(`
2822
2821
  Validation warnings: ${validation.errors.join(", ")}`));
2822
+ }
2823
2823
  }
2824
2824
  console.log("");
2825
2825
  let promptPages;
2826
2826
  if (isV36(essence)) {
2827
- promptPages = essence.blueprint.pages.map((p) => ({
2827
+ const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap((s) => s.pages.map((p) => ({ ...p, _shell: s.shell }))) : essence.blueprint.pages || [];
2828
+ promptPages = allPages.map((p) => ({
2828
2829
  id: p.id,
2829
- shell: p.shell_override ?? essence.blueprint.shell,
2830
- layout: p.layout.map((item) => typeof item === "string" ? item : extractPatternName(item))
2830
+ shell: p.shell_override ?? p._shell ?? essence.blueprint.shell,
2831
+ layout: (p.layout || []).map((item) => typeof item === "string" ? item : extractPatternName(item))
2831
2832
  }));
2832
2833
  } else {
2833
2834
  promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
@@ -3241,7 +3242,7 @@ async function main() {
3241
3242
  break;
3242
3243
  }
3243
3244
  case "upgrade": {
3244
- const { cmdUpgrade } = await import("./upgrade-EHMDEZGC.js");
3245
+ const { cmdUpgrade } = await import("./upgrade-I2RUTNAT.js");
3245
3246
  const applyFlag = args.includes("--apply");
3246
3247
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
3247
3248
  break;
@@ -3496,5 +3497,6 @@ async function main() {
3496
3497
  }
3497
3498
  main().catch((e) => {
3498
3499
  console.error(error(e.message));
3500
+ if (e.stack) console.error(e.stack);
3499
3501
  process.exitCode = 1;
3500
3502
  });
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import "./chunk-OTVIAUQG.js";
2
- import "./chunk-ZQ5FTYKG.js";
1
+ import "./chunk-SUNMRG3P.js";
2
+ import "./chunk-6K6ZPDT4.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RegistryClient,
3
3
  refreshDerivedFiles
4
- } from "./chunk-ZQ5FTYKG.js";
4
+ } from "./chunk-6K6ZPDT4.js";
5
5
 
6
6
  // src/commands/upgrade.ts
7
7
  import { readFileSync, writeFileSync, existsSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decantr/cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Decantr CLI — search the registry, validate essence files, and access design intelligence from the terminal",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -23,8 +23,8 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@decantr/essence-spec": "1.0.0-beta.7",
27
- "@decantr/registry": "1.0.0-beta.7"
26
+ "@decantr/essence-spec": "1.0.0-beta.8",
27
+ "@decantr/registry": "1.0.0-beta.8"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsup",