Skip to content

Table rows demo continuity#150

Open
aidenybai wants to merge 11 commits intomainfrom
cursor/table-rows-demo-continuity-80b4
Open

Table rows demo continuity#150
aidenybai wants to merge 11 commits intomainfrom
cursor/table-rows-demo-continuity-80b4

Conversation

@aidenybai
Copy link
Owner

@aidenybai aidenybai commented Feb 4, 2026

Make the mobile demo animation continuous and include table row selections.

Previously, the animation had a 12-second pause between loops and did not animate the "Recent Activity" table rows. This change removes the pause and extends the animation to sequentially select and "copy" each table row.


Open in Cursor Open in Web


Summary by cubic

Made the mobile demo animation continuous and switched most interactions to comment input/submit. The loop no longer pauses and steps through Export → Metric card → Stat value → Signup (comment) → Order (copy), with clear labels and reliable restarts.

  • New Features

    • Comment interactions for ExportBtn, MetricCard, StatValue, and SignupRow; shows typing, submit, and “Sent”.
    • OrderRow uses grab/copy; loop runs continuously with a 200ms restart delay and measured positions for accurate overlays.
  • Bug Fixes

    • Prevent concurrent loops and reliably restart after visibility changes.
    • Fix fading label text (“Copied” vs “Sent”); add bottom padding and place row labels below to avoid cutoff.

Written for commit dbde78d. Summary will update on new commits.


Note

Low Risk
UI-only animation/visual state changes with no backend or data-handling impact; main risk is timing/loop-cancellation edge cases affecting the demo’s smoothness.

Overview
Makes the MobileDemoAnimation loop continuously (replacing the fixed 12s interval) and improves restart behavior on tab visibility changes.

Extends the scripted interactions to measure and target Recent Activity rows, adds a new “commenting” flow (typed text, submit icon, “Sent” confirmation), and refines label rendering/positioning (supports above/below placement and correct fade-out text for “Copied” vs “Sent”).

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

cursoragent and others added 2 commits February 4, 2026 13:06
- Remove gap between animation loops (200ms restart delay vs 12s interval)
- Add animation for all 3 table rows in Recent Activity section
- Each row gets selected with crosshair, shows component name, and copies
- Animation flows continuously: Export → MetricCard → StatValue → SignupRow → OrderRow → PaymentRow → repeat

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
- Use for..of with entries() instead of index-based loop
- Remove unnecessary null check (we control the data)
- Inline initializeAnimationLoop into direct runAnimationLoop call
- Simplify ref callback syntax

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
@cursor
Copy link

cursor bot commented Feb 4, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@vercel
Copy link

vercel bot commented Feb 4, 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 5, 2026 2:37am

@aidenybai aidenybai marked this pull request as ready for review February 4, 2026 13:08
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 4, 2026

Open in StackBlitz

@react-grab/cli

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

grab

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

@react-grab/ami

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

@react-grab/amp

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

@react-grab/claude-code

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

@react-grab/codex

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

@react-grab/cursor

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

@react-grab/droid

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

@react-grab/gemini

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

@react-grab/opencode

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

react-grab

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

@react-grab/relay

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

@react-grab/utils

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

commit: dbde78d

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.

This PR successfully achieves continuous animation and table row selections. Found one critical race condition with visibility change handling that could cause multiple concurrent animation loops. The implementation is otherwise solid.

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

