@elyracode/design-tools 0.4.7
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 +55 -0
- package/extensions/index.ts +480 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @elyracode/design-tools
|
|
2
|
+
|
|
3
|
+
Design tools for Elyra -- live browser preview, screenshot capture, and Tailwind design system checks.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
elyra install npm:@elyracode/design-tools
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Tools
|
|
12
|
+
|
|
13
|
+
| Tool | Description |
|
|
14
|
+
|------|-------------|
|
|
15
|
+
| `design_preview` | Render HTML/Tailwind in the browser with auto-reload. Iterate on design in real-time. |
|
|
16
|
+
| `screenshot_url` | Capture a screenshot of any URL (localhost or web). Returns image for visual QA. |
|
|
17
|
+
| `design_system_check` | Analyze files for Tailwind consistency: spacing, colors, responsive, dark mode, accessibility. |
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
- `/design` -- Interactive selector for all design tools
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Live Preview
|
|
26
|
+
```
|
|
27
|
+
> Preview a pricing table with 3 tiers using Tailwind
|
|
28
|
+
> Show me a hero section with gradient background
|
|
29
|
+
> Preview this component in dark mode
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The agent writes HTML with Tailwind classes, opens it in your browser, and the page auto-reloads every 2 seconds as you iterate.
|
|
33
|
+
|
|
34
|
+
### Visual QA
|
|
35
|
+
```
|
|
36
|
+
> Take a screenshot of localhost:8000 and check the layout
|
|
37
|
+
> Screenshot the login page on mobile viewport
|
|
38
|
+
> Capture the dashboard and evaluate the spacing
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The agent captures a screenshot and analyzes it with vision capabilities to spot layout issues, alignment problems, and responsive breakpoints.
|
|
42
|
+
|
|
43
|
+
### Design System Check
|
|
44
|
+
```
|
|
45
|
+
> Check resources/views/dashboard.blade.php for design consistency
|
|
46
|
+
> Analyze the spacing and colors in my pricing component
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Checks for: mixed spacing scales, missing responsive breakpoints, no hover/focus states, hardcoded colors, missing dark mode support, and conflicting classes.
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- **Preview**: No dependencies (uses Tailwind CDN)
|
|
54
|
+
- **Screenshots**: Install Puppeteer globally for automated captures: `npm install -g puppeteer`
|
|
55
|
+
- **Design Check**: No dependencies
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { ExtensionAPI } from "@elyracode/coding-agent";
|
|
6
|
+
import { Type } from "typebox";
|
|
7
|
+
|
|
8
|
+
const PREVIEW_DIR = join(homedir(), ".elyra", "design-preview");
|
|
9
|
+
|
|
10
|
+
export default function (elyra: ExtensionAPI): void {
|
|
11
|
+
// ── Tool 1: design_preview ──
|
|
12
|
+
// Render a component in the browser with live Tailwind CSS
|
|
13
|
+
elyra.registerTool({
|
|
14
|
+
name: "design_preview",
|
|
15
|
+
label: "Design Preview",
|
|
16
|
+
description:
|
|
17
|
+
"Render HTML/Tailwind CSS code in the browser for live preview. " +
|
|
18
|
+
"Writes an HTML file with Tailwind CDN and opens it in the default browser. " +
|
|
19
|
+
"Use this to preview UI components, layouts, and pages. " +
|
|
20
|
+
"The user can see the result visually while you iterate on the code.",
|
|
21
|
+
parameters: Type.Object({
|
|
22
|
+
html: Type.String({
|
|
23
|
+
description:
|
|
24
|
+
"The HTML content to preview (can include Tailwind classes, inline styles, etc.)",
|
|
25
|
+
}),
|
|
26
|
+
title: Type.Optional(
|
|
27
|
+
Type.String({
|
|
28
|
+
description: "Page title (default: 'Elyra Design Preview')",
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
31
|
+
dark: Type.Optional(
|
|
32
|
+
Type.Boolean({
|
|
33
|
+
description: "Enable dark mode background (default: false)",
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
width: Type.Optional(
|
|
37
|
+
Type.String({
|
|
38
|
+
description:
|
|
39
|
+
"Container max-width (e.g., '800px', '100%'). Default: '1200px'",
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
}),
|
|
43
|
+
execute: async (_toolCallId, params) => {
|
|
44
|
+
try {
|
|
45
|
+
if (!existsSync(PREVIEW_DIR)) {
|
|
46
|
+
mkdirSync(PREVIEW_DIR, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const title = params.title ?? "Elyra Design Preview";
|
|
50
|
+
const dark = params.dark ?? false;
|
|
51
|
+
const width = params.width ?? "1200px";
|
|
52
|
+
const bgClass = dark
|
|
53
|
+
? "bg-gray-900 text-white"
|
|
54
|
+
: "bg-white text-gray-900";
|
|
55
|
+
|
|
56
|
+
const fullHtml = `<!DOCTYPE html>
|
|
57
|
+
<html lang="en" ${dark ? 'class="dark"' : ""}>
|
|
58
|
+
<head>
|
|
59
|
+
<meta charset="UTF-8">
|
|
60
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
61
|
+
<title>${title}</title>
|
|
62
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
63
|
+
<script>
|
|
64
|
+
tailwind.config = {
|
|
65
|
+
darkMode: 'class',
|
|
66
|
+
theme: { extend: {} }
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
<style>
|
|
70
|
+
/* Smooth transitions for iterative design */
|
|
71
|
+
* { transition: all 0.15s ease; }
|
|
72
|
+
</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body class="${bgClass} min-h-screen p-8">
|
|
75
|
+
<div style="max-width: ${width}; margin: 0 auto;">
|
|
76
|
+
${params.html}
|
|
77
|
+
</div>
|
|
78
|
+
<script>
|
|
79
|
+
// Auto-reload every 2 seconds for live preview
|
|
80
|
+
setTimeout(() => location.reload(), 2000);
|
|
81
|
+
</script>
|
|
82
|
+
</body>
|
|
83
|
+
</html>`;
|
|
84
|
+
|
|
85
|
+
const previewPath = join(PREVIEW_DIR, "preview.html");
|
|
86
|
+
writeFileSync(previewPath, fullHtml, "utf-8");
|
|
87
|
+
|
|
88
|
+
// Open in default browser
|
|
89
|
+
const openCmd =
|
|
90
|
+
process.platform === "darwin"
|
|
91
|
+
? "open"
|
|
92
|
+
: process.platform === "win32"
|
|
93
|
+
? "start"
|
|
94
|
+
: "xdg-open";
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
execSync(`${openCmd} "${previewPath}"`, { timeout: 5000 });
|
|
98
|
+
} catch {
|
|
99
|
+
// Browser might already be open on this file
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: "text",
|
|
106
|
+
text: `Preview opened at ${previewPath}\nThe page auto-reloads every 2 seconds. Call design_preview again to update the content live.`,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
details: { path: previewPath },
|
|
110
|
+
};
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const msg =
|
|
113
|
+
error instanceof Error ? error.message : String(error);
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: `Preview failed: ${msg}`,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
details: {},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ── Tool 2: screenshot_url ──
|
|
128
|
+
// Take a screenshot of a URL for visual QA
|
|
129
|
+
elyra.registerTool({
|
|
130
|
+
name: "screenshot_url",
|
|
131
|
+
label: "Screenshot URL",
|
|
132
|
+
description:
|
|
133
|
+
"Take a screenshot of a web page at a given URL. " +
|
|
134
|
+
"Returns the screenshot as an image that can be analyzed visually. " +
|
|
135
|
+
"Use this for visual QA: after generating UI code, take a screenshot " +
|
|
136
|
+
"and evaluate if the design looks correct. " +
|
|
137
|
+
"Works with localhost URLs for testing local development servers. " +
|
|
138
|
+
"Requires the 'screenshot' CLI tool (macOS built-in) or Puppeteer.",
|
|
139
|
+
parameters: Type.Object({
|
|
140
|
+
url: Type.String({
|
|
141
|
+
description:
|
|
142
|
+
"URL to screenshot (e.g., http://localhost:8000, or file path from design_preview)",
|
|
143
|
+
}),
|
|
144
|
+
width: Type.Optional(
|
|
145
|
+
Type.Number({
|
|
146
|
+
description: "Viewport width in pixels (default: 1280)",
|
|
147
|
+
}),
|
|
148
|
+
),
|
|
149
|
+
height: Type.Optional(
|
|
150
|
+
Type.Number({
|
|
151
|
+
description: "Viewport height in pixels (default: 800)",
|
|
152
|
+
}),
|
|
153
|
+
),
|
|
154
|
+
mobile: Type.Optional(
|
|
155
|
+
Type.Boolean({
|
|
156
|
+
description:
|
|
157
|
+
"Use mobile viewport (375x812) (default: false)",
|
|
158
|
+
}),
|
|
159
|
+
),
|
|
160
|
+
}),
|
|
161
|
+
execute: async (_toolCallId, params) => {
|
|
162
|
+
try {
|
|
163
|
+
if (!existsSync(PREVIEW_DIR)) {
|
|
164
|
+
mkdirSync(PREVIEW_DIR, { recursive: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const screenshotPath = join(
|
|
168
|
+
PREVIEW_DIR,
|
|
169
|
+
`screenshot-${Date.now()}.png`,
|
|
170
|
+
);
|
|
171
|
+
const width = params.mobile
|
|
172
|
+
? 375
|
|
173
|
+
: (params.width ?? 1280);
|
|
174
|
+
const height = params.mobile
|
|
175
|
+
? 812
|
|
176
|
+
: (params.height ?? 800);
|
|
177
|
+
|
|
178
|
+
// Try Puppeteer first (if installed globally or in project)
|
|
179
|
+
let captured = false;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// Write a small Node script that uses puppeteer
|
|
183
|
+
const scriptPath = join(PREVIEW_DIR, "capture.mjs");
|
|
184
|
+
writeFileSync(
|
|
185
|
+
scriptPath,
|
|
186
|
+
`
|
|
187
|
+
import puppeteer from 'puppeteer';
|
|
188
|
+
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
|
|
189
|
+
const page = await browser.newPage();
|
|
190
|
+
await page.setViewport({ width: ${width}, height: ${height} });
|
|
191
|
+
await page.goto('${params.url}', { waitUntil: 'networkidle2', timeout: 15000 });
|
|
192
|
+
await page.screenshot({ path: '${screenshotPath}', fullPage: false });
|
|
193
|
+
await browser.close();
|
|
194
|
+
`,
|
|
195
|
+
"utf-8",
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
execSync(`node "${scriptPath}"`, {
|
|
199
|
+
timeout: 30000,
|
|
200
|
+
stdio: "pipe",
|
|
201
|
+
});
|
|
202
|
+
captured = existsSync(screenshotPath);
|
|
203
|
+
} catch {
|
|
204
|
+
// Puppeteer not available
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Fallback: macOS screencapture (if the URL is a local file)
|
|
208
|
+
if (
|
|
209
|
+
!captured &&
|
|
210
|
+
process.platform === "darwin" &&
|
|
211
|
+
params.url.startsWith("file://")
|
|
212
|
+
) {
|
|
213
|
+
try {
|
|
214
|
+
execSync(
|
|
215
|
+
`screencapture -x -C "${screenshotPath}"`,
|
|
216
|
+
{ timeout: 10000 },
|
|
217
|
+
);
|
|
218
|
+
captured = existsSync(screenshotPath);
|
|
219
|
+
} catch {
|
|
220
|
+
// screencapture failed
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!captured) {
|
|
225
|
+
return {
|
|
226
|
+
content: [
|
|
227
|
+
{
|
|
228
|
+
type: "text",
|
|
229
|
+
text: `Could not capture screenshot. Install Puppeteer for automated screenshots:\n\nnpm install -g puppeteer\n\nOr take a manual screenshot and paste it into the chat.`,
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
details: {},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Read the screenshot and return as image content
|
|
237
|
+
const imageData = readFileSync(screenshotPath);
|
|
238
|
+
const base64 = imageData.toString("base64");
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
content: [
|
|
242
|
+
{
|
|
243
|
+
type: "image",
|
|
244
|
+
data: base64,
|
|
245
|
+
mimeType: "image/png",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: `Screenshot captured (${width}x${height}): ${screenshotPath}\nAnalyze the screenshot for visual issues: alignment, spacing, color contrast, responsive layout, text readability.`,
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
details: { path: screenshotPath, width, height },
|
|
253
|
+
};
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const msg =
|
|
256
|
+
error instanceof Error ? error.message : String(error);
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: `Screenshot failed: ${msg}`,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
details: {},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// ── Tool 3: design_system_check ──
|
|
271
|
+
// Analyze Tailwind classes for consistency
|
|
272
|
+
elyra.registerTool({
|
|
273
|
+
name: "design_system_check",
|
|
274
|
+
label: "Design System Check",
|
|
275
|
+
description:
|
|
276
|
+
"Analyze a file or HTML snippet for Tailwind CSS consistency issues. " +
|
|
277
|
+
"Checks for: mixed spacing scales, inconsistent colors, missing responsive prefixes, " +
|
|
278
|
+
"accessibility issues (contrast, focus states), and unused or conflicting classes. " +
|
|
279
|
+
"Use this to ensure UI code follows a consistent design system.",
|
|
280
|
+
parameters: Type.Object({
|
|
281
|
+
file_path: Type.Optional(
|
|
282
|
+
Type.String({
|
|
283
|
+
description:
|
|
284
|
+
"Path to a file to analyze (Vue, Blade, TSX, HTML)",
|
|
285
|
+
}),
|
|
286
|
+
),
|
|
287
|
+
html: Type.Optional(
|
|
288
|
+
Type.String({
|
|
289
|
+
description:
|
|
290
|
+
"HTML snippet to analyze (alternative to file_path)",
|
|
291
|
+
}),
|
|
292
|
+
),
|
|
293
|
+
}),
|
|
294
|
+
execute: async (_toolCallId, params) => {
|
|
295
|
+
try {
|
|
296
|
+
let content: string;
|
|
297
|
+
if (params.file_path) {
|
|
298
|
+
content = readFileSync(params.file_path, "utf-8");
|
|
299
|
+
} else if (params.html) {
|
|
300
|
+
content = params.html;
|
|
301
|
+
} else {
|
|
302
|
+
return {
|
|
303
|
+
content: [
|
|
304
|
+
{
|
|
305
|
+
type: "text",
|
|
306
|
+
text: "Provide either file_path or html to analyze.",
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
details: {},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const issues: string[] = [];
|
|
314
|
+
|
|
315
|
+
// Check for mixed spacing scales
|
|
316
|
+
const spacingValues = new Set<string>();
|
|
317
|
+
const spacingMatches =
|
|
318
|
+
content.match(
|
|
319
|
+
/(?:p|m|gap|space)-(?:x-|y-)?(\d+)/g,
|
|
320
|
+
) ?? [];
|
|
321
|
+
for (const match of spacingMatches) {
|
|
322
|
+
const num = match.match(/(\d+)$/)?.[1];
|
|
323
|
+
if (num) spacingValues.add(num);
|
|
324
|
+
}
|
|
325
|
+
if (spacingValues.size > 6) {
|
|
326
|
+
issues.push(
|
|
327
|
+
`[Spacing] ${spacingValues.size} different spacing values used (${[...spacingValues].sort((a, b) => Number(a) - Number(b)).join(", ")}). Consider consolidating to a consistent scale.`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check for missing responsive prefixes on layout classes
|
|
332
|
+
const gridCols =
|
|
333
|
+
content.match(/grid-cols-\d+/g) ?? [];
|
|
334
|
+
const responsiveGridCols =
|
|
335
|
+
content.match(
|
|
336
|
+
/(?:sm|md|lg|xl):grid-cols-\d+/g,
|
|
337
|
+
) ?? [];
|
|
338
|
+
if (
|
|
339
|
+
gridCols.length > 0 &&
|
|
340
|
+
responsiveGridCols.length === 0
|
|
341
|
+
) {
|
|
342
|
+
issues.push(
|
|
343
|
+
"[Responsive] Grid columns used without responsive breakpoints. Add sm:/md:/lg: prefixes for mobile support.",
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Check for missing focus/hover states on interactive elements
|
|
348
|
+
const hasButtons = /button|btn|click|submit/i.test(
|
|
349
|
+
content,
|
|
350
|
+
);
|
|
351
|
+
const hasFocusStates =
|
|
352
|
+
/focus:|focus-visible:/i.test(content);
|
|
353
|
+
const hasHoverStates = /hover:/i.test(content);
|
|
354
|
+
if (hasButtons && !hasFocusStates) {
|
|
355
|
+
issues.push(
|
|
356
|
+
"[Accessibility] Interactive elements found without focus states. Add focus:ring or focus-visible: classes.",
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (hasButtons && !hasHoverStates) {
|
|
360
|
+
issues.push(
|
|
361
|
+
"[UX] Interactive elements found without hover states. Add hover: classes for visual feedback.",
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Check for hardcoded colors vs theme colors
|
|
366
|
+
const hardcodedColors =
|
|
367
|
+
content.match(
|
|
368
|
+
/(?:bg|text|border)-\[#[0-9a-fA-F]+\]/g,
|
|
369
|
+
) ?? [];
|
|
370
|
+
if (hardcodedColors.length > 0) {
|
|
371
|
+
issues.push(
|
|
372
|
+
`[Colors] ${hardcodedColors.length} hardcoded color values found. Consider using Tailwind theme colors for consistency.`,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Check for conflicting classes
|
|
377
|
+
const hasHidden =
|
|
378
|
+
/\bhidden\b/.test(content) &&
|
|
379
|
+
/\bblock\b/.test(content);
|
|
380
|
+
if (hasHidden) {
|
|
381
|
+
issues.push(
|
|
382
|
+
"[Conflict] Both 'hidden' and 'block' classes found. These may conflict unless used with responsive prefixes.",
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check for missing dark mode support
|
|
387
|
+
const hasDarkClasses = /dark:/i.test(content);
|
|
388
|
+
const hasBgColor =
|
|
389
|
+
/bg-(?:white|gray|slate|zinc)/i.test(content);
|
|
390
|
+
if (hasBgColor && !hasDarkClasses) {
|
|
391
|
+
issues.push(
|
|
392
|
+
"[Dark Mode] Background colors used without dark: variants. Add dark: prefixes for dark mode support.",
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Check for text truncation without overflow handling
|
|
397
|
+
const hasTruncate =
|
|
398
|
+
/truncate|line-clamp/i.test(content);
|
|
399
|
+
const hasOverflow =
|
|
400
|
+
/overflow-hidden|overflow-auto/i.test(content);
|
|
401
|
+
if (hasTruncate && !hasOverflow) {
|
|
402
|
+
issues.push(
|
|
403
|
+
"[Layout] Text truncation used without overflow handling on parent container.",
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const report: string[] = [
|
|
408
|
+
`# Design System Check${params.file_path ? `: ${params.file_path}` : ""}`,
|
|
409
|
+
"",
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
if (issues.length === 0) {
|
|
413
|
+
report.push(
|
|
414
|
+
"No design consistency issues found.",
|
|
415
|
+
);
|
|
416
|
+
} else {
|
|
417
|
+
report.push(
|
|
418
|
+
`Found ${issues.length} issue${issues.length > 1 ? "s" : ""}:`,
|
|
419
|
+
"",
|
|
420
|
+
);
|
|
421
|
+
for (const issue of issues) {
|
|
422
|
+
report.push(`- ${issue}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
content: [
|
|
428
|
+
{ type: "text", text: report.join("\n") },
|
|
429
|
+
],
|
|
430
|
+
details: { issueCount: issues.length },
|
|
431
|
+
};
|
|
432
|
+
} catch (error) {
|
|
433
|
+
const msg =
|
|
434
|
+
error instanceof Error ? error.message : String(error);
|
|
435
|
+
return {
|
|
436
|
+
content: [
|
|
437
|
+
{
|
|
438
|
+
type: "text",
|
|
439
|
+
text: `Design check failed: ${msg}`,
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
details: {},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// ── Command: /design ──
|
|
449
|
+
elyra.registerCommand("design", {
|
|
450
|
+
description:
|
|
451
|
+
"Design tools: live preview, screenshots, design system check",
|
|
452
|
+
handler: async (_args: string, ctx) => {
|
|
453
|
+
const options = [
|
|
454
|
+
"Preview -- render HTML/Tailwind in the browser",
|
|
455
|
+
"Screenshot -- capture a web page for visual QA",
|
|
456
|
+
"Design Check -- analyze Tailwind classes for consistency",
|
|
457
|
+
];
|
|
458
|
+
|
|
459
|
+
const selected = await ctx.ui.select(
|
|
460
|
+
"Design Tools",
|
|
461
|
+
options,
|
|
462
|
+
);
|
|
463
|
+
if (!selected) return;
|
|
464
|
+
|
|
465
|
+
if (selected.startsWith("Preview")) {
|
|
466
|
+
elyra.sendUserMessage(
|
|
467
|
+
"I want to preview a UI component. Describe what you want to see or give me the HTML/Tailwind code.",
|
|
468
|
+
);
|
|
469
|
+
} else if (selected.startsWith("Screenshot")) {
|
|
470
|
+
elyra.sendUserMessage(
|
|
471
|
+
"I want to take a screenshot of a page for visual QA. What URL should I capture?",
|
|
472
|
+
);
|
|
473
|
+
} else if (selected.startsWith("Design Check")) {
|
|
474
|
+
elyra.sendUserMessage(
|
|
475
|
+
"I want to check a file for Tailwind design consistency. Which file should I analyze?",
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elyracode/design-tools",
|
|
3
|
+
"version": "0.4.7",
|
|
4
|
+
"description": "Elyra extension for UI design -- live browser preview, screenshot capture, and visual QA",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"elyra-package",
|
|
8
|
+
"design",
|
|
9
|
+
"preview",
|
|
10
|
+
"screenshot",
|
|
11
|
+
"visual-qa",
|
|
12
|
+
"tailwind",
|
|
13
|
+
"ui"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Knut W. Horne",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/kwhorne/elyra.git",
|
|
20
|
+
"directory": "packages/design-tools"
|
|
21
|
+
},
|
|
22
|
+
"elyra": {
|
|
23
|
+
"extensions": [
|
|
24
|
+
"./extensions/index.ts"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@elyracode/coding-agent": "*",
|
|
29
|
+
"typebox": "*"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"clean": "echo 'nothing to clean'",
|
|
33
|
+
"build": "echo 'nothing to build'",
|
|
34
|
+
"check": "echo 'nothing to check'"
|
|
35
|
+
}
|
|
36
|
+
}
|