Skip to content

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 automation that rewrites project files and uninstalls/installs packages; regex-based detection/removal could miss variants or over-remove code in edge cases despite added tests.

Overview
Adds a new grab migrate CLI command to migrate from React Scan by detecting react-scan usage (in package.json and common entry/config files), previewing diffs, applying automated removals, and installing react-grab (optionally uninstalling react-scan only when safe).

Extends the CLI utilities with React Scan detection patterns + file targeting and a previewReactScanRemoval transform for Next.js/Vite/Webpack/TanStack setups, backed by comprehensive new unit tests; also adds a Gym dashboard PerformanceTest page component to intentionally create slow renders/effects for performance tooling validation.

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


Summary by cubic

Adds a new Scan mode to visualize and record component renders, plus a CLI migrate command to detect and remove react-scan across common setups. Adds a comment mode, makes drag selection copy by default, improves viewport handling, and keeps uninstalls and logging safe.

  • New Features

    • CLI: migrate command to find react-scan in package.json, imports, and script tags; preview diffs correctly combine removals/additions; remove from Next.js (App/Pages), Vite, and Webpack; applies transforms before package changes; only uninstall react-scan when all detected files are cleaned or no code is found; avoids uninstall on non-targeted file matches; installs needed packages; preview uninstall matches execution; improved removal patterns to avoid corrupting chained imports.
    • UI: mode switch (off/select/scan) and comment mode; drag selection copies by default; render-scan overlay with animated boxes/labels and accurate render counts; fixed overlapping label merge to retain all boxes; start/stop recording and copy log (returns success and avoids data loss if clipboard is blocked); state persists; recording only starts when instrumentation is available; improved selection after scroll/resize and keyboard navigation; removed crosshair lerp and added short‑TTL visibility caching for smoother performance; powered by bippy instrumentation.
  • 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 bf5b0c9. 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 3, 2026 9:03am

@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

commit: bf5b0c9

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]);
}

Comment on lines +1642 to +1641

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 +1651
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.

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.

];

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.


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');


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.

);
}

const grouped = Map.groupBy(countByComponent, ([, count]) => count);
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.

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.

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/render-scan.tsx, line 83:

