# Feature: Aufgaben (Tasks)
## Zweck
Strukturierte Aufgaben, die direkt aus Markdown-Text entstehen — kein separates Task-Formular nötig. Tasks sind echte Datenobjekte (DB-basiert, sync-fähig) und können Personen zugewiesen werden.
## Syntax im Markdown-Editor
| Syntax | Bedeutung |
|--------|-----------|
| `[] Aufgabe erledigen` | Unverknüpfte Task-Markierung (Chip, noch kein Task-Objekt) |
| `[T:abc123]` | Verknüpfte Aufgabe (ID referenziert Task-Objekt in DB) |
- `[]` muss am **Zeilenanfang** stehen (führende Leerzeichen erlaubt)
- Inline-Tasks (z.B. mitten in einem Satz) werden nicht unterstützt
## Shortcut
**Ctrl+9** im Markdown-Editor:
- Wenn Zeile beginnt mit `[] ` → `[]` wird entfernt
- Wenn Zeile beginnt mit `[T:...]` → kein Toggle (Task existiert in DB)
- Sonst → `[] ` wird am Zeilenanfang eingefügt
## Workflow
1. `[] Aufgabe tippen` → wird als grauer Chip `[ ]` gerendert
2. Chip anklicken → **TaskCreateModal** öffnet sich mit vorausgefüllten Feldern:
- Titel (aus der Zeile extrahiert, ohne `[]`, Mentions, Pfeile)
- Assignee (aus `-> ABBR` oder `@NAME` in der Zeile)
- Fälligkeitsdatum (optional)
3. Speichern → Text wird zu `[T:{id}] Aufgabe tippen` geändert
4. Chip `[T:...]` anklicken → Popup: Erledigt/Offen togglen
## Felder
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `title` | string | Titel der Aufgabe |
| `contextId` | string | Zugehöriger Kontext (von Topic des Quell-Eintrags) |
| `historyEntryId` | string \| null | Quell-HistoryEntry (null bei externen Tasks) |
| `assignee` | string \| null | Vollständiger Personenname (nie Abkürzung) |
| `status` | `open` \| `done` | Status |
| `completedAt` | string \| null | ISO-Datum des Abschlusses |
| `dueDate` | string \| null | Fälligkeitsdatum (YYYY-MM-DD) |
| `source` | `local` \| `email` \| `api` | Herkunft |
| `externalId` | string \| null | ID im Quellsystem (für externe Quellen) |
| `externalUrl` | string \| null | Deep-Link ins Quellsystem |
## Wo Tasks sichtbar sind
- **AgendaView**: Eigener Abschnitt "Aufgaben" unterhalb der Topics (pro Kontext, ungefiltert)
- **Personen-Tab "Aufgaben"**: Alle Tasks zugewiesen an diese Person (kontextübergreifend)
- **Chip im Text**: Visuelles Feedback (grau = offen, grün/durchgestrichen = erledigt)
## Chip-Visualisierung
- Unverknüpft `[ ]`: grauer Chip, Klick öffnet Modal
- Offen `[ ]`: grauer Chip
- Erledigt `[✓]`: grüner Chip mit Strikethrough
- Nicht gefunden (gelöschter Task): ausgegraut
## Extensibility: Externe Quellen
Das `source`-Feld und `externalId`/`externalUrl` bereiten künftige Integrationen vor:
- `source: 'email'` — geplant: geflagte E-Mails via Graph-API → Task; `contextId` beim Import Pflicht
- `source: 'api'` — geplant: POST `/api/tasks/import` von Drittsystemen; `contextId` im Payload Pflicht
- Sync läuft über denselben Endpunkt wie lokale Tasks — externe Quellen pushen einfach in dieselbe `tasks`-Tabelle
- Index auf `(userId, source, externalId)` für spätere Deduplizierung vorhanden
## Bekannte Einschränkungen
- `[T:...]`-Chips können nicht per Shortcut entfernt werden (Task existiert in DB)
- Assignee-Auflösung von `-> ABBR` zu vollem Namen: der extrahierte Wert wird direkt verwendet (keine automatische Abkürzungsauflösung im Modal-Prefill)
- Tasks im AgendaView zeigen alle Tasks des Kontexts (kein Status-Filter)
## Implementierungshinweise (für Wartung)
### Architektur: Wo HistoryEntries auftauchen
HistoryEntries sind **topic-scoped**, nicht context-scoped. Ein HistoryEntry gehört zu einem Topic (`entry.topicId`), das Topic gehört zu einem Kontext (`topic.contextId`). Daraus folgt:
```
HistoryEntry.text
→ gerendert in RenderedMarkdown
→ Task-Chip-Klick braucht entry.id + entry.topicId
→ ContextPage löst daraus contextId auf
```
**Der nicht-offensichtliche Fall: `EventCard`**
`EventCard` zeigt einen *Termin* (Kontext vom Typ `meeting`), nicht direkt einen HistoryEntry. Die Meeting-Notizen sind aber intern als HistoryEntry gespeichert — unter dem speziellen Topic `notesTopicId(event.id)`. Dieser Mechanismus ist im EventCard-Code versteckt (`liveQuery` auf `notesTopicId`). Wer `EventCard` nur von außen sieht ("das ist eine Meeting-Karte"), erwartet dort keinen HistoryEntry-Kontext — und vergisst deshalb, `historyEntryId` zu übergeben.
Merksatz: **Überall wo `entry.text` oder ein abgeleiteter Text (title/body-Split) gerendert wird, muss `entry.id` als `historyEntryId` mitgegeben werden — egal wie die Komponente heißt.**
### `historyEntryId` muss überall explizit übergeben werden
`RenderedMarkdown` wird an ~10 Stellen instanziiert. Ohne `historyEntryId`-Prop öffnet ein Task-Chip-Klick das Modal mit `historyEntryId: null` — der Task wird angelegt, aber der Quelltext (`[] …` → `[T:id]`) wird **nicht** aktualisiert. Der Fehler ist stumm: kein Fehler, kein visuelles Feedback, nur der Chip bleibt `[ ]`.
**Vollständige Instanz-Übersicht:**
| Datei | Quelle | historyEntryId | Notiz |
|-------|--------|---------------|-------|
| `HistoryItem.svelte` | `entry` | ✓ | |
| `JournalView.svelte:626/640` | `entry` (daily-log) | ✓ | |
| `JournalView.svelte:674` | `entry` (Meeting-Scope) | ✓ | |
| `EventCard.svelte` | `notesEntry` (versteckt) | ✓ | `notesEntry` = erster Entry aus `notesTopicId(event.id)` |
| `DashboardView.svelte:539/541` | `entry` | ✗ | read-only Dashboard, kein Task-Flow vorgesehen |
| `DashboardView.svelte:569` | `entry` | ✗ | read-only Dashboard |
| `ArchivedView.svelte` | `entry` | ✗ | archivierte Einträge, kein Task-Flow vorgesehen |
| `WiedervorlageCard.svelte` | `entry` (body-Split) | ✗ | Wiedervorlage-Kontext, kein Task-Flow vorgesehen |
| `PersonsView.svelte` | `task.text` | — | kein HistoryEntry |
| `EditableMarkdown.svelte` | Topic-Body | — | kein HistoryEntry, `topicId` wäre theoretisch möglich |
Wenn zukünftig Task-Chips auch in Dashboard/Archiv/Wiedervorlage aktiv werden sollen, müssen dort `historyEntryId` und `topicId` ergänzt werden.
### Debugging-Anleitung
Symptom: Chip klickbar, Modal öffnet, aber Text bleibt `[] …` (kein `[T:id]`).
Diagnose im Browser-Devtools-Konsole:
```js
document.querySelector('[data-task-new]').closest('.markdown-content').parentElement.className
```
→ Klasse identifizieren → zugehörige Komponente finden → prüfen ob `historyEntryId` Prop gesetzt ist.
### Empfohlenes Refactoring: `HistoryEntryText`-Komponente
Die Wurzel des Problems ist, dass `RenderedMarkdown` ein generisches Render-Primitive ist, aber der HistoryEntry-Kontext implizit mitgegeben werden muss. Eine dedizierte Wrapper-Komponente würde das explizit machen:
```svelte
```
Verwendung dann z.B.:
```svelte
```
**Vorteile:**
- `historyEntryId` kann nicht mehr vergessen werden — es ist Pflicht-Prop (TypeScript-Fehler sonst)
- Neue Views die HistoryEntry-Text rendern, verwenden automatisch die richtige Komponente
- `RenderedMarkdown` ohne `historyEntryId` bleibt erlaubt für echte read-only-Kontexte (Dashboard, Archiv)
**Aufwand:** ~7 Stellen umbauen, kein Logik-Änderung. Kein dringender Handlungsbedarf, aber sinnvoll beim nächsten größeren Refactoring-Sprint.