Skip to content

Drops highlight#1552

Merged
simo6529 merged 15 commits intomainfrom
drops-highlight
Oct 21, 2025
Merged

Drops highlight#1552
simo6529 merged 15 commits intomainfrom
drops-highlight

Conversation

@prxt6529
Copy link
Copy Markdown
Collaborator

@prxt6529 prxt6529 commented Oct 17, 2025

Summary by CodeRabbit

  • New Features

    • Visual highlighting for list items with configurable highlight and fade durations; highlights trigger based on visibility and persist while visible during scrolling.
    • New wrapper component to manage highlight lifecycle and active/scroll state.
  • Bug Fixes

    • Safer handling when intersection observation is unavailable to avoid runtime errors in non-browser/SSR environments.
  • Refactor

    • Centralized item wrapping to streamline active/scroll state and visibility handling.
  • Tests

    • New and updated unit tests covering highlight rendering, timing, persistence, restart behavior, and standardized mocks.

Signed-off-by: prxt6529 <prxt@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 17, 2025

Walkthrough

Adds a new HighlightDropWrapper component that observes element visibility (rAF + IntersectionObserver guard) and drives timed highlight → fade phases; replaces per-item outer divs in DropsList to pass active/scrollContainer; adds tests for the wrapper and updates DropsList tests; guards useIntersectionObserver for non-browser environments.

Changes

Cohort / File(s) Summary
Tests — DropsList adjustments
__tests__/components/drops/view/DropsList.test.tsx
Adds a mock for HighlightDropWrapper that records props (highlightProps), standardizes mock export shape, resets highlightProps in beforeEach, and updates assertions to expect data-testid="highlight" elements; minor formatting tweaks.
Tests — New component tests
__tests__/components/drops/view/HighlightDropWrapper.test.tsx
New test suite: polyfills DOMRect/requestAnimationFrame, mocks getBoundingClientRect, uses fake timers to verify child rendering, highlight lifecycle (highlightMs → fadeMs), restart behavior, and cleanup.
Component — DropsList update
components/drops/view/DropsList.tsx
Replaces per-item outer div with HighlightDropWrapper; passes active and scrollContainer props; updates imports and preserves memoized drop rendering.
Component — New wrapper
components/drops/view/HighlightDropWrapper.tsx
Adds default-exported forwardRef component that monitors visibility (rAF + IntersectionObserver), manages highlight → fade timed phases via timeouts, accepts highlightMs, fadeMs, visibilityThreshold, active, scrollContainer, and cleans up rAF/timeouts on unmount or dependency changes.
Hook — Intersection guard
hooks/scroll/useIntersectionObserver.ts
Adds runtime guard to return early when IntersectionObserver is undefined to avoid creating observers in non-browser/SSR environments.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Drop as DropItem
  participant HDW as HighlightDropWrapper
  participant IO as IntersectionObserver (guarded)
  participant RAF as rAF monitor
  participant View as ScrollContainer/Viewport
  participant Timers as highlight/fade timers

  Drop->>HDW: render with props (active, scrollContainer)
  activate HDW
  HDW->>IO: create observer (if available)
  HDW->>RAF: start visibility monitoring loop
  RAF->>View: read getBoundingClientRect → compute visible ratio
  View-->>RAF: rect/ratio
  RAF->>HDW: ratio ≥ visibilityThreshold
  HDW->>Timers: set highlight timeout (highlightMs)
  Note right of HDW: highlighted state begins
  Timers->>HDW: highlight timeout elapsed → set fade timeout (fadeMs)
  Note right of HDW: fading state
  Timers->>HDW: fade timeout elapsed → clear highlight (idle)
  Drop->>HDW: active false or unmount
  HDW->>RAF: cancel rAF
  HDW->>IO: disconnect observer
  HDW->>Timers: clear timers
  deactivate HDW
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • ragnep

Poem

