@decantr/cli 1.7.1 → 1.7.5

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,16 +1268,54 @@ 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.
1147
1284
  2. **Shell regions are frames, not surfaces.** Sidebar and header use var(--d-surface) or var(--d-bg) directly. Apply d-surface only to content cards within the body region.
1148
1285
  3. **One scroll container per region.** Body has overflow-y-auto. Sidebar nav has its own overflow-y-auto. Never nest additional scrollable wrappers.
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
- 5. **Responsive nav rules.** Hamburger menus appear ONLY below the shell collapse breakpoint. Full nav shows above it.`;
1287
+ 5. **Responsive nav rules.** Hamburger menus appear ONLY below the shell collapse breakpoint. Full nav shows above it.
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
+
1296
+ ### Motion Philosophy
1297
+
1298
+ Every interaction should feel responsive and polished. Apply motion by default, not as an afterthought:
1299
+
1300
+ - **Page transitions:** Apply entrance-fade (or the personality entrance animation) to the main content area on route change
1301
+ - **Stagger children:** Lists, grids, and card groups should stagger-animate on mount (50-100ms delay per item)
1302
+ - **Data visualization:** Charts, gauges, progress bars, and counters should animate to their values on mount \u2014 never render static
1303
+ - **Micro-interactions:** All interactive elements (buttons, toggles, cards, nav items) need hover/press transitions. Use the motion tokens (--d-duration-hover, --d-easing) for consistency.
1304
+ - **Scroll reveals:** Sections below the fold should fade-in on scroll intersection (IntersectionObserver, once)
1305
+ - **Reduced motion:** Wrap all animations in \`prefers-reduced-motion\` media query \u2014 skip animation, keep state changes instant
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
+
1309
+ ### Interactivity Philosophy
1310
+
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:
1312
+
1313
+ - **Drag and drop:** Nodes, cards, and items on spatial canvases should be draggable. Use pointer events with proper grab/grabbing cursors.
1314
+ - **Pan and zoom:** Canvases and large visualizations should support pan (click-drag on background) and zoom (scroll wheel or pinch). Show zoom level indicator.
1315
+ - **Connections:** When nodes exist in a graph/topology view, they should have visible connection lines. Implement click-to-select + click-target for connecting nodes.
1316
+ - **Live state:** Data-driven visualizations should update in real-time with simulated data. Status changes should animate (color transitions, pulse effects).
1317
+ - **Direct manipulation:** Prefer drag-to-reorder over dropdown menus. Prefer inline editing over modal forms. Prefer resize handles over fixed layouts.
1318
+ - **Hover reveals:** Show contextual information (tooltips, expanded cards, action menus) on hover \u2014 don't require clicks to discover functionality.`;
1151
1319
  function generateDecantrMdV31(params) {
1152
1320
  const template = loadTemplate("DECANTR.md.template");
1153
1321
  const body = renderTemplate(template, {
@@ -1191,6 +1359,29 @@ function generateDecantrMdV31(params) {
1191
1359
  }
1192
1360
  briefLines.push("");
1193
1361
  }
1362
+ briefLines.push("## Development Workflow");
1363
+ briefLines.push("");
1364
+ briefLines.push("The essence file (`decantr.essence.json`) is the source of truth for your project's structure. Context files in `.decantr/context/` are derived from it. When you need to add, remove, or modify pages, sections, or features:");
1365
+ briefLines.push("");
1366
+ briefLines.push("**1. Update the essence** (use CLI commands for consistency):");
1367
+ briefLines.push("- `decantr add page {section}/{page} --route /{path}`");
1368
+ briefLines.push("- `decantr add section {archetype}`");
1369
+ briefLines.push("- `decantr add feature {name}` (or `--section {id}` for scoped)");
1370
+ briefLines.push("- `decantr remove page {section}/{page}`");
1371
+ briefLines.push("- `decantr remove section {id}`");
1372
+ briefLines.push("- `decantr remove feature {name}`");
1373
+ briefLines.push("- `decantr theme switch {name}`");
1374
+ briefLines.push("");
1375
+ briefLines.push("**2. Regenerate context:** `decantr refresh`");
1376
+ briefLines.push("");
1377
+ briefLines.push("**3. Read the updated context files**, then build.");
1378
+ briefLines.push("");
1379
+ briefLines.push("**Rules:**");
1380
+ briefLines.push("- Never create page components for routes that don't exist in the essence");
1381
+ briefLines.push("- Never delete pages without removing them from the essence");
1382
+ briefLines.push("- Always refresh after mutations \u2014 stale context files lead to drift");
1383
+ briefLines.push("- If you edit the essence directly, run `decantr refresh` before building");
1384
+ briefLines.push("");
1194
1385
  briefLines.push("---");
1195
1386
  briefLines.push("");
1196
1387
  return briefLines.join("\n") + body;
@@ -1261,9 +1452,8 @@ function generateTaskContextV3(templateName, essence) {
1261
1452
  const contentGap = essence.dna.spacing?.content_gap || "_gap4";
1262
1453
  const vars = {
1263
1454
  TARGET: essence.meta.target || "react",
1264
- THEME_STYLE: essence.dna.theme.id || essence.dna.theme.style || "",
1455
+ THEME_ID: essence.dna.theme.id || "",
1265
1456
  THEME_MODE: essence.dna.theme.mode,
1266
- THEME_RECIPE: essence.dna.theme.id || essence.dna.theme.style || "",
1267
1457
  DEFAULT_SHELL: defaultShell,
1268
1458
  GUARD_MODE: essence.meta.guard.mode,
1269
1459
  LAYOUT: layout,
@@ -1273,6 +1463,186 @@ function generateTaskContextV3(templateName, essence) {
1273
1463
  };
1274
1464
  return renderTemplate(template, vars);
1275
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
+ }
1276
1646
  function generateEssenceSummaryV3(essence) {
1277
1647
  const template = loadTemplate("essence-summary.md.template");
1278
1648
  const blueprint = essence.blueprint;
@@ -1300,9 +1670,8 @@ ${rows.join("\n")}`;
1300
1670
  BLUEPRINT: "",
1301
1671
  PERSONALITY: (essence.dna.personality || []).join(", "),
1302
1672
  TARGET: essence.meta.target ?? "",
1303
- THEME_STYLE: essence.dna.theme.id ?? essence.dna.theme.style ?? "",
1673
+ THEME_ID: essence.dna.theme.id ?? "",
1304
1674
  THEME_MODE: essence.dna.theme.mode,
1305
- THEME_RECIPE: essence.dna.theme.id ?? essence.dna.theme.style ?? "",
1306
1675
  SHAPE: essence.dna.theme.shape ?? "",
1307
1676
  PAGES_TABLE: pagesTable,
1308
1677
  FEATURES_LIST: featuresList,
@@ -1354,9 +1723,6 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1354
1723
  }
1355
1724
  writeFileSync(projectJsonPath, JSON.stringify(projectJsonObj, null, 2));
1356
1725
  const contextFiles = [];
1357
- const scaffoldPath = join(contextDir, "task-scaffold.md");
1358
- writeFileSync(scaffoldPath, generateTaskContextV3("task-scaffold.md.template", essenceV3));
1359
- contextFiles.push(scaffoldPath);
1360
1726
  if (composedSections) {
1361
1727
  essenceV3.version = "3.1.0";
1362
1728
  essenceV3.blueprint = {
@@ -1393,7 +1759,7 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
1393
1759
  function scaffoldMinimal(projectRoot) {
1394
1760
  const decantrDir = join(projectRoot, ".decantr");
1395
1761
  const customDir = join(decantrDir, "custom");
1396
- const contentTypes = ["patterns", "themes", "blueprints", "archetypes", "shells"];
1762
+ const contentTypes = API_CONTENT_TYPES;
1397
1763
  for (const type of contentTypes) {
1398
1764
  mkdirSync(join(customDir, type), { recursive: true });
1399
1765
  }
@@ -1451,10 +1817,7 @@ function scaffoldMinimal(projectRoot) {
1451
1817
  meta: {
1452
1818
  archetype: "custom",
1453
1819
  target: "react",
1454
- platform: {
1455
- type: "spa",
1456
- routing: "hash"
1457
- },
1820
+ platform: getPlatformMeta("react"),
1458
1821
  guard: {
1459
1822
  mode: "guided",
1460
1823
  dna_enforcement: "error",
@@ -1578,6 +1941,76 @@ When available, use these tools:
1578
1941
  gitignoreUpdated
1579
1942
  };
1580
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
+ }
1581
2014
  async function resolvePatternSpec(name, registry, prefetched, includeExtendedFields = true) {
1582
2015
  if (prefetched && (prefetched.layout_hints || prefetched.visual_brief || !includeExtendedFields)) {
1583
2016
  if (!prefetched.components || prefetched.components.length === 0) {
@@ -1593,32 +2026,7 @@ async function resolvePatternSpec(name, registry, prefetched, includeExtendedFie
1593
2026
  try {
1594
2027
  const patResult = await registry.fetchPattern(name);
1595
2028
  if (patResult?.data) {
1596
- const inner = patResult.data;
1597
- const defaultPreset = inner.default_preset || "standard";
1598
- const preset = inner.presets?.[defaultPreset];
1599
- let slots = preset?.layout?.slots || {};
1600
- if (Object.keys(slots).length === 0) {
1601
- const synthetic = generateSyntheticSlots(name, inner.description || "");
1602
- if (Object.keys(synthetic).length > 0) slots = synthetic;
1603
- }
1604
- const spec = {
1605
- description: inner.description || prefetched?.description || "",
1606
- components: inner.components || prefetched?.components || [],
1607
- slots,
1608
- layout_hints: inner.layout_hints,
1609
- ...includeExtendedFields ? {
1610
- visual_brief: inner.visual_brief,
1611
- composition: inner.composition,
1612
- motion: inner.motion,
1613
- responsive: inner.responsive,
1614
- accessibility: inner.accessibility
1615
- } : {}
1616
- };
1617
- if (!spec.components || spec.components.length === 0) {
1618
- const syntheticComps2 = generateSyntheticComponents(name, spec.description);
1619
- if (syntheticComps2.length > 0) spec.components = syntheticComps2;
1620
- }
1621
- return spec;
2029
+ return mapRegistryPatternToPatternSpecSummary(patResult.data, prefetched, includeExtendedFields);
1622
2030
  }
1623
2031
  } catch {
1624
2032
  }
@@ -1660,17 +2068,16 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1660
2068
  try {
1661
2069
  const bpResult = await registry.fetchBlueprint(storedBlueprintId);
1662
2070
  if (bpResult?.data) {
1663
- const bpData = bpResult.data;
1664
- if (bpData.voice) {
1665
- storedVoice = bpData.voice;
1666
- projectJsonData.voice = bpData.voice;
2071
+ if (bpResult.data.voice) {
2072
+ storedVoice = bpResult.data.voice;
2073
+ projectJsonData.voice = bpResult.data.voice;
1667
2074
  writeFileSync(projectJsonFilePath, JSON.stringify(projectJsonData, null, 2));
1668
2075
  }
1669
2076
  }
1670
2077
  } catch {
1671
2078
  }
1672
2079
  }
1673
- const themeName = essence.dna.theme.id || essence.dna.theme.style || "default";
2080
+ const themeName = essence.dna.theme.id || "default";
1674
2081
  const mode = essence.dna.theme.mode;
1675
2082
  const guardMode = essence.meta.guard.mode;
1676
2083
  const guardConfig = {
@@ -1683,29 +2090,13 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1683
2090
  if (!themeData) try {
1684
2091
  const themeResult = await registry.fetchTheme(themeName);
1685
2092
  if (themeResult?.data) {
1686
- const t = themeResult.data;
1687
- themeData = {
1688
- seed: t.seed,
1689
- palette: t.palette,
1690
- cvd_support: t.cvd_support,
1691
- tokens: t.tokens,
1692
- typography: t.typography,
1693
- motion: t.motion,
1694
- decorators: t.decorators,
1695
- treatments: t.treatments,
1696
- spatial: t.spatial,
1697
- radius: t.radius,
1698
- shell: t.shell,
1699
- effects: t.effects,
1700
- compositions: t.compositions,
1701
- pattern_preferences: t.pattern_preferences
1702
- };
2093
+ themeData = mapRegistryThemeToThemeData(themeResult.data);
1703
2094
  }
1704
2095
  } catch {
1705
2096
  }
1706
2097
  if (!themeData?.seed?.primary) {
1707
2098
  try {
1708
- const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
2099
+ const apiUrl = registry.getApiUrl();
1709
2100
  const resp = await fetch(`${apiUrl}/themes/@official/${themeName}`);
1710
2101
  if (resp.ok) {
1711
2102
  const apiData = await resp.json();
@@ -1787,7 +2178,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1787
2178
  writeFileSync(decantrMdPath, generateDecantrMdV31({
1788
2179
  guardMode,
1789
2180
  cssApproach: CSS_APPROACH_CONTENT,
1790
- blueprintId: storedBlueprintId || essence.meta.blueprint || void 0,
2181
+ blueprintId: storedBlueprintId || getLegacyBlueprintId(essence.meta) || void 0,
1791
2182
  themeName,
1792
2183
  themeMode: mode,
1793
2184
  themeShape: essence.dna.theme.shape || void 0,
@@ -1804,15 +2195,25 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1804
2195
  writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
1805
2196
  contextFiles.push(summaryPath);
1806
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);
1807
2205
  if (!options?.isInitialScaffold) {
1808
- const scaffoldTaskPath = join(contextDir, "task-scaffold.md");
1809
- writeFileSync(scaffoldTaskPath, generateTaskContextV3("task-scaffold.md.template", essence));
1810
- contextFiles.push(scaffoldTaskPath);
1811
2206
  const addPagePath = join(contextDir, "task-add-page.md");
1812
- writeFileSync(addPagePath, generateTaskContextV3("task-add-page.md.template", essence));
2207
+ writeFileSync(
2208
+ addPagePath,
2209
+ generateAddPageTaskContext(essence, packContexts.scaffoldPack, packContexts.manifest)
2210
+ );
1813
2211
  contextFiles.push(addPagePath);
1814
2212
  const modifyPath = join(contextDir, "task-modify.md");
1815
- writeFileSync(modifyPath, generateTaskContextV3("task-modify.md.template", essence));
2213
+ writeFileSync(
2214
+ modifyPath,
2215
+ generateModifyTaskContext(essence, packContexts.scaffoldPack, packContexts.manifest)
2216
+ );
1816
2217
  contextFiles.push(modifyPath);
1817
2218
  }
1818
2219
  const blueprint = essence.blueprint;
@@ -1878,16 +2279,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1878
2279
  try {
1879
2280
  const shellResult = await registry.fetchShell(shellId);
1880
2281
  if (shellResult?.data) {
1881
- const inner = shellResult.data;
1882
- shellInfoCache[shellId] = {
1883
- description: inner.description || "",
1884
- regions: inner.config?.regions || [],
1885
- layout: inner.layout || void 0,
1886
- guidance: inner.guidance || void 0,
1887
- atoms: inner.atoms || void 0,
1888
- config: inner.config || void 0,
1889
- internal_layout: inner.internal_layout || void 0
1890
- };
2282
+ shellInfoCache[shellId] = mapRegistryShellToShellInfo(shellResult.data);
1891
2283
  }
1892
2284
  } catch {
1893
2285
  }
@@ -1916,6 +2308,12 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1916
2308
  }
1917
2309
  }
1918
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;
1919
2317
  const contextContent = generateSectionContext({
1920
2318
  section,
1921
2319
  themeTokens: themeTokensCss,
@@ -1934,7 +2332,8 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1934
2332
  shellInfo: shellInfoCache[section.shell],
1935
2333
  themeData,
1936
2334
  themeMode: mode,
1937
- voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0
2335
+ voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0,
2336
+ spatialHints: sectionSpatialHints
1938
2337
  });
1939
2338
  const sectionContextPath = join(contextDir, `section-${section.id}.md`);
1940
2339
  writeFileSync(sectionContextPath, contextContent);
@@ -1943,7 +2342,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1943
2342
  const routes = blueprint.routes || {};
1944
2343
  const scaffoldContent = generateScaffoldContext({
1945
2344
  appName: essence.meta.archetype || "Application",
1946
- blueprintId: storedBlueprintId || essence.meta.blueprint || "",
2345
+ blueprintId: storedBlueprintId || getLegacyBlueprintId(essence.meta) || "",
1947
2346
  themeName,
1948
2347
  personality,
1949
2348
  topologyMarkdown,
@@ -1994,19 +2393,16 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1994
2393
  try {
1995
2394
  const shellResult = await registry.fetchShell(shell);
1996
2395
  if (shellResult?.data) {
1997
- const inner = shellResult.data;
1998
- v30ShellInfo = {
1999
- description: inner.description || "",
2000
- regions: inner.config?.regions || [],
2001
- layout: inner.layout || void 0,
2002
- guidance: inner.guidance || void 0,
2003
- atoms: inner.atoms || void 0,
2004
- config: inner.config || void 0,
2005
- internal_layout: inner.internal_layout || void 0
2006
- };
2396
+ v30ShellInfo = mapRegistryShellToShellInfo(shellResult.data);
2007
2397
  }
2008
2398
  } catch {
2009
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;
2010
2406
  const contextContent = generateSectionContext({
2011
2407
  section: syntheticSection,
2012
2408
  themeTokens: themeTokensCss,
@@ -2025,12 +2421,16 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2025
2421
  shellInfo: v30ShellInfo,
2026
2422
  themeData,
2027
2423
  themeMode: mode,
2028
- voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0
2424
+ voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0,
2425
+ spatialHints: v30SpatialHints
2029
2426
  });
2030
2427
  const sectionContextPath = join(contextDir, `section-${syntheticSection.id}.md`);
2031
2428
  writeFileSync(sectionContextPath, contextContent);
2032
2429
  contextFiles.push(sectionContextPath);
2033
2430
  }
2431
+ if (packContexts.paths.length > 0) {
2432
+ contextFiles.push(...packContexts.paths);
2433
+ }
2034
2434
  return {
2035
2435
  decantrMdPath,
2036
2436
  contextFiles,
@@ -2124,6 +2524,50 @@ function generateSyntheticComponents(patternId, description) {
2124
2524
  if (patternId.includes("empty") || patternId.includes("new")) syntheticComponents.push("Icon", "Text", "Button");
2125
2525
  return [...new Set(syntheticComponents)];
2126
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
+ }
2127
2571
  function generateShellImplementation(shellId, shellInfo) {
2128
2572
  const lines = [];
2129
2573
  lines.push(`## Shell Implementation (${shellId})`);
@@ -2132,9 +2576,9 @@ function generateShellImplementation(shellId, shellInfo) {
2132
2576
  for (const [region, props] of Object.entries(shellInfo.internal_layout)) {
2133
2577
  lines.push(`### ${region}`);
2134
2578
  lines.push("");
2135
- if (typeof props === "object" && props !== null) {
2579
+ if (isRecord(props)) {
2136
2580
  for (const [key, value] of Object.entries(props)) {
2137
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
2581
+ if (isRecord(value)) {
2138
2582
  lines.push(`- **${key}:**`);
2139
2583
  for (const [subKey, subValue] of Object.entries(value)) {
2140
2584
  lines.push(` - ${subKey}: ${subValue}`);
@@ -2236,10 +2680,10 @@ function generateQuickStart(input) {
2236
2680
  lines.push("");
2237
2681
  return lines;
2238
2682
  }
2239
- function generateSpacingGuide(density) {
2683
+ function generateSpacingGuide(density, spatialHints) {
2240
2684
  const lines = [];
2241
2685
  const level = density === "compact" || density === "spacious" ? density : "comfortable";
2242
- const tokens = computeSpatialTokens(level);
2686
+ const tokens = computeSpatialTokens(level, spatialHints);
2243
2687
  lines.push("## Spacing Guide");
2244
2688
  lines.push("");
2245
2689
  lines.push("| Context | Token | Value | Usage |");
@@ -2251,11 +2695,15 @@ function generateSpacingGuide(density) {
2251
2695
  lines.push(`| Interactive H | \`--d-interactive-px\` | \`${tokens["--d-interactive-px"]}\` | Horizontal padding on buttons |`);
2252
2696
  lines.push(`| Control | \`--d-control-py\` | \`${tokens["--d-control-py"]}\` | Vertical padding on inputs |`);
2253
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 |`);
2254
2702
  lines.push("");
2255
2703
  return lines;
2256
2704
  }
2257
2705
  function generateSectionContext(input) {
2258
- 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;
2259
2707
  const lines = [];
2260
2708
  lines.push(`# Section: ${section.id}`);
2261
2709
  lines.push("");
@@ -2275,16 +2723,36 @@ function generateSectionContext(input) {
2275
2723
  lines.push(...generateShellImplementation(section.shell, shellInfo));
2276
2724
  }
2277
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"]);
2278
2745
  lines.push(`## Shell Notes (${section.shell})`);
2279
2746
  lines.push("");
2280
2747
  for (const [key, value] of Object.entries(shellInfo.guidance)) {
2748
+ if (structuredKeys.has(key)) continue;
2281
2749
  const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2282
2750
  lines.push(`- **${label}:** ${value}`);
2283
2751
  }
2284
2752
  lines.push("");
2285
2753
  }
2286
2754
  const density = section.dna_overrides?.density || "comfortable";
2287
- lines.push(...generateSpacingGuide(density));
2755
+ lines.push(...generateSpacingGuide(density, spatialHints));
2288
2756
  lines.push("---");
2289
2757
  lines.push("");
2290
2758
  lines.push(`**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`);
@@ -2370,7 +2838,7 @@ function generateSectionContext(input) {
2370
2838
  }
2371
2839
  if (themeHints) {
2372
2840
  if (themeHints.preferred && themeHints.preferred.length > 0) {
2373
- 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)));
2374
2842
  const relevant = themeHints.preferred.filter((p) => sectionPatterns.has(p));
2375
2843
  if (relevant.length > 0) {
2376
2844
  lines.push(`**Preferred:** ${relevant.join(", ")}`);
@@ -2445,6 +2913,9 @@ function generateSectionContext(input) {
2445
2913
  if (uniquePatterns.size > 0) {
2446
2914
  lines.push("## Pattern Reference");
2447
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("");
2448
2919
  for (const [patternName, spec] of uniquePatterns) {
2449
2920
  lines.push(`### ${patternName}`);
2450
2921
  lines.push("");
@@ -2557,6 +3028,15 @@ function generateScaffoldContext(input) {
2557
3028
  if (input.voice.metrics_format) lines.push(`**Metrics format:** ${input.voice.metrics_format}`);
2558
3029
  lines.push("");
2559
3030
  }
3031
+ lines.push("## Development Mode");
3032
+ lines.push("");
3033
+ lines.push("For local development and showcases, wire all zone transitions with mock data:");
3034
+ lines.push("");
3035
+ lines.push("- **Auth bypass:** Auth pages should accept any input and redirect to the primary section's default route");
3036
+ lines.push("- **Route guards:** Check a simple localStorage flag (e.g., `decantr_authenticated`). Login sets it \u2192 redirect to app zone entry. Logout clears it \u2192 redirect to public/gateway zone.");
3037
+ lines.push("- **Mock data on every page:** All pages should render with simulated data on first load \u2014 never show empty states during development");
3038
+ lines.push("- **Zone transitions:** CTA links on marketing pages should route to the gateway (login/register). Successful auth should route to the primary section default page.");
3039
+ lines.push("");
2560
3040
  lines.push(topologyMarkdown);
2561
3041
  lines.push("");
2562
3042
  lines.push("## Sections Overview");
@@ -2644,9 +3124,9 @@ function generateScaffoldContext(input) {
2644
3124
  // src/registry.ts
2645
3125
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync } from "fs";
2646
3126
  import { join as join2 } from "path";
2647
- import { RegistryAPIClient } from "@decantr/registry";
3127
+ import { RegistryAPIClient, API_CONTENT_TYPES as API_CONTENT_TYPES2 } from "@decantr/registry";
2648
3128
  var DEFAULT_API_URL = "https://api.decantr.ai/v1";
2649
- var ALL_CONTENT_TYPES = ["themes", "patterns", "blueprints", "archetypes", "shells"];
3129
+ var ALL_CONTENT_TYPES = API_CONTENT_TYPES2;
2650
3130
  function loadFromCache(cacheDir, contentType, id, namespace) {
2651
3131
  const nsDir = namespace ? join2(cacheDir, namespace) : cacheDir;
2652
3132
  const cachePath = id ? join2(nsDir, contentType, `${id}.json`) : join2(nsDir, contentType, "index.json");
@@ -2673,13 +3153,16 @@ var RegistryClient = class {
2673
3153
  constructor(options = {}) {
2674
3154
  this.projectRoot = options.projectRoot || process.cwd();
2675
3155
  this.cacheDir = options.cacheDir || join2(this.projectRoot, ".decantr", "cache");
2676
- this.apiUrl = options.apiUrl || DEFAULT_API_URL;
3156
+ this.apiUrl = options.apiUrl || process.env.DECANTR_API_URL || DEFAULT_API_URL;
2677
3157
  this.offline = options.offline || false;
2678
3158
  this.apiClient = new RegistryAPIClient({
2679
3159
  baseUrl: this.apiUrl,
2680
- apiKey: options.apiKey
3160
+ apiKey: options.apiKey || process.env.DECANTR_API_KEY || void 0
2681
3161
  });
2682
3162
  }
3163
+ getApiUrl() {
3164
+ return this.apiUrl;
3165
+ }
2683
3166
  /**
2684
3167
  * Load content from .decantr/custom/{contentType}/{id}.json
2685
3168
  * Works for ALL content types, not just themes.
@@ -2719,12 +3202,17 @@ var RegistryClient = class {
2719
3202
  * Unified fetch for a content list.
2720
3203
  * Resolution: API -> Cache. Custom items are merged into the list.
2721
3204
  */
2722
- async fetchContentList(contentType, namespace) {
3205
+ async fetchContentList(contentType, namespace, sort, recommended, intelligenceSource) {
2723
3206
  let apiItems = [];
2724
3207
  let source = { type: "cache" };
2725
3208
  if (!this.offline) {
2726
3209
  try {
2727
- 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
+ });
2728
3216
  apiItems = apiResult.items;
2729
3217
  source = { type: "api", url: this.apiUrl };
2730
3218
  saveToCache(this.cacheDir, contentType, null, apiResult, namespace || "@official");
@@ -2848,14 +3336,19 @@ async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
2848
3336
  }
2849
3337
 
2850
3338
  export {
3339
+ collectPatternIdsFromItems,
3340
+ mapRegistryArchetypeToArchetypeData,
2851
3341
  composeArchetypes,
2852
3342
  composeSections,
2853
3343
  deriveZones,
2854
3344
  deriveTransitions,
2855
3345
  generateTopologySection,
3346
+ mapRegistryThemeToThemeData,
2856
3347
  scaffoldProject,
2857
3348
  scaffoldMinimal,
3349
+ writeExecutionPackBundleArtifacts,
2858
3350
  refreshDerivedFiles,
3351
+ mapRegistryPatternToPatternSpecSummary,
2859
3352
  RegistryClient,
2860
3353
  syncRegistry
2861
3354
  };