From 284ef1f723b43c8520309a785662c23e013c7f53 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sun, 1 Nov 2026 13:40:47 +0100 Subject: [PATCH] feat(kanban): smooth drop slot instead of a flickering line; fix reload after reordering The column works out the slot from the pointer (cards no longer handle drag enter/leave, which flickered on their child elements); a gap of the card's height opens there with framer-motion, the dragged card leaves its old place and lights up where it lands. The realtime handler reloaded with the loadData of its first render ("Alle Aufgaben"), so a selected list showed other lists' tasks after TASK_REORDERED; it now calls the current one. v0.38.1. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 11 +- README.md | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- frontend/src/features/triage/TasksView.tsx | 156 +++++++++++++++------ 5 files changed, 129 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a3a6c..eff416e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ## [Unreleased] +## [0.38.1] - 2026-11-01 + +### Changed +- Kanban: Beim Ziehen einer Karte öffnet sich an der Zielstelle sanft eine Lücke in Kartengröße, die übrigen Karten gleiten nach, und die Karte verlässt ihren alten Platz. Nach dem Ablegen leuchtet sie kurz an der neuen Stelle auf. Die blaue Linie, die beim Ziehen über Text und Symbole der Karten flackerte und die Ansicht zittern ließ, gibt es nicht mehr. + +### Fixed +- Nach dem Umsortieren von Aufgaben (Kanban oder Liste) zeigte eine gewählte Aufgabenliste plötzlich auch Aufgaben aus anderen Listen, bis man sie neu öffnete. + ## [0.38.0] - 2026-11-01 ### Added @@ -639,7 +647,8 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V - Geteilte Listen (Einkaufs-, Pack- und Checklisten) mit `#Kategorie`-Gruppierung, Mengenangaben und Echtzeit-Abhaken. - Benutzer-, Gruppen- und Admin-Verwaltung (Ersteinrichtungs-Assistent, Einladungscodes, Profilbilder, persönliche API-Keys & Webhook-Ingest `/api/v1/webhooks/ingest`, JSON-Datenexport) sowie CSS-Theme-Engine mit 4 Themes (`Dark`, `Light`, `High Contrast`, `Soft`) und PWA-Unterstützung. -[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.38.0...dev +[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.38.1...dev +[0.38.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.38.0...v0.38.1 [0.38.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.37.2...v0.38.0 [0.37.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.37.1...v0.37.2 [0.37.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.37.0...v0.37.1 diff --git a/README.md b/README.md index 473879b..a3e282b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#) [![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-schnellstart) [![SQLite](https://img.shields.io/badge/Daten-SQLite-003b57?style=flat-square&logo=sqlite&logoColor=white)](#-deine-daten-gehören-dir) -[![Version](https://img.shields.io/badge/Version-v0.38.0-16a34a?style=flat-square)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/Version-v0.38.1-16a34a?style=flat-square)](CHANGELOG.md) **MindSift** ist eine selbst gehostete Schaltstelle für spontane Gedanken, Aufgaben mit Kanban-Board, hierarchische Outliner-Notizen, die mehrere Personen gleichzeitig bearbeiten können, und gemeinsame Listen. Alles läuft als ein einzelner Container mit eingebetteter SQLite-Datenbank, auf deinem eigenen Server. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3f00e94..e76b273 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "mindsift-frontend", - "version": "0.38.0", + "version": "0.38.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mindsift-frontend", - "version": "0.38.0", + "version": "0.38.1", "license": "Unlicense", "dependencies": { "@fontsource-variable/inter": "5.3.0", diff --git a/frontend/package.json b/frontend/package.json index 284c46f..b90a769 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "mindsift-frontend", "private": true, - "version": "0.38.0", + "version": "0.38.1", "license": "Unlicense", "type": "module", "scripts": { diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx index f05bb5b..d563a2e 100644 --- a/frontend/src/features/triage/TasksView.tsx +++ b/frontend/src/features/triage/TasksView.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useRef, useMemo } from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; import { Check, Plus, @@ -134,6 +135,12 @@ export const TasksView: React.FC = ({ const [draggingTaskId, setDraggingTaskId] = useState(null); const [dragOverTaskTarget, setDragOverTaskTarget] = useState<{ taskId: string; position: 'before' | 'after' } | null>(null); const [dragOverKanbanCol, setDragOverKanbanCol] = useState(null); + // Kanban drag: the column works out the slot from the pointer; a gap of the card's height opens there + // (no per-card enter/leave flicker), the dragged card leaves its old place, and lights up where it lands + const [kanbanSlot, setKanbanSlot] = useState<{ col: string; index: number } | null>(null); + const [kanbanHiddenId, setKanbanHiddenId] = useState(null); + const [justDroppedId, setJustDroppedId] = useState(null); + const dragHeightRef = useRef(64); // List tab a dragged task hovers over (dropping moves it into that list) const [dragOverListTab, setDragOverListTab] = useState(null); @@ -241,6 +248,9 @@ export const TasksView: React.FC = ({ const onRefreshStatsRef = useRef(onRefreshStats); onRefreshStatsRef.current = onRefreshStats; + // The realtime handler is registered once; it must reload with the current list, not the first one + const loadDataRef = useRef(loadData); + loadDataRef.current = loadData; // Live-Sync WebSocket listener for tasks (registered once on mount) useEffect(() => { @@ -290,7 +300,7 @@ export const TasksView: React.FC = ({ onRefreshStatsRef.current(); } } else if (msg.event === 'TASK_REORDERED') { - loadData(); + loadDataRef.current(); } else if (msg.event === 'TASK_LIST_CREATED' || msg.event === 'TASK_LIST_UPDATED' || msg.event === 'TASK_LIST_DELETED') { // Payload only carries the list ID; reload so permissions reflect this user's view api.getTaskLists().then((lists) => setTaskLists(Array.isArray(lists) ? lists : [])).catch(() => {}); @@ -901,21 +911,89 @@ export const TasksView: React.FC = ({ } }; - const handleDropOnKanbanCol = async (e: React.DragEvent, colId: string) => { - e.preventDefault(); - e.stopPropagation(); - - const sourceId = - e.dataTransfer.getData('application/mindsift-task-id') || - e.dataTransfer.getData('text/plain') || - draggingTaskId; - + const resetKanbanDrag = () => { setDraggingTaskId(null); setDragOverTaskTarget(null); setDragOverKanbanCol(null); - await moveTaskToColumn(sourceId, colId); + setDragOverListTab(null); + setKanbanSlot(null); + setKanbanHiddenId(null); }; + // The gap where the dragged card would land; it opens and closes smoothly, the cards below slide along + const renderDropSlot = (colId: string, index: number) => ( +