From 92de62a620e40efb906377099a3d184c2735b36c Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Nov 2026 18:11:17 +0100 Subject: [PATCH] fix(tasks): switching lists never shows the previous list's tasks The new list's view (list or Kanban) appeared one render before its tasks, so the board briefly showed the old list. Lists opened in this session are kept in memory and shown in the same render; a list not opened yet takes its share of "Alle Aufgaben", else the loading placeholder shows. The reload brings them up to date. Co-Authored-By: Claude Opus 5.5 --- frontend/src/features/triage/TasksView.tsx | 38 ++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx index 950e950..30c9567 100644 --- a/frontend/src/features/triage/TasksView.tsx +++ b/frontend/src/features/triage/TasksView.tsx @@ -173,7 +173,7 @@ export const TasksView: React.FC = ({ const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open'); const [tasks, setTasks] = useState([]); const [taskLists, setTaskLists] = useState([]); - const [selectedListId, setSelectedListId] = useState( + const [selectedListId, setSelectedListIdState] = useState( () => remembered.listId || 'all' ); const viewMode: ViewMode = viewModes[selectedListId] || 'list'; @@ -304,6 +304,10 @@ export const TasksView: React.FC = ({ ]); if (seq !== loadSeqRef.current) return; // a newer reload is under way loadedListRef.current = listId; + listCacheRef.current.set(listId, { + tasks: Array.isArray(allTasks) ? allTasks : [], + trash: Array.isArray(trash) ? trash : [], + }); setTasks(Array.isArray(allTasks) ? allTasks : []); setTaskLists(Array.isArray(allLists) ? allLists : []); // The remembered list was deleted or unshared meanwhile @@ -327,6 +331,33 @@ export const TasksView: React.FC = ({ const selectedListIdRef = useRef(selectedListId); selectedListIdRef.current = selectedListId; + // Last known tasks of every list opened in this session. Switching lists shows them in the same render + // as the new list's view (list or Kanban), so the board never flashes the previous list's tasks; the + // reload then brings them up to date. A list not opened yet takes its share of "Alle Aufgaben". + const listCacheRef = useRef(new Map()); + useEffect(() => { + if (!loading && loadedListRef.current === selectedListId) { + listCacheRef.current.set(selectedListId, { tasks, trash: taskTrash }); + } + }, [tasks, taskTrash, loading, selectedListId]); + const setSelectedListId = useCallback((id: string) => { + if (id === selectedListIdRef.current) return; + const cache = listCacheRef.current; + let known = cache.get(id); + const all = cache.get('all'); + if (!known && id !== 'all' && all) { + known = { + tasks: all.tasks.filter((t) => t.taskListId === id), + trash: all.trash.filter((t) => t.taskListId === id), + }; + } + setTasks(known ? known.tasks : []); + setTaskTrash(known ? known.trash : []); + setLoading(!known); + loadedListRef.current = known ? id : null; + setSelectedListIdState(id); + }, []); + const onRefreshStatsRef = useRef(onRefreshStats); onRefreshStatsRef.current = onRefreshStats; // The realtime handler is registered once; it must reload with the current list, not the first one @@ -1371,7 +1402,9 @@ export const TasksView: React.FC = ({ try { await api.deleteTaskList(listId); setTaskLists((prev) => prev.filter((l) => l.id !== listId)); - setSelectedListId('all'); + // Plain switch: the deleted list's tasks belong to "Alle Aufgaben" anyway (and the menu effect keeps + // this handler out of its dependencies) + setSelectedListIdState('all'); } catch (err: any) { alert(err.message); } @@ -1568,6 +1601,7 @@ export const TasksView: React.FC = ({ groupBy, defaultGroupBy, setViewMode, + setSelectedListId, sortBy, openTasks.length, doneTasks.length,