feat: Link-Widget restlos entfernt (Web, API, Migration, Handbuch, Changelog)

- Web: Union-Mitglied, Constraints, Icon, Registry-Eintrag, wire-Funktion,
  Katalog, Seiten-Verdrahtung, Tests und i18n (de/en) fuer den Typ link
  entfernt; link-widget.tsx/.test.tsx geloescht
- API: create-widget.dto.ts (@IsIn-Liste) und widget-module-map.ts
  (Kommentare) auf sieben Typen angepasst; schema.prisma unveraendert
- Neue Migration 20260916120000_remove_link_widget: eine idempotente
  DELETE-Anweisung auf WidgetInstance, FavoriteLink kaskadiert ueber den
  bestehenden FK; wird in diesem Auftrag NICHT ausgefuehrt
- Neuer widget-wrapper-Test belegt den grauen Fallback fuer unbekannte
  Widget-Typen (Bestandsverhalten, jetzt festgeschrieben)
- Handbuch (Widget-Tabelle, Einstellungs-Hinweise) und CHANGELOG
  (Geaendert/Entfernt/Behoben) aktualisiert
- Web: 52 Dateien / 344 Tests gruen, tsc Exit 0; API: tsc Exit 0,
  dashboard-Spec 31 Tests gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-16 13:44:09 +02:00
parent 7f1ee3b1f0
commit 39ea1474a5
16 changed files with 77 additions and 705 deletions
@@ -0,0 +1,22 @@
-- Widget "Link" entfernt (quick-260916-iex): das Favoriten-Widget deckt den
-- Anwendungsfall "Schnellzugriff auf Links" bereits vollstaendig ab, ein
-- separates Einzel-Link-Widget ist ueberfluessig. Web/API haben den Typ
-- `link` restlos entfernt (Registry, Katalog, DTO); diese Migration raeumt
-- bestehende Link-Kacheln in der Datenbank hinterher.
--
-- FavoriteLink-Zeilen des jeweiligen Widgets kaskadieren automatisch ueber
-- den bestehenden Fremdschluessel "FavoriteLink_widgetId_fkey ... ON DELETE
-- CASCADE" (Migration 20260708090000_add_favorite_link) -- kein separates
-- DELETE auf "FavoriteLink" noetig.
--
-- Laeuft als Migrationsrolle "tessera" (POSTGRES_USER, Superuser mit
-- BYPASSRLS, siehe 20260909130000_rls_app_role). Die FORCE ROW LEVEL
-- SECURITY-Regel auf "WidgetInstance" (Migration 20260909140000) greift
-- fuer diese Rolle deshalb nicht -- das DELETE sieht und loescht ueber
-- alle Mandanten hinweg.
--
-- Idempotent: ein zweiter Lauf loescht 0 Zeilen. Keine Schemaaenderung.
-- Verwaiste Eintraege im Layout-JSON von "DashboardLayout" sind unschaedlich
-- (dashboard-grid.tsx rendert nur ueber widgets.map) und verschwinden beim
-- naechsten Speichern des Dashboards von selbst.
DELETE FROM "WidgetInstance" WHERE "widgetType" = 'link';
@@ -2,12 +2,12 @@ import { IsIn, IsObject, IsOptional, IsString } from 'class-validator';
/**
* DTO for creating a new widget instance on a user's dashboard.
* widgetType must be one of the eight supported types.
* widgetType must be one of the seven supported types.
* config is optional and defaults to {} on the model.
*/
export class CreateWidgetDto {
@IsString()
@IsIn(['clock', 'search', 'calendar', 'note', 'calculator', 'favorites', 'link', 'stopwatch'])
@IsIn(['clock', 'search', 'calendar', 'note', 'calculator', 'favorites', 'stopwatch'])
widgetType!: string;
@IsOptional()
+3 -3
View File
@@ -13,9 +13,9 @@
* für ein Feld, das derzeit für jede Zeile leer wäre, wiegt schwerer als
* diese Konstante mit identischer Aussagekraft (15-RESEARCH.md Pitfall 5).
*
* Die Tabelle ist am Ende dieser Phase bewusst leer: alle acht heute
* Die Tabelle ist am Ende dieser Phase bewusst leer: alle sieben heute
* registrierten Widget-Typen (clock/search/calendar/note/calculator/
* favorites/link/stopwatch, siehe apps/web/src/components/dashboard/
* favorites/stopwatch, siehe apps/web/src/components/dashboard/
* widget-registry.tsx) sind Plattform-Widgets ohne Modulbezug. Das
* einzige bislang geplante modulgebundene Widget steht in
* .planning/REQUIREMENTS.md unter "Future Requirements (deferred)" und
@@ -25,7 +25,7 @@ export const WIDGET_MODULE_MAP: Readonly<Record<string, string>> = {};
/**
* Liefert den Modul-Slug für einen Widget-Typ, oder `undefined`, wenn
* der Typ kein Modul-Widget ist (der heutige Zustand für alle acht
* der Typ kein Modul-Widget ist (der heutige Zustand für alle sieben
* bestehenden Typen). Einziger Lesezugriff auf die Zuordnungstabelle,
* damit Tests sie gezielt mocken können.
*/