-
Notifications
You must be signed in to change notification settings - Fork 580
add delayed hover for feature cards and remove emoji cursor #757
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
Conversation
…s to improve discoverability and reduce accidental clicks.
|
|
WalkthroughAdds 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (3 warnings, 1 inconclusive)
✅ Passed checks (1 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: 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
📒 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
animateprop (instead ofwhileHover) 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.
| import { useRef, useState, useEffect } from "react"; | ||
| import { motion } from "framer-motion"; |
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.
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.
| 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".
| 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); | ||
| }; |
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.
🛠️ 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.
…op to prevent accidental interactions. Hover effects are disabled on touch devices.
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: 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
useEffectcleanup 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
useEffectto 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
📒 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 >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 130-130: Unexpected token. Did you mean {'>'} or >?
(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-destructiveclass provides appropriate visual emphasis.Also applies to: 164-166
| <<<<<<< 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.) |
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.
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.
| <<<<<<< 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.
| <<<<<<< 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.) |
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.
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.
| <<<<<<< 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.
| <<<<<<< 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.) |
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.
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.
| <<<<<<< 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 >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 125-125: Unexpected token. Did you mean {'>'} or >?
(parse)
[error] 130-130: Unexpected token. Did you mean {'>'} or >?
(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).
Description
This PR enforces folder selection during the onboarding flow to prevent users from proceeding with an incomplete setup.
Changes
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
Fixes #704