@decantr/essence-spec 1.0.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/index.d.ts +34 -34
- package/dist/index.js +205 -271
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/schema/essence.v4.json +430 -0
package/dist/index.js
CHANGED
|
@@ -112,8 +112,11 @@ function getColumnPreset(col) {
|
|
|
112
112
|
if (typeof col === "string") return void 0;
|
|
113
113
|
return col.preset;
|
|
114
114
|
}
|
|
115
|
-
function
|
|
116
|
-
return
|
|
115
|
+
function isV4(essence) {
|
|
116
|
+
return typeof essence === "object" && essence !== null && essence.version === "4.0.0" && "dna" in essence && "blueprint" in essence;
|
|
117
|
+
}
|
|
118
|
+
function isLegacyV3(essence) {
|
|
119
|
+
return typeof essence === "object" && essence !== null && (essence.version === "3.0.0" || essence.version === "3.1.0") && "dna" in essence && "blueprint" in essence;
|
|
117
120
|
}
|
|
118
121
|
function flattenPages(blueprint) {
|
|
119
122
|
if (blueprint.sections && blueprint.sections.length > 0) {
|
|
@@ -127,26 +130,19 @@ function flattenPages(blueprint) {
|
|
|
127
130
|
return blueprint.pages ?? [];
|
|
128
131
|
}
|
|
129
132
|
function isSectioned(essence) {
|
|
130
|
-
if (
|
|
131
|
-
return "sections" in essence && Array.isArray(essence.sections);
|
|
133
|
+
if (isV4(essence)) return false;
|
|
134
|
+
return typeof essence === "object" && essence !== null && "sections" in essence && Array.isArray(essence.sections);
|
|
132
135
|
}
|
|
133
136
|
function isSimple(essence) {
|
|
134
|
-
if (
|
|
135
|
-
return "archetype" in essence && !("sections" in essence);
|
|
137
|
+
if (isV4(essence)) return false;
|
|
138
|
+
return typeof essence === "object" && essence !== null && "archetype" in essence && !("sections" in essence);
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
// src/guard.ts
|
|
139
142
|
function checkThemeModeCompatibility(essence, context) {
|
|
140
143
|
if (!context.themeRegistry) return null;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (isV3(essence)) {
|
|
144
|
-
themeId = essence.dna.theme?.id ?? null;
|
|
145
|
-
mode = essence.dna.theme?.mode ?? null;
|
|
146
|
-
} else {
|
|
147
|
-
themeId = isSimple(essence) ? essence.theme?.id : null;
|
|
148
|
-
mode = isSimple(essence) ? essence.theme?.mode : null;
|
|
149
|
-
}
|
|
144
|
+
const themeId = essence.dna.theme?.id ?? null;
|
|
145
|
+
const mode = essence.dna.theme?.mode ?? null;
|
|
150
146
|
if (!themeId || !mode) return null;
|
|
151
147
|
const theme = context.themeRegistry.get(themeId);
|
|
152
148
|
if (!theme) {
|
|
@@ -160,7 +156,8 @@ function checkThemeModeCompatibility(essence, context) {
|
|
|
160
156
|
severity: "error",
|
|
161
157
|
message: `Theme "${themeId}" does not support "${mode}" mode. Supported modes: ${supportedModes}.`,
|
|
162
158
|
suggestion,
|
|
163
|
-
|
|
159
|
+
layer: "dna",
|
|
160
|
+
autoFixable: false
|
|
164
161
|
};
|
|
165
162
|
}
|
|
166
163
|
return null;
|
|
@@ -213,17 +210,18 @@ function checkPatternExistence(essence, context) {
|
|
|
213
210
|
const suggestion = similar.length > 0 ? `Similar patterns: ${similar.join(", ")}` : `Run "decantr search ${patternId}" to find alternatives.`;
|
|
214
211
|
violations.push({
|
|
215
212
|
rule: "pattern-exists",
|
|
216
|
-
severity:
|
|
213
|
+
severity: "warning",
|
|
217
214
|
message: `Pattern "${patternId}" is referenced but does not exist in the registry.`,
|
|
218
215
|
suggestion,
|
|
219
|
-
|
|
216
|
+
layer: "blueprint",
|
|
217
|
+
autoFixable: false
|
|
220
218
|
});
|
|
221
219
|
}
|
|
222
220
|
}
|
|
223
221
|
return violations;
|
|
224
222
|
}
|
|
225
223
|
function checkAccessibility(essence, context) {
|
|
226
|
-
const accessibility =
|
|
224
|
+
const accessibility = essence.dna.accessibility;
|
|
227
225
|
if (!accessibility?.wcag_level || accessibility.wcag_level === "none") {
|
|
228
226
|
return null;
|
|
229
227
|
}
|
|
@@ -235,7 +233,8 @@ function checkAccessibility(essence, context) {
|
|
|
235
233
|
severity: "error",
|
|
236
234
|
message: `WCAG ${accessibility.wcag_level} compliance required. Issues found: ${issueList}${moreCount}`,
|
|
237
235
|
suggestion: "Fix accessibility issues before proceeding. Run an accessibility audit for details.",
|
|
238
|
-
|
|
236
|
+
layer: "dna",
|
|
237
|
+
autoFixable: false
|
|
239
238
|
};
|
|
240
239
|
}
|
|
241
240
|
return null;
|
|
@@ -244,8 +243,7 @@ function checkInteractions(essence, context) {
|
|
|
244
243
|
if (!context.interaction_issues || context.interaction_issues.length === 0) {
|
|
245
244
|
return null;
|
|
246
245
|
}
|
|
247
|
-
const guard =
|
|
248
|
-
if (!guard) return null;
|
|
246
|
+
const guard = essence.meta.guard;
|
|
249
247
|
let enforcement;
|
|
250
248
|
if (guard.interactions_enforcement) {
|
|
251
249
|
enforcement = guard.interactions_enforcement;
|
|
@@ -269,7 +267,7 @@ function checkInteractions(essence, context) {
|
|
|
269
267
|
};
|
|
270
268
|
}
|
|
271
269
|
function evaluateGuard(essence, context = {}) {
|
|
272
|
-
const guard =
|
|
270
|
+
const guard = essence.meta.guard;
|
|
273
271
|
if (guard.mode === "creative") {
|
|
274
272
|
return [];
|
|
275
273
|
}
|
|
@@ -277,19 +275,14 @@ function evaluateGuard(essence, context = {}) {
|
|
|
277
275
|
const isStrict = guard.mode === "strict";
|
|
278
276
|
const requestedTheme = context.theme ?? context.style;
|
|
279
277
|
if (requestedTheme) {
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
essenceThemeId = essence.dna.theme.id;
|
|
283
|
-
} else {
|
|
284
|
-
essenceThemeId = isSimple(essence) ? essence.theme.id : null;
|
|
285
|
-
}
|
|
286
|
-
const enforceStyle = isV3(essence) ? true : guard.enforce_style !== false;
|
|
287
|
-
if (essenceThemeId && requestedTheme !== essenceThemeId && enforceStyle) {
|
|
278
|
+
const essenceThemeId = essence.dna.theme.id;
|
|
279
|
+
if (essenceThemeId && requestedTheme !== essenceThemeId) {
|
|
288
280
|
violations.push({
|
|
289
281
|
rule: "theme",
|
|
290
282
|
severity: "error",
|
|
291
283
|
message: `Theme "${requestedTheme}" does not match essence theme "${essenceThemeId}". Change the essence theme first.`,
|
|
292
|
-
|
|
284
|
+
layer: "dna",
|
|
285
|
+
autoFixable: false
|
|
293
286
|
});
|
|
294
287
|
}
|
|
295
288
|
}
|
|
@@ -299,13 +292,11 @@ function evaluateGuard(essence, context = {}) {
|
|
|
299
292
|
if (!pageExists) {
|
|
300
293
|
violations.push({
|
|
301
294
|
rule: "structure",
|
|
302
|
-
severity:
|
|
295
|
+
severity: "warning",
|
|
303
296
|
message: `Page "${context.pageId}" does not exist in essence structure. Add it to the essence first.`,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
autoFix: { type: "add_page", patch: { id: context.pageId } }
|
|
308
|
-
} : {}
|
|
297
|
+
layer: "blueprint",
|
|
298
|
+
autoFixable: true,
|
|
299
|
+
autoFix: { type: "add_page", patch: { id: context.pageId } }
|
|
309
300
|
});
|
|
310
301
|
}
|
|
311
302
|
}
|
|
@@ -321,34 +312,28 @@ function evaluateGuard(essence, context = {}) {
|
|
|
321
312
|
if (!matches) {
|
|
322
313
|
violations.push({
|
|
323
314
|
rule: "layout",
|
|
324
|
-
severity:
|
|
315
|
+
severity: "warning",
|
|
325
316
|
message: `Layout for page "${context.pageId}" deviates from essence. Expected: [${essenceLayout.join(", ")}].`,
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
} : {}
|
|
317
|
+
layer: "blueprint",
|
|
318
|
+
autoFixable: true,
|
|
319
|
+
autoFix: {
|
|
320
|
+
type: "update_layout",
|
|
321
|
+
patch: { page: context.pageId, layout: context.layout }
|
|
322
|
+
}
|
|
334
323
|
});
|
|
335
324
|
}
|
|
336
325
|
}
|
|
337
326
|
}
|
|
338
327
|
if (isStrict && context.density_gap) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const overriddenDensity = getPageDensityOverride(essence, context.pageId);
|
|
342
|
-
expectedGap = overriddenDensity ?? essence.dna.spacing.content_gap;
|
|
343
|
-
} else {
|
|
344
|
-
expectedGap = essence.density.content_gap;
|
|
345
|
-
}
|
|
328
|
+
const overriddenDensity = getPageDensityOverride(essence, context.pageId);
|
|
329
|
+
const expectedGap = overriddenDensity ?? essence.dna.spacing.content_gap;
|
|
346
330
|
if (context.density_gap !== expectedGap) {
|
|
347
331
|
violations.push({
|
|
348
332
|
rule: "density",
|
|
349
333
|
severity: "warning",
|
|
350
334
|
message: `Content gap "${context.density_gap}" does not match essence density "${expectedGap}".`,
|
|
351
|
-
|
|
335
|
+
layer: "dna",
|
|
336
|
+
autoFixable: false
|
|
352
337
|
});
|
|
353
338
|
}
|
|
354
339
|
}
|
|
@@ -366,37 +351,30 @@ function evaluateGuard(essence, context = {}) {
|
|
|
366
351
|
if (interactionsViolation) {
|
|
367
352
|
violations.push(interactionsViolation);
|
|
368
353
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
v.severity = "warning";
|
|
378
|
-
}
|
|
354
|
+
const v4Guard = guard;
|
|
355
|
+
if (v4Guard.dna_enforcement === "off") {
|
|
356
|
+
return violations.filter((v) => v.layer !== "dna");
|
|
357
|
+
}
|
|
358
|
+
if (v4Guard.dna_enforcement === "warn") {
|
|
359
|
+
for (const v of violations) {
|
|
360
|
+
if (v.layer === "dna") {
|
|
361
|
+
v.severity = "warning";
|
|
379
362
|
}
|
|
380
363
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
364
|
+
}
|
|
365
|
+
if (v4Guard.blueprint_enforcement === "off") {
|
|
366
|
+
return violations.filter((v) => v.layer !== "blueprint");
|
|
384
367
|
}
|
|
385
368
|
return violations;
|
|
386
369
|
}
|
|
387
370
|
function getAllPages(essence) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}));
|
|
396
|
-
}
|
|
397
|
-
if (isSimple(essence)) return essence.structure;
|
|
398
|
-
if (isSectioned(essence)) return essence.sections.flatMap((s) => s.structure);
|
|
399
|
-
throw new Error("Unknown EssenceFile type");
|
|
371
|
+
const pages = flattenPages(essence.blueprint);
|
|
372
|
+
return pages.map((page) => ({
|
|
373
|
+
id: page.id,
|
|
374
|
+
shell: page.shell_override ?? essence.blueprint.shell ?? "",
|
|
375
|
+
layout: page.layout,
|
|
376
|
+
...page.surface ? { surface: page.surface } : {}
|
|
377
|
+
}));
|
|
400
378
|
}
|
|
401
379
|
function getPageDensityOverride(essence, pageId) {
|
|
402
380
|
if (!pageId) return void 0;
|
|
@@ -412,72 +390,135 @@ function getPageDensityOverride(essence, pageId) {
|
|
|
412
390
|
}
|
|
413
391
|
|
|
414
392
|
// src/migrate.ts
|
|
415
|
-
function
|
|
416
|
-
if (
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
throw new Error("Unknown EssenceFile type \u2014 cannot migrate");
|
|
424
|
-
}
|
|
425
|
-
function migrateSimpleToV3(essence) {
|
|
393
|
+
function migrateToV4(input) {
|
|
394
|
+
if (isV4(input)) return normalizeV4(input);
|
|
395
|
+
if (isLegacyV3(input)) return migrateLegacyV3ToV4(input);
|
|
396
|
+
if (isSectioned(input)) return migrateSectionedToV4(input);
|
|
397
|
+
if (isSimple(input)) return migrateSimpleToV4(input);
|
|
398
|
+
throw new Error("Unknown essence format. Only Essence v2, v3.0, v3.1, and v4 can migrate to v4.");
|
|
399
|
+
}
|
|
400
|
+
function migrateSimpleToV4(essence) {
|
|
426
401
|
const dna = buildDNA(essence);
|
|
427
|
-
const
|
|
428
|
-
const
|
|
402
|
+
const defaultShell = essence.structure[0]?.shell ?? "top-nav-main";
|
|
403
|
+
const pages = essence.structure.map((page, index) => ({
|
|
404
|
+
id: page.id,
|
|
405
|
+
route: page.id === "home" || index === 0 ? "/" : `/${page.id}`,
|
|
406
|
+
...page.shell !== defaultShell ? { shell_override: page.shell } : {},
|
|
407
|
+
layout: page.layout,
|
|
408
|
+
...page.surface ? { surface: page.surface } : {}
|
|
409
|
+
}));
|
|
410
|
+
const section = {
|
|
411
|
+
id: essence.archetype,
|
|
412
|
+
role: "primary",
|
|
413
|
+
shell: defaultShell,
|
|
414
|
+
features: essence.features ?? [],
|
|
415
|
+
description: `${essence.archetype} primary section`,
|
|
416
|
+
pages
|
|
417
|
+
};
|
|
429
418
|
return {
|
|
430
|
-
version: "
|
|
419
|
+
version: "4.0.0",
|
|
431
420
|
dna,
|
|
432
|
-
blueprint,
|
|
433
|
-
meta,
|
|
421
|
+
blueprint: buildSectionedBlueprint([section], essence.features ?? []),
|
|
422
|
+
meta: buildMeta(essence),
|
|
434
423
|
...essence._impression ? { _impression: essence._impression } : {}
|
|
435
424
|
};
|
|
436
425
|
}
|
|
437
|
-
function
|
|
426
|
+
function migrateSectionedToV4(essence) {
|
|
438
427
|
const firstSection = essence.sections[0];
|
|
428
|
+
if (!firstSection) {
|
|
429
|
+
throw new Error("Cannot migrate a sectioned essence with no sections.");
|
|
430
|
+
}
|
|
439
431
|
const syntheticSimple = {
|
|
440
432
|
theme: firstSection.theme,
|
|
441
433
|
density: essence.density,
|
|
442
434
|
guard: essence.guard,
|
|
443
|
-
accessibility: essence.accessibility
|
|
435
|
+
accessibility: essence.accessibility,
|
|
436
|
+
personality: essence.personality
|
|
444
437
|
};
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
438
|
+
const sections = essence.sections.map((section) => {
|
|
439
|
+
const defaultShell = section.structure[0]?.shell ?? "top-nav-main";
|
|
440
|
+
return {
|
|
441
|
+
id: section.id,
|
|
442
|
+
role: "primary",
|
|
443
|
+
shell: defaultShell,
|
|
444
|
+
features: section.features ?? [],
|
|
445
|
+
description: `${section.archetype} section`,
|
|
446
|
+
pages: section.structure.map((page, index) => ({
|
|
447
|
+
id: page.id,
|
|
448
|
+
route: index === 0 && section.path ? section.path : `${section.path}/${page.id}`.replace(/\/+/g, "/"),
|
|
449
|
+
...page.shell !== defaultShell ? { shell_override: page.shell } : {},
|
|
450
|
+
layout: page.layout,
|
|
451
|
+
...page.surface ? { surface: page.surface } : {}
|
|
452
|
+
}))
|
|
453
|
+
};
|
|
454
|
+
});
|
|
455
|
+
const features = [
|
|
456
456
|
...essence.shared_features ?? [],
|
|
457
|
-
...essence.sections.flatMap((
|
|
457
|
+
...essence.sections.flatMap((section) => section.features ?? [])
|
|
458
458
|
];
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
459
|
+
return {
|
|
460
|
+
version: "4.0.0",
|
|
461
|
+
dna: buildDNA(syntheticSimple),
|
|
462
|
+
blueprint: buildSectionedBlueprint(sections, [...new Set(features)]),
|
|
463
|
+
meta: {
|
|
464
|
+
archetype: firstSection.archetype,
|
|
465
|
+
target: essence.target,
|
|
466
|
+
platform: essence.platform,
|
|
467
|
+
guard: migrateGuard(essence.guard.mode)
|
|
468
|
+
},
|
|
469
|
+
...essence._impression ? { _impression: essence._impression } : {}
|
|
463
470
|
};
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
471
|
+
}
|
|
472
|
+
function migrateLegacyV3ToV4(essence) {
|
|
473
|
+
const sections = essence.blueprint.sections && essence.blueprint.sections.length > 0 ? essence.blueprint.sections : [
|
|
474
|
+
{
|
|
475
|
+
id: essence.meta.archetype,
|
|
476
|
+
role: "primary",
|
|
477
|
+
shell: essence.blueprint.shell ?? "top-nav-main",
|
|
478
|
+
features: essence.blueprint.features,
|
|
479
|
+
description: `${essence.meta.archetype} primary section`,
|
|
480
|
+
pages: essence.blueprint.pages ?? []
|
|
481
|
+
}
|
|
482
|
+
];
|
|
483
|
+
return normalizeV4({
|
|
484
|
+
...essence,
|
|
485
|
+
version: "4.0.0",
|
|
486
|
+
blueprint: buildSectionedBlueprint(
|
|
487
|
+
sections,
|
|
488
|
+
essence.blueprint.features,
|
|
489
|
+
essence.blueprint.routes
|
|
490
|
+
)
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
function normalizeV4(essence) {
|
|
494
|
+
return {
|
|
495
|
+
...essence,
|
|
496
|
+
version: "4.0.0",
|
|
497
|
+
blueprint: buildSectionedBlueprint(
|
|
498
|
+
essence.blueprint.sections,
|
|
499
|
+
essence.blueprint.features,
|
|
500
|
+
essence.blueprint.routes,
|
|
501
|
+
essence.blueprint.shell
|
|
502
|
+
)
|
|
469
503
|
};
|
|
504
|
+
}
|
|
505
|
+
function buildSectionedBlueprint(sections, features, existingRoutes, shell) {
|
|
506
|
+
const routes = { ...existingRoutes ?? {} };
|
|
507
|
+
for (const section of sections) {
|
|
508
|
+
for (const page of section.pages) {
|
|
509
|
+
if (!page.route || routes[page.route]) continue;
|
|
510
|
+
routes[page.route] = { section: section.id, page: page.id };
|
|
511
|
+
}
|
|
512
|
+
}
|
|
470
513
|
return {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
...essence._impression ? { _impression: essence._impression } : {}
|
|
514
|
+
...shell ? { shell } : {},
|
|
515
|
+
sections,
|
|
516
|
+
features: [...new Set(features)],
|
|
517
|
+
routes
|
|
476
518
|
};
|
|
477
519
|
}
|
|
478
520
|
function buildDNA(essence) {
|
|
479
521
|
const shape = essence.theme.shape ?? "rounded";
|
|
480
|
-
const radiusBase = inferRadiusBase(shape);
|
|
481
522
|
return {
|
|
482
523
|
theme: {
|
|
483
524
|
id: essence.theme.id,
|
|
@@ -488,7 +529,7 @@ function buildDNA(essence) {
|
|
|
488
529
|
base_unit: 4,
|
|
489
530
|
scale: "linear",
|
|
490
531
|
density: essence.density?.level ?? "comfortable",
|
|
491
|
-
content_gap: essence.density?.content_gap ?? "
|
|
532
|
+
content_gap: essence.density?.content_gap ?? "_gap4"
|
|
492
533
|
},
|
|
493
534
|
typography: {
|
|
494
535
|
scale: "modular",
|
|
@@ -502,7 +543,7 @@ function buildDNA(essence) {
|
|
|
502
543
|
},
|
|
503
544
|
radius: {
|
|
504
545
|
philosophy: shape,
|
|
505
|
-
base:
|
|
546
|
+
base: inferRadiusBase(shape)
|
|
506
547
|
},
|
|
507
548
|
elevation: {
|
|
508
549
|
system: "layered",
|
|
@@ -521,19 +562,6 @@ function buildDNA(essence) {
|
|
|
521
562
|
personality: essence.personality ?? ["professional"]
|
|
522
563
|
};
|
|
523
564
|
}
|
|
524
|
-
function buildBlueprintFromSimple(essence) {
|
|
525
|
-
const defaultShell = essence.structure[0]?.shell ?? "top-nav-main";
|
|
526
|
-
return {
|
|
527
|
-
shell: defaultShell,
|
|
528
|
-
pages: essence.structure.map((page) => ({
|
|
529
|
-
id: page.id,
|
|
530
|
-
...page.shell !== defaultShell ? { shell_override: page.shell } : {},
|
|
531
|
-
layout: page.layout,
|
|
532
|
-
...page.surface ? { surface: page.surface } : {}
|
|
533
|
-
})),
|
|
534
|
-
features: essence.features ?? []
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
565
|
function buildMeta(essence) {
|
|
538
566
|
return {
|
|
539
567
|
archetype: essence.archetype,
|
|
@@ -565,135 +593,14 @@ function inferRadiusBase(shape) {
|
|
|
565
593
|
return 8;
|
|
566
594
|
}
|
|
567
595
|
}
|
|
568
|
-
function migrateV30ToV31(essence) {
|
|
569
|
-
if (essence.version === "3.1.0" || essence.blueprint.sections) {
|
|
570
|
-
return essence;
|
|
571
|
-
}
|
|
572
|
-
const section = {
|
|
573
|
-
id: essence.meta.archetype,
|
|
574
|
-
role: "primary",
|
|
575
|
-
shell: essence.blueprint.shell ?? "top-nav-main",
|
|
576
|
-
features: essence.blueprint.features,
|
|
577
|
-
description: `${essence.meta.archetype} primary section`,
|
|
578
|
-
pages: essence.blueprint.pages ?? []
|
|
579
|
-
};
|
|
580
|
-
return {
|
|
581
|
-
...essence,
|
|
582
|
-
version: "3.1.0",
|
|
583
|
-
blueprint: {
|
|
584
|
-
...essence.blueprint,
|
|
585
|
-
sections: [section],
|
|
586
|
-
pages: void 0,
|
|
587
|
-
routes: {}
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
596
|
|
|
592
597
|
// src/normalize.ts
|
|
598
|
+
var V4_REQUIRED_MESSAGE = "Active Decantr V2 workflows require Essence v4.0.0. Run `decantr migrate --to v4` for older essence files.";
|
|
593
599
|
function normalizeEssence(input) {
|
|
594
|
-
if (input
|
|
595
|
-
return input;
|
|
596
|
-
}
|
|
597
|
-
if (input.theme && input.platform) {
|
|
600
|
+
if (isV4(input)) {
|
|
598
601
|
return input;
|
|
599
602
|
}
|
|
600
|
-
|
|
601
|
-
return normalizeSectioned(input);
|
|
602
|
-
}
|
|
603
|
-
return normalizeSimple(input);
|
|
604
|
-
}
|
|
605
|
-
function normalizeSimple(v1) {
|
|
606
|
-
const vintage = v1.vintage;
|
|
607
|
-
const vessel = v1.vessel;
|
|
608
|
-
const clarity = v1.clarity;
|
|
609
|
-
const cork = v1.cork;
|
|
610
|
-
const structure = v1.structure;
|
|
611
|
-
return {
|
|
612
|
-
version: "2.0.0",
|
|
613
|
-
archetype: v1.terroir ?? v1.vignette ?? v1.archetype,
|
|
614
|
-
theme: {
|
|
615
|
-
id: vintage?.style ?? "",
|
|
616
|
-
mode: vintage?.mode ?? "dark",
|
|
617
|
-
...vintage?.shape ? { shape: vintage.shape } : {}
|
|
618
|
-
},
|
|
619
|
-
// Accept both old 'character' and new 'personality' field names
|
|
620
|
-
personality: v1.character ?? v1.personality,
|
|
621
|
-
platform: {
|
|
622
|
-
type: vessel?.type ?? "spa",
|
|
623
|
-
// Modern-SPA default. See packages/cli/src/scaffold.ts getPlatformMeta for rationale.
|
|
624
|
-
routing: vessel?.routing ?? "history"
|
|
625
|
-
},
|
|
626
|
-
structure: (structure ?? []).map(normalizeStructurePage),
|
|
627
|
-
features: v1.tannins ?? v1.features ?? [],
|
|
628
|
-
density: {
|
|
629
|
-
level: clarity?.density ?? "comfortable",
|
|
630
|
-
content_gap: stripGapPrefix(clarity?.content_gap ?? "4")
|
|
631
|
-
},
|
|
632
|
-
guard: normalizeGuard(cork),
|
|
633
|
-
target: v1.target ?? "decantr",
|
|
634
|
-
...v1._impression ? { _impression: v1._impression } : {}
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
function normalizeSectioned(v1) {
|
|
638
|
-
const vessel = v1.vessel;
|
|
639
|
-
const clarity = v1.clarity;
|
|
640
|
-
const cork = v1.cork;
|
|
641
|
-
const sections = v1.sections;
|
|
642
|
-
return {
|
|
643
|
-
version: "2.0.0",
|
|
644
|
-
platform: {
|
|
645
|
-
type: vessel?.type ?? "spa",
|
|
646
|
-
// Modern-SPA default. See packages/cli/src/scaffold.ts getPlatformMeta for rationale.
|
|
647
|
-
routing: vessel?.routing ?? "history"
|
|
648
|
-
},
|
|
649
|
-
// Accept both old 'character' and new 'personality' field names
|
|
650
|
-
personality: v1.character ?? v1.personality,
|
|
651
|
-
sections: sections.map((section) => ({
|
|
652
|
-
id: section.id,
|
|
653
|
-
path: section.path,
|
|
654
|
-
archetype: section.terroir ?? section.vignette ?? section.archetype,
|
|
655
|
-
theme: {
|
|
656
|
-
id: section.vintage?.style ?? "",
|
|
657
|
-
mode: section.vintage?.mode ?? "dark"
|
|
658
|
-
},
|
|
659
|
-
structure: (section.structure ?? []).map(
|
|
660
|
-
normalizeStructurePage
|
|
661
|
-
),
|
|
662
|
-
...section.tannins ? { features: section.tannins } : {}
|
|
663
|
-
})),
|
|
664
|
-
...v1.shared_tannins ? { shared_features: v1.shared_tannins } : {},
|
|
665
|
-
density: {
|
|
666
|
-
level: clarity?.density ?? "comfortable",
|
|
667
|
-
content_gap: stripGapPrefix(clarity?.content_gap ?? "4")
|
|
668
|
-
},
|
|
669
|
-
guard: normalizeGuard(cork),
|
|
670
|
-
target: v1.target ?? "decantr"
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
function normalizeStructurePage(page) {
|
|
674
|
-
return {
|
|
675
|
-
id: page.id,
|
|
676
|
-
shell: page.carafe ?? page.shell,
|
|
677
|
-
layout: page.blend ?? page.layout,
|
|
678
|
-
...page.surface ? { surface: page.surface } : {}
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
function normalizeGuard(cork) {
|
|
682
|
-
if (!cork) return { mode: "creative" };
|
|
683
|
-
const modeMap = {
|
|
684
|
-
creative: "creative",
|
|
685
|
-
maintenance: "strict",
|
|
686
|
-
strict: "strict",
|
|
687
|
-
guided: "guided"
|
|
688
|
-
};
|
|
689
|
-
return {
|
|
690
|
-
...cork.enforce_style !== void 0 ? { enforce_style: cork.enforce_style } : {},
|
|
691
|
-
mode: modeMap[cork.mode ?? "creative"] ?? "creative"
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
function stripGapPrefix(gap) {
|
|
695
|
-
const match = gap.match(/^_gap(\d+)$/);
|
|
696
|
-
return match ? match[1] : gap;
|
|
603
|
+
throw new Error(V4_REQUIRED_MESSAGE);
|
|
697
604
|
}
|
|
698
605
|
|
|
699
606
|
// src/validate.ts
|
|
@@ -704,6 +611,7 @@ import Ajv from "ajv";
|
|
|
704
611
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
705
612
|
var v2SchemaPath = join(__dirname, "..", "schema", "essence.v2.json");
|
|
706
613
|
var v3SchemaPath = join(__dirname, "..", "schema", "essence.v3.json");
|
|
614
|
+
var v4SchemaPath = join(__dirname, "..", "schema", "essence.v4.json");
|
|
707
615
|
var cachedAjv = null;
|
|
708
616
|
var validatorCache = /* @__PURE__ */ new Map();
|
|
709
617
|
function getAjv() {
|
|
@@ -714,7 +622,7 @@ function getAjv() {
|
|
|
714
622
|
}
|
|
715
623
|
function getValidator(version) {
|
|
716
624
|
if (validatorCache.has(version)) return validatorCache.get(version);
|
|
717
|
-
const schemaPath = version === "v3" ? v3SchemaPath : v2SchemaPath;
|
|
625
|
+
const schemaPath = version === "v4" ? v4SchemaPath : version === "v3" ? v3SchemaPath : v2SchemaPath;
|
|
718
626
|
if (!existsSync(schemaPath)) return null;
|
|
719
627
|
const schema = JSON.parse(readFileSync(schemaPath, "utf-8"));
|
|
720
628
|
const ajv = getAjv();
|
|
@@ -722,7 +630,7 @@ function getValidator(version) {
|
|
|
722
630
|
validatorCache.set(version, validate);
|
|
723
631
|
return validate;
|
|
724
632
|
}
|
|
725
|
-
function
|
|
633
|
+
function detectLegacyVersion(data) {
|
|
726
634
|
if (typeof data === "object" && data !== null && "version" in data) {
|
|
727
635
|
const v = data.version;
|
|
728
636
|
if (v === "3.0.0" || v === "3.1.0") return "v3";
|
|
@@ -730,7 +638,32 @@ function detectVersion(data) {
|
|
|
730
638
|
return "v2";
|
|
731
639
|
}
|
|
732
640
|
function validateEssence(data) {
|
|
733
|
-
const version =
|
|
641
|
+
const version = typeof data === "object" && data !== null ? data.version : void 0;
|
|
642
|
+
if (version !== "4.0.0") {
|
|
643
|
+
return {
|
|
644
|
+
valid: false,
|
|
645
|
+
errors: [
|
|
646
|
+
"Active Decantr V2 workflows require Essence v4.0.0. Run `decantr migrate --to v4` for older essence files."
|
|
647
|
+
]
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const validate = getValidator("v4");
|
|
651
|
+
if (!validate) {
|
|
652
|
+
return { valid: false, errors: ["No schema available for v4 documents"] };
|
|
653
|
+
}
|
|
654
|
+
const valid = validate(data);
|
|
655
|
+
if (valid) {
|
|
656
|
+
return { valid: true, errors: [] };
|
|
657
|
+
}
|
|
658
|
+
const errors = (validate.errors ?? []).map((err) => {
|
|
659
|
+
const path = err.instancePath || "/";
|
|
660
|
+
const msg = err.message ?? "unknown error";
|
|
661
|
+
return `${path}: ${msg}`;
|
|
662
|
+
});
|
|
663
|
+
return { valid: false, errors };
|
|
664
|
+
}
|
|
665
|
+
function validateLegacyEssenceForMigration(data) {
|
|
666
|
+
const version = typeof data === "object" && data !== null && data.version === "4.0.0" ? "v4" : detectLegacyVersion(data);
|
|
734
667
|
const validate = getValidator(version);
|
|
735
668
|
if (!validate) {
|
|
736
669
|
return { valid: false, errors: [`No schema available for ${version} documents`] };
|
|
@@ -754,12 +687,13 @@ export {
|
|
|
754
687
|
getColumnAlias,
|
|
755
688
|
getColumnId,
|
|
756
689
|
getColumnPreset,
|
|
690
|
+
isLegacyV3,
|
|
757
691
|
isSectioned,
|
|
758
692
|
isSimple,
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
migrateV30ToV31,
|
|
693
|
+
isV4,
|
|
694
|
+
migrateToV4,
|
|
762
695
|
normalizeEssence,
|
|
763
|
-
validateEssence
|
|
696
|
+
validateEssence,
|
|
697
|
+
validateLegacyEssenceForMigration
|
|
764
698
|
};
|
|
765
699
|
//# sourceMappingURL=index.js.map
|