@decantr/essence-spec 1.0.0-beta.1 → 1.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,29 +1,65 @@
1
1
  // src/types.ts
2
+ function isV3(essence) {
3
+ return (essence.version === "3.0.0" || essence.version === "3.1.0") && "dna" in essence && "blueprint" in essence;
4
+ }
5
+ function flattenPages(blueprint) {
6
+ if (blueprint.sections && blueprint.sections.length > 0) {
7
+ return blueprint.sections.flatMap(
8
+ (s) => s.pages.map((p) => ({
9
+ ...p,
10
+ shell_override: p.shell_override ?? (s.shell !== blueprint.shell ? s.shell : void 0)
11
+ }))
12
+ );
13
+ }
14
+ return blueprint.pages ?? [];
15
+ }
2
16
  function isSectioned(essence) {
17
+ if (isV3(essence)) return false;
3
18
  return "sections" in essence && Array.isArray(essence.sections);
4
19
  }
5
20
  function isSimple(essence) {
21
+ if (isV3(essence)) return false;
6
22
  return "archetype" in essence && !("sections" in essence);
7
23
  }
8
24
 
9
25
  // src/validate.ts
10
26
  import Ajv from "ajv";
11
- import { readFileSync } from "fs";
27
+ import { readFileSync, existsSync } from "fs";
12
28
  import { fileURLToPath } from "url";
13
29
  import { dirname, join } from "path";
14
30
  var __dirname = dirname(fileURLToPath(import.meta.url));
15
- var schemaPath = join(__dirname, "..", "schema", "essence.v2.json");
31
+ var v2SchemaPath = join(__dirname, "..", "schema", "essence.v2.json");
32
+ var v3SchemaPath = join(__dirname, "..", "schema", "essence.v3.json");
16
33
  var cachedAjv = null;
