From 945be2c1ad71d1006d46e27eb5fa2b141815335b Mon Sep 17 00:00:00 2001 From: Sandro Date: Sat, 31 Oct 2026 10:04:16 +0100 Subject: [PATCH 1/4] 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) => { From 57a97a37502dd25fe48b9f1f09397a58b75cebe2 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sat, 31 Oct 2026 10:08:41 +0100 Subject: [PATCH 2/4] fix(pwa): install button appears in the guide once Chrome reports the app installable Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 +++ frontend/src/App.tsx | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8594d5..c678126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ### 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). +### Fixed +- App installieren am Handy: Meldet Chrome erst nach dem Öffnen der Anleitung, dass MindSift installierbar ist (z. B. kurz nach dem Entfernen der alten App), erscheint in der Anleitung jetzt ein Knopf „Jetzt installieren“ für die direkte Installation. + ## [0.17.0] - 2026-10-31 ### Added diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0aa65d3..9adb3e9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1736,6 +1736,18 @@ export const App: React.FC = () => {
) : (
+ {/* Chrome may report "installable" only after the guide opened (e.g. shortly after the + old app was removed): then the real install is one tap away again */} + {deferredPwaPrompt && ( + + )}

From 4a923eaf01a69173581613700f88d27318d5e289 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sat, 31 Oct 2026 10:12:55 +0100 Subject: [PATCH 3/4] feat(pwa): loading screen in the colours of the last used theme Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ frontend/index.html | 26 +++++++++++++++++++++----- frontend/public/boot-theme.js | 17 +++++++++++++++++ frontend/public/sw.js | 4 +++- frontend/src/lib/theme.ts | 10 +++++++++- 5 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 frontend/public/boot-theme.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c678126..1040f15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ### 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). +- Ladebildschirm: Statt immer in Blau erscheint er in den Farben des zuletzt auf dem Gerät genutzten Themes (Hintergrund, Logo in der Akzentfarbe, Statusleiste passend); beim allerersten Besuch bleibt er blau. Neu dafür ist `boot-theme.js` (eigene Datei wegen der Content-Security-Policy); die App-Caches werden einmal erneuert. + ### Fixed - App installieren am Handy: Meldet Chrome erst nach dem Öffnen der Anleitung, dass MindSift installierbar ist (z. B. kurz nach dem Entfernen der alten App), erscheint in der Anleitung jetzt ein Knopf „Jetzt installieren“ für die direkte Installation. diff --git a/frontend/index.html b/frontend/index.html index 3a81e97..97e492a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,8 +3,10 @@ - + + @@ -43,11 +45,25 @@ - +

-
- - +
+ + + + + + + + + + + + + + +
diff --git a/frontend/public/boot-theme.js b/frontend/public/boot-theme.js new file mode 100644 index 0000000..ad962a1 --- /dev/null +++ b/frontend/public/boot-theme.js @@ -0,0 +1,17 @@ +// Runs before the first paint: the loading screen takes the colours of the theme used last on this +// device (stored by the app in lib/theme.ts), so a light or orange theme does not start in blue. +// A file instead of an inline script so the CSP can stay at script-src 'self'. +(function () { + try { + var colors = JSON.parse(localStorage.getItem('mindsift_boot_colors') || 'null'); + if (!colors || typeof colors.bg !== 'string' || typeof colors.accent !== 'string') return; + var root = document.documentElement; + root.style.setProperty('--boot-bg', colors.bg); + root.style.setProperty('--boot-accent', colors.accent); + if (typeof colors.onAccent === 'string') root.style.setProperty('--boot-on-accent', colors.onAccent); + var meta = document.querySelector('meta[name="theme-color"]'); + if (meta) meta.setAttribute('content', colors.bg); + } catch (e) { + // no storage: the loading screen stays blue + } +})(); diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 23ecc51..551a400 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -7,7 +7,7 @@ // would hand it the old values), the cache only offline. // - API requests are never touched; offline data comes from the app's own cache (lib/offline.ts). // Raise VERSION when this file changes in a way that needs fresh caches. -const VERSION = 'v5'; +const VERSION = 'v6'; const SHELL_CACHE = `mindsift-shell-${VERSION}`; const ASSET_CACHE = `mindsift-assets-${VERSION}`; const MAX_ASSETS = 80; @@ -23,6 +23,8 @@ const SHELL_URLS = [ '/icon-512x512.png', '/apple-touch-icon.png', '/badge-96x96.png', + '/boot-theme.js', + '/pwa-install.js', ]; self.addEventListener('install', (event) => { diff --git a/frontend/src/lib/theme.ts b/frontend/src/lib/theme.ts index 3a08ec0..b594469 100644 --- a/frontend/src/lib/theme.ts +++ b/frontend/src/lib/theme.ts @@ -236,6 +236,7 @@ export function getAccentColor(): string | null { export function setAccentColor(hex: string | null): void { store(ACCENT_KEY, hex); applyAccent(hex); + syncThemeColorMeta(); // keeps the loading screen's colours current } function hexToRgb(hex: string): [number, number, number] { @@ -352,15 +353,22 @@ function applyToDocument(themeId: ThemeId, customCss: string | null): void { // The status bar (theme-color) starts black (like the Android navigation bar of the installed app, // which only takes the manifest colour); once the app runs it follows the theme's background. +// The colours are also kept for the next start: public/boot-theme.js paints the loading screen in them. function syncThemeColorMeta(): void { const meta = document.querySelector('meta[name="theme-color"]'); if (!meta || typeof requestAnimationFrame === 'undefined') return; requestAnimationFrame(() => { - const bg = getComputedStyle(document.documentElement).getPropertyValue('--bg-app').trim(); + const css = getComputedStyle(document.documentElement); + const bg = css.getPropertyValue('--bg-app').trim(); if (bg) meta.setAttribute('content', bg); + const accent = css.getPropertyValue('--accent-primary').trim(); + const onAccent = css.getPropertyValue('--text-on-accent').trim(); + if (bg && accent) store(BOOT_COLORS_KEY, JSON.stringify({ bg, accent, onAccent })); }); } +const BOOT_COLORS_KEY = 'mindsift_boot_colors'; + function store(key: string, value: string | null): void { try { if (value === null) localStorage.removeItem(key); From 66d6b97aff94fe411771c6c3c9f934eb512f822a Mon Sep 17 00:00:00 2001 From: Sandro Date: Sat, 31 Oct 2026 10:16:45 +0100 Subject: [PATCH 4/4] style(journal): monochrome calendar icon instead of the emoji in day titles Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ frontend/src/features/workspace/DocumentEditor.tsx | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1040f15..4df762a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V - Ladebildschirm: Statt immer in Blau erscheint er in den Farben des zuletzt auf dem Gerät genutzten Themes (Hintergrund, Logo in der Akzentfarbe, Statusleiste passend); beim allerersten Besuch bleibt er blau. Neu dafür ist `boot-theme.js` (eigene Datei wegen der Content-Security-Policy); die App-Caches werden einmal erneuert. +- Journal: Vor dem Titel eines Tages steht statt des bunten Kalender-Emojis ein schlichtes Kalender-Symbol in der Textfarbe des Themes (nicht anklickbar, das Icon lässt sich im Journal ohnehin nicht ändern). + ### Fixed - App installieren am Handy: Meldet Chrome erst nach dem Öffnen der Anleitung, dass MindSift installierbar ist (z. B. kurz nach dem Entfernen der alten App), erscheint in der Anleitung jetzt ein Knopf „Jetzt installieren“ für die direkte Installation. diff --git a/frontend/src/features/workspace/DocumentEditor.tsx b/frontend/src/features/workspace/DocumentEditor.tsx index 1cd8d55..281323e 100644 --- a/frontend/src/features/workspace/DocumentEditor.tsx +++ b/frontend/src/features/workspace/DocumentEditor.tsx @@ -24,6 +24,7 @@ import { Undo2, Redo2, MoreHorizontal, + CalendarDays, } from 'lucide-react'; import { Document, DocumentBlock, User } from '../../lib/types'; import { api } from '../../lib/api'; @@ -1265,10 +1266,17 @@ export const DocumentEditor: React.FC = ({ {showEmojiPicker && canEdit && (