@calibrate-ds/core 0.1.13 → 0.1.15

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.
Files changed (41) hide show
  1. package/dist/context/compressed-context.d.ts +1 -1
  2. package/dist/context/compressed-context.d.ts.map +1 -1
  3. package/dist/context/compressed-context.js +14 -18
  4. package/dist/context/compressed-context.js.map +1 -1
  5. package/dist/context/export-context.d.ts +18 -0
  6. package/dist/context/export-context.d.ts.map +1 -1
  7. package/dist/context/export-context.js +15 -0
  8. package/dist/context/export-context.js.map +1 -1
  9. package/dist/context/get-component-context.d.ts.map +1 -1
  10. package/dist/context/get-component-context.js +30 -1
  11. package/dist/context/get-component-context.js.map +1 -1
  12. package/dist/docs/setup-storybook.d.ts +9 -1
  13. package/dist/docs/setup-storybook.d.ts.map +1 -1
  14. package/dist/docs/setup-storybook.js +53 -4
  15. package/dist/docs/setup-storybook.js.map +1 -1
  16. package/dist/icons/enrich-bitmap-assets.js +19 -5
  17. package/dist/icons/enrich-bitmap-assets.js.map +1 -1
  18. package/dist/icons/enrich-icon-svgs.d.ts.map +1 -1
  19. package/dist/icons/enrich-icon-svgs.js +4 -2
  20. package/dist/icons/enrich-icon-svgs.js.map +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/mappings/naming-strategy.js +5 -0
  26. package/dist/mappings/naming-strategy.js.map +1 -1
  27. package/dist/normalizer/normalize-components.d.ts.map +1 -1
  28. package/dist/normalizer/normalize-components.js +7 -1
  29. package/dist/normalizer/normalize-components.js.map +1 -1
  30. package/dist/normalizer/variant-diff.d.ts +16 -0
  31. package/dist/normalizer/variant-diff.d.ts.map +1 -1
  32. package/dist/normalizer/variant-diff.js +69 -7
  33. package/dist/normalizer/variant-diff.js.map +1 -1
  34. package/dist/playground/scaffold-playground.d.ts +4 -0
  35. package/dist/playground/scaffold-playground.d.ts.map +1 -0
  36. package/dist/playground/scaffold-playground.js +367 -0
  37. package/dist/playground/scaffold-playground.js.map +1 -0
  38. package/dist/prompt/generate-component-prompt.d.ts.map +1 -1
  39. package/dist/prompt/generate-component-prompt.js +55 -25
  40. package/dist/prompt/generate-component-prompt.js.map +1 -1
  41. package/package.json +3 -3
