uilint-react 0.1.31 → 0.1.32
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/{ElementBadges-BSTXYVB7.js → ElementBadges-IQIPOHBN.js} +1 -1
- package/dist/{InspectionPanel-MUUDJ2BK.js → InspectionPanel-AU4SJPQN.js} +2 -2
- package/dist/{LocatorOverlay-XJH5TE2J.js → LocatorOverlay-XCNSUM34.js} +2 -2
- package/dist/{UILintToolbar-RD6BT3LL.js → UILintToolbar-PRX3LYZD.js} +2 -2
- package/dist/{chunk-GLJBGFZK.js → chunk-K7SUEWNV.js} +1 -1
- package/dist/{chunk-XAAIRXB4.js → chunk-NY7Q5DSG.js} +167 -8
- package/dist/{chunk-OV5BPTNA.js → chunk-VBU72FKU.js} +1 -1
- package/dist/{chunk-M3H56XIZ.js → chunk-Y44J7QO6.js} +221 -12
- package/dist/index.d.ts +40 -9
- package/dist/index.js +21 -52
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
buildEditorUrl,
|
|
4
4
|
useUILintContext,
|
|
5
5
|
useUILintStore
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-Y44J7QO6.js";
|
|
7
7
|
|
|
8
8
|
// src/components/ui-lint/InspectionPanel.tsx
|
|
9
9
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
@@ -573,6 +573,7 @@ function ScanSection({ element }) {
|
|
|
573
573
|
const upsertManualScan = useUILintStore(
|
|
574
574
|
(s) => s.upsertManualScan
|
|
575
575
|
);
|
|
576
|
+
const clearManualScan = useUILintStore((s) => s.clearManualScan);
|
|
576
577
|
const includeChildren = manualScan?.includeChildren ?? false;
|
|
577
578
|
const componentName = element.componentStack[0]?.name || element.element.tagName.toLowerCase();
|
|
578
579
|
const componentLine = element.source?.lineNumber;
|
|
@@ -619,8 +620,18 @@ Please update this component to match our styleguide.`;
|
|
|
619
620
|
const cachedFixPrompt = useMemo(() => {
|
|
620
621
|
if (!cachedIssue || cachedIssue.status !== "complete") return null;
|
|
621
622
|
const relativePath = element.source?.fileName || "unknown";
|
|
622
|
-
|
|
623
|
+
const allIssues = [
|
|
624
|
+
...cachedIssue.issues,
|
|
625
|
+
...(cachedIssue.eslintIssues || []).map((e) => ({
|
|
626
|
+
line: e.line,
|
|
627
|
+
message: `[${e.ruleId || "eslint"}] ${e.message}`
|
|
628
|
+
}))
|
|
629
|
+
];
|
|
630
|
+
return generateFixPrompt(allIssues, relativePath);
|
|
623
631
|
}, [cachedIssue, element.source, generateFixPrompt]);
|
|
632
|
+
const eslintIssues = useMemo(() => {
|
|
633
|
+
return cachedIssue?.eslintIssues || [];
|
|
634
|
+
}, [cachedIssue]);
|
|
624
635
|
const handleScan = useCallback(async () => {
|
|
625
636
|
if (!element.source) {
|
|
626
637
|
upsertManualScan(manualKey, {
|
|
@@ -952,6 +963,7 @@ Please update this component to match our styleguide.`;
|
|
|
952
963
|
]
|
|
953
964
|
}
|
|
954
965
|
),
|
|
966
|
+
eslintIssues.length > 0 && /* @__PURE__ */ jsx(ESLintIssuesSection, { issues: eslintIssues }),
|
|
955
967
|
/* @__PURE__ */ jsxs(
|
|
956
968
|
"div",
|
|
957
969
|
{
|
|
@@ -1025,7 +1037,7 @@ Please update this component to match our styleguide.`;
|
|
|
1025
1037
|
/* @__PURE__ */ jsx("div", { style: { textAlign: "center", marginTop: "12px" }, children: /* @__PURE__ */ jsx(
|
|
1026
1038
|
"button",
|
|
1027
1039
|
{
|
|
1028
|
-
onClick:
|
|
1040
|
+
onClick: () => clearManualScan(manualKey),
|
|
1029
1041
|
style: {
|
|
1030
1042
|
padding: "6px 12px",
|
|
1031
1043
|
borderRadius: "6px",
|
|
@@ -1044,7 +1056,7 @@ Please update this component to match our styleguide.`;
|
|
|
1044
1056
|
e.currentTarget.style.borderColor = STYLES.border;
|
|
1045
1057
|
e.currentTarget.style.color = STYLES.textMuted;
|
|
1046
1058
|
},
|
|
1047
|
-
children: "
|
|
1059
|
+
children: "Clear Analysis"
|
|
1048
1060
|
}
|
|
1049
1061
|
) })
|
|
1050
1062
|
] }),
|
|
@@ -1287,9 +1299,7 @@ Please update this component to match our styleguide.`;
|
|
|
1287
1299
|
/* @__PURE__ */ jsx("div", { style: { textAlign: "center", marginTop: "12px" }, children: /* @__PURE__ */ jsx(
|
|
1288
1300
|
"button",
|
|
1289
1301
|
{
|
|
1290
|
-
onClick: () =>
|
|
1291
|
-
handleScan();
|
|
1292
|
-
},
|
|
1302
|
+
onClick: () => clearManualScan(manualKey),
|
|
1293
1303
|
style: {
|
|
1294
1304
|
padding: "6px 12px",
|
|
1295
1305
|
borderRadius: "6px",
|
|
@@ -1308,7 +1318,7 @@ Please update this component to match our styleguide.`;
|
|
|
1308
1318
|
e.currentTarget.style.borderColor = STYLES.border;
|
|
1309
1319
|
e.currentTarget.style.color = STYLES.textMuted;
|
|
1310
1320
|
},
|
|
1311
|
-
children: "
|
|
1321
|
+
children: "Clear Analysis"
|
|
1312
1322
|
}
|
|
1313
1323
|
) })
|
|
1314
1324
|
] })
|
|
@@ -1316,6 +1326,121 @@ Please update this component to match our styleguide.`;
|
|
|
1316
1326
|
}
|
|
1317
1327
|
);
|
|
1318
1328
|
}
|
|
1329
|
+
function ESLintIssuesSection({ issues }) {
|
|
1330
|
+
if (issues.length === 0) return null;
|
|
1331
|
+
return /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
|
|
1332
|
+
/* @__PURE__ */ jsxs(
|
|
1333
|
+
"div",
|
|
1334
|
+
{
|
|
1335
|
+
style: {
|
|
1336
|
+
display: "flex",
|
|
1337
|
+
alignItems: "center",
|
|
1338
|
+
gap: "8px",
|
|
1339
|
+
marginBottom: "8px"
|
|
1340
|
+
},
|
|
1341
|
+
children: [
|
|
1342
|
+
/* @__PURE__ */ jsx(ESLintIcon, {}),
|
|
1343
|
+
/* @__PURE__ */ jsxs(
|
|
1344
|
+
"span",
|
|
1345
|
+
{
|
|
1346
|
+
style: {
|
|
1347
|
+
fontSize: "12px",
|
|
1348
|
+
fontWeight: 600,
|
|
1349
|
+
color: STYLES.text
|
|
1350
|
+
},
|
|
1351
|
+
children: [
|
|
1352
|
+
"ESLint Issues (",
|
|
1353
|
+
issues.length,
|
|
1354
|
+
")"
|
|
1355
|
+
]
|
|
1356
|
+
}
|
|
1357
|
+
)
|
|
1358
|
+
]
|
|
1359
|
+
}
|
|
1360
|
+
),
|
|
1361
|
+
/* @__PURE__ */ jsx(
|
|
1362
|
+
"div",
|
|
1363
|
+
{
|
|
1364
|
+
style: {
|
|
1365
|
+
display: "flex",
|
|
1366
|
+
flexDirection: "column",
|
|
1367
|
+
gap: "6px"
|
|
1368
|
+
},
|
|
1369
|
+
children: issues.map((issue, index) => /* @__PURE__ */ jsx(
|
|
1370
|
+
"div",
|
|
1371
|
+
{
|
|
1372
|
+
style: {
|
|
1373
|
+
padding: "10px 12px",
|
|
1374
|
+
backgroundColor: "rgba(239, 68, 68, 0.1)",
|
|
1375
|
+
border: "1px solid rgba(239, 68, 68, 0.2)",
|
|
1376
|
+
borderRadius: "6px"
|
|
1377
|
+
},
|
|
1378
|
+
children: /* @__PURE__ */ jsxs(
|
|
1379
|
+
"div",
|
|
1380
|
+
{
|
|
1381
|
+
style: {
|
|
1382
|
+
display: "flex",
|
|
1383
|
+
alignItems: "flex-start",
|
|
1384
|
+
gap: "8px"
|
|
1385
|
+
},
|
|
1386
|
+
children: [
|
|
1387
|
+
/* @__PURE__ */ jsx(WarningIcon, {}),
|
|
1388
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
|
|
1389
|
+
/* @__PURE__ */ jsx(
|
|
1390
|
+
"div",
|
|
1391
|
+
{
|
|
1392
|
+
style: {
|
|
1393
|
+
fontSize: "12px",
|
|
1394
|
+
color: STYLES.text,
|
|
1395
|
+
lineHeight: 1.4,
|
|
1396
|
+
marginBottom: "4px"
|
|
1397
|
+
},
|
|
1398
|
+
children: issue.message
|
|
1399
|
+
}
|
|
1400
|
+
),
|
|
1401
|
+
/* @__PURE__ */ jsxs(
|
|
1402
|
+
"div",
|
|
1403
|
+
{
|
|
1404
|
+
style: {
|
|
1405
|
+
display: "flex",
|
|
1406
|
+
alignItems: "center",
|
|
1407
|
+
gap: "12px",
|
|
1408
|
+
fontSize: "10px",
|
|
1409
|
+
color: STYLES.textDim,
|
|
1410
|
+
fontFamily: STYLES.fontMono
|
|
1411
|
+
},
|
|
1412
|
+
children: [
|
|
1413
|
+
issue.ruleId && /* @__PURE__ */ jsx(
|
|
1414
|
+
"span",
|
|
1415
|
+
{
|
|
1416
|
+
style: {
|
|
1417
|
+
padding: "2px 6px",
|
|
1418
|
+
backgroundColor: "rgba(239, 68, 68, 0.15)",
|
|
1419
|
+
borderRadius: "4px",
|
|
1420
|
+
color: "#EF4444"
|
|
1421
|
+
},
|
|
1422
|
+
children: issue.ruleId
|
|
1423
|
+
}
|
|
1424
|
+
),
|
|
1425
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1426
|
+
"Line ",
|
|
1427
|
+
issue.line,
|
|
1428
|
+
issue.column ? `:${issue.column}` : ""
|
|
1429
|
+
] })
|
|
1430
|
+
]
|
|
1431
|
+
}
|
|
1432
|
+
)
|
|
1433
|
+
] })
|
|
1434
|
+
]
|
|
1435
|
+
}
|
|
1436
|
+
)
|
|
1437
|
+
},
|
|
1438
|
+
index
|
|
1439
|
+
))
|
|
1440
|
+
}
|
|
1441
|
+
)
|
|
1442
|
+
] });
|
|
1443
|
+
}
|
|
1319
1444
|
function CheckIconSmall() {
|
|
1320
1445
|
return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
|
|
1321
1446
|
"path",
|
|
@@ -1519,6 +1644,40 @@ function CheckIcon() {
|
|
|
1519
1644
|
}
|
|
1520
1645
|
) });
|
|
1521
1646
|
}
|
|
1647
|
+
function ESLintIcon() {
|
|
1648
|
+
return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
|
|
1649
|
+
"path",
|
|
1650
|
+
{
|
|
1651
|
+
d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5",
|
|
1652
|
+
stroke: "#EF4444",
|
|
1653
|
+
strokeWidth: "2",
|
|
1654
|
+
strokeLinecap: "round",
|
|
1655
|
+
strokeLinejoin: "round"
|
|
1656
|
+
}
|
|
1657
|
+
) });
|
|
1658
|
+
}
|
|
1659
|
+
function WarningIcon() {
|
|
1660
|
+
return /* @__PURE__ */ jsx(
|
|
1661
|
+
"svg",
|
|
1662
|
+
{
|
|
1663
|
+
width: "14",
|
|
1664
|
+
height: "14",
|
|
1665
|
+
viewBox: "0 0 24 24",
|
|
1666
|
+
fill: "none",
|
|
1667
|
+
style: { flexShrink: 0, marginTop: "1px" },
|
|
1668
|
+
children: /* @__PURE__ */ jsx(
|
|
1669
|
+
"path",
|
|
1670
|
+
{
|
|
1671
|
+
d: "M12 9v4M12 17h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z",
|
|
1672
|
+
stroke: "#EF4444",
|
|
1673
|
+
strokeWidth: "2",
|
|
1674
|
+
strokeLinecap: "round",
|
|
1675
|
+
strokeLinejoin: "round"
|
|
1676
|
+
}
|
|
1677
|
+
)
|
|
1678
|
+
}
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1522
1681
|
|
|
1523
1682
|
export {
|
|
1524
1683
|
fetchSource,
|
|
@@ -270,17 +270,26 @@ function getDataLocFromId(id) {
|
|
|
270
270
|
}
|
|
271
271
|
return null;
|
|
272
272
|
}
|
|
273
|
-
async function scanFileForIssues(sourceFile) {
|
|
273
|
+
async function scanFileForIssues(sourceFile, store) {
|
|
274
274
|
if (sourceFile.elements.length === 0) {
|
|
275
|
-
return { issues: [] };
|
|
275
|
+
return { issues: [], eslintIssues: [] };
|
|
276
276
|
}
|
|
277
277
|
const filePath = sourceFile.path;
|
|
278
|
+
let eslintIssues = [];
|
|
279
|
+
let llmIssues = [];
|
|
280
|
+
if (store.wsConnected && store.wsConnection) {
|
|
281
|
+
try {
|
|
282
|
+
eslintIssues = await store.requestFileLint(filePath);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.warn("[UILint] WebSocket lint failed, will use HTTP:", err);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
278
287
|
try {
|
|
279
288
|
const sourceResponse = await fetch(
|
|
280
289
|
`/api/.uilint/source?path=${encodeURIComponent(filePath)}`
|
|
281
290
|
);
|
|
282
291
|
if (!sourceResponse.ok) {
|
|
283
|
-
return { issues: [], error: true };
|
|
292
|
+
return { issues: [], eslintIssues, error: true };
|
|
284
293
|
}
|
|
285
294
|
const sourceData = await sourceResponse.json();
|
|
286
295
|
const dataLocs = [];
|
|
@@ -300,15 +309,16 @@ async function scanFileForIssues(sourceFile) {
|
|
|
300
309
|
})
|
|
301
310
|
});
|
|
302
311
|
if (!analyzeResponse.ok) {
|
|
303
|
-
return { issues: [], error: true };
|
|
312
|
+
return { issues: [], eslintIssues, error: true };
|
|
304
313
|
}
|
|
305
314
|
const result = await analyzeResponse.json();
|
|
306
|
-
|
|
315
|
+
llmIssues = result.issues || [];
|
|
307
316
|
} catch {
|
|
308
|
-
return { issues: [], error: true };
|
|
317
|
+
return { issues: [], eslintIssues, error: true };
|
|
309
318
|
}
|
|
319
|
+
return { issues: llmIssues, eslintIssues };
|
|
310
320
|
}
|
|
311
|
-
function distributeIssuesToElements(issues, elements, updateElementIssue, hasError) {
|
|
321
|
+
function distributeIssuesToElements(issues, eslintIssues, elements, updateElementIssue, hasError) {
|
|
312
322
|
const dataLocToElementId = /* @__PURE__ */ new Map();
|
|
313
323
|
for (const el of elements) {
|
|
314
324
|
const dataLoc = getDataLocFromId(el.id);
|
|
@@ -327,15 +337,32 @@ function distributeIssuesToElements(issues, elements, updateElementIssue, hasErr
|
|
|
327
337
|
}
|
|
328
338
|
}
|
|
329
339
|
}
|
|
340
|
+
const eslintByElement = /* @__PURE__ */ new Map();
|
|
341
|
+
for (const issue of eslintIssues) {
|
|
342
|
+
if (issue.dataLoc) {
|
|
343
|
+
const elementId = dataLocToElementId.get(issue.dataLoc);
|
|
344
|
+
if (elementId) {
|
|
345
|
+
const existing = eslintByElement.get(elementId) || [];
|
|
346
|
+
existing.push(issue);
|
|
347
|
+
eslintByElement.set(elementId, existing);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
330
351
|
for (const el of elements) {
|
|
331
352
|
const elementIssues = issuesByElement.get(el.id) || [];
|
|
353
|
+
const elementEslintIssues = eslintByElement.get(el.id) || [];
|
|
332
354
|
updateElementIssue(el.id, {
|
|
333
355
|
elementId: el.id,
|
|
334
356
|
issues: elementIssues,
|
|
357
|
+
eslintIssues: elementEslintIssues,
|
|
335
358
|
status: hasError ? "error" : "complete"
|
|
336
359
|
});
|
|
337
360
|
}
|
|
338
361
|
}
|
|
362
|
+
var DEFAULT_WS_URL = "ws://localhost:9234";
|
|
363
|
+
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
364
|
+
var RECONNECT_BASE_DELAY = 1e3;
|
|
365
|
+
var pendingRequests = /* @__PURE__ */ new Map();
|
|
339
366
|
var useUILintStore = create()((set, get) => ({
|
|
340
367
|
// ============ Settings ============
|
|
341
368
|
settings: DEFAULT_SETTINGS,
|
|
@@ -492,9 +519,10 @@ var useUILintStore = create()((set, get) => ({
|
|
|
492
519
|
});
|
|
493
520
|
}
|
|
494
521
|
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
495
|
-
const { issues, error } = await scanFileForIssues(sourceFile);
|
|
522
|
+
const { issues, eslintIssues, error } = await scanFileForIssues(sourceFile, get());
|
|
496
523
|
distributeIssuesToElements(
|
|
497
524
|
issues,
|
|
525
|
+
eslintIssues,
|
|
498
526
|
sourceFile.elements,
|
|
499
527
|
get().updateElementIssue,
|
|
500
528
|
error ?? false
|
|
@@ -510,6 +538,187 @@ var useUILintStore = create()((set, get) => ({
|
|
|
510
538
|
currentIndex: elements.length
|
|
511
539
|
}
|
|
512
540
|
});
|
|
541
|
+
},
|
|
542
|
+
// ============ WebSocket ============
|
|
543
|
+
wsConnection: null,
|
|
544
|
+
wsConnected: false,
|
|
545
|
+
wsUrl: DEFAULT_WS_URL,
|
|
546
|
+
wsReconnectAttempts: 0,
|
|
547
|
+
eslintIssuesCache: /* @__PURE__ */ new Map(),
|
|
548
|
+
wsProgressPhase: /* @__PURE__ */ new Map(),
|
|
549
|
+
connectWebSocket: (url) => {
|
|
550
|
+
const targetUrl = url || get().wsUrl;
|
|
551
|
+
const existing = get().wsConnection;
|
|
552
|
+
if (existing && existing.readyState !== WebSocket.CLOSED) {
|
|
553
|
+
existing.close();
|
|
554
|
+
}
|
|
555
|
+
if (typeof WebSocket === "undefined") {
|
|
556
|
+
console.warn("[UILint] WebSocket not available in this environment");
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
const ws = new WebSocket(targetUrl);
|
|
561
|
+
ws.onopen = () => {
|
|
562
|
+
console.log("[UILint] WebSocket connected to", targetUrl);
|
|
563
|
+
set({
|
|
564
|
+
wsConnected: true,
|
|
565
|
+
wsReconnectAttempts: 0,
|
|
566
|
+
wsUrl: targetUrl
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
ws.onclose = () => {
|
|
570
|
+
console.log("[UILint] WebSocket disconnected");
|
|
571
|
+
set({ wsConnected: false, wsConnection: null });
|
|
572
|
+
const attempts = get().wsReconnectAttempts;
|
|
573
|
+
if (attempts < MAX_RECONNECT_ATTEMPTS) {
|
|
574
|
+
const delay = RECONNECT_BASE_DELAY * Math.pow(2, attempts);
|
|
575
|
+
console.log(`[UILint] Reconnecting in ${delay}ms (attempt ${attempts + 1})`);
|
|
576
|
+
setTimeout(() => {
|
|
577
|
+
set({ wsReconnectAttempts: attempts + 1 });
|
|
578
|
+
get()._reconnectWebSocket();
|
|
579
|
+
}, delay);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
ws.onerror = (error) => {
|
|
583
|
+
console.error("[UILint] WebSocket error:", error);
|
|
584
|
+
};
|
|
585
|
+
ws.onmessage = (event) => {
|
|
586
|
+
try {
|
|
587
|
+
const data = JSON.parse(event.data);
|
|
588
|
+
get()._handleWsMessage(data);
|
|
589
|
+
} catch (err) {
|
|
590
|
+
console.error("[UILint] Failed to parse WebSocket message:", err);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
set({ wsConnection: ws, wsUrl: targetUrl });
|
|
594
|
+
} catch (err) {
|
|
595
|
+
console.error("[UILint] Failed to create WebSocket:", err);
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
disconnectWebSocket: () => {
|
|
599
|
+
const ws = get().wsConnection;
|
|
600
|
+
if (ws) {
|
|
601
|
+
ws.close();
|
|
602
|
+
set({ wsConnection: null, wsConnected: false, wsReconnectAttempts: MAX_RECONNECT_ATTEMPTS });
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
requestFileLint: async (filePath) => {
|
|
606
|
+
const { wsConnection, wsConnected, eslintIssuesCache } = get();
|
|
607
|
+
const cached = eslintIssuesCache.get(filePath);
|
|
608
|
+
if (cached) {
|
|
609
|
+
return cached;
|
|
610
|
+
}
|
|
611
|
+
if (!wsConnected || !wsConnection) {
|
|
612
|
+
console.log("[UILint] WebSocket not connected, using HTTP fallback");
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
return new Promise((resolve, reject) => {
|
|
616
|
+
const requestKey = `file:${filePath}`;
|
|
617
|
+
pendingRequests.set(requestKey, { resolve, reject });
|
|
618
|
+
const message = { type: "lint:file", filePath };
|
|
619
|
+
wsConnection.send(JSON.stringify(message));
|
|
620
|
+
setTimeout(() => {
|
|
621
|
+
if (pendingRequests.has(requestKey)) {
|
|
622
|
+
pendingRequests.delete(requestKey);
|
|
623
|
+
reject(new Error("Request timed out"));
|
|
624
|
+
}
|
|
625
|
+
}, 3e4);
|
|
626
|
+
});
|
|
627
|
+
},
|
|
628
|
+
requestElementLint: async (filePath, dataLoc) => {
|
|
629
|
+
const { wsConnection, wsConnected } = get();
|
|
630
|
+
if (!wsConnected || !wsConnection) {
|
|
631
|
+
console.log("[UILint] WebSocket not connected, using HTTP fallback");
|
|
632
|
+
return [];
|
|
633
|
+
}
|
|
634
|
+
return new Promise((resolve, reject) => {
|
|
635
|
+
const requestKey = `element:${filePath}:${dataLoc}`;
|
|
636
|
+
pendingRequests.set(requestKey, { resolve, reject });
|
|
637
|
+
const message = { type: "lint:element", filePath, dataLoc };
|
|
638
|
+
wsConnection.send(JSON.stringify(message));
|
|
639
|
+
setTimeout(() => {
|
|
640
|
+
if (pendingRequests.has(requestKey)) {
|
|
641
|
+
pendingRequests.delete(requestKey);
|
|
642
|
+
reject(new Error("Request timed out"));
|
|
643
|
+
}
|
|
644
|
+
}, 3e4);
|
|
645
|
+
});
|
|
646
|
+
},
|
|
647
|
+
subscribeToFile: (filePath) => {
|
|
648
|
+
const { wsConnection, wsConnected } = get();
|
|
649
|
+
if (!wsConnected || !wsConnection) return;
|
|
650
|
+
const message = { type: "subscribe:file", filePath };
|
|
651
|
+
wsConnection.send(JSON.stringify(message));
|
|
652
|
+
},
|
|
653
|
+
invalidateCache: (filePath) => {
|
|
654
|
+
const { wsConnection, wsConnected } = get();
|
|
655
|
+
if (filePath) {
|
|
656
|
+
set((state) => {
|
|
657
|
+
const next = new Map(state.eslintIssuesCache);
|
|
658
|
+
next.delete(filePath);
|
|
659
|
+
return { eslintIssuesCache: next };
|
|
660
|
+
});
|
|
661
|
+
} else {
|
|
662
|
+
set({ eslintIssuesCache: /* @__PURE__ */ new Map() });
|
|
663
|
+
}
|
|
664
|
+
if (wsConnected && wsConnection) {
|
|
665
|
+
const message = { type: "cache:invalidate", filePath };
|
|
666
|
+
wsConnection.send(JSON.stringify(message));
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
_handleWsMessage: (data) => {
|
|
670
|
+
switch (data.type) {
|
|
671
|
+
case "lint:result": {
|
|
672
|
+
const { filePath, issues } = data;
|
|
673
|
+
set((state) => {
|
|
674
|
+
const next = new Map(state.eslintIssuesCache);
|
|
675
|
+
next.set(filePath, issues);
|
|
676
|
+
return { eslintIssuesCache: next };
|
|
677
|
+
});
|
|
678
|
+
set((state) => {
|
|
679
|
+
const next = new Map(state.wsProgressPhase);
|
|
680
|
+
next.delete(filePath);
|
|
681
|
+
return { wsProgressPhase: next };
|
|
682
|
+
});
|
|
683
|
+
const fileKey = `file:${filePath}`;
|
|
684
|
+
const pending = pendingRequests.get(fileKey);
|
|
685
|
+
if (pending) {
|
|
686
|
+
pending.resolve(issues);
|
|
687
|
+
pendingRequests.delete(fileKey);
|
|
688
|
+
}
|
|
689
|
+
for (const [key, req] of pendingRequests.entries()) {
|
|
690
|
+
if (key.startsWith(`element:${filePath}:`)) {
|
|
691
|
+
const dataLoc = key.split(":").slice(2).join(":");
|
|
692
|
+
const filtered = issues.filter((i) => i.dataLoc === dataLoc);
|
|
693
|
+
req.resolve(filtered);
|
|
694
|
+
pendingRequests.delete(key);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
case "lint:progress": {
|
|
700
|
+
const { filePath, phase } = data;
|
|
701
|
+
set((state) => {
|
|
702
|
+
const next = new Map(state.wsProgressPhase);
|
|
703
|
+
next.set(filePath, phase);
|
|
704
|
+
return { wsProgressPhase: next };
|
|
705
|
+
});
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
case "file:changed": {
|
|
709
|
+
const { filePath } = data;
|
|
710
|
+
set((state) => {
|
|
711
|
+
const next = new Map(state.eslintIssuesCache);
|
|
712
|
+
next.delete(filePath);
|
|
713
|
+
return { eslintIssuesCache: next };
|
|
714
|
+
});
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
_reconnectWebSocket: () => {
|
|
720
|
+
const { wsUrl } = get();
|
|
721
|
+
get().connectWebSocket(wsUrl);
|
|
513
722
|
}
|
|
514
723
|
}));
|
|
515
724
|
function useEffectiveLocatorTarget() {
|
|
@@ -796,10 +1005,10 @@ function UILintUI() {
|
|
|
796
1005
|
const [components, setComponents] = useState(null);
|
|
797
1006
|
useEffect(() => {
|
|
798
1007
|
Promise.all([
|
|
799
|
-
import("./UILintToolbar-
|
|
800
|
-
import("./InspectionPanel-
|
|
801
|
-
import("./LocatorOverlay-
|
|
802
|
-
import("./ElementBadges-
|
|
1008
|
+
import("./UILintToolbar-PRX3LYZD.js"),
|
|
1009
|
+
import("./InspectionPanel-AU4SJPQN.js"),
|
|
1010
|
+
import("./LocatorOverlay-XCNSUM34.js"),
|
|
1011
|
+
import("./ElementBadges-IQIPOHBN.js")
|
|
803
1012
|
]).then(([toolbar, panel, locator, badges]) => {
|
|
804
1013
|
setComponents({
|
|
805
1014
|
Toolbar: toolbar.UILintToolbar,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React$1 from 'react';
|
|
3
|
-
import { GroupedSnapshot, Violation, DOMSnapshot
|
|
3
|
+
import { GroupedSnapshot, Violation, DOMSnapshot } from 'uilint-core';
|
|
4
4
|
export { AnalysisResult, ConsistencyResult, DOMSnapshot, ElementRole, ElementSnapshot, ExtractedStyles, GroupedSnapshot, SerializedStyles, StyleGuide, StyleSnapshot, UILintIssue, Violation, ViolationCategory, ViolationSeverity, createEmptyStyleGuide, createStyleSummary, extractStylesFromDOM, generateStyleGuideFromStyles as generateStyleGuide, mergeStyleGuides, parseStyleGuide, serializeStyles } from 'uilint-core';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -69,12 +69,30 @@ interface ScanIssue {
|
|
|
69
69
|
/** data-loc value to match to DOM element (format: path:line:column) */
|
|
70
70
|
dataLoc?: string;
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* ESLint issue from WebSocket server (uilint serve)
|
|
74
|
+
*/
|
|
75
|
+
interface ESLintIssue {
|
|
76
|
+
/** Line number in source file */
|
|
77
|
+
line: number;
|
|
78
|
+
/** Column number */
|
|
79
|
+
column?: number;
|
|
80
|
+
/** Issue description */
|
|
81
|
+
message: string;
|
|
82
|
+
/** ESLint rule ID (e.g., "uilint/semantic", "uilint/no-arbitrary-tailwind") */
|
|
83
|
+
ruleId?: string;
|
|
84
|
+
/** data-loc value to match to DOM element */
|
|
85
|
+
dataLoc?: string;
|
|
86
|
+
}
|
|
72
87
|
/**
|
|
73
88
|
* Cached issue data for a scanned element
|
|
74
89
|
*/
|
|
75
90
|
interface ElementIssue {
|
|
76
91
|
elementId: string;
|
|
92
|
+
/** LLM-based issues from styleguide analysis */
|
|
77
93
|
issues: ScanIssue[];
|
|
94
|
+
/** ESLint rule violations from uilint-eslint */
|
|
95
|
+
eslintIssues?: ESLintIssue[];
|
|
78
96
|
status: "pending" | "scanning" | "complete" | "error";
|
|
79
97
|
}
|
|
80
98
|
/**
|
|
@@ -363,9 +381,13 @@ declare function isNode(): boolean;
|
|
|
363
381
|
|
|
364
382
|
/**
|
|
365
383
|
* LLM client for browser environment
|
|
366
|
-
*
|
|
384
|
+
* Wraps API calls to the dev server analyze route (browser environment)
|
|
367
385
|
*/
|
|
368
|
-
|
|
386
|
+
type UILintScanIssue = {
|
|
387
|
+
line?: number;
|
|
388
|
+
message: string;
|
|
389
|
+
dataLoc?: string;
|
|
390
|
+
};
|
|
369
391
|
interface LLMClientOptions {
|
|
370
392
|
apiEndpoint?: string;
|
|
371
393
|
model?: string;
|
|
@@ -378,13 +400,22 @@ declare class LLMClient {
|
|
|
378
400
|
private model;
|
|
379
401
|
constructor(options?: LLMClientOptions);
|
|
380
402
|
/**
|
|
381
|
-
*
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
* Generates a style guide from detected styles
|
|
403
|
+
* Analyze a source file/snippet and return issues.
|
|
404
|
+
*
|
|
405
|
+
* NOTE: This matches the (simplified) `/api/.uilint/analyze` route which is
|
|
406
|
+
* now source-only (no styleSummary / styleguide generation).
|
|
386
407
|
*/
|
|
387
|
-
|
|
408
|
+
analyzeSource(input: {
|
|
409
|
+
sourceCode: string;
|
|
410
|
+
filePath?: string;
|
|
411
|
+
styleGuide?: string | null;
|
|
412
|
+
componentName?: string;
|
|
413
|
+
componentLine?: number;
|
|
414
|
+
includeChildren?: boolean;
|
|
415
|
+
dataLocs?: string[];
|
|
416
|
+
}): Promise<{
|
|
417
|
+
issues: UILintScanIssue[];
|
|
418
|
+
}>;
|
|
388
419
|
}
|
|
389
420
|
|
|
390
421
|
export { type CachedSource, type ComponentInfo, ConsistencyHighlighter, DATA_UILINT_ID, DEFAULT_SETTINGS, FILE_COLORS, type InspectedElement, InspectionPanel, LLMClient, LocatorOverlay, type LocatorTarget, type ScannedElement, type SourceApiResponse, type SourceFile, type SourceLocation, type UILintContextValue, UILintProvider, type UILintProviderProps, type UILintSettings, UILintToolbar, buildEditorUrl, cleanupDataAttributes, cleanupDataElements, clearSourceCache, createSnapshot, fetchSource, fetchSourceWithContext, getCachedSource, getComponentStack, getDebugOwner, getDebugSource, getDisplayName, getElementById, getElementBySnapshotId, getFiberFromElement, groupBySourceFile, isBrowser, isJSDOM, isNode, isNodeModulesPath, prefetchSources, scanDOM, scanDOMForSources, updateElementRects, useUILintContext };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
UILintToolbar
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-K7SUEWNV.js";
|
|
5
5
|
import {
|
|
6
6
|
InspectionPanel,
|
|
7
7
|
clearSourceCache,
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
fetchSourceWithContext,
|
|
10
10
|
getCachedSource,
|
|
11
11
|
prefetchSources
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-NY7Q5DSG.js";
|
|
13
13
|
import {
|
|
14
14
|
LocatorOverlay
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-VBU72FKU.js";
|
|
16
16
|
import {
|
|
17
17
|
DATA_UILINT_ID,
|
|
18
18
|
DEFAULT_SETTINGS,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
scanDOMForSources,
|
|
32
32
|
updateElementRects,
|
|
33
33
|
useUILintContext
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-Y44J7QO6.js";
|
|
35
35
|
|
|
36
36
|
// src/consistency/snapshot.ts
|
|
37
37
|
var DATA_ELEMENTS_ATTR = "data-elements";
|
|
@@ -439,16 +439,11 @@ function isNode() {
|
|
|
439
439
|
import {
|
|
440
440
|
extractStylesFromDOM as extractStylesFromDOM2,
|
|
441
441
|
serializeStyles as serializeStyles2,
|
|
442
|
-
createStyleSummary as
|
|
442
|
+
createStyleSummary as createStyleSummary2
|
|
443
443
|
} from "uilint-core";
|
|
444
444
|
|
|
445
445
|
// src/analyzer/llm-client.ts
|
|
446
|
-
import {
|
|
447
|
-
createStyleSummary as createStyleSummary2,
|
|
448
|
-
buildAnalysisPrompt,
|
|
449
|
-
buildStyleGuidePrompt,
|
|
450
|
-
UILINT_DEFAULT_OLLAMA_MODEL
|
|
451
|
-
} from "uilint-core";
|
|
446
|
+
import { UILINT_DEFAULT_OLLAMA_MODEL } from "uilint-core";
|
|
452
447
|
var DEFAULT_API_ENDPOINT = "/api/.uilint/analyze";
|
|
453
448
|
var LLMClient = class {
|
|
454
449
|
apiEndpoint;
|
|
@@ -458,18 +453,24 @@ var LLMClient = class {
|
|
|
458
453
|
this.model = options.model || UILINT_DEFAULT_OLLAMA_MODEL;
|
|
459
454
|
}
|
|
460
455
|
/**
|
|
461
|
-
*
|
|
456
|
+
* Analyze a source file/snippet and return issues.
|
|
457
|
+
*
|
|
458
|
+
* NOTE: This matches the (simplified) `/api/.uilint/analyze` route which is
|
|
459
|
+
* now source-only (no styleSummary / styleguide generation).
|
|
462
460
|
*/
|
|
463
|
-
async
|
|
464
|
-
const startTime = Date.now();
|
|
465
|
-
const styleSummary = createStyleSummary2(styles);
|
|
461
|
+
async analyzeSource(input) {
|
|
466
462
|
try {
|
|
467
463
|
const response = await fetch(this.apiEndpoint, {
|
|
468
464
|
method: "POST",
|
|
469
465
|
headers: { "Content-Type": "application/json" },
|
|
470
466
|
body: JSON.stringify({
|
|
471
|
-
|
|
472
|
-
|
|
467
|
+
sourceCode: input.sourceCode,
|
|
468
|
+
filePath: input.filePath,
|
|
469
|
+
styleGuide: input.styleGuide ?? void 0,
|
|
470
|
+
componentName: input.componentName,
|
|
471
|
+
componentLine: input.componentLine,
|
|
472
|
+
includeChildren: input.includeChildren,
|
|
473
|
+
dataLocs: input.dataLocs,
|
|
473
474
|
model: this.model
|
|
474
475
|
})
|
|
475
476
|
});
|
|
@@ -477,42 +478,10 @@ var LLMClient = class {
|
|
|
477
478
|
throw new Error(`API request failed: ${response.status}`);
|
|
478
479
|
}
|
|
479
480
|
const data = await response.json();
|
|
480
|
-
return {
|
|
481
|
-
issues: data.issues || [],
|
|
482
|
-
suggestedStyleGuide: data.suggestedStyleGuide,
|
|
483
|
-
analysisTime: Date.now() - startTime
|
|
484
|
-
};
|
|
481
|
+
return { issues: data.issues || [] };
|
|
485
482
|
} catch (error) {
|
|
486
483
|
console.error("[UILint] Analysis failed:", error);
|
|
487
|
-
return {
|
|
488
|
-
issues: [],
|
|
489
|
-
analysisTime: Date.now() - startTime
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Generates a style guide from detected styles
|
|
495
|
-
*/
|
|
496
|
-
async generateStyleGuide(styles) {
|
|
497
|
-
const styleSummary = createStyleSummary2(styles);
|
|
498
|
-
try {
|
|
499
|
-
const response = await fetch(this.apiEndpoint, {
|
|
500
|
-
method: "POST",
|
|
501
|
-
headers: { "Content-Type": "application/json" },
|
|
502
|
-
body: JSON.stringify({
|
|
503
|
-
styleSummary,
|
|
504
|
-
generateGuide: true,
|
|
505
|
-
model: this.model
|
|
506
|
-
})
|
|
507
|
-
});
|
|
508
|
-
if (!response.ok) {
|
|
509
|
-
throw new Error(`API request failed: ${response.status}`);
|
|
510
|
-
}
|
|
511
|
-
const data = await response.json();
|
|
512
|
-
return data.styleGuide || null;
|
|
513
|
-
} catch (error) {
|
|
514
|
-
console.error("[UILint] Style guide generation failed:", error);
|
|
515
|
-
return null;
|
|
484
|
+
return { issues: [] };
|
|
516
485
|
}
|
|
517
486
|
}
|
|
518
487
|
};
|
|
@@ -537,7 +506,7 @@ export {
|
|
|
537
506
|
clearSourceCache,
|
|
538
507
|
createEmptyStyleGuide,
|
|
539
508
|
createSnapshot,
|
|
540
|
-
|
|
509
|
+
createStyleSummary2 as createStyleSummary,
|
|
541
510
|
extractStylesFromDOM2 as extractStylesFromDOM,
|
|
542
511
|
fetchSource,
|
|
543
512
|
fetchSourceWithContext,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uilint-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "React component for AI-powered UI consistency checking",
|
|
5
5
|
"author": "Peter Suggate",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"node": ">=20.0.0"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"uilint-core": "^0.1.
|
|
37
|
+
"uilint-core": "^0.1.32",
|
|
38
38
|
"zustand": "^5.0.5"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|