@decantr/cli 1.7.4 → 1.7.6

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.
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } fr
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { computeSpatialTokens } from "@decantr/essence-spec";
6
+ import { compileExecutionPackBundle } from "@decantr/core";
6
7
 
7
8
  // src/treatments.ts
8
9
  function generateTreatmentCSS(spatialTokens, treatmentOverrides, themeDecorators, themeName) {
@@ -49,7 +50,7 @@ ${themeBody}
49
50
  ["display", "inline-flex"],
50
51
  ["align-items", "center"],
51
52
  ["gap", "0.5em"],
52
- ["padding", "var(--d-interactive-py) var(--d-interactive-px)"],
53
+ ["padding", "calc(var(--d-interactive-py) * var(--d-density-scale, 1)) var(--d-interactive-px)"],
53
54
  ["border", "1px solid var(--d-border)"],
54
55
  ["border-radius", "var(--d-radius)"],
55
56
  ["background", "transparent"],
@@ -98,7 +99,7 @@ ${themeBody}
98
99
  ["border", "1px solid var(--d-border)"],
99
100
  ["border-radius", "var(--d-radius)"],
100
101
  ["box-shadow", "var(--d-shadow)"],
101
- ["padding", "var(--d-surface-p)"]
102
+ ["padding", "calc(var(--d-surface-p) * var(--d-density-scale, 1))"]
102
103
  ]);
103
104
  emitRule('.d-surface[data-elevation="raised"]', [
104
105
  ["background", "var(--d-surface-raised)"],
@@ -124,7 +125,7 @@ ${themeBody}
124
125
  ["font-size", "0.875rem"]
125
126
  ]);
126
127
  emitRule(".d-data-header", [
127
- ["padding", "var(--d-data-py) var(--d-content-gap)"],
128
+ ["padding", "calc(var(--d-data-py) * var(--d-density-scale, 1)) var(--d-content-gap)"],
128
129
  ["font-weight", "500"],
129
130
  ["color", "var(--d-text-muted)"],
130
131
  ["border-bottom", "1px solid var(--d-border)"],
@@ -140,13 +141,13 @@ ${themeBody}
140
141
  ["background", "var(--d-surface)"]
141
142
  ]);
142
143
  emitRule(".d-data-cell", [
143
- ["padding", "var(--d-data-py) var(--d-content-gap)"],
144
+ ["padding", "calc(var(--d-data-py) * var(--d-density-scale, 1)) var(--d-content-gap)"],
144
145
  ["vertical-align", "middle"]
145
146
  ]);
146
147
  emitRule(".d-control", [
147
148
  ["background", "var(--d-surface)"],
148
149
  ["color", "var(--d-text)"],
149
- ["padding", "var(--d-control-py) 0.75rem"],
150
+ ["padding", "calc(var(--d-control-py) * var(--d-density-scale, 1)) 0.75rem"],
150
151
  ["border-radius", "var(--d-radius)"],
151
152
  ["border", "1px solid var(--d-border)"],
152
153
  ["width", "100%"],
@@ -170,16 +171,21 @@ ${themeBody}
170
171
  ["box-shadow", "0 0 0 3px color-mix(in srgb, var(--d-error) 15%, transparent)"]
171
172
  ]);
172
173
  emitRule(".d-section", [
173
- ["padding", "var(--d-section-py) 0"]
174
+ ["--d-density-scale", "1"],
175
+ ["padding", "calc(var(--d-section-py) * var(--d-density-scale)) 0"]
174
176
  ]);
177
+ lines.push('.d-section[data-density="compact"] {');
178
+ lines.push(" --d-density-scale: 0.65;");
179
+ lines.push("}");
180
+ lines.push("");
181
+ lines.push('.d-section[data-density="spacious"] {');
182
+ lines.push(" --d-density-scale: 1.4;");
183
+ lines.push("}");
184
+ lines.push("");
175
185
  lines.push(".d-section + .d-section {");
176
186
  lines.push(" border-top: 1px solid transparent;");
177
187
  lines.push(" border-image: linear-gradient(to right, transparent, var(--d-border), transparent) 1;");
178
- lines.push(" margin-top: var(--d-gap-2);");
179
- lines.push("}");
180
- lines.push("");
181
- lines.push('.d-section[data-density="compact"] {');
182
- lines.push(" padding: calc(var(--d-section-py) * 0.5) 0;");
188
+ lines.push(" margin-top: calc(var(--d-section-gap) * var(--d-density-scale, 1));");
183
189
  lines.push("}");
184
190
  lines.push("");
185
191
  emitRule(".d-annotation", [
@@ -189,6 +195,7 @@ ${themeBody}
189
195
  ["font-size", "0.75rem"],
190
196
  ["font-weight", "500"],
191
197
  ["padding", "0.125rem 0.5rem"],
198
+ ["margin-top", "calc(var(--d-annotation-mt) * var(--d-density-scale, 1))"],
192
199
  ["border-radius", "var(--d-radius-full)"],
193
200
  ["background", "var(--d-surface)"],
194
201
  ["color", "var(--d-text-muted)"],
@@ -216,7 +223,13 @@ ${themeBody}
216
223
  ["text-transform", "uppercase"],
217
224
  ["letter-spacing", "0.08em"],
218
225
  ["color", "var(--d-text-muted)"],
219
- ["font-family", "var(--d-font-mono, ui-monospace, monospace)"]
226
+ ["font-family", "var(--d-font-mono, ui-monospace, monospace)"],
227
+ ["display", "block"],
228
+ ["margin-bottom", "calc(var(--d-label-mb) * var(--d-density-scale, 1))"]
229
+ ]);
230
+ emitRule(".d-label[data-anchor]", [
231
+ ["padding-left", "var(--d-label-px)"],
232
+ ["border-left", "2px solid var(--d-accent)"]
220
233
  ]);
