Merge dev into main (v0.59.1)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 30s

This commit is contained in:
sandrodach committed 2026-11-02 18:14:33 +01:00
commit f22a18c89d
6 files changed
+55 -7

No files matched your search

+8 -1
View File
@@ -6,6 +6,12 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
## [Unreleased] ## [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 ## [0.59.0] - 2026-11-02
### Added ### 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. - 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. - 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.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.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 [0.57.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.56.0...v0.57.0
+1 -1
View File
@@ -6,7 +6,7 @@
[![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#) [![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) [![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) [![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. **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.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"version": "0.59.0", "version": "0.59.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"version": "0.59.0", "version": "0.59.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "5.3.0", "@fontsource-variable/inter": "5.3.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"private": true, "private": true,
"version": "0.59.0", "version": "0.59.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"type": "module", "type": "module",
"scripts": { "scripts": {
+36 -2
View File
@@ -173,7 +173,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open'); const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open');
const [tasks, setTasks] = useState<Thought[]>([]); const [tasks, setTasks] = useState<Thought[]>([]);
const [taskLists, setTaskLists] = useState<TaskList[]>([]); const [taskLists, setTaskLists] = useState<TaskList[]>([]);
const [selectedListId, setSelectedListId] = useState<string>( const [selectedListId, setSelectedListIdState] = useState<string>(
() => remembered.listId || 'all' () => remembered.listId || 'all'
); );
const viewMode: ViewMode = viewModes[selectedListId] || 'list'; const viewMode: ViewMode = viewModes[selectedListId] || 'list';
@@ -304,6 +304,10 @@ export const TasksView: React.FC<TasksViewProps> = ({
]); ]);
if (seq !== loadSeqRef.current) return; // a newer reload is under way if (seq !== loadSeqRef.current) return; // a newer reload is under way
loadedListRef.current = listId; loadedListRef.current = listId;
listCacheRef.current.set(listId, {
tasks: Array.isArray(allTasks) ? allTasks : [],
trash: Array.isArray(trash) ? trash : [],
});
setTasks(Array.isArray(allTasks) ? allTasks : []); setTasks(Array.isArray(allTasks) ? allTasks : []);
setTaskLists(Array.isArray(allLists) ? allLists : []); setTaskLists(Array.isArray(allLists) ? allLists : []);
// The remembered list was deleted or unshared meanwhile // The remembered list was deleted or unshared meanwhile
@@ -327,6 +331,33 @@ export const TasksView: React.FC<TasksViewProps> = ({
const selectedListIdRef = useRef(selectedListId); const selectedListIdRef = useRef(selectedListId);
selectedListIdRef.current = 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<string, { tasks: Thought[]; trash: TrashedTask[] }>());
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); const onRefreshStatsRef = useRef(onRefreshStats);
onRefreshStatsRef.current = onRefreshStats; onRefreshStatsRef.current = onRefreshStats;
// The realtime handler is registered once; it must reload with the current list, not the first one // 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<TasksViewProps> = ({
try { try {
await api.deleteTaskList(listId); await api.deleteTaskList(listId);
setTaskLists((prev) => prev.filter((l) => l.id !== 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) { } catch (err: any) {
alert(err.message); alert(err.message);
} }
@@ -1568,6 +1601,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
groupBy, groupBy,
defaultGroupBy, defaultGroupBy,
setViewMode, setViewMode,
setSelectedListId,
sortBy, sortBy,
openTasks.length, openTasks.length,
doneTasks.length, doneTasks.length,
+7
View File
@@ -29,6 +29,13 @@
-webkit-tap-highlight-color: transparent; -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 { body {
background-color: var(--bg-app); background-color: var(--bg-app);
color: var(--text-main); color: var(--text-main);