Skip to content

Comments

refactor: enhance React Scan detection and removal logic#139

Open
aidenybai wants to merge 4 commits intomainfrom
scan
Open

refactor: enhance React Scan detection and removal logic#139
aidenybai wants to merge 4 commits intomainfrom
scan

Conversation

@aidenybai
Copy link
Owner

@aidenybai aidenybai commented Feb 2, 2026

  • Updated detection logic for React Scan in various file types, including package.json and layout files.
  • Improved the removal process for React Scan scripts in Next.js, Vite, and Webpack configurations.
  • Added comprehensive tests to ensure accurate detection and removal functionality across different scenarios.
  • Refactored related utility functions for better maintainability and performance.

Note

Medium Risk
Touches CLI migration logic that edits project files and installs/uninstalls packages, so mistakes in detection/regex transforms could break user configurations. Changes are mitigated by diff previews, confirmations, and broad unit test coverage.

Overview
Adds a new grab migrate CLI command that guides users through migrating from react-scan to react-grab, including framework/router preflight checks, diff previews, confirmation prompts, and install/uninstall steps.

Improves detection and cleanup utilities by introducing detectReactScan, expanding config-file scanning across common entry/layout files and extensions, and adding previewReactScanRemoval transforms to strip react-scan scripts/imports for Next.js/Vite/Webpack/TanStack. Corresponding test coverage is added/expanded for both detection and removal behavior.

Updates the design-system playground to support toolbar mode/scan UI states (e.g., mode selector, recording/copy controls), and adds a PerformanceTest component in the gym dashboard to generate intentional UI jank for performance testing.

Written by Cursor Bugbot for commit 13fa582. This will update automatically on new commits. Configure here.


Summary by cubic

Adds Scan mode to visualize and record React component renders, plus a migrate CLI to detect and safely remove react-scan across frameworks with guarded uninstall flows.

  • Bug Fixes

    • Migrate CLI: run transforms before package ops; preview matches apply; allow package-only uninstall when no target files; warn when files remain (incl. hasReactGrab path); uninstall only after cleanup or explicit package-only choice.
    • Detection/removal: broaden search to .ts/.js/.tsx/.jsx and HTML entries (layout/_app/_document/index/instrumentation); fix dynamic import/JSX conditional regex; preserve chained imports; chain all pattern replacements; remove CDN and standard HTML <script> tags (inline/external/self-closing) in Next/Vite/Webpack/HTML.
  • Migration

    • If you use ReactGrabRenderer directly: replace enabled/onToggleEnabled with toolbarMode/onToolbarModeChange, and isActive/isCommentMode with selectionMode. Add onStartRecording/onStopRecording/onCopyRecording for Scan mode. ToolbarState now uses mode instead of enabled.
    • Run: npx react-grab migrate to detect and remove react-scan and its scripts.

Written for commit 13fa582. Summary will update on new commits.

@vercel
Copy link

vercel bot commented Feb 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-grab-website Ready Ready Preview, Comment Feb 7, 2026 11:17pm

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 2, 2026

Open in StackBlitz

@react-grab/cli

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/cli@139

grab

npm i https://pkg.pr.new/aidenybai/react-grab/grab@139

@react-grab/ami

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/ami@139

@react-grab/amp

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/amp@139

@react-grab/claude-code

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/claude-code@139

@react-grab/codex

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/codex@139

@react-grab/cursor

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/cursor@139

@react-grab/droid

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/droid@139

@react-grab/gemini

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/gemini@139

@react-grab/opencode

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/opencode@139

react-grab

npm i https://pkg.pr.new/aidenybai/react-grab@139

@react-grab/relay

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/relay@139

@react-grab/utils

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/utils@139

commit: 13fa582

Copy link
Contributor

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Moderate Priority - Found 3 significant issues including a browser compatibility bug and a preview diff bug. The migration logic is sound but has some presentation and compatibility concerns. No blocking security issues detected.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow runpullfrog.com𝕏

);
}

const grouped = Map.groupBy(countByComponent, ([, count]) => count);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: Browser Compatibility Issue

