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

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