🐇 I peek at edges when the scroll drifts by,
I flash a tiny glow, then slowly sigh.
Timers tick, I fade, then bloom anew,
I watch each drop until it’s through.
— a rabbit's quiet cheer

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Drops highlight" is directly related to the main changes in the pull request. The changeset introduces a new HighlightDropWrapper component that implements visibility-based highlighting behavior for drops, modifies DropsList to use this wrapper, adds comprehensive tests for the highlighting functionality, and adds a runtime guard to the intersection observer hook. The title accurately reflects that the primary objective is to implement highlighting functionality for drops, and a teammate scanning the repository history would understand that this change introduces a highlighting feature for the drops component.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ 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 drops-highlight

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
Copy Markdown

@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: 0

🧹 Nitpick comments (2)
components/drops/view/HighlightDropWrapper.tsx (2)

72-90: Consider replacing interval with single setTimeout.

The current approach polls every 150ms to check if the highlight has expired. This could be optimized by calculating the exact time remaining and using a single setTimeout instead.

Apply this diff to optimize the timing logic:

 useEffect(() => {
   if (!active) return;
-  const interval = window.setInterval(() => {
-    if (Date.now() >= highlightUntil && highlightUntil !== 0) {
-      setHighlightUntil(0);
-      setIsFading(true);
-      if (fadeTimeoutRef.current) clearTimeout(fadeTimeoutRef.current);
-      fadeTimeoutRef.current = window.setTimeout(() => {
-        setIsFading(false);
-      }, fadeMs);
-      clearInterval(interval);
-    }
-  }, 150);
+  if (highlightUntil === 0) return;
+  
+  const timeRemaining = highlightUntil - Date.now();
+  if (timeRemaining <= 0) return;
+  
+  const timeoutId = window.setTimeout(() => {
+    setHighlightUntil(0);
+    setIsFading(true);
+    if (fadeTimeoutRef.current) clearTimeout(fadeTimeoutRef.current);
+    fadeTimeoutRef.current = window.setTimeout(() => {
+      setIsFading(false);
+    }, fadeMs);
+  }, timeRemaining);
 
   return () => {
-    clearInterval(interval);
+    clearTimeout(timeoutId);
     if (fadeTimeoutRef.current) clearTimeout(fadeTimeoutRef.current);
   };
 }, [active, highlightUntil, fadeMs]);

92-92: Consider memoizing or moving to state if performance becomes a concern.

The isHighlighted computation runs on every render using Date.now(). While this is likely fine for most use cases, if you notice performance issues with many highlighted drops, consider moving this to state or using useMemo.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a0dc65 and bab2f63.

