diff --git a/web/src/components/focus/FocusCard.tsx b/web/src/components/focus/FocusCard.tsx
index a5cdfcd..0b1f49d 100644
--- a/web/src/components/focus/FocusCard.tsx
+++ b/web/src/components/focus/FocusCard.tsx
@@ -73,7 +73,7 @@ export function FocusCard({ article, isTop = false }: FocusCardProps) {
{/* Hint (only on top card) */}
{isTop && (
- Cliquer pour lire • Glisser pour trier
+ Double-cliquer pour lire • Glisser pour trier
)}
diff --git a/web/src/components/focus/FocusCardStack.tsx b/web/src/components/focus/FocusCardStack.tsx
index ed54185..89ff0fe 100644
--- a/web/src/components/focus/FocusCardStack.tsx
+++ b/web/src/components/focus/FocusCardStack.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useRef } from 'react';
import { useDrag } from '@use-gesture/react';
import { motion, AnimatePresence, useMotionValue, useTransform, useSpring } from 'framer-motion';
import type { Article } from '../../api/articles';
@@ -28,6 +28,8 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
const isMobile = useIsMobile();
const [isProcessing, setIsProcessing] = useState(false);
+ // Track whether a drag gesture occurred to prevent double-click from firing after swipe
+ const hasDraggedRef = useRef(false);
// Visible stack size
const visibleArticles = articles.slice(currentIndex, currentIndex + 3);
@@ -72,6 +74,11 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
const bind = useDrag(({ active, movement: [mx], velocity: [vx] }) => {
if (readingArticle || isProcessing) return; // Disable drag when reading or processing
+ // Mark as dragged if the user moved more than a few pixels
+ if (active && Math.abs(mx) > 5) {
+ hasDraggedRef.current = true;
+ }
+
const trigger = Math.abs(mx) > 200 || (Math.abs(vx) > 0.5 && Math.abs(mx) > 50);
if (!active && trigger) {
@@ -81,9 +88,13 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
// Trigger action via handler
handleAction(isRight ? 'read' : 'keep');
+ } else if (!active) {
+ // Snap Back — reset drag flag after a short delay so double-click can distinguish
+ x.set(0);
+ setTimeout(() => { hasDraggedRef.current = false; }, 50);
} else {
- // Dragging or Snap Back
- x.set(active ? mx : 0);
+ // Actively dragging
+ x.set(mx);
}
});
@@ -190,7 +201,12 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
cursor: isTop ? 'grab' : 'default',
}}
{...(isTop ? (bind() as any) : {})}
- onClick={() => isTop && setReadingArticle(article)}
+ onDoubleClick={() => {
+ // Only open on genuine double-click, not after a drag gesture
+ if (isTop && !hasDraggedRef.current) {
+ setReadingArticle(article);
+ }
+ }}
animate={{ scale: 1 - i * 0.05, y: i * 20 }}
exit={{ x: exitX, opacity: 0, scale: 0.9, transition: { duration: 0.2 } }}
>
@@ -202,10 +218,11 @@ export function FocusCardStack({ articles, onMarkRead, onKeep, onToggleFavorite,
+ {/* Garder — Outlined style matching Dashboard "Actualiser" button */}
- {/* Undo Button - Always rendered for layout, hidden if inactive */}
+ {/* Undo — Subtle outlined, invisible when unavailable */}
+ {/* J'ai lu — Primary filled, matching "Lancer le Mode Focus" CTA */}