17
- var cachedValidate = null;
18
- function getValidator() {
19
- if (cachedValidate) return cachedValidate;
34
+ var validatorCache = /* @__PURE__ */ new Map();
35
+ function getAjv() {
36
+ if (!cachedAjv) {
37
+ cachedAjv = new Ajv({ allErrors: true, strict: false, validateSchema: false });
38
+ }
39
+ return cachedAjv;
40
+ }
41
+ function getValidator(version) {
42
+ if (validatorCache.has(version)) return validatorCache.get(version);
43
+ const schemaPath = version === "v3" ? v3SchemaPath : v2SchemaPath;
44
+ if (!existsSync(schemaPath)) return null;
20
45
  const schema = JSON.parse(readFileSync(schemaPath, "utf-8"));
21
- cachedAjv = new Ajv({ allErrors: true, strict: false, validateSchema: false });
22
- cachedValidate = cachedAjv.compile(schema);
23
- return cachedValidate;
46
+ const ajv = getAjv();
47
+ const validate = ajv.compile(schema);
48
+ validatorCache.set(version, validate);
49
+ return validate;
50
+ }
51
+ function detectVersion(data) {
52
+ if (typeof data === "object" && data !== null && "version" in data) {
53
+ if (data.version === "3.0.0") return "v3";
54
+ }
55
+ return "v2";
24
56
  }
25
57
  function validateEssence(data) {
26
- const validate = getValidator();
58
+ const version = detectVersion(data);
59
+ const validate = getValidator(version);
60
+ if (!validate) {
61
+ return { valid: false, errors: [`No schema available for ${version} documents`] };
62
+ }
27
63
  const valid = validate(data);
28
64
  if (valid) {
29
65
  return { valid: true, errors: [] };
@@ -49,7 +85,7 @@ var MAX_GAP = 10;
49
85
  function clamp(value, min, max) {
50
86
  return Math.max(min, Math.min(max, value));
51
87
  }
52
- function computeDensity(personality, recipeSpatial) {
88
+ function computeDensity(personality, spatialHints) {
53
89
  const lower = personality.map((c) => c.toLowerCase());
54
90
  let level = "comfortable";
55
91
  let gap = 4;
@@ -60,11 +96,11 @@ function computeDensity(personality, recipeSpatial) {
60
96
  break;
61
97
  }
62
98
  }
63
- if (recipeSpatial?.density_bias) {
64
- gap += recipeSpatial.density_bias;
99
+ if (spatialHints?.density_bias) {
100
+ gap += spatialHints.density_bias;
65
101
  }
66
- if (recipeSpatial?.content_gap_shift) {
67
- gap += recipeSpatial.content_gap_shift;
102
+ if (spatialHints?.content_gap_shift) {
103
+ gap += spatialHints.content_gap_shift;
68
104
  }
69
105
  gap = clamp(gap, MIN_GAP, MAX_GAP);
70
106
  if (gap <= 3) level = "compact";
@@ -72,22 +108,173 @@ function computeDensity(personality, recipeSpatial) {
72
108
  else level = "comfortable";
73
109
  return { level, content_gap: String(gap) };
74
110
  }
111
+ var DENSITY_SCALES = {
112
+ compact: 0.65,
113
+ comfortable: 1,
114
+ spacious: 1.4
115
+ };
116
+ var BASE_TOKENS = {
117
+ "--d-section-py": 5,
118
+ "--d-interactive-py": 0.5,
119
+ "--d-interactive-px": 1,
120
+ "--d-surface-p": 1.25,
121
+ "--d-data-py": 0.625,
122
+ "--d-control-py": 0.5,
123
+ "--d-content-gap": 1
124
+ };
125
+ function roundTo3(value) {
126
+ return Math.round(value * 1e3) / 1e3;
127
+ }
128
+ function toRemString(value) {
129
+ const rounded = roundTo3(value);
130
+ return `${rounded}rem`;
131
+ }
132
+ function computeSpatialTokens(density, spatialHints) {
133
+ const scale = DENSITY_SCALES[density];
134
+ const biasMultiplier = 1 + (spatialHints?.density_bias ?? 0) / 10;
135
+ const result = {};
136
+ for (const [key, base] of Object.entries(BASE_TOKENS)) {
137
+ const tokenKey = key;
138
+ if (tokenKey === "--d-section-py" && spatialHints?.section_padding) {
139
+ const pxMatch = spatialHints.section_padding.match(/^(\d+(?:\.\d+)?)px$/);
140
+ if (pxMatch) {
141
+ const remValue = parseFloat(pxMatch[1]) / 16;
142
+ result[tokenKey] = toRemString(remValue * scale * biasMultiplier);
143
+ continue;
144
+ }
145
+ }
146
+ let computed = base * scale * biasMultiplier;
147
+ if (tokenKey === "--d-content-gap" && spatialHints?.content_gap_shift) {
148
+ computed += spatialHints.content_gap_shift * 0.25;
149
+ }
150
+ result[tokenKey] = toRemString(computed);
151
+ }
152
+ return result;
153
+ }
75
154
 
76
155
  // src/guard.ts
77
- function evaluateGuard(essence, context) {
78
- const guard = essence.guard;
156
+ function checkThemeModeCompatibility(essence, context) {
157
+ if (!context.themeRegistry) return null;
158
+ let themeId;
159
+ let mode;
160
+ if (isV3(essence)) {
161
+ themeId = essence.dna.theme?.id ?? null;
162
+ mode = essence.dna.theme?.mode ?? null;
163
+ } else {
164
+ themeId = isSimple(essence) ? essence.theme?.id : null;
165
+ mode = isSimple(essence) ? essence.theme?.mode : null;
166
+ }
167
+ if (!themeId || !mode) return null;
168
+ const theme = context.themeRegistry.get(themeId);
169
+ if (!theme) {
170
+ return null;
171
+ }
172
+ if (!theme.modes.includes(mode) && mode !== "auto") {
173
+ const supportedModes = theme.modes.join(", ");
174
+ const suggestion = theme.modes.length > 0 ? `Use mode: "${theme.modes[0]}" instead, or choose a theme that supports ${mode} mode.` : void 0;
175
+ return {
176
+ rule: "theme-mode",
177
+ severity: "error",
178
+ message: `Theme "${themeId}" does not support "${mode}" mode. Supported modes: ${supportedModes}.`,
179
+ suggestion,
180
+ ...isV3(essence) ? { layer: "dna", autoFixable: false } : {}
181
+ };
182
+ }
183
+ return null;
184
+ }
185
+ function collectPatternIds(item, ids) {
186
+ if (typeof item === "string") {
187
+ ids.add(item);
188
+ return;
189
+ }
190
+ if (typeof item === "object" && item !== null) {
191
+ if ("pattern" in item && typeof item.pattern === "string") {
192
+ ids.add(item.pattern);
193
+ }
194
+ if ("cols" in item && Array.isArray(item.cols)) {
195
+ for (const col of item.cols) {
196
+ if (typeof col === "string") {
197
+ ids.add(col);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ function findSimilarPatterns(target, registry) {
204
+ const similar = [];
205
+ const targetLower = target.toLowerCase();
206
+ for (const id of registry.keys()) {
207
+ const idLower = id.toLowerCase();
208
+ if (idLower.includes(targetLower) || targetLower.includes(idLower)) {
209
+ similar.push(id);
210
+ }
211
+ }
212
+ return similar.slice(0, 3);
213
+ }
214
+ function checkPatternExistence(essence, context) {
215
+ if (!context.patternRegistry) return [];
216
+ const violations = [];
217
+ const referencedPatterns = /* @__PURE__ */ new Set();
218
+ const pages = getAllPages(essence);
219
+ for (const page of pages) {
220
+ for (const item of page.layout || []) {
221
+ collectPatternIds(item, referencedPatterns);
222
+ }
223
+ }
224
+ for (const patternId of referencedPatterns) {
225
+ if (!context.patternRegistry.has(patternId)) {
226
+ const similar = findSimilarPatterns(patternId, context.patternRegistry);
227
+ const suggestion = similar.length > 0 ? `Similar patterns: ${similar.join(", ")}` : `Run "decantr search ${patternId}" to find alternatives.`;
228
+ violations.push({
229
+ rule: "pattern-exists",
230
+ severity: isV3(essence) ? "warning" : "error",
231
+ message: `Pattern "${patternId}" is referenced but does not exist in the registry.`,
232
+ suggestion,
233
+ ...isV3(essence) ? { layer: "blueprint", autoFixable: false } : {}
234
+ });
235
+ }
236
+ }
237
+ return violations;
238
+ }
239
+ function checkAccessibility(essence, context) {
240
+ const accessibility = isV3(essence) ? essence.dna.accessibility : "accessibility" in essence ? essence.accessibility : void 0;
241
+ if (!accessibility?.wcag_level || accessibility.wcag_level === "none") {
242
+ return null;
243
+ }
244
+ if (context.a11y_issues && context.a11y_issues.length > 0) {
245
+ const issueList = context.a11y_issues.slice(0, 3).join(", ");
246
+ const moreCount = context.a11y_issues.length > 3 ? ` (+${context.a11y_issues.length - 3} more)` : "";
247
+ return {
248
+ rule: "accessibility",
249
+ severity: "error",
250
+ message: `WCAG ${accessibility.wcag_level} compliance required. Issues found: ${issueList}${moreCount}`,
251
+ suggestion: "Fix accessibility issues before proceeding. Run an accessibility audit for details.",
252
+ ...isV3(essence) ? { layer: "dna", autoFixable: false } : {}
253
+ };
254
+ }
255
+ return null;
256
+ }
257
+ function evaluateGuard(essence, context = {}) {
258
+ const guard = isV3(essence) ? essence.meta.guard : essence.guard;
79
259
  if (guard.mode === "creative") {
80
260
  return [];
81
261
  }
82
262
  const violations = [];
83
263
  const isStrict = guard.mode === "strict";
84
264
  if (context.style) {
85
- const essenceStyle = isSimple(essence) ? essence.theme.style : null;
86
- if (essenceStyle && context.style !== essenceStyle && guard.enforce_style !== false) {
265
+ let essenceStyle;
266
+ if (isV3(essence)) {
267
+ essenceStyle = essence.dna.theme.id;
268
+ } else {
269
+ essenceStyle = isSimple(essence) ? essence.theme.id : null;
270
+ }
271
+ const enforceStyle = isV3(essence) ? true : guard.enforce_style !== false;
272
+ if (essenceStyle && context.style !== essenceStyle && enforceStyle) {
87
273
  violations.push({
88
274
  rule: "style",
89
275
  severity: "error",
90
- message: `Style "${context.style}" does not match essence theme "${essenceStyle}". Change the essence theme first.`
276
+ message: `Style "${context.style}" does not match essence theme "${essenceStyle}". Change the essence theme first.`,
277
+ ...isV3(essence) ? { layer: "dna", autoFixable: false } : {}
91
278
  });
92
279
  }
93
280
  }
@@ -97,8 +284,13 @@ function evaluateGuard(essence, context) {
97
284
  if (!pageExists) {
98
285
  violations.push({
99
286
  rule: "structure",
100
- severity: "error",
101
- message: `Page "${context.pageId}" does not exist in essence structure. Add it to the essence first.`
287
+ severity: isV3(essence) ? "warning" : "error",
288
+ message: `Page "${context.pageId}" does not exist in essence structure. Add it to the essence first.`,
289
+ ...isV3(essence) ? {
290
+ layer: "blueprint",
291
+ autoFixable: true,
292
+ autoFix: { type: "add_page", patch: { id: context.pageId } }
293
+ } : {}
102
294
  });
103
295
  }
104
296
  }
@@ -114,41 +306,94 @@ function evaluateGuard(essence, context) {
114
306
  if (!matches) {
115
307
  violations.push({
116
308
  rule: "layout",
117
- severity: "error",
118
- message: `Layout for page "${context.pageId}" deviates from essence. Expected: [${essenceLayout.join(", ")}].`
309
+ severity: isV3(essence) ? "warning" : "error",
310
+ message: `Layout for page "${context.pageId}" deviates from essence. Expected: [${essenceLayout.join(", ")}].`,
311
+ ...isV3(essence) ? {
312
+ layer: "blueprint",
313
+ autoFixable: true,
314
+ autoFix: { type: "update_layout", patch: { page: context.pageId, layout: context.layout } }
315
+ } : {}
119
316
  });
120
317
  }
121
318
  }
122
319
  }
123
- if (context.recipe && guard.enforce_recipe !== false) {
124
- const essenceRecipe = isSimple(essence) ? essence.theme.recipe : null;
125
- if (essenceRecipe && context.recipe !== essenceRecipe) {
126
- violations.push({
127
- rule: "recipe",
128
- severity: "error",
129
- message: `Recipe "${context.recipe}" does not match essence recipe "${essenceRecipe}".`
130
- });
131
- }
132
- }
133
320
  if (isStrict && context.density_gap) {
134
- if (context.density_gap !== essence.density.content_gap) {
321
+ let expectedGap;
322
+ if (isV3(essence)) {
323
+ const overriddenDensity = getPageDensityOverride(essence, context.pageId);
324
+ expectedGap = overriddenDensity ?? essence.dna.spacing.content_gap;
325
+ } else {
326
+ expectedGap = essence.density.content_gap;
327
+ }
328
+ if (context.density_gap !== expectedGap) {
135
329
  violations.push({
136
330
  rule: "density",
137
331
  severity: "warning",
138
- message: `Content gap "${context.density_gap}" does not match essence density "${essence.density.content_gap}".`
332
+ message: `Content gap "${context.density_gap}" does not match essence density "${expectedGap}".`,
333
+ ...isV3(essence) ? { layer: "dna", autoFixable: false } : {}
139
334
  });
140
335
  }
141
336
  }
337
+ const themeModeViolation = checkThemeModeCompatibility(essence, context);
338
+ if (themeModeViolation) {
339
+ violations.push(themeModeViolation);
340
+ }
341
+ const patternViolations = checkPatternExistence(essence, context);
342
+ violations.push(...patternViolations);
343
+ const accessibilityViolation = checkAccessibility(essence, context);
344
+ if (accessibilityViolation) {
345
+ violations.push(accessibilityViolation);
346
+ }
347
+ if (isV3(essence)) {
348
+ const v3Guard = guard;
349
+ if (v3Guard.dna_enforcement === "off") {
350
+ return violations.filter((v) => v.layer !== "dna");
351
+ }
352
+ if (v3Guard.dna_enforcement === "warn") {
353
+ for (const v of violations) {
354
+ if (v.layer === "dna") {
355
+ v.severity = "warning";
356
+ }
357
+ }
358
+ }
359
+ if (v3Guard.blueprint_enforcement === "off") {
360
+ return violations.filter((v) => v.layer !== "blueprint");
361
+ }
362
+ }
142
363
  return violations;
143
364
  }
144
365
  function getAllPages(essence) {
366
+ if (isV3(essence)) {
367
+ const pages = flattenPages(essence.blueprint);
368
+ return pages.map((page) => ({
369
+ id: page.id,
370
+ shell: page.shell_override ?? essence.blueprint.shell ?? "",
371
+ layout: page.layout,
372
+ ...page.surface ? { surface: page.surface } : {}
373
+ }));
374
+ }
145
375
  if (isSimple(essence)) return essence.structure;
146
376
  if (isSectioned(essence)) return essence.sections.flatMap((s) => s.structure);
147
- return [];
377
+ throw new Error("Unknown EssenceFile type");
378
+ }
379
+ function getPageDensityOverride(essence, pageId) {
380
+ if (!pageId) return void 0;
381
+ const pages = flattenPages(essence.blueprint);
382
+ const page = pages.find((p) => p.id === pageId);
383
+ if (!page?.dna_overrides?.density) return void 0;
384
+ const densityGapMap = {
385
+ compact: "2",
386
+ comfortable: "4",
387
+ spacious: "6"
388
+ };
389
+ return densityGapMap[page.dna_overrides.density] ?? void 0;
148
390
  }
149
391
 
150
392
  // src/normalize.ts
151
393
  function normalizeEssence(input) {
394
+ if (input.version === "3.0.0") {
395
+ return input;
396
+ }
152
397
  if (input.theme && input.platform) {
153
398
  return input;
154
399
  }
@@ -167,9 +412,8 @@ function normalizeSimple(v1) {
167
412
  version: "2.0.0",
168
413
  archetype: v1.terroir ?? v1.vignette ?? v1.archetype,
169
414
  theme: {
170
- style: vintage?.style ?? "",
415
+ id: vintage?.style ?? "",
171
416
  mode: vintage?.mode ?? "dark",
172
- recipe: vintage?.recipe ?? "",
173
417
  ...vintage?.shape ? { shape: vintage.shape } : {}
174
418
  },
175
419
  // Accept both old 'character' and new 'personality' field names
@@ -207,9 +451,8 @@ function normalizeSectioned(v1) {
207
451
  path: section.path,
208
452
  archetype: section.terroir ?? section.vignette ?? section.archetype,
209
453
  theme: {
210
- style: section.vintage?.style ?? "",
211
- mode: section.vintage?.mode ?? "dark",
212
- recipe: section.vintage?.recipe ?? ""
454
+ id: section.vintage?.style ?? "",
455
+ mode: section.vintage?.mode ?? "dark"
213
456
  },
214
457
  structure: (section.structure ?? []).map(normalizeStructurePage),
215
458
  ...section.tannins ? { features: section.tannins } : {}
@@ -241,7 +484,6 @@ function normalizeGuard(cork) {
241
484
  };
242
485
  return {
243
486
  ...cork.enforce_style !== void 0 ? { enforce_style: cork.enforce_style } : {},
244
- ...cork.enforce_recipe !== void 0 ? { enforce_recipe: cork.enforce_recipe } : {},
245
487
  mode: modeMap[cork.mode ?? "creative"] ?? "creative"
246
488
  };
247
489
  }
@@ -249,11 +491,194 @@ function stripGapPrefix(gap) {
249
491
  const match = gap.match(/^_gap(\d+)$/);
250
492
  return match ? match[1] : gap;
251
493
  }
494
+
495
+ // src/migrate.ts
496
+ function migrateV2ToV3(essence) {
497
+ if (isV3(essence)) return essence;
498
+ if (isSectioned(essence)) {
499
+ return migrateSectionedToV3(essence);
500
+ }
501
+ if (isSimple(essence)) {
502
+ return migrateSimpleToV3(essence);
503
+ }
504
+ throw new Error("Unknown EssenceFile type \u2014 cannot migrate");
505
+ }
506
+ function migrateSimpleToV3(essence) {
507
+ const dna = buildDNA(essence);
508
+ const blueprint = buildBlueprintFromSimple(essence);
509
+ const meta = buildMeta(essence);
510
+ return {
511
+ version: "3.0.0",
512
+ dna,
513
+ blueprint,
514
+ meta,
515
+ ...essence._impression ? { _impression: essence._impression } : {}
516
+ };
517
+ }
518
+ function migrateSectionedToV3(essence) {
519
+ const firstSection = essence.sections[0];
520
+ const syntheticSimple = {
521
+ theme: firstSection.theme,
522
+ density: essence.density,
523
+ guard: essence.guard,
524
+ accessibility: essence.accessibility
525
+ };
526
+ const dna = buildDNA(syntheticSimple);
527
+ dna.personality = essence.personality;
528
+ const pages = essence.sections.flatMap(
529
+ (section) => section.structure.map((page) => ({
530
+ id: page.id,
531
+ shell_override: page.shell,
532
+ layout: page.layout,
533
+ ...page.surface ? { surface: page.surface } : {}
534
+ }))
535
+ );
536
+ const allFeatures = [
537
+ ...essence.shared_features ?? [],
538
+ ...essence.sections.flatMap((s) => s.features ?? [])
539
+ ];
540
+ const blueprint = {
541
+ shell: firstSection.structure[0]?.shell ?? "top-nav-main",
542
+ pages,
543
+ features: [...new Set(allFeatures)]
544
+ };
545
+ const meta = {
546
+ archetype: firstSection.archetype,
547
+ target: essence.target,
548
+ platform: essence.platform,
549
+ guard: migrateGuard(essence.guard.mode)
550
+ };
551
+ return {
552
+ version: "3.0.0",
553
+ dna,
554
+ blueprint,
555
+ meta,
556
+ ...essence._impression ? { _impression: essence._impression } : {}
557
+ };
558
+ }
559
+ function buildDNA(essence) {
560
+ const shape = essence.theme.shape ?? "rounded";
561
+ const radiusBase = inferRadiusBase(shape);
562
+ return {
563
+ theme: {
564
+ id: essence.theme.id,
565
+ mode: essence.theme.mode,
566
+ ...essence.theme.shape ? { shape: essence.theme.shape } : {}
567
+ },
568
+ spacing: {
569
+ base_unit: 4,
570
+ scale: "linear",
571
+ density: essence.density?.level ?? "comfortable",
572
+ content_gap: essence.density?.content_gap ?? "4"
573
+ },
574
+ typography: {
575
+ scale: "modular",
576
+ heading_weight: 600,
577
+ body_weight: 400
578
+ },
579
+ color: {
580
+ palette: "semantic",
581
+ accent_count: 1,
582
+ cvd_preference: essence.accessibility?.cvd_preference ?? "auto"
583
+ },
584
+ radius: {
585
+ philosophy: shape,
586
+ base: radiusBase
587
+ },
588
+ elevation: {
589
+ system: "layered",
590
+ max_levels: 3
591
+ },
592
+ motion: {
593
+ preference: "subtle",
594
+ duration_scale: 1,
595
+ reduce_motion: true
596
+ },
597
+ accessibility: {
598
+ wcag_level: essence.accessibility?.wcag_level ?? "AA",
599
+ focus_visible: true,
600
+ skip_nav: true
601
+ },
602
+ personality: essence.personality ?? ["professional"]
603
+ };
604
+ }
605
+ function buildBlueprintFromSimple(essence) {
606
+ const defaultShell = essence.structure[0]?.shell ?? "top-nav-main";
607
+ return {
608
+ shell: defaultShell,
609
+ pages: essence.structure.map((page) => ({
610
+ id: page.id,
611
+ ...page.shell !== defaultShell ? { shell_override: page.shell } : {},
612
+ layout: page.layout,
613
+ ...page.surface ? { surface: page.surface } : {}
614
+ })),
615
+ features: essence.features ?? []
616
+ };
617
+ }
618
+ function buildMeta(essence) {
619
+ return {
620
+ archetype: essence.archetype,
621
+ target: essence.target,
622
+ platform: essence.platform,
623
+ guard: migrateGuard(essence.guard.mode)
624
+ };
625
+ }
626
+ function migrateGuard(mode) {
627
+ switch (mode) {
628
+ case "strict":
629
+ return { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" };
630
+ case "guided":
631
+ return { mode: "guided", dna_enforcement: "error", blueprint_enforcement: "off" };
632
+ case "creative":
633
+ default:
634
+ return { mode: "creative", dna_enforcement: "off", blueprint_enforcement: "off" };
635
+ }
636
+ }
637
+ function inferRadiusBase(shape) {
638
+ switch (shape) {
639
+ case "pill":
640
+ return 12;
641
+ case "rounded":
642
+ return 8;
643
+ case "sharp":
644
+ return 2;
645
+ default:
646
+ return 8;
647
+ }
648
+ }
649
+ function migrateV30ToV31(essence) {
650
+ if (essence.version === "3.1.0" || essence.blueprint.sections) {
651
+ return essence;
652
+ }
653
+ const section = {
654
+ id: essence.meta.archetype,
655
+ role: "primary",
656
+ shell: essence.blueprint.shell ?? "top-nav-main",
657
+ features: essence.blueprint.features,
658
+ description: "",
659
+ pages: essence.blueprint.pages ?? []
660
+ };
661
+ return {
662
+ ...essence,
663
+ version: "3.1.0",
664
+ blueprint: {
665
+ ...essence.blueprint,
666
+ sections: [section],
667
+ pages: void 0,
668
+ routes: {}
669
+ }
670
+ };
671
+ }
252
672
  export {
253
673
  computeDensity,
674
+ computeSpatialTokens,
254
675
  evaluateGuard,
676
+ flattenPages,
255
677
  isSectioned,
256
678
  isSimple,
679
+ isV3,
680
+ migrateV2ToV3,
681
+ migrateV30ToV31,
257
682
  normalizeEssence,
258
683
  validateEssence
259
684
  };