feat(manacore/web): add PWA support with offline UX, update prompt, and icons

- Generate PWA icons (192x192, 512x512, apple-touch-icon) from favicon
- Add Apple PWA meta tags, theme-color, and viewport-fit=cover to app.html
- Upgrade caching preset to 'full' (adds font + CDN caching)
- Add manifest shortcuts for Dashboard, Todo, Calendar, Chat
- Switch registerType to 'prompt' for user-controlled updates
- Add OfflineIndicator component (offline banner + sync status badge)
- Add PwaUpdatePrompt component (detects waiting SW, skip-waiting on confirm)
- Add networkStore for online/offline + sync status tracking
- Wire sync manager status into networkStore for pending change counts
- Update offline page text to reflect local-first architecture
- Add mobile/desktop app strategy doc and Tauri v2 implementation plan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-05 17:15:36 +02:00
parent a1c3e99c7c
commit 22e06ef803
13 changed files with 1352 additions and 3 deletions

View file

@ -3,7 +3,12 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="%sveltekit.assets%/apple-touch-icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="theme-color" content="#6366f1" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="ManaCore" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">

View file

@ -0,0 +1,180 @@
<script lang="ts">
import { networkStore } from '$lib/stores/network.svelte';
import { _ } from 'svelte-i18n';
let dismissed = $state(false);
// Reset dismissed state when coming back online
$effect(() => {
if (networkStore.isOnline) {
dismissed = false;
}
});
const showBanner = $derived(!networkStore.isOnline && !dismissed);
const showSyncBadge = $derived(
networkStore.isOnline && networkStore.pendingCount > 0 && networkStore.syncStatus !== 'syncing'
);
const showSyncing = $derived(networkStore.isOnline && networkStore.syncStatus === 'syncing');
</script>
{#if showBanner}
<div class="offline-banner" role="status" aria-live="polite">
<div class="offline-icon">
<svg width="16" height="16" viewBox="0 0 256 256" fill="currentColor">
<path
d="M213.92,210.62a8,8,0,1,1-11.84,10.76l-39-42.86A83.93,83.93,0,0,0,48,128a84.37,84.37,0,0,0,.55,9.41,8,8,0,0,1-15.92,1.18A99.64,99.64,0,0,1,32,128a99.68,99.68,0,0,1,22.57-63.24L42.08,51.38A8,8,0,1,1,53.92,40.62ZM167.43,128a83.48,83.48,0,0,1-1.25,14.42,8,8,0,0,0,6.52,9.24,8.25,8.25,0,0,0,1.37.12,8,8,0,0,0,7.87-6.64A99.64,99.64,0,0,0,183.43,128a8,8,0,0,0-16,0Zm-32.53-56.88a8,8,0,0,0,5.15-15.13A100.06,100.06,0,0,0,32.63,128a8,8,0,0,0,16,0,84.07,84.07,0,0,1,86.27-83.88ZM231.43,128a8,8,0,0,0-16,0,84,84,0,0,1-14.82,47.64,8,8,0,0,0,13.22,9,100,100,0,0,0,17.6-56.64Zm-103.43,72a12,12,0,1,0,12,12A12,12,0,0,0,128,200Z"
/>
</svg>
</div>
<span class="offline-text"
>{$_('pwa.offline', { default: 'Offline — Änderungen werden lokal gespeichert' })}</span
>
<button
type="button"
class="dismiss-btn"
onclick={() => (dismissed = true)}
aria-label="Schliessen"
>
<svg width="14" height="14" viewBox="0 0 256 256" fill="currentColor">
<path
d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"
/>
</svg>
</button>
</div>
{/if}
{#if showSyncBadge}
<div class="sync-badge" role="status" aria-live="polite">
<span class="sync-dot pending"></span>
<span class="sync-text">
{networkStore.pendingCount}
{$_('pwa.pendingChanges', { default: 'Änderungen warten auf Sync' })}
</span>
</div>
{/if}
{#if showSyncing}
<div class="sync-badge" role="status" aria-live="polite">
<span class="sync-dot syncing"></span>
<span class="sync-text">{$_('pwa.syncing', { default: 'Synchronisiere...' })}</span>
</div>
{/if}
<style>
.offline-banner {
position: fixed;
top: 12px;
left: 50%;
transform: translateX(-50%);
z-index: 200;
display: flex;
align-items: center;
gap: 8px;
background: rgba(239, 68, 68, 0.92);
color: white;
padding: 8px 16px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
animation: slideDown 0.3s ease-out;
backdrop-filter: blur(8px);
}
@keyframes slideDown {
from {
transform: translateX(-50%) translateY(-20px);
opacity: 0;
}
to {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
}
.offline-icon {
display: flex;
align-items: center;
flex-shrink: 0;
}
.offline-text {
white-space: nowrap;
}
.dismiss-btn {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.15);
border: none;
color: white;
padding: 4px;
border-radius: 4px;
cursor: pointer;
flex-shrink: 0;
margin-left: 4px;
}
.dismiss-btn:hover {
background: rgba(255, 255, 255, 0.25);
}
.sync-badge {
position: fixed;
top: 12px;
right: 12px;
z-index: 200;
display: flex;
align-items: center;
gap: 6px;
background: hsl(var(--color-card, 0 0% 10%) / 0.92);
color: hsl(var(--color-foreground, 0 0% 90%));
padding: 6px 12px;
border-radius: 8px;
font-size: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
animation: fadeIn 0.2s ease-out;
backdrop-filter: blur(8px);
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.sync-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.sync-dot.pending {
background: #f59e0b;
}
.sync-dot.syncing {
background: #6366f1;
animation: pulse 1s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
.sync-text {
white-space: nowrap;
}
</style>

View file

@ -0,0 +1,161 @@
<script lang="ts">
import { onMount } from 'svelte';
import { _ } from 'svelte-i18n';
let showPrompt = $state(false);
let registration = $state<ServiceWorkerRegistration | null>(null);
onMount(() => {
if (!('serviceWorker' in navigator)) return;
function onNewSW(reg: ServiceWorkerRegistration) {
registration = reg;
showPrompt = true;
}
navigator.serviceWorker.ready.then((reg) => {
// Already waiting worker present
if (reg.waiting) {
onNewSW(reg);
return;
}
// Watch for new installs
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing;
if (!newWorker) return;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
onNewSW(reg);
}
});
});
});
});
function handleUpdate() {
if (!registration?.waiting) return;
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
// Reload once the new SW takes over
navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});
}
function handleDismiss() {
showPrompt = false;
}
</script>
{#if showPrompt}
<div class="update-banner" role="alert">
<div class="update-content">
<svg class="update-icon" width="18" height="18" viewBox="0 0 256 256" fill="currentColor">
<path
d="M224,128a96,96,0,1,1-96-96A96.11,96.11,0,0,1,224,128Zm-40-8H136V80a8,8,0,0,0-16,0v48a8,8,0,0,0,8,8h48a8,8,0,0,0,0-16Z"
/>
</svg>
<span class="update-text">
{$_('pwa.updateAvailable', { default: 'Neue Version verfügbar' })}
</span>
</div>
<div class="update-actions">
<button type="button" class="update-btn" onclick={handleUpdate}>
{$_('pwa.updateNow', { default: 'Aktualisieren' })}
</button>
<button type="button" class="dismiss-btn" onclick={handleDismiss}>
{$_('pwa.later', { default: 'Später' })}
</button>
</div>
</div>
{/if}
<style>
.update-banner {
position: fixed;
bottom: 12px;
right: 12px;
z-index: 200;
display: flex;
align-items: center;
gap: 12px;
background: hsl(var(--color-card, 0 0% 10%) / 0.95);
color: hsl(var(--color-foreground, 0 0% 90%));
padding: 12px 16px;
border-radius: 12px;
font-size: 13px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
animation: slideUp 0.3s ease-out;
backdrop-filter: blur(12px);
border: 1px solid hsl(var(--color-foreground, 0 0% 90%) / 0.1);
max-width: 360px;
}
@keyframes slideUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.update-content {
display: flex;
align-items: center;
gap: 8px;
}
.update-icon {
flex-shrink: 0;
color: #6366f1;
}
.update-text {
white-space: nowrap;
}
.update-actions {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.update-btn {
background: #6366f1;
border: none;
color: white;
padding: 5px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
}
.update-btn:hover {
background: #5558e6;
}
.dismiss-btn {
background: hsl(var(--color-foreground, 0 0% 90%) / 0.08);
border: none;
color: hsl(var(--color-foreground, 0 0% 90%) / 0.6);
padding: 5px 10px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
white-space: nowrap;
}
.dismiss-btn:hover {
background: hsl(var(--color-foreground, 0 0% 90%) / 0.15);
color: hsl(var(--color-foreground, 0 0% 90%) / 0.8);
}
</style>

View file

@ -0,0 +1,58 @@
/**
* Network status store tracks online/offline state and pending sync changes.
*
* Integrates with the unified sync manager to show sync status
* and pending change counts in the UI.
*/
import type { SyncStatus } from '$lib/data/sync';
let isOnline = $state(typeof navigator !== 'undefined' ? navigator.onLine : true);
let syncStatus = $state<SyncStatus>('idle');
let pendingCount = $state(0);
let cleanups: (() => void)[] = [];
function initialize() {
if (typeof window === 'undefined') return;
const handleOnline = () => (isOnline = true);
const handleOffline = () => (isOnline = false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
cleanups.push(() => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
});
}
function destroy() {
for (const fn of cleanups) fn();
cleanups = [];
}
function setSyncStatus(status: SyncStatus) {
syncStatus = status;
}
function setPendingCount(count: number) {
pendingCount = count;
}
export const networkStore = {
get isOnline() {
return isOnline;
},
get syncStatus() {
return syncStatus;
},
get pendingCount() {
return pendingCount;
},
initialize,
destroy,
setSyncStatus,
setPendingCount,
};

View file

@ -34,6 +34,8 @@
import { linkLocalStore, linkMutations } from '@manacore/shared-links';
import { manacoreStore } from '$lib/data/local-store';
import { createUnifiedSync } from '$lib/data/sync';
import { networkStore } from '$lib/stores/network.svelte';
import { db } from '$lib/data/database';
import { dashboardStore } from '$lib/stores/dashboard.svelte';
import {
THEME_DEFINITIONS,
@ -306,6 +308,16 @@
trackReturnVisit();
const getToken = () => authStore.getValidToken();
unifiedSync = createUnifiedSync(SYNC_SERVER_URL, getToken);
unifiedSync.onStatusChange(async (s) => {
networkStore.setSyncStatus(s);
// Update pending count when sync status changes
try {
const count = await db.table('_pendingChanges').count();
networkStore.setPendingCount(count);
} catch {
// DB not ready yet
}
});
unifiedSync.startAll();
userSettings.load().catch(() => {});

View file

@ -3,8 +3,11 @@
import { onMount } from 'svelte';
import { theme } from '$lib/stores/theme';
import { authStore } from '$lib/stores/auth.svelte';
import { networkStore } from '$lib/stores/network.svelte';
import { loadAutomations } from '$lib/triggers';
import SuggestionToast from '$lib/components/SuggestionToast.svelte';
import OfflineIndicator from '$lib/components/OfflineIndicator.svelte';
import PwaUpdatePrompt from '$lib/components/PwaUpdatePrompt.svelte';
let { children } = $props();
@ -12,6 +15,9 @@
// Initialize theme
const cleanupTheme = theme.initialize();
// Initialize network status tracking
networkStore.initialize();
// Initialize auth
await authStore.initialize();
@ -20,9 +26,12 @@
return () => {
cleanupTheme();
networkStore.destroy();
};
});
</script>
{@render children()}
<SuggestionToast />
<OfflineIndicator />
<PwaUpdatePrompt />

View file

@ -4,6 +4,6 @@
<OfflinePage
appName="ManaCore"
offlineMessage="ManaCore benötigt eine Internetverbindung."
accentColor="#4f46e5"
offlineMessage="Du bist offline. Deine Daten sind lokal gespeichert und werden synchronisiert, sobald du wieder online bist."
accentColor="#6366f1"
/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -18,6 +18,24 @@ export default defineConfig({
shortName: 'ManaCore',
description: 'ManaCore App Ecosystem',
themeColor: '#6366f1',
registerType: 'prompt',
preset: 'full',
shortcuts: [
{ name: 'Dashboard', short_name: 'Home', url: '/', description: 'Zum Dashboard' },
{
name: 'Neue Aufgabe',
short_name: 'Aufgabe',
url: '/todo',
description: 'Neue Aufgabe erstellen',
},
{
name: 'Kalender',
short_name: 'Kalender',
url: '/calendar',
description: 'Kalender öffnen',
},
{ name: 'Chat', short_name: 'Chat', url: '/chat', description: 'Chat öffnen' },
],
})
),
],

