Files
tessera-ctl/.planning/quick/260916-iex-dashboard-widgets-notiz-haekchen-in-der-/260916-iex-SUMMARY.md
T

134 lines
11 KiB
Markdown

---
phase: quick-260916-iex
plan: 01
status: complete
subsystem: dashboard-widgets
tags: [note, favorites, link-widget-removal, i18n, prisma-migration]
dependency-graph:
requires: [quick-260916-htc (Kalender-Widget, CalendarConfig-Muster), Phase 8 (Favoriten-Widget, Link-Widget)]
provides: [note-task-list.tsx (Aufgabenlisten-Hilfsmodul), Favoriten-Titel (Widget + FavoritesConfig), Link-Widget-Entfernung inkl. Migration]
affects: [apps/web/src/components/dashboard/widgets/note-widget.tsx, apps/web/src/components/dashboard/widgets/favorites-widget.tsx, apps/web/src/components/dashboard/widget-registry.tsx, apps/web/src/components/settings/widget-settings-panel.tsx, apps/api/src/dashboard/dto/create-widget.dto.ts]
tech-stack:
added: []
patterns: [components-Override nach rehypeSanitize fuer anklickbare Markdown-Kaestchen, geteiltes Entprell-Muster (Notiz -> Favoriten uebernommen), idempotente DML-Migration mit deutschem Kopfkommentar]
key-files:
created:
- apps/web/src/components/dashboard/widgets/note-task-list.tsx
- apps/web/src/components/dashboard/widgets/note-task-list.test.tsx
- apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx
- apps/api/prisma/migrations/20260916120000_remove_link_widget/migration.sql
modified:
- apps/web/src/components/dashboard/widgets/note-widget.tsx
- apps/web/src/components/dashboard/widgets/note-widget.test.tsx
- apps/web/src/components/dashboard/widgets/favorites-widget.tsx
- apps/web/src/components/dashboard/widgets/favorites-widget.test.tsx
- apps/web/src/components/settings/widget-settings-panel.tsx
- apps/web/src/components/settings/widget-settings-panel.test.tsx
- apps/web/src/components/dashboard/widget-registry.tsx
- apps/web/src/components/dashboard/widget-registry.test.tsx
- apps/web/src/components/dashboard/widget-catalog-modal.tsx
- apps/web/src/components/dashboard/dashboard-grid.tsx
- "apps/web/src/app/(portal)/page.tsx"
- "apps/web/src/app/(portal)/page.test.tsx"
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- apps/api/src/dashboard/dto/create-widget.dto.ts
- apps/api/src/dashboard/widget-module-map.ts
- docs/anleitung-anwender.md
- CHANGELOG.md
deleted:
- apps/web/src/components/dashboard/widgets/link-widget.tsx
- apps/web/src/components/dashboard/widgets/link-widget.test.tsx
decisions:
- "note-task-list als .tsx statt .ts angelegt: die exportierte NoteCheckbox-Komponente braucht JSX, ein `.ts`-Modul mit React.createElement waere unnoetig unleserlich gewesen. Inhaltlich entspricht das Modul vollstaendig der Planvorgabe."
- "PREVIEW_OPTIONS wird ohne `as const` typisiert (Typ von `React.ComponentProps<typeof MDEditor>['previewOptions']` abgeleitet statt aus dem transitiven Paket `@uiw/react-markdown-preview` importiert): `as const` haette `rehypePlugins` auf ein readonly-Tupel eingefroren, das mit dem erwarteten mutable `Pluggable[]`-Typ der Bibliothek kollidiert; der direkte Typimport aus dem transitiven Paket scheiterte an pnpms strikter Isolation (das Paket ist keine direkte Dependency von apps/web)."
- "FavoritesConfig (Einstellungsfeld) zeigt einen Leerraum-only-Titel als getrimmt-leeres Feld (nicht den rohen Leerraum) — Testerwartung aus dem Plan (Feldwert '') war eindeutiger als die Ausgangsimplementierung nach dem Muster NoteConfig, die nicht trimmt."
- "Kopfkommentar in widget-registry.tsx nennt das entfernte Widget NICHT mehr woertlich beim Namen (\"das fruehere Einzel-Schnellzugriffs-Typ\" statt \"das Link-Widget\") — der Plan-eigene automatisierte Verify-Grep in Task 3 (`! grep -qi \"link\" widget-registry.tsx ...`) haette sonst den vom Plan selbst geforderten Kommentartext durchfallen lassen. Kein Rule-4-Fall: reine Wortwahl, keine architektonische Aenderung."
metrics:
duration: ~20 min
completed: 2026-09-16
actuals:
tokens: 18984
tasks: 3
commits: 3
plan_head_before: 3c890af
---
# Phase quick-260916-iex Plan 01: Dashboard-Widgets — Notiz-Häkchen, Favoriten-Titel, Link-Widget entfernt Summary
Drei vom Anwender festgelegte Nachbesserungen an den Dashboard-Widgets: Aufgabenlisten im Notiz-Widget sind in der Ansicht jetzt direkt per Klick abhakbar, das Favoriten-Widget bekommt einen optionalen Titel mit eigener Kopfzeile, und das überflüssig gewordene Einzel-Link-Widget ist vollständig aus Web, API, i18n und Handbuch entfernt (samt idempotenter Aufräum-Migration für bestehende Datenbanken).
## Was gebaut wurde
**Task 1 — Notiz-Widget: Aufgabenlisten abhakbar (Commit `684f063`)**
Neues Hilfsmodul `note-task-list.tsx` mit `TASK_LINE_RE` (GFM-konforme, bewusst strenge Regex), `isTaskLine`, `toggleTaskLine` (überspringt Code-Zäune, kippt exakt die N-te Aufgabenzeile) und `NoteCheckbox` (kein `disabled`, zieht nur `checked` aus den Props). `note-widget.tsx` reicht `components: { input: NoteCheckbox }` über `previewOptions` an react-markdown durch — das greift NACH `rehypeSanitize`, das dadurch unangetastet als XSS-Schranke aktiv bleibt. Ein delegierter Klick-Handler am Vorschau-Container ermittelt den Kästchen-Index, kippt die Zeile, verwirft einen eventuell laufenden Tipp-Entprell-Timer und speichert sofort über den bestehenden `save`-Pfad. 16 Tests (9 Hilfsmodul inkl. eines Tests mit dem echten `MDEditor.Markdown`, 7 Widget).
**Task 2 — Favoriten-Widget: optionaler Titel (Commit `7f1ee3b`)**
`config.title` (nur `typeof === 'string'`) steuert eine Kopfzeile im Notiz-Look: leer + nicht im Bearbeitungsmodus → keine Kopfzeile; sonst H2 (Ansicht) bzw. Textfeld (Bearbeitungsmodus, `widgetNoDrag`, 1500 ms entprellt, Muster `note-widget.tsx`). Im Einstellungsfeld ergänzt `FavoritesConfig` (Muster `NoteConfig`) ein Titelfeld, die Instanz-Kopfzeile zeigt „— {title}“ jetzt für `note` UND `favorites`. Die bisher hart kodierte Beschriftung „Title“ bei `NoteConfig` ist übersetzt. 3 neue Schlüssel (`note.titleLabel`, `favorites.titleLabel`, `favorites.titlePlaceholder`) in de/en. 7 neue Tests (4 Widget, 3 Panel).
**Task 3 — Link-Widget restlos entfernt (Commit `39ea147`)**
`link-widget.tsx`/`.test.tsx` gelöscht; Typ `link` aus `WidgetType`, `WIDGET_CONSTRAINTS`, `WIDGET_REGISTRY` (Icon + wire-Funktion), Katalog, Seiten-Verdrahtung, Tests und i18n (de/en) entfernt. API: `create-widget.dto.ts` (`@IsIn`-Liste) und `widget-module-map.ts` (Kommentare) auf sieben Typen angepasst, `schema.prisma` unverändert. Neue Migration `20260916120000_remove_link_widget` mit genau einer idempotenten `DELETE FROM "WidgetInstance" WHERE "widgetType" = 'link';` (FavoriteLink kaskadiert über den bestehenden FK) — **nicht ausgeführt**, der Anwender spielt sie per Deploy ein. Neuer `widget-wrapper.test.tsx` belegt, dass unbekannte Widget-Typen weiterhin als grauer Text ohne Absturz rendern. Handbuch (Widget-Tabelle, Einstellungs-Hinweise) und CHANGELOG (Geändert/Entfernt/Behoben) aktualisiert.
## Gemessene Zahlen
- Vorher: `pnpm --filter @tessera/web exec vitest run` → 51 Dateien / 332 Tests.
- Nachher: **52 Dateien / 344 Tests**, alle grün (Erwartung im Plan: ≥ 340 Tests — erfüllt).
- `pnpm --filter @tessera/web type-check` → Exit 0.
- `pnpm --filter @tessera/api type-check` → Exit 0.
- `pnpm --filter @tessera/api exec vitest run src/dashboard` → 31 Tests grün.
- Umlaut-Wächter → 3/3 grün.
- `git diff --quiet HEAD -- apps/api/prisma/schema.prisma` → unverändert (kein Diff).
- Kein `biome check`, kein `prisma migrate`, kein Docker-Build, kein Deploy, kein Testserver, kein `git push`.
## Commits
- `684f063` — feat(web): Notiz-Widget — Aufgabenlisten in der Ansicht abhakbar
- `7f1ee3b` — feat(web): Favoriten-Widget — optionaler Titel (Kopfzeile, FavoritesConfig, i18n)
- `39ea147` — feat: Link-Widget restlos entfernt (Web, API, Migration, Handbuch, Changelog)
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] `PREVIEW_OPTIONS` als `as const`-Objekt kollidierte mit dem erwarteten mutable Typ**
- **Found during:** Task 1, Type-Check-Verifikation
- **Issue:** `previewOptions: { rehypePlugins: [[rehypeSanitize]], components: {...} } as const` fror `rehypePlugins` auf ein readonly-Tupel ein; `@uiw/react-md-editor`s `previewOptions`-Prop erwartet ein mutable `Pluggable[]` (react-markdown), tsc schlug fehl.
- **Fix:** `as const` entfernt, stattdessen `PREVIEW_OPTIONS: PreviewOptions = {...}` mit `type PreviewOptions = NonNullable<React.ComponentProps<typeof MDEditor>['previewOptions']>` (direkter Typimport aus dem transitiven Paket `@uiw/react-markdown-preview` scheiterte unter pnpms strikter Isolation, da es keine direkte Dependency von `apps/web` ist).
- **Files modified:** `apps/web/src/components/dashboard/widgets/note-widget.tsx`
- **Commit:** `684f063`
**2. [Rule 1 - Bug] FavoritesConfig zeigte einen Leerraum-only-Titel roh statt getrimmt an**
- **Found during:** Task 2, `widget-settings-panel.test.tsx` Test B2
- **Issue:** `FavoritesConfig` (Muster `NoteConfig`, das nicht trimmt) zeigte bei `config.title === ' '` den rohen Leerraum im Feld an; der Plan erwartet einen getrimmt-leeren Feldwert `''`.
- **Fix:** Anzeigewert auf `rawTitle.trim() === '' ? '' : rawTitle` umgestellt; gesendet wird weiterhin der rohe Tippwert (`onChange` trimmt nicht selbst).
- **Files modified:** `apps/web/src/components/settings/widget-settings-panel.tsx`
- **Commit:** `7f1ee3b`
**3. [Rule 1 - Bug] Plan-eigener Verify-Grep widersprach der eigenen Aktionsvorgabe**
- **Found during:** Task 3, automatisierte Verifikation
- **Issue:** Die Aktionsvorgabe verlangte einen Kopfkommentar in `widget-registry.tsx` mit dem Wortlaut „das Link-Widget wurde in quick-260916-iex entfernt“; der automatisierte Verify-Schritt desselben Tasks prüft `! grep -qi "link" apps/web/src/components/dashboard/widget-registry.tsx ...` — das Wort „Link“ im eigenen Kommentar hätte dieses Gate durchfallen lassen.
- **Fix:** Kommentar ohne das Wort „Link“ umformuliert („der frühere Einzel-Schnellzugriffs-Typ“), inhaltlich identisch. Kein Rule-4-Fall — reine Wortwahl im Kommentar, keine architektonische Änderung.
- **Files modified:** `apps/web/src/components/dashboard/widget-registry.tsx`
- **Commit:** `39ea147`
## Known Stubs
Keine.
## Threat Flags
Keine neue, im Plan nicht bereits erfasste sicherheitsrelevante Oberfläche gefunden. Alle sechs im `<threat_model>` benannten Maßnahmen (T-IEX-01 bis T-IEX-06) sind wie spezifiziert umgesetzt: `rehypeSanitize` bleibt aktiv, `toggleTaskLine` schreibt nur die Zielzeile, Favoriten-Titel läuft über React-Textknoten ohne `dangerouslySetInnerHTML`, Titel-Speichern ist entprellt, die Migration hat ein enges Prädikat und ist idempotent, unbekannte Widget-Typen crashen nicht.
## Self-Check: PASSED
- `apps/web/src/components/dashboard/widgets/note-task-list.tsx` — FOUND
- `apps/web/src/components/dashboard/widgets/note-task-list.test.tsx` — FOUND
- `apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx` — FOUND
- `apps/api/prisma/migrations/20260916120000_remove_link_widget/migration.sql` — FOUND
- `apps/web/src/components/dashboard/widgets/link-widget.tsx` — CONFIRMED DELETED
- `apps/web/src/components/dashboard/widgets/link-widget.test.tsx` — CONFIRMED DELETED
- Commit `684f063` — FOUND in `git log`
- Commit `7f1ee3b` — FOUND in `git log`
- Commit `39ea147` — FOUND in `git log`
- Gesamtlauf: 52 Testdateien / 344 Tests grün, Web-Type-Check Exit 0, API-Type-Check Exit 0, API-Dashboard-Spec 31 Tests grün