Lösungsvorschläge · Oikos · April 2026

# Konkrete Optimierungen — sortiert nach Impact

13 Punkte · 4 kritisch · 5 hoch · 4 mittel

1
Kritisch · 1–2 h
Sidebar: title-Tooltips für icon-only Modus
Nutzer bei 1024–1279 px sehen 11 Icons ohne jede Beschriftung. Ein einfaches `title`-Attribut auf jedem Nav-Item genügt als sofortiger Fix. Langfristig: expanded sidebar ab 1280 px früher aktivieren.
Ist-Zustand

Kein Hinweis
was die Icons bedeuten

Soll-Zustand

Tooltip bei
Hover/Focus sichtbar

router.js — Nav-Item Rendering+1 Zeile
// In renderAppShell() oder wo nav-items erzeugt werden: - a.setAttribute('aria-label', label); + a.setAttribute('aria-label', label); + a.setAttribute('title', label); // Tooltip für collapsed sidebar
/* Optional: CSS-Tooltip statt native title (für Styling) */ + .nav-sidebar .nav-item[title]:hover::after { + content: attr(title); + position: absolute; + left: calc(100% + 10px); + /* ... Tooltip-Styles */ + }

2
Kritisch · 5 min
Modal-Close: von 40 px auf 44 px
Ein Einzeiler in `layout.css`. Der Fehler liegt darin, dass `--target-md` (40 px) statt `--target-base` (44 px) verwendet wird. Das Token existiert bereits — es muss nur gewechselt werden.
Problem: 40 × 40 px
✕
40 × 40 px–target-md
4 px unter Minimum
Ein durchschnittlicher Fingertipp belegt 44–50 px. Beim Schließen unter Stress (mit einer Hand, unterwegs) erhöht sich die Fehlklickrate spürbar.
Fix: 44 × 44 px
✕
44 × 44 px--target-base
Apple HIG ✓
layout.css · .modal-panel__close 1 Zeile
- width: var(--target-md); /* 40px */ - height: var(--target-md); /* 40px */ + width: var(--target-base); /* 44px — Apple HIG Minimum */ + height: var(--target-base); /* 44px */

3
Kritisch · 15 min
Widget-Links: Tap-Target auf 44 px bringen
Der „Alle anzeigen →"-Link in jedem Dashboard-Widget hat keinen definierten `min-height`. Bei 12 px Text liegt der tatsächliche Klickbereich bei etwa 16–18 px — weit unter iOS-Minimum. Fix: padding + min-height in `dashboard.css`.
Ist-Zustand
Aufgaben
Alle →

Tap-Target ≈ 16 px Höhe

Soll-Zustand
Aufgaben
Alle →

Tap-Target ≥ 32 px mit Padding

dashboard.css · .widget__link
+ .widget__link { + min-height: var(--target-base); /* 44px */ + display: inline-flex; + align-items: center; + padding: 0 var(--space-2); + border-radius: var(--radius-sm); + } + .widget__link:hover { + background: color-mix(in srgb, var(--widget-accent) 10%, transparent); + }

4
Kritisch · 30 min
FAB konsolidieren: .fab + .page-fab → eine Klasse
Beide Klassen definieren fast denselben Button mit leicht abweichender `bottom`-Berechnung. Das führt zu inkonsistenter Positionierung. Die Lösung: eine Klasse, ein Token, alle Seiten konsistent.
Ist-Zustand: 2 Klassen
＋
.fab
bottom: nav-height + safe-area + space-4
＋
.page-fab
bottom: nav-bottom-height + 24px + safe-area

Verschiedene bottom-Werte → visuell inkonsistent je nach Seite

Soll-Zustand: 1 Klasse
＋
.page-fab (Canonical)
Einheitliche bottom-Formel, alle Seiten

Alle Seiten nutzen .page-fab, .fab wird entfernt oder als Alias gesetzt

5
Hoch · 2–4 h
Dashboard: Widget-Reihenfolge anpassbar machen
Die Widget-Config speichert aktuell nur `{ id, visible }`. Ein `order`-Feld hinzufügen und im Customization-Modal drag-sortierbar machen (per `touch-action: none` + pointer events, kein externes Drag-Library nötig).
Ist-Zustand: Feste Reihenfolge
Dashboard
⊙ sichtbar
Aufgaben
⊙ sichtbar
Kalender
⊙ sichtbar

Reihenfolge fest codiert — nicht änderbar

Soll-Zustand: Drag-to-reorder
⠿
Dashboard
⊙
⠿
Kalender
⊙
⠿
Aufgaben
⊙

⠿ Handle zum Sortieren — gespeichert in localStorage

