@decantr/cli 1.3.0 → 1.5.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/dist/bin.js +2 -2
- package/dist/chunk-6K6ZPDT4.js +2249 -0
- package/dist/{chunk-2F6Q5S5W.js → chunk-SUNMRG3P.js} +257 -2038
- package/dist/index.js +2 -2
- package/dist/upgrade-I2RUTNAT.js +131 -0
- package/package.json +3 -3
- package/dist/chunk-CT5XHG6I.js +0 -203
- package/dist/upgrade-ZSLT32KN.js +0 -66
|
@@ -0,0 +1,2249 @@
|
|
|
1
|
+
// src/scaffold.ts
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
function composeArchetypes(composeEntries, archetypeResults) {
|
|
7
|
+
if (composeEntries.length === 0) {
|
|
8
|
+
return {
|
|
9
|
+
pages: [{ id: "home", layout: ["hero"] }],
|
|
10
|
+
features: [],
|
|
11
|
+
defaultShell: "sidebar-main"
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const allPages = [];
|
|
15
|
+
const allFeatures = [];
|
|
16
|
+
let defaultShell = "sidebar-main";
|
|
17
|
+
for (let i = 0; i < composeEntries.length; i++) {
|
|
18
|
+
const entry = composeEntries[i];
|
|
19
|
+
const archetypeId = typeof entry === "string" ? entry : entry.archetype;
|
|
20
|
+
const data = archetypeResults.get(archetypeId);
|
|
21
|
+
if (!data?.pages) continue;
|
|
22
|
+
const isPrimary = i === 0;
|
|
23
|
+
if (isPrimary) {
|
|
24
|
+
defaultShell = data.pages[0]?.shell || defaultShell;
|
|
25
|
+
for (const page of data.pages) {
|
|
26
|
+
allPages.push({
|
|
27
|
+
id: page.id,
|
|
28
|
+
layout: page.default_layout?.length ? page.default_layout : ["hero"],
|
|
29
|
+
...page.shell !== defaultShell ? { shell_override: page.shell } : {}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
const prefix = typeof entry === "string" ? entry : entry.prefix;
|
|
34
|
+
for (const page of data.pages) {
|
|
35
|
+
allPages.push({
|
|
36
|
+
id: `${prefix}-${page.id}`,
|
|
37
|
+
layout: page.default_layout?.length ? page.default_layout : ["hero"],
|
|
38
|
+
...page.shell !== defaultShell ? { shell_override: page.shell } : {}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (data.features) {
|
|
43
|
+
allFeatures.push(...data.features);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (allPages.length === 0) {
|
|
47
|
+
allPages.push({ id: "home", layout: ["hero"] });
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
pages: allPages,
|
|
51
|
+
features: [...new Set(allFeatures)],
|
|
52
|
+
defaultShell
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function composeSections(composeEntries, archetypeResults, overrides) {
|
|
56
|
+
if (composeEntries.length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
sections: [{
|
|
59
|
+
id: "default",
|
|
60
|
+
role: "primary",
|
|
61
|
+
shell: "sidebar-main",
|
|
62
|
+
features: [],
|
|
63
|
+
description: "Default section",
|
|
64
|
+
pages: [{ id: "home", layout: ["hero"] }]
|
|
65
|
+
}],
|
|
66
|
+
features: [],
|
|
67
|
+
defaultShell: "sidebar-main"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const sections = [];
|
|
71
|
+
const allFeatures = [];
|
|
72
|
+
let defaultShell = "sidebar-main";
|
|
73
|
+
const pagesRemoveSet = new Set(overrides?.pages_remove ?? []);
|
|
74
|
+
for (let i = 0; i < composeEntries.length; i++) {
|
|
75
|
+
const entry = composeEntries[i];
|
|
76
|
+
const archetypeId = typeof entry === "string" ? entry : entry.archetype;
|
|
77
|
+
const data = archetypeResults.get(archetypeId);
|
|
78
|
+
if (!data?.pages) continue;
|
|
79
|
+
const isPrimary = i === 0;
|
|
80
|
+
if (isPrimary) {
|
|
81
|
+
defaultShell = data.pages[0]?.shell || defaultShell;
|
|
82
|
+
}
|
|
83
|
+
const pages = [];
|
|
84
|
+
for (const page of data.pages) {
|
|
85
|
+
if (pagesRemoveSet.has(page.id)) continue;
|
|
86
|
+
const overriddenPage = overrides?.pages?.[page.id];
|
|
87
|
+
pages.push({
|
|
88
|
+
id: page.id,
|
|
89
|
+
layout: page.default_layout?.length ? page.default_layout : ["hero"],
|
|
90
|
+
...overriddenPage
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
sections.push({
|
|
94
|
+
id: archetypeId,
|
|
95
|
+
role: data.role ?? "primary",
|
|
96
|
+
shell: data.pages[0]?.shell || "sidebar-main",
|
|
97
|
+
features: data.features ?? [],
|
|
98
|
+
description: data.description ?? "",
|
|
99
|
+
pages
|
|
100
|
+
});
|
|
101
|
+
if (data.features) {
|
|
102
|
+
allFeatures.push(...data.features);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (sections.length === 0) {
|
|
106
|
+
sections.push({
|
|
107
|
+
id: "default",
|
|
108
|
+
role: "primary",
|
|
109
|
+
shell: "sidebar-main",
|
|
110
|
+
features: [],
|
|
111
|
+
description: "Default section",
|
|
112
|
+
pages: [{ id: "home", layout: ["hero"] }]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (overrides?.section_dna_overrides) {
|
|
116
|
+
for (const section of sections) {
|
|
117
|
+
const sectionOverrides = overrides.section_dna_overrides[section.id];
|
|
118
|
+
if (sectionOverrides) {
|
|
119
|
+
section.dna_overrides = { ...section.dna_overrides, ...sectionOverrides };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const primaryShell = sections.find((s) => s.role === "primary")?.shell || defaultShell;
|
|
124
|
+
for (const section of sections) {
|
|
125
|
+
if (section.shell === "inherit") {
|
|
126
|
+
section.shell = primaryShell;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let features = [...new Set(allFeatures)];
|
|
130
|
+
if (overrides?.features_add) {
|
|
131
|
+
for (const f of overrides.features_add) {
|
|
132
|
+
if (!features.includes(f)) features.push(f);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (overrides?.features_remove) {
|
|
136
|
+
const removeSet = new Set(overrides.features_remove);
|
|
137
|
+
features = features.filter((f) => !removeSet.has(f));
|
|
138
|
+
}
|
|
139
|
+
return { sections, features, defaultShell };
|
|
140
|
+
}
|
|
141
|
+
var ZONE_ORDER = ["public", "gateway", "primary", "auxiliary"];
|
|
142
|
+
function deriveZones(inputs) {
|
|
143
|
+
const zoneMap = /* @__PURE__ */ new Map();
|
|
144
|
+
for (const input of inputs) {
|
|
145
|
+
const existing = zoneMap.get(input.role);
|
|
146
|
+
if (existing) {
|
|
147
|
+
existing.archetypes.push(input.archetypeId);
|
|
148
|
+
existing.features.push(...input.features);
|
|
149
|
+
existing.descriptions.push(input.description);
|
|
150
|
+
} else {
|
|
151
|
+
zoneMap.set(input.role, {
|
|
152
|
+
role: input.role,
|
|
153
|
+
archetypes: [input.archetypeId],
|
|
154
|
+
shell: input.shell,
|
|
155
|
+
features: [...input.features],
|
|
156
|
+
descriptions: [input.description]
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const zone of zoneMap.values()) {
|
|
161
|
+
zone.features = [...new Set(zone.features)];
|
|
162
|
+
}
|
|
163
|
+
return ZONE_ORDER.filter((role) => zoneMap.has(role)).map((role) => zoneMap.get(role));
|
|
164
|
+
}
|
|
165
|
+
var GATEWAY_TRIGGER_MAP = {
|
|
166
|
+
auth: "authentication",
|
|
167
|
+
login: "authentication",
|
|
168
|
+
mfa: "authentication",
|
|
169
|
+
payment: "payment",
|
|
170
|
+
subscription: "payment",
|
|
171
|
+
checkout: "payment",
|
|
172
|
+
onboarding: "onboarding",
|
|
173
|
+
"setup-wizard": "onboarding",
|
|
174
|
+
welcome: "onboarding",
|
|
175
|
+
invite: "invitation",
|
|
176
|
+
"access-code": "invitation"
|
|
177
|
+
};
|
|
178
|
+
function resolveGatewayTrigger(features) {
|
|
179
|
+
for (const feature of features) {
|
|
180
|
+
const trigger = GATEWAY_TRIGGER_MAP[feature];
|
|
181
|
+
if (trigger) return trigger;
|
|
182
|
+
}
|
|
183
|
+
return "authentication";
|
|
184
|
+
}
|
|
185
|
+
function deriveTransitions(zones) {
|
|
186
|
+
const transitions = [];
|
|
187
|
+
const roles = new Set(zones.map((z) => z.role));
|
|
188
|
+
const gateway = zones.find((z) => z.role === "gateway");
|
|
189
|
+
const gatewayTrigger = gateway ? resolveGatewayTrigger(gateway.features) : "authentication";
|
|
190
|
+
const hasApp = roles.has("primary") || roles.has("auxiliary");
|
|
191
|
+
const hasGateway = roles.has("gateway");
|
|
192
|
+
const hasPublic = roles.has("public");
|
|
193
|
+
if (hasPublic && hasGateway) {
|
|
194
|
+
transitions.push({ from: "public", to: "gateway", type: "conversion", trigger: gatewayTrigger });
|
|
195
|
+
}
|
|
196
|
+
if (hasPublic && hasApp && !hasGateway) {
|
|
197
|
+
transitions.push({ from: "public", to: "app", type: "conversion", trigger: "navigation" });
|
|
198
|
+
}
|
|
199
|
+
if (hasGateway && hasApp) {
|
|
200
|
+
transitions.push({ from: "gateway", to: "app", type: "gate-pass", trigger: gatewayTrigger });
|
|
201
|
+
transitions.push({ from: "app", to: "gateway", type: "gate-return", trigger: gatewayTrigger });
|
|
202
|
+
}
|
|
203
|
+
if (hasApp && hasPublic) {
|
|
204
|
+
transitions.push({ from: "app", to: "public", type: "navigation", trigger: "external" });
|
|
205
|
+
}
|
|
206
|
+
return transitions;
|
|
207
|
+
}
|
|
208
|
+
var ZONE_LABELS = {
|
|
209
|
+
public: "Public",
|
|
210
|
+
gateway: "Gateway",
|
|
211
|
+
primary: "App",
|
|
212
|
+
auxiliary: "App (auxiliary)"
|
|
213
|
+
};
|
|
214
|
+
function generateTopologySection(data, personality) {
|
|
215
|
+
const lines = [];
|
|
216
|
+
lines.push("## Composition Topology");
|
|
217
|
+
lines.push("");
|
|
218
|
+
lines.push(`**Intent:** ${data.intent}`);
|
|
219
|
+
lines.push("");
|
|
220
|
+
lines.push("### Zones");
|
|
221
|
+
lines.push("");
|
|
222
|
+
for (const zone of data.zones) {
|
|
223
|
+
const label = ZONE_LABELS[zone.role] || zone.role;
|
|
224
|
+
lines.push(`**${label}** \u2014 ${zone.shell} shell`);
|
|
225
|
+
lines.push(` Archetypes: ${zone.archetypes.join(", ")}`);
|
|
226
|
+
lines.push(` Purpose: ${zone.descriptions.join(" ")}`);
|
|
227
|
+
if (personality.length > 0) {
|
|
228
|
+
lines.push(` Tone: ${personality.join(", ")}`);
|
|
229
|
+
}
|
|
230
|
+
if (zone.features.length > 0) {
|
|
231
|
+
lines.push(` Features: ${zone.features.join(", ")}`);
|
|
232
|
+
}
|
|
233
|
+
lines.push("");
|
|
234
|
+
}
|
|
235
|
+
if (data.transitions.length > 0) {
|
|
236
|
+
lines.push("### Zone Transitions");
|
|
237
|
+
lines.push("");
|
|
238
|
+
for (const t of data.transitions) {
|
|
239
|
+
const fromLabel = t.from.charAt(0).toUpperCase() + t.from.slice(1);
|
|
240
|
+
const toLabel = t.to.charAt(0).toUpperCase() + t.to.slice(1);
|
|
241
|
+
lines.push(` ${fromLabel} \u2192 ${toLabel}: ${t.type} (${t.trigger})`);
|
|
242
|
+
}
|
|
243
|
+
lines.push("");
|
|
244
|
+
}
|
|
245
|
+
lines.push("### Default Entry Points");
|
|
246
|
+
lines.push("");
|
|
247
|
+
lines.push(` Anonymous users enter: ${data.entryPoints.anonymous}`);
|
|
248
|
+
lines.push(` Authenticated users enter: ${data.entryPoints.authenticated}`);
|
|
249
|
+
lines.push(` Auth redirect target: ${data.entryPoints.authenticated}`);
|
|
250
|
+
lines.push("");
|
|
251
|
+
return lines.join("\n");
|
|
252
|
+
}
|
|
253
|
+
var CLI_VERSION = "1.0.0";
|
|
254
|
+
function generateTokensCSS(themeData, mode) {
|
|
255
|
+
if (!themeData) {
|
|
256
|
+
return `/* No theme data available */
|
|
257
|
+
:root {
|
|
258
|
+
--d-primary: #6366f1;
|
|
259
|
+
--d-secondary: #a1a1aa;
|
|
260
|
+
--d-accent: #f59e0b;
|
|
261
|
+
--d-bg: #18181b;
|
|
262
|
+
--d-surface: #1f1f23;
|
|
263
|
+
--d-surface-raised: #27272a;
|
|
264
|
+
--d-border: #3f3f46;
|
|
265
|
+
--d-text: #fafafa;
|
|
266
|
+
--d-text-muted: #a1a1aa;
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
270
|
+
const seed = themeData.seed || {};
|
|
271
|
+
const palette = themeData.palette || {};
|
|
272
|
+
const resolvedMode = mode === "auto" ? "dark" : mode;
|
|
273
|
+
function buildTokens(tokenMode) {
|
|
274
|
+
return {
|
|
275
|
+
// Seed colors
|
|
276
|
+
"--d-primary": seed.primary || "#6366f1",
|
|
277
|
+
"--d-secondary": seed.secondary || "#a1a1aa",
|
|
278
|
+
"--d-accent": seed.accent || "#f59e0b",
|
|
279
|
+
// Palette colors (mode-aware)
|
|
280
|
+
"--d-bg": palette.background?.[tokenMode] || "#18181b",
|
|
281
|
+
"--d-surface": palette.surface?.[tokenMode] || "#1f1f23",
|
|
282
|
+
"--d-surface-raised": palette["surface-raised"]?.[tokenMode] || "#27272a",
|
|
283
|
+
"--d-border": palette.border?.[tokenMode] || "#3f3f46",
|
|
284
|
+
"--d-text": palette.text?.[tokenMode] || "#fafafa",
|
|
285
|
+
"--d-text-muted": palette["text-muted"]?.[tokenMode] || "#a1a1aa",
|
|
286
|
+
"--d-primary-hover": palette["primary-hover"]?.[tokenMode] || seed.primary || "#6366f1",
|
|
287
|
+
// Spacing scale
|
|
288
|
+
"--d-gap-1": "0.25rem",
|
|
289
|
+
"--d-gap-2": "0.5rem",
|
|
290
|
+
"--d-gap-3": "0.75rem",
|
|
291
|
+
"--d-gap-4": "1rem",
|
|
292
|
+
"--d-gap-6": "1.5rem",
|
|
293
|
+
"--d-gap-8": "2rem",
|
|
294
|
+
"--d-gap-12": "3rem",
|
|
295
|
+
// Radii
|
|
296
|
+
"--d-radius": "0.5rem",
|
|
297
|
+
"--d-radius-sm": "0.25rem",
|
|
298
|
+
"--d-radius-lg": "0.75rem",
|
|
299
|
+
"--d-radius-xl": "1rem",
|
|
300
|
+
"--d-radius-full": "9999px",
|
|
301
|
+
// Shadows
|
|
302
|
+
"--d-shadow-sm": "0 1px 2px rgba(0,0,0,0.05)",
|
|
303
|
+
"--d-shadow": "0 1px 3px rgba(0,0,0,0.1)",
|
|
304
|
+
"--d-shadow-md": "0 4px 6px rgba(0,0,0,0.1)",
|
|
305
|
+
"--d-shadow-lg": "0 10px 15px rgba(0,0,0,0.1)",
|
|
306
|
+
// Status colors
|
|
307
|
+
"--d-success": themeData.tokens?.base?.success || "#22c55e",
|
|
308
|
+
"--d-error": themeData.tokens?.base?.danger || "#ef4444",
|
|
309
|
+
"--d-warning": themeData.tokens?.base?.warning || "#f59e0b",
|
|
310
|
+
"--d-info": "#3b82f6"
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const tokens = buildTokens(resolvedMode);
|
|
314
|
+
const lines = Object.entries(tokens).map(([key, value]) => ` ${key}: ${value};`).join("\n");
|
|
315
|
+
let css = `/* Generated by @decantr/cli */
|
|
316
|
+
:root {
|
|
317
|
+
${lines}
|
|
318
|
+
}
|
|
319
|
+
`;
|
|
320
|
+
if (mode === "auto") {
|
|
321
|
+
const lightTokens = buildTokens("light");
|
|
322
|
+
const paletteKeys = ["--d-bg", "--d-surface", "--d-surface-raised", "--d-border", "--d-text", "--d-text-muted", "--d-primary-hover"];
|
|
323
|
+
const lightLines = Object.entries(lightTokens).filter(([key]) => paletteKeys.includes(key)).map(([key, value]) => ` ${key}: ${value};`).join("\n");
|
|
324
|
+
css += `
|
|
325
|
+
@media (prefers-color-scheme: light) {
|
|
326
|
+
:root {
|
|
327
|
+
${lightLines}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
return css;
|
|
333
|
+
}
|
|
334
|
+
function generateDecoratorsCSS(recipeData, themeName) {
|
|
335
|
+
if (!recipeData?.decorators) {
|
|
336
|
+
return `/* No recipe decorators available */`;
|
|
337
|
+
}
|
|
338
|
+
const decorators = recipeData.decorators;
|
|
339
|
+
const css = [
|
|
340
|
+
`/* Generated by @decantr/cli from recipe: ${themeName} */`,
|
|
341
|
+
""
|
|
342
|
+
];
|
|
343
|
+
for (const [name, description] of Object.entries(decorators)) {
|
|
344
|
+
css.push(generateDecoratorRule(name, description));
|
|
345
|
+
css.push("");
|
|
346
|
+
}
|
|
347
|
+
css.push(`/* Animation keyframes */
|
|
348
|
+
@keyframes decantr-fade-in {
|
|
349
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
350
|
+
to { opacity: 1; transform: translateY(0); }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@keyframes decantr-pulse {
|
|
354
|
+
0%, 100% { opacity: 1; }
|
|
355
|
+
50% { opacity: 0.5; }
|
|
356
|
+
}
|
|
357
|
+
`);
|
|
358
|
+
return css.join("\n");
|
|
359
|
+
}
|
|
360
|
+
function generateDecoratorsContext(recipeData, recipeName) {
|
|
361
|
+
const lines = [];
|
|
362
|
+
lines.push(`# Recipe Decorators: ${recipeName}`);
|
|
363
|
+
lines.push("");
|
|
364
|
+
lines.push("## Available Classes");
|
|
365
|
+
lines.push("");
|
|
366
|
+
if (recipeData?.decorators && Object.keys(recipeData.decorators).length > 0) {
|
|
367
|
+
lines.push("| Decorator | Description |");
|
|
368
|
+
lines.push("|-----------|-------------|");
|
|
369
|
+
for (const [name, description] of Object.entries(recipeData.decorators)) {
|
|
370
|
+
lines.push(`| ${name} | ${description} |`);
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
lines.push("No decorators defined.");
|
|
374
|
+
}
|
|
375
|
+
lines.push("");
|
|
376
|
+
lines.push("## Usage");
|
|
377
|
+
lines.push("");
|
|
378
|
+
lines.push("Decorators are plain CSS class names from `src/styles/decorators.css`. Combine with atoms:");
|
|
379
|
+
lines.push("");
|
|
380
|
+
lines.push("```tsx");
|
|
381
|
+
lines.push("<div className={css('_flex _col _gap4') + ' " + recipeName + "-card'}>");
|
|
382
|
+
lines.push(" <pre className={css('_p3') + ' " + recipeName + "-code'}>{code}</pre>");
|
|
383
|
+
lines.push("</div>");
|
|
384
|
+
lines.push("```");
|
|
385
|
+
lines.push("");
|
|
386
|
+
lines.push("Atoms use `css()` function. Decorators are plain class strings. Combined via string concatenation.");
|
|
387
|
+
lines.push("");
|
|
388
|
+
return lines.join("\n");
|
|
389
|
+
}
|
|
390
|
+
function generateDecoratorRule(name, description) {
|
|
391
|
+
const rules = [];
|
|
392
|
+
const descLower = description.toLowerCase();
|
|
393
|
+
if (descLower.includes("monospace") || descLower.includes("mono font")) {
|
|
394
|
+
rules.push("font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace");
|
|
395
|
+
}
|
|
396
|
+
if (descLower.includes("surface-raised") || descLower.includes("surface raised")) {
|
|
397
|
+
rules.push("background: var(--d-surface-raised)");
|
|
398
|
+
} else if (descLower.includes("surface background") || descLower.includes("surface elevation")) {
|
|
399
|
+
rules.push("background: var(--d-surface)");
|
|
400
|
+
} else if (descLower.includes("background") && descLower.includes("theme")) {
|
|
401
|
+
rules.push("background: var(--d-bg)");
|
|
402
|
+
} else if (descLower.includes("primary-tinted") || descLower.includes("primary background")) {
|
|
403
|
+
rules.push("background: color-mix(in srgb, var(--d-primary) 15%, var(--d-surface))");
|
|
404
|
+
}
|
|
405
|
+
const leftBorderMatch = descLower.match(/(\d+)px\s+left\s+border/);
|
|
406
|
+
if (leftBorderMatch) {
|
|
407
|
+
rules.push(`border-left: ${leftBorderMatch[1]}px solid var(--d-primary)`);
|
|
408
|
+
} else if (descLower.includes("left border")) {
|
|
409
|
+
rules.push("border-left: 3px solid var(--d-primary)");
|
|
410
|
+
} else if (descLower.includes("1px border") || descLower.includes("subtle border")) {
|
|
411
|
+
rules.push("border: 1px solid var(--d-border)");
|
|
412
|
+
} else if (descLower.includes("border") && !descLower.includes("radius")) {
|
|
413
|
+
rules.push("border: 1px solid var(--d-border)");
|
|
414
|
+
}
|
|
415
|
+
const radiusMatch = descLower.match(/(\d+)px radius/);
|
|
416
|
+
if (radiusMatch) {
|
|
417
|
+
rules.push(`border-radius: ${radiusMatch[1]}px`);
|
|
418
|
+
} else if (descLower.includes("radius") || descLower.includes("rounded")) {
|
|
419
|
+
rules.push("border-radius: var(--d-radius)");
|
|
420
|
+
}
|
|
421
|
+
if (descLower.includes("hover shadow") || descLower.includes("shadow transition")) {
|
|
422
|
+
rules.push("transition: box-shadow 0.15s ease");
|
|
423
|
+
}
|
|
424
|
+
if (descLower.includes("elevation") || descLower.includes("shadow")) {
|
|
425
|
+
rules.push("box-shadow: var(--d-shadow)");
|
|
426
|
+
}
|
|
427
|
+
if (descLower.includes("entrance animation") || descLower.includes("fade")) {
|
|
428
|
+
rules.push("animation: decantr-fade-in 0.2s ease-out");
|
|
429
|
+
}
|
|
430
|
+
if (descLower.includes("pulse animation") || descLower.includes("skeleton")) {
|
|
431
|
+
rules.push("animation: decantr-pulse 1.5s ease-in-out infinite");
|
|
432
|
+
}
|
|
433
|
+
if (descLower.includes("blur") || descLower.includes("glass")) {
|
|
434
|
+
rules.push("backdrop-filter: blur(8px)");
|
|
435
|
+
}
|
|
436
|
+
if (descLower.includes("right-aligned")) {
|
|
437
|
+
rules.push("margin-left: auto");
|
|
438
|
+
} else if (descLower.includes("left-aligned")) {
|
|
439
|
+
rules.push("margin-right: auto");
|
|
440
|
+
}
|
|
441
|
+
if (descLower.includes("message bubble") || descLower.includes("bubble")) {
|
|
442
|
+
rules.push("padding: var(--d-gap-3) var(--d-gap-4)");
|
|
443
|
+
rules.push("border-radius: var(--d-radius-lg)");
|
|
444
|
+
rules.push("max-width: 80%");
|
|
445
|
+
}
|
|
446
|
+
if (descLower.includes("monospace") || descLower.includes("code")) {
|
|
447
|
+
if (!rules.some((r) => r.startsWith("padding"))) {
|
|
448
|
+
rules.push("padding: 0.75rem 1rem");
|
|
449
|
+
}
|
|
450
|
+
if (!rules.some((r) => r.startsWith("border-radius"))) {
|
|
451
|
+
rules.push("border-radius: var(--d-radius-sm)");
|
|
452
|
+
}
|
|
453
|
+
rules.push("overflow-x: auto");
|
|
454
|
+
}
|
|
455
|
+
if (rules.length === 0) {
|
|
456
|
+
return `/* .${name}: ${description} */`;
|
|
457
|
+
}
|
|
458
|
+
return `.${name} {
|
|
459
|
+
${rules.join(";\n ")};
|
|
460
|
+
}`;
|
|
461
|
+
}
|
|
462
|
+
function serializeLayoutItem(item) {
|
|
463
|
+
if (typeof item === "string") {
|
|
464
|
+
return item;
|
|
465
|
+
}
|
|
466
|
+
if (typeof item === "object" && item !== null) {
|
|
467
|
+
const obj = item;
|
|
468
|
+
if (typeof obj.pattern === "string") {
|
|
469
|
+
const preset = obj.preset ? ` (${obj.preset})` : "";
|
|
470
|
+
const alias = obj.as ? ` as ${obj.as}` : "";
|
|
471
|
+
return `${obj.pattern}${preset}${alias}`;
|
|
472
|
+
}
|
|
473
|
+
if (Array.isArray(obj.cols)) {
|
|
474
|
+
const cols = obj.cols.map(serializeLayoutItem).join(" | ");
|
|
475
|
+
const breakpoint = obj.at ? ` @${obj.at}` : "";
|
|
476
|
+
return `[${cols}]${breakpoint}`;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return "custom";
|
|
480
|
+
}
|
|
481
|
+
function extractPatternNames(item) {
|
|
482
|
+
if (typeof item === "string") {
|
|
483
|
+
return [item];
|
|
484
|
+
}
|
|
485
|
+
if (typeof item === "object" && item !== null) {
|
|
486
|
+
const obj = item;
|
|
487
|
+
if (typeof obj.pattern === "string") {
|
|
488
|
+
return [obj.pattern];
|
|
489
|
+
}
|
|
490
|
+
if (Array.isArray(obj.cols)) {
|
|
491
|
+
return obj.cols.flatMap(extractPatternNames);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return [];
|
|
495
|
+
}
|
|
496
|
+
function loadTemplate(name) {
|
|
497
|
+
const fromDist = join(__dirname, "..", "src", "templates", name);
|
|
498
|
+
if (existsSync(fromDist)) {
|
|
499
|
+
return readFileSync(fromDist, "utf-8");
|
|
500
|
+
}
|
|
501
|
+
const fromSrc = join(__dirname, "templates", name);
|
|
502
|
+
if (existsSync(fromSrc)) {
|
|
503
|
+
return readFileSync(fromSrc, "utf-8");
|
|
504
|
+
}
|
|
505
|
+
throw new Error(`Template not found: ${name}`);
|
|
506
|
+
}
|
|
507
|
+
function renderTemplate(template, vars) {
|
|
508
|
+
let result = template;
|
|
509
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
510
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
511
|
+
}
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
function resolvePatternAlias(item, patterns) {
|
|
515
|
+
if (!patterns) return item;
|
|
516
|
+
if (typeof item === "string") {
|
|
517
|
+
const patternDef = patterns.find((p) => p.as === item);
|
|
518
|
+
if (patternDef) {
|
|
519
|
+
if (patternDef.preset) {
|
|
520
|
+
return { pattern: patternDef.pattern, preset: patternDef.preset };
|
|
521
|
+
}
|
|
522
|
+
return patternDef.pattern;
|
|
523
|
+
}
|
|
524
|
+
return item;
|
|
525
|
+
}
|
|
526
|
+
if (typeof item === "object" && item !== null) {
|
|
527
|
+
const obj = item;
|
|
528
|
+
if (Array.isArray(obj.cols)) {
|
|
529
|
+
return {
|
|
530
|
+
...obj,
|
|
531
|
+
cols: obj.cols.map((col) => resolvePatternAlias(col, patterns))
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return item;
|
|
536
|
+
}
|
|
537
|
+
function buildEssence(options, archetypeData) {
|
|
538
|
+
let structure = [
|
|
539
|
+
{ id: "home", shell: options.shell, layout: ["hero"] }
|
|
540
|
+
];
|
|
541
|
+
let features = options.features;
|
|
542
|
+
if (archetypeData?.pages) {
|
|
543
|
+
structure = archetypeData.pages.map((p) => {
|
|
544
|
+
const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
|
|
545
|
+
return {
|
|
546
|
+
id: p.id,
|
|
547
|
+
shell: p.shell || options.shell,
|
|
548
|
+
layout: resolvedLayout
|
|
549
|
+
};
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (archetypeData?.features) {
|
|
553
|
+
features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
|
|
554
|
+
}
|
|
555
|
+
const contentGapMap = {
|
|
556
|
+
compact: "_gap2",
|
|
557
|
+
comfortable: "_gap4",
|
|
558
|
+
spacious: "_gap6"
|
|
559
|
+
};
|
|
560
|
+
const archetype = options.archetype || "custom";
|
|
561
|
+
const essence = {
|
|
562
|
+
version: "2.0.0",
|
|
563
|
+
archetype,
|
|
564
|
+
theme: {
|
|
565
|
+
style: options.theme,
|
|
566
|
+
mode: options.mode,
|
|
567
|
+
recipe: options.theme,
|
|
568
|
+
// Recipe defaults to theme
|
|
569
|
+
shape: options.shape
|
|
570
|
+
},
|
|
571
|
+
personality: options.personality,
|
|
572
|
+
platform: {
|
|
573
|
+
type: "spa",
|
|
574
|
+
routing: "hash"
|
|
575
|
+
},
|
|
576
|
+
structure,
|
|
577
|
+
features,
|
|
578
|
+
guard: {
|
|
579
|
+
enforce_style: true,
|
|
580
|
+
enforce_recipe: true,
|
|
581
|
+
mode: options.guard
|
|
582
|
+
},
|
|
583
|
+
density: {
|
|
584
|
+
level: options.density,
|
|
585
|
+
content_gap: contentGapMap[options.density] || "_gap4"
|
|
586
|
+
},
|
|
587
|
+
target: options.target
|
|
588
|
+
};
|
|
589
|
+
if (options.accessibility) {
|
|
590
|
+
essence.accessibility = options.accessibility;
|
|
591
|
+
}
|
|
592
|
+
return essence;
|
|
593
|
+
}
|
|
594
|
+
function buildEssenceV3(options, archetypeData, themeHints, recipeHints) {
|
|
595
|
+
let pages = [
|
|
596
|
+
{ id: "home", layout: ["hero"] }
|
|
597
|
+
];
|
|
598
|
+
let features = options.features;
|
|
599
|
+
let defaultShell = options.shell || "sidebar-main";
|
|
600
|
+
if (archetypeData?.pages) {
|
|
601
|
+
defaultShell = archetypeData.pages[0]?.shell || defaultShell;
|
|
602
|
+
pages = archetypeData.pages.map((p) => {
|
|
603
|
+
const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
|
|
604
|
+
return {
|
|
605
|
+
id: p.id,
|
|
606
|
+
...p.shell !== defaultShell ? { shell_override: p.shell } : {},
|
|
607
|
+
layout: resolvedLayout
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
if (archetypeData?.features) {
|
|
612
|
+
features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
|
|
613
|
+
}
|
|
614
|
+
const densityLevelMap = {
|
|
615
|
+
compact: "_gap2",
|
|
616
|
+
comfortable: "_gap4",
|
|
617
|
+
spacious: "_gap6"
|
|
618
|
+
};
|
|
619
|
+
const shapeRadiusMap = {
|
|
620
|
+
pill: 12,
|
|
621
|
+
rounded: 8,
|
|
622
|
+
sharp: 2
|
|
623
|
+
};
|
|
624
|
+
const guardModeMap = {
|
|
625
|
+
strict: { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" },
|
|
626
|
+
guided: { mode: "guided", dna_enforcement: "error", blueprint_enforcement: "off" },
|
|
627
|
+
creative: { mode: "creative", dna_enforcement: "off", blueprint_enforcement: "off" }
|
|
628
|
+
};
|
|
629
|
+
const dna = {
|
|
630
|
+
theme: {
|
|
631
|
+
style: options.theme,
|
|
632
|
+
mode: options.mode,
|
|
633
|
+
recipe: options.theme,
|
|
634
|
+
shape: options.shape
|
|
635
|
+
},
|
|
636
|
+
spacing: {
|
|
637
|
+
base_unit: 4,
|
|
638
|
+
scale: "linear",
|
|
639
|
+
density: options.density,
|
|
640
|
+
content_gap: densityLevelMap[options.density] || "_gap4"
|
|
641
|
+
},
|
|
642
|
+
typography: {
|
|
643
|
+
scale: themeHints?.typography_hints?.scale || "modular",
|
|
644
|
+
heading_weight: themeHints?.typography_hints?.heading_weight || 600,
|
|
645
|
+
body_weight: themeHints?.typography_hints?.body_weight || 400
|
|
646
|
+
},
|
|
647
|
+
color: {
|
|
648
|
+
palette: "semantic",
|
|
649
|
+
accent_count: 1,
|
|
650
|
+
cvd_preference: options.accessibility?.cvd_preference || "auto"
|
|
651
|
+
},
|
|
652
|
+
radius: {
|
|
653
|
+
philosophy: recipeHints?.radius_hints?.philosophy || options.shape,
|
|
654
|
+
base: recipeHints?.radius_hints?.base || shapeRadiusMap[options.shape] || 8
|
|
655
|
+
},
|
|
656
|
+
elevation: {
|
|
657
|
+
system: "layered",
|
|
658
|
+
max_levels: 3
|
|
659
|
+
},
|
|
660
|
+
motion: {
|
|
661
|
+
preference: recipeHints?.animation?.preference || themeHints?.motion_hints?.preference || "subtle",
|
|
662
|
+
duration_scale: 1,
|
|
663
|
+
reduce_motion: themeHints?.motion_hints?.reduce_motion_default ?? true
|
|
664
|
+
},
|
|
665
|
+
accessibility: {
|
|
666
|
+
wcag_level: options.accessibility?.wcag_level || "AA",
|
|
667
|
+
focus_visible: true,
|
|
668
|
+
skip_nav: true
|
|
669
|
+
},
|
|
670
|
+
personality: options.personality
|
|
671
|
+
};
|
|
672
|
+
const blueprint = {
|
|
673
|
+
shell: defaultShell,
|
|
674
|
+
pages,
|
|
675
|
+
features
|
|
676
|
+
};
|
|
677
|
+
const meta = {
|
|
678
|
+
archetype: options.archetype || "custom",
|
|
679
|
+
target: options.target,
|
|
680
|
+
platform: {
|
|
681
|
+
type: "spa",
|
|
682
|
+
routing: "hash"
|
|
683
|
+
},
|
|
684
|
+
guard: guardModeMap[options.guard] || guardModeMap.guided
|
|
685
|
+
};
|
|
686
|
+
return {
|
|
687
|
+
version: "3.0.0",
|
|
688
|
+
dna,
|
|
689
|
+
blueprint,
|
|
690
|
+
meta
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
var CSS_APPROACH_CONTENT = `## CSS Implementation
|
|
694
|
+
|
|
695
|
+
This project uses **@decantr/css** for layout atoms and the generated CSS files for theme tokens and recipe decorators.
|
|
696
|
+
|
|
697
|
+
### Setup
|
|
698
|
+
|
|
699
|
+
\`\`\`javascript
|
|
700
|
+
// 1. Import the atoms runtime
|
|
701
|
+
import { css } from '@decantr/css';
|
|
702
|
+
|
|
703
|
+
// 2. Import generated CSS files (created by decantr init)
|
|
704
|
+
import './styles/tokens.css'; // Theme tokens (--d-primary, --d-surface, etc.)
|
|
705
|
+
import './styles/decorators.css'; // Recipe decorators
|
|
706
|
+
\`\`\`
|
|
707
|
+
|
|
708
|
+
### Using Atoms
|
|
709
|
+
|
|
710
|
+
The \`css()\` function processes atom strings and injects CSS at runtime:
|
|
711
|
+
|
|
712
|
+
\`\`\`jsx
|
|
713
|
+
// Layout atoms
|
|
714
|
+
<div className={css('_flex _col _gap4 _p4')}>
|
|
715
|
+
<h1 className={css('_heading1')}>Title</h1>
|
|
716
|
+
<p className={css('_textsm _fgmuted')}>Description</p>
|
|
717
|
+
</div>
|
|
718
|
+
|
|
719
|
+
// Responsive prefixes (mobile-first)
|
|
720
|
+
<div className={css('_gc1 _sm:gc2 _lg:gc4')}>
|
|
721
|
+
{/* 1 col -> 2 cols at 640px -> 4 cols at 1024px */}
|
|
722
|
+
</div>
|
|
723
|
+
\`\`\`
|
|
724
|
+
|
|
725
|
+
### Atom Reference
|
|
726
|
+
|
|
727
|
+
#### Display
|
|
728
|
+
| Atom | CSS |
|
|
729
|
+
|------|-----|
|
|
730
|
+
| \`_flex\` | \`display:flex\` |
|
|
731
|
+
| \`_grid\` | \`display:grid\` |
|
|
732
|
+
| \`_block\` | \`display:block\` |
|
|
733
|
+
| \`_inline\` | \`display:inline\` |
|
|
734
|
+
| \`_inlineflex\` | \`display:inline-flex\` |
|
|
735
|
+
| \`_none\` | \`display:none\` |
|
|
736
|
+
| \`_contents\` | \`display:contents\` |
|
|
737
|
+
|
|
738
|
+
#### Flexbox
|
|
739
|
+
| Atom | CSS |
|
|
740
|
+
|------|-----|
|
|
741
|
+
| \`_col\` | \`flex-direction:column\` |
|
|
742
|
+
| \`_row\` | \`flex-direction:row\` |
|
|
743
|
+
| \`_colrev\` | \`flex-direction:column-reverse\` |
|
|
744
|
+
| \`_wrap\` | \`flex-wrap:wrap\` |
|
|
745
|
+
| \`_nowrap\` | \`flex-wrap:nowrap\` |
|
|
746
|
+
| \`_flex1\` | \`flex:1\` |
|
|
747
|
+
| \`_flex0\` | \`flex:none\` |
|
|
748
|
+
| \`_flexauto\` | \`flex:auto\` |
|
|
749
|
+
| \`_grow\` | \`flex-grow:1\` |
|
|
750
|
+
| \`_grow0\` | \`flex-grow:0\` |
|
|
751
|
+
| \`_shrink0\` | \`flex-shrink:0\` |
|
|
752
|
+
|
|
753
|
+
#### Alignment
|
|
754
|
+
| Atom | CSS |
|
|
755
|
+
|------|-----|
|
|
756
|
+
| \`_aic\` | \`align-items:center\` |
|
|
757
|
+
| \`_aifs\` | \`align-items:flex-start\` |
|
|
758
|
+
| \`_aife\` | \`align-items:flex-end\` |
|
|
759
|
+
| \`_aist\` | \`align-items:stretch\` |
|
|
760
|
+
| \`_aibl\` | \`align-items:baseline\` |
|
|
761
|
+
| \`_jcc\` | \`justify-content:center\` |
|
|
762
|
+
| \`_jcfs\` | \`justify-content:flex-start\` |
|
|
763
|
+
| \`_jcfe\` | \`justify-content:flex-end\` |
|
|
764
|
+
| \`_jcsb\` | \`justify-content:space-between\` |
|
|
765
|
+
| \`_jcsa\` | \`justify-content:space-around\` |
|
|
766
|
+
| \`_jcse\` | \`justify-content:space-evenly\` |
|
|
767
|
+
| \`_pic\` | \`place-items:center\` |
|
|
768
|
+
| \`_pcc\` | \`place-content:center\` |
|
|
769
|
+
|
|
770
|
+
#### Spacing (scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, ...)
|
|
771
|
+
| Atom | CSS | Notes |
|
|
772
|
+
|------|-----|-------|
|
|
773
|
+
| \`_gap{n}\` | \`gap:{scale}\` | e.g. \`_gap4\` = \`gap:1rem\` |
|
|
774
|
+
| \`_gx{n}\` | \`column-gap:{scale}\` | horizontal gap |
|
|
775
|
+
| \`_gy{n}\` | \`row-gap:{scale}\` | vertical gap |
|
|
776
|
+
| \`_p{n}\` | \`padding:{scale}\` | all sides |
|
|
777
|
+
| \`_pt{n}\`, \`_pr{n}\`, \`_pb{n}\`, \`_pl{n}\` | directional padding | top/right/bottom/left |
|
|
778
|
+
| \`_px{n}\` | \`padding-inline:{scale}\` | horizontal |
|
|
779
|
+
| \`_py{n}\` | \`padding-block:{scale}\` | vertical |
|
|
780
|
+
| \`_m{n}\` | \`margin:{scale}\` | same as padding variants |
|
|
781
|
+
| \`_mx{n}\`, \`_my{n}\` | inline/block margin | horizontal/vertical |
|
|
782
|
+
|
|
783
|
+
#### Sizing
|
|
784
|
+
| Atom | CSS |
|
|
785
|
+
|------|-----|
|
|
786
|
+
| \`_wfull\` / \`_w100\` | \`width:100%\` |
|
|
787
|
+
| \`_hfull\` / \`_h100\` | \`height:100%\` |
|
|
788
|
+
| \`_wscreen\` | \`width:100vw\` |
|
|
789
|
+
| \`_hscreen\` | \`height:100vh\` |
|
|
790
|
+
| \`_wfit\` | \`width:fit-content\` |
|
|
791
|
+
| \`_hfit\` | \`height:fit-content\` |
|
|
792
|
+
| \`_wauto\` | \`width:auto\` |
|
|
793
|
+
| \`_minw0\` | \`min-width:0\` |
|
|
794
|
+
| \`_minh0\` | \`min-height:0\` |
|
|
795
|
+
| \`_w{n}\`, \`_h{n}\` | width/height from spacing scale |
|
|
796
|
+
| \`_minw{n}\`, \`_maxw{n}\` | min/max width from scale |
|
|
797
|
+
|
|
798
|
+
#### Text Size
|
|
799
|
+
| Atom | Size | Line-height |
|
|
800
|
+
|------|------|-------------|
|
|
801
|
+
| \`_textxs\` | 0.75rem | 1rem |
|
|
802
|
+
| \`_textsm\` | 0.875rem | 1.25rem |
|
|
803
|
+
| \`_textbase\` | 1rem | 1.5rem |
|
|
804
|
+
| \`_textlg\` | 1.125rem | 1.75rem |
|
|
805
|
+
| \`_textxl\` | 1.25rem | 1.75rem |
|
|
806
|
+
| \`_text2xl\` | 1.5rem | 2rem |
|
|
807
|
+
| \`_text3xl\` | 1.875rem | 2.25rem |
|
|
808
|
+
| \`_heading1\`-\`_heading6\` | Heading presets (size + weight) |
|
|
809
|
+
|
|
810
|
+
#### Text Style
|
|
811
|
+
| Atom | CSS |
|
|
812
|
+
|------|-----|
|
|
813
|
+
| \`_fontbold\` | \`font-weight:700\` |
|
|
814
|
+
| \`_fontsemi\` | \`font-weight:600\` |
|
|
815
|
+
| \`_fontmedium\` | \`font-weight:500\` |
|
|
816
|
+
| \`_fontlight\` | \`font-weight:300\` |
|
|
817
|
+
| \`_italic\` | \`font-style:italic\` |
|
|
818
|
+
| \`_underline\` | \`text-decoration:underline\` |
|
|
819
|
+
| \`_uppercase\` | \`text-transform:uppercase\` |
|
|
820
|
+
| \`_truncate\` | overflow ellipsis + nowrap |
|
|
821
|
+
| \`_textl\`, \`_textc\`, \`_textr\` | text-align left/center/right |
|
|
822
|
+
|
|
823
|
+
#### Color (theme variable based)
|
|
824
|
+
| Atom | CSS |
|
|
825
|
+
|------|-----|
|
|
826
|
+
| \`_bgprimary\` | \`background:var(--d-primary)\` |
|
|
827
|
+
| \`_bgsurface\` | \`background:var(--d-surface)\` |
|
|
828
|
+
| \`_bgsurface0\`-\`_bgsurface2\` | surface elevation layers |
|
|
829
|
+
| \`_bgmuted\` | \`background:var(--d-muted)\` |
|
|
830
|
+
| \`_bgbg\` | \`background:var(--d-bg)\` |
|
|
831
|
+
| \`_bgsuccess\`, \`_bgerror\`, \`_bgwarning\`, \`_bginfo\` | status backgrounds |
|
|
832
|
+
| \`_fgprimary\` | \`color:var(--d-primary)\` |
|
|
833
|
+
| \`_fgtext\` | \`color:var(--d-text)\` |
|
|
834
|
+
| \`_fgmuted\` | \`color:var(--d-text-muted)\` |
|
|
835
|
+
| \`_fgsuccess\`, \`_fgerror\`, \`_fgwarning\`, \`_fginfo\` | status text |
|
|
836
|
+
| \`_bcborder\` | \`border-color:var(--d-border)\` |
|
|
837
|
+
|
|
838
|
+
#### Overflow & Whitespace
|
|
839
|
+
| Atom | CSS |
|
|
840
|
+
|------|-----|
|
|
841
|
+
| \`_overhidden\` | \`overflow:hidden\` |
|
|
842
|
+
| \`_overauto\` | \`overflow:auto\` |
|
|
843
|
+
| \`_overscroll\` | \`overflow:scroll\` |
|
|
844
|
+
| \`_overxauto\`, \`_overyauto\` | axis-specific overflow |
|
|
845
|
+
| \`_nowraptext\` | \`white-space:nowrap\` |
|
|
846
|
+
| \`_prewrap\` | \`white-space:pre-wrap\` |
|
|
847
|
+
| \`_breakword\` | \`overflow-wrap:break-word\` |
|
|
848
|
+
|
|
849
|
+
#### Cursor & Interaction
|
|
850
|
+
| Atom | CSS |
|
|
851
|
+
|------|-----|
|
|
852
|
+
| \`_pointer\` | \`cursor:pointer\` |
|
|
853
|
+
| \`_cursordefault\` | \`cursor:default\` |
|
|
854
|
+
| \`_notallowed\` | \`cursor:not-allowed\` |
|
|
855
|
+
| \`_grab\` | \`cursor:grab\` |
|
|
856
|
+
| \`_selectnone\` | \`user-select:none\` |
|
|
857
|
+
| \`_ptrnone\` | \`pointer-events:none\` |
|
|
858
|
+
|
|
859
|
+
#### Position & Layout
|
|
860
|
+
| Atom | CSS |
|
|
861
|
+
|------|-----|
|
|
862
|
+
| \`_rel\` | \`position:relative\` |
|
|
863
|
+
| \`_abs\` | \`position:absolute\` |
|
|
864
|
+
| \`_fixed\` | \`position:fixed\` |
|
|
865
|
+
| \`_sticky\` | \`position:sticky\` |
|
|
866
|
+
| \`_inset0\` | \`inset:0\` |
|
|
867
|
+
| \`_top0\`, \`_right0\`, \`_bottom0\`, \`_left0\` | edge positioning |
|
|
868
|
+
| \`_z10\`-\`_z50\` | z-index scale |
|
|
869
|
+
|
|
870
|
+
#### Grid
|
|
871
|
+
| Atom | CSS |
|
|
872
|
+
|------|-----|
|
|
873
|
+
| \`_gc1\`-\`_gc12\` | \`grid-template-columns:repeat(N,...)\` |
|
|
874
|
+
| \`_gr1\`-\`_gr6\` | \`grid-template-rows:repeat(N,...)\` |
|
|
875
|
+
| \`_span1\`-\`_span12\`, \`_spanfull\` | column span |
|
|
876
|
+
| \`_rowspan1\`-\`_rowspan6\` | row span |
|
|
877
|
+
|
|
878
|
+
#### Visual
|
|
879
|
+
| Atom | CSS |
|
|
880
|
+
|------|-----|
|
|
881
|
+
| \`_rounded\` | \`border-radius:var(--d-radius)\` |
|
|
882
|
+
| \`_roundedfull\` | \`border-radius:9999px\` |
|
|
883
|
+
| \`_roundedsm\`, \`_roundedlg\`, \`_roundedxl\` | radius variants |
|
|
884
|
+
| \`_shadow\`, \`_shadowmd\`, \`_shadowlg\` | box-shadow presets |
|
|
885
|
+
| \`_bordernone\` | \`border:none\` |
|
|
886
|
+
| \`_bw{n}\` | \`border-width:{n}px\` |
|
|
887
|
+
| \`_op0\`-\`_op100\` | opacity (0, 25, 50, 75, 100) |
|
|
888
|
+
| \`_trans\` | \`transition:all 0.15s ease\` |
|
|
889
|
+
| \`_visible\`, \`_invisible\` | visibility |
|
|
890
|
+
|
|
891
|
+
### Using Recipe Decorators
|
|
892
|
+
|
|
893
|
+
Recipe decorators (from \`src/styles/decorators.css\`) are regular CSS class names, NOT atoms. They are applied directly as class names and combined with atoms using string concatenation:
|
|
894
|
+
|
|
895
|
+
\`\`\`tsx
|
|
896
|
+
// Atoms use css() function, decorators are plain class names
|
|
897
|
+
<div className={css('_flex _col _gap4') + ' carbon-card'}>
|
|
898
|
+
<div className={css('_p4') + ' carbon-glass'}>
|
|
899
|
+
<pre className={css('_p3') + ' carbon-code'}>{code}</pre>
|
|
900
|
+
</div>
|
|
901
|
+
</div>
|
|
902
|
+
\`\`\`
|
|
903
|
+
|
|
904
|
+
**Key difference:**
|
|
905
|
+
- Atoms: \`css('_flex _col _gap4')\` \u2014 processed by @decantr/css runtime
|
|
906
|
+
- Decorators: \`'carbon-card'\`, \`'carbon-glass'\` \u2014 plain CSS classes from decorators.css
|
|
907
|
+
- Combined: \`css('_flex _col') + ' carbon-card'\`
|
|
908
|
+
|
|
909
|
+
### Routing
|
|
910
|
+
|
|
911
|
+
Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing strategy:
|
|
912
|
+
- \`"hash"\` \u2192 use \`HashRouter\` (e.g., for static hosting, GitHub Pages)
|
|
913
|
+
- \`"history"\` \u2192 use \`BrowserRouter\` (e.g., for server-rendered apps)
|
|
914
|
+
|
|
915
|
+
Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.
|
|
916
|
+
|
|
917
|
+
### CSS Architecture
|
|
918
|
+
|
|
919
|
+
The CSS is organized into two parts:
|
|
920
|
+
|
|
921
|
+
1. **Atoms (@decantr/css)** - Layout utilities injected at runtime into \`@layer d.atoms\`
|
|
922
|
+
2. **Generated CSS files** - Theme tokens and recipe decorators created during scaffold
|
|
923
|
+
|
|
924
|
+
\`\`\`
|
|
925
|
+
src/styles/
|
|
926
|
+
tokens.css # :root { --d-primary: #...; --d-surface: #...; }
|
|
927
|
+
decorators.css # .recipe-card { ... }
|
|
928
|
+
\`\`\`
|
|
929
|
+
|
|
930
|
+
### Variable Naming Convention
|
|
931
|
+
|
|
932
|
+
| Prefix | Purpose | Example |
|
|
933
|
+
|--------|---------|---------|
|
|
934
|
+
| \`--d-\` | Core Decantr tokens | \`--d-primary\`, \`--d-bg\` |
|
|
935
|
+
| \`--d-gap-{n}\` | Spacing tokens | \`--d-gap-4\`, \`--d-gap-8\` |
|
|
936
|
+
| \`--d-radius\` | Border radius | \`--d-radius\`, \`--d-radius-lg\` |`;
|
|
937
|
+
function generateDecantrMdV31(guardMode, cssApproach) {
|
|
938
|
+
const template = loadTemplate("DECANTR.md.template");
|
|
939
|
+
return renderTemplate(template, {
|
|
940
|
+
GUARD_MODE: guardMode,
|
|
941
|
+
CSS_APPROACH: cssApproach
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
function generateProjectJson(detected, options, registrySource) {
|
|
945
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
946
|
+
const data = {
|
|
947
|
+
detected: {
|
|
948
|
+
framework: detected.framework,
|
|
949
|
+
version: detected.version || null,
|
|
950
|
+
packageManager: detected.packageManager,
|
|
951
|
+
hasTypeScript: detected.hasTypeScript,
|
|
952
|
+
hasTailwind: detected.hasTailwind,
|
|
953
|
+
existingRuleFiles: detected.existingRuleFiles
|
|
954
|
+
},
|
|
955
|
+
overrides: {
|
|
956
|
+
framework: options.target !== detected.framework ? options.target : null
|
|
957
|
+
},
|
|
958
|
+
sync: {
|
|
959
|
+
status: registrySource === "api" ? "synced" : "needs-sync",
|
|
960
|
+
lastSync: now,
|
|
961
|
+
registrySource,
|
|
962
|
+
cachedContent: {
|
|
963
|
+
archetypes: [],
|
|
964
|
+
patterns: [],
|
|
965
|
+
themes: [],
|
|
966
|
+
recipes: []
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
initialized: {
|
|
970
|
+
at: now,
|
|
971
|
+
via: "cli",
|
|
972
|
+
version: CLI_VERSION,
|
|
973
|
+
flags: buildFlagsString(options)
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
return JSON.stringify(data, null, 2);
|
|
977
|
+
}
|
|
978
|
+
function buildFlagsString(options) {
|
|
979
|
+
const flags = [];
|
|
980
|
+
if (options.blueprint) flags.push(`--blueprint=${options.blueprint}`);
|
|
981
|
+
if (options.theme) flags.push(`--theme=${options.theme}`);
|
|
982
|
+
if (options.mode) flags.push(`--mode=${options.mode}`);
|
|
983
|
+
if (options.guard) flags.push(`--guard=${options.guard}`);
|
|
984
|
+
return flags.join(" ");
|
|
985
|
+
}
|
|
986
|
+
function generateTaskContext(templateName, essence) {
|
|
987
|
+
const template = loadTemplate(templateName);
|
|
988
|
+
const defaultShell = essence.structure[0]?.shell || "sidebar-main";
|
|
989
|
+
const layout = essence.structure[0]?.layout.map(serializeLayoutItem).join(", ") || "none";
|
|
990
|
+
const scaffoldStructure = essence.structure.map((p) => {
|
|
991
|
+
const patterns = p.layout.length > 0 ? `
|
|
992
|
+
- Patterns: ${p.layout.map(serializeLayoutItem).join(", ")}` : "";
|
|
993
|
+
return `- **${p.id}** (${p.shell})${patterns}`;
|
|
994
|
+
}).join("\n");
|
|
995
|
+
const vars = {
|
|
996
|
+
TARGET: essence.target,
|
|
997
|
+
THEME_STYLE: essence.theme.style,
|
|
998
|
+
THEME_MODE: essence.theme.mode,
|
|
999
|
+
THEME_RECIPE: essence.theme.recipe,
|
|
1000
|
+
DEFAULT_SHELL: defaultShell,
|
|
1001
|
+
GUARD_MODE: essence.guard.mode,
|
|
1002
|
+
LAYOUT: layout,
|
|
1003
|
+
DENSITY: essence.density.level,
|
|
1004
|
+
CONTENT_GAP: essence.density.content_gap,
|
|
1005
|
+
SCAFFOLD_STRUCTURE: scaffoldStructure
|
|
1006
|
+
};
|
|
1007
|
+
return renderTemplate(template, vars);
|
|
1008
|
+
}
|
|
1009
|
+
function generateEssenceSummary(essence) {
|
|
1010
|
+
const template = loadTemplate("essence-summary.md.template");
|
|
1011
|
+
const pagesTable = `| Page | Shell | Layout |
|
|
1012
|
+
|------|-------|--------|
|
|
1013
|
+
${essence.structure.map((p) => `| ${p.id} | ${p.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`).join("\n")}`;
|
|
1014
|
+
const featuresList = essence.features.length > 0 ? essence.features.map((f) => `- ${f}`).join("\n") : "- No features specified";
|
|
1015
|
+
const dnaEnforcement = essence.guard.enforce_style ? "error" : "off";
|
|
1016
|
+
const blueprintEnforcement = essence.guard.enforce_recipe ? "warn" : "off";
|
|
1017
|
+
const vars = {
|
|
1018
|
+
ARCHETYPE: essence.archetype || "custom",
|
|
1019
|
+
BLUEPRINT: essence.blueprint || "none",
|
|
1020
|
+
PERSONALITY: essence.personality.join(", "),
|
|
1021
|
+
TARGET: essence.target,
|
|
1022
|
+
THEME_STYLE: essence.theme.style,
|
|
1023
|
+
THEME_MODE: essence.theme.mode,
|
|
1024
|
+
THEME_RECIPE: essence.theme.recipe,
|
|
1025
|
+
SHAPE: essence.theme.shape,
|
|
1026
|
+
PAGES_TABLE: pagesTable,
|
|
1027
|
+
FEATURES_LIST: featuresList,
|
|
1028
|
+
GUARD_MODE: essence.guard.mode,
|
|
1029
|
+
ENFORCE_STYLE: String(essence.guard.enforce_style),
|
|
1030
|
+
ENFORCE_RECIPE: String(essence.guard.enforce_recipe),
|
|
1031
|
+
DNA_ENFORCEMENT: dnaEnforcement,
|
|
1032
|
+
BLUEPRINT_ENFORCEMENT: blueprintEnforcement,
|
|
1033
|
+
DENSITY: essence.density.level,
|
|
1034
|
+
CONTENT_GAP: essence.density.content_gap,
|
|
1035
|
+
LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
|
|
1036
|
+
};
|
|
1037
|
+
return renderTemplate(template, vars);
|
|
1038
|
+
}
|
|
1039
|
+
function generateEssenceSummaryV3(essence) {
|
|
1040
|
+
const template = loadTemplate("essence-summary.md.template");
|
|
1041
|
+
const blueprint = essence.blueprint;
|
|
1042
|
+
const sections = blueprint.sections || [];
|
|
1043
|
+
const flatPages = blueprint.pages || [];
|
|
1044
|
+
let pagesTable;
|
|
1045
|
+
if (sections.length > 0) {
|
|
1046
|
+
const rows = sections.flatMap(
|
|
1047
|
+
(s) => s.pages.map((p) => `| ${p.id} | ${s.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`)
|
|
1048
|
+
);
|
|
1049
|
+
pagesTable = `| Page | Shell | Layout |
|
|
1050
|
+
|------|-------|--------|
|
|
1051
|
+
${rows.join("\n")}`;
|
|
1052
|
+
} else {
|
|
1053
|
+
const shell = blueprint.shell ?? "sidebar-main";
|
|
1054
|
+
const rows = flatPages.map((p) => `| ${p.id} | ${shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`);
|
|
1055
|
+
pagesTable = `| Page | Shell | Layout |
|
|
1056
|
+
|------|-------|--------|
|
|
1057
|
+
${rows.join("\n")}`;
|
|
1058
|
+
}
|
|
1059
|
+
const features = blueprint.features || [];
|
|
1060
|
+
const featuresList = features.length > 0 ? features.map((f) => `- ${f}`).join("\n") : "- No features specified";
|
|
1061
|
+
const vars = {
|
|
1062
|
+
ARCHETYPE: essence.meta.archetype || "custom",
|
|
1063
|
+
BLUEPRINT: "",
|
|
1064
|
+
PERSONALITY: (essence.dna.personality || []).join(", "),
|
|
1065
|
+
TARGET: essence.meta.target ?? "",
|
|
1066
|
+
THEME_STYLE: essence.dna.theme.style,
|
|
1067
|
+
THEME_MODE: essence.dna.theme.mode,
|
|
1068
|
+
THEME_RECIPE: essence.dna.theme.recipe ?? "",
|
|
1069
|
+
SHAPE: essence.dna.theme.shape ?? "",
|
|
1070
|
+
PAGES_TABLE: pagesTable,
|
|
1071
|
+
FEATURES_LIST: featuresList,
|
|
1072
|
+
GUARD_MODE: essence.meta.guard.mode,
|
|
1073
|
+
ENFORCE_STYLE: essence.meta.guard.dna_enforcement || "error",
|
|
1074
|
+
ENFORCE_RECIPE: essence.meta.guard.blueprint_enforcement || "warn",
|
|
1075
|
+
DNA_ENFORCEMENT: essence.meta.guard.dna_enforcement || "error",
|
|
1076
|
+
BLUEPRINT_ENFORCEMENT: essence.meta.guard.blueprint_enforcement || "warn",
|
|
1077
|
+
DENSITY: essence.dna.spacing?.density || "comfortable",
|
|
1078
|
+
CONTENT_GAP: essence.dna.spacing?.content_gap || "_gap4",
|
|
1079
|
+
LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
|
|
1080
|
+
};
|
|
1081
|
+
return renderTemplate(template, vars);
|
|
1082
|
+
}
|
|
1083
|
+
function updateGitignore(projectRoot) {
|
|
1084
|
+
const gitignorePath = join(projectRoot, ".gitignore");
|
|
1085
|
+
const cacheEntry = ".decantr/cache/";
|
|
1086
|
+
if (existsSync(gitignorePath)) {
|
|
1087
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
1088
|
+
if (!content.includes(cacheEntry)) {
|
|
1089
|
+
appendFileSync(gitignorePath, `
|
|
1090
|
+
# Decantr cache
|
|
1091
|
+
${cacheEntry}
|
|
1092
|
+
`);
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
return false;
|
|
1096
|
+
} else {
|
|
1097
|
+
writeFileSync(gitignorePath, `# Decantr cache
|
|
1098
|
+
${cacheEntry}
|
|
1099
|
+
`);
|
|
1100
|
+
return true;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
async function scaffoldProject(projectRoot, options, detected, registry, archetypeData, registrySource = "cache", themeData, recipeData, topologyMarkdown, composedSections, routeMap, patternSpecs, blueprintData) {
|
|
1104
|
+
const essenceV3 = buildEssenceV3(options, archetypeData, themeData, recipeData);
|
|
1105
|
+
const essence = buildEssence(options, archetypeData);
|
|
1106
|
+
const decantrDir = join(projectRoot, ".decantr");
|
|
1107
|
+
const contextDir = join(decantrDir, "context");
|
|
1108
|
+
const cacheDir = join(decantrDir, "cache");
|
|
1109
|
+
mkdirSync(contextDir, { recursive: true });
|
|
1110
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
1111
|
+
const essencePath = join(projectRoot, "decantr.essence.json");
|
|
1112
|
+
writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
|
|
1113
|
+
const projectJsonPath = join(decantrDir, "project.json");
|
|
1114
|
+
writeFileSync(projectJsonPath, generateProjectJson(detected, options, registrySource));
|
|
1115
|
+
const contextFiles = [];
|
|
1116
|
+
const scaffoldPath = join(contextDir, "task-scaffold.md");
|
|
1117
|
+
writeFileSync(scaffoldPath, generateTaskContext("task-scaffold.md.template", essence));
|
|
1118
|
+
contextFiles.push(scaffoldPath);
|
|
1119
|
+
const addPagePath = join(contextDir, "task-add-page.md");
|
|
1120
|
+
writeFileSync(addPagePath, generateTaskContext("task-add-page.md.template", essence));
|
|
1121
|
+
contextFiles.push(addPagePath);
|
|
1122
|
+
const modifyPath = join(contextDir, "task-modify.md");
|
|
1123
|
+
writeFileSync(modifyPath, generateTaskContext("task-modify.md.template", essence));
|
|
1124
|
+
contextFiles.push(modifyPath);
|
|
1125
|
+
const summaryPath = join(contextDir, "essence-summary.md");
|
|
1126
|
+
writeFileSync(summaryPath, generateEssenceSummary(essence));
|
|
1127
|
+
contextFiles.push(summaryPath);
|
|
1128
|
+
if (composedSections) {
|
|
1129
|
+
essenceV3.version = "3.1.0";
|
|
1130
|
+
essenceV3.blueprint = {
|
|
1131
|
+
sections: composedSections.sections,
|
|
1132
|
+
features: composedSections.features,
|
|
1133
|
+
routes: routeMap || {}
|
|
1134
|
+
};
|
|
1135
|
+
if (blueprintData?.personality?.length) {
|
|
1136
|
+
essenceV3.dna.personality = blueprintData.personality;
|
|
1137
|
+
}
|
|
1138
|
+
if (blueprintData?.design_constraints) {
|
|
1139
|
+
essenceV3.dna.constraints = blueprintData.design_constraints;
|
|
1140
|
+
}
|
|
1141
|
+
if (blueprintData?.seo_hints) {
|
|
1142
|
+
essenceV3.meta.seo = blueprintData.seo_hints;
|
|
1143
|
+
}
|
|
1144
|
+
if (blueprintData?.navigation) {
|
|
1145
|
+
essenceV3.meta.navigation = blueprintData.navigation;
|
|
1146
|
+
}
|
|
1147
|
+
writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
|
|
1148
|
+
}
|
|
1149
|
+
const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, recipeData);
|
|
1150
|
+
contextFiles.push(...refreshResult.contextFiles);
|
|
1151
|
+
const gitignoreUpdated = updateGitignore(projectRoot);
|
|
1152
|
+
return {
|
|
1153
|
+
essencePath,
|
|
1154
|
+
decantrMdPath: refreshResult.decantrMdPath,
|
|
1155
|
+
projectJsonPath,
|
|
1156
|
+
contextFiles,
|
|
1157
|
+
cssFiles: refreshResult.cssFiles,
|
|
1158
|
+
gitignoreUpdated
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function scaffoldMinimal(projectRoot) {
|
|
1162
|
+
const decantrDir = join(projectRoot, ".decantr");
|
|
1163
|
+
const customDir = join(decantrDir, "custom");
|
|
1164
|
+
const contentTypes = ["patterns", "recipes", "themes", "blueprints", "archetypes", "shells"];
|
|
1165
|
+
for (const type of contentTypes) {
|
|
1166
|
+
mkdirSync(join(customDir, type), { recursive: true });
|
|
1167
|
+
}
|
|
1168
|
+
const essence = {
|
|
1169
|
+
version: "3.0.0",
|
|
1170
|
+
dna: {
|
|
1171
|
+
theme: {
|
|
1172
|
+
style: "default",
|
|
1173
|
+
mode: "dark",
|
|
1174
|
+
recipe: "default",
|
|
1175
|
+
shape: "rounded"
|
|
1176
|
+
},
|
|
1177
|
+
spacing: {
|
|
1178
|
+
base_unit: 4,
|
|
1179
|
+
scale: "linear",
|
|
1180
|
+
density: "comfortable",
|
|
1181
|
+
content_gap: "_gap4"
|
|
1182
|
+
},
|
|
1183
|
+
typography: {
|
|
1184
|
+
scale: "modular",
|
|
1185
|
+
heading_weight: 600,
|
|
1186
|
+
body_weight: 400
|
|
1187
|
+
},
|
|
1188
|
+
color: {
|
|
1189
|
+
palette: "semantic",
|
|
1190
|
+
accent_count: 1,
|
|
1191
|
+
cvd_preference: "auto"
|
|
1192
|
+
},
|
|
1193
|
+
radius: {
|
|
1194
|
+
philosophy: "rounded",
|
|
1195
|
+
base: 8
|
|
1196
|
+
},
|
|
1197
|
+
elevation: {
|
|
1198
|
+
system: "layered",
|
|
1199
|
+
max_levels: 3
|
|
1200
|
+
},
|
|
1201
|
+
motion: {
|
|
1202
|
+
preference: "subtle",
|
|
1203
|
+
duration_scale: 1,
|
|
1204
|
+
reduce_motion: true
|
|
1205
|
+
},
|
|
1206
|
+
accessibility: {
|
|
1207
|
+
wcag_level: "AA",
|
|
1208
|
+
focus_visible: true,
|
|
1209
|
+
skip_nav: true
|
|
1210
|
+
},
|
|
1211
|
+
personality: ["clean", "modern"]
|
|
1212
|
+
},
|
|
1213
|
+
blueprint: {
|
|
1214
|
+
shell: "sidebar-main",
|
|
1215
|
+
pages: [
|
|
1216
|
+
{ id: "home", layout: ["hero"] }
|
|
1217
|
+
],
|
|
1218
|
+
features: []
|
|
1219
|
+
},
|
|
1220
|
+
meta: {
|
|
1221
|
+
archetype: "custom",
|
|
1222
|
+
target: "react",
|
|
1223
|
+
platform: {
|
|
1224
|
+
type: "spa",
|
|
1225
|
+
routing: "hash"
|
|
1226
|
+
},
|
|
1227
|
+
guard: {
|
|
1228
|
+
mode: "guided",
|
|
1229
|
+
dna_enforcement: "error",
|
|
1230
|
+
blueprint_enforcement: "off"
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
const essencePath = join(projectRoot, "decantr.essence.json");
|
|
1235
|
+
writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
1236
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1237
|
+
const projectJson = {
|
|
1238
|
+
detected: {
|
|
1239
|
+
framework: "unknown",
|
|
1240
|
+
version: null,
|
|
1241
|
+
packageManager: "npm",
|
|
1242
|
+
hasTypeScript: false,
|
|
1243
|
+
hasTailwind: false,
|
|
1244
|
+
existingRuleFiles: []
|
|
1245
|
+
},
|
|
1246
|
+
overrides: {
|
|
1247
|
+
framework: null
|
|
1248
|
+
},
|
|
1249
|
+
sync: {
|
|
1250
|
+
status: "needs-sync",
|
|
1251
|
+
lastSync: now,
|
|
1252
|
+
registrySource: "cache",
|
|
1253
|
+
cachedContent: {
|
|
1254
|
+
archetypes: [],
|
|
1255
|
+
patterns: [],
|
|
1256
|
+
themes: [],
|
|
1257
|
+
recipes: []
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
initialized: {
|
|
1261
|
+
at: now,
|
|
1262
|
+
via: "cli",
|
|
1263
|
+
version: CLI_VERSION,
|
|
1264
|
+
flags: "--offline --minimal"
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
const projectJsonPath = join(decantrDir, "project.json");
|
|
1268
|
+
writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2));
|
|
1269
|
+
const decantrMdPath = join(projectRoot, "DECANTR.md");
|
|
1270
|
+
const decantrMdContent = `# DECANTR.md
|
|
1271
|
+
|
|
1272
|
+
> This file was generated by \`decantr init\` in offline/minimal mode.
|
|
1273
|
+
> Run \`decantr upgrade\` when online to pull full registry content.
|
|
1274
|
+
|
|
1275
|
+
## Two-Layer Model
|
|
1276
|
+
|
|
1277
|
+
This project uses the v3 Essence format with two layers:
|
|
1278
|
+
|
|
1279
|
+
### DNA (Immutable Design Axioms)
|
|
1280
|
+
DNA defines the foundational design rules that must never be violated. DNA violations are **errors**.
|
|
1281
|
+
|
|
1282
|
+
- **Theme:** default (dark mode)
|
|
1283
|
+
- **Spacing:** comfortable density, _gap4
|
|
1284
|
+
- **Radius:** rounded (8px base)
|
|
1285
|
+
- **Accessibility:** WCAG AA
|
|
1286
|
+
- **Personality:** clean, modern
|
|
1287
|
+
|
|
1288
|
+
### Blueprint (Structural Layout)
|
|
1289
|
+
Blueprint defines pages, shells, and patterns. Blueprint deviations are **warnings**.
|
|
1290
|
+
|
|
1291
|
+
| Page | Shell | Layout |
|
|
1292
|
+
|------|-------|--------|
|
|
1293
|
+
| home | sidebar-main | hero |
|
|
1294
|
+
|
|
1295
|
+
## Guard Mode: guided
|
|
1296
|
+
|
|
1297
|
+
- **DNA enforcement:** error (violations block generation)
|
|
1298
|
+
- **Blueprint enforcement:** off (deviations are advisory)
|
|
1299
|
+
|
|
1300
|
+
## MCP Tools
|
|
1301
|
+
|
|
1302
|
+
When available, use these tools:
|
|
1303
|
+
- \`decantr_read_essence\` \u2014 Read the current essence
|
|
1304
|
+
- \`decantr_check_drift\` \u2014 Check for guard violations
|
|
1305
|
+
- \`decantr_accept_drift\` \u2014 Accept a detected drift as intentional
|
|
1306
|
+
- \`decantr_update_essence\` \u2014 Update the essence spec
|
|
1307
|
+
|
|
1308
|
+
## Quick Start
|
|
1309
|
+
|
|
1310
|
+
1. Edit \`decantr.essence.json\` to define your project structure.
|
|
1311
|
+
2. Run \`decantr sync\` when online to fetch registry content.
|
|
1312
|
+
3. Use \`decantr create <type> <name>\` to create custom content.
|
|
1313
|
+
4. Use \`decantr validate\` to check your essence file.
|
|
1314
|
+
|
|
1315
|
+
## Commands
|
|
1316
|
+
|
|
1317
|
+
- \`decantr init\` \u2014 Initialize a new Decantr project
|
|
1318
|
+
- \`decantr status\` \u2014 Project health and DNA/Blueprint overview
|
|
1319
|
+
- \`decantr sync\` \u2014 Sync registry content
|
|
1320
|
+
- \`decantr audit\` \u2014 Audit project for issues
|
|
1321
|
+
- \`decantr migrate\` \u2014 Migrate v2 essence to v3
|
|
1322
|
+
- \`decantr check\` \u2014 Detect drift issues
|
|
1323
|
+
- \`decantr sync-drift\` \u2014 Review and resolve drift entries
|
|
1324
|
+
- \`decantr validate\` \u2014 Validate essence file
|
|
1325
|
+
- \`decantr search\` \u2014 Search the registry
|
|
1326
|
+
- \`decantr suggest\` \u2014 Suggest patterns or alternatives
|
|
1327
|
+
- \`decantr get\` \u2014 Get full details of a registry item
|
|
1328
|
+
- \`decantr list\` \u2014 List items by type
|
|
1329
|
+
- \`decantr theme\` \u2014 Manage custom themes
|
|
1330
|
+
- \`decantr create\` \u2014 Create custom content items
|
|
1331
|
+
- \`decantr publish\` \u2014 Publish custom content to registry
|
|
1332
|
+
- \`decantr login\` \u2014 Authenticate with registry
|
|
1333
|
+
- \`decantr logout\` \u2014 Remove stored credentials
|
|
1334
|
+
- \`decantr upgrade\` \u2014 Check for content updates
|
|
1335
|
+
|
|
1336
|
+
---
|
|
1337
|
+
|
|
1338
|
+
*Generated by @decantr/cli v${CLI_VERSION}*
|
|
1339
|
+
`;
|
|
1340
|
+
writeFileSync(decantrMdPath, decantrMdContent);
|
|
1341
|
+
const gitignoreUpdated = updateGitignore(projectRoot);
|
|
1342
|
+
return {
|
|
1343
|
+
essencePath,
|
|
1344
|
+
decantrMdPath,
|
|
1345
|
+
projectJsonPath,
|
|
1346
|
+
contextFiles: [],
|
|
1347
|
+
cssFiles: [],
|
|
1348
|
+
gitignoreUpdated
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThemeData, prefetchedRecipeData) {
|
|
1352
|
+
const decantrDir = join(projectRoot, ".decantr");
|
|
1353
|
+
const contextDir = join(decantrDir, "context");
|
|
1354
|
+
mkdirSync(contextDir, { recursive: true });
|
|
1355
|
+
const themeName = essence.dna.theme.style;
|
|
1356
|
+
const recipeName = essence.dna.theme.recipe ?? themeName;
|
|
1357
|
+
const mode = essence.dna.theme.mode;
|
|
1358
|
+
const guardMode = essence.meta.guard.mode;
|
|
1359
|
+
const guardConfig = {
|
|
1360
|
+
mode: guardMode,
|
|
1361
|
+
dna_enforcement: essence.meta.guard.dna_enforcement || "error",
|
|
1362
|
+
blueprint_enforcement: essence.meta.guard.blueprint_enforcement || "warn"
|
|
1363
|
+
};
|
|
1364
|
+
const personality = essence.dna.personality || [];
|
|
1365
|
+
let themeData = prefetchedThemeData;
|
|
1366
|
+
let recipeData = prefetchedRecipeData;
|
|
1367
|
+
if (!themeData) try {
|
|
1368
|
+
const themeResult = await registry.fetchTheme(themeName);
|
|
1369
|
+
if (themeResult?.data) {
|
|
1370
|
+
const raw = themeResult.data;
|
|
1371
|
+
const t = raw.data ?? raw;
|
|
1372
|
+
themeData = {
|
|
1373
|
+
seed: t.seed,
|
|
1374
|
+
palette: t.palette,
|
|
1375
|
+
cvd_support: t.cvd_support,
|
|
1376
|
+
tokens: t.tokens,
|
|
1377
|
+
typography_hints: t.typography_hints,
|
|
1378
|
+
motion_hints: t.motion_hints
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
} catch {
|
|
1382
|
+
}
|
|
1383
|
+
if (!recipeData) try {
|
|
1384
|
+
const recipeResult = await registry.fetchRecipe(recipeName);
|
|
1385
|
+
if (recipeResult?.data) {
|
|
1386
|
+
const raw = recipeResult.data;
|
|
1387
|
+
const r = raw.data ?? raw;
|
|
1388
|
+
recipeData = {
|
|
1389
|
+
decorators: r.decorators,
|
|
1390
|
+
spatial_hints: r.spatial_hints,
|
|
1391
|
+
radius_hints: r.radius_hints
|
|
1392
|
+
};
|
|
1393
|
+
if (!recipeData.decorators && raw.data) {
|
|
1394
|
+
const inner = raw.data;
|
|
1395
|
+
if (inner.decorators) {
|
|
1396
|
+
recipeData.decorators = inner.decorators;
|
|
1397
|
+
recipeData.spatial_hints = inner.spatial_hints;
|
|
1398
|
+
recipeData.radius_hints = inner.radius_hints;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
} catch {
|
|
1403
|
+
}
|
|
1404
|
+
if (!recipeData?.decorators || Object.keys(recipeData.decorators).length === 0) {
|
|
1405
|
+
try {
|
|
1406
|
+
const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
|
|
1407
|
+
const resp = await fetch(`${apiUrl}/recipes/@official/${recipeName}`);
|
|
1408
|
+
if (resp.ok) {
|
|
1409
|
+
const apiData = await resp.json();
|
|
1410
|
+
const inner = apiData.data ?? apiData;
|
|
1411
|
+
if (inner.decorators && Object.keys(inner.decorators).length > 0) {
|
|
1412
|
+
recipeData = {
|
|
1413
|
+
decorators: inner.decorators,
|
|
1414
|
+
spatial_hints: inner.spatial_hints,
|
|
1415
|
+
radius_hints: inner.radius_hints
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
} catch {
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (!themeData?.seed?.primary) {
|
|
1423
|
+
try {
|
|
1424
|
+
const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
|
|
1425
|
+
const resp = await fetch(`${apiUrl}/themes/@official/${themeName}`);
|
|
1426
|
+
if (resp.ok) {
|
|
1427
|
+
const apiData = await resp.json();
|
|
1428
|
+
const inner = apiData.data ?? apiData;
|
|
1429
|
+
if (inner.seed) {
|
|
1430
|
+
themeData = {
|
|
1431
|
+
seed: inner.seed,
|
|
1432
|
+
palette: inner.palette,
|
|
1433
|
+
cvd_support: inner.cvd_support,
|
|
1434
|
+
tokens: inner.tokens,
|
|
1435
|
+
typography_hints: inner.typography_hints,
|
|
1436
|
+
motion_hints: inner.motion_hints
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
const stylesDir = join(projectRoot, "src", "styles");
|
|
1444
|
+
mkdirSync(stylesDir, { recursive: true });
|
|
1445
|
+
const tokensPath = join(stylesDir, "tokens.css");
|
|
1446
|
+
const hasRealThemeData = themeData?.seed?.primary || themeData?.palette?.background;
|
|
1447
|
+
if (hasRealThemeData || !existsSync(tokensPath)) {
|
|
1448
|
+
writeFileSync(tokensPath, generateTokensCSS(themeData, mode));
|
|
1449
|
+
}
|
|
1450
|
+
const decoratorsPath = join(stylesDir, "decorators.css");
|
|
1451
|
+
const hasRealRecipeData = recipeData?.decorators && Object.keys(recipeData.decorators).length > 0;
|
|
1452
|
+
if (hasRealRecipeData || !existsSync(decoratorsPath)) {
|
|
1453
|
+
writeFileSync(decoratorsPath, generateDecoratorsCSS(recipeData, themeName));
|
|
1454
|
+
}
|
|
1455
|
+
const cssFiles = [tokensPath, decoratorsPath];
|
|
1456
|
+
const decoratorsMdPath = join(contextDir, "decorators.md");
|
|
1457
|
+
writeFileSync(decoratorsMdPath, generateDecoratorsContext(recipeData, recipeName));
|
|
1458
|
+
const decantrMdPath = join(projectRoot, "DECANTR.md");
|
|
1459
|
+
writeFileSync(decantrMdPath, generateDecantrMdV31(guardMode, CSS_APPROACH_CONTENT));
|
|
1460
|
+
const summaryPath = join(contextDir, "essence-summary.md");
|
|
1461
|
+
writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
|
|
1462
|
+
const contextFiles = [decoratorsMdPath, summaryPath];
|
|
1463
|
+
const blueprint = essence.blueprint;
|
|
1464
|
+
const sections = blueprint.sections && blueprint.sections.length > 0 ? blueprint.sections : [];
|
|
1465
|
+
if (sections.length > 0) {
|
|
1466
|
+
const primarySectionShell = sections.find((s) => s.role === "primary")?.shell || "sidebar-main";
|
|
1467
|
+
for (const section of sections) {
|
|
1468
|
+
if (section.shell === "inherit") {
|
|
1469
|
+
section.shell = primarySectionShell;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
const patternSpecs = {};
|
|
1473
|
+
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1474
|
+
for (const section of sections) {
|
|
1475
|
+
for (const page of section.pages) {
|
|
1476
|
+
for (const item of page.layout) {
|
|
1477
|
+
const names = extractPatternNames(item);
|
|
1478
|
+
for (const name of names) {
|
|
1479
|
+
if (!seenPatterns.has(name)) {
|
|
1480
|
+
seenPatterns.add(name);
|
|
1481
|
+
try {
|
|
1482
|
+
const patResult = await registry.fetchPattern(name);
|
|
1483
|
+
if (patResult?.data) {
|
|
1484
|
+
const raw = patResult.data;
|
|
1485
|
+
const inner = raw.data ?? raw;
|
|
1486
|
+
const defaultPreset = inner.default_preset || "standard";
|
|
1487
|
+
const preset = inner.presets?.[defaultPreset];
|
|
1488
|
+
let slots = preset?.layout?.slots || {};
|
|
1489
|
+
if (Object.keys(slots).length === 0) {
|
|
1490
|
+
const synthetic = generateSyntheticSlots(name, inner.description || "");
|
|
1491
|
+
if (Object.keys(synthetic).length > 0) {
|
|
1492
|
+
slots = synthetic;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
patternSpecs[name] = {
|
|
1496
|
+
description: inner.description || "",
|
|
1497
|
+
components: inner.components || [],
|
|
1498
|
+
slots
|
|
1499
|
+
};
|
|
1500
|
+
} else {
|
|
1501
|
+
const synthetic = generateSyntheticSlots(name, "");
|
|
1502
|
+
if (Object.keys(synthetic).length > 0) {
|
|
1503
|
+
patternSpecs[name] = { description: "", components: [], slots: synthetic };
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
} catch {
|
|
1507
|
+
const synthetic = generateSyntheticSlots(name, "");
|
|
1508
|
+
if (Object.keys(synthetic).length > 0) {
|
|
1509
|
+
patternSpecs[name] = { description: "", components: [], slots: synthetic };
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const zoneInputs = sections.map((s) => ({
|
|
1518
|
+
archetypeId: s.id,
|
|
1519
|
+
role: s.role,
|
|
1520
|
+
shell: s.shell,
|
|
1521
|
+
features: s.features,
|
|
1522
|
+
description: s.description
|
|
1523
|
+
}));
|
|
1524
|
+
const zones = deriveZones(zoneInputs);
|
|
1525
|
+
const transitions = deriveTransitions(zones);
|
|
1526
|
+
const hasPublic = zones.some((z) => z.role === "public");
|
|
1527
|
+
const hasPrimary = zones.some((z) => z.role === "primary");
|
|
1528
|
+
const topologyData = {
|
|
1529
|
+
intent: sections.map((s) => s.id).join(" + "),
|
|
1530
|
+
zones,
|
|
1531
|
+
transitions,
|
|
1532
|
+
entryPoints: {
|
|
1533
|
+
anonymous: hasPublic ? "public zone" : "gateway",
|
|
1534
|
+
authenticated: hasPrimary ? "primary zone" : "first section"
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
const topologyMarkdown = generateTopologySection(topologyData, personality);
|
|
1538
|
+
const themeTokensCss = existsSync(tokensPath) ? readFileSync(tokensPath, "utf-8") : "";
|
|
1539
|
+
const decoratorList = [];
|
|
1540
|
+
if (recipeData?.decorators) {
|
|
1541
|
+
for (const [name, desc] of Object.entries(recipeData.decorators)) {
|
|
1542
|
+
decoratorList.push({ name, description: desc });
|
|
1543
|
+
}
|
|
1544
|
+
} else if (existsSync(decoratorsPath)) {
|
|
1545
|
+
const decoratorsCss = readFileSync(decoratorsPath, "utf-8");
|
|
1546
|
+
const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
|
|
1547
|
+
let match;
|
|
1548
|
+
while ((match = classRegex.exec(decoratorsCss)) !== null) {
|
|
1549
|
+
decoratorList.push({ name: match[2], description: match[1] });
|
|
1550
|
+
}
|
|
1551
|
+
if (decoratorList.length === 0) {
|
|
1552
|
+
const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
|
|
1553
|
+
while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
|
|
1554
|
+
decoratorList.push({ name: match[1], description: "" });
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
const shellInfoCache = {};
|
|
1559
|
+
const seenShells = /* @__PURE__ */ new Set();
|
|
1560
|
+
for (const section of sections) {
|
|
1561
|
+
const shellId = section.shell;
|
|
1562
|
+
if (!seenShells.has(shellId)) {
|
|
1563
|
+
seenShells.add(shellId);
|
|
1564
|
+
try {
|
|
1565
|
+
const shellResult = await registry.fetchShell(shellId);
|
|
1566
|
+
if (shellResult?.data) {
|
|
1567
|
+
const raw = shellResult.data;
|
|
1568
|
+
const inner = raw.data ?? raw;
|
|
1569
|
+
shellInfoCache[shellId] = {
|
|
1570
|
+
description: inner.description || "",
|
|
1571
|
+
regions: inner.config?.regions || [],
|
|
1572
|
+
layout: inner.layout || void 0
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
} catch {
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
for (const section of sections) {
|
|
1580
|
+
const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role === "gateway" ? "Gateway" : "Public";
|
|
1581
|
+
let zoneContext = `**Zone:** ${zoneLabel} (${section.role}) \u2014 ${section.shell} shell`;
|
|
1582
|
+
if (section.role === "gateway") {
|
|
1583
|
+
zoneContext += "\nAuth success \u2192 enters App zone. Sign out returns here.";
|
|
1584
|
+
} else if (section.role === "primary") {
|
|
1585
|
+
zoneContext += "\nAuthenticated users land here. Sign out \u2192 Gateway (/login).";
|
|
1586
|
+
} else if (section.role === "public") {
|
|
1587
|
+
zoneContext += "\nAnonymous visitors. CTAs lead to Gateway (/login, /register).";
|
|
1588
|
+
} else if (section.role === "auxiliary") {
|
|
1589
|
+
zoneContext += "\nSupporting section within App zone. Shares navigation with primary.";
|
|
1590
|
+
}
|
|
1591
|
+
const sectionPatterns = {};
|
|
1592
|
+
for (const page of section.pages) {
|
|
1593
|
+
for (const item of page.layout) {
|
|
1594
|
+
const names = extractPatternNames(item);
|
|
1595
|
+
for (const name of names) {
|
|
1596
|
+
if (patternSpecs[name]) {
|
|
1597
|
+
sectionPatterns[name] = patternSpecs[name];
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
const contextContent = generateSectionContext({
|
|
1603
|
+
section,
|
|
1604
|
+
themeTokens: themeTokensCss,
|
|
1605
|
+
decorators: decoratorList,
|
|
1606
|
+
guardConfig,
|
|
1607
|
+
personality,
|
|
1608
|
+
themeName,
|
|
1609
|
+
recipeName,
|
|
1610
|
+
zoneContext,
|
|
1611
|
+
patternSpecs: sectionPatterns,
|
|
1612
|
+
constraints: essence.dna.constraints,
|
|
1613
|
+
shellInfo: shellInfoCache[section.shell]
|
|
1614
|
+
});
|
|
1615
|
+
const sectionContextPath = join(contextDir, `section-${section.id}.md`);
|
|
1616
|
+
writeFileSync(sectionContextPath, contextContent);
|
|
1617
|
+
contextFiles.push(sectionContextPath);
|
|
1618
|
+
}
|
|
1619
|
+
const routes = blueprint.routes || {};
|
|
1620
|
+
const scaffoldContent = generateScaffoldContext({
|
|
1621
|
+
appName: essence.meta.archetype || "Application",
|
|
1622
|
+
blueprintId: "",
|
|
1623
|
+
themeName,
|
|
1624
|
+
recipeName,
|
|
1625
|
+
personality,
|
|
1626
|
+
topologyMarkdown,
|
|
1627
|
+
sections,
|
|
1628
|
+
routes,
|
|
1629
|
+
constraints: essence.dna.constraints,
|
|
1630
|
+
seo: essence.meta.seo,
|
|
1631
|
+
navigation: essence.meta.navigation
|
|
1632
|
+
});
|
|
1633
|
+
const scaffoldMdPath = join(contextDir, "scaffold.md");
|
|
1634
|
+
writeFileSync(scaffoldMdPath, scaffoldContent);
|
|
1635
|
+
contextFiles.push(scaffoldMdPath);
|
|
1636
|
+
} else {
|
|
1637
|
+
const pages = blueprint.pages || [{ id: "home", layout: ["hero"] }];
|
|
1638
|
+
const shell = blueprint.shell ?? "sidebar-main";
|
|
1639
|
+
const syntheticSection = {
|
|
1640
|
+
id: essence.meta.archetype || "default",
|
|
1641
|
+
role: "primary",
|
|
1642
|
+
shell,
|
|
1643
|
+
features: blueprint.features || [],
|
|
1644
|
+
description: `${essence.meta.archetype || "Application"} section`,
|
|
1645
|
+
pages
|
|
1646
|
+
};
|
|
1647
|
+
const patternSpecs = {};
|
|
1648
|
+
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1649
|
+
for (const page of pages) {
|
|
1650
|
+
for (const item of page.layout) {
|
|
1651
|
+
const names = extractPatternNames(item);
|
|
1652
|
+
for (const name of names) {
|
|
1653
|
+
if (!seenPatterns.has(name)) {
|
|
1654
|
+
seenPatterns.add(name);
|
|
1655
|
+
try {
|
|
1656
|
+
const patResult = await registry.fetchPattern(name);
|
|
1657
|
+
if (patResult?.data) {
|
|
1658
|
+
const raw = patResult.data;
|
|
1659
|
+
const inner = raw.data ?? raw;
|
|
1660
|
+
const defaultPreset = inner.default_preset || "standard";
|
|
1661
|
+
const preset = inner.presets?.[defaultPreset];
|
|
1662
|
+
let slots = preset?.layout?.slots || {};
|
|
1663
|
+
if (Object.keys(slots).length === 0) {
|
|
1664
|
+
const synthetic = generateSyntheticSlots(name, inner.description || "");
|
|
1665
|
+
if (Object.keys(synthetic).length > 0) {
|
|
1666
|
+
slots = synthetic;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
patternSpecs[name] = {
|
|
1670
|
+
description: inner.description || "",
|
|
1671
|
+
components: inner.components || [],
|
|
1672
|
+
slots
|
|
1673
|
+
};
|
|
1674
|
+
} else {
|
|
1675
|
+
const synthetic = generateSyntheticSlots(name, "");
|
|
1676
|
+
if (Object.keys(synthetic).length > 0) {
|
|
1677
|
+
patternSpecs[name] = { description: "", components: [], slots: synthetic };
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
} catch {
|
|
1681
|
+
const synthetic = generateSyntheticSlots(name, "");
|
|
1682
|
+
if (Object.keys(synthetic).length > 0) {
|
|
1683
|
+
patternSpecs[name] = { description: "", components: [], slots: synthetic };
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const themeTokensCss = existsSync(tokensPath) ? readFileSync(tokensPath, "utf-8") : "";
|
|
1691
|
+
const decoratorList = [];
|
|
1692
|
+
if (recipeData?.decorators) {
|
|
1693
|
+
for (const [name, desc] of Object.entries(recipeData.decorators)) {
|
|
1694
|
+
decoratorList.push({ name, description: desc });
|
|
1695
|
+
}
|
|
1696
|
+
} else if (existsSync(decoratorsPath)) {
|
|
1697
|
+
const decoratorsCss = readFileSync(decoratorsPath, "utf-8");
|
|
1698
|
+
const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
|
|
1699
|
+
let match;
|
|
1700
|
+
while ((match = classRegex.exec(decoratorsCss)) !== null) {
|
|
1701
|
+
decoratorList.push({ name: match[2], description: match[1] });
|
|
1702
|
+
}
|
|
1703
|
+
if (decoratorList.length === 0) {
|
|
1704
|
+
const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
|
|
1705
|
+
while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
|
|
1706
|
+
decoratorList.push({ name: match[1], description: "" });
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
let v30ShellInfo;
|
|
1711
|
+
try {
|
|
1712
|
+
const shellResult = await registry.fetchShell(shell);
|
|
1713
|
+
if (shellResult?.data) {
|
|
1714
|
+
const raw = shellResult.data;
|
|
1715
|
+
const inner = raw.data ?? raw;
|
|
1716
|
+
v30ShellInfo = {
|
|
1717
|
+
description: inner.description || "",
|
|
1718
|
+
regions: inner.config?.regions || [],
|
|
1719
|
+
layout: inner.layout || void 0
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
} catch {
|
|
1723
|
+
}
|
|
1724
|
+
const contextContent = generateSectionContext({
|
|
1725
|
+
section: syntheticSection,
|
|
1726
|
+
themeTokens: themeTokensCss,
|
|
1727
|
+
decorators: decoratorList,
|
|
1728
|
+
guardConfig,
|
|
1729
|
+
personality,
|
|
1730
|
+
themeName,
|
|
1731
|
+
recipeName,
|
|
1732
|
+
zoneContext: `This is the primary section (${shell} shell).`,
|
|
1733
|
+
patternSpecs,
|
|
1734
|
+
constraints: essence.dna.constraints,
|
|
1735
|
+
shellInfo: v30ShellInfo
|
|
1736
|
+
});
|
|
1737
|
+
const sectionContextPath = join(contextDir, `section-${syntheticSection.id}.md`);
|
|
1738
|
+
writeFileSync(sectionContextPath, contextContent);
|
|
1739
|
+
contextFiles.push(sectionContextPath);
|
|
1740
|
+
}
|
|
1741
|
+
return {
|
|
1742
|
+
decantrMdPath,
|
|
1743
|
+
contextFiles,
|
|
1744
|
+
cssFiles
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
function generateSyntheticSlots(patternId, description) {
|
|
1748
|
+
const desc = description.toLowerCase();
|
|
1749
|
+
const syntheticSlots = {};
|
|
1750
|
+
if (patternId.includes("feature") || desc.includes("feature")) {
|
|
1751
|
+
syntheticSlots["grid"] = "Grid of feature cards (icon + title + description)";
|
|
1752
|
+
syntheticSlots["feature-card"] = "Individual feature with icon, heading, and description text";
|
|
1753
|
+
}
|
|
1754
|
+
if (patternId.includes("pricing") || desc.includes("pricing")) {
|
|
1755
|
+
syntheticSlots["tiers"] = "Pricing tier cards (name, price, features list, CTA button)";
|
|
1756
|
+
syntheticSlots["toggle"] = "Monthly/annual billing toggle (optional)";
|
|
1757
|
+
}
|
|
1758
|
+
if (patternId.includes("testimonial") || desc.includes("testimonial")) {
|
|
1759
|
+
syntheticSlots["quotes"] = "Testimonial cards (quote text, author name, role, avatar)";
|
|
1760
|
+
}
|
|
1761
|
+
if (patternId.includes("cta") || desc.includes("call-to-action") || desc.includes("call to action")) {
|
|
1762
|
+
syntheticSlots["headline"] = "CTA headline text";
|
|
1763
|
+
syntheticSlots["description"] = "Supporting description text";
|
|
1764
|
+
syntheticSlots["actions"] = "CTA button(s)";
|
|
1765
|
+
}
|
|
1766
|
+
if (patternId.includes("how-it-works") || desc.includes("how it works") || desc.includes("timeline") || desc.includes("steps")) {
|
|
1767
|
+
syntheticSlots["steps"] = "Numbered steps (step number, title, description)";
|
|
1768
|
+
}
|
|
1769
|
+
if (patternId.includes("team") || desc.includes("team")) {
|
|
1770
|
+
syntheticSlots["members"] = "Team member cards (avatar, name, role)";
|
|
1771
|
+
}
|
|
1772
|
+
if (patternId.includes("story") || desc.includes("story") || desc.includes("about")) {
|
|
1773
|
+
syntheticSlots["content"] = "Story/about narrative text content";
|
|
1774
|
+
}
|
|
1775
|
+
if (patternId.includes("values") || desc.includes("values")) {
|
|
1776
|
+
syntheticSlots["values"] = "Value cards (icon/emoji, title, description)";
|
|
1777
|
+
}
|
|
1778
|
+
if (patternId.includes("form") || desc.includes("form") || desc.includes("contact")) {
|
|
1779
|
+
syntheticSlots["fields"] = "Form fields (name, email, message, etc.)";
|
|
1780
|
+
syntheticSlots["submit"] = "Submit button";
|
|
1781
|
+
}
|
|
1782
|
+
if (patternId.includes("content") || desc.includes("legal") || desc.includes("privacy") || desc.includes("policy")) {
|
|
1783
|
+
syntheticSlots["body"] = "Long-form text content with headings and paragraphs";
|
|
1784
|
+
syntheticSlots["toc"] = "Table of contents sidebar (optional)";
|
|
1785
|
+
}
|
|
1786
|
+
if (patternId.includes("settings") || desc.includes("settings") || desc.includes("preferences")) {
|
|
1787
|
+
syntheticSlots["sections"] = "Settings sections (label, description, input/toggle)";
|
|
1788
|
+
}
|
|
1789
|
+
if (patternId.includes("security") || desc.includes("security") || desc.includes("password")) {
|
|
1790
|
+
syntheticSlots["sections"] = "Security sections (password change, MFA toggle, session list)";
|
|
1791
|
+
}
|
|
1792
|
+
if (patternId.includes("session") || desc.includes("session")) {
|
|
1793
|
+
syntheticSlots["list"] = "Active sessions list (device, location, last active, revoke button)";
|
|
1794
|
+
}
|
|
1795
|
+
if (patternId.includes("message") || desc.includes("message") || desc.includes("chat")) {
|
|
1796
|
+
syntheticSlots["messages"] = "Message bubbles (user/assistant, content, timestamp)";
|
|
1797
|
+
}
|
|
1798
|
+
if (patternId.includes("input") && desc.includes("chat")) {
|
|
1799
|
+
syntheticSlots["textarea"] = "Auto-expanding message input";
|
|
1800
|
+
syntheticSlots["actions"] = "Attach file button, send button";
|
|
1801
|
+
}
|
|
1802
|
+
if (patternId.includes("empty") || desc.includes("empty")) {
|
|
1803
|
+
syntheticSlots["illustration"] = "Empty state illustration or icon";
|
|
1804
|
+
syntheticSlots["message"] = "Welcome/empty state message";
|
|
1805
|
+
syntheticSlots["suggestions"] = "Suggested actions or prompts";
|
|
1806
|
+
}
|
|
1807
|
+
if (patternId.includes("header") && desc.includes("chat")) {
|
|
1808
|
+
syntheticSlots["title"] = "Conversation title or model name";
|
|
1809
|
+
syntheticSlots["actions"] = "Header action buttons (new chat, settings)";
|
|
1810
|
+
}
|
|
1811
|
+
return syntheticSlots;
|
|
1812
|
+
}
|
|
1813
|
+
function generateSectionContext(input) {
|
|
1814
|
+
const { section, decorators, guardConfig, personality, themeName, recipeName, zoneContext, patternSpecs, recipeHints, constraints, shellInfo } = input;
|
|
1815
|
+
const lines = [];
|
|
1816
|
+
lines.push(`# Section: ${section.id}`);
|
|
1817
|
+
lines.push("");
|
|
1818
|
+
lines.push(`**Role:** ${section.role} | **Shell:** ${section.shell} | **Archetype:** ${section.id}`);
|
|
1819
|
+
lines.push(`**Description:** ${section.description}`);
|
|
1820
|
+
if (shellInfo) {
|
|
1821
|
+
lines.push(`**Shell structure:** ${shellInfo.description}`);
|
|
1822
|
+
lines.push(`**Regions:** ${shellInfo.regions.join(", ")}`);
|
|
1823
|
+
}
|
|
1824
|
+
if (section.dna_overrides) {
|
|
1825
|
+
const parts = [];
|
|
1826
|
+
if (section.dna_overrides.density) parts.push(`density: ${section.dna_overrides.density}`);
|
|
1827
|
+
if (section.dna_overrides.mode) parts.push(`mode: ${section.dna_overrides.mode}`);
|
|
1828
|
+
if (parts.length > 0) {
|
|
1829
|
+
lines.push(`**DNA Overrides:** ${parts.join(", ")}`);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
lines.push("");
|
|
1833
|
+
lines.push("---");
|
|
1834
|
+
lines.push("");
|
|
1835
|
+
lines.push(`**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`);
|
|
1836
|
+
lines.push("");
|
|
1837
|
+
lines.push(`**Theme tokens:** see \`src/styles/tokens.css\` \u2014 use \`var(--d-primary)\`, \`var(--d-bg)\`, etc.`);
|
|
1838
|
+
lines.push("");
|
|
1839
|
+
if (decorators.length > 0) {
|
|
1840
|
+
const names = decorators.map((d) => d.name).join(", ");
|
|
1841
|
+
lines.push(`**Decorators:** see \`src/styles/decorators.css\` \u2014 available classes: ${names}`);
|
|
1842
|
+
} else {
|
|
1843
|
+
lines.push("**Decorators:** none defined.");
|
|
1844
|
+
}
|
|
1845
|
+
lines.push("");
|
|
1846
|
+
if (recipeHints) {
|
|
1847
|
+
if (recipeHints.preferred && recipeHints.preferred.length > 0) {
|
|
1848
|
+
lines.push(`**Preferred:** ${recipeHints.preferred.join(", ")}`);
|
|
1849
|
+
}
|
|
1850
|
+
if (recipeHints.compositions) {
|
|
1851
|
+
lines.push(`**Compositions:** ${recipeHints.compositions}`);
|
|
1852
|
+
}
|
|
1853
|
+
if (recipeHints.spatialHints) {
|
|
1854
|
+
lines.push(`**Spatial hints:** ${recipeHints.spatialHints}`);
|
|
1855
|
+
}
|
|
1856
|
+
lines.push("");
|
|
1857
|
+
}
|
|
1858
|
+
lines.push("---");
|
|
1859
|
+
lines.push("");
|
|
1860
|
+
if (zoneContext) {
|
|
1861
|
+
lines.push(zoneContext);
|
|
1862
|
+
lines.push("For full app topology, see `.decantr/context/scaffold.md`");
|
|
1863
|
+
lines.push("");
|
|
1864
|
+
}
|
|
1865
|
+
if (section.features.length > 0) {
|
|
1866
|
+
lines.push("## Features");
|
|
1867
|
+
lines.push("");
|
|
1868
|
+
lines.push(section.features.join(", "));
|
|
1869
|
+
lines.push("");
|
|
1870
|
+
lines.push("---");
|
|
1871
|
+
lines.push("");
|
|
1872
|
+
}
|
|
1873
|
+
if (personality.length > 0) {
|
|
1874
|
+
lines.push("## Personality");
|
|
1875
|
+
lines.push("");
|
|
1876
|
+
lines.push(personality.join(", "));
|
|
1877
|
+
lines.push("");
|
|
1878
|
+
lines.push("---");
|
|
1879
|
+
lines.push("");
|
|
1880
|
+
}
|
|
1881
|
+
if (constraints && Object.keys(constraints).length > 0) {
|
|
1882
|
+
lines.push("## Constraints");
|
|
1883
|
+
lines.push("");
|
|
1884
|
+
for (const [key, value] of Object.entries(constraints)) {
|
|
1885
|
+
lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
|
|
1886
|
+
}
|
|
1887
|
+
lines.push("");
|
|
1888
|
+
lines.push("---");
|
|
1889
|
+
lines.push("");
|
|
1890
|
+
}
|
|
1891
|
+
lines.push("## Pages");
|
|
1892
|
+
lines.push("");
|
|
1893
|
+
for (const page of section.pages) {
|
|
1894
|
+
const route = page.route || `/${section.id}/${page.id}`;
|
|
1895
|
+
const layoutStr = page.layout.map(serializeLayoutItem).join(" \u2192 ");
|
|
1896
|
+
lines.push(`### ${page.id} (${route})`);
|
|
1897
|
+
lines.push("");
|
|
1898
|
+
lines.push(`Layout: ${layoutStr}`);
|
|
1899
|
+
lines.push("");
|
|
1900
|
+
if (page.patterns && page.patterns.length > 0) {
|
|
1901
|
+
lines.push("**Pattern mapping:**");
|
|
1902
|
+
lines.push("| Alias | Pattern | Preset |");
|
|
1903
|
+
lines.push("|-------|---------|--------|");
|
|
1904
|
+
for (const ref of page.patterns) {
|
|
1905
|
+
const alias = ref.as || ref.pattern;
|
|
1906
|
+
const preset = ref.preset || "standard";
|
|
1907
|
+
lines.push(`| ${alias} | ${ref.pattern} | ${preset} |`);
|
|
1908
|
+
}
|
|
1909
|
+
lines.push("");
|
|
1910
|
+
}
|
|
1911
|
+
const patternNames = page.layout.flatMap(extractPatternNames);
|
|
1912
|
+
for (const patternName of patternNames) {
|
|
1913
|
+
const spec = patternSpecs[patternName];
|
|
1914
|
+
if (!spec) continue;
|
|
1915
|
+
lines.push(`#### Pattern: ${patternName}`);
|
|
1916
|
+
lines.push("");
|
|
1917
|
+
lines.push(spec.description);
|
|
1918
|
+
lines.push("");
|
|
1919
|
+
lines.push(`**Components:** ${spec.components.join(", ")}`);
|
|
1920
|
+
lines.push("");
|
|
1921
|
+
lines.push("**Layout slots:**");
|
|
1922
|
+
for (const [slot, desc] of Object.entries(spec.slots)) {
|
|
1923
|
+
lines.push(`- \`${slot}\`: ${desc}`);
|
|
1924
|
+
}
|
|
1925
|
+
lines.push("");
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
return lines.join("\n");
|
|
1929
|
+
}
|
|
1930
|
+
function generateScaffoldContext(input) {
|
|
1931
|
+
const { appName, blueprintId, themeName, recipeName, personality, topologyMarkdown, sections, routes, constraints, seo, navigation } = input;
|
|
1932
|
+
const lines = [];
|
|
1933
|
+
lines.push(`# Scaffold: ${appName}`);
|
|
1934
|
+
lines.push("");
|
|
1935
|
+
lines.push(`**Blueprint:** ${blueprintId}`);
|
|
1936
|
+
lines.push(`**Theme:** ${themeName} | **Recipe:** ${recipeName}`);
|
|
1937
|
+
lines.push(`**Personality:** ${personality.join(", ")}`);
|
|
1938
|
+
lines.push("**Guard mode:** creative (no enforcement during initial scaffolding)");
|
|
1939
|
+
lines.push("");
|
|
1940
|
+
lines.push("## App Topology");
|
|
1941
|
+
lines.push("");
|
|
1942
|
+
lines.push(topologyMarkdown);
|
|
1943
|
+
lines.push("");
|
|
1944
|
+
lines.push("## Sections Overview");
|
|
1945
|
+
lines.push("");
|
|
1946
|
+
lines.push("| Section | Role | Shell | Pages | Features |");
|
|
1947
|
+
lines.push("|---------|------|-------|-------|----------|");
|
|
1948
|
+
for (const section of sections) {
|
|
1949
|
+
const pageIds = section.pages.map((p) => p.id).join(", ");
|
|
1950
|
+
const feats = section.features.join(", ") || "none";
|
|
1951
|
+
lines.push(`| ${section.id} | ${section.role} | ${section.shell} | ${pageIds} | ${feats} |`);
|
|
1952
|
+
}
|
|
1953
|
+
lines.push("");
|
|
1954
|
+
lines.push("## Route Map");
|
|
1955
|
+
lines.push("");
|
|
1956
|
+
lines.push("| Route | Section | Page |");
|
|
1957
|
+
lines.push("|-------|---------|------|");
|
|
1958
|
+
for (const [route, entry] of Object.entries(routes)) {
|
|
1959
|
+
lines.push(`| ${route} | ${entry.section} | ${entry.page} |`);
|
|
1960
|
+
}
|
|
1961
|
+
lines.push("");
|
|
1962
|
+
lines.push("## Section Contexts");
|
|
1963
|
+
lines.push("");
|
|
1964
|
+
lines.push("For detailed pattern specs per section, read:");
|
|
1965
|
+
for (const section of sections) {
|
|
1966
|
+
lines.push(`- .decantr/context/section-${section.id}.md`);
|
|
1967
|
+
}
|
|
1968
|
+
lines.push("");
|
|
1969
|
+
const patternUsage = {};
|
|
1970
|
+
for (const section of sections) {
|
|
1971
|
+
for (const page of section.pages) {
|
|
1972
|
+
const names = page.layout.flatMap(extractPatternNames);
|
|
1973
|
+
for (const name of names) {
|
|
1974
|
+
if (!patternUsage[name]) patternUsage[name] = [];
|
|
1975
|
+
const pageLabel = sections.length > 1 ? `${section.id}/${page.id}` : page.id;
|
|
1976
|
+
patternUsage[name].push(pageLabel);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
const sharedPatterns = Object.entries(patternUsage).filter(([, pages]) => pages.length >= 2);
|
|
1981
|
+
if (sharedPatterns.length > 0) {
|
|
1982
|
+
lines.push("## Shared Components");
|
|
1983
|
+
lines.push("");
|
|
1984
|
+
lines.push("These patterns appear on multiple pages. Consider creating shared components:");
|
|
1985
|
+
lines.push("");
|
|
1986
|
+
lines.push("| Pattern | Used by |");
|
|
1987
|
+
lines.push("|---------|---------|");
|
|
1988
|
+
for (const [pattern, pages] of sharedPatterns) {
|
|
1989
|
+
lines.push(`| ${pattern} | ${pages.join(", ")} |`);
|
|
1990
|
+
}
|
|
1991
|
+
lines.push("");
|
|
1992
|
+
}
|
|
1993
|
+
if (constraints && Object.keys(constraints).length > 0) {
|
|
1994
|
+
lines.push("## Design Constraints");
|
|
1995
|
+
lines.push("");
|
|
1996
|
+
for (const [key, value] of Object.entries(constraints)) {
|
|
1997
|
+
lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
|
|
1998
|
+
}
|
|
1999
|
+
lines.push("");
|
|
2000
|
+
}
|
|
2001
|
+
if (seo && (seo.schema_org?.length || seo.meta_priorities?.length)) {
|
|
2002
|
+
lines.push("## SEO Hints");
|
|
2003
|
+
lines.push("");
|
|
2004
|
+
if (seo.schema_org && seo.schema_org.length > 0) {
|
|
2005
|
+
lines.push(`**Schema.org types:** ${seo.schema_org.join(", ")}`);
|
|
2006
|
+
}
|
|
2007
|
+
if (seo.meta_priorities && seo.meta_priorities.length > 0) {
|
|
2008
|
+
lines.push(`**Meta priorities:** ${seo.meta_priorities.join(", ")}`);
|
|
2009
|
+
}
|
|
2010
|
+
lines.push("");
|
|
2011
|
+
}
|
|
2012
|
+
if (navigation && (navigation.hotkeys?.length || navigation.command_palette)) {
|
|
2013
|
+
lines.push("## Navigation");
|
|
2014
|
+
lines.push("");
|
|
2015
|
+
if (navigation.command_palette) {
|
|
2016
|
+
lines.push("- Command palette: enabled");
|
|
2017
|
+
}
|
|
2018
|
+
if (navigation.hotkeys && navigation.hotkeys.length > 0) {
|
|
2019
|
+
lines.push(`- Hotkeys: ${navigation.hotkeys.length} configured`);
|
|
2020
|
+
}
|
|
2021
|
+
lines.push("");
|
|
2022
|
+
}
|
|
2023
|
+
return lines.join("\n");
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
// src/registry.ts
|
|
2027
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync } from "fs";
|
|
2028
|
+
import { join as join2 } from "path";
|
|
2029
|
+
import { RegistryAPIClient } from "@decantr/registry";
|
|
2030
|
+
var DEFAULT_API_URL = "https://api.decantr.ai/v1";
|
|
2031
|
+
var ALL_CONTENT_TYPES = ["themes", "patterns", "recipes", "blueprints", "archetypes", "shells"];
|
|
2032
|
+
function loadFromCache(cacheDir, contentType, id, namespace) {
|
|
2033
|
+
const nsDir = namespace ? join2(cacheDir, namespace) : cacheDir;
|
|
2034
|
+
const cachePath = id ? join2(nsDir, contentType, `${id}.json`) : join2(nsDir, contentType, "index.json");
|
|
2035
|
+
if (!existsSync2(cachePath)) return null;
|
|
2036
|
+
try {
|
|
2037
|
+
const data = JSON.parse(readFileSync2(cachePath, "utf-8"));
|
|
2038
|
+
return { data, source: { type: "cache" } };
|
|
2039
|
+
} catch {
|
|
2040
|
+
return null;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
function saveToCache(cacheDir, contentType, id, data, namespace = "@official") {
|
|
2044
|
+
const dir = join2(cacheDir, namespace, contentType);
|
|
2045
|
+
mkdirSync2(dir, { recursive: true });
|
|
2046
|
+
const cachePath = id ? join2(dir, `${id}.json`) : join2(dir, "index.json");
|
|
2047
|
+
writeFileSync2(cachePath, JSON.stringify(data, null, 2));
|
|
2048
|
+
}
|
|
2049
|
+
var RegistryClient = class {
|
|
2050
|
+
cacheDir;
|
|
2051
|
+
apiUrl;
|
|
2052
|
+
offline;
|
|
2053
|
+
projectRoot;
|
|
2054
|
+
apiClient;
|
|
2055
|
+
constructor(options = {}) {
|
|
2056
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
2057
|
+
this.cacheDir = options.cacheDir || join2(this.projectRoot, ".decantr", "cache");
|
|
2058
|
+
this.apiUrl = options.apiUrl || DEFAULT_API_URL;
|
|
2059
|
+
this.offline = options.offline || false;
|
|
2060
|
+
this.apiClient = new RegistryAPIClient({
|
|
2061
|
+
baseUrl: this.apiUrl,
|
|
2062
|
+
apiKey: options.apiKey
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Load content from .decantr/custom/{contentType}/{id}.json
|
|
2067
|
+
* Works for ALL content types, not just themes.
|
|
2068
|
+
*/
|
|
2069
|
+
loadCustomContent(contentType, id) {
|
|
2070
|
+
const customPath = join2(
|
|
2071
|
+
this.projectRoot,
|
|
2072
|
+
".decantr",
|
|
2073
|
+
"custom",
|
|
2074
|
+
contentType,
|
|
2075
|
+
`${id}.json`
|
|
2076
|
+
);
|
|
2077
|
+
if (!existsSync2(customPath)) return null;
|
|
2078
|
+
try {
|
|
2079
|
+
const data = JSON.parse(readFileSync2(customPath, "utf-8"));
|
|
2080
|
+
return { data, source: { type: "custom", path: customPath } };
|
|
2081
|
+
} catch {
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* List all custom content of a given type from .decantr/custom/{type}/
|
|
2087
|
+
*/
|
|
2088
|
+
listCustomContent(contentType) {
|
|
2089
|
+
const dir = join2(this.projectRoot, ".decantr", "custom", contentType);
|
|
2090
|
+
if (!existsSync2(dir)) return [];
|
|
2091
|
+
try {
|
|
2092
|
+
return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
2093
|
+
const data = JSON.parse(readFileSync2(join2(dir, f), "utf-8"));
|
|
2094
|
+
return { id: data.id || f.replace(".json", ""), ...data };
|
|
2095
|
+
});
|
|
2096
|
+
} catch {
|
|
2097
|
+
return [];
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Unified fetch for a content list.
|
|
2102
|
+
* Resolution: API -> Cache. Custom items are merged into the list.
|
|
2103
|
+
*/
|
|
2104
|
+
async fetchContentList(contentType, namespace) {
|
|
2105
|
+
let apiItems = [];
|
|
2106
|
+
let source = { type: "cache" };
|
|
2107
|
+
if (!this.offline) {
|
|
2108
|
+
try {
|
|
2109
|
+
const apiResult = await this.apiClient.listContent(contentType, { namespace });
|
|
2110
|
+
apiItems = apiResult.items;
|
|
2111
|
+
source = { type: "api", url: this.apiUrl };
|
|
2112
|
+
saveToCache(this.cacheDir, contentType, null, apiResult, namespace || "@official");
|
|
2113
|
+
} catch {
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
if (apiItems.length === 0) {
|
|
2117
|
+
const cacheResult = loadFromCache(
|
|
2118
|
+
this.cacheDir,
|
|
2119
|
+
contentType,
|
|
2120
|
+
void 0,
|
|
2121
|
+
namespace
|
|
2122
|
+
);
|
|
2123
|
+
if (cacheResult) {
|
|
2124
|
+
apiItems = cacheResult.data.items;
|
|
2125
|
+
source = { type: "cache" };
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
const customItems = this.listCustomContent(contentType);
|
|
2129
|
+
const allItems = [...customItems, ...apiItems];
|
|
2130
|
+
return {
|
|
2131
|
+
data: { items: allItems, total: allItems.length },
|
|
2132
|
+
source
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Unified fetch for a single content item.
|
|
2137
|
+
* Resolution: Custom -> API -> Cache
|
|
2138
|
+
*/
|
|
2139
|
+
async fetchContentItem(contentType, id, namespace = "@official") {
|
|
2140
|
+
const customId = id.startsWith("custom:") ? id.slice(7) : id;
|
|
2141
|
+
const customResult = this.loadCustomContent(contentType, customId);
|
|
2142
|
+
if (customResult) return customResult;
|
|
2143
|
+
if (id.startsWith("custom:")) return null;
|
|
2144
|
+
if (!this.offline) {
|
|
2145
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2146
|
+
try {
|
|
2147
|
+
const data = await this.apiClient.getContent(contentType, namespace, id);
|
|
2148
|
+
saveToCache(this.cacheDir, contentType, id, data, namespace);
|
|
2149
|
+
return { data, source: { type: "api", url: this.apiUrl } };
|
|
2150
|
+
} catch (e) {
|
|
2151
|
+
if (process.env.DECANTR_DEBUG) {
|
|
2152
|
+
console.error(` [debug] API fetch ${attempt === 0 ? "failed" : "retry failed"} for ${contentType}/${namespace}/${id}: ${e.message}`);
|
|
2153
|
+
}
|
|
2154
|
+
if (attempt === 0) {
|
|
2155
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
} else if (process.env.DECANTR_DEBUG) {
|
|
2160
|
+
console.error(` [debug] Skipping API (offline mode) for ${contentType}/${namespace}/${id}`);
|
|
2161
|
+
}
|
|
2162
|
+
return loadFromCache(this.cacheDir, contentType, id, namespace);
|
|
2163
|
+
}
|
|
2164
|
+
// ── Convenience methods (delegate to unified fetch) ──
|
|
2165
|
+
async fetchArchetypes() {
|
|
2166
|
+
return this.fetchContentList("archetypes");
|
|
2167
|
+
}
|
|
2168
|
+
async fetchArchetype(id) {
|
|
2169
|
+
return this.fetchContentItem("archetypes", id);
|
|
2170
|
+
}
|
|
2171
|
+
async fetchBlueprints() {
|
|
2172
|
+
return this.fetchContentList("blueprints");
|
|
2173
|
+
}
|
|
2174
|
+
async fetchBlueprint(id) {
|
|
2175
|
+
return this.fetchContentItem("blueprints", id);
|
|
2176
|
+
}
|
|
2177
|
+
async fetchThemes() {
|
|
2178
|
+
return this.fetchContentList("themes");
|
|
2179
|
+
}
|
|
2180
|
+
async fetchTheme(id) {
|
|
2181
|
+
return this.fetchContentItem("themes", id);
|
|
2182
|
+
}
|
|
2183
|
+
async fetchPatterns() {
|
|
2184
|
+
return this.fetchContentList("patterns");
|
|
2185
|
+
}
|
|
2186
|
+
async fetchPattern(id) {
|
|
2187
|
+
return this.fetchContentItem("patterns", id);
|
|
2188
|
+
}
|
|
2189
|
+
async fetchShells() {
|
|
2190
|
+
return this.fetchContentList("shells");
|
|
2191
|
+
}
|
|
2192
|
+
async fetchShell(id) {
|
|
2193
|
+
return this.fetchContentItem("shells", id);
|
|
2194
|
+
}
|
|
2195
|
+
async fetchRecipes() {
|
|
2196
|
+
return this.fetchContentList("recipes");
|
|
2197
|
+
}
|
|
2198
|
+
async fetchRecipe(id) {
|
|
2199
|
+
return this.fetchContentItem("recipes", id);
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Check if API is available.
|
|
2203
|
+
*/
|
|
2204
|
+
async checkApiAvailability() {
|
|
2205
|
+
if (this.offline) return false;
|
|
2206
|
+
return this.apiClient.checkHealth();
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
|
|
2210
|
+
const apiClient = new RegistryAPIClient({ baseUrl: apiUrl });
|
|
2211
|
+
const synced = [];
|
|
2212
|
+
const failed = [];
|
|
2213
|
+
const healthy = await apiClient.checkHealth();
|
|
2214
|
+
if (!healthy) {
|
|
2215
|
+
return { synced: [], failed: ["API unavailable"] };
|
|
2216
|
+
}
|
|
2217
|
+
for (const type of ALL_CONTENT_TYPES) {
|
|
2218
|
+
try {
|
|
2219
|
+
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
2220
|
+
saveToCache(cacheDir, type, null, result, "@official");
|
|
2221
|
+
for (const item of result.items) {
|
|
2222
|
+
const slug = item.slug;
|
|
2223
|
+
const data = item.data;
|
|
2224
|
+
const innerSlug = data?.id || data?.slug;
|
|
2225
|
+
const cacheKey = slug || innerSlug || item.id;
|
|
2226
|
+
if (cacheKey) {
|
|
2227
|
+
saveToCache(cacheDir, type, cacheKey, item, "@official");
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
synced.push(type);
|
|
2231
|
+
} catch {
|
|
2232
|
+
failed.push(type);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
return { synced, failed };
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
export {
|
|
2239
|
+
composeArchetypes,
|
|
2240
|
+
composeSections,
|
|
2241
|
+
deriveZones,
|
|
2242
|
+
deriveTransitions,
|
|
2243
|
+
generateTopologySection,
|
|
2244
|
+
scaffoldProject,
|
|
2245
|
+
scaffoldMinimal,
|
|
2246
|
+
refreshDerivedFiles,
|
|
2247
|
+
RegistryClient,
|
|
2248
|
+
syncRegistry
|
|
2249
|
+
};
|