📒 Files selected for processing (4)
  • __tests__/components/drops/view/DropsList.test.tsx (3 hunks)
  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx (1 hunks)
  • components/drops/view/DropsList.tsx (3 hunks)
  • components/drops/view/HighlightDropWrapper.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/drops/view/HighlightDropWrapper.tsx
  • __tests__/components/drops/view/DropsList.test.tsx
  • components/drops/view/DropsList.tsx
  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • components/drops/view/HighlightDropWrapper.tsx
  • __tests__/components/drops/view/DropsList.test.tsx
  • components/drops/view/DropsList.tsx
  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/drops/view/DropsList.test.tsx
  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/drops/view/DropsList.test.tsx
  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
{**/__tests__/**,**/*.test.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place tests in tests directories or alongside components as ComponentName.test.tsx

Files:

  • __tests__/components/drops/view/DropsList.test.tsx
  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
{**/__tests__/**,**/*.test.{ts,tsx}}

📄 CodeRabbit inference engine (AGENTS.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/drops/view/DropsList.test.tsx
  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
🧬 Code graph analysis (2)
components/drops/view/DropsList.tsx (2)
components/drops/view/HighlightDropWrapper.tsx (1)
  • HighlightDropWrapper (15-109)
components/waves/drops/VirtualScrollWrapper.tsx (1)
  • VirtualScrollWrapper (56-181)
__tests__/components/drops/view/HighlightDropWrapper.test.tsx (1)
components/drops/view/HighlightDropWrapper.tsx (1)
  • HighlightDropWrapper (15-109)
⏰ 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). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
components/drops/view/DropsList.tsx (1)

124-157: LGTM! Clean integration of HighlightDropWrapper.

The replacement of the div wrapper with HighlightDropWrapper is well-implemented. The active state and scrollContainer reference are correctly passed, and the existing component hierarchy is preserved.

__tests__/components/drops/view/DropsList.test.tsx (2)

41-47: LGTM! Consistent mock pattern.

The HighlightDropWrapper mock follows the same pattern as other component mocks and correctly tracks props while rendering children.


82-86: LGTM! Appropriate test coverage.

The addition of the highlight element assertion correctly validates that HighlightDropWrapper is being rendered for each drop.

__tests__/components/drops/view/HighlightDropWrapper.test.tsx (3)

4-32: LGTM! Necessary test environment setup.

The polyfills for DOMRect and requestAnimationFrame are required for the component's visibility checking logic. The @ts-ignore usage is acceptable in test setup context.


69-87: LGTM! Comprehensive timing verification.

The test correctly validates the highlight application, the fade-out after the highlight period, and the final cleanup. The use of act() with timer advances properly simulates the async behavior.


89-122: LGTM! Restart behavior validated.

The test correctly verifies that deactivating and reactivating the wrapper restarts the highlight from the beginning, which is the expected behavior.

components/drops/view/HighlightDropWrapper.tsx (2)

38-63: LGTM! Robust visibility detection.

The visibility checking logic correctly calculates the intersection ratio and handles edge cases (zero dimensions) appropriately. The continuous RAF loop ensures timely detection of visibility changes.


15-109: LGTM! Well-structured highlight wrapper component.

The component implements a sophisticated highlight feature with:

  • Visibility-aware highlight extension
  • Smooth fade transitions
  • Proper cleanup of side effects
  • TypeScript typing throughout

The implementation follows all coding guidelines and handles edge cases appropriately.

Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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: 4

🧹 Nitpick comments (2)
components/drops/view/HighlightDropWrapper.tsx (2)

66-101: Consider IntersectionObserver API for visibility detection.

The manual intersection calculation using getBoundingClientRect and requestAnimationFrame polling reinvents functionality provided by the standard IntersectionObserver API. IntersectionObserver is more efficient and declarative.

Example refactor:

useEffect(() => {
  if (!active) return;
  
  const el = innerRef.current;
  if (!el) return;
  
  setIsFading(false);
  lastExtendedRef.current = false;
  setHighlightUntil(Date.now() + highlightMs);
  
  const observer = new IntersectionObserver(
    ([entry]) => {
      if (entry.isIntersecting && entry.intersectionRatio >= visibilityThreshold && !lastExtendedRef.current) {
        setHighlightUntil(Date.now() + highlightMs);
        lastExtendedRef.current = true;
        observer.disconnect();
      }
    },
    {
      root: scrollContainer,
      threshold: visibilityThreshold,
    }
  );
  
  observer.observe(el);
  
  return () => observer.disconnect();
}, [active, highlightMs, scrollContainer, visibilityThreshold]);

128-137: Type casting for setTimeout is a known TypeScript issue.

The as unknown as number casting pattern addresses the TypeScript discrepancy between browser (number) and Node.js (NodeJS.Timeout) return types for setTimeout. While this works, consider typing the refs as ReturnType<typeof setTimeout> | null for better type safety.

Apply this diff:

-    const rafRef = useRef<number | null>(null);
-    const fadeTimeoutRef = useRef<number | null>(null);
-    const endTimeoutRef = useRef<number | null>(null);
+    const rafRef = useRef<number | null>(null);
+    const fadeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+    const endTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

Then remove the type assertions:

-      endTimeoutRef.current = window.setTimeout(() => {
+      endTimeoutRef.current = setTimeout(() => {
         setHighlightUntil(0);
         setIsFading(true);
         
-        fadeTimeoutRef.current = window.setTimeout(() => {
+        fadeTimeoutRef.current = setTimeout(() => {
           setIsFading(false);
-        }, fadeMs) as unknown as number;
-      }, remaining) as unknown as number;
+        }, fadeMs);
+      }, remaining);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bab2f63 and fc8b3f8.

📒 Files selected for processing (2)
  • components/drops/view/DropsList.tsx (3 hunks)
  • components/drops/view/HighlightDropWrapper.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/drops/view/DropsList.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/drops/view/HighlightDropWrapper.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • components/drops/view/HighlightDropWrapper.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). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
