figma-local 1.0.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/README.md +342 -0
- package/bin/fig-start +289 -0
- package/bin/setup-alias.sh +48 -0
- package/package.json +47 -0
- package/src/blocks/dashboard-01.js +379 -0
- package/src/blocks/index.js +27 -0
- package/src/daemon.js +664 -0
- package/src/figjam-client.js +313 -0
- package/src/figma-client.js +4198 -0
- package/src/figma-patch.js +185 -0
- package/src/index.js +8543 -0
- package/src/platform.js +206 -0
- package/src/prompt-templates.js +289 -0
- package/src/read.js +243 -0
- package/src/shadcn.js +237 -0
package/src/platform.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-specific helpers.
|
|
3
|
+
* Only defines functions for the current platform — no Windows code loaded on Mac, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync, spawn } from 'child_process';
|
|
7
|
+
import { existsSync, readdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
|
|
10
|
+
const PLATFORM = process.platform;
|
|
11
|
+
|
|
12
|
+
// --- Null device ---
|
|
13
|
+
export const nullDevice = PLATFORM === 'win32' ? 'NUL' : '/dev/null';
|
|
14
|
+
|
|
15
|
+
// --- Port cleanup ---
|
|
16
|
+
function killPortUnix(port) {
|
|
17
|
+
const portCheck = execSync(`lsof -ti:${port} 2>/dev/null || true`, { encoding: 'utf8', stdio: 'pipe' });
|
|
18
|
+
if (portCheck.trim()) {
|
|
19
|
+
try { execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null || true`, { stdio: 'pipe' }); } catch {}
|
|
20
|
+
try { execSync('sleep 0.3', { stdio: 'pipe' }); } catch {}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function killPortWindows(port) {
|
|
25
|
+
try {
|
|
26
|
+
const result = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf8', stdio: 'pipe' });
|
|
27
|
+
const lines = result.split('\n').filter(l => l.includes('LISTENING'));
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
const parts = line.trim().split(/\s+/);
|
|
30
|
+
const pid = parts[parts.length - 1];
|
|
31
|
+
if (pid && /^\d+$/.test(pid)) {
|
|
32
|
+
execSync(`taskkill /PID ${pid} /F 2>nul`, { stdio: 'pipe' });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
try { execSync('ping -n 1 127.0.0.1 >nul', { stdio: 'pipe' }); } catch {}
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const killPort = PLATFORM === 'win32' ? killPortWindows : killPortUnix;
|
|
40
|
+
|
|
41
|
+
// --- Get PID listening on port ---
|
|
42
|
+
function getPortPidUnix(port) {
|
|
43
|
+
return execSync(`lsof -ti:${port} 2>/dev/null || true`, { encoding: 'utf8', stdio: 'pipe' }).trim() || null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getPortPidWindows(port) {
|
|
47
|
+
const result = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf8', stdio: 'pipe' });
|
|
48
|
+
const line = result.split('\n').find(l => l.includes('LISTENING'));
|
|
49
|
+
if (line) {
|
|
50
|
+
const parts = line.trim().split(/\s+/);
|
|
51
|
+
return parts[parts.length - 1] || null;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const getPortPid = PLATFORM === 'win32' ? getPortPidWindows : getPortPidUnix;
|
|
57
|
+
|
|
58
|
+
// --- Sleep after daemon stop ---
|
|
59
|
+
export function sleepAfterStop() {
|
|
60
|
+
if (PLATFORM === 'win32') {
|
|
61
|
+
try { execSync('ping -n 2 127.0.0.1 >nul', { stdio: 'pipe' }); } catch {}
|
|
62
|
+
} else {
|
|
63
|
+
try { execSync('sleep 0.5', { stdio: 'pipe' }); } catch {}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- Start Figma ---
|
|
68
|
+
export function startFigmaApp(figmaPath, port) {
|
|
69
|
+
if (PLATFORM === 'darwin') {
|
|
70
|
+
execSync(`open -a Figma --args --remote-debugging-port=${port}`, { stdio: 'pipe' });
|
|
71
|
+
} else {
|
|
72
|
+
spawn(figmaPath, [`--remote-debugging-port=${port}`], { detached: true, stdio: 'ignore' }).unref();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- Kill Figma ---
|
|
77
|
+
export function killFigmaApp() {
|
|
78
|
+
try {
|
|
79
|
+
if (PLATFORM === 'darwin') {
|
|
80
|
+
execSync('pkill -x Figma 2>/dev/null || true', { stdio: 'pipe' });
|
|
81
|
+
} else if (PLATFORM === 'win32') {
|
|
82
|
+
execSync('taskkill /IM Figma.exe /F 2>nul', { stdio: 'pipe' });
|
|
83
|
+
} else {
|
|
84
|
+
execSync('pkill -x figma 2>/dev/null || true', { stdio: 'pipe' });
|
|
85
|
+
}
|
|
86
|
+
} catch {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Figma paths (asar, binary, command) ---
|
|
90
|
+
|
|
91
|
+
// Windows-only helpers (only defined on Windows)
|
|
92
|
+
let findWindowsFigmaPath, findWindowsFigmaExe;
|
|
93
|
+
|
|
94
|
+
if (PLATFORM === 'win32') {
|
|
95
|
+
findWindowsFigmaPath = function() {
|
|
96
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
97
|
+
if (!localAppData) return null;
|
|
98
|
+
|
|
99
|
+
const figmaBase = join(localAppData, 'Figma');
|
|
100
|
+
if (!existsSync(figmaBase)) return null;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const entries = readdirSync(figmaBase);
|
|
104
|
+
const appFolders = entries
|
|
105
|
+
.filter(e => e.startsWith('app-'))
|
|
106
|
+
.sort()
|
|
107
|
+
.reverse();
|
|
108
|
+
|
|
109
|
+
for (const folder of appFolders) {
|
|
110
|
+
const asarPath = join(figmaBase, folder, 'resources', 'app.asar');
|
|
111
|
+
if (existsSync(asarPath)) return asarPath;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const oldPath = join(figmaBase, 'resources', 'app.asar');
|
|
115
|
+
if (existsSync(oldPath)) return oldPath;
|
|
116
|
+
} catch {}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
findWindowsFigmaExe = function() {
|
|
122
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
123
|
+
if (!localAppData) return null;
|
|
124
|
+
|
|
125
|
+
const figmaBase = join(localAppData, 'Figma');
|
|
126
|
+
const mainExe = join(figmaBase, 'Figma.exe');
|
|
127
|
+
if (existsSync(mainExe)) return mainExe;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const entries = readdirSync(figmaBase);
|
|
131
|
+
const appFolders = entries
|
|
132
|
+
.filter(e => e.startsWith('app-'))
|
|
133
|
+
.sort()
|
|
134
|
+
.reverse();
|
|
135
|
+
|
|
136
|
+
for (const folder of appFolders) {
|
|
137
|
+
const exePath = join(figmaBase, folder, 'Figma.exe');
|
|
138
|
+
if (existsSync(exePath)) return exePath;
|
|
139
|
+
}
|
|
140
|
+
} catch {}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ASAR_PATHS = {
|
|
147
|
+
darwin: '/Applications/Figma.app/Contents/Resources/app.asar',
|
|
148
|
+
linux: '/opt/figma/resources/app.asar'
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export function getAsarPath() {
|
|
152
|
+
if (PLATFORM === 'win32') return findWindowsFigmaPath();
|
|
153
|
+
return ASAR_PATHS[PLATFORM] || null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getFigmaBinaryPath() {
|
|
157
|
+
switch (PLATFORM) {
|
|
158
|
+
case 'darwin':
|
|
159
|
+
return '/Applications/Figma.app/Contents/MacOS/Figma';
|
|
160
|
+
case 'win32':
|
|
161
|
+
return findWindowsFigmaExe() || `${process.env.LOCALAPPDATA}\\Figma\\Figma.exe`;
|
|
162
|
+
case 'linux':
|
|
163
|
+
return '/usr/bin/figma';
|
|
164
|
+
default:
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function getFigmaCommand(port = 9222) {
|
|
170
|
+
switch (PLATFORM) {
|
|
171
|
+
case 'darwin':
|
|
172
|
+
return `open -a Figma --args --remote-debugging-port=${port}`;
|
|
173
|
+
case 'win32': {
|
|
174
|
+
const exePath = findWindowsFigmaExe();
|
|
175
|
+
if (exePath) return `"${exePath}" --remote-debugging-port=${port}`;
|
|
176
|
+
return `"%LOCALAPPDATA%\\Figma\\Figma.exe" --remote-debugging-port=${port}`;
|
|
177
|
+
}
|
|
178
|
+
case 'linux':
|
|
179
|
+
return `figma --remote-debugging-port=${port}`;
|
|
180
|
+
default:
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Doctor helpers ---
|
|
186
|
+
export function getFigmaVersion() {
|
|
187
|
+
if (PLATFORM === 'darwin') {
|
|
188
|
+
return execSync('defaults read /Applications/Figma.app/Contents/Info.plist CFBundleShortVersionString 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
189
|
+
} else if (PLATFORM === 'win32') {
|
|
190
|
+
return execSync('powershell -command "(Get-Item \\"$env:LOCALAPPDATA\\Figma\\Figma.exe\\").VersionInfo.ProductVersion" 2>nul', { encoding: 'utf8' }).trim() || 'unknown';
|
|
191
|
+
}
|
|
192
|
+
return 'unknown';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function isFigmaRunning() {
|
|
196
|
+
if (PLATFORM === 'darwin' || PLATFORM === 'linux') {
|
|
197
|
+
const ps = execSync('pgrep -f Figma 2>/dev/null || true', { encoding: 'utf8' });
|
|
198
|
+
return ps.trim().length > 0;
|
|
199
|
+
} else if (PLATFORM === 'win32') {
|
|
200
|
+
const ps = execSync('tasklist /FI "IMAGENAME eq Figma.exe" 2>nul', { encoding: 'utf8' });
|
|
201
|
+
return ps.includes('Figma.exe');
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const platformName = { darwin: 'macOS', win32: 'Windows', linux: 'Linux' }[PLATFORM] || PLATFORM;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prompt-templates.js — Per-tool structured prompt generators
|
|
3
|
+
*
|
|
4
|
+
* Each tool has different expectations: vocabulary, detail level,
|
|
5
|
+
* session context format, and token budgets.
|
|
6
|
+
* We generate lean, tool-calibrated prompts instead of generic dumps.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a ready-to-paste prompt for the given target tool.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} target - 'figma-make' | 'lovable' | 'pencil' | 'paper' | 'stitch'
|
|
13
|
+
* @param {object} design - { frameName, page, size, structure, tokens, interactions }
|
|
14
|
+
* @param {object} options - { platform, stack, goal, guardrails }
|
|
15
|
+
*/
|
|
16
|
+
export function generatePrompt(target, design, options = {}) {
|
|
17
|
+
const { frameName, page, size, structure, tokens, interactions } = design;
|
|
18
|
+
const { platform = 'desktop', stack = 'React + Tailwind', goal = '', guardrails = '' } = options;
|
|
19
|
+
|
|
20
|
+
// Build shared token block (used by all tools)
|
|
21
|
+
const tokenBlock = buildTokenBlock(tokens);
|
|
22
|
+
|
|
23
|
+
switch (target) {
|
|
24
|
+
case 'figma-make': return figmaMakePrompt(design, options, tokenBlock);
|
|
25
|
+
case 'lovable': return lovablePrompt(design, options, tokenBlock);
|
|
26
|
+
case 'pencil': return pencilPrompt(design, options, tokenBlock);
|
|
27
|
+
case 'paper': return paperPrompt(design, options, tokenBlock);
|
|
28
|
+
case 'stitch': return stitchPrompt(design, options, tokenBlock);
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Unknown target: ${target}. Use: figma-make, lovable, pencil, paper, stitch`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildTokenBlock(tokens) {
|
|
35
|
+
if (!tokens || Object.keys(tokens).length === 0) return '';
|
|
36
|
+
const lines = Object.entries(tokens)
|
|
37
|
+
.slice(0, 20) // cap at 20 tokens max
|
|
38
|
+
.map(([k, v]) => ` ${k}: ${v}`);
|
|
39
|
+
return `Design tokens:\n${lines.join('\n')}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─────────────────────────────────────────────────────────────
|
|
43
|
+
// Figma Make — Interactive HTML prototype
|
|
44
|
+
// Key rules: use MCP text context block (NOT frame attachment),
|
|
45
|
+
// one screen at a time, action words: change / set / on click
|
|
46
|
+
// ─────────────────────────────────────────────────────────────
|
|
47
|
+
function figmaMakePrompt(design, options, tokenBlock) {
|
|
48
|
+
const { frameName, size, structure, interactions = [] } = design;
|
|
49
|
+
const { platform = 'desktop', goal = '', guardrails = '' } = options;
|
|
50
|
+
|
|
51
|
+
const sessionCtx = [
|
|
52
|
+
`Screen: ${frameName}`,
|
|
53
|
+
`Size: ${size}`,
|
|
54
|
+
`Platform: ${platform}`,
|
|
55
|
+
tokenBlock,
|
|
56
|
+
].filter(Boolean).join('\n');
|
|
57
|
+
|
|
58
|
+
const interactionList = interactions.length > 0
|
|
59
|
+
? interactions.map(i => `- ${i}`).join('\n')
|
|
60
|
+
: '- (No interactions specified)';
|
|
61
|
+
|
|
62
|
+
const structureBlock = structure ? `\nStructure:\n${structure}` : '';
|
|
63
|
+
|
|
64
|
+
const screenPrompt = [
|
|
65
|
+
`Build a ${platform} UI screen called "${frameName}".`,
|
|
66
|
+
goal ? `Goal: ${goal}` : '',
|
|
67
|
+
'',
|
|
68
|
+
structureBlock,
|
|
69
|
+
'',
|
|
70
|
+
'Interactions:',
|
|
71
|
+
interactionList,
|
|
72
|
+
guardrails ? `\nDo not: ${guardrails}` : '',
|
|
73
|
+
].filter(s => s !== undefined).join('\n').trim();
|
|
74
|
+
|
|
75
|
+
const full = `${sessionCtx}\n\n${screenPrompt}`;
|
|
76
|
+
const tokenEst = Math.round(full.length / 4);
|
|
77
|
+
|
|
78
|
+
return `## Figma Make Prompt — ${frameName}
|
|
79
|
+
|
|
80
|
+
### Session context (paste once at session start):
|
|
81
|
+
${sessionCtx}
|
|
82
|
+
|
|
83
|
+
### Screen prompt:
|
|
84
|
+
${screenPrompt}
|
|
85
|
+
|
|
86
|
+
### Follow-ups:
|
|
87
|
+
1. Adjust spacing and typography to match the design tokens exactly.
|
|
88
|
+
2. Add hover and active states to all interactive elements.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
💰 Token estimate: ~${tokenEst} tokens
|
|
92
|
+
⚡ Use this text block instead of attaching the Figma frame — saves 300–500 hidden tokens.`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─────────────────────────────────────────────────────────────
|
|
96
|
+
// Lovable — Full-stack React app
|
|
97
|
+
// Key rules: specify stack, real component names, action verbs,
|
|
98
|
+
// one screen per prompt, mention shadcn if using it
|
|
99
|
+
// ─────────────────────────────────────────────────────────────
|
|
100
|
+
function lovablePrompt(design, options, tokenBlock) {
|
|
101
|
+
const { frameName, size, structure, interactions = [] } = design;
|
|
102
|
+
const { platform = 'desktop', stack = 'React + shadcn/ui + Tailwind', goal = '', guardrails = '' } = options;
|
|
103
|
+
|
|
104
|
+
const sessionCtx = [
|
|
105
|
+
`App: ${frameName}`,
|
|
106
|
+
`Stack: ${stack}`,
|
|
107
|
+
`Platform: ${platform}`,
|
|
108
|
+
tokenBlock,
|
|
109
|
+
].filter(Boolean).join('\n');
|
|
110
|
+
|
|
111
|
+
const interactionList = interactions.length > 0
|
|
112
|
+
? interactions.map(i => `- ${i}`).join('\n')
|
|
113
|
+
: '- Static layout, no interactions specified';
|
|
114
|
+
|
|
115
|
+
const structureBlock = structure ? `\nComponent structure:\n${structure}` : '';
|
|
116
|
+
|
|
117
|
+
const screenPrompt = [
|
|
118
|
+
`Create a ${platform} React page for "${frameName}".`,
|
|
119
|
+
`Stack: ${stack}.`,
|
|
120
|
+
goal ? `User goal: ${goal}` : '',
|
|
121
|
+
structureBlock,
|
|
122
|
+
'',
|
|
123
|
+
'Interactions:',
|
|
124
|
+
interactionList,
|
|
125
|
+
guardrails ? `\nConstraints: ${guardrails}` : '',
|
|
126
|
+
'\nUse exact hex values from design tokens. Match spacing and layout precisely.',
|
|
127
|
+
].filter(s => s !== undefined).join('\n').trim();
|
|
128
|
+
|
|
129
|
+
const full = `${sessionCtx}\n\n${screenPrompt}`;
|
|
130
|
+
const tokenEst = Math.round(full.length / 4);
|
|
131
|
+
|
|
132
|
+
return `## Lovable Prompt — ${frameName}
|
|
133
|
+
|
|
134
|
+
### Session context (paste once at session start):
|
|
135
|
+
${sessionCtx}
|
|
136
|
+
|
|
137
|
+
### Screen prompt:
|
|
138
|
+
${screenPrompt}
|
|
139
|
+
|
|
140
|
+
### Follow-ups:
|
|
141
|
+
1. Wire up form validation and error states.
|
|
142
|
+
2. Make layout fully responsive for mobile.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
💰 Token estimate: ~${tokenEst} tokens
|
|
146
|
+
⚡ Lovable charges per message — keep follow-ups focused on one change each.`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─────────────────────────────────────────────────────────────
|
|
150
|
+
// Pencil.dev — Production React components
|
|
151
|
+
// Key rules: component-first vocabulary, prop/variant naming,
|
|
152
|
+
// storybook-compatible output
|
|
153
|
+
// ─────────────────────────────────────────────────────────────
|
|
154
|
+
function pencilPrompt(design, options, tokenBlock) {
|
|
155
|
+
const { frameName, size, structure, interactions = [] } = design;
|
|
156
|
+
const { stack = 'React + TypeScript + Tailwind', goal = '', guardrails = '' } = options;
|
|
157
|
+
|
|
158
|
+
const sessionCtx = [
|
|
159
|
+
`Component: ${frameName}`,
|
|
160
|
+
`Stack: ${stack}`,
|
|
161
|
+
tokenBlock,
|
|
162
|
+
].filter(Boolean).join('\n');
|
|
163
|
+
|
|
164
|
+
const structureBlock = structure ? `\nStructure:\n${structure}` : '';
|
|
165
|
+
|
|
166
|
+
const screenPrompt = [
|
|
167
|
+
`Generate a production-ready React component for "${frameName}".`,
|
|
168
|
+
`Stack: ${stack}.`,
|
|
169
|
+
goal ? `Purpose: ${goal}` : '',
|
|
170
|
+
structureBlock,
|
|
171
|
+
'',
|
|
172
|
+
'States and variants:',
|
|
173
|
+
...(interactions.length > 0 ? interactions.map(i => `- ${i}`) : ['- default']),
|
|
174
|
+
guardrails ? `\nDo not: ${guardrails}` : '',
|
|
175
|
+
'\nExport as named export. Include prop types. Match design token values exactly.',
|
|
176
|
+
].filter(s => s !== undefined).join('\n').trim();
|
|
177
|
+
|
|
178
|
+
const full = `${sessionCtx}\n\n${screenPrompt}`;
|
|
179
|
+
const tokenEst = Math.round(full.length / 4);
|
|
180
|
+
|
|
181
|
+
return `## Pencil.dev Prompt — ${frameName}
|
|
182
|
+
|
|
183
|
+
### Session context:
|
|
184
|
+
${sessionCtx}
|
|
185
|
+
|
|
186
|
+
### Component prompt:
|
|
187
|
+
${screenPrompt}
|
|
188
|
+
|
|
189
|
+
### Follow-ups:
|
|
190
|
+
1. Add Storybook story with all variant combinations.
|
|
191
|
+
2. Add unit tests for interaction states.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
💰 Token estimate: ~${tokenEst} tokens`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─────────────────────────────────────────────────────────────
|
|
198
|
+
// Paper.design — HTML/CSS canvas
|
|
199
|
+
// Key rules: structural, semantic HTML, CSS custom properties
|
|
200
|
+
// ─────────────────────────────────────────────────────────────
|
|
201
|
+
function paperPrompt(design, options, tokenBlock) {
|
|
202
|
+
const { frameName, size, structure, interactions = [] } = design;
|
|
203
|
+
const { goal = '', guardrails = '' } = options;
|
|
204
|
+
|
|
205
|
+
const sessionCtx = [
|
|
206
|
+
`Screen: ${frameName} (${size})`,
|
|
207
|
+
tokenBlock,
|
|
208
|
+
].filter(Boolean).join('\n');
|
|
209
|
+
|
|
210
|
+
const structureBlock = structure ? `\nLayout structure:\n${structure}` : '';
|
|
211
|
+
|
|
212
|
+
const screenPrompt = [
|
|
213
|
+
`Build an HTML/CSS layout for "${frameName}" (${size}).`,
|
|
214
|
+
goal ? `Purpose: ${goal}` : '',
|
|
215
|
+
structureBlock,
|
|
216
|
+
'',
|
|
217
|
+
'Use CSS custom properties for all colors and spacing from design tokens.',
|
|
218
|
+
'Semantic HTML5 elements only.',
|
|
219
|
+
guardrails ? `\nDo not: ${guardrails}` : '',
|
|
220
|
+
].filter(s => s !== undefined).join('\n').trim();
|
|
221
|
+
|
|
222
|
+
const full = `${sessionCtx}\n\n${screenPrompt}`;
|
|
223
|
+
const tokenEst = Math.round(full.length / 4);
|
|
224
|
+
|
|
225
|
+
return `## Paper.design Prompt — ${frameName}
|
|
226
|
+
|
|
227
|
+
### Session context:
|
|
228
|
+
${sessionCtx}
|
|
229
|
+
|
|
230
|
+
### Layout prompt:
|
|
231
|
+
${screenPrompt}
|
|
232
|
+
|
|
233
|
+
### Follow-ups:
|
|
234
|
+
1. Add responsive breakpoints at 768px and 1024px.
|
|
235
|
+
2. Export final CSS as a separate file.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
💰 Token estimate: ~${tokenEst} tokens`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─────────────────────────────────────────────────────────────
|
|
242
|
+
// Google Stitch — Gemini 2.5, responsive UI
|
|
243
|
+
// Key rules: explicit responsiveness, Gemini vocabulary,
|
|
244
|
+
// limited monthly generations so keep prompts tight
|
|
245
|
+
// ─────────────────────────────────────────────────────────────
|
|
246
|
+
function stitchPrompt(design, options, tokenBlock) {
|
|
247
|
+
const { frameName, size, structure, interactions = [] } = design;
|
|
248
|
+
const { platform = 'responsive', goal = '', guardrails = '' } = options;
|
|
249
|
+
|
|
250
|
+
const sessionCtx = [
|
|
251
|
+
`UI: ${frameName}`,
|
|
252
|
+
`Platform: ${platform}`,
|
|
253
|
+
tokenBlock,
|
|
254
|
+
].filter(Boolean).join('\n');
|
|
255
|
+
|
|
256
|
+
const structureBlock = structure ? `\nVisual structure:\n${structure}` : '';
|
|
257
|
+
|
|
258
|
+
const screenPrompt = [
|
|
259
|
+
`Design a ${platform} UI layout for "${frameName}".`,
|
|
260
|
+
goal ? `Goal: ${goal}` : '',
|
|
261
|
+
structureBlock,
|
|
262
|
+
'',
|
|
263
|
+
'Requirements:',
|
|
264
|
+
'- Responsive: mobile (375px), tablet (768px), desktop (1280px)',
|
|
265
|
+
'- Use exact color values from design tokens',
|
|
266
|
+
'- Match component hierarchy from structure above',
|
|
267
|
+
...(interactions.map(i => `- ${i}`)),
|
|
268
|
+
guardrails ? `\nAvoid: ${guardrails}` : '',
|
|
269
|
+
].filter(s => s !== undefined).join('\n').trim();
|
|
270
|
+
|
|
271
|
+
const full = `${sessionCtx}\n\n${screenPrompt}`;
|
|
272
|
+
const tokenEst = Math.round(full.length / 4);
|
|
273
|
+
|
|
274
|
+
return `## Google Stitch Prompt — ${frameName}
|
|
275
|
+
|
|
276
|
+
### Session context:
|
|
277
|
+
${sessionCtx}
|
|
278
|
+
|
|
279
|
+
### UI prompt:
|
|
280
|
+
${screenPrompt}
|
|
281
|
+
|
|
282
|
+
### Follow-ups:
|
|
283
|
+
1. Adjust grid columns for tablet breakpoint.
|
|
284
|
+
2. Refine color contrast for accessibility.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
💰 Token estimate: ~${tokenEst} tokens
|
|
288
|
+
⚡ Stitch has limited monthly generations — scope each prompt to one screen.`;
|
|
289
|
+
}
|