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.
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
  import {
4
4
  useUILintContext
5
- } from "./chunk-M3H56XIZ.js";
5
+ } from "./chunk-Y44J7QO6.js";
6
6
 
7
7
  // src/components/ui-lint/ElementBadges.tsx
8
8
  import React, { useState, useEffect, useCallback, useMemo } from "react";
@@ -2,8 +2,8 @@
2
2
  "use client";
3
3
  import {
4
4
  InspectionPanel
5
- } from "./chunk-XAAIRXB4.js";
6
- import "./chunk-M3H56XIZ.js";
5
+ } from "./chunk-NY7Q5DSG.js";
6
+ import "./chunk-Y44J7QO6.js";
7
7
  export {
8
8
  InspectionPanel
9
9
  };
@@ -3,8 +3,8 @@
3
3
  import {
4
4
  InspectedElementHighlight,
5
5
  LocatorOverlay
6
- } from "./chunk-OV5BPTNA.js";
7
- import "./chunk-M3H56XIZ.js";
6
+ } from "./chunk-VBU72FKU.js";
7
+ import "./chunk-Y44J7QO6.js";
8
8
  export {
9
9
  InspectedElementHighlight,
10
10
  LocatorOverlay
@@ -2,8 +2,8 @@
2
2
  "use client";
3
3
  import {
4
4
  UILintToolbar
5
- } from "./chunk-GLJBGFZK.js";
6
- import "./chunk-M3H56XIZ.js";
5
+ } from "./chunk-K7SUEWNV.js";
6
+ import "./chunk-Y44J7QO6.js";
7
7
  export {
8
8
  UILintToolbar
9
9
  };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  useUILintContext
4
- } from "./chunk-M3H56XIZ.js";
4
+ } from "./chunk-Y44J7QO6.js";
5
5
 
6
6
  // src/components/ui-lint/UILintToolbar.tsx
7
7
  import { useState, useRef, useEffect } from "react";
@@ -3,7 +3,7 @@ import {
3
3
  buildEditorUrl,
4
4
  useUILintContext,
5
5
  useUILintStore
6
- } from "./chunk-M3H56XIZ.js";
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
- return generateFixPrompt(cachedIssue.issues, relativePath);
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: handleScan,
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: "Rescan"
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: "Scan Again"
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,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  useUILintContext
4
- } from "./chunk-M3H56XIZ.js";
4
+ } from "./chunk-Y44J7QO6.js";
5
5
 
6
6
  // src/components/ui-lint/LocatorOverlay.tsx
7
7
  import { useState, useEffect, useMemo } from "react";
@@ -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
- return { issues: result.issues || [] };
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-RD6BT3LL.js"),
800
- import("./InspectionPanel-MUUDJ2BK.js"),
801
- import("./LocatorOverlay-XJH5TE2J.js"),
802
- import("./ElementBadges-BSTXYVB7.js")
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, ExtractedStyles, AnalysisResult } from 'uilint-core';
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
- * Uses uilint-core for prompts and wraps API calls
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
- * Analyzes extracted styles and returns issues
382
- */
383
- analyze(styles: ExtractedStyles, styleGuide: string | null): Promise<AnalysisResult>;
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
- generateStyleGuide(styles: ExtractedStyles): Promise<string | null>;
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-GLJBGFZK.js";
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-XAAIRXB4.js";
12
+ } from "./chunk-NY7Q5DSG.js";
13
13
  import {
14
14
  LocatorOverlay
15
- } from "./chunk-OV5BPTNA.js";
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-M3H56XIZ.js";
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 createStyleSummary3
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
- * Analyzes extracted styles and returns issues
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 analyze(styles, styleGuide) {
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
- styleSummary,
472
- styleGuide,
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
- createStyleSummary3 as createStyleSummary,
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.31",
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.31",
37
+ "uilint-core": "^0.1.32",
38
38
  "zustand": "^5.0.5"
39
39
  },
40
40
  "peerDependencies": {