<comment>`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.</comment>

<file context>
@@ -0,0 +1,465 @@
+    );
+  }
+
+  const grouped = Map.groupBy(countByComponent, ([, count]) => count);
+  const text = [...grouped.entries()]
+    .sort(([countA], [countB]) => countB - countA)
</file context>
Fix with Cubic

@@ -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.

return true;
}
return false;
};
Copy link

Choose a reason for hiding this comment

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

Missing error handling for clipboard write operation

Medium Severity

The copyRecording function lacks a try-catch around navigator.clipboard.writeText(). This is inconsistent with other clipboard usage in the codebase (e.g., core/index.tsx lines 2871-2882) which properly wrap clipboard operations in try-catch blocks. If the clipboard API fails due to permissions issues or browser restrictions, this will cause an unhandled promise rejection that propagates to handleCopyRecording, potentially leaving the UI in an inconsistent state where clearLogHistory() never runs.

Fix in Cursor Fix in Web

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


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 succeeds silently when detected files cannot be cleaned

Medium Severity

When React Scan is detected in files (e.g., _app.tsx) that the removal logic doesn't target (which only handles _document.tsx for Pages Router), the migration proceeds without warning. The hasReactGrab branch has validation at lines 209-222 that warns about detectedFiles that couldn't be cleaned, but the main migration branch lacks this check. Users see "Success! Migration complete." while React Scan code remains in their project.

Fix in Cursor Fix in Web

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 3 files (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:306">
P2: The uninstall logic is unsafe when React Scan is used in multiple files.

If `detectReactScan` finds usages in multiple files (e.g., `layout.tsx` and `index.html`), but `previewReactScanRemoval` only targets one file (e.g., `layout.tsx`), `hasRemovalChanges` will be true. This satisfies the condition `(hasRemovalChanges || ...)` and causes the package to be uninstalled, breaking the application because the reference in `index.html` remains.

You should verify that *all* detected files are covered by the removal transform before uninstalling.</violation>
</file>

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

@cursor
Copy link

cursor bot commented Feb 3, 2026

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

  • ✅ Fixed: Migration succeeds silently when detected files cannot be cleaned
    • Added validation to check for detected files that couldn't be cleaned and exit with warning before proceeding with migration, mirroring the existing validation in the hasReactGrab branch.


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 reports success when React Grab code cannot be added

Medium Severity

When previewTransform returns success: false (e.g., layout file not found), the migration doesn't check for this failure. If removal succeeds but addition fails, the migration removes React Scan, installs the react-grab package, but does NOT add React Grab code to the project. The migration still reports "Success!" without showing the error message from addResult.message. This leaves users with a broken setup where neither tool works.

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 3, 2026

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

  • ✅ Fixed: Migration reports success when React Grab code cannot be added
    • Added validation to check if previewTransform fails and exit with error message before making any changes, preventing partial migration.

aidenybai and others added 12 commits February 3, 2026 00:36
- 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.
…iled copy

- Fix package uninstall when React Scan detected in non-targeted files
- Prevent recording data loss when clipboard API unavailable
- Only uninstall react-scan package when code removal succeeds or no code detected
- Make copyRecording return boolean to indicate success/failure
When merging overlapping labels, update label.boxes to include the merged boxes so that subsequent merges don't lose previously merged render information.
…changes

When react-scan is installed in package.json but has no file references,
the migration should still proceed to uninstall the package rather than
exiting early with 'No changes needed'. This fix adds a check for
needsPackageUninstall to the early exit condition.
* feat: add comment mode to toolbar

- Add comment button to toolbar that enters prompt mode on next element click
- Add pendingCommentMode state to store
- Add isCommentMode and onComment props to toolbar and renderer
- Extract common toolbar button handlers (stopEventPropagation, createFreezeHandlers)
- Replace nested ternaries with getToolbarIconColor helper function
- Fix overflow class inconsistency in toolbar-content.tsx
- Remove unused formatShortcut import

* chore: update version in manifest.json to 0.0.7

* fix: clear pendingCommentMode on drag selection to prevent unexpected prompt mode

* feat: enhance comment mode functionality

- Refactor comment mode handling in the toolbar and renderer
- Update state management for pendingCommentMode and isCommentMode
- Improve element interaction logic for entering comment mode
- Ensure proper activation and deactivation of comment mode based on user actions

* feat: enhance toolbar functionality with new props

- Add isToolbarCommentMode and isToolbarCollapsed props to the design system
- Update toolbar content to utilize new props for improved state management
- Refactor button rendering logic in toolbar-content.tsx to accommodate comment mode and active states

* fix: comment mode drag selection and toolbar button styling

- Remove premature setPendingCommentMode(false) in handleDragSelection to allow comment mode for drag selections
- Add isCommentMode prop to ToolbarContentProps and apply conditional styling to defaultCommentButton

* Fix comment icon state inconsistency and extract duplicate icon color helper

- Fix comment icon toggle behavior to check isCommentMode() instead of just pendingCommentMode, ensuring visual state matches click behavior
- Extract getToolbarIconColor to shared utility to eliminate code duplication between toolbar files

* fix: rename confusing isActive parameter to isDimmed in getToolbarIconColor

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
- 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.
…iled copy

- Fix package uninstall when React Scan detected in non-targeted files
- Prevent recording data loss when clipboard API unavailable
- Only uninstall react-scan package when code removal succeeds or no code detected
- Make copyRecording return boolean to indicate success/failure
- Improved migration command to handle non-interactive mode more gracefully, including confirmation prompts and error handling.
- Refactored toolbar components to incorporate new selection modes and improve state management.
- Updated utility functions for better icon color handling based on selection mode.
- Enhanced type definitions for better clarity and maintainability across the codebase.
[migrationSource],
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 uninstalled while react-scan imports remain in uncleaned files

Medium Severity

The detection in detectReactScan checks multiple files (e.g., both _document.tsx and _app.tsx for Pages Router), but findReactScanFile only returns one file per framework. When react-scan exists in multiple files, the removal only cleans one, yet the uninstall condition at line 361 (hasRemovalChanges || reactScanInfo.detectedFiles.length === 0) evaluates to true if ANY removal succeeded. This causes the package to be uninstalled while other files still have react-scan imports, breaking those files at runtime.

Additional Locations (1)

Fix in Cursor Fix in Web

Previously, react-scan package was uninstalled after cleaning ANY file with react-scan imports, even when other files still contained react-scan code. This caused runtime errors when react-scan existed in multiple files (e.g., both _document.tsx and _app.tsx in Next.js Pages Router).

Now checks that all detected files have been cleaned before uninstalling the package.
@cursor
Copy link

cursor bot commented Feb 3, 2026

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

  • ✅ Fixed: Package uninstalled while react-scan imports remain in uncleaned files
    • Modified uninstall conditions to check that all detected files are cleaned before uninstalling the package, preventing runtime errors when react-scan exists in multiple files.

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.

addResult,
`Adding 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.

