From 945be2c1ad71d1006d46e27eb5fa2b141815335b Mon Sep 17 00:00:00 2001 From: Sandro Date: Sat, 31 Oct 2026 10:04:16 +0100 Subject: [PATCH] feat(kanban): wide board animates, keeps header width and caps column width Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 8 ++++++- README.md | 2 +- frontend/package-lock.json | 4 ++-- frontend/package.json | 2 +- frontend/src/App.tsx | 7 +----- frontend/src/features/triage/TasksView.tsx | 26 +++++++++++++++------- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ce2ce..b8594d5 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.17.1] - 2026-10-31 + +### Changed +- Kanban maximieren: Nur noch das Board wird breiter, Listen-Reiter und „Neue Aufgabe anlegen“ behalten ihre Breite. Das Board wächst weich animiert, jede Spalte wird höchstens 380 px breit und das Board bleibt mittig (bei wenigen Spalten also schmaler als das Fenster). + ## [0.17.0] - 2026-10-31 ### Added @@ -395,7 +400,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.17.0...dev +[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.17.1...dev +[0.17.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.17.0...v0.17.1 [0.17.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.16.1...v0.17.0 [0.16.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.16.0...v0.16.1 [0.16.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.15.1...v0.16.0 diff --git a/README.md b/README.md index c3e05a2..31d93e8 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.17.0-16a34a?style=flat-square)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/Version-v0.17.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 31d9b23..9945125 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "mindsift-frontend", - "version": "0.17.0", + "version": "0.17.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mindsift-frontend", - "version": "0.17.0", + "version": "0.17.1", "license": "Unlicense", "dependencies": { "@fontsource-variable/inter": "5.3.0", diff --git a/frontend/package.json b/frontend/package.json index a57abc3..be8ce34 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "mindsift-frontend", "private": true, - "version": "0.17.0", + "version": "0.17.1", "license": "Unlicense", "type": "module", "scripts": { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c27800c..0aa65d3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -164,8 +164,6 @@ export const App: React.FC = () => { const [deck, setDeck] = useState([]); const [lists, setLists] = useState([]); const [activeTab, setActiveTab] = useState('triage'); - // Kanban asked for the full window width (reported by the tasks view) - const [tasksWide, setTasksWide] = useState(false); const [previousTab, setPreviousTab] = useState('triage'); const [selectedDocId, setSelectedDocId] = useState(null); const [selectedBlockId, setSelectedBlockId] = useState(null); @@ -1170,9 +1168,7 @@ export const App: React.FC = () => { {/* Main Container */} {/* The workspace fills the window exactly and scrolls only inside the note, never the page */}
@@ -1196,7 +1192,6 @@ export const App: React.FC = () => { refreshTrigger={refreshRevision} initialSelectedTaskId={selectedTaskId} openTasksCount={stats.tasksOpenCount} - onWideChange={setTasksWide} /> )} diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx index 37a672f..77d425c 100644 --- a/frontend/src/features/triage/TasksView.tsx +++ b/frontend/src/features/triage/TasksView.tsx @@ -62,10 +62,25 @@ interface TasksViewProps { refreshTrigger?: number; initialSelectedTaskId?: string | null; openTasksCount?: number; // all open tasks incl. shared lists (same as the nav bar counter) - onWideChange?: (wide: boolean) => void; // Kanban stretched to the full window width } const KANBAN_WIDE_KEY = 'mindsift:kanban_wide'; +// 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 + +// Only the board leaves the page column (header and "new task" keep their width): its width follows +// the columns (at most KANBAN_COL_MAX each, at most the window) and a margin keeps it centred – a +// negative one when it is wider than the page column. Width and margin are animated. +const kanbanBoardStyle = (wide: boolean, columns: number): React.CSSProperties => { + if (!wide) return { '--kanban-w': '100%', '--kanban-ml': '0px' } as React.CSSProperties; + const w = `min(100vw - 4rem, ${columns * KANBAN_COL_MAX + (columns - 1) * KANBAN_GAP}px)`; + return { + '--kanban-cols': columns, + '--kanban-w': w, + '--kanban-ml': `calc((100% - ${w}) / 2)`, + } as React.CSSProperties; +}; type TasksSession = { listId: string; viewMode: 'list' | 'kanban' }; @@ -76,7 +91,6 @@ export const TasksView: React.FC = ({ refreshTrigger = 0, initialSelectedTaskId, openTasksCount, - onWideChange, }) => { // Open list and view survive switching to other tabs (like the workspace's open note) const [viewMode, setViewMode] = useState<'list' | 'kanban'>(() => @@ -100,10 +114,6 @@ export const TasksView: React.FC = ({ } }; const isKanbanWide = viewMode === 'kanban' && kanbanWide; - useEffect(() => { - onWideChange?.(isKanbanWide); - }, [isKanbanWide, onWideChange]); - useEffect(() => () => onWideChange?.(false), [onWideChange]); const [filter, setFilter] = useState<'open' | 'done' | 'discarded'>('open'); const [tasks, setTasks] = useState([]); const [taskLists, setTaskLists] = useState([]); @@ -2214,10 +2224,10 @@ export const TasksView: React.FC = ({ /* DYNAMIC KANBAN BOARD */
{activeColumns.map((col, colIdx) => { const colTasks = kanbanTasks.filter((t) => {