Map.groupBy() is an ES2024 feature with very limited browser support (not available in Safari, Firefox ESR, and older Chrome versions). This will cause runtime errors in unsupported browsers.

Recommended fix:

const grouped = new Map<number, Array<[string, number]>>();
for (const [name, count] of countByComponent) {
  if (!grouped.has(count)) {
    grouped.set(count, []);
  }
  grouped.get(count)!.push([name, count]);
}


const VITE_REMOVAL_PATTERNS: RegExp[] = [
/\s*import\s*\(\s*["']react-scan["']\s*\);?/g,
/\s*import\s*\(\s*["']react-scan["']\s*\)\s*\.then\s*\([^)]*\)[^;]*;?/g,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk: Overly Greedy Pattern

The pattern [^)]* followed by [^;]* is too permissive and could accidentally match beyond the intended .then() call. For example:

import("react-scan").then(() => console.log("loaded")); doSomething();

The [^;]* will match ) doSomething(), potentially removing user code after the import.

Consider making the pattern more specific:

/\s*import\s*\(\s*["']react-scan["']\s*\)\s*\.then\s*\([^)]*\)(?:\s*\.catch\s*\([^)]*\))?;?/g

Comment on lines 1650 to 1652
const WEBPACK_REMOVAL_PATTERNS: RegExp[] = [
/\s*import\s*\(\s*["']react-scan["']\s*\);?/g,
/^\s*import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+["']react-scan["'];?\s*$/gm,
/if\s*\(\s*process\.env\.NODE_ENV\s*===\s*["']development["']\s*\)\s*\{\s*\}/g,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Pattern May Not Match After First Removal

This pattern matches empty if blocks, but it relies on \s* to match any whitespace between { and }. After the first pattern removes the import statement, there could be varied whitespace (newlines, indentation) that should be matched. The pattern should work, but consider testing edge cases with different indentation styles.

Also, this pattern could theoretically match legitimate empty development conditionals unrelated to React Scan. Consider adding a comment in the code to document this behavior.

Comment on lines 329 to 330
const combinedOriginal = removalResult.originalContent!;
const combinedNew = addResult.newContent!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Incorrect Preview Diff

When removal and addition happen in the same file, this shows removalResult.originalContentaddResult.newContent. However, addResult was calculated from the original file (line 275-281), not from the file after React Scan removal. This means addResult.newContent still contains React Scan code, making the preview diff misleading.

The actual application order (lines 389-401) is correct, so this is a display-only bug.

Fix:

let combinedNew = removalResult.newContent!;
// Need to recalculate addResult based on removed content
// Or chain the operations properly

Alternatively, you could apply the removal transform to addResult.newContent before showing the diff, or show both diffs separately even when they're in the same file.

Comment on lines +323 to +337
];

export const REACT_SCAN_DETECTION_PATTERNS: RegExp[] = [
/["'`][^"'`]*react-scan/,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good: Comprehensive Pattern Detection

The detection patterns cover multiple installation methods (CDN, npm, import, require). However, pattern /["'\][^"'\`]*react-scan/` could theoretically match "react-scan" in comments or strings unrelated to the actual library.

This is unlikely to cause issues in practice, but worth noting for future refinement if false positives occur.

Comment on lines +1093 to +1250

expect(result.success).toBe(true);
expect(result.noChanges).toBeFalsy();
expect(result.newContent).not.toContain("react-scan");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Gap: Missing Assertion

The test verifies react-scan is removed and React is preserved, but doesn't verify that the empty if block itself was also removed. Consider adding:

expect(result.newContent).not.toContain('if (process.env.NODE_ENV');

Comment on lines +475 to +758

export const startRecording = (): void => {
if (!setupInstrumentation()) return;
renderLogHistory = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Potential Data Loss

startRecording() clears renderLogHistory unconditionally. If a user accidentally clicks record twice or the function is called programmatically multiple times, they lose their previous recording data without warning.

Consider either:

  1. Checking if there's existing data and prompting the user
  2. Not clearing the history automatically
  3. Documenting this behavior clearly in the UI

@cursor
Copy link

cursor bot commented Feb 2, 2026

Bugbot Autofix prepared fixes for 4 of the 4 bugs found in the latest run.

  • ✅ Fixed: Sequential transforms overwrite each other on same file
    • Recompute add transform after removal is applied when both target the same file, ensuring transforms chain correctly.
  • ✅ Fixed: Render count merging ignores incoming count value
    • Changed increment from hardcoded +1 to +box.renderCount to properly accumulate all incoming renders.
  • ✅ Fixed: Duplicate function with identical implementation
    • Consolidated findReactScanFile and findReactGrabFile into single findFrameworkEntryFile function to eliminate duplication.
  • ✅ Fixed: Unused exported constants in constants.ts
    • Removed five unused MODE_SWITCH constants that were never imported anywhere in the codebase.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 17 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/cli/test/detect.test.ts">

<violation number="1" location="packages/cli/test/detect.test.ts:229">
P2: These tests use a global mock that returns `package.json` content for all file reads. Since `detectReactScan` scans multiple files (like `layout.tsx`, `index.html`) using regex patterns, and the mocked `package.json` contains "react-scan", the function will incorrectly "detect" `react-scan` in every checked file.

This creates a false positive scenario where `detectedFiles` is populated even when checking only dependencies, potentially masking bugs in the file detection logic.

Fix by strictly mocking `package.json` and other files separately.</violation>
</file>

<file name="packages/cli/src/utils/transform.ts">

<violation number="1" location="packages/cli/src/utils/transform.ts:1633">
P1: This regex strictly requires parentheses around the component in the conditional expression. If the code was formatted (e.g., by Prettier) to remove unnecessary parentheses, this pattern will fail to match.

However, the subsequent pattern `/\s*<Script[^>]*react-scan[^>]*\/>/gi` will likely succeed, removing the component but leaving the conditional wrapper `{process.env.NODE_ENV === 'development' && }`, which causes a syntax error.</violation>
</file>

<file name="packages/react-grab/src/components/toolbar/state.ts">

<violation number="1" location="packages/react-grab/src/components/toolbar/state.ts:12">
P2: Input `mode` from localStorage is not validated against the `ToolbarMode` union. If the stored JSON contains `null` (valid JSON) or an invalid string, it could be returned as-is, violating the type contract and potentially causing runtime errors in consumers.

Add validation to ensure only valid modes are returned.</violation>
</file>

<file name="packages/react-grab/src/components/icons/icon-play.tsx">

<violation number="1" location="packages/react-grab/src/components/icons/icon-play.tsx:3">
P2: This component deviates from the codebase's icon patterns (e.g., `IconCheck`, `IconChevron`):
1. Hardcoded `white` colors limit reusability (should use `currentColor`).
2. Lacks a `size` prop for scaling.

The fix aligns it with other icons and defaults to the current 6x7 size. Ensure usages (e.g., in `toolbar/index.tsx`) set a text color (like `text-white`) if relying on the previous hardcoded value.</violation>
</file>

<file name="packages/react-grab/src/components/render-scan.tsx">

<violation number="1" location="packages/react-grab/src/components/render-scan.tsx:83">
P1: `Map.groupBy` is not supported in Node.js 18 or older browsers (e.g. Chrome < 117, Safari < 17.4). Since the project supports Node 18 (`engines` or `dependencies`), this will cause runtime errors in supported environments. Use a manual grouping loop instead.</violation>
</file>

<file name="packages/react-grab/src/design-system.tsx">

<violation number="1" location="packages/react-grab/src/design-system.tsx:2577">
P2: The Play icon SVG is hardcoded here, duplicating the logic from `IconPlay`.

Use the existing `IconPlay` component (from `src/components/icons/icon-play.tsx`) to ensure consistency and reduce maintenance overhead. You will need to add `import { IconPlay } from "./components/icons/icon-play.jsx";` to the imports.</violation>
</file>

<file name="packages/cli/test/transform.test.ts">

<violation number="1" location="packages/cli/test/transform.test.ts:919">
P2: Tests do not cover valid JSX conditionals without parentheses (e.g., `&& <Script />`). The removal logic relies on finding parentheses `&& (<Script ...)` matching the CLI injection pattern, but manual installations might omit them.

Add a test case to verify robustness against this variation.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -0,0 +1,20 @@
import type { Component } from "solid-js";
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This component deviates from the codebase's icon patterns (e.g., IconCheck, IconChevron):

  1. Hardcoded white colors limit reusability (should use currentColor).
  2. Lacks a size prop for scaling.

The fix aligns it with other icons and defaults to the current 6x7 size. Ensure usages (e.g., in toolbar/index.tsx) set a text color (like text-white) if relying on the previous hardcoded value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/components/icons/icon-play.tsx, line 3:

<comment>This component deviates from the codebase's icon patterns (e.g., `IconCheck`, `IconChevron`):
1. Hardcoded `white` colors limit reusability (should use `currentColor`).
2. Lacks a `size` prop for scaling.

The fix aligns it with other icons and defaults to the current 6x7 size. Ensure usages (e.g., in `toolbar/index.tsx`) set a text color (like `text-white`) if relying on the previous hardcoded value.</comment>

<file context>
@@ -0,0 +1,20 @@
+import type { Component } from "solid-js";
+
+interface IconPlayProps {
+  class?: string;
+}
</file context>
Fix with Cubic

}
>
<button class="contain-layout shrink-0 flex items-center justify-center size-3.5 rounded-full bg-black/70 hover:bg-black cursor-pointer">
<svg
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The Play icon SVG is hardcoded here, duplicating the logic from IconPlay.

Use the existing IconPlay component (from src/components/icons/icon-play.tsx) to ensure consistency and reduce maintenance overhead. You will need to add import { IconPlay } from "./components/icons/icon-play.jsx"; to the imports.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/design-system.tsx, line 2577:

<comment>The Play icon SVG is hardcoded here, duplicating the logic from `IconPlay`.

Use the existing `IconPlay` component (from `src/components/icons/icon-play.tsx`) to ensure consistency and reduce maintenance overhead. You will need to add `import { IconPlay } from "./components/icons/icon-play.jsx";` to the imports.</comment>

<file context>
@@ -2413,10 +2552,83 @@ const StateCard = (props: StateCardProps) => {
+                      }
+                    >
+                      <button class="contain-layout shrink-0 flex items-center justify-center size-3.5 rounded-full bg-black/70 hover:bg-black cursor-pointer">
+                        <svg
+                          class="ml-px"
+                          width="6"
</file context>
Fix with Cubic

expect(result.success).toBe(true);
expect(result.noChanges).toBeFalsy();
expect(result.newContent).not.toContain("react-scan");
expect(result.newContent).toContain("<head>");
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Tests do not cover valid JSX conditionals without parentheses (e.g., && <Script />). The removal logic relies on finding parentheses && (<Script ...) matching the CLI injection pattern, but manual installations might omit them.

Add a test case to verify robustness against this variation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/test/transform.test.ts, line 919:

<comment>Tests do not cover valid JSX conditionals without parentheses (e.g., `&& <Script />`). The removal logic relies on finding parentheses `&& (<Script ...)` matching the CLI injection pattern, but manual installations might omit them.

Add a test case to verify robustness against this variation.</comment>

<file context>
@@ -887,3 +888,340 @@ describe("applyPackageJsonTransform", () => {
+    expect(result.success).toBe(true);
+    expect(result.noChanges).toBeFalsy();
+    expect(result.newContent).not.toContain("react-scan");
+    expect(result.newContent).toContain("<head>");
+  });
+
</file context>
Fix with Cubic

);
}

const grouped = Map.groupBy(countByComponent, ([, count]) => count);
Copy link

@vercel vercel bot Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map.groupBy() is an ES2024 feature that causes runtime errors in unsupported browsers (Safari <18, Firefox <133, Chrome <130, Edge <130)

Fix on Vercel

@cursor
Copy link

cursor bot commented Feb 2, 2026

Bugbot Autofix prepared fixes for 3 of the 3 bugs found in the latest run.

  • ✅ Fixed: Migration diff preview shows incorrect combined file content
    • Modified previewTransform to accept optional content parameter and compute add transformation on already-removed content for accurate preview.
  • ✅ Fixed: Recording UI shows active state when recording silently fails
    • Changed startRecording to return boolean and updated handleStartRecording to only update UI state if recording successfully starts.
  • ✅ Fixed: Unused exported functions in scan.ts
    • Removed unused exports isScanAvailable and getLogHistory from scan.ts.

@cursor
Copy link

cursor bot commented Feb 2, 2026

Bugbot Autofix prepared fixes for 3 of the 3 bugs found in the latest run.

  • ✅ Fixed: Copy recording silently loses data if clipboard unavailable
    • Modified copyRecording to return a boolean indicating success and updated handleCopyRecording to only clear history when copy succeeds.
  • ✅ Fixed: Deferred effect causes render scan to miss initial renders
    • Added { defer: false } option to the createEffect call to ensure the effect runs on mount when props.enabled is already true.
  • ✅ Fixed: Duplicate file pattern checking helper functions
    • Extracted a generic hasPatternInFile helper function and refactored both hasReactGrabInFile and hasReactScanInFile to use it.

stroke-linejoin="round"
>
<path d="M1 1L5 3.5L1 6V1Z" />
</svg>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate play icon SVG instead of using IconPlay component

Low Severity

The play icon SVG is duplicated inline twice (lines 2577-2588 and 2603-2614) in design-system.tsx. The IconPlay component was added in this PR and is already used in toolbar/index.tsx. For consistency with IconCopy which is properly imported and used at line 2617, IconPlay should be imported and used as <IconPlay class="ml-px" /> instead of duplicating the SVG.

Fix in Cursor Fix in Web

stroke-linejoin="round"
>
<path d="M1 1L5 3.5L1 6V1Z" />
</svg>
Copy link

@vercel vercel bot Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two identical inline SVG play icons were duplicated in design-system.tsx instead of using the existing IconPlay component

Fix on Vercel

if (typeof navigator !== "undefined" && navigator.clipboard) {
await navigator.clipboard.writeText(text);
}
};
Copy link

@vercel vercel bot Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyRecording function lacks try-catch error handling around navigator.clipboard.writeText(), causing unhandled errors if clipboard operation fails

Fix on Vercel

projectInfo.packageManager,
projectInfo.projectRoot,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package uninstall ignores detection-removal file mismatch

High Severity

Detection checks files like _app.tsx for Next.js Pages Router and src/main.tsx for Vite, but removal only targets _document.tsx or index.html respectively. When React Scan is in a detected-but-not-removed file, the package is still uninstalled unconditionally, leaving broken imports. The detectedFiles array that tracks where React Scan was found is populated but never used to prevent this inconsistency.

Additional Locations (1)

Fix in Cursor Fix in Web

if (typeof navigator !== "undefined" && navigator.clipboard) {
await navigator.clipboard.writeText(text);
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recording data cleared even when copy fails silently

Medium Severity

The copyRecording function silently returns without copying when navigator.clipboard is unavailable, but handleCopyRecording unconditionally calls clearLogHistory() and setHasRecordedData(false) afterward. This causes user's recorded render data to be permanently lost without being copied in environments where the clipboard API is not available (older browsers, certain security contexts).

Additional Locations (1)

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 2, 2026

Bugbot Autofix prepared fixes for 2 of the 2 bugs found in the latest run.

  • ✅ Fixed: Package uninstall ignores detection-removal file mismatch
    • Added check for detectedFiles to prevent package uninstall when React Scan exists in files that cannot be automatically cleaned, and only uninstall when removal succeeds or no code is detected.
  • ✅ Fixed: Recording data cleared even when copy fails silently
    • Made copyRecording return a boolean indicating success, and only clear recording data in handleCopyRecording when the clipboard operation succeeds.

]);
label.textWidth = context.measureText(label.text).width;
labels.delete(otherKey);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Label merge loses previously merged boxes in sequential merges

Medium Severity

When merging overlapping labels, the code updates label.text using label.boxes and otherLabel.boxes, but never updates label.boxes to include the merged result. If a third label subsequently merges into the same label, it uses the original label.boxes (not the accumulated boxes), causing previously merged render information to be lost from the displayed text.

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 2, 2026

Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.

  • ✅ Fixed: Label merge loses previously merged boxes in sequential merges
    • Updated label.boxes to accumulate merged boxes during label merging to preserve all render information across sequential merges.

logger.log("No changes needed.");
logger.break();
process.exit(0);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration early exit skips package uninstall when no file changes

Medium Severity

The early exit condition !hasRemovalChanges && !hasAddChanges causes the migration to exit with "No changes needed" even when react-scan package is installed in package.json but has no file references. The later package uninstall logic at lines 389-397 correctly checks reactScanInfo.isPackageInstalled && (hasRemovalChanges || reactScanInfo.detectedFiles.length === 0), which would uninstall the package when there are no file references, but this code is never reached due to the early exit.

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 2, 2026

Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.

  • ✅ Fixed: Migration early exit skips package uninstall when no file changes
    • Added needsPackageUninstall check to the early exit condition to ensure package uninstallation proceeds even when there are no file changes but the package should be removed.


if (reactScanInfo.isPackageInstalled) {
logger.log(` ${pc.red("−")} Uninstall ${migrationSource} package`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preview shows package uninstall that won't happen

Medium Severity

The preview at line 330 shows "Uninstall react-scan package" whenever reactScanInfo.isPackageInstalled is true. However, the actual uninstall at lines 393-401 only happens when isPackageInstalled && (hasRemovalChanges || detectedFiles.length === 0). When React Scan is detected in files but can't be auto-removed (detectedFiles.length > 0 and hasRemovalChanges = false), the preview misleadingly shows the uninstall action, but it won't actually be performed.

Additional Locations (1)

Fix in Cursor Fix in Web

const [isEnabled, setIsEnabled] = createSignal(
savedToolbarState?.enabled ?? true,
const [toolbarMode, setToolbarMode] = createSignal<ToolbarMode>(
savedToolbarState?.mode ?? "select",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saved scan mode not validated against instrumentation availability

Medium Severity

When loading the saved toolbar state, the mode value is used directly without checking if it's still valid. If the saved mode is "scan" but isInstrumentationActive() returns false on page reload (e.g., React DevTools unavailable), the toolbar state becomes inconsistent: toolbarMode is "scan" so recording controls are visible and RenderScan is rendered, but the ModeSelector shows the knob at the "off" position because it only displays 2 options when scan mode is unavailable.

Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issue.

}

exitWithMessage();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation makes control flow misleading

Low Severity

The if (removalResult.noChanges) block and its contents are indented at 6 spaces when they should be at 8 spaces to match their actual nesting level inside if (projectInfo.hasReactGrab). This makes the code structure visually appear as if these blocks are at the same level as the parent if, when they're actually inside it. The control flow is correct due to braces, but future maintainers may misunderstand the code structure.

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 7, 2026

Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.

  • ✅ Fixed: Inconsistent indentation makes control flow misleading
    • Corrected indentation of lines 173-206 from 6 spaces to 8 spaces to properly reflect nesting level inside the parent conditional block.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issue.

logger.log(
` ${pc.green("+")} Add React Grab to ${addResult.filePath}`,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing warning for uncleaned react-scan files during migration

Medium Severity

When React Grab is not yet installed, the migration proceeds without warning users about detected react-scan files that weren't automatically cleaned. The warning logic at lines 177-188 only triggers when projectInfo.hasReactGrab is true. In the normal migration path, getOtherDetectedFiles() may contain files with react-scan that the removal didn't target (e.g., react-scan in pages/_app.tsx when removal only targets pages/_document.tsx), but no warning is displayed.

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 7, 2026

Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.

  • ✅ Fixed: Missing warning for uncleaned react-scan files during migration
    • Added warning check in normal migration path to display uncleaned react-scan files after migration completes, mirroring the existing warning in the React Grab-already-installed path.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issues.


if (!hasRemovalChanges && !hasAddChanges) {
exitWithMessage("No changes needed.");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration exits silently ignoring detected React Scan files

Medium Severity

When React Scan is detected (in files or package.json) but the target transformation file doesn't exist, the migration exits with "No changes needed." without warning the user about detected React Scan files that couldn't be cleaned, and without offering to uninstall the react-scan package. This happens because previewReactScanRemoval returns noChanges: true and previewTransform returns success: false when target files are missing, causing both hasRemovalChanges and hasAddChanges to be false, which triggers the early exit before the warning logic at lines 353-363 is reached.

Fix in Cursor Fix in Web


logger.break();
logger.success("Migration complete! React Scan has been removed.");
exitWithMessage();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing warning about uncleaned files in hasReactGrab branch

Medium Severity

When React Grab is already installed and the removal transform succeeds, the code logs "Migration complete! React Scan has been removed." without checking for additional detected files that weren't automatically cleaned. The main migration flow (lines 353-363) includes a warning when getOtherDetectedFiles().length > 0, but this warning is missing in the hasReactGrab branch. A user could have React Scan in multiple files; only one gets cleaned, but the success message suggests the migration is fully complete.

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 7, 2026

Bugbot Autofix prepared fixes for 2 of the 2 bugs found in the latest run.

  • ✅ Fixed: Migration exits silently ignoring detected React Scan files
    • Added check before early exit to warn about detected files and offer package uninstall when no transform changes are needed.
  • ✅ Fixed: Missing warning about uncleaned files in hasReactGrab branch
    • Added warning about additional detected files that weren't automatically cleaned in the hasReactGrab branch after successful removal.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/cli/src/commands/migrate.ts">

<violation number="1" location="packages/cli/src/commands/migrate.ts:295">
P2: In non-interactive mode this block will uninstall react-scan even though references remain (detected files could not be cleaned), leaving broken imports/scripts. Skip uninstall when `-y` is used in this scenario or require explicit confirmation only in interactive mode.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines 295 to 302
uninstallPackagesWithFeedback(
["react-scan"],
projectInfo.packageManager,
projectInfo.projectRoot,
);
logger.break();
logger.success("React Scan package has been uninstalled.");
}
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: In non-interactive mode this block will uninstall react-scan even though references remain (detected files could not be cleaned), leaving broken imports/scripts. Skip uninstall when -y is used in this scenario or require explicit confirmation only in interactive mode.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/commands/migrate.ts, line 295:

<comment>In non-interactive mode this block will uninstall react-scan even though references remain (detected files could not be cleaned), leaving broken imports/scripts. Skip uninstall when `-y` is used in this scenario or require explicit confirmation only in interactive mode.</comment>

<file context>
@@ -261,6 +274,34 @@ export const migrate = new Command()
+              "Uninstall react-scan package?",
+              isNonInteractive,
+            );
+            uninstallPackagesWithFeedback(
+              ["react-scan"],
+              projectInfo.packageManager,
</file context>
Suggested change
uninstallPackagesWithFeedback(
["react-scan"],
projectInfo.packageManager,
projectInfo.projectRoot,
);
logger.break();
logger.success("React Scan package has been uninstalled.");
}
if (!isNonInteractive) {
await confirmOrExit(
"Uninstall react-scan package?",
isNonInteractive,
);
uninstallPackagesWithFeedback(
["react-scan"],
projectInfo.packageManager,
projectInfo.projectRoot,
);
logger.break();
logger.success("React Scan package has been uninstalled.");
}
Fix with Cubic

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issue.

exitWithMessage();
}
exitWithMessage("No changes needed.");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent failure when detected react-scan pattern cannot be removed

Medium Severity

Detection patterns in REACT_SCAN_DETECTION_PATTERNS include /<script[^>]*react-scan/i for CDN script tags, but removal patterns (removeReactScanFromVite) only handle dynamic imports like import("react-scan"). When a Vite project has a CDN script like <script src="unpkg.com/react-scan">, detection finds it, but removal fails silently with noChanges: true. The warning logic at lines 276-306 only fires when BOTH removal and addition have no changes. The warning at lines 394-404 uses getOtherDetectedFiles() which excludes the removal target file. So when removal fails but addition proceeds, the user sees "Migration complete!" with no indication that react-scan is still present.

Additional Locations (1)

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 7, 2026

Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.

  • ✅ Fixed: Silent failure when detected react-scan pattern cannot be removed
    • Added HTML script tag removal patterns (inline, external, and self-closing) to removeReactScanFromVite and removeReactScanFromWebpack to handle CDN-loaded react-scan scripts that were being detected but not removed.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issue.

newContent,
};
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pattern removal exits after first match type

Medium Severity

The removePatternFromContent function iterates through patterns and returns immediately when the first pattern type matches, even if the file contains multiple different types of react-scan patterns. For example, if a Vite HTML file has both an inline script block containing import("react-scan") AND an external <script src="react-scan"> tag, only the first matching pattern type is removed. The file still contains react-scan after "successful" removal, and the warning at lines 394-405 in migrate.ts won't catch this because getOtherDetectedFiles() filters out removalResult.filePath.

Additional Locations (1)

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 7, 2026

Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.

  • ✅ Fixed: Pattern removal exits after first match type
    • Changed removePatternFromContent to chain all pattern replacements instead of returning after the first match, ensuring complete removal of all react-scan references.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

const REACT_SCAN_VOID_IMPORT =
/\s*void\s+import\s*\(\s*["']react-scan["']\s*\)\s*;?\s*/gi;
const REACT_SCAN_HTML_SCRIPT_TAG_INLINE =
/\s*<script[^>]*>[\s\S]*?react-scan[\s\S]*?<\/script>\s*/gi;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overly aggressive inline script removal pattern deletes unrelated code

Medium Severity

The REACT_SCAN_HTML_SCRIPT_TAG_INLINE regex pattern /\s*<script[^>]*>[\s\S]*?react-scan[\s\S]*?<\/script>\s*/gi matches and removes entire <script> blocks containing "react-scan" anywhere in the content. When a script block contains both react-scan imports AND other code, the entire block is removed, deleting unrelated code. The test at line 1181 ("should preserve other imports in same block") expects some-other-lib to be preserved, but this pattern would remove it.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issues.

message: noChangesMessage,
noChanges: true,
};
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pattern removal only applies first matching pattern

Medium Severity

The removePatternFromContent function iterates through patterns but returns immediately after the first pattern produces a change. If a file contains multiple types of react-scan references that match different patterns (e.g., both a conditional {process.env.NODE_ENV === "development" && (<Script src="react-scan" />)} AND a bare <Script src="react-scan" />), only the first matching pattern type is removed. The PR description mentions "chain all pattern replacements to ensure complete removal," but this implementation does not chain.

Fix in Cursor Fix in Web

[REACT_SCAN_DEV_CONDITIONAL_IMPORT, REACT_SCAN_DYNAMIC_IMPORT],
"Remove React Scan from Webpack",
"No React Scan import found",
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CDN script tags in HTML not removable by removal patterns

Medium Severity

Detection patterns catch HTML <script> tags with react-scan via /<script[^>]*react-scan/i, but removal functions for Vite and Webpack only use REACT_SCAN_DYNAMIC_IMPORT. The REACT_SCAN_SCRIPT_TAG pattern also only matches self-closing tags (\/>), not standard HTML <script src="..."></script>. This means CDN script references in index.html files will be detected but cannot be removed, leaving users with warnings about files that weren't cleaned. The PR description claims to "remove CDN HTML <script> tags... in Vite/Webpack" but the implementation doesn't support this.

Fix in Cursor Fix in Web

- Fix removePatternFromContent to chain all pattern replacements instead of returning after first match
- Add REACT_SCAN_HTML_SCRIPT_TAG pattern to match standard HTML <script></script> tags
- Include HTML script tag pattern in Vite and Webpack removal functions
- Run formatter on modified files
@cursor
Copy link

cursor bot commented Feb 7, 2026

Bugbot Autofix prepared fixes for 2 of the 2 bugs found in the latest run.

  • ✅ Fixed: Pattern removal only applies first matching pattern
    • Modified removePatternFromContent to chain all pattern replacements by applying each pattern sequentially to the result of the previous replacement.
  • ✅ Fixed: CDN script tags in HTML not removable by removal patterns
    • Added REACT_SCAN_HTML_SCRIPT_TAG pattern to match standard HTML script tags and included it in Vite and Webpack removal functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants