diff --git a/frontend/src/components/EventGantt.tsx b/frontend/src/components/EventGantt.tsx index 42e7ea9..d5e7d87 100644 --- a/frontend/src/components/EventGantt.tsx +++ b/frontend/src/components/EventGantt.tsx @@ -151,6 +151,11 @@ function Inner({ // Pixel-level offset applied during drag (without committing). const [dragOffset, setDragOffset] = useState<{ id: number; dx: number; mode: DragMode } | null>(null); const [popover, setPopover] = useState(null); + // Timestamp of the most recent drag-end. We use it to suppress the + // synchronous `onClick` that fires on `mouseup` after a successful + // drag — that click would otherwise open the edit popover with stale + // (pre-drag) field values, since React Query hasn't re-fetched yet. + const lastDragEnd = useRef(0); useEffect(() => { const onMove = (e: MouseEvent) => { @@ -169,6 +174,7 @@ function Inner({ drag.current = null; setDragOffset(null); if (Math.abs(dyears) < 1) return; // sub-year drag = ignore + lastDragEnd.current = performance.now(); let body: LifeEventPatchBody = {}; if (mode === 'move') { const newStart = clamp(origYearStart + dyears, 0, horizonYears - 1); @@ -224,6 +230,9 @@ function Inner({ const onBarClick = (e: React.MouseEvent, ev: LifeEvent) => { e.stopPropagation(); + // Suppress the click that fires immediately after a drag — the + // popover would otherwise open with stale (pre-mutation) values. + if (performance.now() - lastDragEnd.current < 250) return; const point = localPoint(e); if (!point) return; setPopover({ kind: 'edit', x: point.x, y: point.y, event: ev });