From c7cff441a2f2a8b9b17381bb29b35447cd96b277 Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 2 Nov 2026 19:15:43 +0100 Subject: [PATCH 1/2] feat(ui): desktop scrolls inside, under a fixed header On the desktop the window no longer scrolls: the area between header and footer does, with room for its scrollbar kept on both sides so the content stays centred and nothing shifts between pages. This replaces the scrollbar-gutter tricks (header colour in the gutter, Chrome's always-on scrollbar, which also let the sticky header scroll away in Chrome). Triage fills the height: card and buttons stay, the queue takes the rest and scrolls inside. The Kanban height fit and the Quick Dump button use the scroll area. Phones keep scrolling the window. Co-Authored-By: Claude Opus 5.5 --- frontend/src/App.tsx | 53 +++++++++++++-------- frontend/src/features/triage/TasksView.tsx | 12 +++-- frontend/src/features/triage/TriageDeck.tsx | 12 +++-- frontend/src/index.css | 51 -------------------- 4 files changed, 48 insertions(+), 80 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index aa9439b..071770e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -206,21 +206,12 @@ export const App: React.FC = () => { const [showProfileMenu, setShowProfileMenu] = useState(false); const connection = useConnectionState(); - // Height of the app header as --app-header-h: the page background paints the header colour up to there, - // so the scrollbar gutter on the right continues the header instead of showing the page colour - const [headerEl, setHeaderEl] = useState(null); + // Desktop: the window does not scroll; this area under the header does (phones keep the window scroll) + const scrollAreaRef = useRef(null); + // A new page starts at its top (the desktop scroll area would otherwise keep the old page's position) useEffect(() => { - if (!headerEl || typeof ResizeObserver === 'undefined') return; - const root = document.documentElement; - const update = () => root.style.setProperty('--app-header-h', `${headerEl.offsetHeight}px`); - update(); - const observer = new ResizeObserver(update); - observer.observe(headerEl); - return () => { - observer.disconnect(); - root.style.removeProperty('--app-header-h'); - }; - }, [headerEl]); + scrollAreaRef.current?.scrollTo?.({ top: 0 }); + }, [activeTab]); const [showAbout, setShowAbout] = useState(false); const [showTriageTrash, setShowTriageTrash] = useState(false); const { update: availableUpdate, dismiss: dismissUpdate } = useUpdateCheck(); @@ -848,7 +839,7 @@ export const App: React.FC = () => { if (isPhoneWidth()) { openMobileTriageCapture(); } else { - window.scrollTo({ top: 0, behavior: 'smooth' }); + scrollAreaRef.current?.scrollTo?.({ top: 0, behavior: 'smooth' }); setCaptureFocusCount((c) => c + 1); } }; @@ -920,13 +911,12 @@ export const App: React.FC = () => { return (
{/* Top Header Bar: Floating on mobile, solid panel on desktop */}
{/* Mobile Header: Floating Bar with Logo Action Dropdown Left & Profile Avatar Right */} @@ -1306,14 +1296,36 @@ export const App: React.FC = () => { )} {/* Main Container */} - {/* The workspace fills the window exactly and scrolls only inside the note, never the page */} + {/* Desktop: the window never scrolls, only this area under the header (room for the scrollbar kept on + both sides, so the content stays centred under the header and nothing shifts between pages). The + workspace fills it exactly and scrolls only inside the note. Phones scroll the window as before. */} +
setActiveTab('triage')}> -
+
{activeTab === 'triage' && ( {
+
= ({ setKanbanColHeight(null); return; } - // From the board's top to the window's bottom; then take back what still overflows (footer, paddings) - const top = el.getBoundingClientRect().top + window.scrollY; - setKanbanColHeight(Math.max(320, window.innerHeight - top - 16)); + // From the board's top to the bottom of the app's scroll area (the desktop window itself does not + // scroll); then take back what still overflows (paddings) + const area = el.closest('[data-app-scroll]'); + const areaTop = area ? area.getBoundingClientRect().top - area.scrollTop : -window.scrollY; + const areaHeight = area ? area.clientHeight : window.innerHeight; + const top = el.getBoundingClientRect().top - areaTop; + setKanbanColHeight(Math.max(320, areaHeight - top - 16)); requestAnimationFrame(() => { - const over = document.documentElement.scrollHeight - window.innerHeight; + const over = area ? area.scrollHeight - area.clientHeight : document.documentElement.scrollHeight - window.innerHeight; if (over > 0) setKanbanColHeight((h) => (h ? Math.max(320, h - over) : h)); }); }; diff --git a/frontend/src/features/triage/TriageDeck.tsx b/frontend/src/features/triage/TriageDeck.tsx index ff0c62f..eca3de5 100644 --- a/frontend/src/features/triage/TriageDeck.tsx +++ b/frontend/src/features/triage/TriageDeck.tsx @@ -488,7 +488,9 @@ export const TriageDeck: React.FC = ({ } return ( -
+ // Desktop: a column filling the height under the header; card and buttons keep their size, the queue + // takes the rest and scrolls inside (the page only scrolls when the window is too low even for that) +
{/* Top Deck Status & Undo Bar */}
@@ -931,7 +933,7 @@ export const TriageDeck: React.FC = ({ {/* Waiting Quick Dump Queue List (Touch-sized & Swipeable Left/Right) */} {cards.length > 1 && ( -
+
{/* Phones: the queue stays folded so the card and its buttons fit on the screen */} {/* Folds open/closed on phones; always open from sm upwards */}
-
+
@@ -970,7 +972,7 @@ export const TriageDeck: React.FC = ({
-
+
{cards.slice(1).map((item) => ( Date: Mon, 2 Nov 2026 19:20:25 +0100 Subject: [PATCH 2/2] chore: release v0.60.0 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 12 +++++++++++- README.md | 2 +- frontend/package-lock.json | 4 ++-- frontend/package.json | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e029955..7b132fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ## [Unreleased] +## [0.60.0] - 2026-11-02 + +### Changed +- Desktop: Das Fenster scrollt nicht mehr als Ganzes. Kopfleiste und Fußzeile stehen fest, nur der Bereich dazwischen scrollt; der Scrollbalken beginnt unter der Kopfleiste. Damit verschiebt sich beim Seitenwechsel nichts mehr, und der Rand neben der Kopfleiste braucht keine Sonderbehandlung. +- Triage am Desktop: Kachel und Knöpfe bleiben stehen, die Warteschlange füllt den restlichen Platz und scrollt in sich. + +### Fixed +- Chrome: Die Kopfleiste scrollte seit 0.59.3 beim Scrollen mit aus dem Bild. + ## [0.59.3] - 2026-11-02 ### Fixed @@ -869,7 +878,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.59.3...dev +[Unreleased]: https://git.dsit.me/sandrodach/MindSift/compare/v0.60.0...dev +[0.60.0]: https://git.dsit.me/sandrodach/MindSift/compare/v0.59.3...v0.60.0 [0.59.3]: https://git.dsit.me/sandrodach/MindSift/compare/v0.59.2...v0.59.3 [0.59.2]: https://git.dsit.me/sandrodach/MindSift/compare/v0.59.1...v0.59.2 [0.59.1]: https://git.dsit.me/sandrodach/MindSift/compare/v0.59.0...v0.59.1 diff --git a/README.md b/README.md index 780f83c..8e9de70 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.59.3-16a34a?style=flat-square)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/Version-v0.60.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 f61c721..2f5f1bd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "mindsift-frontend", - "version": "0.59.3", + "version": "0.60.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mindsift-frontend", - "version": "0.59.3", + "version": "0.60.0", "license": "AGPL-3.0-only", "dependencies": { "@fontsource-variable/inter": "5.3.0", diff --git a/frontend/package.json b/frontend/package.json index 74d6e48..4ef3748 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "mindsift-frontend", "private": true, - "version": "0.59.3", + "version": "0.60.0", "license": "AGPL-3.0-only", "type": "module", "scripts": {