uilint-react 0.1.8 → 0.1.10

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.
Files changed (2) hide show
  1. package/dist/index.js +89 -96
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9,7 +9,6 @@ import {
9
9
  useCallback,
10
10
  useRef
11
11
  } from "react";
12
- import { generateStyleGuideFromStyles as generateStyleGuide } from "uilint-core";
13
12
 
14
13
  // src/scanner/dom-scanner.ts
15
14
  import {
@@ -281,6 +280,7 @@ function QuestionPanel() {
281
280
  const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
282
281
  const [isSaving, setIsSaving] = useState(false);
283
282
  const [saveSuccess, setSaveSuccess] = useState(false);
283
+ const [saveError, setSaveError] = useState(null);
284
284
  const questions = generateQuestionsFromIssues(issues);
285
285
  if (questions.length === 0) {
286
286
  return /* @__PURE__ */ jsxs2(
@@ -313,20 +313,24 @@ function QuestionPanel() {
313
313
  console.log("[UILint] Saving preferences:", answers);
314
314
  setIsSaving(true);
315
315
  setSaveSuccess(false);
316
+ setSaveError(null);
316
317
  try {
317
318
  const getResponse = await fetch("/api/uilint/styleguide");
318
- const { exists, content: existingContent } = await getResponse.json();
319
- const updatedContent = buildUpdatedStyleGuide(
320
- exists ? existingContent : null,
321
- answers
322
- );
319
+ const data = await getResponse.json().catch(() => ({}));
320
+ if (!getResponse.ok || !data?.exists || !data?.content) {
321
+ throw new Error(
322
+ data?.error || 'No style guide found. Create ".uilint/styleguide.md" at your workspace root first.'
323
+ );
324
+ }
325
+ const updatedContent = applyAnswersToStyleGuide(data.content, answers);
323
326
  const postResponse = await fetch("/api/uilint/styleguide", {
324
327
  method: "POST",
325
328
  headers: { "Content-Type": "application/json" },
326
329
  body: JSON.stringify({ content: updatedContent })
327
330
  });
328
331
  if (!postResponse.ok) {
329
- throw new Error("Failed to save style guide");
332
+ const err = await postResponse.json().catch(() => ({}));
333
+ throw new Error(err?.error || "Failed to save style guide");
330
334
  }
331
335
  console.log("[UILint] Style guide saved successfully!");
332
336
  setSaveSuccess(true);
@@ -337,6 +341,7 @@ function QuestionPanel() {
337
341
  }, 1500);
338
342
  } catch (error) {
339
343
  console.error("[UILint] Error saving style guide:", error);
344
+ setSaveError(error instanceof Error ? error.message : "Save failed");
340
345
  } finally {
341
346
  setIsSaving(false);
342
347
  }
@@ -496,6 +501,22 @@ function QuestionPanel() {
496
501
  )
497
502
  ]
498
503
  }
504
+ ),
505
+ saveError && /* @__PURE__ */ jsx2(
506
+ "div",
507
+ {
508
+ style: {
509
+ marginTop: "12px",
510
+ padding: "10px",
511
+ borderRadius: "8px",
512
+ backgroundColor: "#7F1D1D",
513
+ border: "1px solid #EF4444",
514
+ color: "#FEE2E2",
515
+ fontSize: "12px",
516
+ lineHeight: 1.4
517
+ },
518
+ children: saveError
519
+ }
499
520
  )
500
521
  ] });
501
522
  }
@@ -572,98 +593,75 @@ function generateQuestionsFromIssues(issues) {
572
593
  }
573
594
  return questions;
574
595
  }
