Skip to content

Conversation

@prince8094
Copy link

@prince8094 prince8094 commented Dec 13, 2025

Description

This PR enforces folder selection during the onboarding flow to prevent users from proceeding with an incomplete setup.

Changes

  • Disabled the Next button when no folder is selected
  • Added inline validation message when the user attempts to proceed without selecting a folder
  • Added a guard in the onboarding logic to prevent state updates when no folder is chosen

Why

Previously, users could continue the onboarding process without selecting a folder, which led to empty gallery states and potential runtime issues. This change ensures a valid onboarding flow and improves user guidance.

Scope

  • Frontend-only change
  • No backend or API modifications
  • No changes to existing onboarding steps or layout

Fixes #704

@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

Adds delayed hover-intent for gallery feature cards (disabled on touch devices), wraps two navbar icons with tooltips ("Face Scanner" and "Profile"), introduces folder-selection validation UI, and minor formatting/README documentation updates.

Changes

Cohort / File(s) Change Summary
Documentation
README.md
Clarifies gallery feature card behavior: cards expand only on intentional desktop hover; hover effects disabled on touch devices.
Feature card hover-intent
landing-page/src/components/ui/Features.tsx
Replaced instantaneous hover with a 1s delayed hover-intent using internal state and timer (useRef/useState); touch-device detection guards hover behavior; styling and framer-motion usage updated to animate based on hover state.
Navigation tooltips
frontend/src/components/Navigation/Navbar/Navbar.tsx
Added Tooltip/TooltipProvider wrappers around FaceSearchDialog and the user avatar/settings link with labels "Face Scanner" and "Profile"; imports added and markup adjusted to include Tooltip Trigger/Content.
Onboarding validation
frontend/src/components/OnboardingSteps/FolderSetupStep.tsx
Added local error state, validation that prevents advancing without at least one selected folder, renders inline error message, and disables Next/Continue when no folder selected.
Minor formatting
frontend/src/components/Media/ImageCard.tsx
Fixed end-of-file newline/brace formatting only; no functional changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay attention to timer cleanup and edge cases in landing-page/src/components/ui/Features.tsx (hover delay, rapid enter/leave, SSR/touch detection).
  • Verify tooltip accessibility and keyboard focus behavior in frontend/src/components/Navigation/Navbar/Navbar.tsx.
  • Confirm onboarding validation in FolderSetupStep.tsx integrates with existing step flow and disables advancement correctly.
  • Minor: ensure ImageCard.tsx formatting change caused no linter or CI issues.

Possibly related PRs

Suggested labels

frontend

Suggested reviewers

  • rahulharpal1603

Poem

🐇 I waited one second to give cards some grace,

Tooltips whispered names in their place,
Touch skips the flutter, clicks keep the flow,
Folders demand choices before onward we go,
Hop, scan, and smile — a UI snug as snow.

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses issues #1 (tooltips for nav icons) and #2 (folder validation) but completely lacks the custom avatar upload functionality from issue #3. Either complete the custom avatar upload feature with Tauri file picker integration and SQLite storage, or scope the PR to only issues #1 and #2.
Out of Scope Changes check ⚠️ Warning The PR includes changes to README.md and ImageCard.tsx that are not mentioned in linked issue #704, appearing to be out of scope. Clarify the purpose of README.md and ImageCard.tsx changes, or remove them if they are not directly related to the stated objectives.
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.
Title check ❓ Inconclusive The title mentions 'delayed hover for feature cards' and 'remove emoji cursor', but the PR primarily implements tooltips for navigation icons and folder validation, with only partial feature card hover changes. Update the title to accurately reflect the main changes: e.g., 'Add navigation tooltips, delayed feature card hover, and folder selection validation'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

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

🧹 Nitpick comments (1)
frontend/src/components/Navigation/Navbar/Navbar.tsx (1)

73-84: LGTM! Tooltip implementation is correct.

The tooltip wrapping follows the correct pattern with TooltipProvider, Tooltip, TooltipTrigger (asChild), and TooltipContent. The extra div wrapper is necessary for the asChild prop to work properly.

Optional: Consider wrapping the entire Navbar component with a single TooltipProvider at the root level instead of individual providers for each tooltip. This reduces redundant provider instances:

export function Navbar() {
  const userName = useSelector(selectName);
  const userAvatar = useSelector(selectAvatar);
  const searchState = useSelector((state: any) => state.search);
  const isSearchActive = searchState.active;
  const queryImage = searchState.queryImage;
  const dispatch = useDispatch();
  
  return (
+   <TooltipProvider>
    <div className="sticky top-0 z-40 flex h-14 w-full items-center justify-between border-b pr-4 backdrop-blur">
      {/* ... rest of navbar ... */}
    </div>
+   </TooltipProvider>
  );
}

Then remove the individual TooltipProvider wrappers around each tooltip.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d07d817 and e2911dd.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • frontend/src/components/Media/ImageCard.tsx (1 hunks)
  • frontend/src/components/Navigation/Navbar/Navbar.tsx (3 hunks)
  • landing-page/src/components/ui/Features.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/Navigation/Navbar/Navbar.tsx (2)
frontend/src/components/ui/tooltip.tsx (4)
  • TooltipProvider (59-59)
  • Tooltip (59-59)
  • TooltipTrigger (59-59)
  • TooltipContent (59-59)
frontend/src/components/Dialog/FaceSearchDialog.tsx (1)
  • FaceSearchDialog (25-153)
🪛 Biome (2.1.2)
landing-page/src/components/ui/Features.tsx

[error] 57-57: Shouldn't redeclare 'motion'. Consider to delete it or rename it.

'motion' is defined here:

(lint/suspicious/noRedeclare)

🔇 Additional comments (5)
frontend/src/components/Media/ImageCard.tsx (1)

105-105: LGTM!

Formatting fix for end-of-file newline.

frontend/src/components/Navigation/Navbar/Navbar.tsx (1)

104-119: LGTM! User avatar tooltip implemented correctly.

The tooltip provides clear context for the profile settings link and follows the same correct pattern as the FaceSearchDialog tooltip.

landing-page/src/components/ui/Features.tsx (2)

76-79: LGTM! Excellent touch device detection.

Using matchMedia("(hover: none)") is the correct modern approach to detect touch-only devices. It's SSR-safe and avoids false positives on hybrid devices that support both touch and hover.


104-118: LGTM! Animation and styling correctly implement delayed hover.

The state-driven animate prop (instead of whileHover) combined with conditional className successfully prevents instant hover flashes. The border and shadow effects now only appear after the 1-second intentional hover delay, achieving the PR objective.

README.md (1)

67-68: LGTM! Documentation accurately describes the implementation.

The added note clearly explains the intentional hover behavior and aligns with the implementation in landing-page/src/components/ui/Features.tsx.

Comment on lines +56 to +57
import { useRef, useState, useEffect } from "react";
import { motion } from "framer-motion";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove duplicate import and unused import.

Line 57 duplicates the motion import from line 2, causing a redeclaration error. Additionally, useEffect is imported on line 56 but never used.

Apply this diff to fix the imports:

-import { useRef, useState, useEffect } from "react";
-import { motion } from "framer-motion";
+import { useRef, useState } from "react";
📝 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.

Suggested change
import { useRef, useState, useEffect } from "react";
import { motion } from "framer-motion";
import { useRef, useState } from "react";
🧰 Tools
🪛 Biome (2.1.2)

[error] 57-57: Shouldn't redeclare 'motion'. Consider to delete it or rename it.

'motion' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In landing-page/src/components/ui/Features.tsx around lines 56 to 57, the file
has a duplicate import of `motion` and an unused `useEffect` import; remove
`useEffect` from the React named imports and delete the duplicated `motion`
import so you only import { useRef, useState } from "react" and a single import
{ motion } from "framer-motion".

Comment on lines 71 to 96
const hoverTimer = useRef<number | null>(null);
const [isHovered, setIsHovered] = useState(false);

// Clean Up: Detect touch capability safely for SSR/Hybrid devices
// Ideally, rely on the delay logic, but this prevents touch-emulation triggers
const isTouchDevice = () => {
if (typeof window === "undefined") return false;
return window.matchMedia("(hover: none)").matches;
};

const handleMouseEnter = () => {
if (isTouchDevice()) return;

// Logic: Only trigger state after 1 second of "intent"
hoverTimer.current = window.setTimeout(() => {
setIsHovered(true);
}, 1000);
};