components/drops/view/HighlightDropWrapper.tsx (4)

6-15: LGTM: Proper readonly usage.

The interface correctly uses readonly on all props as required by the coding guidelines.


151-163: Time-based computation in render relies on state updates.

Line 151 computes isHighlighted using Date.now(), which is not reactive. The component relies on the timeout effects to trigger re-renders via state updates. In theory, there could be a brief window where isHighlighted is stale between when the time expires and when the timeout callback executes, though this is likely negligible in practice.

The current implementation is acceptable, but be aware of this timing dependency.


45-52: LGTM: Proper ref forwarding.

The setNode callback correctly forwards the ref to both the internal innerRef and the forwardedRef, handling both function and object ref types.


165-174: LGTM: Clean render and export.

The component properly renders with computed classes, sets displayName for debugging, and uses default export as expected.

Comment thread components/drops/view/HighlightDropWrapper.tsx Outdated
Comment thread components/drops/view/HighlightDropWrapper.tsx Outdated
Comment thread components/drops/view/HighlightDropWrapper.tsx Outdated
Comment thread components/drops/view/HighlightDropWrapper.tsx Outdated
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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 (3)
components/drops/view/HighlightDropWrapper.tsx (3)

51-58: Consider simplifying the ref forwarding logic.

The type assertion on lines 55-57 is verbose. You could simplify this pattern by checking the forwardedRef type more cleanly or using a type guard.

Here's a cleaner alternative:

 const setNode = (node: HTMLDivElement | null) => {
   innerRef.current = node;
-  if (typeof forwardedRef === "function") forwardedRef(node);
-  else if (forwardedRef)
-    (
-      forwardedRef as React.MutableRefObject<HTMLDivElement | null>
-    ).current = node;
+  if (typeof forwardedRef === "function") {
+    forwardedRef(node);
+  } else if (forwardedRef) {
+    forwardedRef.current = node;
+  }
 };

89-130: Consider using IntersectionObserver API instead of manual RAF-based tracking.

The manual intersection calculation using getBoundingClientRect and requestAnimationFrame is less efficient and harder to maintain than the standard IntersectionObserver API. IntersectionObserver is specifically designed for visibility tracking, is browser-optimized, and handles edge cases like CSS transforms automatically.

Example refactor using IntersectionObserver:

const trackVisibilityOnce = useCallback(() => {
  const el = innerRef.current;
  if (!el) return;

  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.intersectionRatio >= visibilityThreshold && !lastExtendedRef.current) {
          lastExtendedRef.current = true;
          runHighlightWindow();
          observer.disconnect();
        }
      });
    },
    {
      root: scrollContainer,
      threshold: visibilityThreshold,
    }
  );

  observer.observe(el);
  
  return () => observer.disconnect();
}, [runHighlightWindow, scrollContainer, visibilityThreshold]);

Note: You would need to update the cleanup logic to disconnect the observer instead of canceling RAF.


159-169: Consider simplifying the class building logic for readability.

The nested ternaries in the array make the class logic harder to follow. A more straightforward approach would improve maintainability.

Here's a cleaner alternative:

