-
-
Notifications
You must be signed in to change notification settings - Fork 958
fix(webapp):> Fix for mouse hover on a timeline not being updates after resize #2780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(webapp):> Fix for mouse hover on a timeline not being updates after resize #2780
Conversation
Refactor MousePositionProvider to improve reactivity during resize, scroll, and layout shifts.
⚠️ No Changeset found
Latest commit: 12f508a
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
WalkthroughThe Timeline component gained mouse-position tracking and layout-resync logic: Estimated code review effort🎯 4 (Complex) | ⏱️ ~30–60 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 clearinglastClienton mouse leave.The refs are correctly used to store state that shouldn't trigger re-renders. However,
lastClient.currentretains the last mouse position even after the mouse exits the container. While not critical, clearing it in theonMouseLeavehandler 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
📒 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
envexport ofenv.server.tsinstead of directly accessingprocess.envin 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/corein 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
useEffectis necessary for the new resize/scroll handling and animation frame logic.
25-41: LGTM!The
computeFromClienthelper 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
ResizeObserverfor container changes and scroll capture for ancestor scrolling are appropriate choices. The cleanup logic properly disconnects all observers and listeners.
There was a problem hiding this 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)
14-17: Prefertypeoverinterfaceper coding guidelines.As per the project's coding guidelines for TypeScript files, use
typeinstead ofinterface.-interface MousePosition { - x: number; - y: number; -} +type MousePosition = { + x: number; + y: number; +};
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
envexport ofenv.server.tsinstead of directly accessingprocess.envin 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/corein 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 / webapp / 🧪 Unit Tests: Webapp (3, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
- GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
- GitHub Check: typecheck / typecheck
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
apps/webapp/app/components/primitives/Timeline.tsx (4)
22-23: LGTM!Good use of refs for tracking mutable state without triggering re-renders.
25-41: LGTM!Clean implementation with proper bounds checking and stable callback reference.
43-49: LGTM!Properly stores last client coordinates for recalculation on layout changes.
51-75: LGTM!Correct use of
ResizeObserverand event listeners with proper cleanup. The capture phase for scroll events appropriately catches ancestor scrolls that affect the bounding rect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isAnimating() check doesn't detect active animations correctly.
getComputedStyle().transition returns the defined CSS transition value (e.g., "width 0.3s ease"), not whether a transition is currently running. If the element or its ancestors have any CSS transitions defined, this will always return true, causing the RAF loop to run perpetually while hovering.
Use the Web Animations API to detect active animations:
const isAnimating = () => {
if (!ref.current) return false;
- const styles = window.getComputedStyle(ref.current);
- return styles.transition !== "none" || styles.animation !== "none";
+ return ref.current.getAnimations({ subtree: false }).length > 0;
};Alternatively, consider listening to transitionstart/transitionend events to set a flag, which avoids polling entirely.
📝 Committable suggestion
!!️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
🤖 Prompt for AI Agents
In apps/webapp/app/components/primitives/Timeline.tsx around lines 80-84,
replace the current isAnimating() logic (which checks computed styles) with a
real runtime check using the Web Animations API: call
ref.current.getAnimations({subtree: true}) and return true if any animation's
playState === 'running'; if getAnimations is not available (older browsers),
fall back to adding transitionstart/animationstart and
transitionend/animationend listeners on the element to set/clear an internal
isAnimatingFlag and have isAnimating() return that flag. This ensures the RAF
loop only runs while animations are actually active.
...t-update-on-task-runs
PR Review: Fix mouse hover on timeline not updating after resize
Thanks for this contribution! This PR addresses a real UX issue where the vertical hover line on the timeline doesn't update its position when the container resizes (e.g., when opening a side panel). Here's my feedback:
Code Quality & Design
Overall approach is reasonable - Storing the last client position and recalculating on resize/scroll is the right pattern for this problem.
Good practices observed:
- Proper cleanup in
useEffectreturn functions - Using
useCallbackappropriately for memoized handlers - Using capture phase for scroll events to catch ancestor scrolling
Potential Issues
-
Animation detection logic may be unreliable (lines 80-84)
const isAnimating = () => { const styles = window.getComputedStyle(ref.current); return styles.transition !== "none" || styles.animation !== "none"; };
This checks if a transition/animation property is set, not if an animation is currently running. Elements with
transition: all 0.3swould report as "animating" even when idle, causing unnecessary RAF loops. Consider:- Using the
transitionend/animationendevents instead - Or checking for specific animation states
- Using the
-
Potential infinite RAF loop (lines 77-103)
The second
useEffectdepends onpositionin its dependency array and callscomputeFromClientwhich can updateposition. IfisAnimating()always returns true (due to the issue above), this creates an infinite loop that runs every frame and never stops. This could cause significant performance issues. -
Memory of stale mouse position (line 22)
lastClient.currentis set onmouseMovebut never cleared onmouseLeave. When the user moves mouse out and then the container resizes, you'll recalculate position from a stale coordinate. This might cause the hover line to briefly appear then disappear (since the bounds check will fail), which could cause visual flickering.Consider clearing
lastClient.currentwhenonMouseLeavefires:onMouseLeave={() => { lastClient.current = null; setPosition(undefined); }}
Performance Considerations
-
Scroll listener is very broad - Adding a capture-phase scroll listener to
windowwill fire on every scroll event anywhere in the document. For complex pages, this could add overhead. Consider debouncing/throttling the scroll handler. -
Multiple getComputedStyle calls - Each
tick()call in the RAF loop callsgetComputedStyle(), which can force layout. In the worst case (if animation detection is stuck), this runs every frame.
Suggestions
-
Simplest fix for the animation issue - Remove the animation-tracking useEffect entirely and just rely on ResizeObserver. ResizeObserver will naturally fire during CSS animations that change dimensions. If animations don't change the size, the position shouldn't change either.
-
If animation tracking is needed - Use event listeners:
useEffect(() => { const el = ref.current; if (!el) return; const onAnimationFrame = () => { const lc = lastClient.current; if (lc) computeFromClient(lc.clientX, lc.clientY); }; el.addEventListener('transitionrun', onAnimationFrame); el.addEventListener('animationstart', onAnimationFrame); // ... cleanup }, [computeFromClient]);
-
Consider debouncing scroll handler:
const onRecalc = useMemo(() => debounce(() => { const lc = lastClient.current; if (lc) computeFromClient(lc.clientX, lc.clientY); }, 16), // ~1 frame [computeFromClient]);
Test Coverage
No tests exist for this component (Timeline.tsx). While the component is primarily visual, the new logic (especially the animation detection and RAF handling) would benefit from unit tests to prevent regressions.
Summary
The core idea is sound, but there are potential edge cases that could cause performance issues or visual glitches. The animation detection logic in the second useEffect is the main concern - it could lead to unnecessary continuous RAF loops on elements with always-present transition/animation CSS properties.
Recommendation: Consider simplifying by removing the animation-tracking useEffect and relying solely on ResizeObserver + scroll/resize listeners. If the animation tracking is essential for the use case, please use event-based detection (transitionrun/animationstart) instead of polling getComputedStyle.
Refactor MousePositionProvider to improve reactivity during resize, scroll, and layout shifts.
Closes #
✅ Checklist
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