diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f2d777..8a46a8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ## [Unreleased] +## [0.59.1] - 2026-11-02 + +### Fixed +- Desktop: Das Layout verschiebt sich nicht mehr um ein paar Pixel, wenn eine Seite scrollbar wird (z. B. ein breites Kanban-Board) oder beim Wechsel zwischen Seiten mit und ohne Scrollbalken. Der Platz für den Scrollbalken bleibt immer frei. +- Aufgaben: Beim Wechsel der Aufgabenliste blitzten kurz die Aufgaben der vorherigen Liste auf, besonders beim Wechsel zwischen Listen- und Kanban-Ansicht. Jetzt erscheint die neue Liste sofort mit ihren Aufgaben. + ## [0.59.0] - 2026-11-02 ### Added @@ -853,7 +859,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.59.0...dev +[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.59.1...dev +[0.59.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.59.0...v0.59.1 [0.59.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.58.0...v0.59.0 [0.58.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.57.0...v0.58.0 [0.57.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.56.0...v0.57.0 diff --git a/README.md b/README.md index f8c9bba..99b4812 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.59.0-16a34a?style=flat-square)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/Version-v0.59.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 5448e8d..68e36f6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "mindsift-frontend", - "version": "0.59.0", + "version": "0.59.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mindsift-frontend", - "version": "0.59.0", + "version": "0.59.1", "license": "AGPL-3.0-only", "dependencies": { "@fontsource-variable/inter": "5.3.0", diff --git a/frontend/package.json b/frontend/package.json index 9936cd2..69a11bb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "mindsift-frontend", "private": true, - "version": "0.59.0", + "version": "0.59.1", "license": "AGPL-3.0-only", "type": "module", "scripts": { 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, diff --git a/frontend/src/index.css b/frontend/src/index.css index bd1671e..5b7eed6 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -29,6 +29,13 @@ -webkit-tap-highlight-color: transparent; } +/* Room for the scrollbar is always kept: pages that scroll (e.g. a wide Kanban board) and pages that do + not keep the same width, so nothing shifts when switching tabs. Overlay scrollbars (phones) are not + affected. */ +html { + scrollbar-gutter: stable; +} + body { background-color: var(--bg-app); color: var(--text-main);