@@ -0,0 +1,367 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ // ---------------------------------------------------------------------------
5
+ // Template files written once into .ptb/playground/
6
+ // ---------------------------------------------------------------------------
7
+ const PACKAGE_JSON = JSON.stringify({
8
+ private: true,
9
+ name: "ptb-playground",
10
+ version: "0.0.1",
11
+ type: "module",
12
+ scripts: { dev: "vite" },
13
+ dependencies: {
14
+ react: "^18.2.0",
15
+ "react-dom": "^18.2.0",
16
+ },
17
+ devDependencies: {
18
+ "@vitejs/plugin-react": "^4.2.0",
19
+ "@types/react": "^18.2.0",
20
+ "@types/react-dom": "^18.2.0",
21
+ typescript: "^5.2.0",
22
+ vite: "^5.0.0",
23
+ },
24
+ }, null, 2) + "\n";
25
+ const VITE_CONFIG = `import { defineConfig } from 'vite'
26
+ import react from '@vitejs/plugin-react'
27
+
28
+ export default defineConfig({
29
+ plugins: [react()],
30
+ resolve: {
31
+ // Force all react imports through the playground's own copy — prevents
32
+ // duplicate-React errors when the user's component files also import react.
33
+ dedupe: ['react', 'react-dom'],
34
+ },
35
+ server: {
36
+ port: 4321,
37
+ open: false,
38
+ fs: {
39
+ // Allow Vite to serve files from outside the playground directory
40
+ // so it can read the user's component source files.
41
+ strict: false,
42
+ },
43
+ },
44
+ })
45
+ `;
46
+ const TSCONFIG = JSON.stringify({
47
+ compilerOptions: {
48
+ target: "ES2020",
49
+ useDefineForClassFields: true,
50
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
51
+ module: "ESNext",
52
+ skipLibCheck: true,
53
+ moduleResolution: "bundler",
54
+ allowImportingTsExtensions: true,
55
+ resolveJsonModule: true,
56
+ isolatedModules: true,
57
+ noEmit: true,
58
+ jsx: "react-jsx",
59
+ strict: false,
60
+ },
61
+ include: ["src"],
62
+ }, null, 2) + "\n";
63
+ const INDEX_HTML = `<!DOCTYPE html>
64
+ <html lang="en">
65
+ <head>
66
+ <meta charset="UTF-8" />
67
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
68
+ <title>PTB Playground</title>
69
+ <style>
70
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
71
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0d0d0d; color: #e8e8e8; }
72
+ button { font-family: inherit; }
73
+ </style>
74
+ </head>
75
+ <body>
76
+ <div id="root"></div>
77
+ <script type="module" src="/src/main.tsx"></script>
78
+ </body>
79
+ </html>
80
+ `;
81
+ const MAIN_TSX = `import React from 'react'
82
+ import ReactDOM from 'react-dom/client'
83
+ import { App } from './App'
84
+
85
+ ReactDOM.createRoot(document.getElementById('root')!).render(
86
+ <React.StrictMode>
87
+ <App />
88
+ </React.StrictMode>
89
+ )
90
+ `;
91
+ // registry.ts is generated by ptb dev — this is the empty placeholder so
92
+ // the app compiles before any components are implemented.
93
+ const REGISTRY_PLACEHOLDER = `// AUTO-GENERATED by ptb dev — do not edit
94
+ export type RegistryEntry = {
95
+ name: string;
96
+ slug: string;
97
+ Component: any;
98
+ variantAxes: Array<{ name: string; options: string[]; defaultValue?: string }>;
99
+ };
100
+ export const registry: RegistryEntry[] = [];
101
+ `;
102
+ const APP_TSX = `import React, { useState, useEffect } from 'react'
103
+ import { ComponentTile } from './ComponentTile'
104
+ import { registry } from './registry'
105
+
106
+ function getSlugFromUrl(): string | null {
107
+ const parts = window.location.pathname.split('/').filter(Boolean)
108
+ return parts[0] === 'component' && parts[1] ? parts[1] : null
109
+ }
110
+
111
+ export function App() {
112
+ const [selectedSlug, setSelectedSlug] = useState<string | null>(getSlugFromUrl)
113
+
114
+ useEffect(() => {
115
+ const onPop = () => setSelectedSlug(getSlugFromUrl())
116
+ window.addEventListener('popstate', onPop)
117
+ return () => window.removeEventListener('popstate', onPop)
118
+ }, [])
119
+
120
+ function navigate(slug: string | null) {
121
+ const url = slug ? \`/component/\${slug}\` : '/'
122
+ window.history.pushState({}, '', url)
123
+ setSelectedSlug(slug)
124
+ }
125
+
126
+ const selected = selectedSlug ? registry.find(e => e.slug === selectedSlug) : null
127
+
128
+ return (
129
+ <div style={{ display: 'flex', height: '100vh', overflow: 'hidden' }}>
130
+ <div style={{
131
+ width: 220, flexShrink: 0, background: '#111',
132
+ borderRight: '1px solid #1e1e1e', display: 'flex',
133
+ flexDirection: 'column', overflow: 'hidden',
134
+ }}>
135
+ <div style={{ padding: '14px 14px 8px', fontSize: 11, fontWeight: 600, color: '#444', letterSpacing: '0.08em', textTransform: 'uppercase' }}>
136
+ Components
137
+ </div>
138
+ <div style={{ overflowY: 'auto', flex: 1 }}>
139
+ {registry.length === 0 && (
140
+ <div style={{ padding: '10px 14px', fontSize: 12, color: '#444', lineHeight: 1.6 }}>
141
+ No components yet.<br />Run <code style={{ color: '#666', fontSize: 11 }}>ptb implement component &lt;name&gt;</code>
142
+ </div>
143
+ )}
144
+ {registry.map(entry => (
145
+ <button
146
+ key={entry.slug}
147
+ onClick={() => navigate(entry.slug)}
148
+ style={{
149
+ display: 'block', width: '100%', textAlign: 'left',
150
+ padding: '7px 14px', fontSize: 13,
151
+ background: selectedSlug === entry.slug ? '#1a1a1a' : 'transparent',
152
+ color: selectedSlug === entry.slug ? '#e8e8e8' : '#777',
153
+ border: 'none', cursor: 'pointer',
154
+ borderLeft: selectedSlug === entry.slug ? '2px solid #3b82f6' : '2px solid transparent',
155
+ }}
156
+ >
157
+ {entry.name}
158
+ </button>
159
+ ))}
160
+ </div>
161
+ <div style={{ padding: '10px 14px', fontSize: 10, color: '#2a2a2a', borderTop: '1px solid #1a1a1a' }}>
162
+ ptb playground
163
+ </div>
164
+ </div>
165
+
166
+ <div style={{ flex: 1, overflow: 'auto', padding: 32 }}>
167
+ {selected ? (
168
+ <ComponentTile entry={selected} />
169
+ ) : (
170
+ <div>
171
+ {registry.length > 0 && (
172
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 12 }}>
173
+ {registry.map(entry => (
174
+ <button
175
+ key={entry.slug}
176
+ onClick={() => navigate(entry.slug)}
177
+ style={{
178
+ background: '#111', border: '1px solid #1e1e1e', borderRadius: 8,
179
+ padding: '20px 20px 22px', cursor: 'pointer', textAlign: 'left', color: 'inherit',
180
+ }}
181
+ >
182
+ <ComponentPreview entry={entry} />
183
+ <div style={{ marginTop: 14, fontSize: 13, fontWeight: 500, color: '#c8c8c8' }}>{entry.name}</div>
184
+ {entry.variantAxes.length > 0 && (
185
+ <div style={{ marginTop: 3, fontSize: 11, color: '#444' }}>
186
+ {entry.variantAxes.map(a => a.name).join(' · ')}
187
+ </div>
188
+ )}
189
+ </button>
190
+ ))}
191
+ </div>
192
+ )}
193
+ </div>
194
+ )}
195
+ </div>
196
+ </div>
197
+ )
198
+ }
199
+
200
+ function ComponentPreview({ entry }: { entry: import('./registry').RegistryEntry }) {
201
+ const defaultProps = Object.fromEntries(
202
+ entry.variantAxes.map(a => [a.name, a.defaultValue ?? a.options[0] ?? ''])
203
+ )
204
+ return (
205
+ <div style={{ background: '#0d0d0d', borderRadius: 6, padding: 20, minHeight: 72, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
206
+ <ErrorBoundary fallback={<span style={{ fontSize: 11, color: '#f87171' }}>render error</span>}>
207
+ <entry.Component {...defaultProps} />
208
+ </ErrorBoundary>
209
+ </div>
210
+ )
211
+ }
212
+
213
+ class ErrorBoundary extends React.Component<
214
+ { children: React.ReactNode; fallback: React.ReactNode },
215
+ { hasError: boolean }
216
+ > {
217
+ constructor(props: any) { super(props); this.state = { hasError: false } }
218
+ static getDerivedStateFromError() { return { hasError: true } }
219
+ render() { return this.state.hasError ? this.props.fallback : this.props.children }
220
+ }
221
+ `;
222
+ const COMPONENT_TILE_TSX = `import React, { useState } from 'react'
223
+ import type { RegistryEntry } from './registry'
224
+
225
+ class ErrorBoundary extends React.Component<
226
+ { children: React.ReactNode },
227
+ { error: Error | null }
228
+ > {
229
+ constructor(props: any) { super(props); this.state = { error: null } }
230
+ static getDerivedStateFromError(e: Error) { return { error: e } }
231
+ render() {
232
+ if (this.state.error) {
233
+ return (
234
+ <div style={{ padding: 14, background: '#1a0808', border: '1px solid #7f1d1d', borderRadius: 6, color: '#fca5a5', fontSize: 12 }}>
235
+ <strong>Render error</strong>
236
+ <pre style={{ marginTop: 6, whiteSpace: 'pre-wrap', fontFamily: 'monospace', fontSize: 11 }}>{this.state.error.message}</pre>
237
+ </div>
238
+ )
239
+ }
240
+ return this.props.children
241
+ }
242
+ }
243
+
244
+ export function ComponentTile({ entry }: { entry: RegistryEntry }) {
245
+ const [variantProps, setVariantProps] = useState<Record<string, string>>(
246
+ () => Object.fromEntries(entry.variantAxes.map(a => [a.name, a.defaultValue ?? a.options[0] ?? '']))
247
+ )
248
+
249
+ const allCombinations = getAllCombinations(entry.variantAxes)
250
+ const showGrid = allCombinations.length > 1 && allCombinations.length <= 48
251
+
252
+ return (
253
+ <div>
254
+ <div style={{ marginBottom: 24 }}>
255
+ <h2 style={{ fontSize: 18, fontWeight: 600, color: '#e8e8e8' }}>{entry.name}</h2>
256
+ {entry.variantAxes.length > 0 && (
257
+ <div style={{ marginTop: 3, fontSize: 11, color: '#555' }}>
258
+ {entry.variantAxes.map(a => \`\${a.name}: \${a.options.join(', ')}\`).join(' · ')}
259
+ </div>
260
+ )}
261
+ </div>
262
+
263
+ {/* Variant controls */}
264
+ {entry.variantAxes.length > 0 && (
265
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16, marginBottom: 28 }}>
266
+ {entry.variantAxes.map(axis => (
267
+ <div key={axis.name}>
268
+ <div style={{ fontSize: 10, color: '#555', marginBottom: 5, textTransform: 'uppercase', letterSpacing: '0.07em' }}>{axis.name}</div>
269
+ <div style={{ display: 'flex', gap: 4 }}>
270
+ {axis.options.map(val => (
271
+ <button
272
+ key={val}
273
+ onClick={() => setVariantProps(p => ({ ...p, [axis.name]: val }))}
274
+ style={{
275
+ padding: '3px 9px', fontSize: 12, borderRadius: 4, cursor: 'pointer',
276
+ background: variantProps[axis.name] === val ? '#1d4ed8' : '#181818',
277
+ color: variantProps[axis.name] === val ? '#fff' : '#777',
278
+ border: variantProps[axis.name] === val ? '1px solid #2563eb' : '1px solid #262626',
279
+ }}
280
+ >{val}</button>
281
+ ))}
282
+ </div>
283
+ </div>
284
+ ))}
285
+ </div>
286
+ )}
287
+
288
+ {/* Live preview */}
289
+ <div style={{ marginBottom: 36 }}>
290
+ <div style={{ fontSize: 10, color: '#444', marginBottom: 8, textTransform: 'uppercase', letterSpacing: '0.07em' }}>Preview</div>
291
+ <div style={{ background: '#111', border: '1px solid #1e1e1e', borderRadius: 8, padding: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: 100 }}>
292
+ <ErrorBoundary>
293
+ <entry.Component {...variantProps} />
294
+ </ErrorBoundary>
295
+ </div>
296
+ </div>
297
+
298
+ {/* All variants grid */}
299
+ {showGrid && (
300
+ <div>
301
+ <div style={{ fontSize: 10, color: '#444', marginBottom: 10, textTransform: 'uppercase', letterSpacing: '0.07em' }}>
302
+ All variants ({allCombinations.length})
303
+ </div>
304
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: 8 }}>
305
+ {allCombinations.map((combo, i) => (
306
+ <div key={i} style={{ background: '#111', border: '1px solid #1e1e1e', borderRadius: 6, padding: '14px 14px 12px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
307
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }}>
308
+ <ErrorBoundary>
309
+ <entry.Component {...combo} />
310
+ </ErrorBoundary>
311
+ </div>
312
+ <div style={{ fontSize: 10, color: '#3a3a3a', textAlign: 'center', lineHeight: 1.5, wordBreak: 'break-all' }}>
313
+ {Object.entries(combo).map(([k, v]) => \`\${k}=\${v}\`).join(' ')}
314
+ </div>
315
+ </div>
316
+ ))}
317
+ </div>
318
+ </div>
319
+ )}
320
+ </div>
321
+ )
322
+ }
323
+
324
+ function getAllCombinations(axes: Array<{ name: string; options: string[] }>): Record<string, string>[] {
325
+ if (axes.length === 0) return [{}]
326
+ const [first, ...rest] = axes
327
+ const restCombos = getAllCombinations(rest)
328
+ return first.options.flatMap(val =>
329
+ restCombos.map(combo => ({ [first.name]: val, ...combo }))
330
+ )
331
+ }
332
+ `;
333
+ // ---------------------------------------------------------------------------
334
+ // Public API
335
+ // ---------------------------------------------------------------------------
336
+ export async function scaffoldPlayground(metadataDir) {
337
+ const playgroundDir = path.join(metadataDir, "playground");
338
+ const srcDir = path.join(playgroundDir, "src");
339
+ await fs.mkdir(srcDir, { recursive: true });
340
+ const alreadyScaffolded = await fs.access(path.join(playgroundDir, "index.html")).then(() => true).catch(() => false);
341
+ if (!alreadyScaffolded) {
342
+ await Promise.all([
343
+ fs.writeFile(path.join(playgroundDir, "package.json"), PACKAGE_JSON),
344
+ fs.writeFile(path.join(playgroundDir, "vite.config.ts"), VITE_CONFIG),
345
+ fs.writeFile(path.join(playgroundDir, "tsconfig.json"), TSCONFIG),
346
+ fs.writeFile(path.join(playgroundDir, "index.html"), INDEX_HTML),
347
+ fs.writeFile(path.join(srcDir, "main.tsx"), MAIN_TSX),
348
+ fs.writeFile(path.join(srcDir, "App.tsx"), APP_TSX),
349
+ fs.writeFile(path.join(srcDir, "ComponentTile.tsx"), COMPONENT_TILE_TSX),
350
+ fs.writeFile(path.join(srcDir, "registry.ts"), REGISTRY_PLACEHOLDER),
351
+ ]);
352
+ }
353
+ const nodeModulesExists = await fs.access(path.join(playgroundDir, "node_modules")).then(() => true).catch(() => false);
354
+ if (!nodeModulesExists) {
355
+ const result = spawnSync("npm", ["install", "--silent"], {
356
+ cwd: playgroundDir,
357
+ stdio: "inherit",
358
+ shell: process.platform === "win32",
359
+ });
360
+ if (result.status !== 0) {
361
+ throw new Error("npm install failed in .ptb/playground/. Check your network connection.");
362
+ }
363
+ return { installed: true };
364
+ }
365
+ return { installed: false };
366
+ }
367
+ //# sourceMappingURL=scaffold-playground.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold-playground.js","sourceRoot":"","sources":["../../src/playground/scaffold-playground.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-component-prompt.d.ts","sourceRoot":"","sources":["../../src/prompt/generate-component-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAA8K,MAAM,4BAA4B,CAAC;AAokBvQ;;GAEG;AACH,wBAAgB,uBAAuB,CACnC,OAAO,EAAE,gBAAgB,EACzB,SAAS,GAAE,MAAM,GAAG,IAAW,GAChC,sBAAsB,CAqlCxB"}
1
+ {"version":3,"file":"generate-component-prompt.d.ts","sourceRoot":"","sources":["../../src/prompt/generate-component-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAA8K,MAAM,4BAA4B,CAAC;AAwlBvQ;;GAEG;AACH,wBAAgB,uBAAuB,CACnC,OAAO,EAAE,gBAAgB,EACzB,SAAS,GAAE,MAAM,GAAG,IAAW,GAChC,sBAAsB,CAqlCxB"}
@@ -455,10 +455,25 @@ function buildPropsInterfaceSkeleton(context, codeName) {
455
455
  if (textSlots.length > 0) {
456
456
  lines.push(` // Text content props`);
457
457
  const seen = new Set();
458
- for (const [, def] of textSlots) {
458
+ const findPropNode = (node, name) => {
459
+ if (!node)
460
+ return null;
461
+ if (node.name === name)
462
+ return node;
463
+ for (const c of (node.children ?? [])) {
464
+ const f = findPropNode(c, name);
465
+ if (f)
466
+ return f;
467
+ }
468
+ return null;
469
+ };
470
+ for (const [slotName, def] of textSlots) {
459
471
  if (def.prop && !seen.has(def.prop)) {
460
472
  seen.add(def.prop);
461
- lines.push(` ${def.prop}?: string;`);
473
+ const treeNode = findPropNode(context.renderTree, slotName);
474
+ const baseVal = treeNode?.contentContract?.baseValue;
475
+ const defaultComment = baseVal ? ` // default: "${baseVal}"` : "";
476
+ lines.push(` ${def.prop}?: string;${defaultComment}`);
462
477
  }
463
478
  }
464
479
  }
@@ -504,9 +519,24 @@ function buildJsxScaffold(context, codeName, requiredImports) {
504
519
  const def = sc.controller.defaultValue !== undefined ? ` = ${JSON.stringify(sc.controller.defaultValue)}` : "";
505
520
  addProp(`${p}${def}`);
506
521
  }
507
- for (const [, def] of ownSlots) {
522
+ // Wire text props with baseValue defaults so the scaffold emits e.g. `username = "Alexandra Davis"`
523
+ const findNodeByName = (node, name) => {
524
+ if (!node)
525
+ return null;
526
+ if (node.name === name)
527
+ return node;
528
+ for (const c of (node.children ?? [])) {
529
+ const f = findNodeByName(c, name);
530
+ if (f)
531
+ return f;
532
+ }
533
+ return null;
534
+ };
535
+ for (const [slotName, def] of ownSlots) {
508
536
  if (def.kind === "text" && def.prop && !seenProps.has(def.prop)) {
509
- addProp(def.prop);
537
+ const treeNode = findNodeByName(context.renderTree, slotName);
538
+ const baseVal = treeNode?.contentContract?.baseValue;
539
+ addProp(baseVal ? `${def.prop} = "${baseVal}"` : def.prop);
510
540
  }
511
541
  }
512
542
  propParts.push("className");
@@ -549,34 +579,34 @@ function buildJsxScaffold(context, codeName, requiredImports) {
549
579
  for (const attr of dataLines)
550
580
  lines.push(attr);
551
581
  lines.push(` >`);
552
- // Slot bodies prefer renderPattern from slotContract (most accurate)
553
- if (ownSlots.length > 0) {
554
- for (const [slotName, def] of ownSlots) {
555
- const pattern = def.renderPattern;
556
- if (!pattern)
557
- continue;
558
- lines.push(` {/* ${slotName} */}`);
559
- lines.push(` ${pattern}`);
560
- }
561
- }
562
- else {
563
- // Fall back: top-level render tree children (capped at 6)
582
+ // Scaffold body: always walk the render tree top-level children.
583
+ // Use slotContract.renderPattern when available (pre-compiled JSX snippet).
584
+ // Fall back to role-based inference for text, instances, and structural nodes.
585
+ // Slots promoted from contentSourceMap have kind/prop but NO renderPattern — the
586
+ // old if/else guard skipped them all, producing a hollow scaffold. Unified walk fixes that.
587
+ {
564
588
  const toCls = (n) => { const r = n.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-_]/g, ""); return /^\d/.test(r) ? `n${r}` : r; };
589
+ const slotByName = new Map(ownSlots.map(([name, def]) => [name, def]));
565
590
  const topChildren = context.renderTree?.children ?? [];
566
- for (const child of topChildren.slice(0, 6)) {
591
+ for (const child of topChildren.slice(0, 8)) {
592
+ const slotDef = slotByName.get(child.name ?? "");
593
+ if (slotDef?.renderPattern) {
594
+ lines.push(` {/* ${child.name} */}`);
595
+ lines.push(` ${slotDef.renderPattern}`);
596
+ continue;
597
+ }
567
598
  const isText = child.role === "text" || child.type === "TEXT";
568
599
  const isInstance = !!child.componentId;
569
600
  const cls = toCls(child.name ?? "node");
570
601
  if (isText) {
571
602
  const prop = child.contentContract?.propName;
572
- // Only reference prop as a variable if it was actually declared in propParts
573
603
  const propIsKnown = prop && seenProps.has(prop);
574
604
  const content = propIsKnown ? `{${prop}}` : (child.textContent ? `"${child.textContent}"` : `{/* ${child.name} */}`);
575
605
  lines.push(` <span className={styles['${cls}']}>${content}</span>`);
576
606
  }
577
607
  else if (isInstance) {
578
- const name = child.name.split(/[\s\-_]+/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
579
- lines.push(` <${name} />`);
608
+ const cName = child.name.split(/[\s\-_]+/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
609
+ lines.push(` <${cName} />`);
580
610
  }
581
611
  else {
582
612
  lines.push(` <div className={styles['${cls}']}>`);
@@ -584,8 +614,8 @@ function buildJsxScaffold(context, codeName, requiredImports) {
584
614
  lines.push(` </div>`);
585
615
  }
586
616
  }
587
- if (topChildren.length > 6)
588
- lines.push(` {/* … ${topChildren.length - 6} more children — see Render Structure above */}`);
617
+ if (topChildren.length > 8)
618
+ lines.push(` {/* … ${topChildren.length - 8} more children — see Render Structure */}`);
589
619
  if (topChildren.length === 0)
590
620
  lines.push(` {/* see Slot Render Guide above */}`);
591
621
  }
@@ -694,7 +724,7 @@ export function generateComponentPrompt(context, framework = null) {
694
724
  continue;
695
725
  seen.add(fingerprint);
696
726
  const jsx = allParts.length > 0 ? `<${compName} ${allParts.join(" ")} />` : `<${compName} />`;
697
- instructions.push(`When rendering ${dep.name} in this component, use exactly: \`${jsx}\``);
727
+ instructions.push(`When rendering ${dep.name} in this component, prefer this pattern: \`${jsx}\` — adjust variant/state props if the design context differs.`);
698
728
  }
699
729
  }
700
730
  instructions.push(`- CRITICAL: Use the 'dependencyInstances' recipes to configure each nested instance.`);
@@ -1231,7 +1261,7 @@ export function generateComponentPrompt(context, framework = null) {
1231
1261
  }
1232
1262
  if (inst.recipe.invocationContract) {
1233
1263
  const ic = inst.recipe.invocationContract;
1234
- lines.push(`- **Implementation Invocation Contract** (FOLLOW LITERALLY):`);
1264
+ lines.push(`- **Implementation Invocation Contract** (use as default starting point — verify token/color values against context):`);
1235
1265
  lines.push(` - Component: \`<${ic.componentName} />\``);
1236
1266
  lines.push(` - Children Policy: \`${ic.childrenPolicy}\``);
1237
1267
  if (safeArray(ic.omitProps).length > 0)
@@ -1330,7 +1360,7 @@ export function generateComponentPrompt(context, framework = null) {
1330
1360
  const jsxSnippet = jsxParts.length > 0
1331
1361
  ? `<${compName} ${jsxParts.join(" ")} />`
1332
1362
  : `<${compName} />`;
1333
- lines.push(`- **Concrete JSX** (copy exactly): \`${jsxSnippet}\``);
1363
+ lines.push(`- **Concrete JSX** (suggested starting point): \`${jsxSnippet}\``);
1334
1364
  }
1335
1365
  if (safeArray(inst.recipe.visibleSlots).length > 0)
1336
1366
  lines.push(`- **Visible Slots**: ${safeArray(inst.recipe.visibleSlots).join(", ")}`);