From cce076b093bbc11b579764d237ab5588732a45f8 Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Nov 2026 18:11:17 +0100 Subject: [PATCH 1/3] fix(ui): keep room for the scrollbar so pages do not shift scrollbar-gutter: stable on the root: a page that scrolls (e.g. a wide Kanban board) and one that does not have the same width, so switching tabs no longer moves the layout. Overlay scrollbars are unaffected. Co-Authored-By: Claude Opus 5.5 --- frontend/src/index.css | 7 +++++++ 1 file changed, 7 insertions(+) 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); From 92de62a620e40efb906377099a3d184c2735b36c Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Nov 2026 18:11:17 +0100 Subject: [PATCH 2/3] 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, From 0ba51401064eb0368538dc08e22fc22aad741752 Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Nov 2026 18:12:19 +0100 Subject: [PATCH 3/3] chore: release v0.59.1 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 ++++++++- README.md | 2 +- frontend/package-lock.json | 4 ++-- frontend/package.json | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) 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": {