const handleMouseLeave = () => {
if (hoverTimer.current) {
clearTimeout(hoverTimer.current);
hoverTimer.current = null;
}
setIsHovered(false);
};
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

Add cleanup on unmount to prevent memory leak.

The hover delay implementation is correct and achieves the PR objective of preventing accidental expansion. The timer cleanup in handleMouseLeave works well for normal interactions.

However, if the component unmounts while hoverTimer is pending, the timeout will still fire and attempt to update state on an unmounted component. Add a cleanup effect:

  const hoverTimer = useRef<number | null>(null);
  const [isHovered, setIsHovered] = useState(false);

+ useEffect(() => {
+   // Cleanup timer on unmount
+   return () => {
+     if (hoverTimer.current) {
+       clearTimeout(hoverTimer.current);
+     }
+   };
+ }, []);
+
  // Clean Up: Detect touch capability safely for SSR/Hybrid devices
  // Ideally, rely on the delay logic, but this prevents touch-emulation triggers
  const isTouchDevice = () => {

You'll need to add useEffect to the React imports on line 56.

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

🤖 Prompt for AI Agents
In landing-page/src/components/ui/Features.tsx around lines 71 to 96, the hover
timeout may fire after the component unmounts and call setIsHovered on an
unmounted component; add a useEffect cleanup that clears hoverTimer.current on
unmount to prevent the memory leak and stale state update, and also add
useEffect to the React import list on line 56 so the hook is available.

@github-actions github-actions bot added UI enhancement New feature or request good first issue Good for newcomers labels Dec 13, 2025
…op to prevent accidental interactions. Hover effects are disabled on touch devices.
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: 3

♻️ Duplicate comments (1)
landing-page/src/components/ui/Features.tsx (1)

77-78: Add cleanup effect to prevent memory leak on unmount.

After resolving merge conflicts, add a useEffect cleanup to clear the timer if the component unmounts while hovering. This prevents attempting to update state on an unmounted component.

  const hoverTimer = useRef<number | null>(null);
  const [isHovered, setIsHovered] = useState(false);

+ useEffect(() => {
+   return () => {
+     if (hoverTimer.current) {
+       clearTimeout(hoverTimer.current);
+     }
+   };
+ }, []);
+
  const isTouchDevice = ...

You'll need to add useEffect to the React imports.

Also applies to: 109-115

🧹 Nitpick comments (1)
frontend/src/components/OnboardingSteps/FolderSetupStep.tsx (1)

54-57: Consider removing premature error on folder removal.

Setting the error when the user removes a folder may feel aggressive since they haven't attempted to proceed yet. Consider only showing the error when they click "Next" without a folder selected.

 const handleRemoveFolder = () => {
   setFolder('');
-  setError('Please select at least one folder to continue.');
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2911dd and 581418f.

📒 Files selected for processing (2)
  • frontend/src/components/OnboardingSteps/FolderSetupStep.tsx (4 hunks)
  • landing-page/src/components/ui/Features.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/OnboardingSteps/FolderSetupStep.tsx (1)
frontend/src/features/onboardingSlice.ts (1)
  • markCompleted (31-42)
🪛 Biome (2.1.2)
landing-page/src/components/ui/Features.tsx

[error] 62-63: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 66-67: Expected a statement but instead found '>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)'.

Expected a statement here.

(parse)


[error] 67-67: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 79-80: Expected a statement but instead found '<<<<<<< HEAD'.

Expected a statement here.

(parse)


[error] 94-95: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 105-106: Expected a statement but instead found '>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)'.

Expected a statement here.

(parse)


[error] 106-106: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 121-122: Expected a JSX attribute but instead found '<<<<<<'.

Expected a JSX attribute here.

(parse)


[error] 122-122: expected > but instead found <

Remove <

(parse)


[error] 123-124: Expected a JSX attribute but instead found '======='.

Expected a JSX attribute here.

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 130-130: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 132-132: Expected a type parameter but instead found '<<<<<<'.

Expected a type parameter here.

(parse)


[error] 132-132: expected , but instead found HEAD

Remove HEAD

(parse)


[error] 135-135: expected , but instead found className

Remove className

(parse)


[error] 135-135: expected , but instead found =

Remove =

(parse)


[error] 135-135: expected , but instead found relative

Remove relative

(parse)


[error] 135-135: expected , but instead found h

Remove h

(parse)


[error] 135-135: expected , but instead found -

Remove -

(parse)


[error] 135-135: expected , but instead found full

Remove full

(parse)


[error] 135-135: expected , but instead found rounded

Remove rounded

(parse)


[error] 135-135: expected , but instead found -

Remove -

(parse)


[error] 135-135: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 135-135: expected , but instead found bg

Remove bg

(parse)


[error] 135-135: expected , but instead found -

Remove -

(parse)


[error] 135-135: expected , but instead found white

Remove white

(parse)


[error] 135-135: expected , but instead found dark

Remove dark

(parse)


[error] 135-135: expected , but instead found :

Remove :

(parse)


[error] 135-135: expected , but instead found bg

Remove bg

(parse)


[error] 135-135: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found border

Remove border

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found p

Remove p

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found transition

Remove transition

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found all

Remove all

(parse)


[error] 136-136: expected , but instead found duration

Remove duration

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found cursor

Remove cursor

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found pointer

Remove pointer

(parse)


[error] 137-137: expected , but instead found $

Remove $

(parse)


[error] 137-137: expected , but instead found {

Remove {

(parse)


[error] 139-139: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 141-141: expected , but instead found ```

Remove `

(parse)


[error] 58-58: Shouldn't redeclare 'motion'. Consider to delete it or rename it.

'motion' is defined here:

(lint/suspicious/noRedeclare)


[error] 64-64: Shouldn't redeclare 'useRef'. Consider to delete it or rename it.

'useRef' is defined here:

(lint/suspicious/noRedeclare)


[error] 64-64: Shouldn't redeclare 'useState'. Consider to delete it or rename it.

'useState' is defined here:

(lint/suspicious/noRedeclare)


[error] 65-65: Shouldn't redeclare 'motion'. Consider to delete it or rename it.

'motion' is defined here:

(lint/suspicious/noRedeclare)


[error] 89-89: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🔇 Additional comments (3)
frontend/src/components/OnboardingSteps/FolderSetupStep.tsx (3)

31-32: LGTM - Error state implementation is clean.

The local error state approach is appropriate for this validation requirement.


60-70: Validation logic is correct.

The guard clause properly prevents progression without folder selection, and the error is cleared before proceeding to avoid stale state.


148-152: Good UX: disabled button combined with error messaging.

The dual feedback approach (disabled button + error message) ensures users understand why they cannot proceed. The text-destructive class provides appropriate visual emphasis.

Also applies to: 164-166

Comment on lines +56 to +67
<<<<<<< HEAD
import { useRef, useState, useEffect } from "react";
import { motion } from "framer-motion";

// Assuming Feature type is defined elsewhere or imported
// interface Feature { ... }

=======
import { useRef, useState } from "react";
import { motion } from "framer-motion";

>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Unresolved Git merge conflict markers.

The file contains unresolved merge conflict markers (<<<<<<< HEAD, =======, >>>>>>>) which cause parse errors and prevent the code from compiling. This must be resolved before the PR can be merged.

After resolving the conflict, ensure you keep only one set of imports:

-<<<<<<< HEAD
-import { useRef, useState, useEffect } from "react";
-import { motion } from "framer-motion";
-
-// Assuming Feature type is defined elsewhere or imported
-// interface Feature { ... }
-
-=======
 import { useRef, useState } from "react";
-import { motion } from "framer-motion";
-
->>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)

Note: Remove the duplicate motion import since it's already imported on line 2.

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

Suggested change
<<<<<<< HEAD
import { useRef, useState, useEffect } from "react";
import { motion } from "framer-motion";
// Assuming Feature type is defined elsewhere or imported
// interface Feature { ... }
=======
import { useRef, useState } from "react";
import { motion } from "framer-motion";
>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
import { useRef, useState } from "react";
🧰 Tools
🪛 Biome (2.1.2)

[error] 62-63: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 66-67: Expected a statement but instead found '>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)'.

Expected a statement here.

(parse)


[error] 67-67: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 58-58: Shouldn't redeclare 'motion'. Consider to delete it or rename it.

'motion' is defined here:

(lint/suspicious/noRedeclare)


[error] 64-64: Shouldn't redeclare 'useRef'. Consider to delete it or rename it.

'useRef' is defined here:

(lint/suspicious/noRedeclare)


[error] 64-64: Shouldn't redeclare 'useState'. Consider to delete it or rename it.

'useState' is defined here:

(lint/suspicious/noRedeclare)


[error] 65-65: Shouldn't redeclare 'motion'. Consider to delete it or rename it.

'motion' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In landing-page/src/components/ui/Features.tsx around lines 56 to 67 there are
unresolved Git conflict markers (<<<<<<<, =======, >>>>>>>) that break parsing;
remove the conflict markers and consolidate the duplicate imports into a single
import block (keep useRef, useState, useEffect from "react" and a single import
of motion from "framer-motion"), ensuring no duplicate motion import remains and
only one import section is present.

Comment on lines +80 to +106
<<<<<<< HEAD
// Clean Up: Detect touch capability safely for SSR/Hybrid devices
// Ideally, rely on the delay logic, but this prevents touch-emulation triggers
const isTouchDevice = () => {
if (typeof window === "undefined") return false;
return window.matchMedia("(hover: none)").matches;
};

const handleMouseEnter = () => {
if (isTouchDevice()) return;

// Logic: Only trigger state after 1 second of "intent"
hoverTimer.current = window.setTimeout(() => {
setIsHovered(true);
}, 1000);
=======
const isTouchDevice =
typeof window !== "undefined" &&
("ontouchstart" in window || navigator.maxTouchPoints > 0);

const handleMouseEnter = () => {
if (isTouchDevice) return;

hoverTimer.current = window.setTimeout(() => {
setIsHovered(true);
}, 1000); // 1s intentional hover
>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Unresolved merge conflict in hover logic.

Choose one implementation of isTouchDevice. The HEAD version uses a function with matchMedia("(hover: none)"), which is more reliable for detecting hover capability. The other version uses a constant check for ontouchstart/maxTouchPoints.

Recommended resolution using the more robust approach:

-<<<<<<< HEAD
-  // Clean Up: Detect touch capability safely for SSR/Hybrid devices
-  // Ideally, rely on the delay logic, but this prevents touch-emulation triggers
-  const isTouchDevice = () => {
-    if (typeof window === "undefined") return false;
-    return window.matchMedia("(hover: none)").matches;
-  };
-
-  const handleMouseEnter = () => {
-    if (isTouchDevice()) return;
-
-    // Logic: Only trigger state after 1 second of "intent"
-    hoverTimer.current = window.setTimeout(() => {
-      setIsHovered(true);
-    }, 1000); 
-=======
   const isTouchDevice =
     typeof window !== "undefined" &&
     ("ontouchstart" in window || navigator.maxTouchPoints > 0);

   const handleMouseEnter = () => {
     if (isTouchDevice) return;

     hoverTimer.current = window.setTimeout(() => {
       setIsHovered(true);
     }, 1000); // 1s intentional hover
->>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
   };
📝 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.

Suggested change
<<<<<<< HEAD
// Clean Up: Detect touch capability safely for SSR/Hybrid devices
// Ideally, rely on the delay logic, but this prevents touch-emulation triggers
const isTouchDevice = () => {
if (typeof window === "undefined") return false;
return window.matchMedia("(hover: none)").matches;
};
const handleMouseEnter = () => {
if (isTouchDevice()) return;
// Logic: Only trigger state after 1 second of "intent"
hoverTimer.current = window.setTimeout(() => {
setIsHovered(true);
}, 1000);
=======
const isTouchDevice =
typeof window !== "undefined" &&
("ontouchstart" in window || navigator.maxTouchPoints > 0);
const handleMouseEnter = () => {
if (isTouchDevice) return;
hoverTimer.current = window.setTimeout(() => {
setIsHovered(true);
}, 1000); // 1s intentional hover
>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
// Clean Up: Detect touch capability safely for SSR/Hybrid devices
// Ideally, rely on the delay logic, but this prevents touch-emulation triggers
const isTouchDevice = () => {
if (typeof window === "undefined") return false;
return window.matchMedia("(hover: none)").matches;
};
const handleMouseEnter = () => {
if (isTouchDevice()) return;
// Logic: Only trigger state after 1 second of "intent"
hoverTimer.current = window.setTimeout(() => {
setIsHovered(true);
}, 1000);
};
🧰 Tools
🪛 Biome (2.1.2)

[error] 94-95: Expected a statement but instead found '======='.

Expected a statement here.

(parse)


[error] 105-106: Expected a statement but instead found '>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)'.

Expected a statement here.

(parse)


[error] 106-106: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 89-89: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🤖 Prompt for AI Agents
In landing-page/src/components/ui/Features.tsx around lines 80 to 106, there is
an unresolved merge conflict in the touch-detection/hover logic; keep the
matchMedia("(hover: none)") approach as it’s more reliable, remove the conflict
markers, and convert isTouchDevice into a safe SSR-aware function (if typeof
window === "undefined" return false; otherwise return window.matchMedia("(hover:
none)").matches). Ensure handleMouseEnter calls that function (if
(isTouchDevice()) return;) and preserve the 1s hoverTimer logic and cleanup
behavior.

Comment on lines +122 to +148
<<<<<<< HEAD
// Scale is controlled by state, ensuring it syncs with the delay
=======
>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
animate={isHovered ? { scale: 1.02 } : { scale: 1 }}
transition={{ duration: 0.3 }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div
<<<<<<< HEAD
// CHANGED: Removed 'hover:border-...' and 'hover:shadow-...'
// Moved styling to the conditional string below to prevent instant flashing.
className={`relative h-full rounded-2xl bg-white dark:bg-[#161717]
border-2 p-6 transition-all duration-300 cursor-pointer
${
isHovered
? "border-blue-600 shadow-[0_0_15px_rgba(30,64,175,0.9)] dark:shadow-[0_0_22px_rgba(30,64,179,1)]"
: "border-transparent"
}`}
=======
className="relative h-full rounded-2xl bg-white dark:bg-[#161717]
border-2 border-transparent p-6 transition-all duration-300
hover:border-blue-600 dark:hover:border-blue-600
hover:shadow-[0_0_15px_rgba(30,64,175,0.9)] dark:hover:shadow-[0_0_22px_rgba(30,64,179,1)]"
hover:shadow-[0_0_15px_rgba(30,64,175,0.9)] dark:hover:shadow-[0_0_22px_rgba(30,64,179,1)]
cursor-pointer"
>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Unresolved merge conflict in JSX.

The JSX section also has unresolved conflicts. The HEAD version controls styling via state (preferred for the delayed hover feature), while the other uses CSS hover pseudo-classes.

For the delayed hover to work correctly, use the state-controlled approach:

-<<<<<<< HEAD
-      // Scale is controlled by state, ensuring it syncs with the delay
-=======
->>>>>>> 2a16307 (...)
       animate={isHovered ? { scale: 1.02 } : { scale: 1 }}
       transition={{ duration: 0.3 }}
       ...
     >
       <div
-<<<<<<< HEAD
-        // CHANGED: Removed 'hover:border-...' and 'hover:shadow-...'
-        // Moved styling to the conditional string below to prevent instant flashing.
         className={`relative h-full rounded-2xl bg-white dark:bg-[#161717] 
         border-2 p-6 transition-all duration-300 cursor-pointer
         ${
           isHovered
             ? "border-blue-600 shadow-[0_0_15px_rgba(30,64,175,0.9)] dark:shadow-[0_0_22px_rgba(30,64,179,1)]"
             : "border-transparent"
         }`}
-=======
-        className="relative h-full rounded-2xl ... hover:border-blue-600 ..."
->>>>>>> 2a16307 (...)
       >

The state-controlled version is required; using CSS hover: classes would bypass the intentional delay.

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

Suggested change
<<<<<<< HEAD
// Scale is controlled by state, ensuring it syncs with the delay
=======
>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
animate={isHovered ? { scale: 1.02 } : { scale: 1 }}
transition={{ duration: 0.3 }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div
<<<<<<< HEAD
// CHANGED: Removed 'hover:border-...' and 'hover:shadow-...'
// Moved styling to the conditional string below to prevent instant flashing.
className={`relative h-full rounded-2xl bg-white dark:bg-[#161717]
border-2 p-6 transition-all duration-300 cursor-pointer
${
isHovered
? "border-blue-600 shadow-[0_0_15px_rgba(30,64,175,0.9)] dark:shadow-[0_0_22px_rgba(30,64,179,1)]"
: "border-transparent"
}`}
=======
className="relative h-full rounded-2xl bg-white dark:bg-[#161717]
border-2 border-transparent p-6 transition-all duration-300
hover:border-blue-600 dark:hover:border-blue-600
hover:shadow-[0_0_15px_rgba(30,64,175,0.9)] dark:hover:shadow-[0_0_22px_rgba(30,64,179,1)]"
hover:shadow-[0_0_15px_rgba(30,64,175,0.9)] dark:hover:shadow-[0_0_22px_rgba(30,64,179,1)]
cursor-pointer"
>>>>>>> 2a16307 (Gallery feature cards expand only after an intentional hover on desktop to prevent accidental interactions. Hover effects are disabled on touch devices.)
animate={isHovered ? { scale: 1.02 } : { scale: 1 }}
transition={{ duration: 0.3 }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div
className={`relative h-full rounded-2xl bg-white dark:bg-[#161717]
border-2 p-6 transition-all duration-300 cursor-pointer
${
isHovered
? "border-blue-600 shadow-[0_0_15px_rgba(30,64,175,0.9)] dark:shadow-[0_0_22px_rgba(30,64,179,1)]"
: "border-transparent"
}`}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 122-122: expected > but instead found <

Remove <

(parse)


[error] 123-124: Expected a JSX attribute but instead found '======='.

Expected a JSX attribute here.

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 125-125: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 130-130: Unexpected token. Did you mean {'>'} or &gt;?

(parse)


[error] 132-132: Expected a type parameter but instead found '<<<<<<'.

Expected a type parameter here.

(parse)


[error] 132-132: expected , but instead found HEAD

Remove HEAD

(parse)


[error] 135-135: expected , but instead found className

Remove className

(parse)


[error] 135-135: expected , but instead found =

Remove =

(parse)


[error] 135-135: expected , but instead found relative

Remove relative

(parse)


[error] 135-135: expected , but instead found h

Remove h

(parse)


[error] 135-135: expected , but instead found -

Remove -

(parse)


[error] 135-135: expected , but instead found full

Remove full

(parse)


[error] 135-135: expected , but instead found rounded

Remove rounded

(parse)


[error] 135-135: expected , but instead found -

Remove -

(parse)


[error] 135-135: numbers cannot be followed by identifiers directly after

an identifier cannot appear here

(parse)


[error] 135-135: expected , but instead found bg

Remove bg

(parse)


[error] 135-135: expected , but instead found -

Remove -

(parse)


[error] 135-135: expected , but instead found white

Remove white

(parse)


[error] 135-135: expected , but instead found dark

Remove dark

(parse)


[error] 135-135: expected , but instead found :

Remove :

(parse)


[error] 135-135: expected , but instead found bg

Remove bg

(parse)


[error] 135-135: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found border

Remove border

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found p

Remove p

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found transition

Remove transition

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found all

Remove all

(parse)


[error] 136-136: expected , but instead found duration

Remove duration

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found cursor

Remove cursor

(parse)


[error] 136-136: expected , but instead found -

Remove -

(parse)


[error] 136-136: expected , but instead found pointer

Remove pointer

(parse)


[error] 137-137: expected , but instead found $

Remove $

(parse)


[error] 137-137: expected , but instead found {

Remove {

(parse)


[error] 139-139: ';' expected'

An explicit or implicit semicolon is expected here...

(parse)


[error] 141-141: expected , but instead found ```

Remove `

(parse)

🤖 Prompt for AI Agents
In landing-page/src/components/ui/Features.tsx around lines 122 to 148, there's
an unresolved merge conflict with both the state-controlled hover version (HEAD)
and the CSS hover pseudo-class version present; keep the state-controlled
approach required for the delayed hover feature, remove the conflict markers
(<<<<<<<, =======, >>>>>>>) and delete the alternate branch that uses hover:
classes, ensure the component uses the isHovered-driven animate/transition and
onMouseEnter/onMouseLeave handlers, and preserve the conditional className that
toggles border and shadow based on isHovered (remove any hover:... and
dark:hover:... utilities so the visual changes are only driven by state).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request good first issue Good for newcomers UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: Custom Avatar & Hovering Feature

2 participants