575
- function buildUpdatedStyleGuide(existingContent, answers) {
576
- const lines = [];
577
- lines.push("# UI Style Guide");
578
- lines.push("");
579
- lines.push(
580
- "> Auto-generated by UILint. Edit this file to define your design system."
581
- );
582
- lines.push("");
583
- lines.push("## Colors");
584
- lines.push("");
596
+ function applyAnswersToStyleGuide(existingContent, answers) {
597
+ let content = existingContent;
585
598
  if (answers["primary-color"]) {
586
- lines.push(`- **Primary**: ${answers["primary-color"]}`);
587
- }
588
- const existingColors = extractSection(existingContent, "Colors");
589
- existingColors.filter((line) => !line.includes("**Primary**")).forEach((line) => lines.push(line));
590
- if (!answers["primary-color"] && existingColors.length === 0) {
591
- lines.push("- Define your color palette here");
599
+ content = upsertBulletInSection(
600
+ content,
601
+ "Colors",
602
+ "Primary",
603
+ answers["primary-color"]
604
+ );
592
605
  }
593
- lines.push("");
594
- lines.push("## Typography");
595
- lines.push("");
596
606
  if (answers["font-weights"]) {
597
607
  const weightMap = {
598
608
  "400-600-700": "400 (Regular), 600 (Semibold), 700 (Bold)",
599
609
  "400-500-700": "400 (Regular), 500 (Medium), 700 (Bold)",
600
610
  "300-400-600": "300 (Light), 400 (Regular), 600 (Semibold)"
601
611
  };
602
- lines.push(
603
- `- **Font Weights**: ${weightMap[answers["font-weights"]] || answers["font-weights"]}`
612
+ const value = weightMap[answers["font-weights"]] || answers["font-weights"];
613
+ content = upsertBulletInSection(
614
+ content,
615
+ "Typography",
616
+ "Font Weights",
617
+ value
604
618
  );
605
619
  }
606
- const existingTypography = extractSection(existingContent, "Typography");
607
- existingTypography.filter((line) => !line.includes("**Font Weights**")).forEach((line) => lines.push(line));
608
- if (!answers["font-weights"] && existingTypography.length === 0) {
609
- lines.push("- Define your typography scale here");
610
- }
611
- lines.push("");
612
- lines.push("## Spacing");
613
- lines.push("");
614
620
  if (answers["spacing-scale"]) {
615
621
  const spacingMap = {
616
622
  "4": "4px (4, 8, 12, 16, 20, 24, 32, 40, 48...)",
617
623
  "8": "8px (8, 16, 24, 32, 40, 48, 56, 64...)",
618
624
  tailwind: "Tailwind (4, 8, 12, 16, 20, 24, 32, 40, 48...)"
619
625
  };
620
- lines.push(
621
- `- **Base unit**: ${spacingMap[answers["spacing-scale"]] || answers["spacing-scale"]}`
622
- );
626
+ const value = spacingMap[answers["spacing-scale"]] || answers["spacing-scale"];
627
+ content = upsertBulletInSection(content, "Spacing", "Base unit", value);
623
628
  }
624
- const existingSpacing = extractSection(existingContent, "Spacing");
625
- existingSpacing.filter((line) => !line.includes("**Base unit**")).forEach((line) => lines.push(line));
626
- if (!answers["spacing-scale"] && existingSpacing.length === 0) {
627
- lines.push("- Define your spacing scale here");
628
- }
629
- lines.push("");
630
- const otherSections = ["Border Radius", "Components"];
631
- otherSections.forEach((section) => {
632
- const sectionLines = extractSection(existingContent, section);
633
- if (sectionLines.length > 0) {
634
- lines.push(`## ${section}`);
635
- lines.push("");
636
- sectionLines.forEach((line) => lines.push(line));
637
- lines.push("");
638
- }
639
- });
640
- if (!existingContent?.includes("## Components")) {
641
- lines.push("## Components");
642
- lines.push("");
643
- lines.push("- **Buttons**: Define button styles here");
644
- lines.push("- **Cards**: Define card styles here");
645
- lines.push("- **Inputs**: Define input styles here");
646
- lines.push("");
647
- }
648
- return lines.join("\n");
629
+ return content;
649
630
  }
650
- function extractSection(content, sectionName) {
651
- if (!content) return [];
652
- const lines = content.split("\n");
631
+ function upsertBulletInSection(markdown, sectionName, label, value) {
632
+ const lines = markdown.split("\n");
653
633
  const sectionStart = lines.findIndex(
654
- (line) => line.match(new RegExp(`^##\\s+${sectionName}`, "i"))
634
+ (line) => line.match(new RegExp(`^##\\s+${sectionName}\\s*$`, "i"))
655
635
  );
656
- if (sectionStart === -1) return [];
657
- const result = [];
636
+ if (sectionStart === -1) {
637
+ throw new Error(
638
+ `Style guide is missing section "## ${sectionName}". Add it to your style guide and try again.`
639
+ );
640
+ }
641
+ let sectionEnd = lines.length;
658
642
  for (let i = sectionStart + 1; i < lines.length; i++) {
659
- const line = lines[i];
660
- if (line.startsWith("## ")) break;
661
- if (result.length === 0 && line.trim() === "") continue;
662
- if (line.startsWith("- ")) {
663
- result.push(line);
643
+ if (lines[i].startsWith("## ")) {
644
+ sectionEnd = i;
645
+ break;
646
+ }
647
+ }
648
+ const bulletRe = new RegExp(
649
+ `^-\\s+\\*\\*${escapeRegExp(label)}\\*\\*:\\s+.*$`
650
+ );
651
+ const newBullet = `- **${label}**: ${value}`;
652
+ for (let i = sectionStart + 1; i < sectionEnd; i++) {
653
+ if (bulletRe.test(lines[i])) {
654
+ lines[i] = newBullet;
655
+ return lines.join("\n");
664
656
  }
665
657
  }
666
- return result;
658
+ let insertAt = sectionStart + 1;
659
+ while (insertAt < sectionEnd && lines[insertAt].trim() === "") insertAt++;
660
+ lines.splice(insertAt, 0, newBullet);
661
+ return lines.join("\n");
662
+ }
663
+ function escapeRegExp(s) {
664
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
667
665
  }
668
666
 
669
667
  // src/components/Overlay.tsx
@@ -979,25 +977,17 @@ function UILint({
979
977
  if (!isBrowser()) return;
980
978
  try {
981
979
  const response = await fetch("/api/uilint/styleguide");
982
- const data = await response.json();
983
- setStyleGuideExists(data.exists);
984
- setStyleGuideContent(data.content);
980
+ const data = await response.json().catch(() => ({}));
981
+ if (!response.ok) {
982
+ setStyleGuideExists(false);
983
+ setStyleGuideContent(null);
984
+ return;
985
+ }
986
+ setStyleGuideExists(!!data.exists);
987
+ setStyleGuideContent(data.content ?? null);
985
988
  } catch {
986
989
  setStyleGuideExists(false);
987
- }
988
- }, []);
989
- const saveStyleGuide = useCallback(async (content) => {
990
- if (!isBrowser()) return;
991
- try {
992
- await fetch("/api/uilint/styleguide", {
993
- method: "POST",
994
- headers: { "Content-Type": "application/json" },
995
- body: JSON.stringify({ content })
996
- });
997
- setStyleGuideExists(true);
998
- setStyleGuideContent(content);
999
- } catch (error) {
1000
- console.error("[UILint] Failed to save style guide:", error);
990
+ setStyleGuideContent(null);
1001
991
  }
1002
992
  }, []);
1003
993
  const scan = useCallback(async () => {
@@ -1006,8 +996,11 @@ function UILint({
1006
996
  try {
1007
997
  const snapshot = scanDOM(document.body);
1008
998
  if (!styleGuideContent) {
1009
- const generatedGuide = generateStyleGuide(snapshot.styles);
1010
- await saveStyleGuide(generatedGuide);
999
+ console.error(
1000
+ '[UILint] No style guide found. Create ".uilint/styleguide.md" at your workspace root.'
1001
+ );
1002
+ setIssues([]);
1003
+ return;
1011
1004
  }
1012
1005
  const result = await llmClient.current.analyze(
1013
1006
  snapshot.styles,
@@ -1019,7 +1012,7 @@ function UILint({
1019
1012
  } finally {
1020
1013
  setIsScanning(false);
1021
1014
  }
1022
- }, [styleGuideContent, saveStyleGuide]);
1015
+ }, [styleGuideContent]);
1023
1016
  const clearIssues = useCallback(() => {
1024
1017
  setIssues([]);
1025
1018
  setHighlightedIssue(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uilint-react",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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.8"
37
+ "uilint-core": "^0.1.10"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "^19.0.0",