feat(tasks): assign tasks in shared lists, unified people search, list avatars
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m36s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m36s
- Tasks in shared task lists can be assigned to one or more people with access (task_assignees); assignments are pruned whenever access is lost - UserAutocomplete: type-to-search everywhere, nothing before typing, max 3 results, first match preselected - Task list and list tabs: avatar stack of members and round monochrome counter - Shared list items remember who added them (list_items.created_by) - Doku/ui-patterns.md documents the shared UI patterns Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1bd38f4d50
commit
73fad1c5db
21 files changed
+598
-67
No files matched your search
@@ -18,6 +18,7 @@ Referenz für Entwickler und Administratoren. Alle Angaben wurden gegen den Code
|
||||
| [sharing-and-permissions.md](sharing-and-permissions.md) | Rollen, Gruppen, Freigaben |
|
||||
| [reverse-proxy.md](reverse-proxy.md) | Nginx, Traefik, Caddy, Cloudflare |
|
||||
| [theming.md](theming.md) | Themes und Design-Variablen |
|
||||
| [ui-patterns.md](ui-patterns.md) | Einheitliche Bedienelemente: Personensuche, Profilbild-Stapel, Zähler |
|
||||
| [flaws.md](flaws.md) | Bekannte Schwachstellen und technische Schulden |
|
||||
|
||||
## Überblick in 60 Sekunden
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ Basis: `/api/v1`. Antworten sind JSON; Fehler haben die Form `{"error": "Text"}`
|
||||
|---|---|---|
|
||||
| POST | `/inbox` | Schnellerfassung. Body: `content`, `priority`, `source`, `dueDate`, `target` (`inbox`/`task`/`list_item`/`document`), `targetListId`, `taskListId` (snake_case-Varianten werden akzeptiert) |
|
||||
| GET | `/thoughts?status=` | Gedanken (`inbox`, `kept`, `converted`, `discarded`, `all`) |
|
||||
| GET/PATCH/DELETE | `/thoughts/{id}` | Einzelabruf; Teiländerung (`content`, `isCompleted`, `dueDate`, `priority`, `taskListId`, `kanbanStatus`, `notes`, `subtasks`, `tags`); Löschen |
|
||||
| GET/PATCH/DELETE | `/thoughts/{id}` | Einzelabruf; Teiländerung (`content`, `isCompleted`, `dueDate`, `priority`, `taskListId`, `kanbanStatus`, `notes`, `subtasks`, `tags`, `assignees`); Löschen. `assignees` ersetzt die Zuweisungen; nur Personen mit Zugriff auf die Liste, sonst `403` |
|
||||
| DELETE | `/thoughts/discarded/purge` | Papierkorb leeren |
|
||||
| GET | `/triage/deck` | Posteingang (max. 100) |
|
||||
| PATCH | `/triage/{id}` | `action`: `keep`/`discard`/`restore`; mit `convertTo`: `task`/`document`/`list_item` |
|
||||
|
||||
+2
-1
@@ -19,6 +19,7 @@ Die Datei `migrations/001_init.sql` wird vom Code **nicht** gelesen und ist vera
|
||||
| Tabelle | Zweck | Wichtige Spalten |
|
||||
|---|---|---|
|
||||
| `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` (nur Datum), `task_list_id`, `kanban_status`, `notes`, `subtasks` (JSON), `tags` (JSON), `alarm_sent`, `sort_order`, `source` (`manual`, `voice`, `webhook` …) |
|
||||
| `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), `team_id` (Altfeld), `title`, `icon`, `color`, `columns` (JSON, Kanban-Spalten) |
|
||||
| `task_list_shares` | Freigaben von Aufgabenlisten | `target_type` (`team`/`user`), `target_id`, **`permission`** (`editor`/`viewer`, Standard `editor`) |
|
||||
|
||||
@@ -48,7 +49,7 @@ Blocktypen: `h1`, `h2`, `h3`, `paragraph`, `text`, `todo`, `bullet`, `code`, `ca
|
||||
|---|---|
|
||||
| `lists` | Geteilte Listen: `title`, `icon`, `list_type` (`shopping`/`checklist`/`vacation`) |
|
||||
| `list_shares` | Freigaben: `target_type`, `target_id`, `role` (`editor`/`viewer`) |
|
||||
| `list_items` | Einträge: `content`, `category`, `quantity`, `is_completed`, `completed_by(_name)`, `sort_order` |
|
||||
| `list_items` | Einträge: `content`, `category`, `quantity`, `is_completed`, `completed_by(_name)`, `created_by` (leer bei alten Einträgen), `sort_order` |
|
||||
|
||||
### Gruppen
|
||||
| Tabelle | Zweck |
|
||||
|
||||
@@ -5,7 +5,7 @@ Einkaufs-, Pack- und Checklisten für gemeinsames Arbeiten. Code: [SharedListsVi
|
||||
## Datenmodell
|
||||
|
||||
- `lists`: Eigentümer, `title`, `icon`, `list_type` (`shopping`, `checklist`, `vacation`).
|
||||
- `list_items`: `content`, `category`, `quantity`, `is_completed`, `completed_by(_name)`, `sort_order`.
|
||||
- `list_items`: `content`, `category`, `quantity`, `is_completed`, `completed_by(_name)`, `created_by`, `sort_order`.
|
||||
- `list_shares`: Ziel (Benutzer oder Gruppe) und Rolle `editor` oder `viewer`.
|
||||
|
||||
## Funktionen
|
||||
@@ -16,4 +16,6 @@ Einkaufs-, Pack- und Checklisten für gemeinsames Arbeiten. Code: [SharedListsVi
|
||||
- **Abhaken** speichert, wer und wann; Erledigte stehen in einem eigenen Bereich und lassen sich per „Erledigte entfernen“ löschen (`clear-completed`).
|
||||
- **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.
|
||||
- **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.
|
||||
- **Tabs** zeigen offene Einträge als runden Zähler und die Personen mit Zugriff als Profilbild-Stapel ([ui-patterns.md](../ui-patterns.md)); die API liefert sie je Liste als `members`.
|
||||
- Aus der Triage lassen sich Gedanken direkt als Eintrag in eine Liste übernehmen.
|
||||
@@ -4,7 +4,7 @@ Code: [TasksView.tsx](../../frontend/src/features/triage/TasksView.tsx), [TaskDe
|
||||
|
||||
## Datenmodell
|
||||
|
||||
Eine Aufgabe ist ein `thought` ([database.md](../database.md)) mit `kanbanStatus`, `priority` (`low`/`normal`/`high`/`urgent`), `dueDate` (nur Datum), `notes` (Markdown), `subtasks` (`[{id, title, isCompleted}]`), `tags`, `taskListId` und `sortOrder`. Aufgaben ohne Liste sind persönlich.
|
||||
Eine Aufgabe ist ein `thought` ([database.md](../database.md)) mit `kanbanStatus`, `priority` (`low`/`normal`/`high`/`urgent`), `dueDate` (nur Datum), `notes` (Markdown), `subtasks` (`[{id, title, isCompleted}]`), `tags`, `taskListId`, `sortOrder` und `assignees` (Benutzer-IDs). Aufgaben ohne Liste sind persönlich.
|
||||
|
||||
## Ansichten
|
||||
|
||||
@@ -21,6 +21,9 @@ Eine Aufgabe ist ein `thought` ([database.md](../database.md)) mit `kanbanStatus
|
||||
- **Leser:** sieht alles, kann nichts ändern. Der Server antwortet mit `403`; die Oberfläche blendet Eingaben aus und zeigt „Nur Lesen“.
|
||||
- Gruppenmitglieder mit mehreren Wegen erhalten die höchste Stufe. Nur der Eigentümer legt Freigaben an oder entfernt sie und kann die Liste löschen.
|
||||
- Umsetzung: `Store.GetTaskListPermission` liefert `owner`/`editor`/`viewer`/`""`; `ensureTaskListWritable` schützt Schreibpfade.
|
||||
- **Zuweisen** (nur in geteilten Listen, also mit mindestens einer Person außer dem Eigentümer): Im Detail-Fenster unter „Zugewiesen“ lassen sich eine oder mehrere Personen mit Zugriff auf die Liste zuweisen, über die einheitliche Personensuche ([ui-patterns.md](../ui-patterns.md)). Zuweisen dürfen Eigentümer und Bearbeiter. Zeilen und Kanban-Karten zeigen die Zugewiesenen als Profilbild-Stapel.
|
||||
- Gespeichert in `task_assignees`; `PATCH /thoughts/{id}` mit `assignees` ersetzt die Liste. Der Server lehnt Personen ohne Zugriff ab (`403`, `storage.ErrAssigneeNoAccess`).
|
||||
- **Zuweisungen folgen den Rechten:** `Store.PruneTaskAssignees` entfernt jede Zuweisung, deren Person die Liste nicht mehr sehen kann. Es läuft nach Entfernen einer Freigabe, Ändern von `team_id`, Austritt aus oder Entfernen aus einer Gruppe, Löschen einer Gruppe, Löschen der Liste und Verschieben der Aufgabe in eine andere oder keine Liste; beim Löschen eines Kontos werden dessen Zuweisungen direkt gelöscht. Wer über einen zweiten Weg (z. B. direkt und über eine Gruppe) weiter Zugriff hat, bleibt zugewiesen.
|
||||
- Bekannte Lücken: [FLAW-03](../flaws.md) (Umsortieren per ID), [FLAW-06](../flaws.md) (Editor kann weiterteilen).
|
||||
|
||||
## Alarme
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
- **Notizen und Ordner:** Die effektive Rolle berechnet `Store.DocRole` (Eigentümer, Eigentümer des geteilten Ordners = Bearbeiter, direkte Freigabe, Gruppenfreigabe, Ordnerfreigabe; höchste gewinnt). Im Freigabe-Dialog sind für Notizen und geteilte Ordner „Bearbeiten“ und „Nur Lesen“ wählbar (gespeichert als `write`/`read`). Bearbeiter ändern Inhalt, Struktur und Titel über die Sync-Engine; Leser sehen Änderungen live.
|
||||
- Bei Aufgabenlisten und Listen gilt: Hat jemand mehrere Zugriffswege (direkt und über Gruppen), zählt die **höchste** Stufe.
|
||||
- Für Gruppen gilt die Freigabe für alle aktuellen und künftigen Mitglieder.
|
||||
- **Aufgaben-Zuweisungen** hängen am Zugriff: Verliert jemand den letzten Zugriffsweg auf eine Aufgabenliste, entfernt der Server ihn aus allen Zuweisungen dieser Liste ([modules/tasks.md](modules/tasks.md)).
|
||||
- Aufgabenlisten besitzen zusätzlich das Altfeld `team_id`: Mitglieder dieser Gruppe haben ohne Freigabezeile Bearbeiterrechte. Eine Gruppenfreigabe über den Dialog setzt `team_id`, falls es noch leer ist.
|
||||
|
||||
## Aufgabenlisten teilen (Details)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# UI-Muster
|
||||
|
||||
Wiederkehrende Bedienelemente, die überall gleich aussehen und sich gleich verhalten. Wer eines davon neu braucht, nimmt die vorhandene Komponente und baut keine eigene Variante.
|
||||
|
||||
## Personen suchen (Type-to-Search)
|
||||
|
||||
Komponente: [UserAutocomplete.tsx](../frontend/src/features/UserAutocomplete.tsx)
|
||||
|
||||
Eingesetzt beim Teilen (`UnifiedShareModal`), beim Hinzufügen zu Gruppen (`GroupsView`) und beim Zuweisen von Aufgaben (`TaskDetailModal`).
|
||||
|
||||
- **Keine Liste vorab:** Solange das Feld leer ist, erscheint nichts, auch nicht beim Reinklicken. Kein Dropdown mit allen Personen.
|
||||
- **Erst beim Tippen** erscheinen Treffer direkt unter dem Feld, **höchstens 3** (`MAX_RESULTS`). Gesucht wird in Anzeigename, `@Benutzername` und E-Mail.
|
||||
- Der **erste Treffer ist vorausgewählt**: Tippen + Enter übernimmt ihn. Pfeiltasten wechseln, Escape schließt.
|
||||
- Bereits gewählte oder freigegebene Personen werden über `excludeUserIds` ausgeblendet.
|
||||
- **Einzelauswahl** (Teilen, Gruppen): `selectedUser` setzen; das Feld zeigt dann die gewählte Person mit ×.
|
||||
- **Mehrfachauswahl** (Aufgaben zuweisen): `selectedUser={null}` lassen und in `onSelectUser` zur Liste hinzufügen. Die Gewählten stehen als Chips (Avatar, Name, ×) über dem Feld, das Feld bleibt für die nächste Suche frei.
|
||||
|
||||
## Profilbilder als Stapel
|
||||
|
||||
Komponente: [AvatarStack.tsx](../frontend/src/components/AvatarStack.tsx) (nutzt `UserAvatar`)
|
||||
|
||||
- Kleine runde Profilbilder, **leicht überlappend**, **höchstens 3**. Weitere Personen erscheinen als monochromer Kreis „+n“.
|
||||
- Ohne Profilbild zeigt `UserAvatar` die Initialen.
|
||||
- `ringClass` bekommt die Farbe der Fläche dahinter (meist `ring-[var(--bg-panel)]` oder `ring-[var(--bg-app)]`), damit die Kreise sauber getrennt sind.
|
||||
- Größen: `xxs` (16 px) in Tabs, Kanban-Karten und Listeneinträgen, `xs` (20 px) in Aufgabenzeilen und Kopfzeilen.
|
||||
- **Kein Hover-Effekt** im Stapel selbst. Einzige Ausnahme ist die Anwesenheit in Notizen ([PresenceAvatars.tsx](../frontend/src/features/workspace/PresenceAvatars.tsx)): Dort zeigt Hover oder Tastaturfokus die Liste „Gerade online“.
|
||||
|
||||
| Ort | Wer wird gezeigt |
|
||||
|---|---|
|
||||
| Notiz-Kopfzeile | wer die Notiz gerade geöffnet hat (Profilbilder aus `/users/directory`) |
|
||||
| Tabs der Aufgabenlisten und Listen | alle mit Zugriff außer mir (`members`) |
|
||||
| Aufgabenzeile und Kanban-Karte | zugewiesene Personen (nur in geteilten Listen) |
|
||||
| Eintrag einer geteilten Liste | wer ihn hinzugefügt hat (`createdBy`); ältere Einträge ohne Autor zeigen nichts |
|
||||
|
||||
## Zähler
|
||||
|
||||
CSS-Klasse `badge-count` in [index.css](../frontend/src/index.css): runder Zähler in der Form der Zähler der Navigationsleiste, aber **monochrom** (grau gefüllt) statt in der Akzentfarbe.
|
||||
|
||||
- Verwendet für die Anzahl offener Einträge in den Tabs der Aufgabenlisten und der Listen.
|
||||
- Die Zähler der Navigationsleiste bleiben blau (Akzent), weil sie auf neue Inhalte aufmerksam machen.
|
||||
- Kein eckiges `badge-neutral` mehr für Zähler in Tabs.
|
||||
Reference in new issue
Block a user