-const classes = [
-  className,
-  isHighlighted
-    ? "tw-bg-[#25263f] tw-transition-colors tw-duration-500"
-    : "",
-  !isHighlighted && isFading
-    ? "tw-bg-transparent tw-transition-colors tw-duration-500"
-    : "",
-]
-  .filter(Boolean)
-  .join(" ");
+const transitionClasses = "tw-transition-colors tw-duration-500";
+const classes = [
+  className,
+  transitionClasses,
+  isHighlighted && "tw-bg-[#25263f]",
+  isFading && "tw-bg-transparent",
+]
+  .filter(Boolean)
+  .join(" ");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff4a54 and 5cf95a5.

📒 Files selected for processing (1)
  • components/drops/view/HighlightDropWrapper.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/drops/view/HighlightDropWrapper.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • components/drops/view/HighlightDropWrapper.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). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment thread components/drops/view/HighlightDropWrapper.tsx
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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)
components/drops/view/HighlightDropWrapper.tsx (1)

83-94: Consider typing timeout refs as NodeJS.Timeout to avoid casting.

The as unknown as number casts on lines 87 and 90 work but indicate a type mismatch. Consider declaring highlightTimeoutRef and fadeTimeoutRef as useRef<ReturnType<typeof setTimeout> | null>(null) to avoid casting.

Apply this change to the ref declarations (lines 46-47):

-    const highlightTimeoutRef = useRef<number | null>(null);
-    const fadeTimeoutRef = useRef<number | null>(null);
+    const highlightTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+    const fadeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

Then remove the casts from the setTimeout calls:

-      highlightTimeoutRef.current = globalThis.setTimeout(() => {
+      highlightTimeoutRef.current = setTimeout(() => {
         setPhase("fading");

-        fadeTimeoutRef.current = globalThis.setTimeout(() => {
+        fadeTimeoutRef.current = setTimeout(() => {
           setPhase("idle");
-        }, fadeMs) as unknown as number;
-      }, highlightMs) as unknown as number;
+        }, fadeMs);
+      }, highlightMs);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cf95a5 and dd5e1fb.

📒 Files selected for processing (2)
  • components/drops/view/HighlightDropWrapper.tsx (1 hunks)
  • hooks/scroll/useIntersectionObserver.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/drops/view/HighlightDropWrapper.tsx
  • hooks/scroll/useIntersectionObserver.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • components/drops/view/HighlightDropWrapper.tsx
🧬 Code graph analysis (1)
components/drops/view/HighlightDropWrapper.tsx (2)
hooks/scroll/useIntersectionObserver.ts (1)
  • useIntersectionObserver (9-49)
helpers/Helpers.ts (1)
  • classNames (706-707)
⏰ 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). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (9)
hooks/scroll/useIntersectionObserver.ts (1)

22-24: LGTM! SSR-safe guard added.

The runtime check prevents errors in environments where IntersectionObserver is unavailable (e.g., SSR contexts). The early return is correctly placed before observer instantiation.

components/drops/view/HighlightDropWrapper.tsx (8)

14-23: LGTM! Props interface is well-defined.

All props correctly use the readonly modifier as required by coding guidelines, and types are appropriate.


42-53: LGTM! State and refs properly initialized.

The refs and state correctly track the highlight lifecycle, and maxVisibilityWaitMs addresses the previous review concern about unbounded RAF loops.


55-81: LGTM! Helper functions are correctly implemented.

The setNode function properly forwards refs, and cleanup functions stopRAF and clearTimers are appropriately wrapped in useCallback.


96-159: RAF-based visibility tracking provides fallback for IntersectionObserver.

The manual intersection calculation provides a fallback when IntersectionObserver is unavailable (SSR) and ensures visibility is caught even if the observer misses the initial intersection. The maximum wait time guard on lines 111-114 correctly prevents infinite loops.


179-207: LGTM! Observer hook and cleanup effects are properly configured.

The IntersectionObserver is correctly configured with freezeOnceVisible: true and enabled: active, and cleanup effects properly stop RAF and clear timers.