221
234
  if (themeOverrideRules.length > 0) {
222
235
  lines.push("/* \u2500\u2500 Theme-scoped Treatment Overrides \u2500\u2500 */");
@@ -275,7 +288,62 @@ function generatePersonalityCSS(personality, themeData) {
275
288
  }
276
289
 
277
290
  // src/scaffold.ts
291
+ import { API_CONTENT_TYPES } from "@decantr/registry";
278
292
  var __dirname = dirname(fileURLToPath(import.meta.url));
293
+ function getPlatformMeta(target) {
294
+ const normalized = (target || "react").toLowerCase();
295
+ const routing = normalized === "nextjs" ? "pathname" : "hash";
296
+ return {
297
+ type: "spa",
298
+ routing
299
+ };
300
+ }
301
+ function getLegacyBlueprintId(meta) {
302
+ return meta.blueprint;
303
+ }
304
+ function isRecord(value) {
305
+ return typeof value === "object" && value !== null && !Array.isArray(value);
306
+ }
307
+ function getStringArray(value) {
308
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
309
+ }
310
+ function toPatternReferenceObject(ref) {
311
+ return typeof ref === "string" ? { pattern: ref } : ref;
312
+ }
313
+ function collectPatternIdsFromValue(value, ids) {
314
+ if (typeof value === "string") {
315
+ ids.add(value);
316
+ return;
317
+ }
318
+ if (!value || typeof value !== "object") return;
319
+ if ("pattern" in value && typeof value.pattern === "string") {
320
+ ids.add(value.pattern);
321
+ }
322
+ if ("cols" in value && Array.isArray(value.cols)) {
323
+ for (const col of value.cols) collectPatternIdsFromValue(col, ids);
324
+ }
325
+ }
326
+ function collectPatternIdsFromItems(items) {
327
+ const ids = /* @__PURE__ */ new Set();
328
+ for (const item of items) collectPatternIdsFromValue(item, ids);
329
+ return [...ids];
330
+ }
331
+ function mapRegistryArchetypeToArchetypeData(archetype) {
332
+ return {
333
+ id: archetype.id,
334
+ name: archetype.name,
335
+ role: archetype.role,
336
+ description: archetype.description,
337
+ pages: archetype.pages?.map((page) => ({
338
+ id: page.id,
339
+ shell: page.shell,
340
+ default_layout: page.default_layout?.length ? page.default_layout : ["hero"],
341
+ patterns: page.patterns?.map(toPatternReferenceObject)
342
+ })),
343
+ features: archetype.features,
344
+ seo_hints: archetype.seo_hints
345
+ };
346
+ }
279
347
  function composeArchetypes(composeEntries, archetypeResults) {
280
348
  if (composeEntries.length === 0) {
281
349
  return {
@@ -298,7 +366,7 @@ function composeArchetypes(composeEntries, archetypeResults) {
298
366
  for (const page of data.pages) {
299
367
  allPages.push({
300
368
  id: page.id,
301
- layout: page.default_layout?.length ? page.default_layout : ["hero"],
369
+ layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, page.patterns)),
302
370
  ...page.shell !== defaultShell ? { shell_override: page.shell } : {}
303
371
  });
304
372
  }
@@ -307,7 +375,7 @@ function composeArchetypes(composeEntries, archetypeResults) {
307
375
  for (const page of data.pages) {
308
376
  allPages.push({
309
377
  id: `${prefix}-${page.id}`,
310
- layout: page.default_layout?.length ? page.default_layout : ["hero"],
378
+ layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, page.patterns)),
311
379
  ...page.shell !== defaultShell ? { shell_override: page.shell } : {}
312
380
  });
313
381
  }
@@ -359,7 +427,7 @@ function composeSections(composeEntries, archetypeResults, overrides) {
359
427
  const overriddenPage = overrides?.pages?.[page.id];
360
428
  pages.push({
361
429
  id: page.id,
362
- layout: page.default_layout?.length ? page.default_layout : ["hero"],
430
+ layout: (page.default_layout?.length ? page.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, page.patterns)),
363
431
  ...overriddenPage
364
432
  });
365
433
  }
@@ -521,6 +589,24 @@ function generateTopologySection(data, personality) {
521
589
  return lines.join("\n");
522
590
  }
523
591
  var CLI_VERSION = "1.0.0";
592
+ function mapRegistryThemeToThemeData(theme) {
593
+ return {
594
+ seed: theme.seed,
595
+ palette: theme.palette,
596
+ tokens: theme.tokens,
597
+ cvd_support: theme.cvd_support,
598
+ typography: theme.typography,
599
+ motion: theme.motion,
600
+ decorators: theme.decorators,
601
+ treatments: theme.treatments,
602
+ spatial: theme.spatial,
603
+ radius: theme.radius,
604
+ shell: theme.shell,
605
+ effects: theme.effects,
606
+ compositions: theme.compositions,
607
+ pattern_preferences: theme.pattern_preferences
608
+ };
609
+ }
524
610
  function generateTokensCSS(themeData, mode, spatialTokens) {
525
611
  if (!themeData) {
526
612
  const spatialLines2 = spatialTokens ? "\n" + Object.entries(spatialTokens).map(([k, v]) => ` ${k}: ${v};`).join("\n") : "";
@@ -671,6 +757,26 @@ body {
671
757
  min-height: 100dvh;
672
758
  }
673
759
 
760
+ .skip-link {
761
+ position: absolute;
762
+ top: 0.75rem;
763
+ left: 0.75rem;
764
+ z-index: 100;
765
+ padding: 0.5rem 0.75rem;
766
+ border-radius: var(--d-radius);
767
+ background: var(--d-surface-raised);
768
+ color: var(--d-text);
769
+ text-decoration: none;
770
+ border: 1px solid var(--d-border);
771
+ transform: translateY(-150%);
772
+ transition: transform 0.15s ease;
773
+ }
774
+
775
+ .skip-link:focus,
776
+ .skip-link:focus-visible {
777
+ transform: translateY(0);
778
+ }
779
+
674
780
  img, picture, video, canvas, svg {
675
781
  display: block;
676
782
  max-width: 100%;
@@ -698,6 +804,15 @@ input, button, textarea, select {
698
804
  border-width: 0;
699
805
  }
700
806
 
807
+ @media (prefers-reduced-motion: reduce) {
808
+ *, *::before, *::after {
809
+ animation-duration: 0.01ms !important;
810
+ animation-iteration-count: 1 !important;
811
+ transition-duration: 0.01ms !important;
812
+ scroll-behavior: auto !important;
813
+ }
814
+ }
815
+
701
816
  }
702
817
  `;
703
818
  }
@@ -861,10 +976,7 @@ function buildEssenceV3(options, archetypeData, themeHints) {
861
976
  const meta = {
862
977
  archetype: options.archetype || "custom",
863
978
  target: options.target,
864
- platform: {
865
- type: "spa",
866
- routing: "hash"
867
- },
979
+ platform: getPlatformMeta(options.target),
868
980
  guard: guardModeMap[options.guard] || guardModeMap.guided
869
981
  };
870
982
  return {
@@ -894,6 +1006,14 @@ import './styles/treatments.css'; // Treatments + theme decorators
894
1006
  import './styles/global.css'; // Resets
895
1007
  \`\`\`
896
1008
 
1009
+ ### Runtime Rules
1010
+
1011
+ - Use the real \`@decantr/css\` runtime for atoms. If \`package.json\` does not already depend on \`@decantr/css\`, add it before building.
1012
+ - Do **not** create local atom-runtime substitutes such as \`src/lib/css.js\`, \`src/lib/css.ts\`, or hand-written \`src/styles/atoms.css\` files unless the task explicitly asks for a fallback runtime.
1013
+ - Keep atoms in \`css(...)\`, treatments as semantic classes, and theme decorators as additive classes. Do not blur those roles together.
1014
+ - Use \`d-control\` as the default semantic treatment for inputs, selects, and textareas. Theme decorators such as \`carbon-input\` are additive and should only layer on when the section or theme contract explicitly calls for them.
1015
+ - Use loading decorators such as \`carbon-skeleton\` as optional enhancement on top of a structurally correct loading state \u2014 they do not replace the need for a real loading/skeleton branch.
1016
+
897
1017
  ### Visual Treatments
898
1018
 
899
1019
  Six base treatment classes provide semantic styling. Combine with atoms for layout:
@@ -1032,16 +1152,26 @@ css('hover:_opacity80')
1032
1152
  | Atom | CSS |
1033
1153
  |------|-----|
1034
1154
  | \`_bgprimary\` | \`background:var(--d-primary)\` |
1155
+ | \`_bgaccent\` | \`background:var(--d-accent)\` |
1156
+ | \`_bgsecondary\` | \`background:var(--d-secondary)\` |
1035
1157
  | \`_bgsurface\` | \`background:var(--d-surface)\` |
1036
1158
  | \`_bgsurface0\`-\`_bgsurface2\` | surface elevation layers |
1037
1159
  | \`_bgmuted\` | \`background:var(--d-muted)\` |
1038
1160
  | \`_bgbg\` | \`background:var(--d-bg)\` |
1161
+ | \`_bgtransparent\` | \`background:transparent\` |
1039
1162
  | \`_bgsuccess\`, \`_bgerror\`, \`_bgwarning\`, \`_bginfo\` | status backgrounds |
1040
1163
  | \`_fgprimary\` | \`color:var(--d-primary)\` |
1164
+ | \`_fgaccent\` | \`color:var(--d-accent)\` |
1165
+ | \`_fgsecondary\` | \`color:var(--d-secondary)\` |
1041
1166
  | \`_fgtext\` | \`color:var(--d-text)\` |
1042
1167
  | \`_fgmuted\` | \`color:var(--d-text-muted)\` |
1168
+ | \`_fgwhite\`, \`_fgblack\`, \`_fginherit\` | absolute/inherited text colors |
1043
1169
  | \`_fgsuccess\`, \`_fgerror\`, \`_fgwarning\`, \`_fginfo\` | status text |
1170
+ | \`_bcprimary\` | \`border-color:var(--d-primary)\` |
1171
+ | \`_bcaccent\` | \`border-color:var(--d-accent)\` |
1044
1172
  | \`_bcborder\` | \`border-color:var(--d-border)\` |
1173
+ | \`_bcmuted\` | \`border-color:var(--d-muted)\` |
1174
+ | \`_bctransparent\` | \`border-color:transparent\` |
1045
1175
 
1046
1176
  #### Overflow & Whitespace
1047
1177
  | Atom | CSS |
@@ -1138,9 +1268,16 @@ If the essence defines hotkeys or command_palette, implement as keyboard event l
1138
1268
  Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing strategy:
1139
1269
  - \`"hash"\` \u2192 use \`HashRouter\` (e.g., for static hosting, GitHub Pages)
1140
1270
  - \`"history"\` \u2192 use \`BrowserRouter\` (e.g., for server-rendered apps)
1271
+ - \`"pathname"\` \u2192 use pathname-based routing (e.g., Next.js App Router or React apps using \`BrowserRouter\`)
1141
1272
 
1142
1273
  Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.
1143
1274
 
1275
+ ### SEO Expectations by Platform
1276
+
1277
+ - 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.
1278
+ - Do **not** invent SSR-only per-route metadata systems for a clearly hash-routed scaffold.
1279
+ - 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.
1280
+
1144
1281
  ### Layout Rules
1145
1282
 
1146
1283
  1. **Never nest d-surface inside d-surface.** Inner sections use plain containers with padding atoms.
@@ -1149,6 +1286,13 @@ Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and l
1149
1286
  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.
1150
1287
  5. **Responsive nav rules.** Hamburger menus appear ONLY below the shell collapse breakpoint. Full nav shows above it.
1151
1288
 
1289
+ ### Accessibility Defaults
1290
+
1291
+ - 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>\`.
1292
+ - Pair that skip link with a real main landmark target such as \`<main id="main-content">\`.
1293
+ - Keep keyboard focus visible with \`:focus-visible\` treatments on custom interactive surfaces, not just browser defaults.
1294
+ - 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.
1295
+
1152
1296
  ### Motion Philosophy
1153
1297
 
1154
1298
  Every interaction should feel responsive and polished. Apply motion by default, not as an afterthought:
@@ -1160,6 +1304,8 @@ Every interaction should feel responsive and polished. Apply motion by default,
1160
1304
  - **Scroll reveals:** Sections below the fold should fade-in on scroll intersection (IntersectionObserver, once)
1161
1305
  - **Reduced motion:** Wrap all animations in \`prefers-reduced-motion\` media query \u2014 skip animation, keep state changes instant
1162
1306
 
1307
+ 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.
1308
+
1163
1309
  ### Interactivity Philosophy
1164
1310
 
1165
1311
  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:
@@ -1306,9 +1452,8 @@ function generateTaskContextV3(templateName, essence) {
1306
1452
  const contentGap = essence.dna.spacing?.content_gap || "_gap4";
1307
1453
  const vars = {
1308
1454
  TARGET: essence.meta.target || "react",
1309
- THEME_STYLE: essence.dna.theme.id || essence.dna.theme.style || "",
1455
+ THEME_ID: essence.dna.theme.id || "",
1310
1456
  THEME_MODE: essence.dna.theme.mode,
1311
- THEME_RECIPE: essence.dna.theme.id || essence.dna.theme.style || "",
1312
1457
  DEFAULT_SHELL: defaultShell,
1313
1458
  GUARD_MODE: essence.meta.guard.mode,
1314
1459
  LAYOUT: layout,
@@ -1318,6 +1463,186 @@ function generateTaskContextV3(templateName, essence) {
1318
1463
  };
1319
1464
  return renderTemplate(template, vars);
1320
1465
  }
1466
+ function renderPackReferenceList(title, entries, fallback, summaryPath = ".decantr/context/pack-manifest.json") {
1467
+ if (entries.length === 0) {
1468
+ return `### ${title}
1469
+
1470
+ - ${fallback}`;
1471
+ }
1472
+ if (entries.length <= 6) {
1473
+ return `### ${title}
1474
+
1475
+ ${entries.map((entry) => `- ${entry}`).join("\n")}`;
1476
+ }
1477
+ return `### ${title}
1478
+
1479
+ - ${entries.length} compiled references available. Use \`${summaryPath}\` to resolve the exact files for this scope.`;
1480
+ }
1481
+ function generateScaffoldTaskContext(essence, scaffoldPack, manifest) {
1482
+ if (!scaffoldPack) {
1483
+ return generateTaskContextV3("task-scaffold.md.template", essence);
1484
+ }
1485
+ const themeShape = scaffoldPack.data.theme.shape || "default";
1486
+ const features = scaffoldPack.data.features.length > 0 ? scaffoldPack.data.features.join(", ") : "none";
1487
+ const routePlan = scaffoldPack.data.routes.length > 0 ? scaffoldPack.data.routes.map((route) => {
1488
+ const patternSummary = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
1489
+ return `- \`${route.path}\` -> \`${route.pageId}\` [${patternSummary}]`;
1490
+ }).join("\n") : "- No routes declared";
1491
+ const successChecks = scaffoldPack.successChecks.map((check) => `- [${check.severity}] ${check.label}`).join("\n");
1492
+ const tokenStrategy = scaffoldPack.tokenBudget.strategy.map((item) => `- ${item}`).join("\n");
1493
+ const sectionRefs = manifest?.sections.map(
1494
+ (section) => `Section \`${section.id}\` -> \`.decantr/context/${section.markdown}\``
1495
+ ) ?? [];
1496
+ const pageRefs = manifest?.pages.map(
1497
+ (page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``
1498
+ ) ?? [];
1499
+ return `# Task Context: Scaffolding
1500
+
1501
+ **Enforcement Tier: Creative** \u2014 Guard rules are advisory during initial scaffolding.
1502
+
1503
+ ## Primary Compiled Contract
1504
+
1505
+ - Start with \`.decantr/context/scaffold-pack.md\` for the compact route, shell, and theme contract.
1506
+ - Use \`.decantr/context/scaffold.md\` only as secondary detail when the compiled pack is not enough.
1507
+ - Read the route-local page packs before building each page so layout and wiring stay aligned with the compiled plan.
1508
+
1509
+ ## Generate This Application
1510
+
1511
+ - Target: \`${scaffoldPack.target.adapter}\` (${scaffoldPack.target.framework || "unknown framework"})
1512
+ - Shell: \`${scaffoldPack.data.shell}\`
1513
+ - Theme: \`${scaffoldPack.data.theme.id}\` (${scaffoldPack.data.theme.mode}, ${themeShape})
1514
+ - Routing: \`${scaffoldPack.data.routing}\`
1515
+ - Features: ${features}
1516
+
1517
+ ## Route Plan
1518
+
1519
+ ${routePlan}
1520
+
1521
+ ${renderPackReferenceList("Section Packs", sectionRefs, "No section packs were generated for this scaffold.")}
1522
+
1523
+ ${renderPackReferenceList("Page Packs", pageRefs, "No page packs were generated for this scaffold.")}
1524
+
1525
+ ## Success Checks
1526
+
1527
+ ${successChecks}
1528
+
1529
+ ## Token Budget
1530
+
1531
+ - Target: ${scaffoldPack.tokenBudget.target} tokens
1532
+ - Max: ${scaffoldPack.tokenBudget.max} tokens
1533
+ ${tokenStrategy}
1534
+
1535
+ Post-scaffold enforcement mode: **${essence.meta.guard.mode.toUpperCase()}**.
1536
+
1537
+ ---
1538
+
1539
+ *Task context generated from Decantr execution packs*`;
1540
+ }
1541
+ function generateAddPageTaskContext(essence, scaffoldPack, manifest) {
1542
+ if (!scaffoldPack) {
1543
+ return generateTaskContextV3("task-add-page.md.template", essence);
1544
+ }
1545
+ const routePlan = scaffoldPack.data.routes.length > 0 ? scaffoldPack.data.routes.map((route) => {
1546
+ const patternSummary = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
1547
+ return `- \`${route.path}\` -> \`${route.pageId}\` [${patternSummary}]`;
1548
+ }).join("\n") : "- No routes declared";
1549
+ const sectionRefs = manifest?.sections.map(
1550
+ (section) => `Section \`${section.id}\` -> \`.decantr/context/${section.markdown}\``
1551
+ ) ?? [];
1552
+ const pageRefs = manifest?.pages.map(
1553
+ (page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``
1554
+ ) ?? [];
1555
+ return `# Task Context: Adding Pages
1556
+
1557
+ **Enforcement Tier: Guided**
1558
+
1559
+ ## Primary Compiled Contract
1560
+
1561
+ - Start with \`.decantr/context/mutation-add-page-pack.md\` for the add-page workflow contract.
1562
+ - Use \`.decantr/context/scaffold-pack.md\` for the current route, shell, and theme contract.
1563
+ - Use \`.decantr/context/pack-manifest.json\` to choose the target section before you add a route.
1564
+ - After updating the essence, run \`npx @decantr/cli refresh\` so the new section/page packs exist before code generation.
1565
+
1566
+ ## Current Scaffold Contract
1567
+
1568
+ - Target: \`${scaffoldPack.target.adapter}\` (${scaffoldPack.target.framework || "unknown framework"})
1569
+ - Shell: \`${scaffoldPack.data.shell}\`
1570
+ - Theme: \`${scaffoldPack.data.theme.id}\` (${scaffoldPack.data.theme.mode})
1571
+ - Existing routes: ${scaffoldPack.data.routes.length}
1572
+
1573
+ ## Existing Routes
1574
+
1575
+ ${routePlan}
1576
+
1577
+ ${renderPackReferenceList("Section Packs", sectionRefs, "No section packs were generated for this scaffold.")}
1578
+
1579
+ ${renderPackReferenceList("Page Packs", pageRefs, "No page packs were generated for this scaffold.")}
1580
+
1581
+ ## Required Workflow
1582
+
1583
+ 1. Add the new page to the essence before generating any code.
1584
+ 2. Keep the new page inside a declared section and shell contract.
1585
+ 3. Refresh derived files so Decantr recompiles the section and page packs.
1586
+ 4. Read the relevant section pack and the new page pack before implementation.
1587
+
1588
+ ## Guided Checks
1589
+
1590
+ - [error] Theme identity remains \`${scaffoldPack.data.theme.id}\` until the essence changes.
1591
+ - [error] The new page exists in the essence before code generation begins.
1592
+ - [error] New layouts only use registry-backed patterns.
1593
+ - [warn] New routes should fit the current shell and section topology instead of creating off-contract filler pages.
1594
+
1595
+ ---
1596
+
1597
+ *Task context generated from Decantr execution packs*`;
1598
+ }
1599
+ function generateModifyTaskContext(essence, scaffoldPack, manifest) {
1600
+ if (!scaffoldPack) {
1601
+ return generateTaskContextV3("task-modify.md.template", essence);
1602
+ }
1603
+ const routePlan = scaffoldPack.data.routes.length > 0 ? scaffoldPack.data.routes.map((route) => {
1604
+ const patternSummary = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
1605
+ return `- \`${route.path}\` -> \`${route.pageId}\` [${patternSummary}]`;
1606
+ }).join("\n") : "- No routes declared";
1607
+ const pageRefs = manifest?.pages.map(
1608
+ (page) => `Page \`${page.id}\` -> \`.decantr/context/${page.markdown}\``
1609
+ ) ?? [];
1610
+ const successChecks = scaffoldPack.successChecks.map((check) => `- [${check.severity}] ${check.label}`).join("\n");
1611
+ return `# Task Context: Modifying Code
1612
+
1613
+ **Enforcement Tier: Strict**
1614
+
1615
+ ## Primary Compiled Contract
1616
+
1617
+ - Start with \`.decantr/context/mutation-modify-pack.md\` for the strict modification workflow contract.
1618
+ - Start with \`decantr_get_page_context\` or the matching \`.decantr/context/page-*-pack.md\` file for the route you are editing.
1619
+ - Use \`decantr_get_section_context\` when you need the richer section contract behind that route.
1620
+ - If a change would alter route identity, shell identity, theme identity, or pattern contract, update the essence first and then refresh the packs.
1621
+
1622
+ ## Current Route Topology
1623
+
1624
+ ${routePlan}
1625
+
1626
+ ${renderPackReferenceList("Page Packs", pageRefs, "No page packs were generated for this scaffold.")}
1627
+
1628
+ ## Strict Workflow
1629
+
1630
+ 1. Identify the target page and read its compiled page pack first.
1631
+ 2. Compare the planned edit against the compiled route, shell, and pattern contract.
1632
+ 3. If the edit changes that contract, stop and update the essence before writing code.
1633
+ 4. Run \`npx @decantr/cli validate\` and \`npx @decantr/cli check\` after the modification.
1634
+
1635
+ ## Strict Checks
1636
+
1637
+ ${successChecks}
1638
+ - [error] The page you modify must already exist in the compiled topology.
1639
+ - [error] Pattern order and shell usage should stay aligned with the page pack unless the essence changes first.
1640
+ - [warn] Use section context only as supporting detail; the page pack is the primary contract for route-local work.
1641
+
1642
+ ---
1643
+
1644
+ *Task context generated from Decantr execution packs*`;
1645
+ }
1321
1646
  function generateEssenceSummaryV3(essence) {
1322
1647
  const template = loadTemplate("essence-summary.md.template");
1323
1648
  const blueprint = essence.blueprint;
@@ -1345,9 +1670,8 @@ ${rows.join("\n")}`;
1345
1670
  BLUEPRINT: "",
1346
1671
  PERSONALITY: (essence.dna.personality || []).join(", "),
1347
1672
  TARGET: essence.meta.target ?? "",
1348
- THEME_STYLE: essence.dna.theme.id ?? essence.dna.theme.style ?? "",
1673
+ THEME_ID: essence.dna.theme.id ?? "",
1349
1674
  THEME_MODE: essence.dna.theme.mode,
1350
- THEME_RECIPE: essence.dna.theme.id ?? essence.dna.theme.style ?? "",
1351
1675
  SHAPE: essence.dna.theme.shape ?? "",
1352
1676
  PAGES_TABLE: pagesTable,
1353
1677
  FEATURES_LIST: featuresList,
@@ -1399,9 +1723,6 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1399
1723
  }
1400
1724
  writeFileSync(projectJsonPath, JSON.stringify(projectJsonObj, null, 2));
1401
1725
  const contextFiles = [];
1402
- const scaffoldPath = join(contextDir, "task-scaffold.md");
1403
- writeFileSync(scaffoldPath, generateTaskContextV3("task-scaffold.md.template", essenceV3));
1404
- contextFiles.push(scaffoldPath);
1405
1726
  if (composedSections) {
1406
1727
  essenceV3.version = "3.1.0";
1407
1728
  essenceV3.blueprint = {
@@ -1438,7 +1759,7 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1438
1759
  function scaffoldMinimal(projectRoot) {
1439
1760
  const decantrDir = join(projectRoot, ".decantr");
1440
1761
  const customDir = join(decantrDir, "custom");
1441
- const contentTypes = ["patterns", "themes", "blueprints", "archetypes", "shells"];
1762
+ const contentTypes = API_CONTENT_TYPES;
1442
1763
  for (const type of contentTypes) {
1443
1764
  mkdirSync(join(customDir, type), { recursive: true });
1444
1765
  }
@@ -1496,10 +1817,7 @@ function scaffoldMinimal(projectRoot) {
1496
1817
  meta: {
1497
1818
  archetype: "custom",
1498
1819
  target: "react",
1499
- platform: {
1500
- type: "spa",
1501
- routing: "hash"
1502
- },
1820
+ platform: getPlatformMeta("react"),
1503
1821
  guard: {
1504
1822
  mode: "guided",
1505
1823
  dna_enforcement: "error",
@@ -1623,6 +1941,76 @@ When available, use these tools:
1623
1941
  gitignoreUpdated
1624
1942
  };
1625
1943
  }
1944
+ function writeExecutionPackArtifacts(basePathWithoutExtension, pack) {
1945
+ const markdownPath = `${basePathWithoutExtension}.md`;
1946
+ const jsonPath = `${basePathWithoutExtension}.json`;
1947
+ writeFileSync(markdownPath, pack.renderedMarkdown);
1948
+ writeFileSync(jsonPath, JSON.stringify(pack, null, 2) + "\n");
1949
+ return markdownPath;
1950
+ }
1951
+ function writeExecutionPackBundleArtifacts(contextDir, bundle) {
1952
+ mkdirSync(contextDir, { recursive: true });
1953
+ const outputPaths = [];
1954
+ const scaffoldPackPath = writeExecutionPackArtifacts(join(contextDir, "scaffold-pack"), bundle.scaffold);
1955
+ outputPaths.push(scaffoldPackPath);
1956
+ const reviewPackPath = writeExecutionPackArtifacts(join(contextDir, "review-pack"), bundle.review);
1957
+ outputPaths.push(reviewPackPath);
1958
+ for (const sectionPack of bundle.sections) {
1959
+ const sectionPackPath = writeExecutionPackArtifacts(
1960
+ join(contextDir, `section-${sectionPack.data.sectionId}-pack`),
1961
+ sectionPack
1962
+ );
1963
+ outputPaths.push(sectionPackPath);
1964
+ }
1965
+ for (const pagePack of bundle.pages) {
1966
+ const pagePackPath = writeExecutionPackArtifacts(
1967
+ join(contextDir, `page-${pagePack.data.pageId}-pack`),
1968
+ pagePack
1969
+ );
1970
+ outputPaths.push(pagePackPath);
1971
+ }
1972
+ for (const mutationPack of bundle.mutations) {
1973
+ const mutationPackPath = writeExecutionPackArtifacts(
1974
+ join(contextDir, `mutation-${mutationPack.data.mutationType}-pack`),
1975
+ mutationPack
1976
+ );
1977
+ outputPaths.push(mutationPackPath);
1978
+ }
1979
+ const manifestPath = join(contextDir, "pack-manifest.json");
1980
+ writeFileSync(manifestPath, JSON.stringify(bundle.manifest, null, 2) + "\n");
1981
+ outputPaths.push(manifestPath);
1982
+ return {
1983
+ paths: outputPaths,
1984
+ scaffoldPackPath,
1985
+ reviewPackPath,
1986
+ manifestPath
1987
+ };
1988
+ }
1989
+ async function generatePackContexts(projectRoot, contextDir, essence) {
1990
+ const emptyResult = {
1991
+ paths: [],
1992
+ scaffoldPack: null,
1993
+ manifest: null
1994
+ };
1995
+ const cacheRoot = join(projectRoot, ".decantr", "cache", "@official");
1996
+ if (!existsSync(cacheRoot)) return emptyResult;
1997
+ const customRoot = join(projectRoot, ".decantr", "custom");
1998
+ const overridePaths = existsSync(customRoot) ? [customRoot] : void 0;
1999
+ try {
2000
+ const bundle = await compileExecutionPackBundle(essence, {
2001
+ contentRoot: cacheRoot,
2002
+ overridePaths
2003
+ });
2004
+ const writtenArtifacts = writeExecutionPackBundleArtifacts(contextDir, bundle);
2005
+ return {
2006
+ paths: writtenArtifacts.paths,
2007
+ scaffoldPack: bundle.scaffold,
2008
+ manifest: bundle.manifest
2009
+ };
2010
+ } catch {
2011
+ return emptyResult;
2012
+ }
2013
+ }
1626
2014
  async function resolvePatternSpec(name, registry, prefetched, includeExtendedFields = true) {
1627
2015
  if (prefetched && (prefetched.layout_hints || prefetched.visual_brief || !includeExtendedFields)) {
1628
2016
  if (!prefetched.components || prefetched.components.length === 0) {
@@ -1638,32 +2026,7 @@ async function resolvePatternSpec(name, registry, prefetched, includeExtendedFie
1638
2026
  try {
1639
2027
  const patResult = await registry.fetchPattern(name);
1640
2028
  if (patResult?.data) {
1641
- const inner = patResult.data;
1642
- const defaultPreset = inner.default_preset || "standard";
1643
- const preset = inner.presets?.[defaultPreset];
1644
- let slots = preset?.layout?.slots || {};
1645
- if (Object.keys(slots).length === 0) {
1646
- const synthetic = generateSyntheticSlots(name, inner.description || "");
1647
- if (Object.keys(synthetic).length > 0) slots = synthetic;
1648
- }
1649
- const spec = {
1650
- description: inner.description || prefetched?.description || "",
1651
- components: inner.components || prefetched?.components || [],
1652
- slots,
1653
- layout_hints: inner.layout_hints,
1654
- ...includeExtendedFields ? {
1655
- visual_brief: inner.visual_brief,
1656
- composition: inner.composition,
1657
- motion: inner.motion,
1658
- responsive: inner.responsive,
1659
- accessibility: inner.accessibility
1660
- } : {}
1661
- };
1662
- if (!spec.components || spec.components.length === 0) {
1663
- const syntheticComps2 = generateSyntheticComponents(name, spec.description);
1664
- if (syntheticComps2.length > 0) spec.components = syntheticComps2;
1665
- }
1666
- return spec;
2029
+ return mapRegistryPatternToPatternSpecSummary(patResult.data, prefetched, includeExtendedFields);
1667
2030
  }
1668
2031
  } catch {
1669
2032
  }
@@ -1705,17 +2068,16 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1705
2068
  try {
1706
2069
  const bpResult = await registry.fetchBlueprint(storedBlueprintId);
1707
2070
  if (bpResult?.data) {
1708
- const bpData = bpResult.data;
1709
- if (bpData.voice) {
1710
- storedVoice = bpData.voice;
1711
- projectJsonData.voice = bpData.voice;
2071
+ if (bpResult.data.voice) {
2072
+ storedVoice = bpResult.data.voice;
2073
+ projectJsonData.voice = bpResult.data.voice;
1712
2074
  writeFileSync(projectJsonFilePath, JSON.stringify(projectJsonData, null, 2));
1713
2075
  }
1714
2076
  }
1715
2077
  } catch {
1716
2078
  }
1717
2079
  }
1718
- const themeName = essence.dna.theme.id || essence.dna.theme.style || "default";
2080
+ const themeName = essence.dna.theme.id || "default";
1719
2081
  const mode = essence.dna.theme.mode;
1720
2082
  const guardMode = essence.meta.guard.mode;
1721
2083
  const guardConfig = {
@@ -1728,29 +2090,13 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1728
2090
  if (!themeData) try {
1729
2091
  const themeResult = await registry.fetchTheme(themeName);
1730
2092
  if (themeResult?.data) {
1731
- const t = themeResult.data;
1732
- themeData = {
1733
- seed: t.seed,
1734
- palette: t.palette,
1735
- cvd_support: t.cvd_support,
1736
- tokens: t.tokens,
1737
- typography: t.typography,
1738
- motion: t.motion,
1739
- decorators: t.decorators,
1740
- treatments: t.treatments,
1741
- spatial: t.spatial,
1742
- radius: t.radius,
1743
- shell: t.shell,
1744
- effects: t.effects,
1745
- compositions: t.compositions,
1746
- pattern_preferences: t.pattern_preferences
1747
- };
2093
+ themeData = mapRegistryThemeToThemeData(themeResult.data);
1748
2094
  }
1749
2095
  } catch {
1750
2096
  }
1751
2097
  if (!themeData?.seed?.primary) {
1752
2098
  try {
1753
- const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
2099
+ const apiUrl = registry.getApiUrl();
1754
2100
  const resp = await fetch(`${apiUrl}/themes/@official/${themeName}`);
1755
2101
  if (resp.ok) {
1756
2102
  const apiData = await resp.json();
@@ -1832,7 +2178,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1832
2178
  writeFileSync(decantrMdPath, generateDecantrMdV31({
1833
2179
  guardMode,
1834
2180
  cssApproach: CSS_APPROACH_CONTENT,
1835
- blueprintId: storedBlueprintId || essence.meta.blueprint || void 0,
2181
+ blueprintId: storedBlueprintId || getLegacyBlueprintId(essence.meta) || void 0,
1836
2182
  themeName,
1837
2183
  themeMode: mode,
1838
2184
  themeShape: essence.dna.theme.shape || void 0,
@@ -1849,15 +2195,25 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1849
2195
  writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
1850
2196
  contextFiles.push(summaryPath);
1851
2197
  }
2198
+ const packContexts = await generatePackContexts(projectRoot, contextDir, essence);
2199
+ const scaffoldTaskPath = join(contextDir, "task-scaffold.md");
2200
+ writeFileSync(
2201
+ scaffoldTaskPath,
2202
+ generateScaffoldTaskContext(essence, packContexts.scaffoldPack, packContexts.manifest)
2203
+ );
2204
+ contextFiles.push(scaffoldTaskPath);
1852
2205
  if (!options?.isInitialScaffold) {
1853
- const scaffoldTaskPath = join(contextDir, "task-scaffold.md");
1854
- writeFileSync(scaffoldTaskPath, generateTaskContextV3("task-scaffold.md.template", essence));
1855
- contextFiles.push(scaffoldTaskPath);
1856
2206
  const addPagePath = join(contextDir, "task-add-page.md");
1857
- writeFileSync(addPagePath, generateTaskContextV3("task-add-page.md.template", essence));
2207
+ writeFileSync(
2208
+ addPagePath,
2209
+ generateAddPageTaskContext(essence, packContexts.scaffoldPack, packContexts.manifest)
2210
+ );
1858
2211
  contextFiles.push(addPagePath);
1859
2212
  const modifyPath = join(contextDir, "task-modify.md");
1860
- writeFileSync(modifyPath, generateTaskContextV3("task-modify.md.template", essence));
2213
+ writeFileSync(
2214
+ modifyPath,
2215
+ generateModifyTaskContext(essence, packContexts.scaffoldPack, packContexts.manifest)
2216
+ );
1861
2217
  contextFiles.push(modifyPath);
1862
2218
  }
1863
2219
  const blueprint = essence.blueprint;
@@ -1923,16 +2279,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1923
2279
  try {
1924
2280
  const shellResult = await registry.fetchShell(shellId);
1925
2281
  if (shellResult?.data) {
1926
- const inner = shellResult.data;
1927
- shellInfoCache[shellId] = {
1928
- description: inner.description || "",
1929
- regions: inner.config?.regions || [],
1930
- layout: inner.layout || void 0,
1931
- guidance: inner.guidance || void 0,
1932
- atoms: inner.atoms || void 0,
1933
- config: inner.config || void 0,
1934
- internal_layout: inner.internal_layout || void 0
1935
- };
2282
+ shellInfoCache[shellId] = mapRegistryShellToShellInfo(shellResult.data);
1936
2283
  }
1937
2284
  } catch {
1938
2285
  }
@@ -1961,6 +2308,12 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1961
2308
  }
1962
2309
  }
1963
2310
  }
2311
+ const sectionSpatialHints = themeData?.spatial ? {
2312
+ section_padding: themeData.spatial.section_padding ?? void 0,
2313
+ density_bias: typeof themeData.spatial.density_bias === "number" ? themeData.spatial.density_bias : void 0,
2314
+ content_gap_shift: themeData.spatial.content_gap_shift,
2315
+ label_content_gap: themeData.spatial.label_content_gap ?? void 0
2316
+ } : void 0;
1964
2317
  const contextContent = generateSectionContext({
1965
2318
  section,
1966
2319
  themeTokens: themeTokensCss,
@@ -1979,7 +2332,8 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1979
2332
  shellInfo: shellInfoCache[section.shell],
1980
2333
  themeData,
1981
2334
  themeMode: mode,
1982
- voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0
2335
+ voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0,
2336
+ spatialHints: sectionSpatialHints
1983
2337
  });
1984
2338
  const sectionContextPath = join(contextDir, `section-${section.id}.md`);
1985
2339
  writeFileSync(sectionContextPath, contextContent);
@@ -1988,7 +2342,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1988
2342
  const routes = blueprint.routes || {};
1989
2343
  const scaffoldContent = generateScaffoldContext({
1990
2344
  appName: essence.meta.archetype || "Application",
1991
- blueprintId: storedBlueprintId || essence.meta.blueprint || "",
2345
+ blueprintId: storedBlueprintId || getLegacyBlueprintId(essence.meta) || "",
1992
2346
  themeName,
1993
2347
  personality,
1994
2348
  topologyMarkdown,
@@ -2039,19 +2393,16 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2039
2393
  try {
2040
2394
  const shellResult = await registry.fetchShell(shell);
2041
2395
  if (shellResult?.data) {
2042
- const inner = shellResult.data;
2043
- v30ShellInfo = {
2044
- description: inner.description || "",
2045
- regions: inner.config?.regions || [],
2046
- layout: inner.layout || void 0,
2047
- guidance: inner.guidance || void 0,
2048
- atoms: inner.atoms || void 0,
2049
- config: inner.config || void 0,
2050
- internal_layout: inner.internal_layout || void 0
2051
- };
2396
+ v30ShellInfo = mapRegistryShellToShellInfo(shellResult.data);
2052
2397
  }
2053
2398
  } catch {
2054
2399
  }
2400
+ const v30SpatialHints = themeData?.spatial ? {
2401
+ section_padding: themeData.spatial.section_padding ?? void 0,
2402
+ density_bias: typeof themeData.spatial.density_bias === "number" ? themeData.spatial.density_bias : void 0,
2403
+ content_gap_shift: themeData.spatial.content_gap_shift,
2404
+ label_content_gap: themeData.spatial.label_content_gap ?? void 0
2405
+ } : void 0;
2055
2406
  const contextContent = generateSectionContext({
2056
2407
  section: syntheticSection,
2057
2408
  themeTokens: themeTokensCss,
@@ -2070,12 +2421,16 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2070
2421
  shellInfo: v30ShellInfo,
2071
2422
  themeData,
2072
2423
  themeMode: mode,
2073
- voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0
2424
+ voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0,
2425
+ spatialHints: v30SpatialHints
2074
2426
  });
2075
2427
  const sectionContextPath = join(contextDir, `section-${syntheticSection.id}.md`);
2076
2428
  writeFileSync(sectionContextPath, contextContent);
2077
2429
  contextFiles.push(sectionContextPath);
2078
2430
  }
2431
+ if (packContexts.paths.length > 0) {
2432
+ contextFiles.push(...packContexts.paths);
2433
+ }
2079
2434
  return {
2080
2435
  decantrMdPath,
2081
2436
  contextFiles,
@@ -2169,6 +2524,50 @@ function generateSyntheticComponents(patternId, description) {
2169
2524
  if (patternId.includes("empty") || patternId.includes("new")) syntheticComponents.push("Icon", "Text", "Button");
2170
2525
  return [...new Set(syntheticComponents)];
2171
2526
  }
2527
+ function mapRegistryPatternToPatternSpecSummary(pattern, prefetched, includeExtendedFields = true) {
2528
+ const defaultPreset = pattern.default_preset || "standard";
2529
+ const preset = pattern.presets?.[defaultPreset];
2530
+ let slots = preset?.layout?.slots || pattern.default_layout?.slots || prefetched?.slots || {};
2531
+ if (Object.keys(slots).length === 0) {
2532
+ const synthetic = generateSyntheticSlots(pattern.id, pattern.description || prefetched?.description || "");
2533
+ if (Object.keys(synthetic).length > 0) slots = synthetic;
2534
+ }
2535
+ const spec = {
2536
+ description: pattern.description || prefetched?.description || "",
2537
+ components: pattern.components || prefetched?.components || [],
2538
+ slots,
2539
+ layout_hints: pattern.layout_hints,
2540
+ ...includeExtendedFields ? {
2541
+ visual_brief: pattern.visual_brief,
2542
+ composition: pattern.composition,
2543
+ motion: pattern.motion,
2544
+ responsive: pattern.responsive,
2545
+ accessibility: pattern.accessibility ? {
2546
+ role: pattern.accessibility.role,
2547
+ keyboard: pattern.accessibility.keyboard,
2548
+ announcements: pattern.accessibility.announcements,
2549
+ focus_management: pattern.accessibility.focus_management
2550
+ } : void 0
2551
+ } : {}
2552
+ };
2553
+ if (!spec.components || spec.components.length === 0) {
2554
+ const syntheticComps = generateSyntheticComponents(pattern.id, spec.description);
2555
+ if (syntheticComps.length > 0) spec.components = syntheticComps;
2556
+ }
2557
+ return spec;
2558
+ }
2559
+ function mapRegistryShellToShellInfo(shell) {
2560
+ const config = isRecord(shell.config) ? shell.config : void 0;
2561
+ return {
2562
+ description: shell.description || "",
2563
+ regions: getStringArray(config?.regions),
2564
+ layout: shell.layout || void 0,
2565
+ guidance: shell.guidance,
2566
+ atoms: shell.atoms,
2567
+ config,
2568
+ internal_layout: isRecord(shell.internal_layout) ? shell.internal_layout : void 0
2569
+ };
2570
+ }
2172
2571
  function generateShellImplementation(shellId, shellInfo) {
2173
2572
  const lines = [];
2174
2573
  lines.push(`## Shell Implementation (${shellId})`);
@@ -2177,9 +2576,9 @@ function generateShellImplementation(shellId, shellInfo) {
2177
2576
  for (const [region, props] of Object.entries(shellInfo.internal_layout)) {
2178
2577
  lines.push(`### ${region}`);
2179
2578
  lines.push("");
2180
- if (typeof props === "object" && props !== null) {
2579
+ if (isRecord(props)) {
2181
2580
  for (const [key, value] of Object.entries(props)) {
2182
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
2581
+ if (isRecord(value)) {
2183
2582
  lines.push(`- **${key}:**`);
2184
2583
  for (const [subKey, subValue] of Object.entries(value)) {
2185
2584
  lines.push(` - ${subKey}: ${subValue}`);
@@ -2281,10 +2680,10 @@ function generateQuickStart(input) {
2281
2680
  lines.push("");
2282
2681
  return lines;
2283
2682
  }
2284
- function generateSpacingGuide(density) {
2683
+ function generateSpacingGuide(density, spatialHints) {
2285
2684
  const lines = [];
2286
2685
  const level = density === "compact" || density === "spacious" ? density : "comfortable";
2287
- const tokens = computeSpatialTokens(level);
2686
+ const tokens = computeSpatialTokens(level, spatialHints);
2288
2687
  lines.push("## Spacing Guide");
2289
2688
  lines.push("");
2290
2689
  lines.push("| Context | Token | Value | Usage |");
@@ -2296,11 +2695,15 @@ function generateSpacingGuide(density) {
2296
2695
  lines.push(`| Interactive H | \`--d-interactive-px\` | \`${tokens["--d-interactive-px"]}\` | Horizontal padding on buttons |`);
2297
2696
  lines.push(`| Control | \`--d-control-py\` | \`${tokens["--d-control-py"]}\` | Vertical padding on inputs |`);
2298
2697
  lines.push(`| Data row | \`--d-data-py\` | \`${tokens["--d-data-py"]}\` | Vertical padding on table rows |`);
2698
+ lines.push(`| Label gap | \`--d-label-mb\` | \`${tokens["--d-label-mb"]}\` | Gap below d-label section headers |`);
2699
+ lines.push(`| Label indent | \`--d-label-px\` | \`${tokens["--d-label-px"]}\` | Anchor indent for d-label[data-anchor] |`);
2700
+ lines.push(`| Section gap | \`--d-section-gap\` | \`${tokens["--d-section-gap"]}\` | Gap between adjacent d-sections |`);
2701
+ lines.push(`| Annotation gap | \`--d-annotation-mt\` | \`${tokens["--d-annotation-mt"]}\` | Top margin on d-annotation |`);
2299
2702
  lines.push("");
2300
2703
  return lines;
2301
2704
  }
2302
2705
  function generateSectionContext(input) {
2303
- const { section, decorators, guardConfig, personality, themeName, zoneContext, patternSpecs, themeHints, constraints, shellInfo } = input;
2706
+ const { section, decorators, guardConfig, personality, themeName, zoneContext, patternSpecs, themeHints, constraints, shellInfo, spatialHints } = input;
2304
2707
  const lines = [];
2305
2708
  lines.push(`# Section: ${section.id}`);
2306
2709
  lines.push("");
@@ -2320,16 +2723,36 @@ function generateSectionContext(input) {
2320
2723
  lines.push(...generateShellImplementation(section.shell, shellInfo));
2321
2724
  }
2322
2725
  if (shellInfo?.guidance && Object.keys(shellInfo.guidance).length > 0) {
2726
+ const labelTreatment = shellInfo.guidance.section_label_treatment;
2727
+ const sectionDensity = shellInfo.guidance.section_density;
2728
+ if (labelTreatment) {
2729
+ lines.push("## Section Label Treatment");
2730
+ lines.push("");
2731
+ lines.push(`Apply \`${labelTreatment}\` to section headers in this shell.`);
2732
+ lines.push("- Uppercase monospace label typography (d-label base treatment)");
2733
+ if (labelTreatment.includes("[data-anchor]")) {
2734
+ lines.push("- Left accent border anchor (data-anchor variant)");
2735
+ }
2736
+ lines.push("- Density-responsive bottom gap via `--d-label-mb` x `--d-density-scale`");
2737
+ if (sectionDensity) {
2738
+ const scaleMap = { compact: "0.65", comfortable: "1", spacious: "1.4" };
2739
+ lines.push("");
2740
+ lines.push(`Section density: ${sectionDensity} (--d-density-scale: ${scaleMap[sectionDensity] || "1"})`);
2741
+ }
2742
+ lines.push("");
2743
+ }
2744
+ const structuredKeys = /* @__PURE__ */ new Set(["section_label_treatment", "section_density"]);
2323
2745
  lines.push(`## Shell Notes (${section.shell})`);
2324
2746
  lines.push("");
2325
2747
  for (const [key, value] of Object.entries(shellInfo.guidance)) {
2748
+ if (structuredKeys.has(key)) continue;
2326
2749
  const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2327
2750
  lines.push(`- **${label}:** ${value}`);
2328
2751
  }
2329
2752
  lines.push("");
2330
2753
  }
2331
2754
  const density = section.dna_overrides?.density || "comfortable";
2332
- lines.push(...generateSpacingGuide(density));
2755
+ lines.push(...generateSpacingGuide(density, spatialHints));
2333
2756
  lines.push("---");
2334
2757
  lines.push("");
2335
2758
  lines.push(`**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`);
@@ -2415,7 +2838,7 @@ function generateSectionContext(input) {
2415
2838
  }
2416
2839
  if (themeHints) {
2417
2840
  if (themeHints.preferred && themeHints.preferred.length > 0) {
2418
- const sectionPatterns = new Set(section.pages.flatMap((p) => p.layout.map((l) => typeof l === "string" ? l : l.pattern)));
2841
+ const sectionPatterns = new Set(section.pages.flatMap((p) => p.layout.flatMap(extractPatternNames)));
2419
2842
  const relevant = themeHints.preferred.filter((p) => sectionPatterns.has(p));
2420
2843
  if (relevant.length > 0) {
2421
2844
  lines.push(`**Preferred:** ${relevant.join(", ")}`);
@@ -2490,6 +2913,9 @@ function generateSectionContext(input) {
2490
2913
  if (uniquePatterns.size > 0) {
2491
2914
  lines.push("## Pattern Reference");
2492
2915
  lines.push("");
2916
+ lines.push("Scaffold-tier rule: implement the core visual structure, states, and required slots first.");
2917
+ 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.");
2918
+ lines.push("");
2493
2919
  for (const [patternName, spec] of uniquePatterns) {
2494
2920
  lines.push(`### ${patternName}`);
2495
2921
  lines.push("");
@@ -2698,9 +3124,9 @@ function generateScaffoldContext(input) {
2698
3124
  // src/registry.ts
2699
3125
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync } from "fs";
2700
3126
  import { join as join2 } from "path";
2701
- import { RegistryAPIClient } from "@decantr/registry";
3127
+ import { RegistryAPIClient, API_CONTENT_TYPES as API_CONTENT_TYPES2 } from "@decantr/registry";
2702
3128
  var DEFAULT_API_URL = "https://api.decantr.ai/v1";
2703
- var ALL_CONTENT_TYPES = ["themes", "patterns", "blueprints", "archetypes", "shells"];
3129
+ var ALL_CONTENT_TYPES = API_CONTENT_TYPES2;
2704
3130
  function loadFromCache(cacheDir, contentType, id, namespace) {
2705
3131
  const nsDir = namespace ? join2(cacheDir, namespace) : cacheDir;
2706
3132
  const cachePath = id ? join2(nsDir, contentType, `${id}.json`) : join2(nsDir, contentType, "index.json");
@@ -2727,13 +3153,16 @@ var RegistryClient = class {
2727
3153
  constructor(options = {}) {
2728
3154
  this.projectRoot = options.projectRoot || process.cwd();
2729
3155
  this.cacheDir = options.cacheDir || join2(this.projectRoot, ".decantr", "cache");
2730
- this.apiUrl = options.apiUrl || DEFAULT_API_URL;
3156
+ this.apiUrl = options.apiUrl || process.env.DECANTR_API_URL || DEFAULT_API_URL;
2731
3157
  this.offline = options.offline || false;
2732
3158
  this.apiClient = new RegistryAPIClient({
2733
3159
  baseUrl: this.apiUrl,
2734
- apiKey: options.apiKey
3160
+ apiKey: options.apiKey || process.env.DECANTR_API_KEY || void 0
2735
3161
  });
2736
3162
  }
3163
+ getApiUrl() {
3164
+ return this.apiUrl;
3165
+ }
2737
3166
  /**
2738
3167
  * Load content from .decantr/custom/{contentType}/{id}.json
2739
3168
  * Works for ALL content types, not just themes.
@@ -2773,12 +3202,17 @@ var RegistryClient = class {
2773
3202
  * Unified fetch for a content list.
2774
3203
  * Resolution: API -> Cache. Custom items are merged into the list.
2775
3204
  */
2776
- async fetchContentList(contentType, namespace) {
3205
+ async fetchContentList(contentType, namespace, sort, recommended, intelligenceSource) {
2777
3206
  let apiItems = [];
2778
3207
  let source = { type: "cache" };
2779
3208
  if (!this.offline) {
2780
3209
  try {
2781
- const apiResult = await this.apiClient.listContent(contentType, { namespace });
3210
+ const apiResult = await this.apiClient.listContent(contentType, {
3211
+ namespace,
3212
+ sort,
3213
+ recommended,
3214
+ intelligenceSource
3215
+ });
2782
3216
  apiItems = apiResult.items;
2783
3217
  source = { type: "api", url: this.apiUrl };
2784
3218
  saveToCache(this.cacheDir, contentType, null, apiResult, namespace || "@official");
@@ -2902,14 +3336,19 @@ async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
2902
3336
  }
2903
3337
 
2904
3338
  export {
3339
+ collectPatternIdsFromItems,
3340
+ mapRegistryArchetypeToArchetypeData,
2905
3341
  composeArchetypes,
2906
3342
  composeSections,
2907
3343
  deriveZones,
2908
3344
  deriveTransitions,
2909
3345
  generateTopologySection,
3346
+ mapRegistryThemeToThemeData,
2910
3347
  scaffoldProject,
2911
3348
  scaffoldMinimal,
3349
+ writeExecutionPackBundleArtifacts,
2912
3350
  refreshDerivedFiles,
3351
+ mapRegistryPatternToPatternSpecSummary,
2913
3352
  RegistryClient,
2914
3353
  syncRegistry
2915
3354
  };