# 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.