@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.
- package/dist/context/compressed-context.d.ts +1 -1
- package/dist/context/compressed-context.d.ts.map +1 -1
- package/dist/context/compressed-context.js +14 -18
- package/dist/context/compressed-context.js.map +1 -1
- package/dist/context/export-context.d.ts +18 -0
- package/dist/context/export-context.d.ts.map +1 -1
- package/dist/context/export-context.js +15 -0
- package/dist/context/export-context.js.map +1 -1
- package/dist/context/get-component-context.d.ts.map +1 -1
- package/dist/context/get-component-context.js +30 -1
- package/dist/context/get-component-context.js.map +1 -1
- package/dist/docs/setup-storybook.d.ts +9 -1
- package/dist/docs/setup-storybook.d.ts.map +1 -1
- package/dist/docs/setup-storybook.js +53 -4
- package/dist/docs/setup-storybook.js.map +1 -1
- package/dist/icons/enrich-bitmap-assets.js +19 -5
- package/dist/icons/enrich-bitmap-assets.js.map +1 -1
- package/dist/icons/enrich-icon-svgs.d.ts.map +1 -1
- package/dist/icons/enrich-icon-svgs.js +4 -2
- package/dist/icons/enrich-icon-svgs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mappings/naming-strategy.js +5 -0
- package/dist/mappings/naming-strategy.js.map +1 -1
- package/dist/normalizer/normalize-components.d.ts.map +1 -1
- package/dist/normalizer/normalize-components.js +7 -1
- package/dist/normalizer/normalize-components.js.map +1 -1
- package/dist/normalizer/variant-diff.d.ts +16 -0
- package/dist/normalizer/variant-diff.d.ts.map +1 -1
- package/dist/normalizer/variant-diff.js +69 -7
- package/dist/normalizer/variant-diff.js.map +1 -1
- package/dist/playground/scaffold-playground.d.ts +4 -0
- package/dist/playground/scaffold-playground.d.ts.map +1 -0
- package/dist/playground/scaffold-playground.js +367 -0
- package/dist/playground/scaffold-playground.js.map +1 -0
- package/dist/prompt/generate-component-prompt.d.ts.map +1 -1
- package/dist/prompt/generate-component-prompt.js +55 -25
- package/dist/prompt/generate-component-prompt.js.map +1 -1
- 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 <name></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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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,
|
|
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
|
|
579
|
-
lines.push(` <${
|
|
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 >
|
|
588
|
-
lines.push(` {/* … ${topChildren.length -
|
|
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,
|
|
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** (
|
|
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** (
|
|
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(", ")}`);
|