dashboard.js — Config-Schema
- const DEFAULT_WIDGET_CONFIG = WIDGET_IDS.map((id) => ({ id, visible: true })); + const DEFAULT_WIDGET_CONFIG = WIDGET_IDS.map((id, i) => ({ id, visible: true, order: i }));
// Beim Laden: nach order sortieren + config.sort((a, b) => a.order - b.order);

6
Hoch · 1 h
Offline-Banner in App-Shell
Der Service Worker ist vorhanden, aber die App gibt kein visuelles Feedback zum Offline-Zustand. Ein kleines Banner direkt unter der Navigation (wenn `navigator.onLine` false ist) ist die robusteste Lösung — kein Flickering, immer sichtbar.
Soll-Zustand: Shell-Level Banner
📡 Offline — Änderungen werden gespeichert und beim nächsten Verbindungsaufbau synchronisiert. Offline

Erscheint unter Nav-Bar, verschwindet automatisch wenn online

router.js — App-Shell Setup
+ function initOfflineBanner() { + const banner = document.getElementById('offline-banner'); + const update = () => banner.hidden = navigator.onLine; + window.addEventListener('online', update); + window.addEventListener('offline', update); + update(); + }

7
Hoch · 2–3 h
Globale Keyboard Shortcuts (Desktop)
30 % der Nutzer verwenden Desktop. Ein zentrales Keyboard-Shortcut-System im Router beschleunigt häufige Aktionen erheblich. Alle Shortcuts per `?` einsehbar.
`/`Suche öffnen
`N` Neu (kontextabhängig)
`G D`→ Dashboard
`G T`→ Tasks
`G C`→ Kalender
`Esc` Modal / Sheet schließen
`?`Shortcut-Übersicht
`⌘K` Command Palette

8
Hoch · 3–4 h
Zentrales Undo-System für destruktive Aktionen
Aktuell haben manche Aktionen Undo-Toasts, andere nicht. Eine zentrale `undoStack`-Utility mit standardisiertem Toast-Muster schafft konsistente Sicherheit über alle Module hinweg.
Soll-Zustand: konsistente Undo-Abdeckung
Aufgabe löschen
✓ Undo vorhanden
Eintrag Einkaufsliste löschen
✓ Undo vorhanden
Kontakt löschen
✗ fehlt
Notiz löschen
✗ fehlt
Geburtstag löschen
✗ fehlt
Mahlzeit löschen
✗ fehlt

→ Alle DELETE-Aktionen über eine zentrale `deleteWithUndo(url, label)` Funktion routen

9
Hoch · 3 h
Onboarding: Modul-spezifische Erst-Nutzung
Statt 3 generischen Screens beim ersten Start: leere Zustände auf jeder Seite mit einem konkreten Tipp für genau dieses Modul. Zusätzlich: ein permanentes „?" / Hilfe-Icon in der Toolbar für den Onboarding-Replay.
📋
Aufgaben
Tippe ＋ um deine erste Aufgabe zu erstellen. Wische links zum Löschen.
📅
Kalender
Verbinde deinen Google Kalender unter Einstellungen → Kalender-Sync.
💰
Budget
Lege Kategorien und ein Monatsbudget fest. Einnahmen und Ausgaben werden automatisch summiert.
🔔
Erinnerungen
RRule ermöglicht Wiederholungen: täglich, wöchentlich, oder komplex wie „jeden 2. Montag".
…
+7 weitere
Jedes Modul erklärt sich beim ersten Besuch selbst.

10–13
Mittel · Backlog
Weitere Optimierungen
🟢 Toast Swipe-to-Dismiss

`pointerdown` + `pointermove` auf `.toast` → bei >40 px horizontaler Bewegung `toast--out` Klasse setzen und entfernen. Entspricht iOS/Android-Konvention. Ca. 30 Zeilen JS.

🟢 Swipe-Reveal auf alle Listen

Das bestehende `.swipe-row` Pattern auf Kontakte, Notizen und Geburtstage ausweiten. Die Basis-CSS existiert bereits in `layout.css` — nur modul-spezifische Reveal-Farben und JS-Handler ergänzen.

🟢 Dashboard: weniger Farbrauschen

Widget-Icons auf neutrale `--color-text-secondary` setzen, Widget-Akzentfarbe nur für Badge und Link reservieren. Reduziert visuelle Überforderung wenn alle 11 Widgets gleichzeitig sichtbar sind.

🟢 reminders.css lazy laden

`reminders.css` dynamisch per `<link rel="stylesheet">` nur in den Seiten laden, die Reminders anzeigen — analog zum bestehenden Lazy-Loading-Pattern für Page-Module.