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

This commit is contained in:
sandrodach committed 2026-11-02 22:13:14 +01:00
commit f0f7b50464
5 files changed
+28 -27

No files matched your search

+7 -1
View File
@@ -6,6 +6,11 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
## [Unreleased] ## [Unreleased]
## [0.65.0] - 2026-11-02
### Changed
- Kanban am Desktop: Auch bei nur einer Spaltenreihe schiebt der erste Mausrad-Schritt die Leiste darüber (neue Aufgabe, Hashtags, Listen) weg, und die Spalten wachsen auf Bildschirmhöhe. Zurückscrollen holt die Leiste wieder.
## [0.64.1] - 2026-11-02 ## [0.64.1] - 2026-11-02
### Fixed ### Fixed
@@ -922,7 +927,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.64.1...dev [Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.65.0...dev
[0.65.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.64.1...v0.65.0
[0.64.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.64.0...v0.64.1 [0.64.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.64.0...v0.64.1
[0.64.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.63.2...v0.64.0 [0.64.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.63.2...v0.64.0
[0.63.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.63.1...v0.63.2 [0.63.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.63.1...v0.63.2
+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.64.1-16a34a?style=flat-square)](CHANGELOG.md) [![Version](https://img.shields.io/badge/Version-v0.65.0-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.64.1", "version": "0.65.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mindsift-frontend", "name": "mindsift-frontend",
"version": "0.64.1", "version": "0.65.0",
"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.64.1", "version": "0.65.0",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"type": "module", "type": "module",
"scripts": { "scripts": {
+17 -22
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useLayoutEffect, useState, useRef, useMemo, useCallback } from 'react'; import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { import {
Check, Check,
@@ -82,6 +82,8 @@ const KANBAN_MIN_PHONE_COL_HEIGHT = 280;
// First row of a Kanban board with several rows: the full height minus what is still visible of the bar // First row of a Kanban board with several rows: the full height minus what is still visible of the bar
// above the board (--kb-top at the start, shrinking with the scroll position --kb-scroll) // above the board (--kb-top at the start, shrinking with the scroll position --kb-scroll)
const KANBAN_FIRST_ROW_HEIGHT = `max(${KANBAN_MIN_COL_HEIGHT}px, calc(var(--kb-full) - max(0px, calc(var(--kb-top) - var(--kb-scroll, 0px)))))`; const KANBAN_FIRST_ROW_HEIGHT = `max(${KANBAN_MIN_COL_HEIGHT}px, calc(var(--kb-full) - max(0px, calc(var(--kb-top) - var(--kb-scroll, 0px)))))`;
// Below the board: what the first row has not grown into yet, so the page keeps its height while scrolling
const KANBAN_BAR_SPACER = 'max(0px, calc(var(--kb-top) - var(--kb-scroll, 0px)))';
// Wide Kanban: a column grows to at most this width, the board stays centred // Wide Kanban: a column grows to at most this width, the board stays centred
const KANBAN_COL_MAX = 380; const KANBAN_COL_MAX = 380;
const KANBAN_GAP = 12; // gap-3 const KANBAN_GAP = 12; // gap-3
@@ -563,9 +565,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
// Desktop Kanban: every column, in every row, is as high as the room from the board's top to the bottom of // Desktop Kanban: every column, in every row, is as high as the room from the board's top to the bottom of
// the scroll area, so the first row fits completely on opening and each further row fills one screen; // the scroll area, so the first row fits completely on opening and each further row fills one screen;
// more cards scroll inside the column. At least room for about three cards (the page scrolls below that). // more cards scroll inside the column. At least room for about three cards (the page scrolls below that).
// One row: the page itself does not scroll (like the workspace). Several rows: the first row grows while // The first row grows while the bar above the board scrolls away, so a row always fills the screen (with
// the bar above the board scrolls away, so a row always fills the screen; the wheel moves row by row // one row too: the first scroll step hides the bar); the wheel moves row by row (below).
// (below).
const kanbanBoardRef = useRef<HTMLDivElement>(null); const kanbanBoardRef = useRef<HTMLDivElement>(null);
const [kanbanColHeight, setKanbanColHeight] = useState<number | null>(null); const [kanbanColHeight, setKanbanColHeight] = useState<number | null>(null);
const [kanbanMultiRow, setKanbanMultiRow] = useState(false); const [kanbanMultiRow, setKanbanMultiRow] = useState(false);
@@ -610,15 +611,15 @@ export const TasksView: React.FC<TasksViewProps> = ({
const areaHeight = area ? area.clientHeight : window.innerHeight; const areaHeight = area ? area.clientHeight : window.innerHeight;
const top = el.getBoundingClientRect().top - areaTop; const top = el.getBoundingClientRect().top - areaTop;
setKanbanColHeight(Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - top - 16)); setKanbanColHeight(Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - top - 16));
// Several rows: a row fills the whole area (--kb-full); the first one grows into the room the bar // A row fills the whole area (--kb-full); the first one grows into the room the bar above the board
// above the board frees while scrolling (--kb-top - --kb-scroll, see the scroll effect) // frees while scrolling (--kb-top - --kb-scroll, see the scroll effect). With one row as well, so the
el.style.setProperty('--kb-full', `${Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - 24)}px`); // first scroll step hides the bar there too. Set on the board's wrapper: the spacer below needs them.
el.style.setProperty('--kb-top', `${Math.max(0, top - 12)}px`); const vars = el.parentElement ?? el;
vars.style.setProperty('--kb-full', `${Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - 24)}px`);
vars.style.setProperty('--kb-top', `${Math.max(0, top - 12)}px`);
setKanbanMultiRow(true);
requestAnimationFrame(() => { requestAnimationFrame(() => {
const colTops = Array.from(el.querySelectorAll<HTMLElement>('[data-kanban-col]')).map((c) => c.offsetTop); const colTops = Array.from(el.querySelectorAll<HTMLElement>('[data-kanban-col]')).map((c) => c.offsetTop);
const tops = new Set(colTops);
const multi = tops.size > 1;
setKanbanMultiRow(multi);
setKanbanPerRow(colTops.filter((t) => t === Math.min(...colTops)).length); setKanbanPerRow(colTops.filter((t) => t === Math.min(...colTops)).length);
}); });
}; };
@@ -636,16 +637,6 @@ export const TasksView: React.FC<TasksViewProps> = ({
}; };
}, [isKanban, isKanbanWide, boardColumns.length, selectedListId, loading]); }, [isKanban, isKanbanWide, boardColumns.length, selectedListId, loading]);
// One row on the desktop: take back what still overflows (paddings), so the page does not scroll. Measured
// only once the new height is on the page; measured right away, the old layout (e.g. two tall rows before
// stretching the board to full width) counted as overflow and squeezed the columns to their minimum.
useLayoutEffect(() => {
if (!kanbanColHeight || kanbanMultiRow || kanbanPhoneSnap) return;
const area = kanbanBoardRef.current?.closest<HTMLElement>('[data-app-scroll]');
if (!area) return;
const over = area.scrollHeight - area.clientHeight;
if (over > 0) setKanbanColHeight((h) => (h ? Math.max(KANBAN_MIN_COL_HEIGHT, h - over) : h));
}, [kanbanColHeight, kanbanMultiRow, kanbanPhoneSnap]);
// Several rows: one wheel step moves to the next row (or back up to the previous row / the page top), // Several rows: one wheel step moves to the next row (or back up to the previous row / the page top),
// smoothly. The browser's own snapping goes back to the nearest point, so a wheel step shorter than a row // smoothly. The browser's own snapping goes back to the nearest point, so a wheel step shorter than a row
@@ -694,7 +685,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
area.scrollTo({ top: next, behavior: 'smooth' }); area.scrollTo({ top: next, behavior: 'smooth' });
}; };
// The first row grows while the bar above the board scrolls away // The first row grows while the bar above the board scrolls away
const onScroll = () => board.style.setProperty('--kb-scroll', `${area.scrollTop}px`); const vars = board.parentElement ?? board;
const onScroll = () => vars.style.setProperty('--kb-scroll', `${area.scrollTop}px`);
onScroll(); onScroll();
area.addEventListener('wheel', onWheel, { passive: false }); area.addEventListener('wheel', onWheel, { passive: false });
area.addEventListener('scroll', onScroll, { passive: true }); area.addEventListener('scroll', onScroll, { passive: true });
@@ -3163,6 +3155,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
); );
})} })}
</div> </div>
{/* Desktop: room as high as what is still visible of the bar above the board, so the page can
scroll the bar away even with one row while the first row grows (the page keeps its height) */}
{kanbanMultiRow && <div aria-hidden style={{ height: KANBAN_BAR_SPACER }} />}
</div> </div>
) : ( ) : (
/* STANDARD LIST VIEW (CLEAN & UNCLUTTERED) */ /* STANDARD LIST VIEW (CLEAN & UNCLUTTERED) */