From 2ff15491122732b54cc7bfbe459ea0c344b6a176 Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Nov 2026 22:11:10 +0100 Subject: [PATCH 1/2] feat(tasks): with one Kanban row too, the first scroll step hides the bar above On the desktop the board always works like with several rows: the first row grows while the bar above scrolls away, and a spacer below as high as what is still visible of the bar lets the page scroll even with one row. Replaces the one-row overflow correction. Co-Authored-By: Claude Opus 5.5 --- frontend/src/features/triage/TasksView.tsx | 39 ++++++++++------------ 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx index 28b5710..c943016 100644 --- a/frontend/src/features/triage/TasksView.tsx +++ b/frontend/src/features/triage/TasksView.tsx @@ -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 { 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 // 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)))))`; +// 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 const KANBAN_COL_MAX = 380; const KANBAN_GAP = 12; // gap-3 @@ -563,9 +565,8 @@ export const TasksView: React.FC = ({ // 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; // 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 bar above the board scrolls away, so a row always fills the screen; the wheel moves row by row - // (below). + // The first row grows while the bar above the board scrolls away, so a row always fills the screen (with + // one row too: the first scroll step hides the bar); the wheel moves row by row (below). const kanbanBoardRef = useRef(null); const [kanbanColHeight, setKanbanColHeight] = useState(null); const [kanbanMultiRow, setKanbanMultiRow] = useState(false); @@ -610,15 +611,15 @@ export const TasksView: React.FC = ({ const areaHeight = area ? area.clientHeight : window.innerHeight; const top = el.getBoundingClientRect().top - areaTop; 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 - // above the board frees while scrolling (--kb-top - --kb-scroll, see the scroll effect) - el.style.setProperty('--kb-full', `${Math.max(KANBAN_MIN_COL_HEIGHT, areaHeight - 24)}px`); - el.style.setProperty('--kb-top', `${Math.max(0, top - 12)}px`); + // A row fills the whole area (--kb-full); the first one grows into the room the bar above the board + // frees while scrolling (--kb-top - --kb-scroll, see the scroll effect). With one row as well, so the + // first scroll step hides the bar there too. Set on the board's wrapper: the spacer below needs them. + 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(() => { const colTops = Array.from(el.querySelectorAll('[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); }); }; @@ -636,16 +637,6 @@ export const TasksView: React.FC = ({ }; }, [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('[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), // 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 = ({ area.scrollTo({ top: next, behavior: 'smooth' }); }; // 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(); area.addEventListener('wheel', onWheel, { passive: false }); area.addEventListener('scroll', onScroll, { passive: true }); @@ -3163,6 +3155,9 @@ export const TasksView: React.FC = ({ ); })} + {/* 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 &&
}
) : ( /* STANDARD LIST VIEW (CLEAN & UNCLUTTERED) */ From da92ffa6b5444527add759512c5c8d5a26522016 Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Nov 2026 22:11:10 +0100 Subject: [PATCH 2/2] chore: release v0.65.0 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 8 +++++++- README.md | 2 +- frontend/package-lock.json | 4 ++-- frontend/package.json | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e555e0e..29f61f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ## [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 ### 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. - 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.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 diff --git a/README.md b/README.md index e555b03..a600dd1 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.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. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1559f7c..75ad226 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "mindsift-frontend", - "version": "0.64.1", + "version": "0.65.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mindsift-frontend", - "version": "0.64.1", + "version": "0.65.0", "license": "AGPL-3.0-only", "dependencies": { "@fontsource-variable/inter": "5.3.0", diff --git a/frontend/package.json b/frontend/package.json index 0f5a6a0..3a7aa72 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "mindsift-frontend", "private": true, - "version": "0.64.1", + "version": "0.65.0", "license": "AGPL-3.0-only", "type": "module", "scripts": {