uilint-vision 0.2.138 → 0.2.139
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/chunk-XELUGF6W.js +233 -0
- package/dist/chunk-XELUGF6W.js.map +1 -0
- package/dist/chunk-XPBTVJRY.js +1 -0
- package/dist/chunk-XPBTVJRY.js.map +1 -0
- package/dist/eslint-rules/index.d.ts +3 -0
- package/dist/eslint-rules/index.js +10 -0
- package/dist/eslint-rules/index.js.map +1 -0
- package/dist/eslint-rules/register.d.ts +2 -0
- package/dist/eslint-rules/register.js +18 -0
- package/dist/eslint-rules/register.js.map +1 -0
- package/dist/eslint-rules/semantic-vision.d.ts +33 -0
- package/dist/eslint-rules/semantic-vision.js +9 -0
- package/dist/eslint-rules/semantic-vision.js.map +1 -0
- package/package.json +15 -2
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// src/eslint-rules/semantic-vision.ts
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
3
|
+
import { dirname, join, relative } from "path";
|
|
4
|
+
import { createRule, defineRuleMeta } from "uilint-eslint";
|
|
5
|
+
var meta = defineRuleMeta({
|
|
6
|
+
id: "semantic-vision",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
name: "Vision Analysis",
|
|
9
|
+
description: "Report cached vision analysis results from UILint browser overlay",
|
|
10
|
+
defaultSeverity: "warn",
|
|
11
|
+
category: "semantic",
|
|
12
|
+
icon: "\u{1F441}\uFE0F",
|
|
13
|
+
hint: "Vision AI for rendered UI",
|
|
14
|
+
defaultEnabled: false,
|
|
15
|
+
requiresStyleguide: false,
|
|
16
|
+
plugin: "vision",
|
|
17
|
+
eslintImport: "uilint-vision/eslint-rules/semantic-vision",
|
|
18
|
+
customInspector: "vision-issue",
|
|
19
|
+
heatmapColor: "#8B5CF6",
|
|
20
|
+
postInstallInstructions: "Add the UILint browser overlay to your app and run analysis from the browser to generate cached results.",
|
|
21
|
+
defaultOptions: [{ maxAgeMs: 36e5, screenshotsPath: ".uilint/screenshots" }],
|
|
22
|
+
optionSchema: {
|
|
23
|
+
fields: [
|
|
24
|
+
{
|
|
25
|
+
key: "maxAgeMs",
|
|
26
|
+
label: "Max cache age (milliseconds)",
|
|
27
|
+
type: "number",
|
|
28
|
+
defaultValue: 36e5,
|
|
29
|
+
placeholder: "3600000",
|
|
30
|
+
description: "Maximum age of cached results in milliseconds (default: 1 hour)"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "screenshotsPath",
|
|
34
|
+
label: "Screenshots directory path",
|
|
35
|
+
type: "text",
|
|
36
|
+
defaultValue: ".uilint/screenshots",
|
|
37
|
+
placeholder: ".uilint/screenshots",
|
|
38
|
+
description: "Relative path to the screenshots directory containing analysis results"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
docs: `
|
|
43
|
+
## What it does
|
|
44
|
+
|
|
45
|
+
Reports UI issues found by the UILint browser overlay's vision analysis. The overlay
|
|
46
|
+
captures screenshots and analyzes them using vision AI, then caches the results.
|
|
47
|
+
This ESLint rule reads those cached results and reports them as linting errors.
|
|
48
|
+
|
|
49
|
+
## How it works
|
|
50
|
+
|
|
51
|
+
1. **Browser overlay**: When running your dev server with the UILint overlay, it captures
|
|
52
|
+
screenshots and analyzes them using vision AI
|
|
53
|
+
2. **Results cached**: Analysis results are saved to \`.uilint/screenshots/*.json\`
|
|
54
|
+
3. **ESLint reports**: This rule reads cached results and reports issues at the correct
|
|
55
|
+
source locations using \`data-loc\` attributes
|
|
56
|
+
|
|
57
|
+
## Why it's useful
|
|
58
|
+
|
|
59
|
+
- **Visual issues**: Catches problems that can only be seen in rendered UI
|
|
60
|
+
- **Continuous feedback**: Issues appear in your editor as you develop
|
|
61
|
+
- **No manual review**: AI spots spacing, alignment, and consistency issues automatically
|
|
62
|
+
|
|
63
|
+
## Prerequisites
|
|
64
|
+
|
|
65
|
+
1. **UILint overlay installed**: Add the overlay component to your app
|
|
66
|
+
2. **Run analysis**: Load pages in the browser with the overlay active
|
|
67
|
+
3. **Results cached**: Wait for analysis to complete and cache results
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
\`\`\`js
|
|
72
|
+
// eslint.config.js
|
|
73
|
+
"uilint/semantic-vision": ["warn", {
|
|
74
|
+
maxAgeMs: 3600000, // Ignore results older than 1 hour
|
|
75
|
+
screenshotsPath: ".uilint/screenshots" // Where cached results are stored
|
|
76
|
+
}]
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
## Notes
|
|
80
|
+
|
|
81
|
+
- If no cached results exist, the rule passes silently
|
|
82
|
+
- Results are matched to source files using \`data-loc\` attributes
|
|
83
|
+
- Stale results (older than \`maxAgeMs\`) are reported as warnings
|
|
84
|
+
- Run the browser overlay to refresh cached analysis
|
|
85
|
+
`
|
|
86
|
+
});
|
|
87
|
+
function findProjectRoot(startDir) {
|
|
88
|
+
let dir = startDir;
|
|
89
|
+
for (let i = 0; i < 20; i++) {
|
|
90
|
+
if (existsSync(join(dir, "package.json"))) {
|
|
91
|
+
return dir;
|
|
92
|
+
}
|
|
93
|
+
const parent = dirname(dir);
|
|
94
|
+
if (parent === dir) break;
|
|
95
|
+
dir = parent;
|
|
96
|
+
}
|
|
97
|
+
return startDir;
|
|
98
|
+
}
|
|
99
|
+
function getVisionResultFiles(screenshotsDir) {
|
|
100
|
+
if (!existsSync(screenshotsDir)) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const files = readdirSync(screenshotsDir);
|
|
105
|
+
return files.filter((f) => f.endsWith(".json")).map((f) => join(screenshotsDir, f)).sort().reverse();
|
|
106
|
+
} catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function loadVisionResult(filePath) {
|
|
111
|
+
try {
|
|
112
|
+
const content = readFileSync(filePath, "utf-8");
|
|
113
|
+
return JSON.parse(content);
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function parseDataLoc(dataLoc) {
|
|
119
|
+
const match = dataLoc.match(/^(.+):(\d+):(\d+)$/);
|
|
120
|
+
if (!match) return null;
|
|
121
|
+
return {
|
|
122
|
+
filePath: match[1],
|
|
123
|
+
line: parseInt(match[2], 10),
|
|
124
|
+
column: parseInt(match[3], 10)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function normalizeFilePath(filePath, projectRoot) {
|
|
128
|
+
if (!filePath.startsWith("/")) {
|
|
129
|
+
return filePath;
|
|
130
|
+
}
|
|
131
|
+
return relative(projectRoot, filePath);
|
|
132
|
+
}
|
|
133
|
+
var semantic_vision_default = createRule({
|
|
134
|
+
name: "semantic-vision",
|
|
135
|
+
meta: {
|
|
136
|
+
type: "suggestion",
|
|
137
|
+
docs: {
|
|
138
|
+
description: "Report cached vision analysis results from UILint browser overlay"
|
|
139
|
+
},
|
|
140
|
+
messages: {
|
|
141
|
+
visionIssue: "[Vision] {{message}}",
|
|
142
|
+
analysisStale: "Vision analysis results are stale (older than {{age}}). Re-run analysis in browser."
|
|
143
|
+
},
|
|
144
|
+
schema: [
|
|
145
|
+
{
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
maxAgeMs: {
|
|
149
|
+
type: "number",
|
|
150
|
+
description: "Maximum age of cached results in milliseconds (default: 1 hour)"
|
|
151
|
+
},
|
|
152
|
+
screenshotsPath: {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "Path to screenshots directory (default: .uilint/screenshots)"
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
additionalProperties: false
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
defaultOptions: [{ maxAgeMs: 60 * 60 * 1e3 }],
|
|
162
|
+
// 1 hour default
|
|
163
|
+
create(context) {
|
|
164
|
+
const options = context.options[0] || {};
|
|
165
|
+
const maxAgeMs = options.maxAgeMs ?? 60 * 60 * 1e3;
|
|
166
|
+
const filePath = context.filename;
|
|
167
|
+
const fileDir = dirname(filePath);
|
|
168
|
+
const projectRoot = findProjectRoot(fileDir);
|
|
169
|
+
const screenshotsDir = options.screenshotsPath ? join(projectRoot, options.screenshotsPath) : join(projectRoot, ".uilint", "screenshots");
|
|
170
|
+
const relativeFilePath = normalizeFilePath(filePath, projectRoot);
|
|
171
|
+
const resultFiles = getVisionResultFiles(screenshotsDir);
|
|
172
|
+
if (resultFiles.length === 0) {
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
const matchingIssues = [];
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
for (const resultFile of resultFiles) {
|
|
178
|
+
const result = loadVisionResult(resultFile);
|
|
179
|
+
if (!result || !result.issues) continue;
|
|
180
|
+
const isStale = now - result.timestamp > maxAgeMs;
|
|
181
|
+
for (const issue of result.issues) {
|
|
182
|
+
if (!issue.dataLoc) continue;
|
|
183
|
+
const parsed = parseDataLoc(issue.dataLoc);
|
|
184
|
+
if (!parsed) continue;
|
|
185
|
+
const issueFilePath = normalizeFilePath(parsed.filePath, projectRoot);
|
|
186
|
+
if (issueFilePath === relativeFilePath) {
|
|
187
|
+
matchingIssues.push({
|
|
188
|
+
issue,
|
|
189
|
+
line: parsed.line,
|
|
190
|
+
column: parsed.column,
|
|
191
|
+
isStale
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const seenIssues = /* @__PURE__ */ new Set();
|
|
197
|
+
const uniqueIssues = matchingIssues.filter((item) => {
|
|
198
|
+
const key = `${item.line}:${item.column}:${item.issue.message}`;
|
|
199
|
+
if (seenIssues.has(key)) return false;
|
|
200
|
+
seenIssues.add(key);
|
|
201
|
+
return true;
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
Program(node) {
|
|
205
|
+
for (const { issue, line, column, isStale } of uniqueIssues) {
|
|
206
|
+
const categoryPrefix = issue.category ? `[${issue.category}] ` : "";
|
|
207
|
+
const message = `${categoryPrefix}${issue.message}`;
|
|
208
|
+
if (isStale) {
|
|
209
|
+
const ageHours = Math.round(maxAgeMs / (60 * 60 * 1e3));
|
|
210
|
+
context.report({
|
|
211
|
+
node,
|
|
212
|
+
loc: { line, column },
|
|
213
|
+
messageId: "analysisStale",
|
|
214
|
+
data: { age: `${ageHours}h` }
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
context.report({
|
|
218
|
+
node,
|
|
219
|
+
loc: { line, column },
|
|
220
|
+
messageId: "visionIssue",
|
|
221
|
+
data: { message }
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
export {
|
|
230
|
+
meta,
|
|
231
|
+
semantic_vision_default
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=chunk-XELUGF6W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/eslint-rules/semantic-vision.ts"],"sourcesContent":["/**\n * Rule: semantic-vision\n *\n * ESLint rule that reports cached vision analysis results for UI elements.\n * Vision analysis is performed by the UILint browser overlay and results are\n * cached in .uilint/screenshots/{timestamp}-{route}.json files.\n *\n * This rule:\n * 1. Finds cached vision analysis results for the current file\n * 2. Reports any issues that match elements in this file (by data-loc)\n * 3. If no cached results exist, silently passes (analysis is triggered by browser)\n */\n\nimport { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { dirname, join, relative } from \"path\";\nimport { createRule, defineRuleMeta } from \"uilint-eslint\";\n\ntype MessageIds = \"visionIssue\" | \"analysisStale\";\n\ntype Options = [\n {\n /** Maximum age of cached results in milliseconds (default: 1 hour) */\n maxAgeMs?: number;\n /** Path to screenshots directory (default: .uilint/screenshots) */\n screenshotsPath?: string;\n }\n];\n\n/**\n * Rule metadata - colocated with implementation for maintainability\n */\nexport const meta = defineRuleMeta({\n id: \"semantic-vision\",\n version: \"1.0.0\",\n name: \"Vision Analysis\",\n description: \"Report cached vision analysis results from UILint browser overlay\",\n defaultSeverity: \"warn\",\n category: \"semantic\",\n icon: \"👁️\",\n hint: \"Vision AI for rendered UI\",\n defaultEnabled: false,\n requiresStyleguide: false,\n plugin: \"vision\",\n eslintImport: \"uilint-vision/eslint-rules/semantic-vision\",\n customInspector: \"vision-issue\",\n heatmapColor: \"#8B5CF6\",\n postInstallInstructions: \"Add the UILint browser overlay to your app and run analysis from the browser to generate cached results.\",\n defaultOptions: [{ maxAgeMs: 3600000, screenshotsPath: \".uilint/screenshots\" }],\n optionSchema: {\n fields: [\n {\n key: \"maxAgeMs\",\n label: \"Max cache age (milliseconds)\",\n type: \"number\",\n defaultValue: 3600000,\n placeholder: \"3600000\",\n description: \"Maximum age of cached results in milliseconds (default: 1 hour)\",\n },\n {\n key: \"screenshotsPath\",\n label: \"Screenshots directory path\",\n type: \"text\",\n defaultValue: \".uilint/screenshots\",\n placeholder: \".uilint/screenshots\",\n description: \"Relative path to the screenshots directory containing analysis results\",\n },\n ],\n },\n docs: `\n## What it does\n\nReports UI issues found by the UILint browser overlay's vision analysis. The overlay\ncaptures screenshots and analyzes them using vision AI, then caches the results.\nThis ESLint rule reads those cached results and reports them as linting errors.\n\n## How it works\n\n1. **Browser overlay**: When running your dev server with the UILint overlay, it captures\n screenshots and analyzes them using vision AI\n2. **Results cached**: Analysis results are saved to \\`.uilint/screenshots/*.json\\`\n3. **ESLint reports**: This rule reads cached results and reports issues at the correct\n source locations using \\`data-loc\\` attributes\n\n## Why it's useful\n\n- **Visual issues**: Catches problems that can only be seen in rendered UI\n- **Continuous feedback**: Issues appear in your editor as you develop\n- **No manual review**: AI spots spacing, alignment, and consistency issues automatically\n\n## Prerequisites\n\n1. **UILint overlay installed**: Add the overlay component to your app\n2. **Run analysis**: Load pages in the browser with the overlay active\n3. **Results cached**: Wait for analysis to complete and cache results\n\n## Configuration\n\n\\`\\`\\`js\n// eslint.config.js\n\"uilint/semantic-vision\": [\"warn\", {\n maxAgeMs: 3600000, // Ignore results older than 1 hour\n screenshotsPath: \".uilint/screenshots\" // Where cached results are stored\n}]\n\\`\\`\\`\n\n## Notes\n\n- If no cached results exist, the rule passes silently\n- Results are matched to source files using \\`data-loc\\` attributes\n- Stale results (older than \\`maxAgeMs\\`) are reported as warnings\n- Run the browser overlay to refresh cached analysis\n`,\n});\n\n/**\n * Vision analysis result structure stored in JSON files\n */\ninterface VisionAnalysisResult {\n /** Timestamp when analysis was performed */\n timestamp: number;\n /** Route that was analyzed (e.g., \"/\", \"/profile\") */\n route: string;\n /** Screenshot filename (for reference) */\n screenshotFile?: string;\n /** Issues found by vision analysis */\n issues: VisionIssue[];\n}\n\n/**\n * Individual issue from vision analysis\n */\ninterface VisionIssue {\n /** Element text that the LLM referenced */\n elementText?: string;\n /** data-loc reference (format: \"path:line:column\") */\n dataLoc?: string;\n /** Human-readable description of the issue */\n message: string;\n /** Issue category */\n category?: \"spacing\" | \"color\" | \"typography\" | \"alignment\" | \"accessibility\" | \"layout\" | \"other\";\n /** Severity level */\n severity?: \"error\" | \"warning\" | \"info\";\n}\n\n/**\n * Find project root by looking for package.json\n */\nfunction findProjectRoot(startDir: string): string {\n let dir = startDir;\n for (let i = 0; i < 20; i++) {\n if (existsSync(join(dir, \"package.json\"))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return startDir;\n}\n\n/**\n * Get all vision analysis result files from screenshots directory\n */\nfunction getVisionResultFiles(screenshotsDir: string): string[] {\n if (!existsSync(screenshotsDir)) {\n return [];\n }\n\n try {\n const files = readdirSync(screenshotsDir);\n return files\n .filter((f) => f.endsWith(\".json\"))\n .map((f) => join(screenshotsDir, f))\n .sort()\n .reverse(); // Most recent first\n } catch {\n return [];\n }\n}\n\n/**\n * Load and parse a vision analysis result file\n */\nfunction loadVisionResult(filePath: string): VisionAnalysisResult | null {\n try {\n const content = readFileSync(filePath, \"utf-8\");\n return JSON.parse(content) as VisionAnalysisResult;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse a data-loc string into file path and location\n * Format: \"path/to/file.tsx:line:column\"\n */\nfunction parseDataLoc(dataLoc: string): { filePath: string; line: number; column: number } | null {\n // Match pattern: path:line:column (line and column are numbers)\n const match = dataLoc.match(/^(.+):(\\d+):(\\d+)$/);\n if (!match) return null;\n\n return {\n filePath: match[1]!,\n line: parseInt(match[2]!, 10),\n column: parseInt(match[3]!, 10),\n };\n}\n\n/**\n * Normalize file path for comparison (handle relative vs absolute paths)\n */\nfunction normalizeFilePath(filePath: string, projectRoot: string): string {\n // If it's already a relative path, return as-is\n if (!filePath.startsWith(\"/\")) {\n return filePath;\n }\n // Convert absolute to relative\n return relative(projectRoot, filePath);\n}\n\nexport default createRule<Options, MessageIds>({\n name: \"semantic-vision\",\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Report cached vision analysis results from UILint browser overlay\",\n },\n messages: {\n visionIssue: \"[Vision] {{message}}\",\n analysisStale:\n \"Vision analysis results are stale (older than {{age}}). Re-run analysis in browser.\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n maxAgeMs: {\n type: \"number\",\n description:\n \"Maximum age of cached results in milliseconds (default: 1 hour)\",\n },\n screenshotsPath: {\n type: \"string\",\n description:\n \"Path to screenshots directory (default: .uilint/screenshots)\",\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [{ maxAgeMs: 60 * 60 * 1000 }], // 1 hour default\n create(context) {\n const options = context.options[0] || {};\n const maxAgeMs = options.maxAgeMs ?? 60 * 60 * 1000;\n const filePath = context.filename;\n const fileDir = dirname(filePath);\n\n // Find project root and screenshots directory\n const projectRoot = findProjectRoot(fileDir);\n const screenshotsDir = options.screenshotsPath\n ? join(projectRoot, options.screenshotsPath)\n : join(projectRoot, \".uilint\", \"screenshots\");\n\n // Get the relative path of the current file for matching against data-loc\n const relativeFilePath = normalizeFilePath(filePath, projectRoot);\n\n // Find all vision result files\n const resultFiles = getVisionResultFiles(screenshotsDir);\n if (resultFiles.length === 0) {\n // No cached results - silently pass (analysis happens in browser)\n return {};\n }\n\n // Collect issues that match this file from all recent results\n const matchingIssues: Array<{\n issue: VisionIssue;\n line: number;\n column: number;\n isStale: boolean;\n }> = [];\n\n const now = Date.now();\n\n for (const resultFile of resultFiles) {\n const result = loadVisionResult(resultFile);\n if (!result || !result.issues) continue;\n\n const isStale = now - result.timestamp > maxAgeMs;\n\n for (const issue of result.issues) {\n if (!issue.dataLoc) continue;\n\n const parsed = parseDataLoc(issue.dataLoc);\n if (!parsed) continue;\n\n // Check if this issue is for the current file\n const issueFilePath = normalizeFilePath(parsed.filePath, projectRoot);\n if (issueFilePath === relativeFilePath) {\n matchingIssues.push({\n issue,\n line: parsed.line,\n column: parsed.column,\n isStale,\n });\n }\n }\n }\n\n // De-duplicate issues by line:column:message\n const seenIssues = new Set<string>();\n const uniqueIssues = matchingIssues.filter((item) => {\n const key = `${item.line}:${item.column}:${item.issue.message}`;\n if (seenIssues.has(key)) return false;\n seenIssues.add(key);\n return true;\n });\n\n return {\n Program(node) {\n for (const { issue, line, column, isStale } of uniqueIssues) {\n // Build message with category prefix if available\n const categoryPrefix = issue.category\n ? `[${issue.category}] `\n : \"\";\n const message = `${categoryPrefix}${issue.message}`;\n\n // Report stale warning separately if enabled\n if (isStale) {\n const ageHours = Math.round(maxAgeMs / (60 * 60 * 1000));\n context.report({\n node,\n loc: { line, column },\n messageId: \"analysisStale\",\n data: { age: `${ageHours}h` },\n });\n }\n\n context.report({\n node,\n loc: { line, column },\n messageId: \"visionIssue\",\n data: { message },\n });\n }\n },\n };\n },\n});\n"],"mappings":";AAaA,SAAS,YAAY,aAAa,oBAAoB;AACtD,SAAS,SAAS,MAAM,gBAAgB;AACxC,SAAS,YAAY,sBAAsB;AAgBpC,IAAM,OAAO,eAAe;AAAA,EACjC,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,MAAM;AAAA,EACN,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,yBAAyB;AAAA,EACzB,gBAAgB,CAAC,EAAE,UAAU,MAAS,iBAAiB,sBAAsB,CAAC;AAAA,EAC9E,cAAc;AAAA,IACZ,QAAQ;AAAA,MACN;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,cAAc;AAAA,QACd,aAAa;AAAA,QACb,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,cAAc;AAAA,QACd,aAAa;AAAA,QACb,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4CR,CAAC;AAmCD,SAAS,gBAAgB,UAA0B;AACjD,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI,WAAW,KAAK,KAAK,cAAc,CAAC,GAAG;AACzC,aAAO;AAAA,IACT;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO;AACT;AAKA,SAAS,qBAAqB,gBAAkC;AAC9D,MAAI,CAAC,WAAW,cAAc,GAAG;AAC/B,WAAO,CAAC;AAAA,EACV;AAEA,MAAI;AACF,UAAM,QAAQ,YAAY,cAAc;AACxC,WAAO,MACJ,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC,EACjC,IAAI,CAAC,MAAM,KAAK,gBAAgB,CAAC,CAAC,EAClC,KAAK,EACL,QAAQ;AAAA,EACb,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAKA,SAAS,iBAAiB,UAA+C;AACvE,MAAI;AACF,UAAM,UAAU,aAAa,UAAU,OAAO;AAC9C,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,aAAa,SAA4E;AAEhG,QAAM,QAAQ,QAAQ,MAAM,oBAAoB;AAChD,MAAI,CAAC,MAAO,QAAO;AAEnB,SAAO;AAAA,IACL,UAAU,MAAM,CAAC;AAAA,IACjB,MAAM,SAAS,MAAM,CAAC,GAAI,EAAE;AAAA,IAC5B,QAAQ,SAAS,MAAM,CAAC,GAAI,EAAE;AAAA,EAChC;AACF;AAKA,SAAS,kBAAkB,UAAkB,aAA6B;AAExE,MAAI,CAAC,SAAS,WAAW,GAAG,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO,SAAS,aAAa,QAAQ;AACvC;AAEA,IAAO,0BAAQ,WAAgC;AAAA,EAC7C,MAAM;AAAA,EACN,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,aACE;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,MACR,aAAa;AAAA,MACb,eACE;AAAA,IACJ;AAAA,IACA,QAAQ;AAAA,MACN;AAAA,QACE,MAAM;AAAA,QACN,YAAY;AAAA,UACV,UAAU;AAAA,YACR,MAAM;AAAA,YACN,aACE;AAAA,UACJ;AAAA,UACA,iBAAiB;AAAA,YACf,MAAM;AAAA,YACN,aACE;AAAA,UACJ;AAAA,QACF;AAAA,QACA,sBAAsB;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EACA,gBAAgB,CAAC,EAAE,UAAU,KAAK,KAAK,IAAK,CAAC;AAAA;AAAA,EAC7C,OAAO,SAAS;AACd,UAAM,UAAU,QAAQ,QAAQ,CAAC,KAAK,CAAC;AACvC,UAAM,WAAW,QAAQ,YAAY,KAAK,KAAK;AAC/C,UAAM,WAAW,QAAQ;AACzB,UAAM,UAAU,QAAQ,QAAQ;AAGhC,UAAM,cAAc,gBAAgB,OAAO;AAC3C,UAAM,iBAAiB,QAAQ,kBAC3B,KAAK,aAAa,QAAQ,eAAe,IACzC,KAAK,aAAa,WAAW,aAAa;AAG9C,UAAM,mBAAmB,kBAAkB,UAAU,WAAW;AAGhE,UAAM,cAAc,qBAAqB,cAAc;AACvD,QAAI,YAAY,WAAW,GAAG;AAE5B,aAAO,CAAC;AAAA,IACV;AAGA,UAAM,iBAKD,CAAC;AAEN,UAAM,MAAM,KAAK,IAAI;AAErB,eAAW,cAAc,aAAa;AACpC,YAAM,SAAS,iBAAiB,UAAU;AAC1C,UAAI,CAAC,UAAU,CAAC,OAAO,OAAQ;AAE/B,YAAM,UAAU,MAAM,OAAO,YAAY;AAEzC,iBAAW,SAAS,OAAO,QAAQ;AACjC,YAAI,CAAC,MAAM,QAAS;AAEpB,cAAM,SAAS,aAAa,MAAM,OAAO;AACzC,YAAI,CAAC,OAAQ;AAGb,cAAM,gBAAgB,kBAAkB,OAAO,UAAU,WAAW;AACpE,YAAI,kBAAkB,kBAAkB;AACtC,yBAAe,KAAK;AAAA,YAClB;AAAA,YACA,MAAM,OAAO;AAAA,YACb,QAAQ,OAAO;AAAA,YACf;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAGA,UAAM,aAAa,oBAAI,IAAY;AACnC,UAAM,eAAe,eAAe,OAAO,CAAC,SAAS;AACnD,YAAM,MAAM,GAAG,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM,OAAO;AAC7D,UAAI,WAAW,IAAI,GAAG,EAAG,QAAO;AAChC,iBAAW,IAAI,GAAG;AAClB,aAAO;AAAA,IACT,CAAC;AAED,WAAO;AAAA,MACL,QAAQ,MAAM;AACZ,mBAAW,EAAE,OAAO,MAAM,QAAQ,QAAQ,KAAK,cAAc;AAE3D,gBAAM,iBAAiB,MAAM,WACzB,IAAI,MAAM,QAAQ,OAClB;AACJ,gBAAM,UAAU,GAAG,cAAc,GAAG,MAAM,OAAO;AAGjD,cAAI,SAAS;AACX,kBAAM,WAAW,KAAK,MAAM,YAAY,KAAK,KAAK,IAAK;AACvD,oBAAQ,OAAO;AAAA,cACb;AAAA,cACA,KAAK,EAAE,MAAM,OAAO;AAAA,cACpB,WAAW;AAAA,cACX,MAAM,EAAE,KAAK,GAAG,QAAQ,IAAI;AAAA,YAC9B,CAAC;AAAA,UACH;AAEA,kBAAQ,OAAO;AAAA,YACb;AAAA,YACA,KAAK,EAAE,MAAM,OAAO;AAAA,YACpB,WAAW;AAAA,YACX,MAAM,EAAE,QAAQ;AAAA,UAClB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-XPBTVJRY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "../chunk-XPBTVJRY.js";
|
|
2
|
+
import {
|
|
3
|
+
meta,
|
|
4
|
+
semantic_vision_default
|
|
5
|
+
} from "../chunk-XELUGF6W.js";
|
|
6
|
+
|
|
7
|
+
// src/eslint-rules/register.ts
|
|
8
|
+
import { registerRuleMeta, registerESLintRule, registerCategory } from "uilint-eslint";
|
|
9
|
+
registerCategory({
|
|
10
|
+
id: "vision",
|
|
11
|
+
name: "Vision Rules",
|
|
12
|
+
description: "AI-powered visual analysis",
|
|
13
|
+
icon: "\u{1F441}\uFE0F",
|
|
14
|
+
defaultEnabled: false
|
|
15
|
+
});
|
|
16
|
+
registerRuleMeta(meta);
|
|
17
|
+
registerESLintRule("semantic-vision", semantic_vision_default);
|
|
18
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/eslint-rules/register.ts"],"sourcesContent":["/**\n * Auto-registration of uilint-vision ESLint rules.\n *\n * Importing this module registers the vision ESLint rules with the\n * uilint-eslint rule registry. This is called by the CLI when vision\n * is installed.\n */\n\nimport { registerRuleMeta, registerESLintRule, registerCategory } from \"uilint-eslint\";\nimport { semanticVisionMeta } from \"./index.js\";\nimport semanticVisionRule from \"./semantic-vision.js\";\n\n// Register the vision category\nregisterCategory({\n id: \"vision\",\n name: \"Vision Rules\",\n description: \"AI-powered visual analysis\",\n icon: \"👁️\",\n defaultEnabled: false,\n});\n\n// Register rule metadata and implementation\nregisterRuleMeta(semanticVisionMeta);\nregisterESLintRule(\"semantic-vision\", semanticVisionRule);\n"],"mappings":";;;;;;;AAQA,SAAS,kBAAkB,oBAAoB,wBAAwB;AAKvE,iBAAiB;AAAA,EACf,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,aAAa;AAAA,EACb,MAAM;AAAA,EACN,gBAAgB;AAClB,CAAC;AAGD,iBAAiB,IAAkB;AACnC,mBAAmB,mBAAmB,uBAAkB;","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
+
import * as uilint_eslint from 'uilint-eslint';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rule: semantic-vision
|
|
6
|
+
*
|
|
7
|
+
* ESLint rule that reports cached vision analysis results for UI elements.
|
|
8
|
+
* Vision analysis is performed by the UILint browser overlay and results are
|
|
9
|
+
* cached in .uilint/screenshots/{timestamp}-{route}.json files.
|
|
10
|
+
*
|
|
11
|
+
* This rule:
|
|
12
|
+
* 1. Finds cached vision analysis results for the current file
|
|
13
|
+
* 2. Reports any issues that match elements in this file (by data-loc)
|
|
14
|
+
* 3. If no cached results exist, silently passes (analysis is triggered by browser)
|
|
15
|
+
*/
|
|
16
|
+
type MessageIds = "visionIssue" | "analysisStale";
|
|
17
|
+
type Options = [
|
|
18
|
+
{
|
|
19
|
+
/** Maximum age of cached results in milliseconds (default: 1 hour) */
|
|
20
|
+
maxAgeMs?: number;
|
|
21
|
+
/** Path to screenshots directory (default: .uilint/screenshots) */
|
|
22
|
+
screenshotsPath?: string;
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Rule metadata - colocated with implementation for maintainability
|
|
27
|
+
*/
|
|
28
|
+
declare const meta: uilint_eslint.RuleMeta;
|
|
29
|
+
declare const _default: _typescript_eslint_utils_ts_eslint.RuleModule<MessageIds, Options, unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { _default as default, meta };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uilint-vision",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.139",
|
|
4
4
|
"description": "Vision-based UI consistency analysis for UILint",
|
|
5
5
|
"author": "Peter Suggate",
|
|
6
6
|
"repository": {
|
|
@@ -28,13 +28,26 @@
|
|
|
28
28
|
"./plugin": {
|
|
29
29
|
"types": "./dist/plugin/index.d.ts",
|
|
30
30
|
"import": "./dist/plugin/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./eslint-rules": {
|
|
33
|
+
"types": "./dist/eslint-rules/index.d.ts",
|
|
34
|
+
"import": "./dist/eslint-rules/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./eslint-rules/register": {
|
|
37
|
+
"types": "./dist/eslint-rules/register.d.ts",
|
|
38
|
+
"import": "./dist/eslint-rules/register.js"
|
|
39
|
+
},
|
|
40
|
+
"./eslint-rules/semantic-vision": {
|
|
41
|
+
"types": "./dist/eslint-rules/semantic-vision.d.ts",
|
|
42
|
+
"import": "./dist/eslint-rules/semantic-vision.js"
|
|
31
43
|
}
|
|
32
44
|
},
|
|
33
45
|
"files": [
|
|
34
46
|
"dist"
|
|
35
47
|
],
|
|
36
48
|
"peerDependencies": {
|
|
37
|
-
"uilint-core": "0.2.
|
|
49
|
+
"uilint-core": "0.2.139",
|
|
50
|
+
"uilint-eslint": "0.2.139"
|
|
38
51
|
},
|
|
39
52
|
"dependencies": {
|
|
40
53
|
"html-to-image": "^1.11.13"
|