220-236: LGTM! Render logic correctly applies highlight and fade states.

The CSS classes are properly computed based on phase state, using TailwindCSS as required by coding guidelines. The transition effects are appropriately applied during highlight and fade phases.


240-241: LGTM! Display name and export are correct.

Setting displayName is a best practice for forwardRef components to aid debugging.


209-218: Verify the intended behavior for highlight triggering when active transitions and element visibility changes.

Your concern is valid. Code inspection confirms the double-trigger behavior exists:

  1. Line 215 calls runHighlightWindow() immediately when active becomes true
  2. Line 214 resets lastExtendedRef.current = false, allowing re-triggering
  3. Line 143 (inside trackVisibilityOnce) calls runHighlightWindow() again if visibility is detected
  4. Line 84 (clearTimers()) cancels prior timers, restarting the highlight cycle

The tests confirm the highlight appears immediately, but they don't verify whether the restart on visibility detection is intentional or an edge case.

Questions for clarification:

  1. Is the initial call on line 215 meant as a "warm-up," with the real highlight starting only after visibility is confirmed (line 143)?
  2. Or should the highlight trigger only once—checking visibility first before calling runHighlightWindow()?

If the current behavior is intentional, consider adding a comment explaining the warm-up pattern. If unintentional, the reset of lastExtendedRef on line 214 may need adjustment.

Comment thread components/drops/view/HighlightDropWrapper.tsx
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
components/drops/view/HighlightDropWrapper.tsx (1)

162-178: Past issue already resolved.

The previous review comment about removing scrollContainer from the dependency array has already been addressed. The current dependency array correctly includes only [active, runHighlightWindow, stopRAF, visibilityThreshold].

🧹 Nitpick comments (1)
components/drops/view/HighlightDropWrapper.tsx (1)

54-54: Consider moving constant outside the component.

maxVisibilityWaitMs is a constant that never changes but is recreated on every render and included in the trackVisibilityOnce dependency array (line 155). While not harmful, it's more idiomatic to define constants outside the component body.

Apply this diff to move the constant outside:

+const MAX_VISIBILITY_WAIT_MS = 4000;
+
 const HighlightDropWrapper = forwardRef<
   HTMLDivElement,
   HighlightDropWrapperProps
@@ -51,7 +53,6 @@
     const lastExtendedRef = useRef(false);
     const prevActiveRef = useRef(false);
-    const maxVisibilityWaitMs = 4000;

Then update the reference at line 112:

-        if (elapsed >= maxVisibilityWaitMs) {
+        if (elapsed >= MAX_VISIBILITY_WAIT_MS) {

And remove from the dependency array at line 155:

     }, [
-      maxVisibilityWaitMs,
       runHighlightWindow,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd5e1fb and aebff59.

📒 Files selected for processing (1)
  • components/drops/view/HighlightDropWrapper.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/drops/view/HighlightDropWrapper.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • components/drops/view/HighlightDropWrapper.tsx
🧬 Code graph analysis (1)
components/drops/view/HighlightDropWrapper.tsx (2)
hooks/scroll/useIntersectionObserver.ts (1)
  • useIntersectionObserver (9-49)
helpers/Helpers.ts (1)
  • classNames (706-707)
⏰ 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). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
components/drops/view/HighlightDropWrapper.tsx (4)

65-95: LGTM: Callbacks are correctly implemented.

The stopRAF, clearTimers, and runHighlightWindow callbacks are properly memoized with correct dependencies and implement the phase management logic accurately.


97-160: LGTM: Visibility tracking is correctly implemented.

The RAF-based visibility tracking includes proper timeout handling (addresses previous concern about infinite loops), correct intersection calculations, and appropriate dependencies.


210-219: Verify the dual highlight trigger logic.

When active becomes true, the code starts the highlight cycle immediately (line 216) and also begins visibility tracking (line 217). If the element achieves the visibility threshold soon after (detected by either the RAF loop or IntersectionObserver), it will restart the highlight window because lastExtendedRef.current is still false at that point.

This means:

  • If the element is already visible when activated, the highlight may restart on the next frame, causing timer churn
  • If the element is not yet visible, the highlight runs even while off-screen, then restarts when it becomes visible

Please confirm this is the intended behavior. If the highlight should only run when the element is visible, consider setting lastExtendedRef.current = true after calling runHighlightWindow() at line 216, or deferring the highlight until visibility is achieved.


241-242: LGTM: Export configuration is correct.

Setting displayName is a best practice for debugging, and the default export is appropriate.

Comment thread components/drops/view/HighlightDropWrapper.tsx
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 579e493 and 5eb5dff.

📒 Files selected for processing (1)
  • components/drops/view/HighlightDropWrapper.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • components/drops/view/HighlightDropWrapper.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • components/drops/view/HighlightDropWrapper.tsx
🧬 Code graph analysis (1)
components/drops/view/HighlightDropWrapper.tsx (2)
hooks/scroll/useIntersectionObserver.ts (1)
  • useIntersectionObserver (9-49)
helpers/Helpers.ts (1)
  • classNames (706-707)
⏰ 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). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
components/drops/view/HighlightDropWrapper.tsx (5)