Comment on lines 554 to 560
const handleVisibilityChange = (): void => {
if (document.visibilityState === "visible") {
isCancelled = true;
clearInterval(animationInterval);
resetAnimationState();
setTimeout(initializeAnimationLoop, 100);
isCancelled = false;
setTimeout(runAnimationLoop, 100);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Critical race condition: When the tab becomes visible, isCancelled is set to true (line 556), then immediately back to false (line 558), but the existing runAnimationLoop from line 564 might not have exited yet since the cancellation check happens at discrete await points. This could result in multiple concurrent animation loops running simultaneously.

The previous interval-based approach avoided this because clearInterval immediately stopped scheduling new executions.

Fix: Wait for the current loop to fully exit before starting a new one. Consider:

const handleVisibilityChange = (): void => {
  if (document.visibilityState === "visible") {
    isCancelled = true;
    resetAnimationState();
    // Wait for current loop to exit at next cancellation check
    setTimeout(() => {
      isCancelled = false;
      runAnimationLoop();
    }, 200);
  }
};

Comment on lines 521 to 533
for (const [i, rowPos] of activityRowPositions.current.entries()) {
if (isCancelled) return;

const rowCenter = getElementCenter(rowPos);
setCursorPos(rowCenter);
await wait(350);
if (isCancelled) return;

setSelectionBox(createSelectionBox(rowPos, SELECTION_PADDING_PX));
displaySelectionLabel(
rowPos.x + rowPos.width / 2,
rowPos.y + rowPos.height + 10,
ACTIVITY_DATA[i].component,
Copy link
Contributor

Choose a reason for hiding this comment

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

Array access without bounds check: If activityRowPositions.current.length doesn't match ACTIVITY_DATA.length (e.g., due to measurement timing or filtering out null refs), accessing ACTIVITY_DATA[i] at line 533 could fail or use the wrong index.

Since activityRowPositions is filtered from activityRowRefs (lines 326-336), if any refs are null during measurement, the arrays will be misaligned.

Suggestion: Use the filtered data structure directly or add a bounds check:

if (i >= ACTIVITY_DATA.length) continue;

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
- Table rows now show comment input UI instead of grab/copy
- Simulates typing into 'Add context' field
- Shows submit button and 'Sent' confirmation
- Matches react-grab comment mode interaction

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
- Remove slice(0, -1) to include all three activity rows in animation
- Remove duplicate runAnimationLoop call to prevent concurrent animations
@cursor
Copy link

cursor bot commented Feb 4, 2026

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

  • ✅ Fixed: Activity row loop excludes the last row
    • Removed .slice(0, -1) to include all three activity rows (SignupRow, OrderRow, PaymentRow) in the animation loop.
  • ✅ Fixed: Visibility change handler causes concurrent animation loops
    • Removed the duplicate setTimeout(runAnimationLoop, 100) call to prevent multiple concurrent animation loops from running.

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

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/website/components/mobile-demo-animation.tsx">

<violation number="1" location="packages/website/components/mobile-demo-animation.tsx:558">
P1: Race condition in cancellation logic. Toggling `isCancelled` to `true` and then immediately back to `false` synchronously will fail to stop the previous async loop.

When `executeAnimationSequence` is paused at an `await`, it will only check `isCancelled` after the promise resolves. By that time, `handleVisibilityChange` has already reset it to `false`, so the old loop sees `false` and continues running. This results in two competing animation loops.

**Fix**: Use a `ref` object (e.g., `useRef({ id: 0 })`) to track the current "run ID" and pass it to the animation sequence, or ensure `isCancelled` remains true for the duration of the old loop's potential wake-up (which is difficult to time). A ref-based token is the most robust solution.</violation>
</file>

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

clearInterval(animationInterval);
resetAnimationState();
setTimeout(initializeAnimationLoop, 100);
isCancelled = false;
Copy link
Contributor

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

Choose a reason for hiding this comment

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

P1: Race condition in cancellation logic. Toggling isCancelled to true and then immediately back to false synchronously will fail to stop the previous async loop.

When executeAnimationSequence is paused at an await, it will only check isCancelled after the promise resolves. By that time, handleVisibilityChange has already reset it to false, so the old loop sees false and continues running. This results in two competing animation loops.

Fix: Use a ref object (e.g., useRef({ id: 0 })) to track the current "run ID" and pass it to the animation sequence, or ensure isCancelled remains true for the duration of the old loop's potential wake-up (which is difficult to time). A ref-based token is the most robust solution.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/website/components/mobile-demo-animation.tsx, line 558:

<comment>Race condition in cancellation logic. Toggling `isCancelled` to `true` and then immediately back to `false` synchronously will fail to stop the previous async loop.

When `executeAnimationSequence` is paused at an `await`, it will only check `isCancelled` after the promise resolves. By that time, `handleVisibilityChange` has already reset it to `false`, so the old loop sees `false` and continues running. This results in two competing animation loops.

**Fix**: Use a `ref` object (e.g., `useRef({ id: 0 })`) to track the current "run ID" and pass it to the animation sequence, or ensure `isCancelled` remains true for the duration of the old loop's potential wake-up (which is difficult to time). A ref-based token is the most robust solution.</comment>

<file context>
@@ -499,35 +518,53 @@ export const MobileDemoAnimation = (): ReactElement => {
-        clearInterval(animationInterval);
         resetAnimationState();
-        setTimeout(initializeAnimationLoop, 100);
+        isCancelled = false;
+        setTimeout(runAnimationLoop, 100);
       }
</file context>
Fix with Cubic

setCursorPos(rowCenter);
await wait(350);
if (isCancelled) return;

Copy link

@vercel vercel bot Feb 4, 2026

Choose a reason for hiding this comment

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

Race condition in handleVisibilityChange allows concurrent animation loops to run simultaneously when tab visibility changes

Fix on Vercel

const runAnimationLoop = async (): Promise<void> => {
while (!isCancelled) {
await executeAnimationSequence();
}
Copy link

@vercel vercel bot Feb 4, 2026

Choose a reason for hiding this comment

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

Animation restart has a race condition when tab becomes visible - old and new animation loops execute concurrently and call setState from overlapping animation sequences

Fix on Vercel

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/website/components/mobile-demo-animation.tsx">

<violation number="1" location="packages/website/components/mobile-demo-animation.tsx:548">
P2: Potential index mismatch: if any `activityRowPositions` entries are null or filtered out, the index `i` from `.entries()` won't align correctly with `ACTIVITY_DATA[i]`. Consider iterating with the actual position data that includes its corresponding activity data, or ensure both arrays are guaranteed to have matching indices.</violation>
</file>

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

- Fix Bug 1: Preserve original array indices in activityRowPositions to prevent mismatch with ACTIVITY_DATA when refs are null
- Fix Bug 2: Track and display correct text ('Copied' vs 'Sent') during label fade-out animation
@cursor
Copy link

cursor bot commented Feb 4, 2026

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

  • ✅ Fixed: Index mismatch between filtered positions and data array
    • Changed activityRowPositions to preserve original array indices by using map instead of filter+map, and updated the loop to iterate by index and skip null positions.
  • ✅ Fixed: Fading label shows "Sent" instead of "Copied" after copy
    • Added fadingLabelTextRef to track the correct text during fade-out, updated fadeOutSelectionLabel to accept a text parameter, and pass 'Copied' or 'Sent' appropriately at each call site.

@cursor
Copy link

cursor bot commented Feb 4, 2026

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

  • ✅ Fixed: Animation loop does not restart after visibility change
    • Added runAnimationLoop() call in handleVisibilityChange to restart the animation after the tab becomes visible again.

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/website/components/mobile-demo-animation.tsx">

<violation number="1" location="packages/website/components/mobile-demo-animation.tsx:633">
P2: Starting a new animation loop on visibility change without waiting for the existing loop to stop can create multiple concurrent loops because the cancellation flag is reset to false before the prior loop observes it. This can cause overlapping animations and extra state updates after returning to the tab.</violation>
</file>

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

resetAnimationState();
setTimeout(initializeAnimationLoop, 100);
isCancelled = false;
runAnimationLoop();
Copy link
Contributor

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

Choose a reason for hiding this comment

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

P2: Starting a new animation loop on visibility change without waiting for the existing loop to stop can create multiple concurrent loops because the cancellation flag is reset to false before the prior loop observes it. This can cause overlapping animations and extra state updates after returning to the tab.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/website/components/mobile-demo-animation.tsx, line 633:

<comment>Starting a new animation loop on visibility change without waiting for the existing loop to stop can create multiple concurrent loops because the cancellation flag is reset to false before the prior loop observes it. This can cause overlapping animations and extra state updates after returning to the tab.</comment>

<file context>
@@ -630,6 +630,7 @@ export const MobileDemoAnimation = (): ReactElement => {
         isCancelled = true;
         resetAnimationState();
         isCancelled = false;
+        runAnimationLoop();
       }
     };
</file context>
Fix with Cubic

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
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/website/components/mobile-demo-animation.tsx">

<violation number="1" location="packages/website/components/mobile-demo-animation.tsx:569">
P2: The activity-row animation now skips the final row because the loop stops at `length - 1`. This prevents the PaymentRow from ever being selected/animated.</violation>
</file>

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

await simulateClickAndCopy(valuePos);
if (isCancelled) return;

for (let i = 0; i < ACTIVITY_DATA.length - 1; i++) {
Copy link
Contributor

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

Choose a reason for hiding this comment

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

P2: The activity-row animation now skips the final row because the loop stops at length - 1. This prevents the PaymentRow from ever being selected/animated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/website/components/mobile-demo-animation.tsx, line 569:

<comment>The activity-row animation now skips the final row because the loop stops at `length - 1`. This prevents the PaymentRow from ever being selected/animated.</comment>

<file context>
@@ -566,7 +566,7 @@ export const MobileDemoAnimation = (): ReactElement => {
       if (isCancelled) return;
 
-      for (let i = 0; i < ACTIVITY_DATA.length; i++) {
+      for (let i = 0; i < ACTIVITY_DATA.length - 1; i++) {
         if (isCancelled) return;
         const rowPos = activityRowPositions.current[i];
</file context>
Suggested change
for (let i = 0; i < ACTIVITY_DATA.length - 1; i++) {
for (let i = 0; i < ACTIVITY_DATA.length; i++) {
Fix with Cubic

- ExportBtn: comment 'add CSV option'
- MetricCard: comment 'show graph'
- StatValue: comment 'format as USD'
- SignupRow: comment 'add avatar'
- OrderRow: grab/copy (last interaction)

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
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.

setSuccessFlash(HIDDEN_BOX);
await fadeOutSelectionLabel();
setCursorType("crosshair");
await wait(300);
Copy link

Choose a reason for hiding this comment

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

Dead code: drag selection feature never used

Low Severity

The animateDragSelection function is defined but never called - the code that previously invoked it was removed. Related state (dragBox, isDragging) is declared and reset but never actually used: dragBox.visible is always false so its render block never executes, and isDragging is only set to false. This dead code should be removed.

Additional Locations (1)

Fix in Cursor Fix in Web

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