@decantr/essence-spec 1.0.0-beta.1
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 +133 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
- package/schema/essence.v2.json +149 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Essence v2 schema types — normalized terminology.
|
|
3
|
+
*
|
|
4
|
+
* Terminology mapping (v1 wine terms → v2 normalized):
|
|
5
|
+
* terroir → archetype, vintage → theme, tannins → features,
|
|
6
|
+
* carafe → shell, cork → guard, blend → layout, clarity → density,
|
|
7
|
+
* vessel → platform, character → personality,
|
|
8
|
+
* decantation process → essence pipeline
|
|
9
|
+
*/
|
|
10
|
+
type ThemeStyle = 'auradecantism' | 'clean' | 'glassmorphism' | 'retro' | 'bioluminescent' | 'launchpad' | 'dopamine' | 'editorial' | 'liquid-glass' | 'prismatic';
|
|
11
|
+
type ThemeMode = 'light' | 'dark' | 'auto';
|
|
12
|
+
type ThemeShape = 'sharp' | 'rounded' | 'pill';
|
|
13
|
+
interface Theme {
|
|
14
|
+
style: ThemeStyle | string;
|
|
15
|
+
mode: ThemeMode;
|
|
16
|
+
recipe: string;
|
|
17
|
+
shape?: ThemeShape;
|
|
18
|
+
}
|
|
19
|
+
type PlatformType = 'spa' | 'ssr' | 'static';
|
|
20
|
+
type RoutingStrategy = 'hash' | 'history';
|
|
21
|
+
interface Platform {
|
|
22
|
+
type: PlatformType;
|
|
23
|
+
routing: RoutingStrategy;
|
|
24
|
+
}
|
|
25
|
+
type ShellType = 'sidebar-main' | 'top-nav-main' | 'full-bleed' | 'dashboard' | 'stacked' | string;
|
|
26
|
+
type LayoutItem = string | PatternRef | ColumnLayout;
|
|
27
|
+
interface PatternRef {
|
|
28
|
+
pattern: string;
|
|
29
|
+
preset?: string;
|
|
30
|
+
as?: string;
|
|
31
|
+
}
|
|
32
|
+
interface ColumnLayout {
|
|
33
|
+
cols: string[];
|
|
34
|
+
at?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
35
|
+
span?: Record<string, number>;
|
|
36
|
+
breakpoints?: {
|
|
37
|
+
at: string;
|
|
38
|
+
cols: number;
|
|
39
|
+
}[];
|
|
40
|
+
responsive?: 'viewport' | 'container';
|
|
41
|
+
}
|
|
42
|
+
interface StructurePage {
|
|
43
|
+
id: string;
|
|
44
|
+
shell: ShellType;
|
|
45
|
+
layout: LayoutItem[];
|
|
46
|
+
surface?: string;
|
|
47
|
+
}
|
|
48
|
+
type DensityLevel = 'compact' | 'comfortable' | 'spacious';
|
|
49
|
+
interface Density {
|
|
50
|
+
level: DensityLevel;
|
|
51
|
+
content_gap: string;
|
|
52
|
+
}
|
|
53
|
+
type GuardMode = 'creative' | 'guided' | 'strict';
|
|
54
|
+
interface Guard {
|
|
55
|
+
enforce_style?: boolean;
|
|
56
|
+
enforce_recipe?: boolean;
|
|
57
|
+
mode: GuardMode;
|
|
58
|
+
}
|
|
59
|
+
interface Impression {
|
|
60
|
+
vibe: string[];
|
|
61
|
+
references: string[];
|
|
62
|
+
density_intent: DensityLevel | string;
|
|
63
|
+
layout_intent: ShellType | string;
|
|
64
|
+
novel_elements: string[];
|
|
65
|
+
}
|
|
66
|
+
type GeneratorTarget = 'decantr' | 'react' | 'vue' | 'svelte' | string;
|
|
67
|
+
interface Essence {
|
|
68
|
+
$schema?: string;
|
|
69
|
+
version: string;
|
|
70
|
+
archetype: string;
|
|
71
|
+
theme: Theme;
|
|
72
|
+
personality: string[];
|
|
73
|
+
platform: Platform;
|
|
74
|
+
structure: StructurePage[];
|
|
75
|
+
features: string[];
|
|
76
|
+
density: Density;
|
|
77
|
+
guard: Guard;
|
|
78
|
+
target: GeneratorTarget;
|
|
79
|
+
_impression?: Impression;
|
|
80
|
+
}
|
|
81
|
+
interface EssenceSection {
|
|
82
|
+
id: string;
|
|
83
|
+
path: string;
|
|
84
|
+
archetype: string;
|
|
85
|
+
theme: Theme;
|
|
86
|
+
structure: StructurePage[];
|
|
87
|
+
features?: string[];
|
|
88
|
+
}
|
|
89
|
+
interface SectionedEssence {
|
|
90
|
+
$schema?: string;
|
|
91
|
+
version: string;
|
|
92
|
+
platform: Platform;
|
|
93
|
+
personality: string[];
|
|
94
|
+
sections: EssenceSection[];
|
|
95
|
+
shared_features?: string[];
|
|
96
|
+
density: Density;
|
|
97
|
+
guard: Guard;
|
|
98
|
+
target: GeneratorTarget;
|
|
99
|
+
_impression?: Impression;
|
|
100
|
+
}
|
|
101
|
+
type EssenceFile = Essence | SectionedEssence;
|
|
102
|
+
declare function isSectioned(essence: EssenceFile): essence is SectionedEssence;
|
|
103
|
+
declare function isSimple(essence: EssenceFile): essence is Essence;
|
|
104
|
+
|
|
105
|
+
interface ValidationResult {
|
|
106
|
+
valid: boolean;
|
|
107
|
+
errors: string[];
|
|
108
|
+
}
|
|
109
|
+
declare function validateEssence(data: unknown): ValidationResult;
|
|
110
|
+
|
|
111
|
+
interface RecipeSpatialHints {
|
|
112
|
+
density_bias?: number;
|
|
113
|
+
content_gap_shift?: number;
|
|
114
|
+
}
|
|
115
|
+
declare function computeDensity(personality: string[], recipeSpatial?: RecipeSpatialHints): Density;
|
|
116
|
+
|
|
117
|
+
interface GuardViolation {
|
|
118
|
+
rule: 'style' | 'structure' | 'layout' | 'recipe' | 'density';
|
|
119
|
+
severity: 'error' | 'warning';
|
|
120
|
+
message: string;
|
|
121
|
+
}
|
|
122
|
+
interface GuardContext {
|
|
123
|
+
pageId?: string;
|
|
124
|
+
style?: string;
|
|
125
|
+
recipe?: string;
|
|
126
|
+
layout?: string[];
|
|
127
|
+
density_gap?: string;
|
|
128
|
+
}
|
|
129
|
+
declare function evaluateGuard(essence: EssenceFile, context: GuardContext): GuardViolation[];
|
|
130
|
+
|
|
131
|
+
declare function normalizeEssence(input: Record<string, unknown>): EssenceFile;
|
|
132
|
+
|
|
133
|
+
export { type ColumnLayout, type Density, type DensityLevel, type Essence, type EssenceFile, type EssenceSection, type GeneratorTarget, type Guard, type GuardContext, type GuardMode, type GuardViolation, type Impression, type LayoutItem, type PatternRef, type Platform, type PlatformType, type RoutingStrategy, type SectionedEssence, type ShellType, type StructurePage, type Theme, type ThemeMode, type ThemeShape, type ThemeStyle, type ValidationResult, computeDensity, evaluateGuard, isSectioned, isSimple, normalizeEssence, validateEssence };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
function isSectioned(essence) {
|
|
3
|
+
return "sections" in essence && Array.isArray(essence.sections);
|
|
4
|
+
}
|
|
5
|
+
function isSimple(essence) {
|
|
6
|
+
return "archetype" in essence && !("sections" in essence);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// src/validate.ts
|
|
10
|
+
import Ajv from "ajv";
|
|
11
|
+
import { readFileSync } from "fs";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
var schemaPath = join(__dirname, "..", "schema", "essence.v2.json");
|
|
16
|
+
var cachedAjv = null;
|
|
17
|
+
var cachedValidate = null;
|
|
18
|
+
function getValidator() {
|
|
19
|
+
if (cachedValidate) return cachedValidate;
|
|
20
|
+
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;
|
|
24
|
+
}
|
|
25
|
+
function validateEssence(data) {
|
|
26
|
+
const validate = getValidator();
|
|
27
|
+
const valid = validate(data);
|
|
28
|
+
if (valid) {
|
|
29
|
+
return { valid: true, errors: [] };
|
|
30
|
+
}
|
|
31
|
+
const errors = (validate.errors ?? []).map((err) => {
|
|
32
|
+
const path = err.instancePath || "/";
|
|
33
|
+
const msg = err.message ?? "unknown error";
|
|
34
|
+
return `${path}: ${msg}`;
|
|
35
|
+
});
|
|
36
|
+
return { valid: false, errors };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/density.ts
|
|
40
|
+
var RULES = [
|
|
41
|
+
{ traits: ["tactical", "dense", "data-dense", "utilitarian"], level: "compact", gap: 3 },
|
|
42
|
+
{ traits: ["luxurious", "premium", "editorial"], level: "spacious", gap: 8 },
|
|
43
|
+
{ traits: ["playful", "bouncy", "expressive"], level: "comfortable", gap: 5 },
|
|
44
|
+
{ traits: ["minimal", "clean", "airy"], level: "spacious", gap: 6 },
|
|
45
|
+
{ traits: ["professional", "modern", "balanced"], level: "comfortable", gap: 4 }
|
|
46
|
+
];
|
|
47
|
+
var MIN_GAP = 2;
|
|
48
|
+
var MAX_GAP = 10;
|
|
49
|
+
function clamp(value, min, max) {
|
|
50
|
+
return Math.max(min, Math.min(max, value));
|
|
51
|
+
}
|
|
52
|
+
function computeDensity(personality, recipeSpatial) {
|
|
53
|
+
const lower = personality.map((c) => c.toLowerCase());
|
|
54
|
+
let level = "comfortable";
|
|
55
|
+
let gap = 4;
|
|
56
|
+
for (const rule of RULES) {
|
|
57
|
+
if (rule.traits.some((t) => lower.includes(t))) {
|
|
58
|
+
level = rule.level;
|
|
59
|
+
gap = rule.gap;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (recipeSpatial?.density_bias) {
|
|
64
|
+
gap += recipeSpatial.density_bias;
|
|
65
|
+
}
|
|
66
|
+
if (recipeSpatial?.content_gap_shift) {
|
|
67
|
+
gap += recipeSpatial.content_gap_shift;
|
|
68
|
+
}
|
|
69
|
+
gap = clamp(gap, MIN_GAP, MAX_GAP);
|
|
70
|
+
if (gap <= 3) level = "compact";
|
|
71
|
+
else if (gap >= 6) level = "spacious";
|
|
72
|
+
else level = "comfortable";
|
|
73
|
+
return { level, content_gap: String(gap) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/guard.ts
|
|
77
|
+
function evaluateGuard(essence, context) {
|
|
78
|
+
const guard = essence.guard;
|
|
79
|
+
if (guard.mode === "creative") {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const violations = [];
|
|
83
|
+
const isStrict = guard.mode === "strict";
|
|
84
|
+
if (context.style) {
|
|
85
|
+
const essenceStyle = isSimple(essence) ? essence.theme.style : null;
|
|
86
|
+
if (essenceStyle && context.style !== essenceStyle && guard.enforce_style !== false) {
|
|
87
|
+
violations.push({
|
|
88
|
+
rule: "style",
|
|
89
|
+
severity: "error",
|
|
90
|
+
message: `Style "${context.style}" does not match essence theme "${essenceStyle}". Change the essence theme first.`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (context.pageId) {
|
|
95
|
+
const pages = getAllPages(essence);
|
|
96
|
+
const pageExists = pages.some((p) => p.id === context.pageId);
|
|
97
|
+
if (!pageExists) {
|
|
98
|
+
violations.push({
|
|
99
|
+
rule: "structure",
|
|
100
|
+
severity: "error",
|
|
101
|
+
message: `Page "${context.pageId}" does not exist in essence structure. Add it to the essence first.`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (isStrict && context.pageId && context.layout) {
|
|
106
|
+
const pages = getAllPages(essence);
|
|
107
|
+
const page = pages.find((p) => p.id === context.pageId);
|
|
108
|
+
if (page) {
|
|
109
|
+
const essenceLayout = page.layout.map(
|
|
110
|
+
(item) => typeof item === "string" ? item : "pattern" in item ? item.pattern : "cols"
|
|
111
|
+
);
|
|
112
|
+
const proposedLayout = context.layout;
|
|
113
|
+
const matches = essenceLayout.length === proposedLayout.length && essenceLayout.every((item, i) => item === proposedLayout[i]);
|
|
114
|
+
if (!matches) {
|
|
115
|
+
violations.push({
|
|
116
|
+
rule: "layout",
|
|
117
|
+
severity: "error",
|
|
118
|
+
message: `Layout for page "${context.pageId}" deviates from essence. Expected: [${essenceLayout.join(", ")}].`
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
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
|
+
if (isStrict && context.density_gap) {
|
|
134
|
+
if (context.density_gap !== essence.density.content_gap) {
|
|
135
|
+
violations.push({
|
|
136
|
+
rule: "density",
|
|
137
|
+
severity: "warning",
|
|
138
|
+
message: `Content gap "${context.density_gap}" does not match essence density "${essence.density.content_gap}".`
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return violations;
|
|
143
|
+
}
|
|
144
|
+
function getAllPages(essence) {
|
|
145
|
+
if (isSimple(essence)) return essence.structure;
|
|
146
|
+
if (isSectioned(essence)) return essence.sections.flatMap((s) => s.structure);
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/normalize.ts
|
|
151
|
+
function normalizeEssence(input) {
|
|
152
|
+
if (input.theme && input.platform) {
|
|
153
|
+
return input;
|
|
154
|
+
}
|
|
155
|
+
if (input.sections) {
|
|
156
|
+
return normalizeSectioned(input);
|
|
157
|
+
}
|
|
158
|
+
return normalizeSimple(input);
|
|
159
|
+
}
|
|
160
|
+
function normalizeSimple(v1) {
|
|
161
|
+
const vintage = v1.vintage;
|
|
162
|
+
const vessel = v1.vessel;
|
|
163
|
+
const clarity = v1.clarity;
|
|
164
|
+
const cork = v1.cork;
|
|
165
|
+
const structure = v1.structure;
|
|
166
|
+
return {
|
|
167
|
+
version: "2.0.0",
|
|
168
|
+
archetype: v1.terroir ?? v1.vignette ?? v1.archetype,
|
|
169
|
+
theme: {
|
|
170
|
+
style: vintage?.style ?? "",
|
|
171
|
+
mode: vintage?.mode ?? "dark",
|
|
172
|
+
recipe: vintage?.recipe ?? "",
|
|
173
|
+
...vintage?.shape ? { shape: vintage.shape } : {}
|
|
174
|
+
},
|
|
175
|
+
// Accept both old 'character' and new 'personality' field names
|
|
176
|
+
personality: v1.character ?? v1.personality,
|
|
177
|
+
platform: {
|
|
178
|
+
type: vessel?.type ?? "spa",
|
|
179
|
+
routing: vessel?.routing ?? "hash"
|
|
180
|
+
},
|
|
181
|
+
structure: (structure ?? []).map(normalizeStructurePage),
|
|
182
|
+
features: v1.tannins ?? v1.features ?? [],
|
|
183
|
+
density: {
|
|
184
|
+
level: clarity?.density ?? "comfortable",
|
|
185
|
+
content_gap: stripGapPrefix(clarity?.content_gap ?? "4")
|
|
186
|
+
},
|
|
187
|
+
guard: normalizeGuard(cork),
|
|
188
|
+
target: v1.target ?? "decantr",
|
|
189
|
+
...v1._impression ? { _impression: v1._impression } : {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function normalizeSectioned(v1) {
|
|
193
|
+
const vessel = v1.vessel;
|
|
194
|
+
const clarity = v1.clarity;
|
|
195
|
+
const cork = v1.cork;
|
|
196
|
+
const sections = v1.sections;
|
|
197
|
+
return {
|
|
198
|
+
version: "2.0.0",
|
|
199
|
+
platform: {
|
|
200
|
+
type: vessel?.type ?? "spa",
|
|
201
|
+
routing: vessel?.routing ?? "hash"
|
|
202
|
+
},
|
|
203
|
+
// Accept both old 'character' and new 'personality' field names
|
|
204
|
+
personality: v1.character ?? v1.personality,
|
|
205
|
+
sections: sections.map((section) => ({
|
|
206
|
+
id: section.id,
|
|
207
|
+
path: section.path,
|
|
208
|
+
archetype: section.terroir ?? section.vignette ?? section.archetype,
|
|
209
|
+
theme: {
|
|
210
|
+
style: section.vintage?.style ?? "",
|
|
211
|
+
mode: section.vintage?.mode ?? "dark",
|
|
212
|
+
recipe: section.vintage?.recipe ?? ""
|
|
213
|
+
},
|
|
214
|
+
structure: (section.structure ?? []).map(normalizeStructurePage),
|
|
215
|
+
...section.tannins ? { features: section.tannins } : {}
|
|
216
|
+
})),
|
|
217
|
+
...v1.shared_tannins ? { shared_features: v1.shared_tannins } : {},
|
|
218
|
+
density: {
|
|
219
|
+
level: clarity?.density ?? "comfortable",
|
|
220
|
+
content_gap: stripGapPrefix(clarity?.content_gap ?? "4")
|
|
221
|
+
},
|
|
222
|
+
guard: normalizeGuard(cork),
|
|
223
|
+
target: v1.target ?? "decantr"
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function normalizeStructurePage(page) {
|
|
227
|
+
return {
|
|
228
|
+
id: page.id,
|
|
229
|
+
shell: page.carafe ?? page.shell,
|
|
230
|
+
layout: page.blend ?? page.layout,
|
|
231
|
+
...page.surface ? { surface: page.surface } : {}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function normalizeGuard(cork) {
|
|
235
|
+
if (!cork) return { mode: "creative" };
|
|
236
|
+
const modeMap = {
|
|
237
|
+
creative: "creative",
|
|
238
|
+
maintenance: "strict",
|
|
239
|
+
strict: "strict",
|
|
240
|
+
guided: "guided"
|
|
241
|
+
};
|
|
242
|
+
return {
|
|
243
|
+
...cork.enforce_style !== void 0 ? { enforce_style: cork.enforce_style } : {},
|
|
244
|
+
...cork.enforce_recipe !== void 0 ? { enforce_recipe: cork.enforce_recipe } : {},
|
|
245
|
+
mode: modeMap[cork.mode ?? "creative"] ?? "creative"
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function stripGapPrefix(gap) {
|
|
249
|
+
const match = gap.match(/^_gap(\d+)$/);
|
|
250
|
+
return match ? match[1] : gap;
|
|
251
|
+
}
|
|
252
|
+
export {
|
|
253
|
+
computeDensity,
|
|
254
|
+
evaluateGuard,
|
|
255
|
+
isSectioned,
|
|
256
|
+
isSimple,
|
|
257
|
+
normalizeEssence,
|
|
258
|
+
validateEssence
|
|
259
|
+
};
|
|
260
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/validate.ts","../src/density.ts","../src/guard.ts","../src/normalize.ts"],"sourcesContent":["/**\n * Essence v2 schema types — normalized terminology.\n *\n * Terminology mapping (v1 wine terms → v2 normalized):\n * terroir → archetype, vintage → theme, tannins → features,\n * carafe → shell, cork → guard, blend → layout, clarity → density,\n * vessel → platform, character → personality,\n * decantation process → essence pipeline\n */\n\n// --- Theme ---\n\nexport type ThemeStyle =\n | 'auradecantism'\n | 'clean'\n | 'glassmorphism'\n | 'retro'\n | 'bioluminescent'\n | 'launchpad'\n | 'dopamine'\n | 'editorial'\n | 'liquid-glass'\n | 'prismatic';\n\nexport type ThemeMode = 'light' | 'dark' | 'auto';\nexport type ThemeShape = 'sharp' | 'rounded' | 'pill';\n\nexport interface Theme {\n style: ThemeStyle | string;\n mode: ThemeMode;\n recipe: string;\n shape?: ThemeShape;\n}\n\n// --- Platform ---\n\nexport type PlatformType = 'spa' | 'ssr' | 'static';\nexport type RoutingStrategy = 'hash' | 'history';\n\nexport interface Platform {\n type: PlatformType;\n routing: RoutingStrategy;\n}\n\n// --- Structure ---\n\nexport type ShellType =\n | 'sidebar-main'\n | 'top-nav-main'\n | 'full-bleed'\n | 'dashboard'\n | 'stacked'\n | string;\n\nexport type LayoutItem = string | PatternRef | ColumnLayout;\n\nexport interface PatternRef {\n pattern: string;\n preset?: string;\n as?: string;\n}\n\nexport interface ColumnLayout {\n cols: string[];\n at?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n span?: Record<string, number>;\n // AUTO: Multi-breakpoint grid — array of { at, cols } entries for cascading breakpoints\n breakpoints?: { at: string; cols: number }[];\n // AUTO: \"container\" uses container queries instead of viewport breakpoints\n responsive?: 'viewport' | 'container';\n}\n\nexport interface StructurePage {\n id: string;\n shell: ShellType;\n layout: LayoutItem[];\n surface?: string;\n}\n\n// --- Density ---\n\nexport type DensityLevel = 'compact' | 'comfortable' | 'spacious';\n\nexport interface Density {\n level: DensityLevel;\n content_gap: string;\n}\n\n// --- Guard ---\n\nexport type GuardMode = 'creative' | 'guided' | 'strict';\n\nexport interface Guard {\n enforce_style?: boolean;\n enforce_recipe?: boolean;\n mode: GuardMode;\n}\n\n// --- Impression ---\n\nexport interface Impression {\n vibe: string[];\n references: string[];\n density_intent: DensityLevel | string;\n layout_intent: ShellType | string;\n novel_elements: string[];\n}\n\n// --- Generator Target ---\n\nexport type GeneratorTarget = 'decantr' | 'react' | 'vue' | 'svelte' | string;\n\n// --- Essence (simple) ---\n\nexport interface Essence {\n $schema?: string;\n version: string;\n archetype: string;\n theme: Theme;\n personality: string[];\n platform: Platform;\n structure: StructurePage[];\n features: string[];\n density: Density;\n guard: Guard;\n target: GeneratorTarget;\n _impression?: Impression;\n}\n\n// --- Essence (sectioned) ---\n\nexport interface EssenceSection {\n id: string;\n path: string;\n archetype: string;\n theme: Theme;\n structure: StructurePage[];\n features?: string[];\n}\n\nexport interface SectionedEssence {\n $schema?: string;\n version: string;\n platform: Platform;\n personality: string[];\n sections: EssenceSection[];\n shared_features?: string[];\n density: Density;\n guard: Guard;\n target: GeneratorTarget;\n _impression?: Impression;\n}\n\nexport type EssenceFile = Essence | SectionedEssence;\n\nexport function isSectioned(essence: EssenceFile): essence is SectionedEssence {\n return 'sections' in essence && Array.isArray((essence as SectionedEssence).sections);\n}\n\nexport function isSimple(essence: EssenceFile): essence is Essence {\n return 'archetype' in essence && !('sections' in essence);\n}\n","import Ajv from 'ajv';\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst schemaPath = join(__dirname, '..', 'schema', 'essence.v2.json');\n\nlet cachedAjv: Ajv | null = null;\nlet cachedValidate: ReturnType<Ajv['compile']> | null = null;\n\nfunction getValidator() {\n if (cachedValidate) return cachedValidate;\n const schema = JSON.parse(readFileSync(schemaPath, 'utf-8'));\n cachedAjv = new Ajv({ allErrors: true, strict: false, validateSchema: false });\n cachedValidate = cachedAjv.compile(schema);\n return cachedValidate;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n}\n\nexport function validateEssence(data: unknown): ValidationResult {\n const validate = getValidator();\n const valid = validate(data);\n\n if (valid) {\n return { valid: true, errors: [] };\n }\n\n const errors = (validate.errors ?? []).map((err) => {\n const path = err.instancePath || '/';\n const msg = err.message ?? 'unknown error';\n return `${path}: ${msg}`;\n });\n\n return { valid: false, errors };\n}\n","import type { Density, DensityLevel } from './types.js';\n\ninterface RecipeSpatialHints {\n density_bias?: number;\n content_gap_shift?: number;\n}\n\ninterface DensityRule {\n traits: string[];\n level: DensityLevel;\n gap: number;\n}\n\nconst RULES: DensityRule[] = [\n { traits: ['tactical', 'dense', 'data-dense', 'utilitarian'], level: 'compact', gap: 3 },\n { traits: ['luxurious', 'premium', 'editorial'], level: 'spacious', gap: 8 },\n { traits: ['playful', 'bouncy', 'expressive'], level: 'comfortable', gap: 5 },\n { traits: ['minimal', 'clean', 'airy'], level: 'spacious', gap: 6 },\n { traits: ['professional', 'modern', 'balanced'], level: 'comfortable', gap: 4 },\n];\n\nconst MIN_GAP = 2;\nconst MAX_GAP = 10;\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\nexport function computeDensity(\n personality: string[],\n recipeSpatial?: RecipeSpatialHints,\n): Density {\n const lower = personality.map(c => c.toLowerCase());\n\n let level: DensityLevel = 'comfortable';\n let gap = 4;\n\n for (const rule of RULES) {\n if (rule.traits.some(t => lower.includes(t))) {\n level = rule.level;\n gap = rule.gap;\n break;\n }\n }\n\n if (recipeSpatial?.density_bias) {\n gap += recipeSpatial.density_bias;\n }\n if (recipeSpatial?.content_gap_shift) {\n gap += recipeSpatial.content_gap_shift;\n }\n\n gap = clamp(gap, MIN_GAP, MAX_GAP);\n\n if (gap <= 3) level = 'compact';\n else if (gap >= 6) level = 'spacious';\n else level = 'comfortable';\n\n return { level, content_gap: String(gap) };\n}\n","import type { EssenceFile, StructurePage } from './types.js';\nimport { isSimple, isSectioned } from './types.js';\n\nexport interface GuardViolation {\n rule: 'style' | 'structure' | 'layout' | 'recipe' | 'density';\n severity: 'error' | 'warning';\n message: string;\n}\n\nexport interface GuardContext {\n pageId?: string;\n style?: string;\n recipe?: string;\n layout?: string[];\n density_gap?: string;\n}\n\nexport function evaluateGuard(essence: EssenceFile, context: GuardContext): GuardViolation[] {\n const guard = essence.guard;\n\n if (guard.mode === 'creative') {\n return [];\n }\n\n const violations: GuardViolation[] = [];\n const isStrict = guard.mode === 'strict';\n\n // Rule 1: Style guard\n if (context.style) {\n const essenceStyle = isSimple(essence) ? essence.theme.style : null;\n if (essenceStyle && context.style !== essenceStyle && guard.enforce_style !== false) {\n violations.push({\n rule: 'style',\n severity: 'error',\n message: `Style \"${context.style}\" does not match essence theme \"${essenceStyle}\". Change the essence theme first.`,\n });\n }\n }\n\n // Rule 2: Structure guard (enforced in both guided and strict)\n if (context.pageId) {\n const pages = getAllPages(essence);\n const pageExists = pages.some(p => p.id === context.pageId);\n if (!pageExists) {\n violations.push({\n rule: 'structure',\n severity: 'error',\n message: `Page \"${context.pageId}\" does not exist in essence structure. Add it to the essence first.`,\n });\n }\n }\n\n // Rule 3: Layout guard (strict only)\n if (isStrict && context.pageId && context.layout) {\n const pages = getAllPages(essence);\n const page = pages.find(p => p.id === context.pageId);\n if (page) {\n const essenceLayout = page.layout.map(item =>\n typeof item === 'string' ? item : 'pattern' in item ? item.pattern : 'cols'\n );\n const proposedLayout = context.layout;\n const matches = essenceLayout.length === proposedLayout.length &&\n essenceLayout.every((item, i) => item === proposedLayout[i]);\n if (!matches) {\n violations.push({\n rule: 'layout',\n severity: 'error',\n message: `Layout for page \"${context.pageId}\" deviates from essence. Expected: [${essenceLayout.join(', ')}].`,\n });\n }\n }\n }\n\n // Rule 4: Recipe guard\n if (context.recipe && guard.enforce_recipe !== false) {\n const essenceRecipe = isSimple(essence) ? essence.theme.recipe : null;\n if (essenceRecipe && context.recipe !== essenceRecipe) {\n violations.push({\n rule: 'recipe',\n severity: 'error',\n message: `Recipe \"${context.recipe}\" does not match essence recipe \"${essenceRecipe}\".`,\n });\n }\n }\n\n // Rule 5: Density guard (strict only)\n if (isStrict && context.density_gap) {\n if (context.density_gap !== essence.density.content_gap) {\n violations.push({\n rule: 'density',\n severity: 'warning',\n message: `Content gap \"${context.density_gap}\" does not match essence density \"${essence.density.content_gap}\".`,\n });\n }\n }\n\n return violations;\n}\n\nfunction getAllPages(essence: EssenceFile): StructurePage[] {\n if (isSimple(essence)) return essence.structure;\n if (isSectioned(essence)) return essence.sections.flatMap(s => s.structure);\n return [];\n}\n","import type { Essence, SectionedEssence, EssenceFile, StructurePage, GuardMode } from './types.js';\n\nexport function normalizeEssence(input: Record<string, unknown>): EssenceFile {\n // Detect v2: has 'theme' and 'platform' (normalized terms)\n if (input.theme && input.platform) {\n return input as unknown as EssenceFile;\n }\n\n if (input.sections) {\n return normalizeSectioned(input);\n }\n\n return normalizeSimple(input);\n}\n\nfunction normalizeSimple(v1: Record<string, unknown>): Essence {\n // v1 field names (wine terms) → v2 normalized names:\n // vintage → theme, vessel → platform, clarity → density,\n // cork → guard, terroir/vignette → archetype, tannins → features,\n // carafe → shell, blend → layout, character → personality\n const vintage = v1.vintage as Record<string, string> | undefined;\n const vessel = v1.vessel as Record<string, string> | undefined;\n const clarity = v1.clarity as Record<string, string> | undefined;\n const cork = v1.cork as Record<string, unknown> | undefined;\n const structure = v1.structure as Array<Record<string, unknown>> | undefined;\n\n return {\n version: '2.0.0',\n archetype: (v1.terroir ?? v1.vignette ?? v1.archetype) as string,\n theme: {\n style: vintage?.style ?? '',\n mode: (vintage?.mode ?? 'dark') as 'light' | 'dark' | 'auto',\n recipe: vintage?.recipe ?? '',\n ...(vintage?.shape ? { shape: vintage.shape as 'sharp' | 'rounded' | 'pill' } : {}),\n },\n // Accept both old 'character' and new 'personality' field names\n personality: (v1.character ?? v1.personality) as string[],\n platform: {\n type: (vessel?.type ?? 'spa') as 'spa' | 'ssr' | 'static',\n routing: (vessel?.routing ?? 'hash') as 'hash' | 'history',\n },\n structure: (structure ?? []).map(normalizeStructurePage),\n features: (v1.tannins ?? v1.features ?? []) as string[],\n density: {\n level: (clarity?.density ?? 'comfortable') as 'compact' | 'comfortable' | 'spacious',\n content_gap: stripGapPrefix(clarity?.content_gap ?? '4'),\n },\n guard: normalizeGuard(cork),\n target: (v1.target as string) ?? 'decantr',\n ...(v1._impression ? { _impression: v1._impression as Essence['_impression'] } : {}),\n };\n}\n\nfunction normalizeSectioned(v1: Record<string, unknown>): SectionedEssence {\n // v1 field names (wine terms) → v2 normalized names:\n // vessel → platform, clarity → density, cork → guard,\n // terroir/vignette → archetype, tannins → features,\n // shared_tannins → shared_features, character → personality\n const vessel = v1.vessel as Record<string, string> | undefined;\n const clarity = v1.clarity as Record<string, string> | undefined;\n const cork = v1.cork as Record<string, unknown> | undefined;\n const sections = v1.sections as Array<Record<string, unknown>>;\n\n return {\n version: '2.0.0',\n platform: {\n type: (vessel?.type ?? 'spa') as 'spa' | 'ssr' | 'static',\n routing: (vessel?.routing ?? 'hash') as 'hash' | 'history',\n },\n // Accept both old 'character' and new 'personality' field names\n personality: (v1.character ?? v1.personality) as string[],\n sections: sections.map(section => ({\n id: section.id as string,\n path: section.path as string,\n archetype: (section.terroir ?? section.vignette ?? section.archetype) as string,\n theme: {\n style: (section.vintage as Record<string, string>)?.style ?? '',\n mode: ((section.vintage as Record<string, string>)?.mode ?? 'dark') as 'light' | 'dark' | 'auto',\n recipe: (section.vintage as Record<string, string>)?.recipe ?? '',\n },\n structure: ((section.structure as Array<Record<string, unknown>>) ?? []).map(normalizeStructurePage),\n ...(section.tannins ? { features: section.tannins as string[] } : {}),\n })),\n ...(v1.shared_tannins ? { shared_features: v1.shared_tannins as string[] } : {}),\n density: {\n level: (clarity?.density ?? 'comfortable') as 'compact' | 'comfortable' | 'spacious',\n content_gap: stripGapPrefix(clarity?.content_gap ?? '4'),\n },\n guard: normalizeGuard(cork),\n target: (v1.target as string) ?? 'decantr',\n };\n}\n\nfunction normalizeStructurePage(page: Record<string, unknown>): StructurePage {\n // Accept v1 wine terms (carafe → shell, blend → layout) for backward compatibility\n return {\n id: page.id as string,\n shell: (page.carafe ?? page.shell) as string,\n layout: (page.blend ?? page.layout) as StructurePage['layout'],\n ...(page.surface ? { surface: page.surface as string } : {}),\n };\n}\n\n// Accept v1 'cork' object and normalize to v2 'guard'\nfunction normalizeGuard(cork: Record<string, unknown> | undefined): Essence['guard'] {\n if (!cork) return { mode: 'creative' };\n\n const modeMap: Record<string, GuardMode> = {\n creative: 'creative',\n maintenance: 'strict',\n strict: 'strict',\n guided: 'guided',\n };\n\n return {\n ...(cork.enforce_style !== undefined ? { enforce_style: cork.enforce_style as boolean } : {}),\n ...(cork.enforce_recipe !== undefined ? { enforce_recipe: cork.enforce_recipe as boolean } : {}),\n mode: modeMap[(cork.mode as string) ?? 'creative'] ?? 'creative',\n };\n}\n\nfunction stripGapPrefix(gap: string): string {\n const match = gap.match(/^_gap(\\d+)$/);\n return match ? match[1] : gap;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@decantr/essence-spec",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Essence schema, validator, and TypeScript types for Decantr",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/decantr-ai/decantr.git",
|
|
9
|
+
"directory": "packages/essence-spec"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://decantr.ai",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./schema": {
|
|
21
|
+
"import": "./schema/essence.v2.json"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": ["dist", "schema"],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"ajv": "^8.17.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://decantr.ai/schema/essence/2.0.json",
|
|
4
|
+
"title": "Decantr Essence v2",
|
|
5
|
+
"description": "Design intent specification for AI-generated web applications",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{ "$ref": "#/$defs/SimpleEssence" },
|
|
8
|
+
{ "$ref": "#/$defs/SectionedEssence" }
|
|
9
|
+
],
|
|
10
|
+
"$defs": {
|
|
11
|
+
"SimpleEssence": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"required": ["version", "archetype", "theme", "personality", "platform", "structure", "features", "density", "guard", "target"],
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"properties": {
|
|
16
|
+
"$schema": { "type": "string" },
|
|
17
|
+
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
|
|
18
|
+
"archetype": { "type": "string", "minLength": 1 },
|
|
19
|
+
"theme": { "$ref": "#/$defs/Theme" },
|
|
20
|
+
"personality": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
|
|
21
|
+
"platform": { "$ref": "#/$defs/Platform" },
|
|
22
|
+
"structure": { "type": "array", "items": { "$ref": "#/$defs/StructurePage" }, "minItems": 1 },
|
|
23
|
+
"features": { "type": "array", "items": { "type": "string" } },
|
|
24
|
+
"density": { "$ref": "#/$defs/Density" },
|
|
25
|
+
"guard": { "$ref": "#/$defs/Guard" },
|
|
26
|
+
"target": { "type": "string", "minLength": 1 },
|
|
27
|
+
"_impression": { "$ref": "#/$defs/Impression" }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"SectionedEssence": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"required": ["version", "platform", "personality", "sections", "density", "guard", "target"],
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"properties": {
|
|
35
|
+
"$schema": { "type": "string" },
|
|
36
|
+
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
|
|
37
|
+
"platform": { "$ref": "#/$defs/Platform" },
|
|
38
|
+
"personality": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
|
|
39
|
+
"sections": { "type": "array", "items": { "$ref": "#/$defs/Section" }, "minItems": 1 },
|
|
40
|
+
"shared_features": { "type": "array", "items": { "type": "string" } },
|
|
41
|
+
"density": { "$ref": "#/$defs/Density" },
|
|
42
|
+
"guard": { "$ref": "#/$defs/Guard" },
|
|
43
|
+
"target": { "type": "string", "minLength": 1 },
|
|
44
|
+
"_impression": { "$ref": "#/$defs/Impression" }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"Theme": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"required": ["style", "mode", "recipe"],
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"properties": {
|
|
52
|
+
"style": { "type": "string", "minLength": 1 },
|
|
53
|
+
"mode": { "type": "string", "enum": ["light", "dark", "auto"] },
|
|
54
|
+
"recipe": { "type": "string", "minLength": 1 },
|
|
55
|
+
"shape": { "type": "string", "enum": ["sharp", "rounded", "pill"] }
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"Platform": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"required": ["type", "routing"],
|
|
61
|
+
"additionalProperties": false,
|
|
62
|
+
"properties": {
|
|
63
|
+
"type": { "type": "string", "enum": ["spa", "ssr", "static"] },
|
|
64
|
+
"routing": { "type": "string", "enum": ["hash", "history"] }
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"StructurePage": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"required": ["id", "shell", "layout"],
|
|
70
|
+
"additionalProperties": false,
|
|
71
|
+
"properties": {
|
|
72
|
+
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
73
|
+
"shell": { "type": "string", "minLength": 1 },
|
|
74
|
+
"layout": { "type": "array", "items": { "$ref": "#/$defs/LayoutItem" }, "minItems": 1 },
|
|
75
|
+
"surface": { "type": "string" }
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"LayoutItem": {
|
|
79
|
+
"oneOf": [
|
|
80
|
+
{ "type": "string", "minLength": 1 },
|
|
81
|
+
{ "$ref": "#/$defs/PatternRef" },
|
|
82
|
+
{ "$ref": "#/$defs/ColumnLayout" }
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
"PatternRef": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"required": ["pattern"],
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"properties": {
|
|
90
|
+
"pattern": { "type": "string", "minLength": 1 },
|
|
91
|
+
"preset": { "type": "string" },
|
|
92
|
+
"as": { "type": "string" }
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"ColumnLayout": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"required": ["cols"],
|
|
98
|
+
"additionalProperties": false,
|
|
99
|
+
"properties": {
|
|
100
|
+
"cols": { "type": "array", "items": { "type": "string" }, "minItems": 2 },
|
|
101
|
+
"at": { "type": "string", "enum": ["sm", "md", "lg", "xl", "2xl"] },
|
|
102
|
+
"span": { "type": "object", "additionalProperties": { "type": "number", "minimum": 1 } }
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"Density": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"required": ["level", "content_gap"],
|
|
108
|
+
"additionalProperties": false,
|
|
109
|
+
"properties": {
|
|
110
|
+
"level": { "type": "string", "enum": ["compact", "comfortable", "spacious"] },
|
|
111
|
+
"content_gap": { "type": "string" }
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"Guard": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"required": ["mode"],
|
|
117
|
+
"additionalProperties": false,
|
|
118
|
+
"properties": {
|
|
119
|
+
"enforce_style": { "type": "boolean" },
|
|
120
|
+
"enforce_recipe": { "type": "boolean" },
|
|
121
|
+
"mode": { "type": "string", "enum": ["creative", "guided", "strict"] }
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"Impression": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"additionalProperties": false,
|
|
127
|
+
"properties": {
|
|
128
|
+
"vibe": { "type": "array", "items": { "type": "string" } },
|
|
129
|
+
"references": { "type": "array", "items": { "type": "string" } },
|
|
130
|
+
"density_intent": { "type": "string" },
|
|
131
|
+
"layout_intent": { "type": "string" },
|
|
132
|
+
"novel_elements": { "type": "array", "items": { "type": "string" } }
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"Section": {
|
|
136
|
+
"type": "object",
|
|
137
|
+
"required": ["id", "path", "archetype", "theme", "structure"],
|
|
138
|
+
"additionalProperties": false,
|
|
139
|
+
"properties": {
|
|
140
|
+
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
141
|
+
"path": { "type": "string", "pattern": "^/" },
|
|
142
|
+
"archetype": { "type": "string", "minLength": 1 },
|
|
143
|
+
"theme": { "$ref": "#/$defs/Theme" },
|
|
144
|
+
"structure": { "type": "array", "items": { "$ref": "#/$defs/StructurePage" }, "minItems": 1 },
|
|
145
|
+
"features": { "type": "array", "items": { "type": "string" } }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|