1-24: LGTM!

The props interface correctly uses readonly on all props per coding guidelines, and the component properly implements functional component patterns with TypeScript.


26-97: LGTM!

The MAX_VISIBILITY_WAIT_MS timeout addresses the previous concern about infinite RAF loops. Ref forwarding and timer management callbacks are correctly implemented with appropriate dependencies.


99-174: LGTM!

The visibility tracking mechanisms are well-implemented:

  • RAF loop includes timeout protection and proper cleanup
  • IntersectionObserver callback dependencies are correct (previous scrollContainer issue resolved)
  • Both mechanisms coordinate through lastExtendedRef to avoid duplicate triggers

176-204: LGTM!

The useIntersectionObserver hook is properly configured with correct parameters, and cleanup effects appropriately stop RAF and clear timers on unmount and state changes.


217-244: LGTM!

The rendering logic correctly applies dynamic transition duration based on the fadeMs prop (previous hardcoded duration issue resolved). The useMemo optimization for transitionStyle is appropriate and prevents unnecessary object creation.

Comment thread components/drops/view/HighlightDropWrapper.tsx
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d72f13c and c2dd74a.

📒 Files selected for processing (1)
  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

**/*.{ts,tsx}: Use TypeScript for source code and follow existing code style and naming conventions
Adhere to clean code standards as measured by SonarQube

Files:

  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Implement React components as functional components using hooks (no class components)

Files:

  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
__tests__/components/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use @testing-library/react and @testing-library/user-event for React component tests

Files:

  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
{**/__tests__/**,**/*.test.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place tests in tests directories or alongside components as ComponentName.test.tsx

Files:

  • __tests__/components/drops/view/HighlightDropWrapper.test.tsx
{**/__tests__/**,**/*.test.{ts,tsx}}

📄 CodeRabbit inference engine (AGENTS.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/drops/view/HighlightDropWrapper.test.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). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
__tests__/components/drops/view/HighlightDropWrapper.test.tsx (1)

59-174: Comprehensive test coverage for highlight lifecycle.

The test suite thoroughly covers the HighlightDropWrapper behavior including rendering, highlight phases, reactivation, and edge cases. The use of fake timers with act() correctly simulates the timed transitions.

Comment thread __tests__/components/drops/view/HighlightDropWrapper.test.tsx
Comment thread __tests__/components/drops/view/HighlightDropWrapper.test.tsx
@sonarqubecloud
Copy link
Copy Markdown

@simo6529 simo6529 merged commit 5aa6e0f into main Oct 21, 2025
8 checks passed
@simo6529 simo6529 deleted the drops-highlight branch October 21, 2025 12:13
This was referenced Oct 22, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Jan 7, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Mar 19, 2026
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