@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.d.ts +174 -10
- package/dist/index.js +467 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/essence.v2.json +23 -5
- package/schema/essence.v3.json +216 -0
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
|
|
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
|
+
if (data.version === "3.0.0") return "v3";
|
|
54
|
+
}
|
|
55
|
+
return "v2";
|
|
24
56
|
}
|
|
25
57
|
function validateEssence(data) {
|
|
26
|
-
const
|
|
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,
|
|
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 (
|
|
64
|
-
gap +=
|
|
99
|
+
if (spatialHints?.density_bias) {
|
|
100
|
+
gap += spatialHints.density_bias;
|
|
65
101
|
}
|
|
66
|
-
if (
|
|
67
|
-
gap +=
|
|
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
|
|
78
|
-
|
|
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
|
-
|
|
86
|
-
if (
|
|
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
|
-
|
|
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 "${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|