diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24ce2ce..4df762a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,18 @@ 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).
+
+- 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.
+
## [0.17.0] - 2026-10-31
### Added
@@ -395,7 +407,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 @@
[](#)
[](#-schnellstart)
[](#-deine-daten-gehören-dir)
-[](CHANGELOG.md)
+[](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/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/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/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/App.tsx b/frontend/src/App.tsx
index c27800c..9adb3e9 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}
/>
)}
@@ -1741,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 && (
+
+ )}
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 */