View file

@ -0,0 +1,279 @@
# Mobile & Desktop App Strategie
> Analyse der Optionen, die bestehende ManaCore SvelteKit-App als native Mobile- und Desktop-App auszuliefern.
>
> Stand: April 2026
## Ausgangslage
Die ManaCore Unified App ist eine SvelteKit 2 + Svelte 5 Anwendung mit:
- **27+ Module** in einer einzigen App (`apps/manacore/apps/web`)
- **Local-first Architektur** mit Dexie.js / IndexedDB (120+ Collections)
- **Tailwind CSS** für Styling
- **Hintergrund-Sync** via mana-sync (Go, WebSocket)
- Bestehende **Expo/React Native Mobile-Apps** im Monorepo (einzelne Module)
Ziel: Die gesamte unified App auf iOS, Android, macOS, Windows und Linux bringen — idealerweise mit maximaler Code-Wiederverwendung.
---
## Optionen
### 1. Tauri v2
**Funktionsweise:** Nutzt die native WebView des Betriebssystems (WebKit auf macOS/iOS, WebView2 auf Windows, WebKitGTK auf Linux, Android WebView). Backend in Rust. Kein gebündelter Browser.
**Plattformen:** iOS, Android, macOS, Windows, Linux — alles mit einer Codebase.
| Aspekt | Bewertung |
|--------|-----------|
| SvelteKit-Wiederverwendung | Sehr gut — SPA via `adapter-static`, Svelte ist First-Class-Citizen |
| IndexedDB/Dexie.js | Funktioniert, aber iOS-WebKit-Limit ~500 MB |
| Native APIs | Gut — Dateisystem, Notifications, Clipboard, Biometrics, Updater u.v.m. Eigene Plugins in Rust/Swift/Kotlin |
| Bundle-Grösse | 210 MB (kein Chromium gebündelt) |
| App Store Distribution | Alle Stores unterstützt |
| Performance | Sehr gut auf Desktop. Auf Mobile abhängig von System-WebView-Qualität |
| Community | ~85k GitHub Stars, aktive Entwicklung |
**Vorteile:**
- Einziges Framework das alle 5 Plattformen mit einer Codebase abdeckt
- Winzige Bundles im Vergleich zu Electron
- Svelte/SvelteKit offiziell unterstützt
- Gute Plugin-Architektur
**Nachteile:**
- Mobile-Support erst seit Oktober 2024 stabil — für eine komplexe App mit 27+ Modulen ein Risiko
- WebView-Inkonsistenzen zwischen Plattformen (besonders ältere Android-Geräte)
- Rust-Toolchain für eigene Plugins nötig
- Plugin-Ökosystem kleiner als Capacitor oder Electron
---
### 2. Capacitor (Ionic)
**Funktionsweise:** Wrapping-Framework, das eine Web-App in einer nativen WebView-Shell ausführt. Entwickelt von Ionic (Nachfolger von Cordova). Bridge zu nativen APIs.
**Plattformen:** iOS und Android (Kernfokus). Desktop nur via Electron-Plugin.
| Aspekt | Bewertung |
|--------|-----------|
| SvelteKit-Wiederverwendung | Gut — SPA via `adapter-static`, offizielle Anleitungen verfügbar |
| IndexedDB/Dexie.js | Funktioniert, selbes iOS-WebKit-Limit wie Tauri |
| Native APIs | Sehr umfangreich — grösstes Plugin-Ökosystem aller WebView-Frameworks (Camera, Filesystem, Push, Haptics, Contacts, etc.) |
| Bundle-Grösse | 1530 MB |
| App Store Distribution | Exzellent — explizit dafür gebaut, tausende Apps in den Stores |
| Performance | Gut für die meisten Anwendungsfälle |
| Community | Sehr gross, Version 6, produktionsbewährt (Burger King, Sworkit) |
**Vorteile:**
- Grösstes Plugin-Ökosystem für Mobile
- Sehr ausgereift und produktionsbewährt
- Einfache Integration (`npx cap sync`)
- Niedriger Wartungsaufwand
**Nachteile:**
- Kein nativer Desktop-Support — braucht zusätzlich Electron oder Tauri
- Für "alle Plattformen" sind zwei Frameworks nötig
- Selbe iOS-IndexedDB-Einschränkungen
---
### 3. Electron
**Funktionsweise:** Bündelt eine vollständige Chromium-Instanz + Node.js mit der Web-App. Die App läuft in ihrem eigenen Chrome-Browser.
**Plattformen:** macOS, Windows, Linux. Kein Mobile-Support.
| Aspekt | Bewertung |
|--------|-----------|
| SvelteKit-Wiederverwendung | Gut — SPA oder SSR mit Node-Adapter möglich |
| IndexedDB/Dexie.js | Exzellent — eigenes Chromium, keine Limits, konsistentes Verhalten |
| Native APIs | Sehr umfangreich — Dateisystem, Tray, Menüs, Dialoge, Shortcuts, Autostart, Protocol Handler u.v.m. Volles npm-Ökosystem |
| Bundle-Grösse | 80200+ MB (Chromium + Node.js gebündelt) |
| App Store Distribution | Möglich (VS Code, Slack, Discord im Mac App Store) |
| Performance | Gut für UI, hoher RAM-Verbrauch (100300 MB Baseline) |
| Community | Industriestandard seit 2013, riesige Community |
**Vorteile:**
- Beste IndexedDB-Unterstützung (eigenes Chromium, keine Plattform-Abhängigkeit)
- Ausgereiftestes Desktop-Framework
- Riesiges Ökosystem
- Node.js-Zugriff für Server-seitige Operationen
**Nachteile:**
- Kein Mobile-Support
- Sehr grosse Bundles (80200 MB)
- Hoher RAM-Verbrauch
- Regelmässige Chromium-Sicherheitsupdates nötig
---
### 4. PWA (Progressive Web App)
**Funktionsweise:** Die bestehende Web-App wird mit Service Worker und Web App Manifest erweitert. Kein nativer Wrapper — läuft direkt im Browser.
**Plattformen:** Alle (via Browser installierbar). iOS eingeschränkt.
| Aspekt | Bewertung |
|--------|-----------|
| SvelteKit-Wiederverwendung | Perfekt — 100%, keine Anpassungen nötig |
| IndexedDB/Dexie.js | Funktioniert, aber **kritisch auf iOS: Safari löscht IndexedDB-Daten nach 7 Tagen Inaktivität** |
| Native APIs | Begrenzt — Notifications, Geolocation, Camera, Share API, Clipboard. Kein echtes Dateisystem (iOS), eingeschränkter Hintergrund-Sync |
| Bundle-Grösse | 0 MB zusätzlich |
| App Store Distribution | Schwierig — Apple lehnt reine WebView-Apps zunehmend ab. Google Play via TWA möglich |
| Performance | Identisch zur Web-Version |
| Community | Web-Standard, aber Apple treibt PWA-Support nicht voran |
**Vorteile:**
- Zero Aufwand — Service Worker + Manifest hinzufügen
- Sofort installierbar auf allen Plattformen
- Kein App-Store-Review nötig
- Updates sofort verfügbar
**Nachteile:**
- **Für local-first auf iOS nicht verlässlich** (7-Tage-IndexedDB-Löschung)
- Begrenzte native APIs
- Apple akzeptiert reine WebView-Apps kaum noch im App Store
- Keine echte "App-Erfahrung" auf iOS
---
### 5. React Native / Expo
**Funktionsweise:** Echte native UI-Komponenten, gesteuert durch JavaScript. Kein WebView — rendert direkt UIKit (iOS) bzw. Android Views. Neue Architektur (Fabric + TurboModules) seit 2024 Standard.
**Plattformen:** iOS und Android (Kernfokus). Desktop experimentell (react-native-macos/windows von Microsoft).
| Aspekt | Bewertung |
|--------|-----------|
| SvelteKit-Wiederverwendung | Keine — komplett andere Technologie (React + JSX statt Svelte) |
| IndexedDB/Dexie.js | Nicht verfügbar — Alternativen: SQLite, MMKV, WatermelonDB |
| Native APIs | Exzellent — voller Zugriff auf alle nativen APIs. 50+ Expo-Module |
| Bundle-Grösse | 2050 MB |
| App Store Distribution | Exzellent — EAS Build + EAS Submit für automatisierte Store-Submissions |
| Performance | Beste Mobile-Performance aller Optionen (native Rendering) |
| Community | Sehr gross — Meta, Microsoft, Amazon, Shopify nutzen React Native |
**Vorteile:**
- Beste Performance auf Mobile (kein WebView-Overhead)
- Voller Zugang zu allen nativen APIs
- Bestehende Expo-Erfahrung und Apps im Monorepo
- OTA-Updates via EAS Update
**Nachteile:**
- **Kein Code-Sharing mit SvelteKit** — komplettes UI-Rewrite nötig
- Dexie.js/IndexedDB nicht verfügbar, Datenschicht muss komplett neu gebaut werden
- 27+ Module doppelt pflegen = enormer Aufwand
- Kein verlässlicher Desktop-Support
---
### 6. Wails
**Funktionsweise:** Wie Tauri, aber Backend in Go statt Rust. Nutzt die System-WebView. Go-Funktionen direkt aus dem Frontend aufrufbar.
**Plattformen:** macOS, Windows, Linux. Kein Mobile-Support.
| Aspekt | Bewertung |
|--------|-----------|
| SvelteKit-Wiederverwendung | Gut — SPA, Svelte offiziell unterstützt |
| IndexedDB/Dexie.js | Wie Tauri — abhängig von System-WebView |
| Native APIs | Basis — Fenster, Menüs, Dialoge, Events, Clipboard. Eigene APIs in Go |
| Bundle-Grösse | 515 MB |
| App Store Distribution | Desktop-Stores möglich |
| Performance | Gut auf Desktop |
| Community | Mittel (~26k Stars), Wails v3 seit langem in Entwicklung |
**Vorteile:**
- Passt zur bestehenden Go-Expertise (6 Go-Services im Monorepo)
- Kleine Bundles
- Go-Backend kann direkt auf bestehende Service-Logik zugreifen
**Nachteile:**
- Kein Mobile-Support
- Deutlich kleinere Community als Tauri oder Electron
- Weniger Native APIs als Tauri
- Wails v3 Entwicklung schleppend
---
## Vergleichsmatrix
| Kriterium | Tauri v2 | Capacitor | Electron | PWA | React Native | Wails |
|---|---|---|---|---|---|---|
| **iOS** | Ja (seit 2024) | Ja (ausgereift) | Nein | Eingeschränkt | Ja (ausgereift) | Nein |
| **Android** | Ja (seit 2024) | Ja (ausgereift) | Nein | Ja | Ja (ausgereift) | Nein |
| **macOS** | Ja | Via Electron | Ja | Via Browser | Experimentell | Ja |
| **Windows** | Ja | Via Electron | Ja | Via Browser | Experimentell | Ja |
| **Linux** | Ja | Via Electron | Ja | Via Browser | Nein | Ja |
| **SvelteKit direkt nutzbar** | Ja (SPA) | Ja (SPA) | Ja (SPA/SSR) | Ja (100%) | Nein | Ja (SPA) |
| **IndexedDB/Dexie.js** | Gut¹ | Gut¹ | Exzellent | Riskant² | N/A | Gut¹ |
| **Native API Umfang** | Gut | Sehr gut | Sehr gut | Begrenzt | Exzellent | Basis |
| **Bundle-Grösse** | 210 MB | 1530 MB | 80200 MB | 0 MB | 2050 MB | 515 MB |
| **App Store tauglich** | Ja | Ja | Ja (Desktop) | Schwierig | Ja | Ja (Desktop) |
| **Community** | Gross (85k★) | Gross | Sehr gross | Web-Standard | Sehr gross | Mittel (26k★) |
| **Wartungsaufwand** | Mittel | NiedrigMittel | Mittel | Minimal | Hoch (2. Codebase) | Niedrig |
¹ iOS-WebKit-Limit ~500 MB für IndexedDB
² iOS Safari: Löschung nach 7 Tagen Inaktivität
---
## Empfehlung
### Primärstrategie: Tauri v2
Tauri v2 ist das einzige Framework, das alle 5 Zielplattformen (iOS, Android, macOS, Windows, Linux) mit einer einzigen SvelteKit-Codebase abdeckt. Svelte ist offiziell unterstützt, die Bundles sind winzig, und die Plugin-Architektur ist erweiterbar.
**Risiken die im Auge behalten werden müssen:**
- Mobile-Support ist jung — gründliches Testing mit allen 27+ Modulen nötig
- WebView-Inkonsistenzen auf älteren Android-Geräten
- iOS-WebKit-Limit für IndexedDB (~500 MB) bei wachsender Datenmenge
### Fallback: Capacitor (Mobile) + Tauri (Desktop)
Falls Tauri v2 Mobile sich für die Komplexität der 27+ Module als zu unreif erweist:
- **Capacitor** für iOS/Android — ausgereifter, grösstes Mobile-Plugin-Ökosystem
- **Tauri v2** für Desktop — leichtgewichtig, Svelte-First-Class
- Dieselbe SvelteKit SPA-Codebase für beide Wrapper
### PWA als sofortige Massnahme
Unabhängig von der nativen Strategie: Service Worker und Web App Manifest hinzufügen. Kostet fast nichts, bringt sofortige Installierbarkeit auf Desktop (Chrome/Edge). Auf iOS für local-first allerdings nicht verlässlich.
### React Native / Expo nur für dedizierte Einzel-Apps
Die bestehenden Expo-Apps im Monorepo machen Sinn für Module, die eine fundamental native Mobile-UX brauchen (z.B. Cards mit Swipe-Gesten, Chat mit nativen Push Notifications). Für "die gesamte unified App auf Mobile bringen" ist der Aufwand (komplettes Rewrite) nicht verhältnismässig.
### IndexedDB-Risiko mitigieren
Das grösste technische Risiko über alle WebView-Ansätze hinweg ist das iOS-WebKit-Verhalten:
- **SQLite-Plugin als Alternative auf Mobile** — Tauri hat `tauri-plugin-sql`, Capacitor hat `@capacitor-community/sqlite`
- **Hybride Strategie:** IndexedDB im Web, SQLite im nativen Wrapper
- **Dexie.js** arbeitet an experimentellen SQLite-Backends (Dexie Cloud)
---
## Nächste Schritte
1. **PWA-Grundlagen einbauen** — Service Worker + Manifest für sofortige Desktop-Installierbarkeit
2. **Tauri v2 Proof-of-Concept** — SvelteKit-App als SPA mit adapter-static bauen, in Tauri laden, auf allen 5 Plattformen testen
3. **IndexedDB-Limits evaluieren** — Tatsächlichen Speicherbedarf der 120+ Collections messen, iOS-Verhalten unter Last testen
4. **SQLite-Fallback prototypen** — Dexie.js mit SQLite-Backend oder Storage-Abstraktionsschicht evaluieren
5. **Entscheidung treffen** — Basierend auf PoC-Ergebnissen: Tauri allein oder Capacitor+Tauri Kombi