Package uninstall happens before file transforms, risking inconsistent state

Medium Severity

In the main migration path, uninstallPackagesWithFeedback and installPackagesWithFeedback are called before applyTransformWithFeedback runs. If the file transforms fail (e.g., due to permissions), the project is left with react-scan uninstalled and react-grab installed, but the files still contain react-scan code and lack react-grab code. This contradicts the PR description stating "only uninstall react-scan when removals succeed." The other code path (lines 260-275 for when React Grab is already installed) correctly applies the transform first, then uninstalls the package.

Fix in Cursor Fix in Web


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 actually happen

Medium Severity

The preview message at lines 315-317 shows "Uninstall react-scan package" whenever reactScanInfo.isPackageInstalled is true. However, the actual uninstall at lines 370-376 has an additional condition: otherDetectedFiles.length === 0. If react-scan code exists in files that weren't cleaned by the removal transform (e.g., detected in src/main.tsx but removal targeted index.html), the preview will claim the package will be uninstalled, but it won't be. The preview condition needs to match the execution condition.

Additional Locations (1)

Fix in Cursor Fix in Web

…view condition

- Apply file transforms before uninstalling/installing packages to avoid inconsistent state if transforms fail
- Fix preview message to accurately show when react-scan package will be uninstalled by adding otherDetectedFiles.length check
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:269">
P2: The execution condition for package uninstall includes `otherDetectedFiles.length === 0`, but the preview logic (lines 314-317) may not include this same condition. This can cause the preview to incorrectly show "Uninstall react-scan package" in cases where the uninstall won't actually happen due to remaining detected files. The preview condition should match this execution condition to avoid misleading users.</violation>
</file>

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

(file) => file !== removalResult.filePath,
);

if (reactScanInfo.isPackageInstalled && otherDetectedFiles.length === 0) {
Copy link
Contributor

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

Choose a reason for hiding this comment

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

P2: The execution condition for package uninstall includes otherDetectedFiles.length === 0, but the preview logic (lines 314-317) may not include this same condition. This can cause the preview to incorrectly show "Uninstall react-scan package" in cases where the uninstall won't actually happen due to remaining detected files. The preview condition should match this execution condition to avoid misleading users.

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 269:

<comment>The execution condition for package uninstall includes `otherDetectedFiles.length === 0`, but the preview logic (lines 314-317) may not include this same condition. This can cause the preview to incorrectly show "Uninstall react-scan package" in cases where the uninstall won't actually happen due to remaining detected files. The preview condition should match this execution condition to avoid misleading users.</comment>

<file context>
@@ -262,7 +262,11 @@ export const migrate = new Command()
+            (file) => file !== removalResult.filePath,
+          );
+
+          if (reactScanInfo.isPackageInstalled && otherDetectedFiles.length === 0) {
             uninstallPackagesWithFeedback(
               [migrationSource],
</file context>
Fix with Cubic

@cursor
Copy link

cursor bot commented Feb 3, 2026

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

  • ✅ Fixed: Package uninstall happens before file transforms, risking inconsistent state
    • Reordered operations to apply file transforms before package operations, ensuring packages are only modified after transforms succeed.
  • ✅ Fixed: Preview shows package uninstall that won't actually happen
    • Added otherDetectedFiles.length === 0 condition to preview message to match the actual execution condition for package uninstall.

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.

3 participants