@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/LICENSE +21 -0
- package/dist/index.d.ts +174 -10
- package/dist/index.js +468 -42
- package/dist/index.js.map +1 -1
- package/package.json +9 -6
- package/schema/essence.v2.json +23 -5
- package/schema/essence.v3.json +216 -0
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
|
|
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
|
|
18
|
-
function
|
|
19
|
-
if (
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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,
|
|
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 (
|
|
64
|
-
gap +=
|
|
100
|
+
if (spatialHints?.density_bias) {
|
|
101
|
+
gap += spatialHints.density_bias;
|
|
65
102
|
}
|
|
66
|
-
if (
|
|
67
|
-
gap +=
|
|
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
|
|
78
|
-
|
|
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
|
-
|
|
86
|
-
if (
|
|
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
|
-
|
|
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 "${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|