Skip to content

Conversation

@mpcgrid
Copy link
Collaborator

@mpcgrid mpcgrid commented Dec 11, 2025

Refactor MousePositionProvider to improve reactivity during resize, scroll, and layout shifts.

Closes #

✅ Checklist

  • I have followed every step in the contributing guide
  • The PR title follows the convention.
  • I ran and tested the code works

Testing

In the runs page, after clicking on a segment that opens the side menu the vertical line updates to the new position without mouse movement.

Changelog

Mose hover vertical line on a timeline updates after timeline resize

Refactor MousePositionProvider to improve reactivity during resize, scroll, and layout shifts.
@changeset-bot
Copy link

changeset-bot bot commented Dec 11, 2025

⚠️ No Changeset found

Latest commit: b4d6d78

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Walkthrough

The Timeline component was refactored to improve position tracking and synchronization across multiple event sources. The component now uses a computeFromClient() helper function to normalize mouse positions within the container. A ResizeObserver was added to monitor container size changes, and window resize/scroll listeners were introduced with scroll capture. A RequestAnimationFrame loop was implemented to continuously recompute position from the last recorded client coordinates, ensuring position accuracy persists during layout changes and animation ticks. Import statements were updated to reflect these changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–25 minutes

  • The new computeFromClient() function and boundary-checking logic should be verified for correctness
  • The ResizeObserver setup and cleanup to ensure no memory leaks
  • The RAF loop implementation, including proper cancellation and state synchronization with lastClient
  • Event listener attachment/removal (resize, scroll, mouse) to ensure no duplicate listeners or memory leaks
  • Interaction between the RAF loop and handleMouseMove to verify position updates are coherent across both code paths

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly relates to the main fix of updating the mouse hover timeline position after resize, matching the changeset's primary objective.
Description check ✅ Passed The description follows the template structure with completed sections for testing and changelog, though the changelog contains a typo ('Mose' instead of 'Mouse').
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/tri-6732-hover-vertical-timeline-does-not-update-on-task-runs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/webapp/app/components/primitives/Timeline.tsx (1)

22-23: Consider clearing lastClient on mouse leave.

The refs are correctly used to store state that shouldn't trigger re-renders. However, lastClient.current retains the last mouse position even after the mouse exits the container. While not critical, clearing it in the onMouseLeave handler would be more precise.

Apply this diff to clear the ref on mouse leave:

      <div
        ref={ref}
        onMouseEnter={handleMouseMove}
-       onMouseLeave={() => setPosition(undefined)}
+       onMouseLeave={() => {
+         setPosition(undefined);
+         lastClient.current = null;
+       }}
        onMouseMove={handleMouseMove}
        style={{ width: "100%", height: "100%" }}
      >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f7f993 and b4d6d78.

📒 Files selected for processing (1)
  • apps/webapp/app/components/primitives/Timeline.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/components/primitives/Timeline.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/primitives/Timeline.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/primitives/Timeline.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/primitives/Timeline.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/primitives/Timeline.tsx
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/components/primitives/Timeline.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
apps/webapp/app/components/primitives/Timeline.tsx (4)

8-8: LGTM!

The addition of useEffect is necessary for the new resize/scroll handling and animation frame logic.


25-41: LGTM!

The computeFromClient helper effectively centralizes position calculation logic. The boundary checks correctly prevent position updates when the cursor is outside the container.


43-49: LGTM!

The updated mouse handler correctly stores the last client coordinates and delegates calculation to computeFromClient.


52-75: LGTM!

The resize and scroll handling effectively addresses the core issue. Using ResizeObserver for container changes and scroll capture for ancestor scrolling are appropriate choices. The cleanup logic properly disconnects all observers and listeners.

Comment on lines +77 to +91
useEffect(() => {
if (position === undefined || !lastClient.current) return;

const tick = () => {
const lc = lastClient.current;
if (lc) computeFromClient(lc.clientX, lc.clientY);
rafId.current = requestAnimationFrame(tick);
};

rafId.current = requestAnimationFrame(tick);
return () => {
if (rafId.current !== null) cancelAnimationFrame(rafId.current);
rafId.current = null;
};
}, [position, computeFromClient]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Optimize the animation frame loop to reduce performance overhead.

The continuous RAF loop runs at ~60fps whenever the mouse is hovering, repeatedly calling getBoundingClientRect() even when the container is static. This forced layout operation on every frame can impact performance, especially with multiple timeline instances or on lower-end devices.

Consider these optimizations:

  1. Add a transition/animation detector to only run the RAF loop when the container is actually animating
  2. Use a throttle or debounce mechanism to reduce frequency
  3. Add a flag to control when continuous updates are needed

Additionally, the effect's dependency on position causes the RAF loop to restart whenever position updates during animations, which is inefficient.

Example: Only run RAF during transitions

  useEffect(() => {
-   if (position === undefined || !lastClient.current) return;
+   if (position === undefined || !lastClient.current || !ref.current) return;
+   
+   // Check if container or ancestors are transitioning/animating
+   const isAnimating = () => {
+     if (!ref.current) return false;
+     const styles = window.getComputedStyle(ref.current);
+     // Check for ongoing transitions or animations
+     return styles.transition !== 'none' || styles.animation !== 'none';
+   };

    const tick = () => {
      const lc = lastClient.current;
-     if (lc) computeFromClient(lc.clientX, lc.clientY);
-     rafId.current = requestAnimationFrame(tick);
+     if (lc) {
+       computeFromClient(lc.clientX, lc.clientY);
+       // Only continue RAF if actively animating
+       if (isAnimating()) {
+         rafId.current = requestAnimationFrame(tick);
+       } else {
+         rafId.current = null;
+       }
+     }
    };

    rafId.current = requestAnimationFrame(tick);
    return () => {
      if (rafId.current !== null) cancelAnimationFrame(rafId.current);
      rafId.current = null;
    };
  }, [position, computeFromClient]);

Or consider removing the RAF loop entirely and relying on the ResizeObserver and scroll/resize listeners, which should handle most layout changes adequately.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/webapp/app/components/primitives/Timeline.tsx around lines 77–91, the
current RAF loop runs continuously while hovering and forces layout each frame;
instead start/stop RAF only when the container is actually animating or needs
continuous updates: add a ref/boolean (e.g., needsContinuousUpdate or
isAnimating) and detect animations/transitions via element.getAnimations() or
getComputedStyle(elt).transitionDuration > 0 (and listen for
animationend/transitionend to clear the flag), run requestAnimationFrame only
when that flag is true, cancel it when the flag clears, and replace the
RAF-for-everything approach with ResizeObserver and scroll/resize listeners (or
a throttled handler) to handle static layout changes; also avoid restarting the
effect on every mouse move by removing position from the dependency array and
using refs for computeFromClient, and add a small throttle/debounce on
computeFromClient to reduce frequency while preserving responsiveness.

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