docs: bring README and Doku up to date with trash, retention, voice choice, undo, combined kanban and editor selection

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 16:56:56 +01:00
1 parent 3de6c6088b
commit a4e08bdff8
9 files changed
+54 -33

No files matched your search

+4 -4
View File
@@ -7,7 +7,7 @@ Basis: `/api/v1`. Antworten sind JSON; Fehler haben die Form `{"error": "Text"}`
| Methode | Pfad | Zweck | | Methode | Pfad | Zweck |
|---|---|---| |---|---|---|
| GET | `/health` | Lebenszeichen | | GET | `/health` | Lebenszeichen |
| GET | `/auth/status` | `needsSetup`, `allowRegistration`, `instanceName`, `demoMode`, `sharingMode`, `legal` (`imprintUrl`, `hasImprintContent`, `privacyUrl`, `hasPrivacyContent`, `customLinkLabel`, `customLinkUrl`) | | GET | `/auth/status` | `needsSetup`, `allowRegistration`, `instanceName`, `demoMode`, `sharingMode`, `legal` (`imprintUrl`, `hasImprintContent`, `privacyUrl`, `hasPrivacyContent`, `customLinkLabel`, `customLinkUrl`), `voice` (`mode` = `server`/`browser`/`off`, `maxSeconds`, `browserAllowed`) |
| GET | `/legal/{imprint\|privacy}` | Hinterlegter Markdown-Text (`content`), ohne Anmeldung; `404`, wenn keiner gesetzt ist | | GET | `/legal/{imprint\|privacy}` | Hinterlegter Markdown-Text (`content`), ohne Anmeldung; `404`, wenn keiner gesetzt ist |
| POST | `/share-target` | PWA Web Share Target Fallback: Weiterleitung auf `/` (`303 See Other`), falls Service Worker noch inaktiv | | POST | `/share-target` | PWA Web Share Target Fallback: Weiterleitung auf `/` (`303 See Other`), falls Service Worker noch inaktiv |
| POST | `/auth/setup` | Erstes Administratorkonto (`email`, `password`, `displayName`, `instanceName`, `allowRegistration`, `sharingMode`, `seedSampleData`); nur solange es keine Konten gibt | | POST | `/auth/setup` | Erstes Administratorkonto (`email`, `password`, `displayName`, `instanceName`, `allowRegistration`, `sharingMode`, `seedSampleData`); nur solange es keine Konten gibt |
@@ -150,7 +150,7 @@ Die Ids `webhook:…` stammen aus der Zeit, als Schlüssel nur einliefern konnte
| Methode | Pfad | Zweck | | Methode | Pfad | Zweck |
|---|---|---| |---|---|---|
| GET/POST | `/task-lists` | Listen (`permission` je Liste: `owner`/`editor`/`viewer`); anlegen (`title`, `icon`, `color`, `teamId`) | | GET/POST | `/task-lists` | Listen (`permission` je Liste: `owner`/`editor`/`viewer`); anlegen (`title`, `icon`, `color`, `teamId`; `icon: ""` = ohne Symbol, fehlt es: 📋) |
| PUT/DELETE | `/task-lists/{id}` | Ändern (`title`, `icon`, `color`, `teamId`, `columns`) – nicht für Leser; Löschen nur Eigentümer | | PUT/DELETE | `/task-lists/{id}` | Ändern (`title`, `icon`, `color`, `teamId`, `columns`) – nicht für Leser; Löschen nur Eigentümer |
| GET | `/task-lists/{id}/shares` | Freigaben | | GET | `/task-lists/{id}/shares` | Freigaben |
| POST | `/task-lists/{id}/shares` | Nur Eigentümer. Body: `targetType` (`team`/`user`), `targetId`, `permission` (`editor`/`viewer`, Standard `editor`). Erneutes Senden für dasselbe Ziel ändert die Stufe | | POST | `/task-lists/{id}/shares` | Nur Eigentümer. Body: `targetType` (`team`/`user`), `targetId`, `permission` (`editor`/`viewer`, Standard `editor`). Erneutes Senden für dasselbe Ziel ändert die Stufe |
@@ -171,7 +171,7 @@ Schreibzugriffe von Lesern auf Listen oder deren Aufgaben liefern `403` („Kein
| DELETE | `/workspace/folders/shared/{id}/shares/{shareId}` | Freigabe eines geteilten Ordners entfernen | | DELETE | `/workspace/folders/shared/{id}/shares/{shareId}` | Freigabe eines geteilten Ordners entfernen |
| GET | `/workspace/tags`, `/workspace/tags/{tag}` | Tag-Übersicht und -Details | | GET | `/workspace/tags`, `/workspace/tags/{tag}` | Tag-Übersicht und -Details |
| PUT | `/workspace/tags/{tag}/note` | Tag-Seite speichern (`content`) | | PUT | `/workspace/tags/{tag}/note` | Tag-Seite speichern (`content`) |
| GET/POST | `/documents` | Notizen auflisten; anlegen (`title`, `icon`, `folderPath`, `blocks`, `tags`, `isPinned`) | | GET/POST | `/documents` | Notizen auflisten; anlegen (`title`, `icon`, `folderPath`, `blocks`, `tags`, `isPinned`; `icon: ""` = ohne Symbol, fehlt es: 🗒️) |
| GET/PUT/DELETE | `/documents/{id}` | Abruf; Ändern von `title`, `icon`, `folderPath`, `isPinned` (Blöcke → `400`, sie laufen über `/docs/{id}/sync`); Löschen = in den Papierkorb (Eigentümer) bzw. Freigabe verlassen (andere) | | GET/PUT/DELETE | `/documents/{id}` | Abruf; Ändern von `title`, `icon`, `folderPath`, `isPinned` (Blöcke → `400`, sie laufen über `/docs/{id}/sync`); Löschen = in den Papierkorb (Eigentümer) bzw. Freigabe verlassen (andere) |
| GET/DELETE | `/documents/trash` | Papierkorb auflisten (`id`, `title`, `icon`, `folderPath`, `deletedAt`, `isOwner`) bzw. leeren | | GET/DELETE | `/documents/trash` | Papierkorb auflisten (`id`, `title`, `icon`, `folderPath`, `deletedAt`, `isOwner`) bzw. leeren |
| GET | `/documents/{id}/backlinks` | Notizen mit `[[Titel]]`-Link auf diese Notiz (nur sichtbare, ohne Papierkorb): `docId`, `title`, `icon`, `folderPath`, bis zu 5 `blocks` (`id`, `content`), `more` | | GET | `/documents/{id}/backlinks` | Notizen mit `[[Titel]]`-Link auf diese Notiz (nur sichtbare, ohne Papierkorb): `docId`, `title`, `icon`, `folderPath`, bis zu 5 `blocks` (`id`, `content`), `more` |
@@ -238,7 +238,7 @@ Wiederholte Operationen (gleiche `opId`) werden nicht erneut angewendet; die Ant
| Methode | Pfad | Zweck | | Methode | Pfad | Zweck |
|---|---|---| |---|---|---|
| GET/POST | `/lists` | Listen; anlegen (`title`, `icon`, `listType`) | | GET/POST | `/lists` | Listen; anlegen (`title`, `icon`, `listType`; `icon: ""` = ohne Symbol, fehlt es: 🛒) |
| GET/DELETE | `/lists/{id}` | Abruf mit Einträgen; Löschen | | GET/DELETE | `/lists/{id}` | Abruf mit Einträgen; Löschen |
| GET/POST | `/lists/{id}/shares` | Freigaben abrufen bzw. anlegen (`targetType`, `targetId`, `role`: `editor`/`viewer`) | | GET/POST | `/lists/{id}/shares` | Freigaben abrufen bzw. anlegen (`targetType`, `targetId`, `role`: `editor`/`viewer`) |
| DELETE | `/lists/{id}/shares/{shareId}` | Freigabe entfernen | | DELETE | `/lists/{id}/shares/{shareId}` | Freigabe entfernen |
+9 -3
View File
@@ -49,7 +49,7 @@ Das Frontend liegt per `go:embed` im Binary ([cmd/server/main.go](../cmd/server/
2. `storage.NewStore()` öffnet SQLite (`busy_timeout`, WAL, max. 8 Verbindungen) und führt `migrate()` aus. 2. `storage.NewStore()` öffnet SQLite (`busy_timeout`, WAL, max. 8 Verbindungen) und führt `migrate()` aus.
3. SMTP-Werte aus der Umgebung werden in `system_settings` übernommen, sofern dort noch nichts steht. 3. SMTP-Werte aus der Umgebung werden in `system_settings` übernommen, sofern dort noch nichts steht.
4. Mit Demo-Flag legt `EnsureDemoAccounts` die Konten `admin/admin` und `user/user` an. 4. Mit Demo-Flag legt `EnsureDemoAccounts` die Konten `admin/admin` und `user/user` an.
5. `api.NewServer` startet den **Alarm-Ticker** (jede Minute), die **Verdichtung des Notiz-Logs** (beim Start, dann alle 6 Stunden) und baut den Router. Server-Timeouts: Lesen 15 s, Schreiben 30 s, Leerlauf 60 s. Beim Beenden wird sauber heruntergefahren. 5. `api.NewServer` startet den **Alarm-Ticker** (jede Minute), die **Verdichtung des Notiz-Logs** (beim Start, dann alle 6 Stunden), die **Aufbewahrungsfristen** (2 Minuten nach dem Start, dann stündlich; nicht in der Demo) und baut den Router. Server-Timeouts: Lesen 15 s, Schreiben 30 s, Leerlauf 60 s. Beim Beenden wird sauber heruntergefahren.
## Konfiguration ## Konfiguration
@@ -78,7 +78,7 @@ Das Frontend liegt per `go:embed` im Binary ([cmd/server/main.go](../cmd/server/
| `MEDIA_MAX_IMAGE_MB`, `MEDIA_MAX_AUDIO_MB` | `15` / `50` | Größtes Bild bzw. größte Audiodatei in Notizen in MB | | `MEDIA_MAX_IMAGE_MB`, `MEDIA_MAX_AUDIO_MB` | `15` / `50` | Größtes Bild bzw. größte Audiodatei in Notizen in MB |
| `MEDIA_QUOTA_MB` | `0` | Speicherplatz für Medien pro Benutzer in MB (`0` = unbegrenzt) | | `MEDIA_QUOTA_MB` | `0` | Speicherplatz für Medien pro Benutzer in MB (`0` = unbegrenzt) |
| `MEDIA_DEMO_MAX_MB` | `3` | Nur im Demo-Modus: maximale Dateigröße für Medien-Uploads in MB | | `MEDIA_DEMO_MAX_MB` | `3` | Nur im Demo-Modus: maximale Dateigröße für Medien-Uploads in MB |
| `RETENTION_ALLOW_NEVER` | leer | `false` verbietet „nie“ bei den Aufbewahrungsfristen (Kontoeinstellungen → Aufbewahrung); wer „nie“ gewählt hatte, bekommt die Voreinstellung des Bereichs. `true` erlaubt es fest. Leer: Schalter in Administration → Systemeinstellungen (Standard: erlaubt) | | `RETENTION_ALLOW_NEVER` | leer | `false` verbietet „nie“ bei den Aufbewahrungsfristen (Kontoeinstellungen → Aufbewahrung); wer „nie“ gewählt hatte, bekommt die Voreinstellung des Bereichs. `true` erlaubt es fest. Leer: Schalter in Administration → Server & Isolation (Standard: erlaubt) |
| `STT_URL` | leer | Eigene Spracherkennung: Basis-URL eines OpenAI-kompatiblen Whisper-Servers (z. B. `http://whisper:8000`, speaches). Gesetzt = Server-Modus, sonst Spracherkennung des Browsers; in der Demo immer Browser | | `STT_URL` | leer | Eigene Spracherkennung: Basis-URL eines OpenAI-kompatiblen Whisper-Servers (z. B. `http://whisper:8000`, speaches). Gesetzt = Server-Modus, sonst Spracherkennung des Browsers; in der Demo immer Browser |
| `STT_MODEL`, `STT_LANGUAGE`, `STT_API_KEY` | `deepdml/faster-whisper-large-v3-turbo-ct2` / `de` / leer | Modell, Sprache (`auto` = erkennen), Schlüssel falls der Whisper-Server einen verlangt | | `STT_MODEL`, `STT_LANGUAGE`, `STT_API_KEY` | `deepdml/faster-whisper-large-v3-turbo-ct2` / `de` / leer | Modell, Sprache (`auto` = erkennen), Schlüssel falls der Whisper-Server einen verlangt |
| `STT_DISABLED` | `false` | `true` blendet die Spracheingabe überall aus | | `STT_DISABLED` | `false` | `true` blendet die Spracheingabe überall aus |
@@ -122,7 +122,7 @@ Punkte mit einer reinen https-Adresse zeigen darüber eine Karte (`LinkPreviewCa
## Spracherkennung ## Spracherkennung
Nur über Umgebungsvariablen (`STT_*`, siehe oben), nicht in der Oberfläche; Administration → Server & Isolation zeigt den aktiven Modus, den Zustand des Whisper-Servers und bietet eine Testaufnahme. Alle Mikrofon-Knöpfe (Quick-Dump-Leiste, mobiles Erfassen-Sheet, Halten des +-Knopfs) nutzen denselben Hook `useVoiceInput` (`frontend/src/lib/voice.ts`, `useVoiceInput.ts`): `server` nimmt auf und zeigt „Wird erkannt…“, `browser` liefert Text schon beim Sprechen, `off` oder ein Browser ohne die nötige Funktion blendet das Mikrofon aus. Im Server-Modus zeigen Wellen den echten Mikrofonpegel (`VoiceWave`), die Aufnahme endet spätestens nach `STT_MAX_SECONDS`. Der Browser nimmt per `MediaRecorder` auf (WebM/Opus, in Safari MP4/AAC) und schickt die Aufnahme an `POST /voice/transcribe`; MindSift reicht sie an `STT_URL/v1/audio/transcriptions` weiter (`model`, `language`) und gibt nur den Text zurück. Nichts wird gespeichert oder protokolliert außer Fehlern (`internal/api/voice.go`). Beim Start wartet MindSift auf den Whisper-Server und stößt den Download des Modells an, falls es fehlt (`POST /v1/models/<id>` bei speaches). Der Admin richtet sie über Umgebungsvariablen ein (`STT_*`, siehe oben); Administration → Server & Isolation zeigt den aktiven Modus, den Zustand des Whisper-Servers und bietet eine Testaufnahme. Mit Whisper-Server wählt jede Person unter Kontoeinstellungen → Spracheingabe zwischen Whisper (Standard) und der Erkennung des Browsers (`users.voice_input`), sofern `STT_ALLOW_BROWSER` das nicht verbietet; `/auth/status` meldet dazu `voice.browserAllowed`, die App wendet die Wahl in `lib/voice.ts` an. Alle Mikrofon-Knöpfe (Quick-Dump-Leiste, mobiles Erfassen-Sheet, Halten des +-Knopfs) nutzen denselben Hook `useVoiceInput` (`frontend/src/lib/voice.ts`, `useVoiceInput.ts`): `server` nimmt auf und zeigt „Wird erkannt…“ (Quick Dump, Erfassen-Sheet und Halte-Knopf schicken die Aufnahme schon beim Sprechen in Stücken, wie das Diktat), `browser` liefert Text schon beim Sprechen und endet nach 5 s Stille, `off` oder ein Browser ohne die nötige Funktion blendet das Mikrofon aus. Im Server-Modus zeigen Wellen den echten Mikrofonpegel (`VoiceWave`), die Aufnahme endet spätestens nach `STT_MAX_SECONDS`. Der Browser nimmt per `MediaRecorder` auf (WebM/Opus, in Safari MP4/AAC) und schickt die Aufnahme an `POST /voice/transcribe`; MindSift reicht sie an `STT_URL/v1/audio/transcriptions` weiter (`model`, `language`) und gibt nur den Text zurück. Nichts wird gespeichert oder protokolliert außer Fehlern (`internal/api/voice.go`). Beim Start wartet MindSift auf den Whisper-Server und stößt den Download des Modells an, falls es fehlt (`POST /v1/models/<id>` bei speaches).
`docker-compose.yml` enthält den Dienst `whisper` (speaches 0.8.3, CPU) als auskommentierten Block: Rauten entfernen, `STT_URL=http://whisper:8000` in `.env` setzen, `docker compose up -d`; das Modell liegt in `./whisper` (Ordner gehört UID 1000). Gemessen auf einem Desktop mit 16 Kernen (int8) für einen Satz von 4 s: `Systran/faster-whisper-small` 1,6 s, `deepdml/faster-whisper-large-v3-turbo-ct2` 8 s bei besserer Erkennung (RAM ca. 1 bzw. 2 GB). Auf geteilten vCPUs ist mit dem Mehrfachen zu rechnen. `docker-compose.yml` enthält den Dienst `whisper` (speaches 0.8.3, CPU) als auskommentierten Block: Rauten entfernen, `STT_URL=http://whisper:8000` in `.env` setzen, `docker compose up -d`; das Modell liegt in `./whisper` (Ordner gehört UID 1000). Gemessen auf einem Desktop mit 16 Kernen (int8) für einen Satz von 4 s: `Systran/faster-whisper-small` 1,6 s, `deepdml/faster-whisper-large-v3-turbo-ct2` 8 s bei besserer Erkennung (RAM ca. 1 bzw. 2 GB). Auf geteilten vCPUs ist mit dem Mehrfachen zu rechnen.
@@ -159,11 +159,17 @@ Die Tests laufen beim Image-Build (Frontend: `npm run test:run`, Backend: `go te
- Danach werden Dateien älter als `backup_retention_days` (Standard 3) gelöscht. Dateien entstehen über `.part` und Umbenennen, halbe Backups tauchen nie in der Liste auf. Die API liefert nur Dateien mit genau diesem Namensmuster aus. - Danach werden Dateien älter als `backup_retention_days` (Standard 3) gelöscht. Dateien entstehen über `.part` und Umbenennen, halbe Backups tauchen nie in der Liste auf. Die API liefert nur Dateien mit genau diesem Namensmuster aus.
- Im Demo-Modus läuft kein Zeitplan. Zurückspielen eines Datenbank-Abzugs: Container stoppen, `mindsift.db` durch die entpackte Datei ersetzen, starten. Benutzer spielen ihre eigenen Backups in den Kontoeinstellungen ein, auch nur für einzelne Bereiche (Notizen, Aufgaben, Listen, Inbox, Themes). - Im Demo-Modus läuft kein Zeitplan. Zurückspielen eines Datenbank-Abzugs: Container stoppen, `mindsift.db` durch die entpackte Datei ersetzen, starten. Benutzer spielen ihre eigenen Backups in den Kontoeinstellungen ein, auch nur für einzelne Bereiche (Notizen, Aufgaben, Listen, Inbox, Themes).
## Hintergrundjob: Aufbewahrung
[retention.go](../internal/storage/retention.go), gestartet in `internal/api/done_cleanup.go`: Jede Person wählt unter Kontoeinstellungen → Aufbewahrung je Bereich 7, 14, 30, 90, 365 oder 730 Tage oder nie. Stündlich wandern erledigte Aufgaben nach Ablauf in den Papierkorb der Aufgaben (`trashed_tasks`); der Papierkorb der Aufgaben, der Triage (verworfene Gedanken), der Notizen und abgehakte Listeneinträge werden nach Ablauf endgültig geleert. In geteilten Aufgabenlisten, Listen und Notizen gilt die Frist des Eigentümers. Was beim Einführen schon erledigt oder gelöscht war, zählt ab diesem Zeitpunkt (`system_settings.retention_since`). Mit `RETENTION_ALLOW_NEVER=false` (oder dem Schalter „Dauerhaftes Aufbewahren erlauben“ in Administration → Server & Isolation) gibt es „nie“ nicht; gespeichertes „nie“ gilt dann als Voreinstellung des Bereichs.
## Offline (PWA) ## Offline (PWA)
- **Service Worker** ([public/sw.js](../frontend/public/sw.js)): Seitenaufrufe Network-First mit der zuletzt geladenen `index.html` als Rückfall, Bundles unter `/assets/` (Hash im Namen) aus dem Cache, Icons und Manifest Stale-While-Revalidate. API-Anfragen fasst er nie an. Alte Caches verschwinden beim Aktivieren einer neuen `VERSION`, Bundles sind auf 80 Einträge begrenzt. - **Service Worker** ([public/sw.js](../frontend/public/sw.js)): Seitenaufrufe Network-First mit der zuletzt geladenen `index.html` als Rückfall, Bundles unter `/assets/` (Hash im Namen) aus dem Cache, Icons und Manifest Stale-While-Revalidate. API-Anfragen fasst er nie an. Alte Caches verschwinden beim Aktivieren einer neuen `VERSION`, Bundles sind auf 80 Einträge begrenzt.
- **Lese-Cache** ([lib/offline.ts](../frontend/src/lib/offline.ts)): Jede erfolgreiche GET-Antwort landet pro Benutzer in IndexedDB (`mindsift-offline`). Ohne Verbindung liefert `request()` die letzte bekannte Antwort; so startet die App offline angemeldet und zeigt Gedanken, Aufgaben und Listen. Ausgenommen: Streams, Long-Polling, Notiz-Sync (eigener Speicher, [modules/collaboration.md](modules/collaboration.md)), Export, Suche. Abmelden leert Cache und Warteschlange. - **Lese-Cache** ([lib/offline.ts](../frontend/src/lib/offline.ts)): Jede erfolgreiche GET-Antwort landet pro Benutzer in IndexedDB (`mindsift-offline`). Ohne Verbindung liefert `request()` die letzte bekannte Antwort; so startet die App offline angemeldet und zeigt Gedanken, Aufgaben und Listen. Ausgenommen: Streams, Long-Polling, Notiz-Sync (eigener Speicher, [modules/collaboration.md](modules/collaboration.md)), Export, Suche. Abmelden leert Cache und Warteschlange.
- **Warteschlange (Outbox):** Offline erfasste Gedanken (`POST /inbox`) und abgehakte Aufgaben bzw. Listeneinträge (`PATCH`) werden in `localStorage` gesammelt, wiederholtes Umschalten zählt nur mit dem Endstand. Gesendet wird in Reihenfolge beim `online`-Ereignis, beim Start und alle 30 s, solange etwas wartet; vom Server abgelehnte Einträge (4xx) entfallen. Andere Änderungen brauchen weiterhin eine Verbindung. - **Warteschlange (Outbox):** Offline erfasste Gedanken (`POST /inbox`) und abgehakte Aufgaben bzw. Listeneinträge (`PATCH`) werden in `localStorage` gesammelt, wiederholtes Umschalten zählt nur mit dem Endstand. Gesendet wird in Reihenfolge beim `online`-Ereignis, beim Start und alle 30 s, solange etwas wartet; vom Server abgelehnte Einträge (4xx) entfallen. Andere Änderungen brauchen weiterhin eine Verbindung.
- **Neue Version:** Jeder Build legt `version.json` (Version und Build-Kennung) neben `index.html`, nie gecacht. Die App vergleicht sie mit ihrer eigenen Kennung beim Zurückkehren in Tab oder App, alle 15 Minuten und nach einem Neustart des Servers (Echtzeit-Verbindung wieder da) und zeigt dann „Neue Version verfügbar“ mit „Jetzt aktualisieren“ ([lib/useUpdateCheck.ts](../frontend/src/lib/useUpdateCheck.ts)).
- **Zustand pro Tab:** Der offene Bereich (Triage, Workspace, Aufgaben …), die offene Notiz und die gewählte Liste stehen in `sessionStorage` ([lib/viewSession.ts](../frontend/src/lib/viewSession.ts)) und bleiben beim Neuladen erhalten.
- **Hinweis:** Ein Banner über der unteren Navigation zeigt „Offline“ und die Zahl wartender Änderungen bzw. „Synchronisiere…“. - **Hinweis:** Ein Banner über der unteren Navigation zeigt „Offline“ und die Zahl wartender Änderungen bzw. „Synchronisiere…“.
- **Verbindungsstatus im Kopf** ([lib/useServerOnline.ts](../frontend/src/lib/useServerOnline.ts)): Maßgeblich ist die app-weite Echtzeit-Verbindung (WebSocket/SSE), nicht `navigator.onLine`; so fällt auch ein ausgefallener Server auf. Offline gilt erst nach 4 s ohne Verbindung (Start, kurze Reconnects), ohne Gerätenetz sofort. Desktop: „● offline“ unter dem Namen, nur ohne Verbindung; Handy: Punkt am Profilbild, grün oder rot. - **Verbindungsstatus im Kopf** ([lib/useServerOnline.ts](../frontend/src/lib/useServerOnline.ts)): Maßgeblich ist die app-weite Echtzeit-Verbindung (WebSocket/SSE), nicht `navigator.onLine`; so fällt auch ein ausgefallener Server auf. Offline gilt erst nach 4 s ohne Verbindung (Start, kurze Reconnects), ohne Gerätenetz sofort. Desktop: „● offline“ unter dem Namen, nur ohne Verbindung; Handy: Punkt am Profilbild, grün oder rot.
+4 -3
View File
@@ -8,7 +8,7 @@ SQLite-Datei `mindsift.db` (Standard: `$DATA_DIR/mindsift.db`), geöffnet mit `j
### Benutzer und Zugang ### Benutzer und Zugang
| Tabelle | Zweck | Wichtige Spalten | | Tabelle | Zweck | Wichtige Spalten |
|---|---|---| |---|---|---|
| `users` | Konten | `email` (eindeutig), `username`, `password_hash`, `display_name`, `role` (`admin`/`member`), `is_active`, `token_version`, `alarm_channels` (kommagetrennt), `alarm_email`, `discord_webhook_url`, `custom_webhook_url`, `notify_before_minutes`, `avatar_url`, `organization_id` (leer = keine Organisation), `notification_settings` (JSON der Kanäle und Ereignisse), `last_daily_summary` (Datum des letzten Versands) | | `users` | Konten | `email` (eindeutig), `username`, `password_hash`, `display_name`, `role` (`admin`/`member`), `is_active`, `token_version`, `alarm_channels` (kommagetrennt), `alarm_email`, `discord_webhook_url`, `custom_webhook_url`, `notify_before_minutes`, `avatar_url`, `organization_id` (leer = keine Organisation), `notification_settings` (JSON der Kanäle und Ereignisse), `last_daily_summary` (Datum des letzten Versands), Aufbewahrungsfristen in Tagen (`done_retention_days` 30, `task_trash_days` 30, `triage_trash_days` 14, `note_trash_days` 30, `list_done_days` 7; 0 = nie), `voice_input` (`''` = Whisper-Server, `browser`) |
| `user_invites` | Einladungscodes | `code`, `email`, `role`, `organization_id`, `expires_at`, `used_by` | | `user_invites` | Einladungscodes | `code`, `email`, `role`, `organization_id`, `expires_at`, `used_by` |
| `media` | Bilder und Audio in Notizen (Migration 5); Datei unter `$DATA_DIR/media/<doc_id>/<id>.<ext>` | `id`, `doc_id`, `owner_id`, `kind` (`image`/`audio`), `mime`, `size`, `source_url` (kopiertes externes Bild), `created_at` | | `media` | Bilder und Audio in Notizen (Migration 5); Datei unter `$DATA_DIR/media/<doc_id>/<id>.<ext>` | `id`, `doc_id`, `owner_id`, `kind` (`image`/`audio`), `mime`, `size`, `source_url` (kopiertes externes Bild), `created_at` |
| `link_previews` | Zwischengespeicherte Link-Vorschauen (Migration 6) | `key` (Hash der URL), `url`, `title`, `description`, `site_name`, `image`/`icon` (BLOB, mit `*_mime`), `ok`, `fetched_at` | | `link_previews` | Zwischengespeicherte Link-Vorschauen (Migration 6) | `key` (Hash der URL), `url`, `title`, `description`, `site_name`, `image`/`icon` (BLOB, mit `*_mime`), `ok`, `fetched_at` |
@@ -26,7 +26,8 @@ SQLite-Datei `mindsift.db` (Standard: `$DATA_DIR/mindsift.db`), geöffnet mit `j
| Tabelle | Zweck | Wichtige Spalten | | Tabelle | Zweck | Wichtige Spalten |
|---|---|---| |---|---|---|
| `password_reset_tokens` | Links aus „Passwort vergessen?“ | `token_hash` (SHA-256), `user_id`, `created_at`, `expires_at` (1 h), `used_at` | | `password_reset_tokens` | Links aus „Passwort vergessen?“ | `token_hash` (SHA-256), `user_id`, `created_at`, `expires_at` (1 h), `used_at` |
| `thoughts` | Gedanken **und** Aufgaben in einer Tabelle | `status` (`inbox`/`kept`/`converted`/`discarded`), `converted_type` (`task`/`document`/`list_item`/`document_block`), `converted_ref_id`, `priority`, `is_completed`, `due_date` (`YYYY-MM-DD` ganztägig oder `YYYY-MM-DDTHH:MM`, Zeitzone des Servers), `task_list_id`, `kanban_status`, `notes`, `subtasks` (JSON), `tags` (JSON), `alarm_sent`, `alarm_failures` (erfolglose Alarm-Durchläufe, ab 3 aufgegeben), `recurrence` (`daily`/`weekly`/`monthly`/`yearly`/leer), `recurrence_spawned`, `sort_order`, `source` (`manual`, `voice`, `webhook` …) | | `thoughts` | Gedanken **und** Aufgaben in einer Tabelle | `status` (`inbox`/`kept`/`converted`/`discarded`), `converted_type` (`task`/`document`/`list_item`/`document_block`), `converted_ref_id`, `priority`, `is_completed`, `due_date` (`YYYY-MM-DD` ganztägig oder `YYYY-MM-DDTHH:MM`, Zeitzone des Servers), `task_list_id`, `kanban_status`, `notes`, `subtasks` (JSON), `tags` (JSON), `alarm_sent`, `alarm_failures` (erfolglose Alarm-Durchläufe, ab 3 aufgegeben), `recurrence` (`daily`/`weekly`/`monthly`/`yearly`/leer), `recurrence_spawned`, `sort_order`, `source` (`manual`, `voice`, `webhook` …), `completed_at` (per Trigger beim Abhaken gesetzt, beim Öffnen geleert), Herkunft (`created_via`, `created_key_id`, `changed_via`, `changed_key_id`, `changed_at`) |
| `trashed_tasks` | Papierkorb der Aufgaben | `id` (der Aufgabe), `user_id`, `task_list_id`, `owner_id` (wessen Frist gilt), `content`, `is_completed`, `data` (die ganze `thoughts`-Zeile als JSON), `assignees` (JSON), `trashed_at`, `trashed_by` |
| `task_assignees` | Zuweisungen von Aufgaben | `task_id` (FK `thoughts`, `ON DELETE CASCADE`), `user_id`, `created_at`; nur Personen mit Zugriff auf die Liste, bereinigt durch `PruneTaskAssignees` | | `task_assignees` | Zuweisungen von Aufgaben | `task_id` (FK `thoughts`, `ON DELETE CASCADE`), `user_id`, `created_at`; nur Personen mit Zugriff auf die Liste, bereinigt durch `PruneTaskAssignees` |
| `task_lists` | Aufgabenlisten | `user_id` (Eigentümer), `title`, `icon`, `color`, `columns` (JSON, Kanban-Spalten) | | `task_lists` | Aufgabenlisten | `user_id` (Eigentümer), `title`, `icon`, `color`, `columns` (JSON, Kanban-Spalten) |
| `task_list_shares` | Freigaben von Aufgabenlisten | `target_type` (`team`/`user`), `target_id`, **`permission`** (`editor`/`viewer`, Standard `editor`) | | `task_list_shares` | Freigaben von Aufgabenlisten | `target_type` (`team`/`user`), `target_id`, **`permission`** (`editor`/`viewer`, Standard `editor`) |
@@ -87,7 +88,7 @@ teams 1─n team_members n─1 users
- `users.username` wird aus dem E-Mail-Präfix aufgefüllt. - `users.username` wird aus dem E-Mail-Präfix aufgefüllt.
- Notiz-Synchronisation (`migrateDocSync`): neue Spalten an `blocks` (inkl. `deleted_at`; vorhandene Grabsteine erhalten den Migrationszeitpunkt) und `documents` (`head_seq`, `title_version`, `log_floor`), Tabellen `doc_ops` und `doc_op_results`. Blöcke im alten flachen Format (nur `level`/`position`) werden einmalig in den Baum umgewandelt. - Notiz-Synchronisation (`migrateDocSync`): neue Spalten an `blocks` (inkl. `deleted_at`; vorhandene Grabsteine erhalten den Migrationszeitpunkt) und `documents` (`head_seq`, `title_version`, `log_floor`), Tabellen `doc_ops` und `doc_op_results`. Blöcke im alten flachen Format (nur `level`/`position`) werden einmalig in den Baum umgewandelt.
- Standardwerte in `system_settings` (`INSERT OR IGNORE`): `sharing_mode=organizations`, `allow_registration=false`, `instance_name=MindSift`, SMTP leer. - Standardwerte in `system_settings` (`INSERT OR IGNORE`): `sharing_mode=organizations`, `allow_registration=false`, `instance_name=MindSift`, SMTP leer.
- **Migrationen:** [migrations.go](../internal/storage/migrations.go) führt nummerierte Schritte (Migrationen 3 bis 9: 3 „Basis-Schema v0.4.1“, 4 „Sitzungen“, 5 „Medien“, 6 „Link-Vorschauen“, 7 „Benachrichtigungen“, 8 „Journal“, 9 „Web-Push“) genau einmal aus und vermerkt sie in `schema_migrations` (`id`, `name`, `applied_at`); `PRAGMA user_version` zeigt die höchste angewandte Nummer. Schritt 3 (`migrateBaseline`) baut das Basisschema auf und bringt ältere Datenbanken auf diesen Stand; Spalten werden nur angelegt, wenn sie fehlen. Schlägt ein Schritt fehl, bricht der Start mit der Fehlermeldung ab, statt mit halbem Schema weiterzulaufen. Neue Änderungen kommen als neuer Schritt ans Ende, alte Schritte werden nie verändert. Daten werden beim Start nie gelöscht; nur der Demo-Modus leert die Datenbank. - **Migrationen:** [migrations.go](../internal/storage/migrations.go) führt nummerierte Schritte (3 „Basis-Schema v0.4.1“, 4 „Sitzungen“, 5 „Medien“, 6 „Link-Vorschauen“, 7 „Benachrichtigungen“, 8 „Journal“, 9 „Web-Push“, 10 „Registrierung und Einladungslinks“, 11 „E-Mail-Bestätigung“, 12 „Markierung für Einträge von KI-Agenten“, 13 „Herkunft von Einträgen (API-Schlüssel)“, 14 „Erledigte Aufgaben aufräumen“, 15 „Spracheingabe pro Person“, 16 „Aufbewahrung und Papierkorb für Aufgaben“) genau einmal aus und vermerkt sie in `schema_migrations` (`id`, `name`, `applied_at`); `PRAGMA user_version` zeigt die höchste angewandte Nummer. Schritt 3 (`migrateBaseline`) baut das Basisschema auf und bringt ältere Datenbanken auf diesen Stand; Spalten werden nur angelegt, wenn sie fehlen. Schlägt ein Schritt fehl, bricht der Start mit der Fehlermeldung ab, statt mit halbem Schema weiterzulaufen. Neue Änderungen kommen als neuer Schritt ans Ende, alte Schritte werden nie verändert. Daten werden beim Start nie gelöscht; nur der Demo-Modus leert die Datenbank.
## Sicherung ## Sicherung
+1 -1
View File
@@ -13,7 +13,7 @@ Einkaufs-, Pack- und Checklisten für gemeinsames Arbeiten. Code: [SharedListsVi
- **Kategorien per Hashtag:** `Hafermilch #Kühlregal` landet in der Kategorie „Kühlregal“. Kategorien werden alphabetisch als Abschnitte angezeigt; die Eingabe schlägt vorhandene Hashtags vor. - **Kategorien per Hashtag:** `Hafermilch #Kühlregal` landet in der Kategorie „Kühlregal“. Kategorien werden alphabetisch als Abschnitte angezeigt; die Eingabe schlägt vorhandene Hashtags vor.
- **Menge:** Feld `quantity` (z. B. `2x`, `500g`). - **Menge:** Feld `quantity` (z. B. `2x`, `500g`).
- **Drag & Drop** ordnet Einträge um; in eine andere Kategorie gezogen, wird der Hashtag im Text angepasst (`items/reorder` mit `category` und `content`). - **Drag & Drop** ordnet Einträge um; in eine andere Kategorie gezogen, wird der Hashtag im Text angepasst (`items/reorder` mit `category` und `content`).
- **Abhaken** speichert, wer und wann; Erledigte stehen in einem eigenen Bereich und lassen sich per „Erledigte entfernen“ löschen (`clear-completed`). - **Abhaken** speichert, wer und wann; Erledigte stehen in einem eigenen Bereich und lassen sich per „Erledigte entfernen“ löschen (`clear-completed`). Nach der Frist des Listen-Eigentümers (Kontoeinstellungen → Aufbewahrung, Standard 7 Tage) löscht der Server abgehakte Einträge von selbst ([retention.go](../../internal/storage/retention.go)).
- **Freigabe** über den Dialog `UnifiedShareModal` (Bearbeiten oder Nur Lesen). Ein Leser sieht Änderungen live, ändert aber nichts. - **Freigabe** über den Dialog `UnifiedShareModal` (Bearbeiten oder Nur Lesen). Ein Leser sieht Änderungen live, ändert aber nichts.
- **Live-Sync:** Ereignisse `ITEM_*` und `LIST_*` im Raum der Liste ([websocket.md](../api/websocket.md)). Abhaken erscheint bei allen Beteiligten sofort. - **Live-Sync:** Ereignisse `ITEM_*` und `LIST_*` im Raum der Liste ([websocket.md](../api/websocket.md)). Abhaken erscheint bei allen Beteiligten sofort.
- **Wer hat's hinzugefügt:** In geteilten Listen zeigt jeder Eintrag ein kleines Profilbild der Person, die ihn angelegt hat (`createdBy`, `createdByName`). Einträge von vor dieser Funktion haben keinen Autor und zeigen nichts. - **Wer hat's hinzugefügt:** In geteilten Listen zeigt jeder Eintrag ein kleines Profilbild der Person, die ihn angelegt hat (`createdBy`, `createdByName`). Einträge von vor dieser Funktion haben keinen Autor und zeigen nichts.
+6 -4
View File
@@ -8,11 +8,13 @@ Eine Aufgabe ist ein `thought` ([database.md](../database.md)) mit `kanbanStatus
## Ansichten ## Ansichten
- **Liste** mit Filtern (offen, erledigt, Papierkorb), Gruppierung (z. B. nach Status oder Fälligkeit), Sortierung und Hashtag-Filter. - **Liste** mit den Reitern Offen, Erledigt und Papierkorb; Gruppierung (z. B. nach Status oder Fälligkeit) und Sortierung stehen am PC rechts daneben, direkt über der Liste. Die Hashtag-Filter darüber bleiben einzeilig; passen nicht alle hinein, klappt „…“ sie auf mehrere Zeilen auf.
- **Kanban:** Spalten pro Liste frei konfigurierbar (Name, Farbe, „Erledigt“-Spalte). Farben in „Spalten anpassen“ über den Punkt vor dem Namen: Grau, Anthrazit (Textfarbe des Themes), Grün, Gelb, Blau, Rot, Lila, Akzentfarbe (`columns[].color` = `gray`/`slate`/`green`/`yellow`/`blue`/`red`/`purple`/`accent`; ältere Werte `emerald`/`amber` gelten als Grün/Gelb, Spalten ohne Farbe behalten die ihrer Rolle). Der Punkt erscheint im Spaltenkopf, vor dem Status in der Listenansicht und im Detail-Fenster ([ManageColumnsModal.tsx](../../frontend/src/features/triage/ManageColumnsModal.tsx), `columnDotClass`). Blau und Lila kommen aus `--color-info` / `--color-purple`; Themes können sie setzen, sonst gelten Standardwerte aus `index.css`. Standard: *Zu erledigen*, *In Bearbeitung*, *Wartet auf andere*, *Erledigt*. Eine Karte in eine „Erledigt“-Spalte zu ziehen setzt `isCompleted`. - **Kanban:** Spalten pro Liste frei konfigurierbar (Name, Farbe, „Erledigt“-Spalte). Farben in „Spalten anpassen“ über den Punkt vor dem Namen: Grau, Anthrazit (Textfarbe des Themes), Grün, Gelb, Blau, Rot, Lila, Akzentfarbe (`columns[].color` = `gray`/`slate`/`green`/`yellow`/`blue`/`red`/`purple`/`accent`; ältere Werte `emerald`/`amber` gelten als Grün/Gelb, Spalten ohne Farbe behalten die ihrer Rolle). Der Punkt erscheint im Spaltenkopf, vor dem Status in der Listenansicht und im Detail-Fenster ([ManageColumnsModal.tsx](../../frontend/src/features/triage/ManageColumnsModal.tsx), `columnDotClass`). Blau und Lila kommen aus `--color-info` / `--color-purple`; Themes können sie setzen, sonst gelten Standardwerte aus `index.css`. Standard: *Zu erledigen*, *In Bearbeitung*, *Wartet auf andere*, *Erledigt*. Eine Karte in eine „Erledigt“-Spalte zu ziehen setzt `isCompleted`; maßgeblich ist die als erledigt markierte Spalte der Liste, egal mit welcher ID (`isDoneColumn` beim Umsortieren). Am PC ist das Board so hoch wie das Fenster, wenn alle Spalten in eine Reihe passen; dann scrollen nur die Spalten.
- **Erledigte aufräumen** ([done_cleanup.go](../../internal/storage/done_cleanup.go)): Erledigte Aufgaben werden standardmäßig 30 Tage nach dem Abhaken gelöscht. Die Frist stellt jede Person im Reiter „Erledigt“ ein (7, 30, 90, 365 Tage oder nie, `users.done_retention_days`); für Aufgaben in einer Liste gilt die Frist des Eigentümers, für persönliche die eigene. Den Zeitpunkt setzt ein Trigger (`thoughts.completed_at`), egal auf welchem Weg abgehakt wird; der Server räumt stündlich auf (nicht in der Demo). „Erledigte leeren“ (`POST /tasks/clear-done`) löscht sofort, aber nur, was man bearbeiten darf. Ein mit einer Notiz verknüpftes Häkchen bleibt dabei als normales Häkchen stehen. - **„Alle Aufgaben“ als Kanban:** Die Spalten aller Listen werden nach Namen zusammengeführt (Groß/Klein egal), in Reihenfolge und Farbe der ersten Liste, die eine Spalte hat; verschieden benannte Spalten bleiben getrennt. Beim Ziehen sind Spalten, die die Liste der Karte nicht hat, ausgegraut und nehmen nichts an. Status-Auswahl, Pfeile auf der Karte und Detail-Fenster nutzen immer die Spalten der eigenen Liste.
- **Papierkorb und Aufbewahrung** ([task_trash.go](../../internal/storage/task_trash.go), [retention.go](../../internal/storage/retention.go)): Gelöschte Aufgaben, „Erledigte in den Papierkorb“ (`POST /tasks/clear-done`) und erledigte Aufgaben nach Ablauf ihrer Frist wandern in den Papierkorb der Aufgaben (Tabelle `trashed_tasks`: die ganze Zeile samt Zuweisungen als Kopie). Wer die Liste sieht, sieht ihren Papierkorb; Zurückholen und endgültig Löschen brauchen Bearbeitungsrecht. Zurückgeholt wird die Aufgabe unverändert (eine gelöschte Liste macht sie zur eigenen Aufgabe ihres Erstellers). Die Fristen stellt jede Person unter Kontoeinstellungen → Aufbewahrung ein (erledigte Aufgaben Standard 30 Tage, Papierkorb 30 Tage); in einer Liste gilt die Frist des Eigentümers. Den Zeitpunkt des Abhakens setzt ein Trigger (`thoughts.completed_at`), egal auf welchem Weg abgehakt wird; der Server räumt stündlich auf (nicht in der Demo). Ein mit einer Notiz verknüpftes Häkchen bleibt als normales Häkchen stehen.
- **Rückgängig:** `Strg+Z`/`Cmd+Z` nimmt in der Aufgabenansicht die letzten Schritte zurück (bis 20): Löschen (aus dem Papierkorb zurück), Verschieben (Spalten, Reihenfolge) und Abhaken. Nach dem Löschen bietet ein Hinweis unten „Rückgängig“, auch am Handy. In Textfeldern gilt deren eigenes Rückgängig.
- Drag & Drop sortiert in der Liste und zwischen Spalten; ein Klick öffnet das Detail-Fenster (Titel, Status, Priorität, Fälligkeit mit optionaler Uhrzeit und Schnellwahl, Liste, Hashtags, Unteraufgaben mit Fortschrittsbalken, Notizen). - Drag & Drop sortiert in der Liste und zwischen Spalten; ein Klick öffnet das Detail-Fenster (Titel, Status, Priorität, Fälligkeit mit optionaler Uhrzeit und Schnellwahl, Liste, Hashtags, Unteraufgaben mit Fortschrittsbalken, Notizen).
- „Als Notiz vertiefen“ legt aus einer Aufgabe eine Notiz an (Titel, Notizen, Unteraufgaben, Fälligkeit, Priorität als Inhalt). Die Aufgabe bleibt in ihrer Liste und wird mit dem ersten Häkchen der Notiz verknüpft (`converted_type = document_block`); ein Gedanke aus der Triage wird dagegen ganz zur Notiz. Aufgaben aus Notiz-Checkboxen (`enhanced-task`) bleiben verknüpft, „In Notiz öffnen“ springt zum Block. - „Als Notiz vertiefen“ legt aus einer Aufgabe eine Notiz an (Titel, darunter Fälligkeit und Priorität, dann Notizen und Unteraufgaben). Die Aufgabe bleibt in ihrer Liste und wird mit dem ersten Häkchen der Notiz verknüpft (`converted_type = document_block`); ein Gedanke aus der Triage wird dagegen ganz zur Notiz. Aufgaben aus Notiz-Checkboxen (`enhanced-task`) bleiben verknüpft, „In Notiz öffnen“ springt zum Block.
- **Abgleich verknüpfter Aufgaben** ([task_doc_sync.go](../../internal/storage/task_doc_sync.go)): Haken und Titel gehen in beide Richtungen (Notiz → Aufgabe in `ReconcileDocTaskLinks` nach jeder Notiz-Änderung, Aufgabe → Notiz in `SyncTaskToDoc` nach jeder Aufgaben-Änderung). Unteraufgaben gehören zur Verknüpfung: Jede ist 1:1 mit einem Häkchen derselben Notiz verbunden (`subtasks[].blockId`, Badge „Unteraufgabe“, `DocumentBlock.subtaskOf`), auch wenn es verschoben oder anders eingerückt wird. Neue Unteraufgaben bekommen ein Häkchen direkt unter dem Haupt-Häkchen; neue Häkchen direkt eine Ebene darunter werden Unteraufgaben (tiefer eingerückte bleiben Notiz). Löschen wird nicht abgeglichen: Ein gelöschtes Häkchen löst nur die Verknüpfung, eine gelöschte Unteraufgabe lässt ihr Häkchen stehen. Ein Häkchen, das in eine andere Notiz wandert, bekommt dort eine neue ID und verliert damit die Verknüpfung. Die Sync-Antwort der Notiz liefert `taskLinks` und `subtaskLinks`, damit die Badges sofort stimmen. - **Abgleich verknüpfter Aufgaben** ([task_doc_sync.go](../../internal/storage/task_doc_sync.go)): Haken und Titel gehen in beide Richtungen (Notiz → Aufgabe in `ReconcileDocTaskLinks` nach jeder Notiz-Änderung, Aufgabe → Notiz in `SyncTaskToDoc` nach jeder Aufgaben-Änderung). Unteraufgaben gehören zur Verknüpfung: Jede ist 1:1 mit einem Häkchen derselben Notiz verbunden (`subtasks[].blockId`, Badge „Unteraufgabe“, `DocumentBlock.subtaskOf`), auch wenn es verschoben oder anders eingerückt wird. Neue Unteraufgaben bekommen ein Häkchen direkt unter dem Haupt-Häkchen; neue Häkchen direkt eine Ebene darunter werden Unteraufgaben (tiefer eingerückte bleiben Notiz). Löschen wird nicht abgeglichen: Ein gelöschtes Häkchen löst nur die Verknüpfung, eine gelöschte Unteraufgabe lässt ihr Häkchen stehen. Ein Häkchen, das in eine andere Notiz wandert, bekommt dort eine neue ID und verliert damit die Verknüpfung. Die Sync-Antwort der Notiz liefert `taskLinks` und `subtaskLinks`, damit die Badges sofort stimmen.
## Aufgabenlisten und Freigabe ## Aufgabenlisten und Freigabe
+4 -2
View File
@@ -5,8 +5,8 @@ Ziel: Gedanken in Sekunden festhalten und später in Ruhe sortieren. Code: [Inst
## Erfassung ## Erfassung
- Die Eingabeleiste sitzt im Header; `C` oder `/` fokussiert sie (außer beim Tippen in einem Feld). - Die Eingabeleiste sitzt im Header; `C` oder `/` fokussiert sie (außer beim Tippen in einem Feld).
- Ziel (`target`): **Posteingang** (`inbox`, Standard), **Direkt-Aufgabe** (`task`), **Listeneintrag** (`list_item`, mit Zielliste) oder **Direkt-Notiz** (`document`). - Ziel (`target`): **Posteingang** (`inbox`, Standard), **Direkt-Aufgabe** (`task`, mit Aufgabenliste), **Listeneintrag** (`list_item`, mit Zielliste) oder **Direkt-Notiz** (`document`). Im aufgeklappten Bereich wählen Ziel und Liste nur aus; gespeichert wird mit Enter bzw. dem Knopf. Ziel und Liste bleiben nach dem Abschicken eingestellt (je Browser-Tab).
- Mehrzeilige Eingabe wird unterstützt. Spracheingabe nutzt `useVoiceInput` (Whisper-Server oder Spracherkennung des Browsers); Quelle `voice` fügt den Tag `#diktat` an. - Mehrzeilige Eingabe wird unterstützt. Spracheingabe nutzt `useVoiceInput` (Whisper-Server oder Spracherkennung des Browsers, je nach Kontoeinstellung): Mit Whisper geht die Aufnahme schon beim Sprechen in Stücken zur Erkennung (`chunked`), die Browser-Erkennung endet nach 5 s Stille. Quelle `voice` fügt den Tag `#diktat` an.
- Quelle `webhook` (per API-Key/Webhook) fügt den Tag `#webhook` an. Hashtags im Text werden als Tags extrahiert (`domain.ExtractTags`). - Quelle `webhook` (per API-Key/Webhook) fügt den Tag `#webhook` an. Hashtags im Text werden als Tags extrahiert (`domain.ExtractTags`).
- Per API: `POST /inbox` und `POST /webhooks/ingest` (siehe [rest.md](../api/rest.md)). - Per API: `POST /inbox` und `POST /webhooks/ingest` (siehe [rest.md](../api/rest.md)).
@@ -33,4 +33,6 @@ inbox ──keep──▶ kept ──convert──▶ converted (task | document
└─discard─▶ discarded ──restore──▶ inbox (Papierkorb leeren: DELETE /thoughts/discarded/purge) └─discard─▶ discarded ──restore──▶ inbox (Papierkorb leeren: DELETE /thoughts/discarded/purge)
``` ```
Verworfene Gedanken liegen im eigenen **Papierkorb der Triage** ([TriageTrash.tsx](../../frontend/src/features/triage/TriageTrash.tsx)): Knopf oben in der Triage, am Handy im Menü. Dort lassen sie sich zurückholen, einzeln oder alle endgültig löschen. Nach der Frist aus Kontoeinstellungen → Aufbewahrung (Standard 14 Tage, gezählt ab dem Verwerfen, `triaged_at`) löscht der Server sie.
Ein Gedanke mit `status = kept` zählt bereits als Aufgabe und erscheint in der Aufgabenansicht. Ein Gedanke mit `status = kept` zählt bereits als Aufgabe und erscheint in der Aufgabenansicht.
+5 -4
View File
@@ -36,12 +36,13 @@ Code: [WorkspaceView.tsx](../../frontend/src/features/workspace/WorkspaceView.ts
- **Konfliktkopien** sind farbig markiert („Konflikt – Version von …“) mit „Übernehmen“ (Text ersetzt den ursprünglichen Punkt bzw. den Titel, die Kopie verschwindet) und „Verwerfen“. - **Konfliktkopien** sind farbig markiert („Konflikt – Version von …“) mit „Übernehmen“ (Text ersetzt den ursprünglichen Punkt bzw. den Titel, die Kopie verschwindet) und „Verwerfen“.
- **Rückgängig:** `Strg+Z`/`Cmd+Z` nimmt die eigene letzte Änderung zurück, `Strg+Umschalt+Z` oder `Strg+Y` wiederholt (auch im Titel und über das Kontextmenü/mobile Tastaturen). Tippen im selben Punkt bis 1 s Pause ist ein Schritt; bis zu 200 Schritte je Tab. Hat jemand anderes den Punkt inzwischen geändert oder hält ihn gesperrt, wird der Schritt mit Hinweis übersprungen. - **Rückgängig:** `Strg+Z`/`Cmd+Z` nimmt die eigene letzte Änderung zurück, `Strg+Umschalt+Z` oder `Strg+Y` wiederholt (auch im Titel und über das Kontextmenü/mobile Tastaturen). Tippen im selben Punkt bis 1 s Pause ist ein Schritt; bis zu 200 Schritte je Tab. Hat jemand anderes den Punkt inzwischen geändert oder hält ihn gesperrt, wird der Schritt mit Hinweis übersprungen.
- `PUT /documents/{id}` ändert nur noch Symbol, Anheften und Ordner (Titel wird ins Log übernommen); Inhalte laufen ausschließlich über die Engine. Das To-Do-Rollup hakt per einzelner Operation über denselben Sync-Endpunkt ab. - `PUT /documents/{id}` ändert nur noch Symbol, Anheften und Ordner (Titel wird ins Log übernommen); Inhalte laufen ausschließlich über die Engine. Das To-Do-Rollup hakt per einzelner Operation über denselben Sync-Endpunkt ab.
- **Notiz-Icon (Emoji-Picker):** Vollständiger Emoji-Katalog mit Kategoriereitern, Suche, Verlauf zuletzt verwendeter Symbole (`localStorage`) und dynamischer Theme-Anpassung. Auf Mobilgeräten (< 640 px) als Bottom-Sheet mit touch-freundlichen Zielen (≥ 44 px) und `text-base`-Suchfeld (verhindert iOS-Zoom). - **Notiz-Icon (Emoji-Picker):** Vollständiger Emoji-Katalog mit Kategoriereitern, Suche, Verlauf zuletzt verwendeter Symbole (`localStorage`) und Farben aus dem Theme. Am PC kompakt wie ein Kontextmenü auf eigener Fläche, ohne Titelleiste; auf Mobilgeräten (< 640 px) als Bottom-Sheet mit touch-freundlichen Zielen (≥ 44 px) und `text-base`-Suchfeld (verhindert iOS-Zoom). Derselbe Auswähler dient beim Anlegen von Notizen, Aufgabenlisten und Listen (`EmojiIconButton`). „Entfernen“ lässt das Icon ganz weg; ohne Icon steht nur der Titel. Neue Notizen starten mit 🗒️.
- **Markieren über mehrere Punkte** ([OutlinerTree.tsx](../../frontend/src/features/workspace/OutlinerTree.tsx)): Mit der Maus von einem Punkt in einen anderen ziehen markiert Text über Punktgrenzen (CSS Highlight API, sonst eingefärbte Zeilen). Entf/Backspace löscht wie ein Texteditor (Rest des ersten und letzten Punkts werden ein Punkt, Punkte dazwischen samt Unterpunkten gehen, Unterpunkte des letzten wandern unter den ersten) in einem Schritt (`onReplaceRange`, ein `Strg+Z` holt alles zurück); ein Zeichen ersetzt die Markierung, `Strg+C`/`Strg+X` kopieren bzw. schneiden aus.
## Explorer, Tags, To-Dos ## Explorer, Tags, To-Dos
- Ordnerbaum mit Favoriten (`doc_pins`), Verschieben per Drag & Drop (`/documents/{id}/move`, `/folders/move`). - Ordnerbaum mit Favoriten (`doc_pins`), Verschieben per Drag & Drop (`/documents/{id}/move`, `/folders/move`). Ordner zeigen ihren Zustand am offenen bzw. geschlossenen Ordnersymbol (kein Pfeil).
- **Mehrere Notizen** (PC): Strg/Cmd+Klick markiert einzeln, Shift+Klick einen Bereich in der angezeigten Reihenfolge; ein normaler Klick, Escape oder ein Klick ins Leere hebt die Markierung auf. Wer eine markierte Notiz zieht, nimmt alle markierten mit, in einen Ordner oder auf den Papierkorb (Zusammenführen gibt es dabei nicht). - **Mehrere Notizen** (PC): Strg/Cmd+Klick markiert einzeln, Shift+Klick einen Bereich in der angezeigten Reihenfolge (eine angeheftete Notiz steht zweimal im Baum; der Bereich beginnt an der Stelle, die angeklickt wurde); ein normaler Klick, Escape oder ein Klick ins Leere hebt die Markierung auf. Wer eine markierte Notiz zieht, nimmt alle markierten mit, in einen Ordner oder auf den Papierkorb (Zusammenführen gibt es dabei nicht).
- **Auf den Papierkorb ziehen** (auch eine einzelne Notiz, am Handy per langem Drücken wie beim Verschieben in Ordner) verschiebt in den Papierkorb; der Hinweis bietet „Rückgängig“. - **Auf den Papierkorb ziehen** (auch eine einzelne Notiz, am Handy per langem Drücken wie beim Verschieben in Ordner) verschiebt in den Papierkorb; der Hinweis bietet „Rückgängig“.
- **Tags:** Alle `#Hashtags` werden aggregiert (`/workspace/tags`). Eine Tag-Seite zeigt alle Fundstellen gruppiert nach Notiz und kann eine eigene Freitext-Notiz tragen (`tag_pages`). - **Tags:** Alle `#Hashtags` werden aggregiert (`/workspace/tags`). Eine Tag-Seite zeigt alle Fundstellen gruppiert nach Notiz und kann eine eigene Freitext-Notiz tragen (`tag_pages`).
- **To-Do-Rollup:** Die Ansicht „To-Dos“ sammelt alle `todo`-Blöcke aller Notizen, filterbar nach Ordner. Abhaken im Rollup ändert den Block in der Notiz. - **To-Do-Rollup:** Die Ansicht „To-Dos“ sammelt alle `todo`-Blöcke aller Notizen, filterbar nach Ordner. Abhaken im Rollup ändert den Block in der Notiz.
@@ -52,7 +53,7 @@ Code: [WorkspaceView.tsx](../../frontend/src/features/workspace/WorkspaceView.ts
- Löschen (einzeln oder mit dem Ordner) verschiebt eigene Notizen in den Papierkorb (`is_archived = 1`). Wer eine geteilte Notiz löscht, die ihm nicht gehört, verlässt nur die Freigabe. - Löschen (einzeln oder mit dem Ordner) verschiebt eigene Notizen in den Papierkorb (`is_archived = 1`). Wer eine geteilte Notiz löscht, die ihm nicht gehört, verlässt nur die Freigabe.
- Notizen im Papierkorb fehlen im Baum, in Suche, Export und Statistik und sind für alle Empfänger unsichtbar (`DocRole` = keine Rolle). Eigentümer und Löschende sehen sie schreibgeschützt mit Hinweisleiste und „Wiederherstellen“; Umbenennen, Verschieben und Teilen werden abgelehnt. - Notizen im Papierkorb fehlen im Baum, in Suche, Export und Statistik und sind für alle Empfänger unsichtbar (`DocRole` = keine Rolle). Eigentümer und Löschende sehen sie schreibgeschützt mit Hinweisleiste und „Wiederherstellen“; Umbenennen, Verschieben und Teilen werden abgelehnt.
- Eintrag „Papierkorb“ mit Zähler unten in der Ordnerleiste und im mobilen Menü. Die Liste zeigt Ordner und Löschzeitpunkt; „Endgültig löschen“ und „Papierkorb leeren“ brauchen einen zweiten Tipp. - Eintrag „Papierkorb“ mit Zähler unten in der Ordnerleiste und im mobilen Menü. Die Liste zeigt Ordner und Löschzeitpunkt; „Endgültig löschen“ und „Papierkorb leeren“ brauchen einen zweiten Tipp.
- Wiederherstellen legt einen inzwischen gelöschten Ordner neu an; existiert ein geteilter Ordner nicht mehr, landet die Notiz auf der Hauptebene. Freigaben bleiben erhalten. Der Papierkorb leert sich nicht von selbst. - Wiederherstellen legt einen inzwischen gelöschten Ordner neu an; existiert ein geteilter Ordner nicht mehr, landet die Notiz auf der Hauptebene. Freigaben bleiben erhalten. Nach der Frist der Eigentümerin bzw. des Eigentümers (Kontoeinstellungen → Aufbewahrung, Standard 30 Tage ab dem Löschen) löscht der Server die Notiz endgültig ([retention.go](../../internal/storage/retention.go)).
## Freigabe ## Freigabe
+8 -2
View File
@@ -46,7 +46,13 @@ Komponente: [FormSheet.tsx](../frontend/src/components/FormSheet.tsx), Tastatur-
- **Handy:** Bottom-Sheet über die volle Breite, das direkt über der Bildschirmtastatur sitzt (der Abstand kommt aus `visualViewport`), Griff oben, Felder und Buttons mindestens 44 px hoch, Eingaben in 16 px (sonst zoomt iOS), Abbrechen/Erstellen nebeneinander über die ganze Breite. - **Handy:** Bottom-Sheet über die volle Breite, das direkt über der Bildschirmtastatur sitzt (der Abstand kommt aus `visualViewport`), Griff oben, Felder und Buttons mindestens 44 px hoch, Eingaben in 16 px (sonst zoomt iOS), Abbrechen/Erstellen nebeneinander über die ganze Breite.
- **Desktop:** zentriertes Fenster, oder mit `anchor` neben der Klickposition (z. B. „Neuer Ordner“ im Explorer). - **Desktop:** zentriertes Fenster, oder mit `anchor` neben der Klickposition (z. B. „Neuer Ordner“ im Explorer).
- Escape und Klick auf den Hintergrund schließen. Genutzt für neue Aufgabenliste, neue Einkaufsliste und neuen Ordner; das Emoji-Sheet nutzt denselben Tastatur-Abstand. - Escape und Klick auf den Hintergrund schließen. Genutzt für neue Aufgabenliste, neue Einkaufsliste, neue Notiz und neuen Ordner; das Emoji-Sheet nutzt denselben Tastatur-Abstand.
- **Icon-Feld:** [EmojiIconButton.tsx](../frontend/src/components/EmojiIconButton.tsx) statt eines Textfelds: zeigt das Emoji (ohne Icon ein dezentes „Icon wählen“) und öffnet denselben Emoji-Auswähler wie der Notiz-Kopf, inklusive „Entfernen“. Im Dialog liegt der Auswähler per Portal über der Seite; Escape schließt nur ihn.
## Hinweis mit „Rückgängig“
- Kurzer Hinweis unten mittig (`role="status"`, Fläche `bg-panel-elevated`), am Handy über der unteren Leiste; mit Aktion bleibt er 8–10 s, sonst 3–4 s.
- Nach Löschen in den Papierkorb: „… im Papierkorb · Rückgängig“ (Workspace und Aufgaben). In den Aufgaben macht `Strg+Z` dasselbe; eine neue Aktion blendet den alten Hinweis aus, damit „Rückgängig“ nie einen anderen Schritt trifft.
## Zähler ## Zähler
@@ -78,7 +84,7 @@ Komponente: [VoiceInputButton.tsx](../frontend/src/components/VoiceInputButton.t
- Runder Mikrofon-Knopf direkt in der Zeile, rechts im Feld; diktierter Text wird an den Text angehängt, der beim Start im Feld stand. Am Handy 36 px, am PC 28 px. - Runder Mikrofon-Knopf direkt in der Zeile, rechts im Feld; diktierter Text wird an den Text angehängt, der beim Start im Feld stand. Am Handy 36 px, am PC 28 px.
- Während der Aufnahme `MicOff` in Akzentfarbe, mit eigenem Whisper-Server zusätzlich die Live-Wellen, danach „Wird erkannt…“ mit Spinner. - Während der Aufnahme `MicOff` in Akzentfarbe, mit eigenem Whisper-Server zusätzlich die Live-Wellen, danach „Wird erkannt…“ mit Spinner.
- Ausgeblendet, wenn die Spracheingabe aus ist (`STT_DISABLED`) oder der Browser sie nicht kann (z. B. Firefox ohne eigenen Whisper-Server). Fehler zeigt das Feld kurz als Platzhalter. - Ausgeblendet, wenn die Spracheingabe aus ist (`STT_DISABLED`, oder `STT_ALLOW_BROWSER=false` ohne Whisper-Server) oder der Browser sie nicht kann (z. B. Firefox ohne eigenen Whisper-Server). Fehler zeigt das Feld kurz als Platzhalter.
- Eingesetzt: Quick Dump (eigene Umsetzung), Aufgabe hinzufügen (Zeile am PC, „Neue Aufgabe anlegen“ am Handy), Listeneintrag hinzufügen. - Eingesetzt: Quick Dump (eigene Umsetzung), Aufgabe hinzufügen (Zeile am PC, „Neue Aufgabe anlegen“ am Handy), Listeneintrag hinzufügen.
## Ladebildschirm & Statusleiste ## Ladebildschirm & Statusleiste
+13 -10
View File
@@ -37,7 +37,7 @@ Die meisten Notiz- und Aufgaben-Apps verlangen Struktur im falschen Moment: Wenn
### ⚡ Schnellerfassung & Triage ### ⚡ Schnellerfassung & Triage
* Eingabeleiste im Kopf der App, jederzeit mit `C` oder `/` fokussierbar; auch per Spracheingabe: über den Browser oder datenschutzfreundlich über einen eigenen Whisper-Container (optional in `docker-compose.yml`, siehe [Architektur](Doku/architecture.md#spracherkennung)); auf Smartphones auch per Gedrückthalten des `+`-Knopfs (Hold-to-Record mit Pegel-Wellen). * Eingabeleiste im Kopf der App, jederzeit mit `C` oder `/` fokussierbar; auch per Spracheingabe: über den Browser oder datenschutzfreundlich über einen eigenen Whisper-Container (optional in `docker-compose.yml`, siehe [Architektur](Doku/architecture.md#spracherkennung)); auf Smartphones auch per Gedrückthalten des `+`-Knopfs (Hold-to-Record mit Pegel-Wellen).
* Ziel beim Erfassen wählbar: Posteingang, Aufgabe, Listeneintrag oder Notiz. * Ziel beim Erfassen wählbar: Posteingang, Aufgabe (mit Aufgabenliste), Listeneintrag oder Notiz; die Wahl bleibt für die nächsten Einträge stehen. Mit eigenem Whisper-Server erscheint der Text schon beim Sprechen; jede Person wählt selbst, ob ihr Mikrofon Whisper oder die Erkennung des Browsers nutzt.
* **Triage-Deck:** `H`/`←` verwerfen, `L`/`→` zur Aufgabe, `T` Aufgabe mit Termin, `D` in Notiz verwandeln, Leertaste = später. Auf dem Smartphone per Wischgeste oder über große Knöpfe unter der Karte; die Warteschlange klappt bei Bedarf auf. * **Triage-Deck:** `H`/`←` verwerfen, `L`/`→` zur Aufgabe, `T` Aufgabe mit Termin, `D` in Notiz verwandeln, Leertaste = später. Auf dem Smartphone per Wischgeste oder über große Knöpfe unter der Karte; die Warteschlange klappt bei Bedarf auf.
* **Teilen aus anderen Apps:** Als installierte App erscheint MindSift im Teilen-Menü des Smartphones; geteilte Texte und Links landen im Posteingang und lassen sich direkt danach mit einem Tipp zur Aufgabe oder auf eine Liste schicken. * **Teilen aus anderen Apps:** Als installierte App erscheint MindSift im Teilen-Menü des Smartphones; geteilte Texte und Links landen im Posteingang und lassen sich direkt danach mit einem Tipp zur Aufgabe oder auf eine Liste schicken.
* Papierkörbe mit Wiederherstellen für verworfene Gedanken, gelöschte Aufgaben und Notizen; unter Kontoeinstellungen → Aufbewahrung legt jede Person fest, nach wie vielen Tagen Erledigtes und Gelöschtes aufgeräumt wird. * Papierkörbe mit Wiederherstellen für verworfene Gedanken, gelöschte Aufgaben und Notizen; unter Kontoeinstellungen → Aufbewahrung legt jede Person fest, nach wie vielen Tagen Erledigtes und Gelöschtes aufgeräumt wird.
@@ -50,9 +50,10 @@ Die meisten Notiz- und Aufgaben-Apps verlangen Struktur im falschen Moment: Wenn
### ✅ Aufgaben & Kanban ### ✅ Aufgaben & Kanban
* Listen- und Kanban-Ansicht, Spalten pro Aufgabenliste frei konfigurierbar. * Listen- und Kanban-Ansicht, Spalten pro Aufgabenliste frei konfigurierbar (Name, Farbe, Erledigt-Spalte). „Alle Aufgaben“ führt die Spalten aller Listen nach Namen zu einem Board zusammen.
* Prioritäten, Fälligkeitsdatum, Hashtags, Unteraufgaben mit Fortschrittsbalken, Markdown-Notizen. * Prioritäten, Fälligkeitsdatum, Hashtags, Unteraufgaben mit Fortschrittsbalken, Markdown-Notizen.
* Filter, Gruppierung und Sortierung; Drag & Drop in Liste und Board, auch auf den Reiter einer anderen Aufgabenliste. * Filter, Gruppierung und Sortierung; Drag & Drop in Liste und Board, auch auf den Reiter einer anderen Aufgabenliste. `Strg+Z` nimmt Löschen, Verschieben und Abhaken zurück.
* **Papierkorb und Aufbewahrung:** Gelöschte und aufgeräumte Aufgaben lassen sich aus dem Papierkorb zurückholen; erledigte Aufgaben wandern nach einer selbst gewählten Frist (Standard 30 Tage) dorthin.
* **Aufgabenlisten teilen** mit Benutzern oder Gruppen, wahlweise als **Bearbeiter** oder **nur lesend**. * **Aufgabenlisten teilen** mit Benutzern oder Gruppen, wahlweise als **Bearbeiter** oder **nur lesend**.
* **Aufgaben beobachten:** Beliebige Aufgaben per Klick abonnieren, um über alle Änderungen informiert zu werden. * **Aufgaben beobachten:** Beliebige Aufgaben per Klick abonnieren, um über alle Änderungen informiert zu werden.
* **Wiederholungen:** Wiederkehrende Aufgaben (täglich, wöchentlich, monatlich, jährlich) mit automatischer Folgeaufgabe beim Abhaken. * **Wiederholungen:** Wiederkehrende Aufgaben (täglich, wöchentlich, monatlich, jährlich) mit automatischer Folgeaufgabe beim Abhaken.
@@ -66,7 +67,7 @@ Die meisten Notiz- und Aufgaben-Apps verlangen Struktur im falschen Moment: Wenn
### 📝 Workspace: Outliner-Notizen ### 📝 Workspace: Outliner-Notizen
* Jeder Gedanke ein Bullet: `Enter`, `Tab`/`Shift+Tab`, Ein-/Ausklappen, Drag & Drop ganzer Äste; am Handy eine Leiste über der Tastatur zum Ein-/Ausrücken und Verschieben. * Jeder Gedanke ein Bullet: `Enter`, `Tab`/`Shift+Tab`, Ein-/Ausklappen, Drag & Drop ganzer Äste; am Handy eine Leiste über der Tastatur zum Ein-/Ausrücken und Verschieben. Text lässt sich mit der Maus über mehrere Punkte markieren und wie in einem Texteditor löschen, ersetzen oder kopieren.
* **Zoom-Fokus** per Klick auf den Bullet, Breadcrumbs zum Zurücknavigieren; **Fokus-Modus** (`Strg+Umschalt+F`) zeigt nur die Notiz in Lesebreite, Ordner gleiten bei Bedarf von links herein. * **Zoom-Fokus** per Klick auf den Bullet, Breadcrumbs zum Zurücknavigieren; **Fokus-Modus** (`Strg+Umschalt+F`) zeigt nur die Notiz in Lesebreite, Ordner gleiten bei Bedarf von links herein.
* **Journal:** eine Notiz pro Tag, alle Tage untereinander (heute oben, ältere laden beim Scrollen nach), mit Kalender zum Springen; einzelne Tage lassen sich teilen. Pro Gerät abschaltbar. * **Journal:** eine Notiz pro Tag, alle Tage untereinander (heute oben, ältere laden beim Scrollen nach), mit Kalender zum Springen; einzelne Tage lassen sich teilen. Pro Gerät abschaltbar.
* Blocktypen und Slash-Befehle: `/todo`, `/number` (nummerierte Liste: 1., a., i.), `/link`, `/h1`–`/h3`, `/code` (mit Syntax-Hervorhebung), `/callout`, `/divider`, `/extract`, `/image`, `/audio`, `/diktat`. * Blocktypen und Slash-Befehle: `/todo`, `/number` (nummerierte Liste: 1., a., i.), `/link`, `/h1`–`/h3`, `/code` (mit Syntax-Hervorhebung), `/callout`, `/divider`, `/extract`, `/image`, `/audio`, `/diktat`.
@@ -74,7 +75,8 @@ Die meisten Notiz- und Aufgaben-Apps verlangen Struktur im falschen Moment: Wenn
* **Link-Vorschau:** Vorschaukarte (Titel, Beschreibung, Bild) für Punkte, die nur aus einer Web-Adresse bestehen, z. B. über `/link`; die Seite ruft der Server ab, nicht dein Browser. Pro Gerät abschaltbar. * **Link-Vorschau:** Vorschaukarte (Titel, Beschreibung, Bild) für Punkte, die nur aus einer Web-Adresse bestehen, z. B. über `/link`; die Seite ruft der Server ab, nicht dein Browser. Pro Gerät abschaltbar.
* **Ast auslagern & Notizen zusammenführen:** Bulletpoints samt Unterbaum in eigenständige Notizen auslagern (`/extract`) oder Notizen per Drag & Drop bzw. Menü zusammenführen (jeweils mit vollständiger Rückgängig-Funktion). * **Ast auslagern & Notizen zusammenführen:** Bulletpoints samt Unterbaum in eigenständige Notizen auslagern (`/extract`) oder Notizen per Drag & Drop bzw. Menü zusammenführen (jeweils mit vollständiger Rückgängig-Funktion).
* Wiki-Links `[[Notiz]]`, auch auf **einzelne Punkte** (`[[Notiz#^id|Text]]`, kopierbar an jedem Punkt), Links und `#Hashtags`; Tag-Seiten bündeln alle Fundstellen. * Wiki-Links `[[Notiz]]`, auch auf **einzelne Punkte** (`[[Notiz#^id|Text]]`, kopierbar an jedem Punkt), Links und `#Hashtags`; Tag-Seiten bündeln alle Fundstellen.
* Ordner, Favoriten, **To-Do-Rollup** über alle Notizen und Export einer Notiz als `.md`. * Ordner, Favoriten, **To-Do-Rollup** über alle Notizen und Export einer Notiz als `.md`. Mehrere Notizen mit `Strg`/`Shift`+Klick markieren und gemeinsam in einen Ordner oder auf den Papierkorb ziehen. Notizen und Listen bekommen ein Emoji als Symbol, oder keins.
* **Aufgaben aus Notizen:** Ein Häkchen lässt sich zur Aufgabe erweitern (und eine Aufgabe zur Notiz vertiefen); Haken, Titel und Unteraufgaben bleiben in beide Richtungen abgeglichen.
* Eingefügtes Markdown wird zur Gliederung: Überschriften werden zu Abschnitten mit eingerücktem Inhalt, Listen (auch nummeriert) behalten ihre Ebenen, Absätze werden eigene Punkte. Kürzel beim Tippen: `- `, `1. `, `[] `, `# `, `Strg+B`/`I`/`K`. `Strg+Z` / `Strg+Umschalt+Z` nimmt eigene Änderungen zurück bzw. stellt sie wieder her. * Eingefügtes Markdown wird zur Gliederung: Überschriften werden zu Abschnitten mit eingerücktem Inhalt, Listen (auch nummeriert) behalten ihre Ebenen, Absätze werden eigene Punkte. Kürzel beim Tippen: `- `, `1. `, `[] `, `# `, `Strg+B`/`I`/`K`. `Strg+Z` / `Strg+Umschalt+Z` nimmt eigene Änderungen zurück bzw. stellt sie wieder her.
* **Teilen:** Notizen und ganze Ordner lassen sich Benutzern oder Gruppen als **Bearbeiter** oder **Leser** freigeben. Empfänger sortieren sie in ihren eigenen Baum ein. * **Teilen:** Notizen und ganze Ordner lassen sich Benutzern oder Gruppen als **Bearbeiter** oder **Leser** freigeben. Empfänger sortieren sie in ihren eigenen Baum ein.
@@ -97,7 +99,7 @@ Technik dahinter: [Doku/modules/collaboration.md](Doku/modules/collaboration.md)
### 🛒 Geteilte Listen ### 🛒 Geteilte Listen
* Einkaufs-, Pack- und Checklisten mit Kategorien per Hashtag (`Hafermilch #Kühlregal`), Mengenangaben und Drag & Drop zwischen Kategorien oder in eine andere Liste. * Einkaufs-, Pack- und Checklisten mit Kategorien per Hashtag (`Hafermilch #Kühlregal`), Mengenangaben und Drag & Drop zwischen Kategorien oder in eine andere Liste. Abgehakte Einträge verschwinden nach einer wählbaren Frist (Standard 7 Tage) von selbst.
* Freigabe an Benutzer und Gruppen (Bearbeiter oder Leser); abgehakte Einträge erscheinen bei allen in Echtzeit. * Freigabe an Benutzer und Gruppen (Bearbeiter oder Leser); abgehakte Einträge erscheinen bei allen in Echtzeit.
<p align="center"> <p align="center">
@@ -108,12 +110,12 @@ Technik dahinter: [Doku/modules/collaboration.md](Doku/modules/collaboration.md)
* Geführte Ersteinrichtung, Rollen *Admin* und *Mitglied*. Einladungen als Code oder Link (auch für mehrere Personen, auf Wunsch per E-Mail verschickt). Registrierung ohne Einladung wahlweise geschlossen, mit Freigabe durch einen Admin, nur für bestimmte E-Mail-Endungen oder offen, auf Wunsch mit Bestätigung der E-Mail-Adresse per Link – auch fest per Umgebungsvariable. * Geführte Ersteinrichtung, Rollen *Admin* und *Mitglied*. Einladungen als Code oder Link (auch für mehrere Personen, auf Wunsch per E-Mail verschickt). Registrierung ohne Einladung wahlweise geschlossen, mit Freigabe durch einen Admin, nur für bestimmte E-Mail-Endungen oder offen, auf Wunsch mit Bestätigung der E-Mail-Adresse per Link – auch fest per Umgebungsvariable.
* Gruppen mit Gruppen-Admins für Freigaben. * Gruppen mit Gruppen-Admins für Freigaben.
* Admin-Bereich: Benutzer, Einladungen, Instanzname, SMTP, Freigabe-Modus, Sicherungen, Spracherkennung. * Admin-Bereich: Benutzer, Einladungen, Instanzname, SMTP, Freigabe-Modus, Sicherungen, Spracherkennung, ob „dauerhaft aufbewahren“ erlaubt ist.
### 🎨 Darstellung & App ### 🎨 Darstellung & App
* **Fünf Farbfamilien** mit je einer hellen und dunklen Variante: Klassik, Fjord, Terrakotta, Polarized, Suri. Hell/Dunkel auf Wunsch „Wie System“, dazu eine eigene Akzentfarbe, Ecken rund oder eckig und Knöpfe mit oder ohne Rahmen (pro Gerät). Für eigene Themes gibt es einen Theme-Manager (eine CSS-Datei). * **Fünf Farbfamilien** mit je einer hellen und dunklen Variante: Klassik, Fjord, Terrakotta, Polarized, Suri. Hell/Dunkel auf Wunsch „Wie System“, dazu eine eigene Akzentfarbe, Ecken rund oder eckig und Knöpfe mit oder ohne Rahmen (pro Gerät). Für eigene Themes gibt es einen Theme-Manager (eine CSS-Datei).
* **Installierbar als App (PWA)** auf Smartphone und Desktop. Sie startet auch ohne Netz mit dem zuletzt geladenen Stand; offline erfasste Gedanken und abgehakte Aufgaben oder Listeneinträge werden nachgereicht, sobald die Verbindung wieder da ist. * **Installierbar als App (PWA)** auf Smartphone und Desktop. Nach einem Server-Update meldet die offene App „Neue Version verfügbar“ und lädt auf Wunsch neu; beim Neuladen bleibt der zuletzt offene Bereich offen. Sie startet auch ohne Netz mit dem zuletzt geladenen Stand; offline erfasste Gedanken und abgehakte Aufgaben oder Listeneinträge werden nachgereicht, sobald die Verbindung wieder da ist.
<table> <table>
<tr> <tr>
@@ -205,7 +207,8 @@ Rufe `http://localhost:8080` auf. Beim ersten Start führt ein Assistent durch d
| `NOTIFY_DIGEST_MINUTES` | `5` | Bündelungsintervall für Benachrichtigungen in Minuten | | `NOTIFY_DIGEST_MINUTES` | `5` | Bündelungsintervall für Benachrichtigungen in Minuten |
| `VAPID_PUBLIC_KEY`, `VAPID_PRIVATE_KEY`, `VAPID_SUBJECT` | automatisch | Schlüssel für Push-Benachrichtigungen; ohne Angabe einmal erzeugt und in der Datenbank gespeichert. `VAPID_SUBJECT` (Kontakt für die Push-Dienste, `mailto:` oder `https://`) sonst aus `BASE_URL` | | `VAPID_PUBLIC_KEY`, `VAPID_PRIVATE_KEY`, `VAPID_SUBJECT` | automatisch | Schlüssel für Push-Benachrichtigungen; ohne Angabe einmal erzeugt und in der Datenbank gespeichert. `VAPID_SUBJECT` (Kontakt für die Push-Dienste, `mailto:` oder `https://`) sonst aus `BASE_URL` |
| `MEDIA_*` | diverse | Grenzen für Bilder und Audio in Notizen (`MEDIA_MAX_IMAGE_MB=15`, `MEDIA_MAX_AUDIO_MB=50`, `MEDIA_QUOTA_MB=0`) | | `MEDIA_*` | diverse | Grenzen für Bilder und Audio in Notizen (`MEDIA_MAX_IMAGE_MB=15`, `MEDIA_MAX_AUDIO_MB=50`, `MEDIA_QUOTA_MB=0`) |
| `STT_*` | diverse | Optionale eigene Spracherkennung per Whisper (`STT_URL`, `STT_MODEL`, `STT_LANGUAGE` …; siehe [Doku/architecture.md](Doku/architecture.md#spracherkennung)) | | `STT_*` | diverse | Optionale eigene Spracherkennung per Whisper (`STT_URL`, `STT_MODEL`, `STT_LANGUAGE`, `STT_ALLOW_BROWSER` …; siehe [Doku/architecture.md](Doku/architecture.md#spracherkennung)) |
| `RETENTION_ALLOW_NEVER` | leer (Admin-UI entscheidet) | `false`: Bei den Aufbewahrungsfristen gibt es „nie“ nicht (Erledigtes und Gelöschtes wird immer irgendwann aufgeräumt); `true` erlaubt es fest |
| `LEGAL_IMPRINT_URL`, `LEGAL_PRIVACY_URL` | leer | Impressum/Datenschutz als externer Link (nur `http(s)`) | | `LEGAL_IMPRINT_URL`, `LEGAL_PRIVACY_URL` | leer | Impressum/Datenschutz als externer Link (nur `http(s)`) |
| `LEGAL_IMPRINT_CONTENT`, `LEGAL_PRIVACY_CONTENT` (oder `…_CONTENT_FILE` mit Dateipfad) | leer | Impressum/Datenschutz als Markdown, angezeigt in einem Lesefenster. Ohne URL und Text bleibt der Link unsichtbar. | | `LEGAL_IMPRINT_CONTENT`, `LEGAL_PRIVACY_CONTENT` (oder `…_CONTENT_FILE` mit Dateipfad) | leer | Impressum/Datenschutz als Markdown, angezeigt in einem Lesefenster. Ohne URL und Text bleibt der Link unsichtbar. |
| `LEGAL_CUSTOM_LINK_LABEL`, `LEGAL_CUSTOM_LINK_URL` | leer | Zusätzlicher Link im Footer (z. B. Statusseite), nur `https` | | `LEGAL_CUSTOM_LINK_LABEL`, `LEGAL_CUSTOM_LINK_URL` | leer | Zusätzlicher Link im Footer (z. B. Statusseite), nur `https` |
@@ -214,7 +217,7 @@ Rufe `http://localhost:8080` auf. Beim ersten Start führt ein Assistent durch d
* **Reverse-Proxy:** Am besten WebSocket-Upgrade und ungepufferte SSE-Streams durchreichen. Blockiert ein Proxy beides, funktioniert MindSift trotzdem über Long-Polling bzw. regelmäßige Abfragen, nur etwas träger. Beispiele für Nginx, Traefik, Caddy und Cloudflare: [Doku/reverse-proxy.md](Doku/reverse-proxy.md). * **Reverse-Proxy:** Am besten WebSocket-Upgrade und ungepufferte SSE-Streams durchreichen. Blockiert ein Proxy beides, funktioniert MindSift trotzdem über Long-Polling bzw. regelmäßige Abfragen, nur etwas träger. Beispiele für Nginx, Traefik, Caddy und Cloudflare: [Doku/reverse-proxy.md](Doku/reverse-proxy.md).
* **Datensicherung:** In den Admin-Einstellungen lassen sich automatische tägliche Backups einschalten (ganze Datenbank plus ein Backup je Benutzer unter `data/backups/`, Standard 3 Tage aufbewahrt). Benutzer laden ihre Server-Backups herunter oder spielen sie ganz oder für einzelne Bereiche wieder ein. Der Ordner liegt neben der Datenbank: zusätzlich woanders sichern. Manuell geht weiterhin `sqlite3 mindsift.db ".backup 'backup.db'"`. * **Datensicherung:** In den Admin-Einstellungen lassen sich automatische tägliche Backups einschalten (ganze Datenbank plus ein Backup je Benutzer unter `data/backups/`, Standard 3 Tage aufbewahrt). Benutzer laden ihre Server-Backups herunter oder spielen sie ganz oder für einzelne Bereiche wieder ein. Der Ordner liegt neben der Datenbank: zusätzlich woanders sichern. Manuell geht weiterhin `sqlite3 mindsift.db ".backup 'backup.db'"`.
* **Updates:** Neues Image ziehen und Container neu starten (`docker compose pull && docker compose up -d`). Das Schema wird beim Start automatisch angepasst. Die laufende Version steht im Kopf der App unter dem Namen. * **Updates:** Neues Image ziehen und Container neu starten (`docker compose pull && docker compose up -d`). Das Schema wird beim Start automatisch angepasst. Die laufende Version steht im Kopf der App unter dem Namen; offene Apps bieten danach selbst das Neuladen an.
* **Image-Tags:** `:dev` = Stand des Branches `dev`, `:latest` = Stand von `main` (jede neue Version landet dort). Ein Git-Tag wie `v1.2.0` erzeugt zusätzlich `:v1.2.0` und `:stable`; Vorabversionen wie `v1.0.0-rc.1` verschieben `:stable` nicht. * **Image-Tags:** `:dev` = Stand des Branches `dev`, `:latest` = Stand von `main` (jede neue Version landet dort). Ein Git-Tag wie `v1.2.0` erzeugt zusätzlich `:v1.2.0` und `:stable`; Vorabversionen wie `v1.0.0-rc.1` verschieben `:stable` nicht.
* **Versionen & Änderungen:** [Semantic Versioning](https://semver.org/spec/v2.0.0.html), was sich geändert hat, steht in der [CHANGELOG.md](CHANGELOG.md). * **Versionen & Änderungen:** [Semantic Versioning](https://semver.org/spec/v2.0.0.html), was sich geändert hat, steht in der [CHANGELOG.md](CHANGELOG.md).