docs: add comprehensive technical wiki documentation and flaws analysis; remove legacy BRAIN.md
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 8s

This commit is contained in:
sandrodach committed 2026-10-26 13:27:35 +01:00
1 parent 10d5c6d563
commit 3480859d8a
13 files changed
+1577 -317

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
# 📚 MindSift Technische Dokumentation & Wiki
Willkommen in der zentralen technischen Dokumentation von **MindSift**. Dieses Wiki dient als verbindliche, tiefgehende Referenz für Entwickler, DevOps-Ingenieure und Systemadministratoren. Es dokumentiert die Architektur, das Datenmodell, APIs, Echtzeit-Protokolle, Kernmodule sowie bekannte technische Schwachstellen der Plattform.
---
## 🧭 Inhaltsverzeichnis (Wiki-Navigation)
```
Doku/
├── README.md # Einstieg, Inhaltsverzeichnis & Gesamtübersicht (dieses Dokument)
├── architecture.md # Systemarchitektur, Schichtenmodell, Concurrency & Stack
├── database.md # SQLite-Datenbankdesign, Schemas, Indizes & Migrationen
├── api/
│ ├── rest.md # Vollständige REST-API Referenz aller Endpunkte
│ └── websocket.md # WebSocket-Echtzeitprotokoll, Rooms & Document Locking
├── modules/
│ ├── triage.md # Modul 1: Triage & Quick-Dump (State Machine & Audio)
│ ├── tasks.md # Modul 2: Aufgaben & Kanban-System (Alarme & Scheduler)
│ ├── workspace.md # Modul 3: Workspace Outliner-Engine, Wiki-Links & Disk-Sync
│ └── lists.md # Modul 4: Geteilte Listen (Live-Kollaboration & D&D)
├── sharing-and-permissions.md # Synchrones Teilen, Rollen, Isolation & Mount-Entkopplung
├── theming.md # Single-File CSS Theme-Engine & Design-System
└── flaws.md # Technische Schwachstellen, Risiken & zukünftige Optimierungen
```
### 📑 Kapitel im Detail
1. **[Systemarchitektur & Technologie-Stack](architecture.md)**
Schichtenmodell, Go-Backend, React-Frontend, Concurrency-Handling und Thread-Safety.
2. **[Datenbank-Design & SQLite Schema](database.md)**
Vollständiges relationales Datenmodell, ER-Diagramm, Tabellendefinitionen, Indizes und Migrationsstrategie.
3. **[REST-API Referenz](api/rest.md)**
Spezifikation aller Endpunkte für Authentifizierung, Gedanken, Aufgaben, Dokumente, Listen, Teams und Webhooks.
4. **[WebSocket & Echtzeit-Synchronisation](api/websocket.md)**
Gorilla WebSocket Hub, Rooms, Presence-Tracking, Document Locking Mechanismen und Polling-Fallback.
5. **[Kernmodul: Triage & Quick-Dump](modules/triage.md)**
Instant-Capture-Pipeline, Finite State Machine, Gestensteuerung, Spracheingabe und Webhook-Ingestion.
6. **[Kernmodul: Aufgaben & Kanban-System](modules/tasks.md)**
Multi-Listen-Konzept, 3-Spalten Kanban-Board, Subtasks, Fälligkeiten und Multi-Channel Alarm-Ticker (SMTP, Discord, Webhooks).
7. **[Kernmodul: Workspace & Outliner-Engine](modules/workspace.md)**
Logseq-inspirierte hierarchische Block-Engine, Zoom-Hoisting, Wiki-Links, Disk-Mirroring (`.md`) und Globales To-Do Rollup.
8. **[Kernmodul: Geteilte Listen & Kollaboration](modules/lists.md)**
Listen-Typen, Mengen, Kategorien, Live-Checkoff und Drag & Drop Sortierung.
9. **[Synchrones Teilen & Berechtigungskonzept](sharing-and-permissions.md)**
Rollenmodell (Admin/Member), System-Isolations-Modi (`strict_private`, `teams_only`, `open`), entkoppelte Ordner-Mounts und Notizen-Freigaben.
10. **[Single-File Theming & Design-System](theming.md)**
1-Datei = 1-Theme Philosophie, die 6 CSS-Variablen-Blöcke, Semantische CSS-Klassen und Geometrie-Steuerung.
11. **[Technische Schwachstellen & Optimierungspotenziale (Flaws)](flaws.md)**
Kritische Analyse von Concurrency-Grenzen, Dual-Storage-Risiken, Undo/Redo, Volltextsuche und Skalierungs-Engpässen.
---
## 🏛️ Systemübersicht (High-Level Architektur)
```mermaid
graph TB
subgraph Client ["Client Layer (Browser / PWA)"]
UI["React 18 SPA (Vite, TS, Tailwind)"]
WSClient["WebSocket Client (Auto-Reconnect)"]
Cache["IndexedDB / Offline Queue"]
end
subgraph ReverseProxy ["Reverse Proxy / Gateway"]
Traefik["Traefik / Nginx (HTTPS, WSS, Rate Limits)"]
end
subgraph Backend ["Backend Layer (Go 1.23 Engine)"]
ChiRouter["Chi Router (REST API)"]
WSHub["Gorilla WebSocket Hub & Lock Manager"]
AlarmTicker["Alarm Scheduler (Background Ticker)"]
DiskSync["Disk Mirror Engine (/data/workspace)"]
end
subgraph Storage ["Storage Layer"]
SQLite[("SQLite WAL Database (/data/mindsift.db)")]
DiskFiles[("Physische Markdown Files (/data/workspace)")]
end
subgraph External ["Externe Schnittstellen"]
HomeAssistant["Home Assistant / Webhooks"]
Discord["Discord Webhooks"]
SMTPServer["SMTP Server (E-Mail Alarme)"]
end
UI -->|HTTPS REST| Traefik
WSClient -->|WSS Realtime| Traefik
Traefik --> ChiRouter
Traefik --> WSHub
ChiRouter --> SQLite
ChiRouter --> DiskSync
DiskSync --> DiskFiles
AlarmTicker --> Discord
AlarmTicker --> SMTPServer
HomeAssistant -->|POST /api/v1/inbox| Traefik
HomeAssistant -->|POST /api/v1/webhooks/ingest| Traefik
```
---
## 🛠️ Schnellstart für Entwickler
### Voraussetzungen
- **Go:** 1.22 oder 1.23+
- **Node.js:** 20+ (LTS) & npm
- **Container-Runtime:** Docker oder Podman
### Lokale Entwicklung ohne Container
```bash
# 1. Repository klonen
git clone https://git.dsit.me/sandrodach/MindSift.git
cd MindSift
# 2. Frontend Abhängigkeiten installieren & Dev-Server starten
cd frontend
npm install
npm run dev
# 3. Backend starten (in neuem Terminal)
cd ..
go run ./cmd/server
```
### Container Build & Testlauf (wie in der CI/CD)
```bash
# Backend-Tests ausführen
podman run --rm -v $(pwd):/app:z -w /app golang:1.23 go test -v ./internal/...
# Frontend Typecheck & Build testen
podman run --rm -v $(pwd):/app:z -w /app/frontend node:20 sh -c "npx tsc --noEmit && npm run build"
# Komplettes Multi-Stage Image bauen
podman build -t mindsift:dev .
```
+185
View File
@@ -0,0 +1,185 @@
# 🔌 REST-API Referenz
Die MindSift REST-API ermöglicht den vollständigen Zugriff auf Gedanken, Aufgaben, Workspace-Notizen, Listen, Benutzerkonten und Smarthome-Automationen.
---
## 🔐 1. Authentifizierung
MindSift unterstützt zwei Authentifizierungsmethoden:
1. **JSON Web Token (JWT Bearer):**
Wird beim Login oder Setup erzeugt und im HTTP-Header mitgesendet:
`Authorization: Bearer <jwt-token>`
2. **Persönliche API-Schlüssel (Smarthome / Automatisierung):**
Werden unter *Einstellungen ➔ Private Webhooks* erzeugt (Präfix `mindsift_sec_...`):
- Per HTTP-Header: `X-API-Key: mindsift_sec_xxxxxxxxxxxx`
- Per Authorization-Header: `Authorization: Bearer mindsift_sec_xxxxxxxxxxxx`
- Per Query-Parameter: `?token=mindsift_sec_xxxxxxxxxxxx`
---
## 🚦 2. Status- & Fehler-Codes
- `200 OK`: Erfolgreiche Abfrage oder Aktualisierung.
- `201 Created`: Ressource erfolgreich angelegt.
- `400 Bad Request`: Ungültiges JSON oder fehlende Pflichtfelder.
- `401 Unauthorized`: Token ungültig, abgelaufen oder fehlend.
- `403 Forbidden`: Unzureichende Berechtigungen (z.B. nur Server-Admin oder nur Lesezugriff).
- `404 Not Found`: Ressource existiert nicht oder gehört einem anderen Benutzer.
- `409 Conflict`: Ressource gesperrt (z.B. Dokument wird bereits von anderem Benutzer bearbeitet).
Fehlerrückgaben erfolgen im einheitlichen Format:
```json
{
"error": "Fehlermeldung im Klartext"
}
```
---
## 📋 3. Endpunkte nach Modulen
### 3.1 Authentifizierung & System-Status
| Methode | Pfad | Auth | Beschreibung |
| :--- | :--- | :--- | :--- |
| `GET` | `/api/v1/health` | Öffentlich | Liveness-Check (`{"status":"ok"}`) |
| `GET` | `/api/v1/auth/status` | Öffentlich | Prüft ob Ersteinrichtung nötig ist (`needsSetup: true/false`) |
| `POST` | `/api/v1/auth/setup` | Öffentlich | Führt First-Boot Setup durch (legt ersten Admin an) |
| `POST` | `/api/v1/auth/register` | Öffentlich | Registrierung über Einladungscode (`inviteCode`) |
| `POST` | `/api/v1/auth/login` | Öffentlich | Login via E-Mail oder Benutzername + Passwort |
| `GET` | `/api/v1/auth/me` | JWT | Gibt das aktuelle Benutzerprofil zurück |
| `PATCH` | `/api/v1/auth/settings` | JWT | Aktualisiert Profil, Passwort, Theme oder Alarm-Kanäle |
| `POST` | `/api/v1/auth/alarms/test` | JWT | Sendet Test-Alarm an E-Mail, Discord oder Custom Webhook |
#### Beispiel: `POST /api/v1/auth/login`
```json
// Request
{
"login": "sandro",
"password": "geheimesPasswort123"
}
// Response (200 OK)
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "usr_abc123",
"email": "sandro@example.com",
"username": "sandro",
"displayName": "Sandro",
"role": "admin",
"alarmChannels": ["discord", "webhook"]
}
}
```
---
### 3.2 Triage & Instant Capture
| Methode | Pfad | Auth | Beschreibung |
| :--- | :--- | :--- | :--- |
| `POST` | `/api/v1/inbox` | JWT / API-Key | Erfasst einen neuen Quick Dump oder leitet direkt weiter |
| `GET` | `/api/v1/triage/deck` | JWT | Liefert den Stapel aller noch nicht triagierten Gedanken (`status: 'inbox'`) |
| `PATCH` | `/api/v1/triage/{id}` | JWT | Führt Triage-Aktion aus (`keep`, `discard`, `restore`) |
#### Beispiel: `POST /api/v1/inbox`
Ermöglicht flexible Direkt-Zuordnungen:
```json
// Request (Quick Dump in Triage-Deck)
{
"content": "Kellerfeuchtigkeit über 68% gestiegen #smarthome #alarm",
"priority": "urgent"
}
// Request (Direkt als Aufgabe anlegen)
{
"content": "Server Backup prüfen #devops",
"priority": "high",
"target": "task",
"dueDate": "2026-10-30"
}
```
*Hinweis:* Wenn `/api/v1/inbox` über einen API-Key aufgerufen wird und kein `source` definiert ist, wird die Quelle automatisch als `"webhook"` markiert!
---
### 3.3 Aufgaben & Kanban-System
| Methode | Pfad | Auth | Beschreibung |
| :--- | :--- | :--- | :--- |
| `GET` | `/api/v1/tasks` | JWT | Liefert alle Aufgaben des Benutzers (inkl. geteilter Aufgaben) |
| `POST` | `/api/v1/tasks/reorder` | JWT | Speichert Kanban-Status und Reihenfolge (`sort_order`) |
| `GET` | `/api/v1/task-lists` | JWT | Liefert alle Aufgabenlisten (eigene & geteilte) |
| `POST` | `/api/v1/task-lists` | JWT | Erstellt eine neue Aufgabenliste (mit Farbe & Spalten) |
| `PUT` | `/api/v1/task-lists/{id}` | JWT | Ändert Titel, Farbe, Icon oder Kanban-Spalten |
| `DELETE` | `/api/v1/task-lists/{id}` | JWT | Löscht Aufgabenliste |
| `POST` | `/api/v1/task-lists/{id}/shares` | JWT | Teilt Liste mit Team oder Benutzer |
---
### 3.4 Workspace & Dokumente
| Methode | Pfad | Auth | Beschreibung |
| :--- | :--- | :--- | :--- |
| `GET` | `/api/v1/documents` | JWT | Liefert Liste aller Dokumente des Benutzers (Metadaten) |
| `POST` | `/api/v1/documents` | JWT | Erstellt neues Notiz-Dokument (mit Blocks oder Markdown) |
| `GET` | `/api/v1/documents/{id}` | JWT | Lädt komplettes Dokument inklusive aller Blöcke |
| `PUT` | `/api/v1/documents/{id}` | JWT | Aktualisiert Dokumenttitel, Icon oder Ordnerpfad |
| `PATCH` | `/api/v1/documents/{id}/blocks` | JWT | Granulares Patching von Dokument-Blöcken (Database-First) |
| `DELETE` | `/api/v1/documents/{id}` | JWT | Löscht Dokument (Datenbank & physische Datei) |
| `POST` | `/api/v1/documents/{id}/shares` | JWT | Notiz mit anderem Benutzer teilen (`read` / `write`) |
| `GET` | `/api/v1/documents/{id}/lock` | JWT | Fragt Bearbeitungs-Sperrstatus ab |
| `POST` | `/api/v1/documents/{id}/lock` | JWT | Sperrt Dokument für Bearbeitung (30s Lease) |
| `DELETE` | `/api/v1/documents/{id}/lock` | JWT | Gibt Bearbeitungssperre wieder frei |
---
### 3.5 Workspace Ordner & Geteilte Ordner
| Methode | Pfad | Auth | Beschreibung |
| :--- | :--- | :--- | :--- |
| `GET` | `/api/v1/folders` | JWT | Liefert alle Ordnerpfade des Benutzers |
| `POST` | `/api/v1/folders` | JWT | Legt neuen (auch leeren) Ordner an |
| `POST` | `/api/v1/folders/move` | JWT | Benennt Ordner um oder verschiebt ihn hierarchisch |
| `DELETE` | `/api/v1/folders` | JWT | Löscht Ordner und verschiebt Notizen |
| `GET` | `/api/v1/workspace/folders/shared` | JWT | Liefert für den Benutzer freigegebene geteilte Ordner |
| `POST` | `/api/v1/workspace/folders/shared` | JWT | Erstellt neuen geteilten Ordner |
| `PUT` | `/api/v1/workspace/folders/shared/{id}/mount` | JWT | Ändert den lokalen Einhängepfad beim Empfänger |
| `POST` | `/api/v1/workspace/folders/shared/{id}/shares` | JWT | Gibt Ordner für Teams oder Benutzer frei |
---
### 3.6 Geteilte Listen (Listen & Einkauf)
| Methode | Pfad | Auth | Beschreibung |
| :--- | :--- | :--- | :--- |
| `GET` | `/api/v1/lists` | JWT | Liefert alle geteilten Listen |
| `POST` | `/api/v1/lists` | JWT | Erstellt neue geteilte Liste |
| `GET` | `/api/v1/lists/{id}` | JWT | Lädt Liste inklusive aller Einträge |
| `POST` | `/api/v1/lists/{id}/items` | JWT | Fügt Eintrag hinzu |
| `PATCH` | `/api/v1/lists/{id}/items/{itemId}` | JWT | Hakt Eintrag ab (`isCompleted: true/false`) |
| `POST` | `/api/v1/lists/{id}/items/reorder` | JWT | Speichert sortierte Einträge |
| `POST` | `/api/v1/lists/{id}/clear-completed`| JWT | Löscht alle abgehakten Einträge |
| `POST` | `/api/v1/lists/{id}/shares` | JWT | Teilt Liste mit Team oder Benutzer |
---
### 3.7 Smarthome Ingestion & Externe Webhooks
| Methode | Pfad | Auth | Beschreibung |
| :--- | :--- | :--- | :--- |
| `POST` | `/api/v1/webhooks/ingest` | Token / Key | Empfängt externe Webhook-Payloads von Home Assistant / n8n |
#### Beispiel: `POST /api/v1/webhooks/ingest?token=mindsift_sec_...`
Unterstützt flexible Payloads (JSON oder Plain Text):
```json
{
"title": "Waschmaschine Keller fertig",
"priority": "high",
"tags": ["smarthome", "keller"]
}
```
*Live-Aktualisierung:* Sendet automatisch ein `TASK_CREATED` WebSocket-Event an alle Clients des Benutzers, sodass die neue Karte sofort ohne Neuladen im Triage-Deck erscheint!
+113
View File
@@ -0,0 +1,113 @@
# 📡 WebSocket & Echtzeit-Synchronisation
MindSift implementiert eine bidirektionale Echtzeit-Synchronisationsschicht auf Basis von WebSockets ([`internal/ws/hub.go`](../../internal/ws/hub.go)). Sie sorgt dafür, dass Änderungen an Aufgaben, Triage-Karten, Notizen und geteilten Einkaufslisten ohne manuelles Neuladen sofort auf allen geöffneten Geräten und bei allen Kollaborationspartnern sichtbar werden.
---
## 🔌 1. Verbindung & Handshake
### Verbindungs-Endpunkte
```
wss://<domain>/api/v1/ws?token=<jwt-token>
wss://<domain>/api/v1/ws/lists/<listId>?token=<jwt-token>
```
### Authentifizierung & Upgrade
- Die Authentifizierung erfolgt über das JWT-Token im Query-Parameter `?token=...` oder den `Authorization: Bearer`-Header während des HTTP-Upgrade-Requests.
- Der Server extrahiert die `userID` und registriert die Verbindung im internen `Hub`.
- Der `Upgrader` akzeptiert alle Origins (`CheckOrigin: return true`), um volle Kompatibilität mit Reverse-Proxies (Traefik, Nginx, Caddy, Cloudflare) und Docker-Netzwerken zu gewährleisten.
---
## 🏛️ 2. Hub-Architektur & Rooms
Der WebSocket-Hub unterteilt Verbindungen in drei Abstraktionsebenen:
```
┌─────────────────────────────────────────────────────────────────┐
│ WebSocket Hub │
├─────────────────┬─────────────────────────────┬─────────────────┤
│ allClients │ userRooms │ rooms │
│ (Alle aktiven │ (Pro Benutzer-ID) │ (Pro Liste/ │
│ Verbindungen) │ Cross-Device Sync für │ Space) │
│ │ Tasks, Triage & Workspace │ Geteilte Liste │
└────────┬────────┴──────────────┬──────────────┴────────┬────────┘
▼ ▼ ▼
Presence Stats TASK_CREATED ITEM_TOGGLED
DOC_LOCKED TASK_UPDATED ITEM_CREATED
DOC_UNLOCKED DOC_UPDATED LIST_UPDATED
```
1. **`allClients` (Global):** Hält jede aktive Client-Verbindung. Ermöglicht Broadcasts systemweiter Ereignisse (z.B. globale Dokument-Sperrungen).
2. **`userRooms[userID]` (Benutzer-Räume):** Sendet Events an alle Geräte desselben Benutzers (z.B. Desktop und Smartphone). Erzeugt ein Webhook oder Quick Dump auf dem Smartphone einen Eintrag, taucht dieser zeitgleich auf dem Desktop auf.
3. **`rooms[listID]` (Kollaborations-Räume):** Multiplexing für geteilte Listen. Alle Mitglieder einer Liste empfangen Aktionen (wie Abhaken, Löschen, Umsortieren) in Echtzeit.
---
## 📨 3. Nachrichten-Format (Envelope)
Alle WebSocket-Nachrichten werden als JSON mit einheitlicher Hülle übertragen:
```json
{
"event": "ITEM_TOGGLED",
"listId": "lst_12345",
"payload": {
"itemId": "itm_987",
"isCompleted": true,
"completedBy": "usr_sandro",
"completedByName": "Sandro"
},
"timestamp": "2026-10-26T13:30:00Z"
}
```
---
## 🔒 4. Dokument-Sperrsystem (Document Locking)
Um Schreibkonflikte bei Notizen im Workspace zu vermeiden, verwendet MindSift ein **serverseitiges Lease-Locking mit Heartbeat**:
```mermaid
sequenceDiagram
autonumber
actor UserA as Benutzer A
participant WS as WebSocket Hub
actor UserB as Benutzer B
UserA->>WS: LOCK_DOC (docId: "doc_123")
WS-->>UserA: Lock bestätigt (30s TTL)
WS->>UserB: DOC_LOCKED (docId: "doc_123", lockedBy: "Benutzer A")
Note over UserB: Editor gesperrt (Schreibschutz aktiv)
loop Alle 15 Sekunden
UserA->>WS: HEARTBEAT_DOC (docId: "doc_123")
WS-->>UserA: TTL um 30s verlängert
end
alt Reguläres Schließen
UserA->>WS: UNLOCK_DOC (docId: "doc_123")
WS->>UserB: DOC_UNLOCKED (docId: "doc_123")
Note over UserB: Editor wieder freigegeben
else Verbindungsabbruch / Tab geschlossen
Note over WS: Heartbeat bleibt aus (TTL läuft ab)
WS->>UserB: DOC_UNLOCKED (Automatisch nach Ablauf)
end
```
### Locking-Events
- **`LOCK_DOC` (Client ➔ Server):** Fordert eine 30-Sekunden-Sperre für ein Dokument an.
- **`HEARTBEAT_DOC` (Client ➔ Server):** Verlängert die Sperre alle 15 Sekunden, solange der Benutzer das Dokument geöffnet hat.
- **`UNLOCK_DOC` (Client ➔ Server):** Gibt die Sperre beim Verlassen des Dokuments frei.
- **`DOC_LOCKED` (Server ➔ Clients):** Informiert andere Benutzer, dass das Dokument schreibgeschützt ist.
- **`DOC_UNLOCKED` (Server ➔ Clients):** Hebt den Schreibschutz auf.
---
## 🔄 5. Fallback-Strategie & Reconnect
In strikten Firmennetzwerken oder bei Proxy-Problemen (z.B. fehlende `Upgrade`-Header bei HTTP/1.1) kann eine WebSocket-Verbindung fehlschlagen:
1. **Exponentielles Backoff:** Das Frontend ([`frontend/src/lib/api.ts`](../../frontend/src/lib/api.ts)) versucht automatisch die Wiederverbindung nach 1s, 2s, 4s, 8s, bis maximal 30s.
2. **HTTP-Polling Fallback:** Geteilte Listen und Notizen aktivieren im Hintergrund ein periodisches Polling (5s Intervall), falls der WebSocket-Status nicht `CONNECTED` ist.
3. **Status-Indikator:** Ein dezenter grüner bzw. gelber Indikator oben rechts im Header zeigt dem Benutzer jederzeit den aktuellen Live-Verbindungsstatus an.
+142
View File
@@ -0,0 +1,142 @@
# 🏛️ Systemarchitektur & Technologie-Stack
Dieses Dokument beschreibt die interne Architektur von MindSift, die Interaktion zwischen den Systemkomponenten, das Concurrency- und Thread-Safety-Modell sowie die Deployment-Strategie.
---
## 🧩 1. Schichten-Architektur
MindSift ist als monolithische, hochperformante Single-Binary-Lösung konzipiert. Das Go-Backend liefert sowohl die REST- und WebSocket-Schnittstellen aus als auch die vorkompilierte React Single Page Application (SPA) als eingebettetes statisches Dateisystem (`embed.FS`).
```
┌────────────────────────────────────────────────────────┐
│ Frontend (React 18) │
│ Vite • TypeScript • Tailwind CSS • Lucide • Motion │
└───────────────────────────┬────────────────────────────┘
│ HTTPS REST / WSS WebSocket
┌───────────────────────────▼────────────────────────────┐
│ Go Chi Routing Layer │
│ CORS • RealIP • Recoverer • Auth / Role Guard │
├───────────────────────────┬────────────────────────────┤
│ Business Engine │ Gorilla WebSocket │
│ Triage • Tasks • Alarms │ Rooms • Hub • Doc-Locking │
├───────────────────────────┴────────────────────────────┤
│ Storage Abstraction │
│ sqlite.go (modernc.org/sqlite pure Go, WAL-Mode) │
└───────────────────────────┬────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
/data/mindsift.db /data/workspace/
(SQLite Datenbank) (Physische .md Dateien)
```
---
## 🛠️ 2. Technologie-Stack
### Backend (Go 1.23)
- **HTTP Routing:** [`github.com/go-chi/chi/v5`](https://github.com/go-chi/chi) – Minimalistischer, idiomatisch schneller Router mit Middleware-Ketten.
- **WebSocket Hub:** [`github.com/gorilla/websocket`](https://github.com/gorilla/websocket) – Verbindungs-Upgrade, Keep-Alive/Ping-Pong und Room-Multiplexing.
- **Datenbank-Treiber:** [`modernc.org/sqlite`](https://gitlab.com/cznic/sqlite) – CGO-freier, rein in Go übersetzter SQLite-Treiber für einfache plattformübergreifende Kompilierung und Containerisierung ohne GCC-Toolchain.
- **Authentifizierung:** [`github.com/golang-jwt/jwt/v5`](https://github.com/golang-jwt/jwt) (HMAC-SHA256 Token) und [`golang.org/x/crypto/bcrypt`](https://pkg.go.dev/golang.org/x/crypto/bcrypt) für Passwort-Hashing.
### Frontend (React 18 SPA)
- **Build-Tool:** [Vite 5](https://vitejs.dev/) für schnelles Hot-Module-Replacement (HMR) und optimierte Production-Bundles.
- **Sprache:** TypeScript 5 mit strikter Typisierung.
- **Design & Layout:** [Tailwind CSS 3](https://tailwindcss.com/) in Verbindung mit dem MindSift Single-File CSS Theming System.
- **Icons & Animationen:** [Lucide React](https://lucide.dev/) und [Framer Motion](https://www.framer.com/motion/) für Gesten, Karten-Swipes und Übergänge.
---
## 📁 3. Verzeichnisstruktur des Projekts
```
MindSift/
├── cmd/
│ └── server/
│ └── main.go # Einstiegspunkt: Initialisierung, Config, Store, Start
├── internal/
│ ├── api/
│ │ ├── server.go # HTTP-Routen, Handler-Methoden, Middleware
│ │ └── server_test.go # Integrationstests für alle REST- und Ingest-Routen
│ ├── auth/
│ │ ├── auth.go # JWT Validierung, Token-Erstellung, Context-Helpers
│ │ └── apikey.go # API-Key Erzeugung (mindsift_sec_...) & SHA-256 Hashing
│ ├── config/
│ │ └── config.go # Environment-Variablen (PORT, DATA_DIR, JWT_SECRET, SMTP)
│ ├── domain/
│ │ └── models.go # Go-Structs für Thought, Document, List, User, Team etc.
│ ├── storage/
│ │ ├── sqlite.go # DB-Schema, CRUD-Operationen, Queries, Migrationen
│ │ └── storage_test.go # Unit-Tests für Speicheroperationen
│ └── ws/
│ └── hub.go # WebSocket Client, Room-Management, Locking-Mechanismus
├── frontend/
│ ├── src/
│ │ ├── components/ # Wiederverwendbare UI-Elemente (Modals, Avatar, Menüs)
│ │ ├── context/ # MobileActionContext (Mobile Bottom Bar State)
│ │ ├── features/
│ │ │ ├── auth/ # SetupWizard & Login
│ │ │ ├── capture/ # InstantCaptureBar (Schnelleingabe)
│ │ │ ├── help/ # Integrierte Benutzerdokumentation
│ │ │ ├── lists/ # Geteilte Listen & Einkauf
│ │ │ ├── settings/ # Benutzer-, Admin- & Theme-Einstellungen
│ │ │ ├── teams/ # Gruppen & Mitglieder-Management
│ │ │ ├── triage/ # Triage-Deck, Aufgabenliste, Kanban & TaskModal
│ │ │ └── workspace/ # Outliner-Editor, Datei-Explorer, Wiki & Rollup
│ │ ├── lib/ # api.ts (HTTP/WS Client), types.ts, theme.ts
│ │ ├── themes/ # Eigenständige Theme CSS-Dateien (dark, light, etc.)
│ │ ├── App.tsx # Root-Komponente, Navigation, Top/Bottom Bars
│ │ └── main.tsx # React DOM Bootstrap
│ ├── package.json
│ └── vite.config.ts
├── Doku/ # Dieses technische Wiki
├── Dockerfile # Multi-Stage Build Definition
└── compose.yaml # Docker Compose Vorlage für Produktivbetrieb
```
---
## ⚡ 4. Thread-Safety & Concurrency-Modell
Da SQLite Datei-basiert arbeitet und Go standardmäßig hochgradig nebenläufig (Multi-Threaded via Goroutines) operiert, sind folgende Schutzmaßnahmen implementiert:
### 1. SQLite Pragmas & WAL-Modus
Im Storage-Layer ([`internal/storage/sqlite.go`](../internal/storage/sqlite.go)) wird die Datenbankverbindung beim Start zwingend mit folgenden DSN-Pragmas konfiguriert:
```go
dsn := filepath.Join(dataDir, "mindsift.db") +
"?_pragma=busy_timeout(10000)&" +
"_pragma=journal_mode(WAL)&" +
"_pragma=synchronous(NORMAL)&" +
"_pragma=foreign_keys(1)"
```
- **`journal_mode(WAL)`:** Write-Ahead Logging erlaubt parallele Leser (`SELECT`), während ein Schreiber (`INSERT`/`UPDATE`) aktiv ist. Leser blockieren niemals Schreiber und Schreiber blockieren niemals Leser.
- **`busy_timeout(10000)`:** Sollte ein Schreibvorgang kurzzeitig blockiert sein, wartet SQLite bis zu 10.000 ms (10 Sekunden), bevor ein `SQLITE_BUSY` Fehler geworfen wird.
- **`foreign_keys(1)`:** Erzwingt referenzielle Integrität und automatische Kaskadierungs-Löschungen (`ON DELETE CASCADE`).
### 2. Connection Pool Begrenzung
SQLite unterstützt nur einen einzigen Schreib-Thread zur gleichen Zeit:
```go
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(4)
db.SetConnMaxLifetime(time.Hour)
```
### 3. Cursor-Deadlock-Vermeidung
> **Kritische Backend-Regel:** Niemals verschachtelte Abfragen bei offenem `*sql.Rows`-Cursor ausführen!
In Go hält ein offener Cursor eine Verbindung aus dem Pool besetzt. Führt der Code innerhalb von `for rows.Next()` eine weitere Abfrage auf `s.db` aus, kann der Connection-Pool bei mehreren parallelen Benutzern innerhalb von Millisekunden einfrieren.
*Pattern:* Alle Zeilen werden sofort vollständig in ein Go-Slice gelesen, `rows.Close()` wird ausgeführt, und erst danach werden verknüpfte Entitäten geladen.
---
## 🌐 5. Deployment & Container-Architektur
MindSift wird als schlanker, autarker Docker-Container ausgeliefert:
1. **Stage 1 (Frontend Build):** `node:20-alpine` kompiliert TypeScript und generiert statische Assets nach `frontend/dist/`.
2. **Stage 2 (Backend Build):** `golang:1.23-alpine` bettet `frontend/dist/` via `//go:embed` ein, führt Unit-Tests aus und kompiliert ein statisches Binary (`mindsift-server`).
3. **Stage 3 (Production Runtime):** `alpine:3.20` mit `ca-certificates` und `tzdata`. Das finale Image ist ca. 45 MB groß und benötigt keine externen Datenbankserver oder Abhängigkeiten.
Alle persistenten Daten befinden sich in einem einzigen Verzeichnis `/data`:
- `/data/mindsift.db` (SQLite Hauptdatenbank)
- `/data/workspace/` (Physische Markdown-Dateien der Workspace-Notizen)
+348
View File
@@ -0,0 +1,348 @@
# 🗄️ Datenbank-Design & SQLite Schema
MindSift verwendet als persistente Datenspeicherung eine lokale SQLite-Datenbank (`/data/mindsift.db`). Dieses Dokument beschreibt das relationale Datenmodell, Tabellenstrukturen, Indizes, Fremdschlüsselbeziehungen und das Migrationskonzept.
---
## 📊 1. Entity-Relationship-Diagramm (ERD)
```mermaid
erDiagram
users ||--o{ thoughts : "erstellt"
users ||--o{ documents : "besitzt"
users ||--o{ lists : "besitzt"
users ||--o{ task_lists : "besitzt"
users ||--o{ api_keys : "besitzt"
users ||--o{ team_members : "ist Mitglied in"
teams ||--o{ team_members : "besteht aus"
teams ||--o{ task_list_shares : "erhält Zugriff"
teams ||--o{ list_shares : "erhält Zugriff"
teams ||--o{ doc_team_shares : "erhält Zugriff"
teams ||--o{ workspace_folder_shares : "erhält Zugriff"
task_lists ||--o{ thoughts : "gruppiert"
task_lists ||--o{ task_list_shares : "wird geteilt mit"
lists ||--o{ list_items : "enthält"
lists ||--o{ list_shares : "wird geteilt mit"
documents ||--o{ document_blocks : "besteht aus"
documents ||--o{ doc_shares : "geteilt mit Benutzern"
documents ||--o{ doc_team_shares : "geteilt mit Teams"
documents ||--o{ doc_user_folders : "eigene Platzierung"
documents ||--o{ doc_pins : "Favorit von"
workspace_shared_folders ||--o{ workspace_folder_shares : "wird geteilt mit"
workspace_shared_folders ||--o{ workspace_folder_mounts : "eingehängt bei"
workspace_shared_folders ||--o{ documents : "enthält"
```
---
## 📑 2. Detaillierte Tabellen-Spezifikation
### 2.1 Benutzerverwaltung, System & Sicherheit
#### `users`
Verwaltet Konten, Rollen, Multi-Channel Alarmziele und Benutzerpräferenzen.
```sql
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY, -- Präfix "usr_..."
email TEXT UNIQUE NOT NULL, -- E-Mail-Adresse
username TEXT NOT NULL DEFAULT '', -- Eindeutiger Login-Name
password_hash TEXT NOT NULL, -- bcrypt Hash
display_name TEXT NOT NULL, -- Anzeigename
avatar_url TEXT NOT NULL DEFAULT '', -- Profilbild URL oder leer
role TEXT NOT NULL DEFAULT 'member', -- 'admin' | 'member'
is_active INTEGER NOT NULL DEFAULT 1, -- 1 = aktiv, 0 = gesperrt
alarm_channels TEXT NOT NULL DEFAULT 'discord', -- Kommagetrennt: 'email', 'discord', 'webhook'
alarm_email TEXT NOT NULL DEFAULT '', -- Ziel-E-Mail für Alarme
discord_webhook_url TEXT NOT NULL DEFAULT '', -- Discord Webhook URL
custom_webhook_url TEXT NOT NULL DEFAULT '', -- HTTP POST JSON Webhook URL
notify_before_minutes INTEGER NOT NULL DEFAULT 30, -- Vorwarnzeit in Minuten
created_at TEXT NOT NULL, -- ISO 8601 String
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
```
#### `system_settings`
Globale Instanz-Konfiguration (Key-Value-Tabelle).
```sql
CREATE TABLE IF NOT EXISTS system_settings (
key TEXT PRIMARY KEY, -- 'sharing_mode', 'allow_registration', 'instance_name', 'smtp_...'
value TEXT NOT NULL
);
```
#### `user_invites`
Einladungscodes für kontrollierte Benutzer-Registrierungen.
```sql
CREATE TABLE IF NOT EXISTS user_invites (
id TEXT PRIMARY KEY, -- Präfix "inv_..."
code TEXT UNIQUE NOT NULL, -- Format "MS-XXXXXXXX"
email TEXT DEFAULT '', -- Optional: an bestimmte E-Mail gebunden
role TEXT NOT NULL DEFAULT 'member', -- Rolle des neuen Kontos ('member', 'admin')
created_by TEXT NOT NULL, -- Ersteller User-ID
used_by TEXT DEFAULT '', -- Eingelöste User-ID
expires_at TEXT NOT NULL, -- Gültig bis (ISO 8601)
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_invites_code ON user_invites(code);
```
#### `api_keys`
Persönliche API-Schlüssel für externe Skripte, Smarthome und Ingestion.
```sql
CREATE TABLE IF NOT EXISTS api_keys (
id TEXT PRIMARY KEY, -- Präfix "key_..."
user_id TEXT NOT NULL,
name TEXT NOT NULL, -- Bezeichnung (z.B. "Home Assistant")
key_prefix TEXT NOT NULL, -- Erste 12 Zeichen im Klartext für Anzeige
key_hash TEXT UNIQUE NOT NULL, -- SHA-256 Hash des kompletten Schlüssels
scopes TEXT NOT NULL DEFAULT 'ingest,read',
last_used_at TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
```
---
### 2.2 Gruppen & Organisations-Teams
#### `organizations` & `teams` & `team_members`
```sql
CREATE TABLE IF NOT EXISTS organizations (
id TEXT PRIMARY KEY, -- Präfix "org_..."
name TEXT NOT NULL,
owner_id TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(owner_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS teams (
id TEXT PRIMARY KEY, -- Präfix "tm_..."
org_id TEXT NOT NULL DEFAULT 'default',
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS team_members (
team_id TEXT NOT NULL,
user_id TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'member', -- 'admin' | 'member'
joined_at TEXT NOT NULL,
PRIMARY KEY (team_id, user_id),
FOREIGN KEY(team_id) REFERENCES teams(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
```
---
### 2.3 Gedanken, Triage & Aufgaben
In MindSift nutzen Quick Dumps (Triage) und Aufgaben dieselbe Basistabelle **`thoughts`**. Ein Quick Dump ist ein Gedanke mit `status = 'inbox'`, der durch Triage in `status = 'kept'` oder `status = 'converted'` übergeht.
#### `thoughts`
```sql
CREATE TABLE IF NOT EXISTS thoughts (
id TEXT PRIMARY KEY, -- Präfix "th_..."
user_id TEXT NOT NULL,
content TEXT NOT NULL, -- Textinhalt inkl. Hashtags
tags TEXT NOT NULL DEFAULT '[]', -- JSON Array: ["smarthome", "keller"]
priority TEXT NOT NULL DEFAULT 'normal', -- 'low' | 'normal' | 'high' | 'urgent'
source TEXT NOT NULL DEFAULT 'manual', -- 'manual' | 'voice' | 'webhook'
status TEXT NOT NULL DEFAULT 'inbox', -- 'inbox' | 'kept' | 'discarded' | 'converted'
is_completed INTEGER NOT NULL DEFAULT 0, -- 0 = offen, 1 = erledigt
converted_type TEXT DEFAULT '', -- 'task' | 'document' | 'list_item'
converted_ref_id TEXT DEFAULT '', -- Verknüpfte ID (z.B. doc_id oder list_item_id)
due_date TEXT DEFAULT '', -- Fälligkeitsdatum "YYYY-MM-DD"
task_list_id TEXT NOT NULL DEFAULT '', -- Zugeordnete Aufgabenliste ("" = Standard)
kanban_status TEXT NOT NULL DEFAULT 'todo',-- 'todo' | 'in_progress' | 'done' | column.id
notes TEXT NOT NULL DEFAULT '', -- Zusätzliche Notizen / Beschreibung
subtasks TEXT NOT NULL DEFAULT '[]', -- JSON Array von SubTask: [{id, title, isCompleted}]
alarm_sent INTEGER NOT NULL DEFAULT 0, -- 1 = Alarm wurde bereits versendet
sort_order INTEGER NOT NULL DEFAULT 0, -- Manuelle Reorder-Sortierung
created_at TEXT NOT NULL,
triaged_at TEXT DEFAULT '',
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_thoughts_user_status ON thoughts(user_id, status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_thoughts_list_sort ON thoughts(task_list_id, sort_order ASC, created_at DESC);
```
#### `task_lists` & `task_list_shares`
```sql
CREATE TABLE IF NOT EXISTS task_lists (
id TEXT PRIMARY KEY, -- Präfix "tl_..."
user_id TEXT NOT NULL,
title TEXT NOT NULL,
icon TEXT NOT NULL DEFAULT '📋',
color TEXT NOT NULL DEFAULT '#3b82f6',
team_id TEXT NOT NULL DEFAULT '', -- Optional: an Team gebunden
columns TEXT NOT NULL DEFAULT '[]', -- JSON Array von Kanban-Spalten
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS task_list_shares (
id TEXT PRIMARY KEY,
task_list_id TEXT NOT NULL,
target_type TEXT NOT NULL CHECK(target_type IN ('team', 'user')),
target_id TEXT NOT NULL, -- Team-ID oder User-ID
created_at TEXT NOT NULL,
created_by TEXT NOT NULL,
UNIQUE(task_list_id, target_type, target_id),
FOREIGN KEY(task_list_id) REFERENCES task_lists(id) ON DELETE CASCADE
);
```
---
### 2.4 Workspace: Dokumente, Blöcke & Geteilte Ordner
#### `documents` & `document_blocks` (Database-First Outliner)
Dokumente werden als Kopfdatensatz in `documents` gespeichert, während der Inhalt als feingranulare Blöcke in `document_blocks` verwaltet wird. Zusätzlich spiegelt MindSift die Daten synchron als physische `.md`-Datei auf Festplatte.
```sql
CREATE TABLE IF NOT EXISTS documents (
id TEXT PRIMARY KEY, -- Präfix "doc_..."
user_id TEXT NOT NULL, -- Eigentümer
title TEXT NOT NULL, -- Dokumenttitel
icon TEXT NOT NULL DEFAULT '📄', -- Emoji-Icon
content TEXT NOT NULL DEFAULT '', -- Markdown-Volltext (Spiegelung)
blocks_json TEXT NOT NULL DEFAULT '[]', -- Legacy JSON-Cache
tags TEXT NOT NULL DEFAULT '[]', -- JSON Array von Tags
folder_path TEXT NOT NULL DEFAULT '', -- Relativer Ordnerpfad des Eigentümers
shared_folder_id TEXT NOT NULL DEFAULT '', -- ID aus workspace_shared_folders (wenn in geteiltem Ordner)
sub_folder_path TEXT NOT NULL DEFAULT '', -- Relativer Unterordnerpfad innerhalb des geteilten Ordners
is_pinned INTEGER NOT NULL DEFAULT 0, -- Globaler Pin
is_archived INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_documents_user ON documents(user_id, is_archived, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_documents_shared_folder ON documents(shared_folder_id);
CREATE TABLE IF NOT EXISTS document_blocks (
id TEXT PRIMARY KEY, -- Präfix "blk_..."
doc_id TEXT NOT NULL,
sort_order REAL NOT NULL DEFAULT 0, -- Fließkommazahl für konfliktfreies Einfügen
type TEXT NOT NULL DEFAULT 'bullet', -- 'bullet', 'todo', 'h1', 'h2', 'h3', 'code', 'quote', 'callout', 'divider'
content TEXT NOT NULL DEFAULT '', -- Textinhalt des Blocks
level INTEGER NOT NULL DEFAULT 0, -- Hierarchieebene (0 = Root, 1 = Kind...)
checked INTEGER NOT NULL DEFAULT 0, -- Für Checklisten (0 = offen, 1 = abgehakt)
collapsed INTEGER NOT NULL DEFAULT 0, -- Für eingeklappte Unterbäume
lang TEXT NOT NULL DEFAULT '', -- Programmiersprache für Codeblöcke
updated_at TEXT NOT NULL,
updated_by TEXT NOT NULL DEFAULT '',
FOREIGN KEY(doc_id) REFERENCES documents(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_document_blocks_doc_order ON document_blocks(doc_id, sort_order);
```
#### Entkoppelte Ordner-Freigabe (`workspace_shared_folders` & `workspace_folder_mounts`)
Ermöglicht das Teilen kompletter Ordnerstrukturen. Verschiebt ein Empfänger den geteilten Ordner in seiner Hierarchie, wird **nur sein Mount-Pfad** aktualisiert – die Quellstruktur beim Eigentümer und anderen Empfängern bleibt unberührt!
```sql
CREATE TABLE IF NOT EXISTS workspace_shared_folders (
id TEXT PRIMARY KEY, -- Präfix "wsf_..."
owner_id TEXT NOT NULL,
name TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS workspace_folder_shares (
id TEXT PRIMARY KEY,
folder_id TEXT NOT NULL,
target_type TEXT NOT NULL CHECK(target_type IN ('team', 'user')),
target_id TEXT NOT NULL,
permission TEXT NOT NULL CHECK(permission IN ('read', 'write')),
created_by TEXT NOT NULL,
created_at TEXT NOT NULL,
UNIQUE(folder_id, target_type, target_id)
);
CREATE TABLE IF NOT EXISTS workspace_folder_mounts (
user_id TEXT NOT NULL,
folder_id TEXT NOT NULL,
mount_path TEXT NOT NULL, -- Eigener lokaler Pfad des Benutzers (z.B. "Arbeit/Projekte/Team-Docs")
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY(user_id, folder_id)
);
CREATE TABLE IF NOT EXISTS doc_user_folders (
user_id TEXT NOT NULL,
doc_id TEXT NOT NULL,
folder_path TEXT NOT NULL DEFAULT '', -- Individuelle Ordnerplatzierung für geteilte Einzelnotizen
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY(user_id, doc_id)
);
```
---
### 2.5 Geteilte Listen & Listen-Einträge
#### `lists`, `list_items` & `list_shares`
```sql
CREATE TABLE IF NOT EXISTS lists (
id TEXT PRIMARY KEY, -- Präfix "lst_..."
user_id TEXT NOT NULL,
title TEXT NOT NULL,
icon TEXT NOT NULL DEFAULT '🛒',
list_type TEXT NOT NULL DEFAULT 'shopping',-- 'shopping', 'packing', 'inventory', 'todo'
updated_at TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_lists_user ON lists(user_id, updated_at DESC);
CREATE TABLE IF NOT EXISTS list_shares (
id TEXT PRIMARY KEY,
list_id TEXT NOT NULL,
target_type TEXT NOT NULL CHECK(target_type IN ('team', 'user')),
target_id TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'editor' CHECK(role IN ('editor', 'viewer')),
created_by TEXT NOT NULL,
created_at TEXT NOT NULL,
UNIQUE(list_id, target_type, target_id),
FOREIGN KEY(list_id) REFERENCES lists(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS list_items (
id TEXT PRIMARY KEY, -- Präfix "itm_..."
list_id TEXT NOT NULL,
content TEXT NOT NULL, -- Bezeichnung des Eintrags
category TEXT NOT NULL DEFAULT '', -- Regal/Kategorie (z.B. "Kühlregal")
quantity TEXT NOT NULL DEFAULT '', -- Menge (z.B. "2x", "500g")
is_completed INTEGER NOT NULL DEFAULT 0, -- 1 = abgehakt
completed_by TEXT NOT NULL DEFAULT '', -- User-ID des Abhakers
completed_by_name TEXT NOT NULL DEFAULT '',-- Name des Abhakers für Live-Badge
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
completed_at TEXT NOT NULL DEFAULT '',
FOREIGN KEY(list_id) REFERENCES lists(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_list_items_sync ON list_items(list_id, is_completed, sort_order ASC);
```
---
## 🔄 3. Migrationsstrategie
MindSift setzt auf **idempotente automatische In-Process-Migrationen** beim Serverstart ([`internal/storage/sqlite.go`](../internal/storage/sqlite.go)):
1. **Schema-Bootstrap:** Ausführung des Basis-Schemas via `CREATE TABLE IF NOT EXISTS`.
2. **Column & Index Migration Array:** Ein vordefiniertes Array idempotenter DDL-Befehle (z.B. `ALTER TABLE thoughts ADD COLUMN ...`, `CREATE TABLE IF NOT EXISTS doc_user_folders ...`).
3. **Fehlerbehandlung:** Wenn Spalten bereits existieren (z.B. `"duplicate column name"`), wird der Fehler ignoriert und der Start fortgesetzt.
4. **Vorteil:** Keine manuellen Migrations-Skripte nötig; Container-Updates können jederzeit ohne Downtime oder Vorbereitung über bestehende Datenbanken ausgerollt werden.
+136
View File
@@ -0,0 +1,136 @@
# Bekannte Schwachstellen, Risiken & Technische Schulden (Flaws)
Diese Datei dokumentiert eine ehrliche und schonungslose technische Analyse aller bekannten Limitierungen, Architektur-Kompromisse, Skalierungsrisiken und UX-Schwachstellen in **MindSift**. Sie dient als verbindlicher Backlog für zukünftige Refactorings und Architektur-Meilensteine.
---
## 📌 Übersicht & Risikomatrix
| ID | Bereich | Problem / Schwachstelle | Schweregrad | Risiko im Produktivbetrieb |
|---|---|---|---|---|
| **FLAW-01** | Workspace Sync | Kein echtes CRDT/OT – Lock-Heartbeat-Modell mit Last-Write-Wins | **Kritisch** | Zeilenverlust bei gleichzeitigen Bearbeitungen oder Netzschwankungen |
| **FLAW-02** | Storage | Dual-Storage Drift (SQLite `document_blocks` vs. Dateisystem `.md`) | **Hoch** | Externe Änderungen an `.md` werden nicht automatisch in SQLite gespiegelt |
| **FLAW-03** | Netzwerk / WS | Proxy-Inkompatibilität bei WebSocket-Upgrades & Polling-Fallback | **Mittel** | Verzögerte Synchronisierung (1–3s Polling) in Firmennetzwerken |
| **FLAW-04** | Datenbank / Suche | Volltextsuche via `LIKE '%query%'` statt SQLite FTS5 | **Mittel** | Performance-Einbrüche bei wachsenden Notizbeständen (>10.000 Blöcke) |
| **FLAW-05** | Frontend DOM | Fehlende Virtualisierung im Outliner-Baum | **Mittel** | Frame-Drops und Lags bei Dokumenten mit >500 Blöcken auf Mobilgeräten |
| **FLAW-06** | SQLite Concurrency | Single-Writer-Bottleneck bei hohem parallelem Schreibaufkommen | **Mittel** | `SQLITE_BUSY` Fehler bei vielen gleichzeitigen Team-Nutzern |
| **FLAW-07** | Benachrichtigungen | Fehlende Dead-Letter-Queue für Webhook- & E-Mail-Alarme | **Niedrig** | Verlorene Benachrichtigungen bei temporärem Ausfall von Drittsystemen |
| **FLAW-08** | Sicherheit / Auth | Statische JWT-Tokens ohne rotierende Refresh-Tokens & Blacklisting | **Niedrig** | Keine sofortige Sitzungsinvalidierung bei Rechteentzug |
| **FLAW-09** | Mobile UX | Viewport-Verschiebung durch Virtual-Keyboard auf iOS/Android | **Niedrig** | Verdeckte Eingabefelder und springende Bottom-Navigationsleisten |
| **FLAW-10** | Outliner UX | Kein hierarchischer Undo/Redo-Stack für Strukturmutationen | **Mittel** | Versehentliches Löschen/Einrücken von Blöcken nicht per `Ctrl+Z` rückgängig machbar |
---
## 🔍 Detaillierte Analyse & Maßnahmen
### FLAW-01: Kein echtes CRDT/OT – Lock-Heartbeat & Last-Write-Wins
- **Betroffene Komponenten:** [`api/websocket.md`](./api/websocket.md), [`modules/workspace.md`](./modules/workspace.md), `internal/ws/hub.go`, `frontend/src/lib/components/Outliner.svelte`
- **Ursache:** MindSift nutzt zur Vermeidung von Konflikten eine kooperative Bearbeitungssperre (*Document Lease Lock*, 30 Sekunden TTL mit Heartbeat). Wenn zwei Nutzer dasselbe Dokument geöffnet haben, blockiert der aktive Schreiber das Bearbeiten für andere. Verliert ein Nutzer jedoch kurzzeitig die WebSocket-Verbindung oder tippt während des Übergangs zum Polling-Fallback, werden Änderungen per REST via Last-Write-Wins (`PUT /api/v1/documents/:id`) gespeichert.
- **Folgen:** Wie in früheren Versionen beobachtet, können bei parallelem Schreiben Zeilen überschrieben werden oder Strukturhierarchien (Parent-IDs) verloren gehen.
- **Empfohlene Lösung:**
1. Langfristige Migration auf ein echtes blockweises CRDT-Framework (wie [Yjs](https://yjs.dev/) oder [Automerge](https://automerge.org/)).
2. Kurzfristig: Optimistisches Block-Locking (Sperre nur auf Block-Ebene `block_id` statt des gesamten Dokuments) und atomare Versionsprüfung (*Optimistic Concurrency Control* mit `version_seq`).
---
### FLAW-02: Dual-Storage Drift (SQLite vs. Dateisystem `.md`)
- **Betroffene Komponenten:** [`database.md`](./database.md), `internal/storage/sqlite.go`, `internal/storage/markdown_sync.go`
- **Ursache:** MindSift pflegt Notizen primär in SQLite (`documents`, `document_blocks`), exportiert bzw. spiegelt sie jedoch als reine `.md`-Dateien auf das Dateisystem (`data/notes/`), um Exportierbarkeit und lokale Lesbarkeit zu gewährleisten.
- **Problem:** Es existiert kein systemweiter File-Watcher ([fsnotify](https://github.com/fsnotify/fsnotify)). Bearbeitet der Nutzer eine Notizdatei direkt auf dem Host (z. B. via Obsidian, VS Code, Git-Sync oder SSH), bekommt die SQLite-Datenbank dies nicht mit. Beim nächsten Web-Edit überschreibt SQLite die Datei auf der Festplatte.
- **Empfohlene Lösung:**
1. Integrieren eines Background-File-Watchers mit `fsnotify` für das Datenverzeichnis.
2. Implementieren einer Hash-Prüfung (`sha256` der Datei vs. DB-Metadaten) vor dem Speichern. Bei Abweichung: Nutzerkonflikt-Dialog anzeigen.
---
### FLAW-03: Proxy-Inkompatibilität bei WebSocket-Upgrades
- **Betroffene Komponenten:** [`api/websocket.md`](./api/websocket.md), `frontend/src/lib/stores/websocket.js`
- **Ursache:** In restriktiven Firmennetzwerken oder über bestimmte Reverse-Proxies (Corporate SSL-Termination, Firewalls) schlägt der HTTP-Upgrade-Header `Upgrade: websocket` fehl (`NS_ERROR_CONNECTION_REFUSED` oder `400 Bad Request`).
- **Folgen:** Der Client schaltet in den HTTP-Polling-Fallback um (alle 2 Sekunden). Dadurch:
- Werden Sperren (*Document Locks*) und Online-Statusanzeigen verzögert übertragen.
- Entsteht unnötige Serverlast durch kontinuierliche HTTP-Requests.
- **Empfohlene Lösung:**
1. Sicherstellen von standardmäßigen Nginx/Traefik-Snippets in der Dokumentation (`proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";`).
2. Optionale Implementierung von Server-Sent Events (SSE) als unidirektionale Echtzeit-Alternative für Lesevorgänge bei blockiertem WebSocket.
---
### FLAW-04: Volltextsuche via `LIKE '%query%'`
- **Betroffene Komponenten:** [`database.md`](./database.md), `internal/api/search.go`, `internal/api/thoughts.go`
- **Ursache:** Suchabfragen über Gedanken, Notizblöcke und Aufgaben nutzen aktuell einfaches SQL:
```sql
WHERE content LIKE '%' || ? || '%'
```
- **Folgen:** `LIKE '%...'` kann B-Tree-Indizes prinzipbedingt nicht nutzen und erzwingt einen sequentiellen Full Table Scan. Bei Datenbanken mit Zehntausenden Blöcken führt dies zu CPU-Spitzen und trägen Suchreaktionen.
- **Empfohlene Lösung:**
1. Einrichten einer virtuellen SQLite FTS5-Tabelle (`document_blocks_fts`) mit Tokenizer (`unicode61` oder `trigram`).
2. Automatische Synchronisierung über SQLite Trigger (`AFTER INSERT`, `AFTER UPDATE`, `AFTER DELETE`).
---
### FLAW-05: Fehlende Virtualisierung im Outliner-Baum
- **Betroffene Komponenten:** [`modules/workspace.md`](./modules/workspace.md), `frontend/src/lib/components/Outliner.svelte`
- **Ursache:** Jeder Block im Notizbaum wird als eigenes DOM-Element gerendert. Enthält ein Dokument 500 bis 2000 Blöcke (z. B. lange Projektjournale oder Besprechungsprotokolle), befinden sich tausende verschachtelte `div`- und `contenteditable`-Elemente gleichzeitig im DOM.
- **Folgen:** Auf älteren Smartphones oder bei schnellem Tippen kommt es zu spürbaren Input-Latenzen und Ruckeln beim Scrollen.
- **Empfohlene Lösung:**
1. Implementierung einer virtuellen Fensterung (Virtual Windowing) für Blöcke außerhalb des Sichtbereichs.
2. Automatisches Einklappen (*Collapsing*) tiefer Hierarchieebenen beim initialen Laden langer Dokumente.
---
### FLAW-06: SQLite Concurrency & Single-Writer-Bottleneck
- **Betroffene Komponenten:** [`architecture.md`](./architecture.md), [`database.md`](./database.md), `internal/storage/sqlite.go`
- **Ursache:** Obwohl SQLite mit `PRAGMA journal_mode = WAL;` parallele Leser erlaubt, kann zu jedem Zeitpunkt immer nur **ein einziger Thread** schreiben. Bei intensiver gleichzeitiger Nutzung (z. B. 20 Teammitglieder tippen gleichzeitig in Listen und Notizen, während im Hintergrund Webhook-Events einlaufen) können Schreib-Transaktionen aufeinander warten.
- **Folgen:** Risiko von `database is locked (SQLITE_BUSY)`-Fehlern, wenn der Busy-Timeout überschritten wird.
- **Empfohlene Lösung:**
1. Setzen von `PRAGMA busy_timeout = 5000;` (5 Sekunden Wartezeit vor Fehlerwurf).
2. Kapselung aller Schreibzugriffe über einen dedizierten Go-Channel / Single-Worker-Pool oder Migration auf PostgreSQL bei größeren Installationen.
---
### FLAW-07: Fehlende Dead-Letter-Queue für Benachrichtigungen
- **Betroffene Komponenten:** [`modules/tasks.md`](./modules/tasks.md), `internal/scheduler/alarm_ticker.go`
- **Ursache:** Der Alarm-Ticker überprüft minütlich fällige Aufgaben und sendet Webhooks bzw. E-Mails. Schlägt der HTTP-Post an den externen Webhook fehl (z. B. Zielserver vorübergehend offline), wird der Fehler lediglich ins Log geschrieben.
- **Folgen:** Der Alarm wird nicht erneut versucht und die Benachrichtigung geht dauerhaft verloren.
- **Empfohlene Lösung:**
1. Einführung einer Tabelle `notification_outbox` mit Spalten `retry_count`, `next_retry_at`, `status`.
2. Exponentieller Backoff (1m, 5m, 15m, 1h) und Status-Markierung (*failed*, *delivered*).
---
### FLAW-08: Statische JWT-Tokens ohne Blacklisting
- **Betroffene Komponenten:** [`architecture.md`](./architecture.md), `internal/api/auth.go`
- **Ursache:** Nach dem Login wird ein JWT ausgestellt, das clientseitig im `localStorage` gespeichert wird. Der Server prüft lediglich die kryptografische Signatur des Tokens, führt aber keine serverseitige Sitzungsliste.
- **Folgen:** Wird einem Benutzer die Berechtigung entzogen oder ändert er sein Passwort, bleibt ein bereits ausgestellter Token bis zu seinem Ablaufdatum (z. B. 7 Tage) gültig.
- **Empfohlene Lösung:**
1. Einführung einer `revoked_tokens`-Tabelle oder einer `token_version`-Spalte in der `users`-Tabelle.
2. Implementierung von kurzlebigen Access-Tokens (15 Min.) in Verbindung mit rotierenden Refresh-Tokens in `httpOnly`-Cookies.
---
### FLAW-09: Mobile Viewport-Verschiebung durch Virtual-Keyboard
- **Betroffene Komponenten:** `frontend/src/routes/+layout.svelte`, `frontend/src/lib/components/BottomNav.svelte`
- **Ursache:** Öffnet sich auf iOS Safari oder Android Chrome die Bildschirmtastatur, ändert der Browser die Höhe des sichtbaren Viewports dynamisch. Feste CSS-Attribute wie `height: 100vh` oder `fixed bottom-0` führen dazu, dass Eingabefelder unter die Tastatur rutschen oder die Navigationsleiste den Bildschirm verdeckt.
- **Folgen:** In der mobilen Triage- und Aufgabenansicht muss der Nutzer manuell scrollen, um die Speichern-Buttons zu erreichen.
- **Empfohlene Lösung:**
1. Nutzung der modernen `window.visualViewport`-API in Svelte.
2. Dynamisches Setzen einer CSS-Variable `--vvh` (`visualViewport.height`) zur präzisen Anpassung von Modals und Bottom-Sheets.
---
### FLAW-10: Kein hierarchischer Undo/Redo-Stack im Outliner
- **Betroffene Komponenten:** [`modules/workspace.md`](./modules/workspace.md), `frontend/src/lib/components/Outliner.svelte`
- **Ursache:** Standardmäßiges Browser-`Ctrl+Z` funktioniert nur innerhalb eines einzelnen aktiven Textfelds (`contenteditable`). Strukturelle Aktionen – wie das Verschieben von Blöcken via Drag & Drop, Einrücken (`Tab`), Ausrücken (`Shift+Tab`) oder das Löschen ganzer Unterbäume – sind im Standard-Browser-Undo-Stack nicht enthalten.
- **Folgen:** Verschiebt ein Nutzer versehentlich einen Block samt 10 Unterpunkten, kann dies nicht mit `Ctrl+Z` rückgängig gemacht werden.
- **Empfohlene Lösung:**
1. Implementierung eines expliziten In-Memory Command-Stacks im Frontend (Command-Pattern: `execute()` / `undo()`).
2. Aufzeichnung aller Block-Mutationen (Insert, Indent, Move, Delete) mit Revert-Payloads.
---
## 🧭 Verwandte Dokumente
- 🏗️ [Gesamtarchitektur](architecture.md)
- 💾 [Datenbank-Design & Tabellenschemata](database.md)
- 🔌 [WebSocket & Live-Protokoll](api/websocket.md)
- 🧠 [Workspace-Modul (Outliner)](modules/workspace.md)
- 🛡️ [Rechte & Freigabesystem](sharing-and-permissions.md)
+62
View File
@@ -0,0 +1,62 @@
# 🛒 Kernmodul 4: Geteilte Listen & Kollaboration
Das Modul für geteilte Listen ([`SharedListsView.tsx`](../../frontend/src/features/lists/SharedListsView.tsx)) ist auf **Echtzeit-Kollaboration im Alltag** ausgelegt (z.B. gemeinsamer Wocheneinkauf im Supermarkt, Packlisten für Reisen, Inventarlisten oder schnelle Checklisten).
---
## 🛍️ 1. Listen-Typen & Kategorien
Jede Liste besitzt einen Typ, ein Icon und eine Farb-Codierung:
| Listen-Typ | Icon | Einsatzzweck | Typische Kategorien |
| :--- | :--- | :--- | :--- |
| `shopping` | 🛒 | Supermarkt- & Lebensmitteleinkauf | Obst & Gemüse, Kühlregal, Vorrat, Drogerie |
| `packing` | 🎒 | Urlaubs- & Reise-Packlisten | Kleidung, Elektronik, Dokumente, Kosmetik |
| `inventory` | 📦 | Vorratskammer, Keller, Homelab-Hardware | Regal 1, Gefrierschrank, Server-Rack |
| `todo` | 📝 | Einfache Gruppen-Checklisten | Heute, Später, Ideen |
### Automatische Kategorisierung & Mengenangaben
- Einträge können mit einer Mengenangabe (z.B. `3x`, `500g`, `1 Kasten`) versehen werden.
- Die intelligente Eingabe erkennt Einheiten automatisch oder trennt Mengen über Eingabefelder.
- Einträge werden übersichtlich nach Kategorien gruppiert. Jede Kategorie kann zur besseren Übersicht eingeklappt werden.
---
## ⚡ 2. Echtzeit-Abhaken (Multi-User Live Sync)
Wenn mehrere Familienmitglieder oder Teammitglieder gleichzeitig im Supermarkt unterwegs sind, synchronisiert MindSift jeden Schritt in Echtzeit über WebSockets:
```mermaid
sequenceDiagram
autonumber
actor Alice as Alice (im Markt)
participant WS as WebSocket Hub
actor Bob as Bob (zu Hause)
Alice->>WS: PATCH /items/123 (isCompleted: true)
WS->>Bob: Event: ITEM_TOGGLED (itemId: "123", completedBy: "Alice")
Note over Bob: Eintrag wird sofort durchgestrichen<br/>Grünes Badge "von Alice" erscheint
```
- **Transparenz:** Beim Abhaken speichert MindSift `completed_by_name`. Ein dezentes grünes Pillen-Badge zeigt an, wer den Artikel eingepackt hat.
- **Erledigte Einträge:** Abgehakte Artikel wandern nach unten in einen einklappbaren Bereich „Erledigt".
- **Liste leeren:** Ein Klick auf „Erledigte entfernen" löscht alle abgehakten Einträge (`LIST_CLEARED`) für alle Teilnehmer.
---
## 🖐️ 3. Drag & Drop Umsortierung
Innerhalb einer Liste können Einträge frei per Drag & Drop sortiert werden:
1. **Innerhalb einer Kategorie:** Ändert die Reihenfolge (`sort_order`).
2. **Kategorie-Übergreifend:** Zieht der Benutzer einen Eintrag in eine andere Kategorie (z.B. von „Kühlregal" nach „Obst & Gemüse"), aktualisiert das Frontend optimistisch die Kategorie und sendet das Update ans Backend.
---
## 👥 4. Freigaben & Berechtigungen
Listen können über den einheitlichen Freigabe-Dialog ([`UnifiedShareModal.tsx`](../../frontend/src/components/UnifiedShareModal.tsx)) geteilt werden:
- **Freigabe für Teams:** Alle aktuellen und zukünftigen Mitglieder der gewählten Gruppe erhalten automatisch Zugriff.
- **Freigabe für Einzelbenutzer:** Gezielter Zugriff für einzelne Personen.
- **Rollen:**
- `editor`: Darf Einträge hinzufügen, bearbeiten, abhaken und löschen.
- `viewer`: Nur Lesezugriff (Einträge ansehen, aber nicht verändern).
+86
View File
@@ -0,0 +1,86 @@
# 📋 Kernmodul 2: Aufgaben & Kanban-System
Das Aufgaben-Modul ([`TasksView.tsx`](../../frontend/src/features/triage/TasksView.tsx)) bietet eine flexible Aufgabenverwaltung mit frei konfigurierbaren Listen, Kanban-Boards, Subtask-Checklisten und einem automatisierten Multi-Channel Alarm-System.
---
## 🗂️ 1. Datenmodell & Listen-Hierarchie
Aufgaben basieren technisch auf der Tabelle **`thoughts`** in SQLite. Ein Eintrag gilt als Aufgabe, wenn `status = 'kept'` oder `(status = 'converted' AND converted_type = 'task')` zutrifft.
```
Benutzer (users)
└── Aufgabenlisten (task_lists)
├── Eigene Listen (z.B. "Privat", "Homelab")
└── Mit Teams geteilte Listen (task_list_shares)
└── Aufgaben (thoughts)
├── Kanban-Status (todo, in_progress, done)
├── Priorität (low, normal, high, urgent)
├── Fälligkeitsdatum (dueDate)
├── Notizen (notes)
└── Subtasks (JSON Array von Teilaufgaben)
```
### Listen-Sharing & Avatar-Stack
Aufgabenlisten können für Gruppen/Teams freigegeben werden. Ein Avatar-Stack im Listen-Header und in der Tab-Leiste visualisiert alle Mitglieder, die Zugriff auf die jeweilige Liste haben.
---
## 🖥️ 2. Ansichten: Liste vs. Kanban-Board
Der Benutzer kann jederzeit nahtlos zwischen zwei Ansichten umschalten:
### 1. Kompakte Listenansicht
- Optimiert für schnelles Abarbeiten und Tastatursteuerung.
- Zeigt Checkbox, Titel, Prioritäts-Badge, Herkunfts-Pille (`Webhook`, `Diktat`), Fälligkeitsdatum und Subtask-Zähler (`2/5`).
- 1-Klick-Prioritäts-Wechsel: Klick auf ein Badge rotiert durch die Prioritäten.
### 2. Kanban-Board (Drag & Drop)
- Standardmäßig 3 Spalten: **Zu erledigen** (`todo`), **In Arbeit** (`in_progress`) und **Erledigt** (`done`).
- **Freie Spalten-Konfiguration:** Über das Spalten-Menü können Spalten umbenannt, farblich markiert oder neue Spalten hinzugefügt werden.
- **Drag & Drop:** Aufgaben können per HTML5 Drag & Drop zwischen Spalten verschoben werden. Das Backend speichert die relative Position (`sort_order`) und den neuen `kanban_status` automatisch.
---
## 🎯 3. Prioritäten & Subtasks
### 4-Stufen-Prioritätensystem
Aufgaben unterstützen vier einheitliche Prioritätsstufen:
- **Niedrig (`low`):** Dezent grau hinterlegt.
- **Normal (`normal`):** Akzentfarbe.
- **Wichtig (`high`):** Gelb hervorgehoben mit Warndreieck (`⚠️ Wichtig`).
- **Dringend (`urgent`):** Rot pulsierend mit Flammen-Icon (`🔥 Dringend`).
### Subtasks (Teilaufgaben)
Jede Aufgabe kann eine hierarchische Checkliste von Subtasks enthalten:
- Gespeichert als JSON in der Spalte `subtasks`.
- Fortschrittsanzeige im Kartenkopf (z.B. `3/5 erledigt`).
- Einzelnes Abhaken synchronisiert direkt in die Datenbank.
---
## ⏰ 4. Multi-Channel Alarm-Scheduler
MindSift enthält einen serverseitigen Hintergrund-Ticker in Go ([`internal/api/server.go`](../../internal/api/server.go)), der fällige Aufgaben proaktiv an externe Kanäle meldet.
```mermaid
flowchart TD
Ticker["Go Background Ticker (alle 30s)"] --> Query["SELECT thoughts WHERE due_date <= now + Vorwarnzeit AND alarm_sent = 0"]
Query --> CheckChannels{"Welche Kanäle sind beim User aktiv?"}
CheckChannels -->|email| SendSMTP["E-Mail via SMTP versenden"]
CheckChannels -->|discord| SendDiscord["Discord Webhook POST"]
CheckChannels -->|webhook| SendCustom["Custom HTTP POST Webhook"]
SendSMTP --> MarkSent["UPDATE thoughts SET alarm_sent = 1"]
SendDiscord --> MarkSent
SendCustom --> MarkSent
```
### Alarm-Konfiguration
1. **Vorwarnzeit:** Einstellbar in Minuten (z.B. 15, 30, 60 Minuten vor Fälligkeit).
2. **Kanal-Multi-Select:** Benutzer können beliebig viele Kanäle parallel aktivieren:
- **E-Mail:** Versand über den in den Admin-Einstellungen hinterlegten SMTP-Server.
- **Discord:** Benachrichtigung mit formatierter Embed-Karte in einen Discord-Kanal.
- **Eigener Webhook:** Beliebige HTTP POST JSON Endpunkte (z.B. Gotify, Ntfy, Home Assistant).
3. **Schutz vor Doppel-Alarmen:** Sobald ein Alarm abgesetzt wurde, wird `alarm_sent = 1` gesetzt. Wird das Datum der Aufgabe später geändert, setzt der Server `alarm_sent = 0` automatisch zurück.
+88
View File
@@ -0,0 +1,88 @@
# 📥 Kernmodul 1: Triage & Quick-Dump Engine
Das Triage-Modul von MindSift basiert auf dem *Getting Things Done (GTD)*-Prinzip. Es trennt die **Erfassung von Gedanken** strikt von der **Entscheidung, was damit geschehen soll**.
---
## 💡 1. Das 2-Phasen-Konzept
1. **Phase 1: Schnelles Erfassen (Instant Dump)**
Ein Gedanke, ein Anruf, eine Idee oder ein Server-Alarm wird ohne Reibung erfasst. Der Benutzer muss in diesem Moment weder ein Projekt auswählen, noch Fälligkeitsdaten eingeben oder Ordner durchsuchen.
2. **Phase 2: Fokussierte Triage (Inbox Zero)**
Zu einem späteren Zeitpunkt (z.B. morgens oder beim Feierabend) sichtet der Benutzer den Stapel im Triage-Deck Karte für Karte und trifft für jeden Eintrag eine eindeutige Entscheidung.
---
## 🔄 2. Triage State Machine
```mermaid
stateDiagram-v2
[*] --> Capture: Gedanke erfasst (Desktop / Mobile / Webhook)
Capture --> Inbox: Standard (status: 'inbox')
Capture --> Task: Direkt als Aufgabe (target: 'task')
Capture --> Document: Direkt als Notiz (target: 'document')
Capture --> ListItem: Direkt auf Liste (target: 'list_item')
state Inbox {
[*] --> ActiveCard: Oben auf Stapel
ActiveCard --> Skipped: Leertaste / Später (Ans Ende)
Skipped --> ActiveCard
}
ActiveCard --> Discarded: Swipe links / H (Wegwerfen)
ActiveCard --> Task: Swipe rechts / L (Als Aufgabe)
ActiveCard --> TaskDated: T (Mit Fälligkeitsdatum)
ActiveCard --> Document: D (In Notiz umwandeln)
ActiveCard --> ListItem: Auf Liste schieben
Discarded --> Trash: status: 'discarded'
Task --> TasksModule: status: 'kept', converted: 'task'
TaskDated --> TasksModule: status: 'kept', converted: 'task', dueDate
Document --> WorkspaceModule: status: 'converted', converted: 'document'
ListItem --> ListsModule: status: 'converted', converted: 'list_item'
```
---
## 🖐️ 3. Steuerung & Gesten (Mobile vs. Desktop)
### Desktop-Bedienung (Tastatur-First)
- `C` oder `/`: Fokussiert sofort die Eingabeleiste oben im Header.
- `Enter`: Speichert den Gedanken sofort im Deck.
- `←` oder `H`: Aktive Karte wegwerfen (`discard`).
- `→` oder `L`: Aktive Karte sofort zur Aufgabe machen (`task`).
- `T`: Datumsauswahl für Termin-Aufgabe öffnen.
- `D`: Aktive Karte in eine vollwertige Workspace-Notiz umwandeln.
- `Leertaste`: Karte überspringen und ans Ende des Stapels schieben („Später").
### Mobile Bedienung (Touch & Swipe)
- **Mittlerer Plus-Button (`+`):** Öffnet das design-angeglichene Bottom-Sheet mit Spracheingabe, 4 Prioritäts-Buttons (`Niedrig`, `Normal`, `Wichtig`, `Dringend`) und Fälligkeitsdatum.
- **Wischgeste nach links (Swipe Left):** Roter Indikator erscheint, Karte wird verworfen.
- **Wischgeste nach rechts (Swipe Right):** Grüner Indikator erscheint, Karte wird sofort zur Aufgabe.
- **Long-Press (Langes Drücken auf Karte):** Öffnet das Schnell-Aktions-Menü (Auf Liste, Mit Termin, Als Notiz, Später).
---
## 🎙️ 4. Spracheingabe (Speech-to-Text)
In der Desktop-Schnelleingabeleiste ([`InstantCaptureBar.tsx`](../../frontend/src/features/capture/InstantCaptureBar.tsx)) sowie im mobilen Triage-Modal ([`App.tsx`](../../frontend/src/App.tsx)) ist ein nativer Browser-Sprachassistent integriert:
- Verwendet die standardisierte `SpeechRecognition` bzw. `webkitSpeechRecognition` API.
- Erkennt Sprache auf Deutsch (`lang: 'de-DE'`).
- Live-Transkription: Gesprochener Text wird in Echtzeit in das Textfeld eingefügt.
- Haptisches Feedback: Vibrieren beim Starten und Stoppen der Aufnahme.
- Markiert erfasste Gedanken automatisch mit dem Herkunfts-Attribut `source: 'voice'` (Diktat-Badge).
---
## ⚡ 5. Webhook-Ingestion & Smarthome-Pille
MindSift kann externe Alarme (Home Assistant, Zigbee2MQTT, n8n, Proxmox, Monitoring) automatisch ins Triage-Deck einspeisen:
### Herkunftserkennung
1. Wird ein Gedanke über `/api/v1/inbox` mit `X-API-Key` oder über `/api/v1/webhooks/ingest` eingeliefert, setzt das Backend automatisch `source = "webhook"`.
2. Im Frontend visualisiert die Triage-Karte eine dezente Webhook-Pille (`<Webhook className="w-3 h-3 text-accent" /> Webhook`).
3. Diese Kennzeichnung bleibt auch erhalten, wenn die Karte später zu einer Aufgabe umgewandelt wird.
### Live-WebSocket Update
Nach erfolgreichem Ingest sendet der Server sofort ein `TASK_CREATED` Event über den WebSocket-Hub. Das geöffnete Triage-Deck fügt die neue Alarm-Karte sofort oben in den Stapel ein – ohne dass der Benutzer die Seite neu laden muss.
+82
View File
@@ -0,0 +1,82 @@
# 📝 Kernmodul 3: Workspace Outliner-Engine, Wiki-Links & Disk-Sync
Der MindSift Workspace ([`WorkspaceView.tsx`](../../frontend/src/features/workspace/WorkspaceView.tsx)) kombiniert ein **hierarchisches Bullet-Point Notizsystem (inspiriert von Logseq)** mit einem **persönlichen Markdown-Wiki** und einem **globalen To-Do Rollup Dashboard**.
---
## 🌳 1. Die Logseq-Outliner-Engine
Im Gegensatz zu klassischen Fließtext-Editoren basiert der MindSift Workspace auf **Blöcken** (`document_blocks` in SQLite). Jeder Block ist ein gedanklicher Knotenpunkt mit einer flexiblen Einrückungsebene (`level: 0, 1, 2, ...`).
```
• Projekt MindSift v2.0 (level 0)
• Architektur-Entscheidung (level 1)
- [x] Database-First Blocks implementieren (level 2)
- [ ] Performance Benchmark mit 10.000 Blöcken (level 2)
• Notizen aus dem Dev-Meeting (level 1)
> Wichtig: Keine verschachtelten SQLite Queries ausführen! (level 2)
```
### Tastatur-Navigation & Shortcuts
- `Enter`: Erstellt einen neuen Block auf derselben Hierarchieebene.
- `Tab`: Rückt den aktuellen Block ein (wird Kindknoten des vorherigen Blocks).
- `Shift + Tab`: Rückt den Block um eine Ebene aus (verringert die Tiefe).
- `Shift + Enter`: Weicher Zeilenumbruch innerhalb desselben Blocks (z.B. für mehrzeilige Notizen oder Code).
- `Backspace` (auf leerem Block): Rückt den Block aus oder löscht ihn, wenn er auf Level 0 ist.
- `/` (Slash-Commands): Öffnet das Umwandlungsmenü (`/todo`, `/h1`, `/h2`, `/h3`, `/code`, `/callout`, `/quote`, `/divider`).
### Zoom-In Hoisting (Fokus-Modus)
Klickt der Benutzer auf den Bullet-Dot (`•`) eines Blocks, blendet der Editor alle übergeordneten und benachbarten Blöcke aus und zoomt **ausschließlich in diesen Ast** hinein.
- Eine dezente Breadcrumb-Leiste oben zeigt den Pfad an (z.B. `Workspace / Projekte / MindSift / Architektur`).
- Ein Klick auf ein beliebiges Breadcrumb-Element zoomt sofort auf die jeweilige Ebene zurück.
### Drag & Drop von Blöcken
Der Bullet-Dot dient gleichzeitig als Drag-Anfasser. Wird ein Block verschoben, erkennt die Engine automatisch alle untergeordneten Kind-Blöcke (`isDescendantBlock`) und verschiebt den **kompletten Unterbaum** an die neue Zielposition.
---
## 🔗 2. Wiki-Links (`[[Link]]`) & Hashtags
1. **Interne Wiki-Links:**
- Text in doppelten eckigen Klammern (z.B. `[[Reiseplanung 2026]]`) wird automatisch als klickbarer Link gerendert.
- Ein Klick darauf durchsucht die Notizen des Benutzers und öffnet das Ziel-Dokument direkt im Editor.
2. **Hashtags (`#tag`):**
- Hashtags im Fließtext werden automatisch indiziert (`doc_block_tags`-Tabelle).
- In der linken Leiste listet der Tag-Explorer alle verwendeten Schlagworte auf. Klick auf ein Tag filtert alle Dokumente und Blöcke, die dieses Tag enthalten.
---
## 💾 3. Dual-Storage: Database-First & Disk-Mirroring
MindSift löst das Dilemma zwischen schneller relationaler Abfrage und portabler Dateispeicherung durch ein **synchrones Dual-Storage-Modell**:
```
Client (Block-Editor)
│ PATCH /api/v1/documents/:id/blocks
▼
Backend Engine
┌──────────────────────────────────────────────┐
│ 1. Transaktion in SQLite (document_blocks) │
│ -> Sort-Order, Content, Level, Checked │
│ 2. Volltext-Generierung (Markdown-String) │
│ 3. Schreiben auf Disk (/data/workspace/...) │
└──────────────────────────────────────────────┘
```
1. **Präzision & Geschwindigkeit (SQLite):**
- Jeder Block besitzt eine eindeutige ID (`blk_...`) und eine Gleitkomma-Reihenfolge (`sort_order REAL`).
- Schnelle Status-Updates (z.B. Checkbox abhaken) schreiben nur genau eine Zeile in die Datenbank.
2. **Datensouveränität & Portabilität (Disk-Mirror):**
- Zeitgleich spiegelt der Server das Dokument als Standard-`.md`-Datei unter `/data/workspace/<user_id>/<ordner>/<dateiname>.md`.
- Einrückungen werden nach dem 2-Leerzeichen-Standard (` - [ ] `) formatiert.
- Administratoren können das Verzeichnis jederzeit mit externen Werkzeugen (Obsidian, Logseq, Git, rsync) sichern.
---
## 📊 4. Globaler To-Do Rollup Dashboard
Über den Tab **„Globale To-Dos"** bietet der Workspace eine aggregierte Übersicht aller im gesamten Wiki verteilten Aufgaben:
- Scannt in Echtzeit alle unvollendeten `- [ ] ` Checkboxen über alle Ordner und Notizen hinweg.
- Übersichtliche Gruppierung nach **Ordner ➔ Dokument ➔ Aufgabe**.
- **Ordner-Filter:** Klick auf einen Ordner isoliert nur dessen Aufgaben für fokussiertes Arbeiten.
- **2-Wege-Live-Sync:** Wird eine Checkbox im Rollup abgehakt, wird der Block in SQLite sofort auf `checked = 1` aktualisiert und die physische `.md`-Datei synchronisiert.
+93
View File
@@ -0,0 +1,93 @@
# 👥 Synchrones Teilen & Berechtigungskonzept
MindSift bietet ein mehrstufiges Kollaborations- und Freigabesystem. Es gewährleistet strikte Datensouveränität und Privatsphäre, ermöglicht aber gleichzeitig reibungslose Zusammenarbeit in Teams, Familien oder Unternehmen.
---
## 🛡️ 1. Globale Rollen & System-Isolation
### Globale Rollen
- **Server-Admin (`role = 'admin'`):** Vollzugriff auf System-Einstellungen, E-Mail-Konfiguration (SMTP), Benutzerverwaltung, Einladungscodes und System-Statistiken.
- **Mitglied (`role = 'member'`):** Regulärer Benutzer; arbeitet in seinen eigenen Notizen, Aufgaben und freigegebenen Teams.
### System-Isolations-Modi (`sharing_mode`)
Der Server-Admin legt global fest, wie Benutzer miteinander interagieren dürfen:
```
┌────────────────────────────────────────────────────────────────────────┐
│ System-Isolations-Modi │
├──────────────────────┬─────────────────────────┬───────────────────────┤
│ strict_private │ teams_only │ open │
│ (Strikt Isoliert) │ (Teams & Gruppen) │ (Offene Instanz) │
│ │ [EMPFOHLENER STANDARD]│ │
├──────────────────────┼─────────────────────────┼───────────────────────┤
│ Kein Adressbuch │ Adressbuch zeigt nur │ Alle Benutzer sehen │
│ Keine Freigaben │ gemeinsame Teammit- │ alle anderen Konten │
│ Autarke Einzel-Instanz│ glieder │ im System │
└──────────────────────┴─────────────────────────┴───────────────────────┘
```
1. **`strict_private`:** Vollständige Abschottung. Benutzer wissen nicht voneinander. Keine Freigaben möglich.
2. **`teams_only` (Standard):** Kollaboration ist strikt an Gruppen gebunden. Ein Benutzer kann Objekte nur mit Personen teilen, die mindestens in einer gemeinsamen Gruppe/Team mit ihm sind.
3. **`open`:** Geeignet für Familien oder kleine Firmen. Jeder Benutzer kann für jeden anderen Benutzer Objekte freigeben.
---
## 🏢 2. Gruppen & Teams (`teams` & `team_members`)
Gruppen strukturieren Benutzer (z.B. „Familie", „Entwicklung", „Marketing"):
- **Gruppen-Admin:** Darf Mitglieder einladen, Rollen ändern oder die Gruppe löschen.
- **Gruppen-Mitglied:** Hat Zugriff auf alle mit der Gruppe geteilten Ressourcen (Ordner, Aufgabenlisten, Einkaufslisten, Notizen).
Wird eine Ressource für eine Gruppe freigegeben, erhalten **alle aktuellen und zukünftigen Mitglieder** der Gruppe automatisch Zugriff, ohne dass Einzelberechtigungen gepflegt werden müssen.
---
## 📁 3. Entkoppelte Ordner-Freigabe (Decoupled Folder Mounts)
### Das traditionelle Problem
In herkömmlichen Cloud-Systemen (wie Google Drive oder Nextcloud) führt das Verschieben eines geteilten Ordners oft dazu, dass er auch bei allen anderen Teilnehmern den Platz wechselt. Verschiebt ein Kollege den Ordner in sein Unterverzeichnis „Archiv", verschwindet er beim Ersteller aus dem Sichtfeld.
### Die MindSift-Lösung: Unabhängige Einhängepunkte (Mounts)
MindSift entkoppelt die physische Ordner-Identität von der persönlichen Ordner-Hierarchie des Benutzers:
```
Eigentümer (Alice):
Ordner: "Kundenprojekte"
└── Geteilter Ordner: "Projekt Phoenix" (wsf_123)
├── spec.md
└── roadmap.md
Empfänger (Bob):
Ordner: "Meine Aufgaben"
└── "Wichtiges"
└── Einhängepunkt: "Projekt Phoenix" (wsf_123 eingehängt via mount_path)
├── spec.md
└── roadmap.md
```
#### Wie es technisch funktioniert:
1. Der geteilte Ordner existiert als Entität in `workspace_shared_folders`.
2. Beim Eigentümer liegt der Ordner unter seinem Pfad.
3. Jeder Empfänger erhält in `workspace_folder_mounts` seinen **eigenen lokalen `mount_path`**.
4. **Unabhängiges Verschieben:** Schiebt Bob den Ordner an eine andere Stelle in seinem Dateibaum, wird **ausschließlich sein persönlicher `mount_path`** aktualisiert. Alice und andere Teammitglieder bemerken davon nichts.
5. **Inhaltliche Synchronisation:** Verschiebt, erstellt oder bearbeitet jemand eine Notiz *innerhalb* des geteilten Ordners, wird der relative Unterpfad (`sub_folder_path`) synchronisiert, sodass alle dieselbe interne Dokumentstruktur sehen.
---
## 📄 4. Entkoppelte Notizen-Freigabe (`doc_user_folders`)
Dasselbe Prinzip gilt für einzeln geteilte Dokumente:
- Erhält Benutzer Bob eine einzelne Notiz von Alice freigegeben, kann Bob diese Notiz in jeden beliebigen seiner eigenen Ordner einsortieren.
- Seine persönliche Zuordnung wird in der Tabelle `doc_user_folders` (`user_id`, `doc_id`, `folder_path`) gespeichert.
- Die Original-Notiz von Alice verbleibt an ihrem ursprünglichen Platz.
---
## 📋 5. Geteilte Aufgaben- & Einkaufslisten
- **Aufgabenlisten (`task_list_shares`):** Freigabe für Teams oder Einzelbenutzer mit Lese- oder Schreibrechten.
- **Geteilte Listen (`list_shares`):**
- Rolle `editor`: Einträge hinzufügen, ändern, abhaken und löschen.
- Rolle `viewer`: Nur Lesezugriff (Echtzeit-Ansicht).
- **Einheitlicher Freigabe-Dialog:** [`UnifiedShareModal.tsx`](../../frontend/src/components/UnifiedShareModal.tsx) bündelt alle Freigabeoptionen (Teams, Benutzer, Berechtigungsstufen) in einer konsistenten Benutzeroberfläche.
+109
View File
@@ -0,0 +1,109 @@
# 🎨 Single-File CSS Theming Engine & Design-System
MindSift implementiert ein radikal einfaches, modulares Theming-System. Anstatt Farb- und Geometriewerte in Dutzenden Tailwind-Klassen über hunderte Komponenten zu verstreuen, wird das komplette Erscheinungsbild der Plattform über **eine einzige CSS-Datei pro Theme** gesteuert.
---
## 💡 1. Philosophie: 1 Datei = 1 komplettes Theme
Jedes Theme liegt im Verzeichnis [`frontend/src/themes/`](../../frontend/src/themes/) und definiert alle visuellen Eigenschaften über standardisierte CSS-Variablen.
### Trennung von Struktur und Design
- **React-Komponenten** definieren ausschließlich semantische Klassen (wie `.panel-surface`, `.input-field`, `.btn-primary`, `text-main`, `bg-canvas`).
- **Die aktive Theme-CSS-Datei** bestimmt, welche konkreten Farben, Kontraste, Rahmenbreiten und Ecken-Rundungen diesen Klassen zugewiesen werden.
---
## 🧱 2. Die 6 standardisierten Variablen-Blöcke
Jede Theme-Datei gliedert sich in sechs exakt definierte Bereiche:
```css
:root[data-theme="dein-theme"] {
/* ==========================================================================
1. AKZENTFARBE (Fokus, Primäraktionen, Highlights)
========================================================================== */
--accent: #2563eb; /* Primäre Schaltflächen & aktive Tabs */
--accent-hover: #3b82f6; /* Hover-Zustand */
--accent-dim: rgba(37, 99, 235, 0.15);/* Transparenter Hintergrund für Badges */
--accent-contrast: #ffffff; /* Textfarbe auf Akzentflächen */
/* ==========================================================================
2. OBERFLÄCHEN & HINTERGRÜNDE
========================================================================== */
--bg-canvas: #0e1013; /* Grundhintergrund der gesamten Seite */
--bg-panel: #16181d; /* Panels, Karten, Modals & Seitenleisten */
--bg-panel-elevated: #1f2229; /* Hervorgehobene Elemente & Hover-Panels */
--bg-input: #121418; /* Hintergrund von Textfeldern */
--bg-code: #0b0c0f; /* Codeblöcke & Tastatur-Badges */
--bg-overlay: rgba(0, 0, 0, 0.75); /* Modal-Hintergrund Backdrop */
/* ==========================================================================
3. TEXT & KONTRASTE
========================================================================== */
--text-main: #f4f4f5; /* Haupttext, Überschriften, Labels */
--text-muted: #a1a1aa; /* Sekundärtext, Beschreibungen */
--text-faint: #71717a; /* Dezentester Text, Tastaturhinweise */
--text-on-accent: #ffffff; /* Lesbarer Text auf --accent */
/* ==========================================================================
4. RAHMEN, LINIEN & SCHATTEN
========================================================================== */
--border-color: rgba(255, 255, 255, 0.08); /* Standard-Trennlinien */
--border-strong: rgba(255, 255, 255, 0.18);/* Fokussierte Rahmen, Tabs */
--shadow-panel: 0 4px 20px -2px rgba(0, 0, 0, 0.45); /* Box-Shadows */
/* ==========================================================================
5. STATUS- & SIGNALFARBEN
========================================================================== */
--status-ok: #22c55e; /* Erledigt, Online, Aktiv (Grün) */
--status-err: #ef4444; /* Fehler, Löschen, Dringend (Rot) */
--status-warn: #eab308; /* Wichtig, Warnung, Gesperrt (Gelb) */
--status-info: #3b82f6; /* Hinweise, Information (Blau) */
/* ==========================================================================
6. RUNDUNGEN / GEOMETRIE
========================================================================== */
--radius-card: 0px; /* Eckenrundung von Panels & Karten */
--radius-btn: 2px; /* Eckenrundung von Schaltflächen */
--radius-input: 2px; /* Eckenrundung von Eingabefeldern */
--radius-checkbox: 2px; /* Checkboxen */
--radius-pill: 9999px; /* Pillen & Status-Badges */
}
```
---
## 🎭 3. Verfügbare Standard-Themes
| Theme-ID | Datei | Stil & Charakter | Standard-Geometrie |
| :--- | :--- | :--- | :--- |
| `dark` | [`theme-dark.css`](../../frontend/src/themes/theme-dark.css) | Standard Dark Slate / Monochrom | Streng eckig (`0px` Cards, `2px` Buttons) |
| `light` | [`theme-light.css`](../../frontend/src/themes/theme-light.css) | Daylight Clean White / Minimalistisch | Modern dezent (`4px` Cards, `4px` Buttons) |
| `high-contrast` | [`theme-high-contrast.css`](../../frontend/src/themes/theme-high-contrast.css) | Reines Schwarz/Weiß mit AAA-Kontrast | Streng eckig (`0px`, kräftige Rahmen) |
| `soft` | [`theme-soft.css`](../../frontend/src/themes/theme-soft.css) | Warmes Soft Slate mit Indigo-Akzent | Weich gerundet (`12px` Cards, `8px` Buttons) |
| *Template* | [`theme-template.css`](../../frontend/src/themes/theme-template.css) | Vollständig dokumentierte Kopiervorlage | Frei anpassbar |
---
## 🔌 4. Tailwind-Bridge & Semantische Klassen
In [`frontend/src/index.css`](../../frontend/src/index.css) und `tailwind.config.js` sind die semantischen Utility-Klassen definiert, die sich automatisch an das gewählte Theme anpassen:
- `.panel-surface`: Wendet `--bg-panel`, `--border-color` und `--radius-card` an.
- `.input-field`: Wendet `--bg-input`, `--border-color`, `--radius-input` und `--text-main` an.
- `.btn-primary`: Akzent-Schaltfläche mit `--accent`, `--accent-hover`, `--text-on-accent` und `--radius-btn`.
- `.btn-action`: Sekundär-Aktion mit dezentem Rahmen und Hover-Zustand.
- `.checkbox-square`: Kontrollkästchen mit `--radius-checkbox`.
- `.capture-pill`: Spezial-Pille für die Desktop-Schnelleingabeleiste mit `--radius-pill`.
---
## 🛠️ 5. Ein eigenes Theme erstellen
Um ein neues Theme (z.B. „Nordic Blue" oder „Cyberpunk") hinzuzufügen:
1. Kopiere `frontend/src/themes/theme-template.css` nach `frontend/src/themes/theme-meintheme.css`.
2. Ändere den Selektor `:root[data-theme="template"]` in `:root[data-theme="meintheme"]`.
3. Passe Farben und Rundungen nach Belieben an.
4. Importiere die CSS-Datei in [`frontend/src/lib/theme.ts`](../../frontend/src/lib/theme.ts) und trage das Theme im `AVAILABLE_THEMES`-Array ein.
5. Das neue Theme steht sofort in den Kontoeinstellungen und im Profil-Dropdown zur Verfügung!