design-clone 2.1.0 → 3.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 +13 -34
- package/SKILL.md +69 -45
- package/bin/cli.js +22 -4
- package/bin/commands/clone-site.js +31 -171
- package/bin/commands/help.js +19 -6
- package/bin/commands/init.js +9 -86
- package/bin/commands/uninstall.js +105 -0
- package/bin/commands/update.js +70 -0
- package/bin/commands/verify.js +7 -14
- package/bin/utils/paths.js +28 -0
- package/bin/utils/validate.js +2 -22
- package/bin/utils/version.js +23 -0
- package/docs/code-standards.md +789 -0
- package/docs/codebase-summary.md +533 -286
- package/docs/index.md +74 -0
- package/docs/project-overview-pdr.md +797 -0
- package/docs/system-architecture.md +718 -0
- package/package.json +14 -17
- package/src/ai/prompts/design-tokens/basic.md +80 -0
- package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
- package/src/ai/prompts/design-tokens/section.md +48 -0
- package/src/ai/prompts/design-tokens/with-css.md +87 -0
- package/src/ai/prompts/structure-analysis/basic.md +55 -0
- package/src/ai/prompts/structure-analysis/with-context.md +59 -0
- package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
- package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
- package/src/ai/prompts/ux-audit/aggregation.md +42 -0
- package/src/ai/prompts/ux-audit/desktop.md +92 -0
- package/src/ai/prompts/ux-audit/mobile.md +93 -0
- package/src/ai/prompts/ux-audit/tablet.md +92 -0
- package/src/core/animation/animation-extractor-ast.js +183 -0
- package/src/core/animation/animation-extractor-output.js +152 -0
- package/src/core/animation/animation-extractor.js +178 -0
- package/src/core/animation/state-capture-detection.js +200 -0
- package/src/core/animation/state-capture.js +193 -0
- package/src/core/capture/browser-context-pool.js +96 -0
- package/src/core/capture/multi-page-screenshot-page.js +110 -0
- package/src/core/capture/multi-page-screenshot.js +208 -0
- package/src/core/capture/screenshot-extraction.js +186 -0
- package/src/core/capture/screenshot-helpers.js +175 -0
- package/src/core/capture/screenshot-orchestrator.js +174 -0
- package/src/core/capture/screenshot-viewport.js +93 -0
- package/src/core/capture/screenshot.js +192 -0
- package/src/core/content/content-counter-dom.js +191 -0
- package/src/core/content/content-counter.js +76 -0
- package/src/core/css/breakpoint-detector.js +66 -0
- package/src/core/css/chromium-defaults.json +23 -0
- package/src/core/css/computed-style-extractor.js +102 -0
- package/src/core/css/css-chunker.js +103 -0
- package/src/core/css/filter-css-dead-code.js +120 -0
- package/src/core/css/filter-css-html-analyzer.js +110 -0
- package/src/core/css/filter-css-selector-matcher.js +172 -0
- package/src/core/css/filter-css.js +206 -0
- package/src/core/css/merge-css-atrule-processor.js +158 -0
- package/src/core/css/merge-css-file-io.js +68 -0
- package/src/core/css/merge-css.js +148 -0
- package/src/core/detection/framework-detector-routing.js +68 -0
- package/src/core/detection/framework-detector-signals.js +65 -0
- package/src/core/detection/framework-detector.js +198 -0
- package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
- package/src/core/dimension/dimension-extractor.js +317 -0
- package/src/core/dimension/dimension-output-ai-summary.js +111 -0
- package/src/core/dimension/dimension-output.js +173 -0
- package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
- package/src/core/dimension/dom-tree-analyzer.js +191 -0
- package/src/core/discovery/app-state-snapshot-capture.js +195 -0
- package/src/core/discovery/app-state-snapshot-utils.js +178 -0
- package/src/core/discovery/app-state-snapshot.js +131 -0
- package/src/core/discovery/discover-pages-routes.js +84 -0
- package/src/core/discovery/discover-pages-utils.js +177 -0
- package/src/core/discovery/discover-pages.js +191 -0
- package/src/core/html/html-extractor-inline-styler.js +70 -0
- package/src/core/html/html-extractor.js +147 -0
- package/src/core/html/semantic-enhancer-mappings.js +200 -0
- package/src/core/html/semantic-enhancer-page.js +148 -0
- package/src/core/html/semantic-enhancer.js +135 -0
- package/src/core/links/rewrite-links-css-rewriter.js +53 -0
- package/src/core/links/rewrite-links.js +173 -0
- package/src/core/media/asset-validator.js +118 -0
- package/src/core/media/extract-assets-downloader.js +187 -0
- package/src/core/media/extract-assets-page-scraper.js +115 -0
- package/src/core/media/extract-assets.js +159 -0
- package/src/core/media/video-capture-convert.js +200 -0
- package/src/core/media/video-capture.js +201 -0
- package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
- package/src/core/section/section-cropper-helpers.js +43 -0
- package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
- package/src/core/section/section-detector-strategies.js +139 -0
- package/src/core/section/section-detector-utils.js +100 -0
- package/src/core/section/section-detector.js +88 -0
- package/src/core/tests/test-section-cropper.js +2 -2
- package/src/core/tests/test-section-detector.js +2 -2
- package/src/post-process/enhance-assets.js +29 -4
- package/src/post-process/fetch-images-unsplash-client.js +123 -0
- package/src/post-process/fetch-images.js +60 -263
- package/src/post-process/inject-gosnap.js +88 -0
- package/src/post-process/inject-icons-svg-replacer.js +76 -0
- package/src/post-process/inject-icons.js +47 -200
- package/src/route-discoverers/base-discoverer-utils.js +137 -0
- package/src/route-discoverers/base-discoverer.js +29 -118
- package/src/route-discoverers/index.js +1 -1
- package/src/shared/config.js +38 -0
- package/src/shared/error-codes.js +31 -0
- package/src/shared/viewports.js +46 -0
- package/src/utils/browser.js +0 -7
- package/src/utils/helpers.js +4 -0
- package/src/utils/log.js +12 -0
- package/src/utils/playwright-loader.js +76 -0
- package/src/utils/playwright.js +3 -69
- package/src/utils/progress.js +32 -0
- package/src/verification/generate-audit-report-css-fixes.js +52 -0
- package/src/verification/generate-audit-report-sections.js +158 -0
- package/src/verification/generate-audit-report.js +5 -281
- package/src/verification/quality-scorer.js +92 -0
- package/src/verification/verify-footer-checks.js +103 -0
- package/src/verification/verify-footer-helpers.js +178 -0
- package/src/verification/verify-footer.js +23 -381
- package/src/verification/verify-header-checks.js +104 -0
- package/src/verification/verify-header-helpers.js +156 -0
- package/src/verification/verify-header.js +23 -365
- package/src/verification/verify-layout-report.js +101 -0
- package/src/verification/verify-layout.js +13 -259
- package/src/verification/verify-menu-checks.js +104 -0
- package/src/verification/verify-menu-helpers.js +112 -0
- package/src/verification/verify-menu.js +17 -285
- package/src/verification/verify-slider-checks.js +115 -0
- package/src/verification/verify-slider-constants.js +65 -0
- package/src/verification/verify-slider-helpers.js +164 -0
- package/src/verification/verify-slider.js +23 -414
- package/.env.example +0 -14
- package/docs/basic-clone.md +0 -63
- package/docs/cli-reference.md +0 -316
- package/docs/design-clone-architecture.md +0 -492
- package/docs/pixel-perfect.md +0 -117
- package/docs/project-roadmap.md +0 -382
- package/docs/troubleshooting.md +0 -170
- package/requirements.txt +0 -5
- package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
- package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
- package/src/ai/analyze-structure.py +0 -375
- package/src/ai/extract-design-tokens.py +0 -782
- package/src/ai/prompts/__init__.py +0 -2
- package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
- package/src/ai/prompts/design_tokens.py +0 -316
- package/src/ai/prompts/structure_analysis.py +0 -592
- package/src/ai/prompts/ux_audit.py +0 -198
- package/src/ai/ux-audit.js +0 -596
- package/src/core/animation-extractor.js +0 -526
- package/src/core/app-state-snapshot.js +0 -511
- package/src/core/content-counter.js +0 -342
- package/src/core/design-tokens.js +0 -103
- package/src/core/dimension-extractor.js +0 -438
- package/src/core/dimension-output.js +0 -305
- package/src/core/discover-pages.js +0 -542
- package/src/core/dom-tree-analyzer.js +0 -298
- package/src/core/extract-assets.js +0 -468
- package/src/core/filter-css.js +0 -499
- package/src/core/framework-detector.js +0 -538
- package/src/core/html-extractor.js +0 -212
- package/src/core/merge-css.js +0 -407
- package/src/core/multi-page-screenshot.js +0 -380
- package/src/core/rewrite-links.js +0 -226
- package/src/core/screenshot.js +0 -701
- package/src/core/section-detector.js +0 -386
- package/src/core/semantic-enhancer.js +0 -492
- package/src/core/state-capture.js +0 -598
- package/src/core/video-capture.js +0 -546
- package/src/utils/__init__.py +0 -16
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/env.py +0 -134
- /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
- /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
- /package/src/core/{page-readiness.js → page-prep/page-readiness.js} +0 -0
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
# Design Clone Code Standards & Guidelines
|
|
2
|
+
|
|
3
|
+
**Version:** 2.1.0
|
|
4
|
+
**Last Updated:** February 23, 2026
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
This document establishes code quality standards, architectural patterns, and development guidelines for the Design Clone project. All contributions must follow these standards to maintain consistency, readability, and long-term maintainability.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
1. [JavaScript/Node.js Standards](#javascriptnodejs-standards)
|
|
15
|
+
2. [Python Standards](#python-standards)
|
|
16
|
+
3. [Project Structure](#project-structure)
|
|
17
|
+
4. [Error Handling](#error-handling)
|
|
18
|
+
5. [Testing Standards](#testing-standards)
|
|
19
|
+
6. [Documentation Standards](#documentation-standards)
|
|
20
|
+
7. [Performance Guidelines](#performance-guidelines)
|
|
21
|
+
8. [Security Standards](#security-standards)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## JavaScript/Node.js Standards
|
|
26
|
+
|
|
27
|
+
### Code Style
|
|
28
|
+
|
|
29
|
+
**File Format:**
|
|
30
|
+
```javascript
|
|
31
|
+
/**
|
|
32
|
+
* Brief description of what this file does.
|
|
33
|
+
* @module moduleName
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
'use strict';
|
|
37
|
+
|
|
38
|
+
// Imports (group: external, relative)
|
|
39
|
+
const path = require('path');
|
|
40
|
+
const fs = require('fs').promises;
|
|
41
|
+
const { parseUrl } = require('../utils/url-parser');
|
|
42
|
+
|
|
43
|
+
// Constants
|
|
44
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
45
|
+
const SCREENSHOT_WIDTH = 1920;
|
|
46
|
+
|
|
47
|
+
// Types/Interfaces (JSDoc style)
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {Object} ScreenshotOptions
|
|
50
|
+
* @property {string} url - Target URL
|
|
51
|
+
* @property {string} output - Output directory
|
|
52
|
+
* @property {number} timeout - Operation timeout in ms
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
// Main exports
|
|
56
|
+
module.exports = {
|
|
57
|
+
captureScreenshots,
|
|
58
|
+
filterCSS,
|
|
59
|
+
validateOutput,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Implementation
|
|
63
|
+
async function captureScreenshots(url, options) {
|
|
64
|
+
// ...
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Naming Conventions:**
|
|
69
|
+
```javascript
|
|
70
|
+
// Constants: UPPER_SNAKE_CASE
|
|
71
|
+
const MAX_RETRIES = 3;
|
|
72
|
+
const DEFAULT_VIEWPORT_WIDTH = 1920;
|
|
73
|
+
|
|
74
|
+
// Functions: camelCase
|
|
75
|
+
function extractDesignTokens() {}
|
|
76
|
+
const validateUrl = () => {};
|
|
77
|
+
|
|
78
|
+
// Classes: PascalCase
|
|
79
|
+
class PlaywrightBrowser {
|
|
80
|
+
constructor() {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Private methods: _leadingUnderscore or #privateField
|
|
84
|
+
class BrowserPool {
|
|
85
|
+
#browsers = [];
|
|
86
|
+
_initializeBrowser() {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Variables: camelCase
|
|
90
|
+
let currentState = null;
|
|
91
|
+
const outputDirectory = './output';
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Formatting Rules:**
|
|
95
|
+
- Line length: 100 characters maximum
|
|
96
|
+
- Indentation: 2 spaces
|
|
97
|
+
- Semicolons: Required
|
|
98
|
+
- Quotes: Single quotes for strings
|
|
99
|
+
- Arrow functions: Use for callbacks
|
|
100
|
+
- Async/await: Prefer over .then() chains
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// Good
|
|
104
|
+
const results = await Promise.all(
|
|
105
|
+
screenshots.map(async (shot) => {
|
|
106
|
+
return await processScreenshot(shot);
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Bad
|
|
111
|
+
const results = await Promise.all(screenshots.map(ss => {
|
|
112
|
+
return processScreenshot(ss);
|
|
113
|
+
}));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Function Structure
|
|
117
|
+
|
|
118
|
+
**Pattern:**
|
|
119
|
+
```javascript
|
|
120
|
+
/**
|
|
121
|
+
* Brief one-line description.
|
|
122
|
+
*
|
|
123
|
+
* Detailed explanation if needed. Describe the algorithm or key behavior.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} inputPath - Description of param
|
|
126
|
+
* @param {Object} options - Configuration object
|
|
127
|
+
* @param {boolean} options.verbose - Enable verbose logging
|
|
128
|
+
* @param {number} options.timeout - Timeout in milliseconds
|
|
129
|
+
* @returns {Promise<Object>} Returns processed data
|
|
130
|
+
* @throws {Error} When input is invalid
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* const result = await processFile('path/to/file.html', { verbose: true });
|
|
134
|
+
*/
|
|
135
|
+
async function processFile(inputPath, options = {}) {
|
|
136
|
+
// 1. Input validation
|
|
137
|
+
if (!inputPath || typeof inputPath !== 'string') {
|
|
138
|
+
throw new Error('inputPath must be non-empty string');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Setup/initialization
|
|
142
|
+
const config = {
|
|
143
|
+
timeout: 5000,
|
|
144
|
+
...options,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// 3. Main logic
|
|
148
|
+
try {
|
|
149
|
+
const data = await readFile(inputPath);
|
|
150
|
+
const processed = transform(data);
|
|
151
|
+
return processed;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
// 4. Error handling
|
|
154
|
+
if (error.code === 'ENOENT') {
|
|
155
|
+
throw new Error(`File not found: ${inputPath}`);
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Control Flow
|
|
163
|
+
|
|
164
|
+
**Conditionals:**
|
|
165
|
+
```javascript
|
|
166
|
+
// Good: Early return
|
|
167
|
+
if (!value) return null;
|
|
168
|
+
if (typeof x !== 'string') throw new Error('Must be string');
|
|
169
|
+
|
|
170
|
+
// Guard clauses
|
|
171
|
+
function validate(input) {
|
|
172
|
+
if (!input) return false;
|
|
173
|
+
if (!input.trim()) return false;
|
|
174
|
+
if (input.length > 1000) return false;
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Ternary for simple logic
|
|
179
|
+
const status = isActive ? 'running' : 'stopped';
|
|
180
|
+
|
|
181
|
+
// Avoid nested ternary
|
|
182
|
+
// Bad: status === 'pending' ? 'waiting' : status === 'done' ? 'complete' : 'error'
|
|
183
|
+
// Good: Use switch or if-else
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Loops & Iteration:**
|
|
187
|
+
```javascript
|
|
188
|
+
// Prefer forEach for side effects
|
|
189
|
+
items.forEach((item) => {
|
|
190
|
+
console.log(item);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Prefer map for transformations
|
|
194
|
+
const doubled = numbers.map((n) => n * 2);
|
|
195
|
+
|
|
196
|
+
// Prefer filter for selection
|
|
197
|
+
const active = users.filter((u) => u.isActive);
|
|
198
|
+
|
|
199
|
+
// Use for-of for breaking early
|
|
200
|
+
for (const item of items) {
|
|
201
|
+
if (shouldStop(item)) break;
|
|
202
|
+
processItem(item);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Avoid traditional for loops unless necessary
|
|
206
|
+
// Bad: for (let i = 0; i < items.length; i++)
|
|
207
|
+
// Good: for (const item of items)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Error Handling
|
|
211
|
+
|
|
212
|
+
**Pattern:**
|
|
213
|
+
```javascript
|
|
214
|
+
/**
|
|
215
|
+
* Executes operation with retry logic.
|
|
216
|
+
*
|
|
217
|
+
* @param {Function} operation - Async function to retry
|
|
218
|
+
* @param {number} maxRetries - Maximum retry attempts (default: 3)
|
|
219
|
+
* @returns {Promise<*>} Result of successful operation
|
|
220
|
+
*/
|
|
221
|
+
async function executeWithRetry(operation, maxRetries = 3) {
|
|
222
|
+
let lastError;
|
|
223
|
+
|
|
224
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
225
|
+
try {
|
|
226
|
+
return await operation();
|
|
227
|
+
} catch (error) {
|
|
228
|
+
lastError = error;
|
|
229
|
+
|
|
230
|
+
if (attempt < maxRetries) {
|
|
231
|
+
const delay = Math.pow(2, attempt - 1) * 1000; // Exponential backoff
|
|
232
|
+
console.warn(
|
|
233
|
+
`Attempt ${attempt} failed: ${error.message}. Retrying in ${delay}ms...`
|
|
234
|
+
);
|
|
235
|
+
await sleep(delay);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error(
|
|
241
|
+
`Operation failed after ${maxRetries} attempts: ${lastError.message}`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Custom Errors:**
|
|
247
|
+
```javascript
|
|
248
|
+
class ValidationError extends Error {
|
|
249
|
+
constructor(message, context = {}) {
|
|
250
|
+
super(message);
|
|
251
|
+
this.name = 'ValidationError';
|
|
252
|
+
this.context = context;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class TimeoutError extends Error {
|
|
257
|
+
constructor(operation, timeout) {
|
|
258
|
+
super(`${operation} exceeded timeout of ${timeout}ms`);
|
|
259
|
+
this.name = 'TimeoutError';
|
|
260
|
+
this.timeout = timeout;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Usage
|
|
265
|
+
if (!isValid(input)) {
|
|
266
|
+
throw new ValidationError('Invalid input format', { input, expected });
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Async/Promises
|
|
271
|
+
|
|
272
|
+
**Always use async/await:**
|
|
273
|
+
```javascript
|
|
274
|
+
// Good
|
|
275
|
+
async function fetchData() {
|
|
276
|
+
const response = await fetch(url);
|
|
277
|
+
const data = await response.json();
|
|
278
|
+
return data;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Avoid bare promises
|
|
282
|
+
// Bad: function fetchData() { return fetch(url).then(...) }
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Parallel operations:**
|
|
286
|
+
```javascript
|
|
287
|
+
// Good: Parallel with Promise.all
|
|
288
|
+
const [screenshots, css, html] = await Promise.all([
|
|
289
|
+
captureScreenshots(url),
|
|
290
|
+
extractCSS(url),
|
|
291
|
+
extractHTML(url),
|
|
292
|
+
]);
|
|
293
|
+
|
|
294
|
+
// Sequential when dependent
|
|
295
|
+
const tokens = await extractDesignTokens(url);
|
|
296
|
+
const css = await generateCSS(tokens);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Timeout handling:**
|
|
300
|
+
```javascript
|
|
301
|
+
async function withTimeout(promise, timeoutMs) {
|
|
302
|
+
return Promise.race([
|
|
303
|
+
promise,
|
|
304
|
+
new Promise((_, reject) =>
|
|
305
|
+
setTimeout(() => reject(new TimeoutError('Operation', timeoutMs)), timeoutMs)
|
|
306
|
+
),
|
|
307
|
+
]);
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Python Standards
|
|
314
|
+
|
|
315
|
+
### Code Style
|
|
316
|
+
|
|
317
|
+
**File Format:**
|
|
318
|
+
```python
|
|
319
|
+
#!/usr/bin/env python3
|
|
320
|
+
"""
|
|
321
|
+
Module docstring: Brief description of module purpose.
|
|
322
|
+
|
|
323
|
+
This module handles [description of main responsibility].
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
import sys
|
|
327
|
+
import json
|
|
328
|
+
from pathlib import Path
|
|
329
|
+
from typing import Dict, List, Optional
|
|
330
|
+
|
|
331
|
+
# Constants
|
|
332
|
+
DEFAULT_TIMEOUT = 5000
|
|
333
|
+
FIGMA_API_BASE = 'https://api.figma.com/v1'
|
|
334
|
+
|
|
335
|
+
# Helper functions
|
|
336
|
+
def validate_token(token: str) -> bool:
|
|
337
|
+
"""Check if token format is valid."""
|
|
338
|
+
return bool(token and len(token) > 10)
|
|
339
|
+
|
|
340
|
+
# Main classes
|
|
341
|
+
class FigmaClient:
|
|
342
|
+
"""Figma REST API client."""
|
|
343
|
+
|
|
344
|
+
def __init__(self, access_token: str):
|
|
345
|
+
"""Initialize with access token."""
|
|
346
|
+
if not validate_token(access_token):
|
|
347
|
+
raise ValueError('Invalid access token format')
|
|
348
|
+
self.token = access_token
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Naming Conventions:**
|
|
352
|
+
```python
|
|
353
|
+
# Constants: UPPER_SNAKE_CASE
|
|
354
|
+
MAX_RETRIES = 3
|
|
355
|
+
DEFAULT_TIMEOUT = 5000
|
|
356
|
+
|
|
357
|
+
# Functions & methods: snake_case
|
|
358
|
+
def extract_design_tokens(file_path):
|
|
359
|
+
pass
|
|
360
|
+
|
|
361
|
+
def validate_figma_url(url):
|
|
362
|
+
pass
|
|
363
|
+
|
|
364
|
+
# Classes: PascalCase
|
|
365
|
+
class FigmaClient:
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
class DesignTokenExtractor:
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
# Private: Leading underscore
|
|
372
|
+
def _internal_helper():
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
class MyClass:
|
|
376
|
+
def _private_method(self):
|
|
377
|
+
pass
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Type Hints (Required):**
|
|
381
|
+
```python
|
|
382
|
+
from typing import Dict, List, Optional, Tuple
|
|
383
|
+
|
|
384
|
+
def process_tokens(
|
|
385
|
+
data: Dict[str, any],
|
|
386
|
+
output_path: str,
|
|
387
|
+
verbose: bool = False
|
|
388
|
+
) -> Dict[str, List[str]]:
|
|
389
|
+
"""Process design tokens."""
|
|
390
|
+
return {'colors': [], 'fonts': []}
|
|
391
|
+
|
|
392
|
+
def extract_color(rgba: Dict[str, float]) -> str:
|
|
393
|
+
"""Convert RGBA dict to hex color."""
|
|
394
|
+
pass
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Formatting Rules:**
|
|
398
|
+
- Line length: 100 characters
|
|
399
|
+
- Indentation: 4 spaces
|
|
400
|
+
- Docstrings: Google style
|
|
401
|
+
- Type hints: All function signatures
|
|
402
|
+
- Blank lines: 2 between top-level definitions, 1 within class
|
|
403
|
+
|
|
404
|
+
### Function Structure
|
|
405
|
+
|
|
406
|
+
**Pattern (condensed):**
|
|
407
|
+
```python
|
|
408
|
+
def extract_design_tokens(figma_data: Dict, output_dir: str) -> Dict:
|
|
409
|
+
"""Extract colors, typography, spacing from Figma file."""
|
|
410
|
+
if not isinstance(figma_data, dict):
|
|
411
|
+
raise ValueError('figma_data must be dictionary')
|
|
412
|
+
|
|
413
|
+
tokens = {'colors': {}, 'typography': {}, 'spacing': {}}
|
|
414
|
+
tokens['colors'] = _extract_colors(figma_data)
|
|
415
|
+
return tokens
|
|
416
|
+
|
|
417
|
+
def _extract_colors(data: Dict) -> Dict[str, str]:
|
|
418
|
+
"""Extract color palette from design."""
|
|
419
|
+
colors = {}
|
|
420
|
+
# Implementation
|
|
421
|
+
return colors
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Error Handling
|
|
425
|
+
|
|
426
|
+
**Pattern:**
|
|
427
|
+
```python
|
|
428
|
+
class FigmaError(Exception):
|
|
429
|
+
"""Base exception for Figma operations."""
|
|
430
|
+
pass
|
|
431
|
+
|
|
432
|
+
class FigmaAuthError(FigmaError):
|
|
433
|
+
"""Raised when authentication fails."""
|
|
434
|
+
pass
|
|
435
|
+
|
|
436
|
+
class FigmaAPIError(FigmaError):
|
|
437
|
+
"""Raised when API request fails."""
|
|
438
|
+
def __init__(self, message: str, status_code: int = None):
|
|
439
|
+
super().__init__(message)
|
|
440
|
+
self.status_code = status_code
|
|
441
|
+
|
|
442
|
+
def authenticate(token: str) -> bool:
|
|
443
|
+
"""Authenticate with Figma API."""
|
|
444
|
+
try:
|
|
445
|
+
response = requests.get(
|
|
446
|
+
'https://api.figma.com/v1/me',
|
|
447
|
+
headers={'X-Figma-Token': token}
|
|
448
|
+
)
|
|
449
|
+
response.raise_for_status()
|
|
450
|
+
return True
|
|
451
|
+
except requests.exceptions.ConnectionError as e:
|
|
452
|
+
raise FigmaError(f'Network error: {e}')
|
|
453
|
+
except requests.exceptions.HTTPError as e:
|
|
454
|
+
if e.response.status_code == 401:
|
|
455
|
+
raise FigmaAuthError('Invalid token')
|
|
456
|
+
raise FigmaAPIError(str(e), e.response.status_code)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Project Structure
|
|
462
|
+
|
|
463
|
+
### Directory Organization
|
|
464
|
+
|
|
465
|
+
```
|
|
466
|
+
design-clone/
|
|
467
|
+
├── bin/
|
|
468
|
+
│ ├── cli.js # Main CLI entry
|
|
469
|
+
│ ├── commands/
|
|
470
|
+
│ │ ├── clone-site.js # Multi-page command
|
|
471
|
+
│ │ ├── init.js # Setup command
|
|
472
|
+
│ │ └── verify.js # Verification command
|
|
473
|
+
│ └── utils/
|
|
474
|
+
│ ├── copy.js # Utility functions
|
|
475
|
+
│ └── validate.js
|
|
476
|
+
│
|
|
477
|
+
├── src/
|
|
478
|
+
│ ├── core/ # Core extraction engines (12 semantic subdirectories)
|
|
479
|
+
│ │ ├── capture/ # Screenshot pipeline (7 modules)
|
|
480
|
+
│ │ ├── css/ # CSS processing (7 modules)
|
|
481
|
+
│ │ ├── html/ # HTML extraction (5 modules)
|
|
482
|
+
│ │ ├── animation/ # Animation & hover states (5 modules)
|
|
483
|
+
│ │ ├── discovery/ # Page discovery (6 modules)
|
|
484
|
+
│ │ ├── detection/ # Framework detection (3 modules)
|
|
485
|
+
│ │ ├── dimension/ # DOM analysis (6 modules)
|
|
486
|
+
│ │ ├── section/ # Section detection (5 modules)
|
|
487
|
+
│ │ ├── media/ # Asset extraction (5 modules)
|
|
488
|
+
│ │ ├── page-prep/ # Page readiness (3 modules)
|
|
489
|
+
│ │ ├── content/ # Content analysis (2 modules)
|
|
490
|
+
│ │ ├── links/ # URL rewriting (2 modules)
|
|
491
|
+
│ │ └── tests/ # Core tests (2 modules)
|
|
492
|
+
│ │
|
|
493
|
+
│ ├── figma/ # Figma-to-code pipeline
|
|
494
|
+
│ │ ├── parse-url.js # URL parsing
|
|
495
|
+
│ │ ├── figma-client.py # API client
|
|
496
|
+
│ │ ├── extract-figma.py # Token extraction
|
|
497
|
+
│ │ ├── generate-css.py # BEM generation
|
|
498
|
+
│ │ └── generate-tailwind.py # Tailwind generation
|
|
499
|
+
│ │
|
|
500
|
+
│ ├── ai/ # AI analysis
|
|
501
|
+
│ │ ├── analyze-structure.py
|
|
502
|
+
│ │ ├── extract-design-tokens.py
|
|
503
|
+
│ │ └── prompts/
|
|
504
|
+
│ │ ├── structure_analysis.py
|
|
505
|
+
│ │ ├── design_tokens.py
|
|
506
|
+
│ │ └── ux_audit.py
|
|
507
|
+
│ │
|
|
508
|
+
│ ├── verification/ # Quality checks
|
|
509
|
+
│ │ ├── verify-menu.js
|
|
510
|
+
│ │ ├── verify-layout.js
|
|
511
|
+
│ │ └── [other verifications]
|
|
512
|
+
│ │
|
|
513
|
+
│ ├── post-process/ # Asset processing
|
|
514
|
+
│ │ ├── fetch-images.js
|
|
515
|
+
│ │ ├── inject-icons.js
|
|
516
|
+
│ │ └── enhance-assets.js
|
|
517
|
+
│ │
|
|
518
|
+
│ ├── utils/ # Shared utilities
|
|
519
|
+
│ │ ├── browser.js
|
|
520
|
+
│ │ ├── env.js / env.py
|
|
521
|
+
│ │ ├── helpers.js
|
|
522
|
+
│ │ ├── playwright.js
|
|
523
|
+
│ │ └── log.js # Centralized logging (Feb 23)
|
|
524
|
+
│ │
|
|
525
|
+
│ └── route-discoverers/ # Framework detection
|
|
526
|
+
│ ├── base-discoverer.js
|
|
527
|
+
│ ├── react-discoverer.js
|
|
528
|
+
│ └── [other frameworks]
|
|
529
|
+
│
|
|
530
|
+
├── tests/ # Test files
|
|
531
|
+
│ ├── unit/
|
|
532
|
+
│ ├── integration/
|
|
533
|
+
│ └── fixtures/
|
|
534
|
+
│
|
|
535
|
+
├── templates/ # HTML/CSS templates
|
|
536
|
+
│ ├── base.html
|
|
537
|
+
│ └── base.css
|
|
538
|
+
│
|
|
539
|
+
├── docs/ # Documentation
|
|
540
|
+
└── prd/ # Product requirements
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Module Dependencies
|
|
544
|
+
|
|
545
|
+
**Guidelines:**
|
|
546
|
+
- Minimize circular dependencies
|
|
547
|
+
- Organize imports: external → local
|
|
548
|
+
- Separate concerns across modules
|
|
549
|
+
- Use dependency injection for testability
|
|
550
|
+
- Import directly from module files using relative paths
|
|
551
|
+
- Use centralized logging (src/utils/log.js) instead of console.log
|
|
552
|
+
|
|
553
|
+
**Companion Module Pattern (Feb 23):**
|
|
554
|
+
|
|
555
|
+
When a module grows beyond 150-200 LOC with repetitive logic, decompose using companion modules:
|
|
556
|
+
- `module.js` - Main orchestrator (stable API)
|
|
557
|
+
- `module-feature.js` - Specific feature extraction
|
|
558
|
+
- `module-output.js` - Output formatting
|
|
559
|
+
- `module-mappings.js` - Mapping tables/rules
|
|
560
|
+
|
|
561
|
+
**Examples:**
|
|
562
|
+
- `semantic-enhancer.js` → `semantic-enhancer-page.js`, `semantic-enhancer-mappings.js`
|
|
563
|
+
- `dimension-extractor.js` → `dimension-extractor-card-detector.js`, `dimension-output.js`, `dimension-output-ai-summary.js`
|
|
564
|
+
|
|
565
|
+
**Example Dependency Structure:**
|
|
566
|
+
```
|
|
567
|
+
CLI Layer
|
|
568
|
+
↓
|
|
569
|
+
Workflow Layer (clone, clone-px, clone-site, figma-to-code)
|
|
570
|
+
↓
|
|
571
|
+
Core Engines (capture/, css/, discovery/, detection/, etc. via direct imports)
|
|
572
|
+
↓
|
|
573
|
+
Utilities (browser, env, helpers, log, playwright)
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Error Handling
|
|
579
|
+
|
|
580
|
+
### Error Classification
|
|
581
|
+
|
|
582
|
+
**User Errors (exit code 1):**
|
|
583
|
+
```javascript
|
|
584
|
+
// Invalid input
|
|
585
|
+
if (!isValidUrl(url)) {
|
|
586
|
+
console.error(`Error: Invalid URL format: ${url}`);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Missing configuration
|
|
591
|
+
if (!process.env.FIGMA_ACCESS_TOKEN) {
|
|
592
|
+
console.error('Error: FIGMA_ACCESS_TOKEN environment variable not set');
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**System Errors (exit code 2):**
|
|
598
|
+
```javascript
|
|
599
|
+
// Browser crash
|
|
600
|
+
if (!browser || !browser.isConnected()) {
|
|
601
|
+
console.error('Error: Browser disconnected unexpectedly');
|
|
602
|
+
process.exit(2);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Disk I/O failure
|
|
606
|
+
catch (error) {
|
|
607
|
+
if (error.code === 'ENOSPC') {
|
|
608
|
+
console.error('Error: Disk space exhausted');
|
|
609
|
+
process.exit(2);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Unknown Errors (exit code 3):**
|
|
615
|
+
```javascript
|
|
616
|
+
process.on('unhandledRejection', (reason) => {
|
|
617
|
+
console.error('Unhandled rejection:', reason);
|
|
618
|
+
process.exit(3);
|
|
619
|
+
});
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Error Messages
|
|
623
|
+
|
|
624
|
+
**Format:**
|
|
625
|
+
```
|
|
626
|
+
Error: [Brief description]
|
|
627
|
+
Context: [Relevant details]
|
|
628
|
+
Suggestion: [How to fix]
|
|
629
|
+
|
|
630
|
+
Example:
|
|
631
|
+
Error: Invalid Figma URL format
|
|
632
|
+
Context: URL must contain /design/ or /file/ path
|
|
633
|
+
Suggestion: Use: figma.com/design/{file_key}/...
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
## Testing Standards
|
|
639
|
+
|
|
640
|
+
### Test Structure
|
|
641
|
+
|
|
642
|
+
**File naming:** `src/core/capture/screenshot.js` → `tests/unit/screenshot.test.js`
|
|
643
|
+
|
|
644
|
+
**Template (JavaScript):**
|
|
645
|
+
```javascript
|
|
646
|
+
describe('ScreenshotCapture', () => {
|
|
647
|
+
it('should capture all three viewports', async () => {
|
|
648
|
+
const screenshots = await captureViewports('https://example.com');
|
|
649
|
+
expect(screenshots).toHaveLength(3);
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
**Template (Python):**
|
|
655
|
+
```python
|
|
656
|
+
def test_extract_colors(self):
|
|
657
|
+
tokens = extract_design_tokens(sample_data)
|
|
658
|
+
assert 'colors' in tokens
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**Coverage:** Minimum 85% statement coverage, 100% for critical paths, integration tests for workflows
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## Documentation Standards
|
|
666
|
+
|
|
667
|
+
### Code Comments
|
|
668
|
+
|
|
669
|
+
**Use comments for WHY, not WHAT:**
|
|
670
|
+
```javascript
|
|
671
|
+
// Bad
|
|
672
|
+
// Increment counter
|
|
673
|
+
counter++;
|
|
674
|
+
|
|
675
|
+
// Good
|
|
676
|
+
// Reset counter for next batch processing cycle
|
|
677
|
+
counter++;
|
|
678
|
+
|
|
679
|
+
// Good: Explaining non-obvious logic
|
|
680
|
+
// Playwright requires minimum 100ms delay between viewport changes
|
|
681
|
+
// to avoid race conditions in CSS media query evaluation
|
|
682
|
+
await page.waitForTimeout(100);
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Block comments for complex algorithms:**
|
|
686
|
+
```python
|
|
687
|
+
def extract_colors(nodes):
|
|
688
|
+
"""
|
|
689
|
+
Extract unique colors from Figma nodes.
|
|
690
|
+
|
|
691
|
+
Algorithm:
|
|
692
|
+
1. Traverse all nodes recursively
|
|
693
|
+
2. For each node, check fills and strokes
|
|
694
|
+
3. Convert RGBA to hex, normalize to #RRGGBB
|
|
695
|
+
4. Deduplicate using case-insensitive matching
|
|
696
|
+
5. Categorize by brightness (light/dark)
|
|
697
|
+
6. Assign semantic names (primary, secondary, etc.)
|
|
698
|
+
"""
|
|
699
|
+
colors = {}
|
|
700
|
+
# Implementation...
|
|
701
|
+
return colors
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### JSDoc/Docstrings
|
|
705
|
+
|
|
706
|
+
**Required for all public APIs:**
|
|
707
|
+
```javascript
|
|
708
|
+
/**
|
|
709
|
+
* Extract CSS rules used in HTML.
|
|
710
|
+
*
|
|
711
|
+
* Analyzes HTML for all used selectors and matches them against
|
|
712
|
+
* CSS rules. Removes declarations that don't match any selector
|
|
713
|
+
* while preserving media queries and keyframes.
|
|
714
|
+
*
|
|
715
|
+
* @param {string} html - HTML content
|
|
716
|
+
* @param {string} css - CSS stylesheet
|
|
717
|
+
* @param {Object} options - Configuration
|
|
718
|
+
* @param {boolean} options.preserveAnimations - Keep @keyframes (default: true)
|
|
719
|
+
* @param {boolean} options.verbose - Log removals (default: false)
|
|
720
|
+
* @returns {string} Filtered CSS
|
|
721
|
+
* @throws {Error} If HTML or CSS is invalid
|
|
722
|
+
*/
|
|
723
|
+
function filterUnusedCSS(html, css, options = {}) {}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### README Structure
|
|
727
|
+
|
|
728
|
+
Each module with public APIs should have:
|
|
729
|
+
- One-line description
|
|
730
|
+
- Key functions/classes
|
|
731
|
+
- Usage examples
|
|
732
|
+
- Error handling
|
|
733
|
+
- Configuration options
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## Performance Guidelines
|
|
738
|
+
|
|
739
|
+
**Rules:**
|
|
740
|
+
- Profile before optimizing (use `console.time()`)
|
|
741
|
+
- Use `Promise.all()` for parallel work, sequence only when dependent
|
|
742
|
+
- Stream large files, batch writes with `fs.promises`
|
|
743
|
+
- Release large objects: `obj = null`
|
|
744
|
+
|
|
745
|
+
**Target Performance:**
|
|
746
|
+
- Screenshot: 3-5s per viewport
|
|
747
|
+
- CSS filtering: 1-2s
|
|
748
|
+
- Asset extraction: 5-10s per 10 images
|
|
749
|
+
- Full workflow: <120s
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## Security Standards
|
|
754
|
+
|
|
755
|
+
**Input Validation:** Always validate external input (URLs, tokens, files)
|
|
756
|
+
|
|
757
|
+
```javascript
|
|
758
|
+
function validateUrl(url) {
|
|
759
|
+
if (typeof url !== 'string' || !url.trim()) throw new Error('Invalid URL');
|
|
760
|
+
const parsed = new URL(url);
|
|
761
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) throw new Error('HTTP only');
|
|
762
|
+
return url;
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**Credentials:** Use environment variables only, never log tokens
|
|
767
|
+
|
|
768
|
+
```javascript
|
|
769
|
+
const token = process.env.FIGMA_ACCESS_TOKEN;
|
|
770
|
+
if (!token) throw new Error('FIGMA_ACCESS_TOKEN required');
|
|
771
|
+
console.log('Using token:', 'REDACTED'); // Good
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**HTML Sanitization:** Remove `<script>`, event handlers, `javascript:` URLs
|
|
775
|
+
|
|
776
|
+
---
|
|
777
|
+
|
|
778
|
+
## Code Review Standards
|
|
779
|
+
|
|
780
|
+
Before submitting code:
|
|
781
|
+
- Code follows naming conventions & project structure
|
|
782
|
+
- Functions have JSDoc/docstring comments
|
|
783
|
+
- Error handling is comprehensive
|
|
784
|
+
- No credentials or secrets
|
|
785
|
+
- Tests added/updated (maintain 85%+ coverage)
|
|
786
|
+
- No console.log in production
|
|
787
|
+
- Documentation updated
|
|
788
|
+
|
|
789
|
+
**References:** [Google JS Style Guide](https://google.github.io/styleguide/tsguide.html), [PEP 8](https://pep8.org), OWASP Top 10
|