627
docs/PLAN_TAURI_V2.md Normal file
View file

@ -0,0 +1,627 @@
# Implementierungsplan: PWA + Tauri v2 für ManaCore
> Schrittweiser Plan um die ManaCore Unified Web App zuerst als PWA auszubauen und anschliessend als native Desktop- und Mobile-App via Tauri v2 auszuliefern — Plattform für Plattform.
>
> Stand: April 2026
---
## Übersicht
### Roadmap
```
Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6 Phase 7 Phase 8
PWA → SPA-Modus → macOS → Windows → Linux → Android → iOS → CI/CD & QA
(Web) (Basis) (Desktop) (Desktop) (Desktop) (Mobile) (Mobile) (alle)
◄── geringster Aufwand höchster Aufwand ──►
```
Jede Phase liefert ein funktionsfähiges Release. Man muss nicht alle Plattformen gleichzeitig fertig haben.
### Zielstruktur
```
apps/manacore/
├── apps/
│ ├── web/ # Bestehende SvelteKit-App (Web + PWA)
│ ├── tauri/ # NEU: Tauri v2 Shell (alle nativen Plattformen)
│ │ ├── src-tauri/
│ │ │ ├── src/ # Rust Backend
│ │ │ ├── capabilities/ # Permissions pro Plattform
│ │ │ ├── icons/ # App-Icons
│ │ │ ├── Cargo.toml
│ │ │ └── tauri.conf.json
│ │ └── package.json
│ └── ...
```
---
## Phase 1: PWA ausbauen
**Ziel:** Die Web-App wird installierbar, offline-fähig und fühlt sich auf allen Geräten wie eine App an. Diese Arbeit ist **direkte Vorarbeit für Tauri** — nichts davon ist verschwendet.
**Aufwand:** 12 Wochen (davon Responsive UI der grösste Posten)
### 1.1 Service Worker Caching-Strategie
`@vite-pwa/sveltekit` ist bereits integriert. Was fehlt ist eine durchdachte Caching-Strategie:
- [ ] **App-Shell** (HTML, CSS, JS, Fonts) → Precaching (beim Install sofort cachen)
- [ ] **Modul-Assets** (Icons, Bilder) → Cache-First mit Stale-While-Revalidate
- [ ] **API-Calls** → Network-First mit Fallback auf Cache (für Offline-Lesbarkeit)
- [ ] **Dexie.js Daten** → Brauchen kein SW-Caching (liegen in IndexedDB)
Konfiguration in `vite.config.ts` erweitern:
```typescript
// Workbox-Strategien für @vite-pwa/sveltekit
{
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/.*\.mana\.how\/api\//,
handler: 'NetworkFirst',
options: { cacheName: 'api-cache', expiration: { maxEntries: 100 } }
}
]
}
}
```
### 1.2 Icons & Manifest vervollständigen
- [ ] App-Icons in allen Grössen generieren (16×16 bis 512×512)
- [ ] Maskable Icon für Android (sichere Zone beachten)
- [ ] Apple Touch Icons (180×180)
- [ ] `manifest.json` Felder prüfen:
- `display: "standalone"` (kein Browser-Chrome)
- `orientation: "any"`
- `theme_color` und `background_color`
- `shortcuts` für Quick-Actions (Neue Aufgabe, Neuer Termin, etc.)
- `categories: ["productivity", "utilities"]`
### 1.3 Offline-UX
Da die App local-first ist, funktioniert sie offline bereits grundlegend. Was fehlt:
- [ ] **Offline-Indikator** — dezentes Banner/Icon wenn kein Internet
- [ ] **Sync-Status-Anzeige** — "3 Änderungen warten auf Sync"
- [ ] **Graceful Degradation** — Module die Backend brauchen (Chat AI, Picture AI) zeigen "Offline" statt Error
- [ ] **Queued Actions** — Pending Changes sichtbar machen
### 1.4 Update-Flow
- [ ] "Neue Version verfügbar" Dialog wenn Service Worker updated
- [ ] Sanfter Übergang — nicht mitten in der Arbeit neu laden
- [ ] Skip-Waiting Strategie mit User-Bestätigung
### 1.5 Responsive UI
Der grösste Einzelposten — aber **identische Arbeit** die für Tauri Mobile (Phase 67) sowieso anfällt:
- [ ] **Audit:** Welche der 27+ Module haben bereits Mobile-Breakpoints?
- [ ] **Navigation:** Sidebar → Bottom-Tab-Bar oder Hamburger-Drawer auf Mobile
- [ ] **Touch-Targets:** Mindestens 44×44px für alle interaktiven Elemente
- [ ] **Keyboard-Handling:** Input-Felder nicht von Soft-Keyboard verdeckt
- [ ] **Viewport Meta-Tag:** `viewport-fit=cover` für Edge-to-Edge
- [ ] **Hover-States:** Alternativen für Touch-Geräte (kein Hover)
- [ ] **Modale/Dialoge:** Full-Screen auf Mobile statt zentriertes Overlay
### 1.6 Validierung
- [ ] Chrome DevTools → Lighthouse PWA-Audit → Score 100
- [ ] App installieren auf: macOS (Chrome), Windows (Edge), Android (Chrome), iOS (Safari)
- [ ] Offline testen: Flugmodus → App öffnen → CRUD-Operationen → Internet an → Sync
- [ ] Update testen: Neue Version deployen → "Update verfügbar" erscheint
### 1.7 Bekannte PWA-Limitationen (beobachten, nicht lösen)
Diese Punkte sind der Grund warum Tauri als nächster Schritt folgt:
- iOS Safari: IndexedDB-Löschung nach 7 Tagen Inaktivität (betrifft local-first)
- iOS Safari: Keine Push-Notifications im Hintergrund
- Kein System Tray, keine globalen Shortcuts
- Kein Deep Link Protocol (`manacore://`)
- App Store Präsenz nicht möglich (ausser TWA auf Android)
---
## Phase 2: SvelteKit SPA-Modus
**Ziel:** Die Web-App kann als statische SPA gebaut werden — Voraussetzung für Tauri.
**Aufwand:** 24 Tage
### 2.1 Adapter-Static Konfiguration
Aktuell nutzt die App `@sveltejs/adapter-node`. Für Tauri brauchen wir `adapter-static`:
- [ ] `@sveltejs/adapter-static` als Dependency hinzufügen
- [ ] Build-Flag für Tauri-Modus:
```js
// svelte.config.js
import adapterNode from '@sveltejs/adapter-node';
import adapterStatic from '@sveltejs/adapter-static';
const isTauri = process.env.TAURI_ENV === 'true';
const config = {
kit: {
adapter: isTauri
? adapterStatic({ pages: 'build-static', assets: 'build-static', fallback: 'index.html' })
: adapterNode({ out: 'build' }),
},
};
```
- [ ] `fallback: 'index.html'` setzen für SPA-Routing (alle Routen → index.html)
### 2.2 Server-seitige Logik auflösen
**`hooks.server.ts` — Runtime Env-Injection (grösste Hürde):**
Aktuell injiziert `hooks.server.ts` ~15 API-URLs als `window.__PUBLIC_*__` Variablen. Lösung:
- [ ] Config-Abstraktionsschicht bauen die Web und Tauri bedient:
```typescript
// src/lib/config/env.ts
export async function getApiUrl(key: string): Promise<string> {
if (window.__TAURI__) {
const config = await invoke('get_config');
return config[key];
}
return window[`__PUBLIC_${key}__`] || import.meta.env[`PUBLIC_${key}`] || '';
}
```
- [ ] Bestehende `window.__PUBLIC_*__` Lösung bleibt für Web-Deployment intakt
**`hooks.server.ts` — Subdomain-Routing & Security Headers:**
- [ ] Beide irrelevant in Tauri — werden automatisch übersprungen
**Server-Routes (`routes/api/`, `routes/status/`):**
- [ ] `routes/api/*` Endpunkte inventarisieren
- [ ] Falls Client sie aufruft: auf direkte Backend-Aufrufe umstellen
- [ ] `routes/status/+page.server.ts` → Client-seitige Health-Checks
### 2.3 Validierung
- [ ] `TAURI_ENV=true pnpm --filter @manacore/web build` erzeugt `build-static/` mit `index.html`
- [ ] `build-static/` in einem einfachen HTTP-Server testen (`npx serve build-static`)
- [ ] Alle 27+ Module durchklicken — Client-Side-Routing funktioniert
- [ ] **Go/No-Go:** Falls fundamental SSR-abhängig → Plan überdenken
---
## Phase 3: macOS Desktop
**Ziel:** ManaCore läuft als native Desktop-App auf macOS. Erste Tauri-Plattform.
**Aufwand:** 23 Tage (inkrementell über SPA-Basis)
**Warum zuerst:** Entwicklung passiert auf macOS → direktes Testen, kein Extra-Setup. Tauri Desktop seit 2022 stabil (4 Jahre Reife). WebKit auf macOS = gleiche Engine wie Safari, bekanntes Verhalten. Kein App Store nötig — DMG direkt verteilen.
### 3.1 Voraussetzungen
- [ ] Rust Toolchain installieren (`rustup`)
- [ ] Xcode installieren/aktualisieren (für macOS Builds)
- [ ] Tauri CLI: `pnpm add -D @tauri-apps/cli`
### 3.2 Tauri-Projekt initialisieren
- [ ] `apps/manacore/apps/tauri/` erstellen
- [ ] `package.json` mit Workspace-Referenz zu `@manacore/web`
- [ ] `tauri.conf.json` konfigurieren:
```json
{
"build": {
"beforeBuildCommand": "TAURI_ENV=true pnpm --filter @manacore/web build",
"devUrl": "http://localhost:5173",
"frontendDist": "../../web/build-static"
},
"app": {
"title": "ManaCore",
"windows": [
{
"title": "ManaCore",
"width": 1280,
"height": 800,
"minWidth": 800,
"minHeight": 600
}
]
},
"bundle": {
"identifier": "how.mana.manacore",
"icon": ["icons/icon.png"]
}
}
```
### 3.3 Rust-Backend Setup
- [ ] Minimales `src-tauri/src/main.rs` mit Config-Provider:
```rust
#[tauri::command]
fn get_config() -> serde_json::Value {
serde_json::json!({
"MANA_CORE_AUTH_URL": "https://auth.mana.how",
"SYNC_SERVER_URL": "wss://sync.mana.how",
// ...
})
}
```
### 3.4 Capabilities & Permissions
- [ ] `capabilities/default.json`:
- `core:default` (Fenster, Events)
- `http:default` (HTTP-Requests an Backend-APIs)
- `notification:default` (Benachrichtigungen)
- `clipboard-manager:default` (Zwischenablage)
- `shell:default` (URLs im Browser öffnen)
### 3.5 Dev-Workflow einrichten
- [ ] Turborepo-Tasks für `tauri:dev`, `tauri:build`
- [ ] Root-Level Script: `"dev:manacore:desktop": "pnpm --filter @manacore/tauri dev"`
- [ ] Tauri Dev-Mode nutzt Vite Dev-Server (`devUrl: http://localhost:5173`) → Hot Reload
### 3.6 IndexedDB auf macOS WebKit testen
- [ ] Dexie.js Persistenz: App schliessen → öffnen → Daten noch da?
- [ ] Speicherverbrauch der 120+ Collections messen
- [ ] WebSocket-Sync zu mana-sync funktioniert?
- [ ] Reconnect nach Sleep/Wake?
### 3.7 Validierung
- [ ] App startet als natives macOS-Fenster
- [ ] Login/Auth funktioniert (gegen mana-auth)
- [ ] IndexedDB/Dexie.js persistiert Daten
- [ ] Sync funktioniert (WebSocket zu mana-sync)
- [ ] Mindestens 10 Module durchklicken und testen
- [ ] **Go/No-Go:** IndexedDB/Sync stabil? → Weiter. Instabil? → Electron-Fallback evaluieren.
---
## Phase 4: Windows Desktop
**Ziel:** ManaCore als native Windows-App.
**Aufwand:** 23 Tage (inkrementell über macOS)
**Warum als Zweites:** Relevanteste Desktop-Plattform nach macOS. Nutzt WebView2 (Chromium-basiert) — **andere Rendering-Engine als macOS** (WebKit). Das Testen hier bereitet auf Android vor (ebenfalls Chromium-basiert).
### 4.1 Voraussetzungen
- [ ] Windows-Testumgebung (VM, Dual-Boot, oder CI)
- [ ] WebView2 Runtime (auf Windows 10/11 vorinstalliert, Fallback für ältere Systeme einrichten)
- [ ] Rust Target hinzufügen: `rustup target add x86_64-pc-windows-msvc`
### 4.2 Plattform-spezifische Anpassungen
- [ ] NSIS oder MSI Installer konfigurieren in `tauri.conf.json`
- [ ] Code Signing Zertifikat beschaffen und einrichten
- [ ] Windows-spezifische Fenster-Optionen (Dekorationen, Startmenü-Integration)
### 4.3 Rendering-Unterschiede testen
Da WebView2 auf Chromium basiert (≠ macOS WebKit):
- [ ] CSS Rendering vergleichen (Flexbox, Grid, Scrollbars)
- [ ] IndexedDB-Verhalten prüfen (Chromium hat andere Limits/Verhalten als WebKit)
- [ ] Font-Rendering prüfen (Windows rendert Fonts anders)
- [ ] Scroll-Verhalten (Windows hat keine Rubber-Band-Scrolling)
### 4.4 Validierung
- [ ] App startet auf Windows 10 + 11
- [ ] Alle Module funktionieren (besonders UI-intensive: Calendar, Presi, Music)
- [ ] Installer erzeugt saubere Installation + Deinstallation
- [ ] Auto-Start Option funktioniert
---
## Phase 5: Linux Desktop
**Ziel:** ManaCore als native Linux-App.
**Aufwand:** 0.51 Tag (inkrementell über macOS)
**Warum als Drittes:** Technisch fast identisch zu macOS (beide WebKitGTK-basiert). Geringster zusätzlicher Aufwand aller Plattformen. Kein Signing nötig.
### 5.1 Voraussetzungen
- [ ] Linux-Testumgebung (VM, WSL2, oder GPU-Server)
- [ ] WebKitGTK + System-Dependencies: `sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev`
- [ ] Rust Target: `rustup target add x86_64-unknown-linux-gnu`
### 5.2 Paketformate
- [ ] AppImage generieren (universell, kein Install nötig)
- [ ] DEB-Paket für Debian/Ubuntu
- [ ] Optional: RPM für Fedora, Flatpak für breitere Distribution
### 5.3 Validierung
- [ ] App startet auf Ubuntu 22.04+ und Fedora 38+
- [ ] WebKitGTK-spezifische Rendering-Checks
- [ ] AppImage funktioniert ohne Installation
- [ ] System-Tray Integration (falls in Phase 8 implementiert)
---
## Phase 6: Android
**Ziel:** ManaCore als Android-App im Play Store.
**Aufwand:** 12 Wochen (inkrementell über Desktop)
**Warum vor iOS:** Android WebView basiert auf Chromium — ähnlich wie Windows WebView2, also bereits vertrautes Terrain. Kein Developer Account nötig zum Testen (APK Sideloading). Tauri v2 Android-Support ist etwas reifer als iOS.
### 6.1 Voraussetzungen
- [ ] Android Studio + Android SDK installieren
- [ ] Android Emulator oder physisches Testgerät
- [ ] Tauri Android-Target: `tauri android init`
- [ ] Keystore für Signing erstellen
- [ ] Google Play Developer Account (25$ einmalig)
### 6.2 Android-spezifische Anpassungen
- [ ] `AndroidManifest.xml` — Permissions (Internet, Notifications, Camera falls nötig)
- [ ] Minimum SDK Version festlegen (API 24 / Android 7.0 = WebView 80+)
- [ ] Status Bar / Navigation Bar Farben anpassen
- [ ] Back-Button Verhalten (Android Hardware-Back)
- [ ] Splash Screen (`tauri-plugin-splash-screen`)
### 6.3 Responsive UI auf echten Geräten testen
Die Responsive-Arbeit aus Phase 1.5 (PWA) wird hier validiert:
- [ ] Navigation auf kleinen Screens (Bottom-Tab / Drawer)
- [ ] Touch-Targets auf verschiedenen Bildschirmgrössen
- [ ] Landscape vs Portrait Orientation
- [ ] Soft-Keyboard schiebt Layout nicht kaputt
- [ ] Scroll-Performance in langen Listen
### 6.4 WebView-Versionen
Grösstes Android-spezifisches Risiko — die System-WebView variiert je nach Gerät:
- [ ] Minimum WebView-Version definieren und enforzen
- [ ] Auf älteren Geräten testen (Android 8, 9, 10)
- [ ] Feature-Detection für kritische APIs (IndexedDB, WebSocket)
### 6.5 Mobile-spezifische Plugins
- [ ] `tauri-plugin-haptics` — Haptisches Feedback bei Interaktionen
- [ ] `tauri-plugin-biometric` — Fingerprint für Login
- [ ] Push Notifications via FCM (Firebase Cloud Messaging)
### 6.6 Play Store Vorbereitung
- [ ] App-Icons in allen Android-Grössen (Adaptive Icons)
- [ ] Play Store Screenshots (Phone + Tablet)
- [ ] Privacy Policy URL
- [ ] App-Beschreibung (Deutsch + Englisch)
- [ ] Internal Testing Track einrichten
- [ ] AAB (Android App Bundle) statt APK für Store-Upload
### 6.7 Validierung
- [ ] App läuft auf Android 10+ stabil
- [ ] IndexedDB-Persistenz über App-Neustarts
- [ ] Sync funktioniert (inkl. Reconnect nach Background/Foreground)
- [ ] Play Store Internal Testing erfolgreich
- [ ] **Go/No-Go:** Android stabil? → Weiter zu iOS. Probleme? → Capacitor als Mobile-Fallback evaluieren.
---
## Phase 7: iOS
**Ziel:** ManaCore als iOS-App im App Store.
**Aufwand:** 12 Wochen (inkrementell über Android)
**Warum zuletzt:** Höchster Overhead aller Plattformen: Apple Developer Account (99$/Jahr), Provisioning Profiles, Xcode-Pflicht, strengster App Store Review. iOS WebKit hat die strengsten IndexedDB-Limits. Hier entscheidet sich ob ein SQLite-Fallback nötig wird.
### 7.1 Voraussetzungen
- [ ] Apple Developer Account (99$/Jahr) — zwingend auch zum Testen auf echtem Gerät
- [ ] Xcode aktualisieren (neueste Version)
- [ ] Provisioning Profile + Signing Certificates erstellen
- [ ] Tauri iOS-Target: `tauri ios init`
- [ ] Physisches iOS-Gerät zum Testen (Simulator reicht nicht für alles)
### 7.2 iOS-spezifische Anpassungen
- [ ] `Info.plist` — Permissions, URL Schemes, Privacy Descriptions
- [ ] Safe Area Insets (Notch, Dynamic Island, Home Indicator)
- [ ] `viewport-fit=cover` + CSS `env(safe-area-inset-*)` für Edge-to-Edge
- [ ] iOS-spezifische Scroll-Bouncing / Overscroll-Verhalten
- [ ] Status Bar Style (Light/Dark Content je nach Theme)
### 7.3 IndexedDB Stresstest (kritisch)
Das ist der **wichtigste Test der gesamten Roadmap:**
- [ ] Speicherverbrauch der 120+ Collections unter realer Nutzung messen
- [ ] iOS WebKit-Limit (~500 MB) Stresstest — wie nah kommen wir?
- [ ] Persistenz: App schliessen → Tage warten → öffnen → Daten noch da?
- [ ] Vergleich: PWA Safari vs installierte Tauri-App (Tauri sollte stabiler sein)
Falls Limits zum Problem werden:
- [ ] `tauri-plugin-sql` (SQLite) als Fallback evaluieren
- [ ] Hybride Strategie: IndexedDB im Web, SQLite in Tauri iOS
- [ ] Storage-Abstraktionsschicht in Dexie.js integrieren
### 7.4 Mobile-spezifische Plugins (iOS)
- [ ] `tauri-plugin-haptics` — Taptic Engine Feedback
- [ ] `tauri-plugin-biometric` — Face ID / Touch ID
- [ ] Push Notifications via APNs (Apple Push Notification service)
- [ ] Optional: `tauri-plugin-barcode-scanner` für QR-Codes
### 7.5 App Store Vorbereitung
- [ ] App-Icons in allen iOS-Grössen (1024×1024 Store Icon)
- [ ] App Store Screenshots (iPhone 6.7", 6.1", iPad)
- [ ] App Store Beschreibung + Keywords (Deutsch + Englisch)
- [ ] Privacy Policy + Terms of Service URLs
- [ ] App Review Information (Demo-Account für Apple Reviewer)
- [ ] TestFlight einrichten für Beta-Tester
### 7.6 App Store Review vorbereiten
Apple prüft strenger als Google. Wichtige Punkte:
- [ ] Guideline 4.2 (Minimum Functionality) — App muss Mehrwert über Website hinaus bieten
- [ ] Tauri erzeugt echte native Apps (kein reiner WebView-Wrapper) → sollte akzeptiert werden
- [ ] Native Features dokumentieren (Notifications, Biometrics, Haptics) als Differenzierung zur Web-App
- [ ] Offline-Fähigkeit hervorheben
### 7.7 Validierung
- [ ] App läuft auf iOS 16+ stabil (iPhone + iPad)
- [ ] IndexedDB-Persistenz über Wochen (!) testen
- [ ] Face ID / Touch ID funktioniert
- [ ] Push Notifications kommen an
- [ ] TestFlight Beta-Test erfolgreich
- [ ] App Store Review bestanden
---
## Phase 8: Desktop-Features, CI/CD & Qualitätssicherung
**Ziel:** Plattformübergreifende Stabilität, native Desktop-Features und automatisierte Builds.
**Aufwand:** 23 Wochen
### 8.1 Desktop-Features (alle Desktop-Plattformen)
- [ ] **System Tray**`tauri-plugin-positioner`, Minimieren in Tray, Quick-Actions
- [ ] **Auto-Updater**`tauri-plugin-updater`, Update-Server, In-App Dialog
- [ ] **Native Notifications**`tauri-plugin-notification`, Calendar-Erinnerungen
- [ ] **Globale Shortcuts**`Cmd/Ctrl+N` (Neues Item), `Cmd/Ctrl+K` (Quick-Suche), `Cmd/Ctrl+1-9` (Modul wechseln)
- [ ] **Deep Links**`manacore://` Protocol für Links aus Emails/Chat
- [ ] **Dateisystem** — Drag & Drop, "Öffnen mit", Export (PDF/CSV)
### 8.2 Build Pipeline (Forgejo CI)
- [ ] macOS Build → DMG + App Bundle
- [ ] Windows Build → MSI + NSIS Installer
- [ ] Linux Build → AppImage + DEB
- [ ] Android Build → APK + AAB
- [ ] iOS Build → IPA
- [ ] Signing für alle Plattformen automatisieren
- [ ] Build-Artefakte als Releases veröffentlichen
### 8.3 Auto-Update Infrastruktur
- [ ] Update-Server / Release-Endpunkt auf Mac Mini
- [ ] Versionierung: SemVer aus Git-Tags
- [ ] Delta-Updates wo möglich
### 8.4 App Store Deployment
- [ ] Apple App Store: Upload via `xcrun altool` oder Transporter
- [ ] Google Play: Upload via Fastlane oder Play Console API
- [ ] Optional: Microsoft Store (MSIX), Snapcraft (Linux)
### 8.5 Testmatrix
| Test | macOS | Windows | Linux | Android | iOS |
|------|-------|---------|-------|---------|-----|
| App startet | | | | | |
| Login/Auth | | | | | |
| IndexedDB Persistenz | | | | | |
| Sync (Push/Pull) | | | | | |
| WebSocket Reconnect | | | | | |
| Alle 27+ Module | | | | | |
| Offline-Modus | | | | | |
| Auto-Update | | | | | |
| Deep Links | | | | | |
| Native Notifications | | | | | |
| Biometrics | — | — | — | | |
### 8.6 Performance-Benchmarks
- [ ] Cold-Start: Ziel < 2s Desktop, < 3s Mobile
- [ ] RAM: Ziel < 150 MB Desktop, < 100 MB Mobile
- [ ] IndexedDB R/W Performance: Web vs Tauri vergleichen
- [ ] Scroll-Performance mit 1000+ Items in Listen
### 8.7 Edge Cases
- [ ] Kein Internet → App funktioniert offline
- [ ] Sleep/Wake → Sync reconnect
- [ ] Mehrere Desktop-Instanzen gleichzeitig
- [ ] Web-Nutzer öffnet Desktop-App → Daten synchronisieren automatisch
---
## Aufwand-Übersicht
| Phase | Plattform | Aufwand (inkrementell) | Kumuliert | Grösste Hürde |
|-------|-----------|----------------------|-----------|---------------|
| **1** | PWA (Web) | 12 Wochen | 12 Wochen | Responsive UI |
| **2** | SPA-Basis | 24 Tage | ~2.5 Wochen | `hooks.server.ts` → Client-Config |
| **3** | macOS | 23 Tage | ~3 Wochen | — (geringster Aufwand) |
| **4** | Windows | 23 Tage | ~3.5 Wochen | Andere Rendering-Engine (Chromium) |
| **5** | Linux | 0.51 Tag | ~3.5 Wochen | Nur Testen |
| **6** | Android | 12 Wochen | ~5.5 Wochen | WebView-Versionen älterer Geräte |
| **7** | iOS | 12 Wochen | ~7.5 Wochen | IndexedDB-Limits, App Store Review |
| **8** | CI/CD & QA | 23 Wochen | ~10 Wochen | Plattformübergreifende Stabilität |
**Gesamt: ca. 812 Wochen** von PWA bis zur stabilen Auslieferung auf allen 5 Plattformen + App Stores.
### Was jede Phase freischaltet
| Nach Phase | Nutzer können... |
|------------|-----------------|
| Phase 1 | App auf jedem Gerät aus dem Browser installieren (PWA) |
| Phase 3 | ManaCore als macOS Desktop-App nutzen (DMG) |
| Phase 4 | ManaCore als Windows Desktop-App nutzen (Installer) |
| Phase 5 | ManaCore auf Linux nutzen (AppImage) |
| Phase 6 | ManaCore aus dem Play Store installieren |
| Phase 7 | ManaCore aus dem App Store installieren |
| Phase 8 | Automatische Updates, native Features, stabile CI/CD |
---
## Risiken & Mitigationen
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|--------|-------------------|--------|------------|
| iOS WebKit IndexedDB-Limit | Mittel | Hoch | SQLite-Fallback evaluieren (Phase 7.3) |
| WebView-Inkonsistenzen Android | Mittel | Mittel | Minimum WebView-Version enforzen, Polyfills |
| Server-Routes im SPA-Modus | Sicher | Mittel | Phase 2.2 löst dies systematisch |
| Tauri Mobile Bugs | Mittel | Hoch | Capacitor als Fallback bereithalten |
| App Store Rejection (Apple) | Niedrig | Hoch | Native Features als Differenzierung betonen |
| Bundle-Grösse zu gross | Niedrig | Niedrig | Tauri-Bundles typisch 210 MB |
| Responsive UI Aufwand unterschätzt | Mittel | Mittel | In Phase 1 (PWA) anpacken, nicht aufschieben |
---
## Entscheidungspunkte (Go/No-Go)
| Nach Phase | Frage | Falls Nein |
|------------|-------|------------|
| **Phase 2** | Funktioniert die App stabil als SPA? | Falls fundamental SSR-abhängig → Architektur überdenken |
| **Phase 3** | Läuft Desktop auf macOS? IndexedDB/Sync stabil? | → Electron als Desktop-Fallback |
| **Phase 6** | Android stabil? WebView-Performance akzeptabel? | → Capacitor als Mobile-Fallback |
| **Phase 7** | iOS IndexedDB-Persistenz ausreichend? | → SQLite-Fallback oder Capacitor + SQLite |
| **Phase 7** | App Store Review bestanden? | → Native Features nachrüsten, ggf. Capacitor |