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
@@ -3,7 +3,9 @@ import type { ComponentType } from 'react';
/**
* Supported widget types for the dashboard.
* clock/search/calendar/note: implemented in previous plans.
* calculator/favorites/link/stopwatch: Phase 8 additions.
* calculator/favorites/stopwatch: Phase 8 additions (der fruehere
* Einzel-Schnellzugriffs-Typ wurde in quick-260916-iex entfernt — Favoriten
* decken den Fall ab).
*/
export type WidgetType =
| 'clock'
@@ -12,7 +14,6 @@ export type WidgetType =
| 'note'
| 'calculator'
| 'favorites'
| 'link'
| 'stopwatch';
/**
@@ -51,7 +52,6 @@ export const WIDGET_CONSTRAINTS: Record<
// Inhalt 268 px; bei 9 Zeilen (244 px) fehlte die unterste Reihe um 25 px.
calculator: { minW: 3, minH: 10, defaultW: 6, defaultH: 10 },
favorites: { minW: 3, minH: 3, defaultW: 6, defaultH: 10 },
link: { minW: 3, minH: 2, defaultW: 4, defaultH: 4 },
stopwatch: { minW: 4, minH: 3, defaultW: 6, defaultH: 6 },
};
@@ -219,26 +219,6 @@ function FavoritesIcon({ className }: { className?: string }) {
);
}
function LinkIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
);
}
function StopwatchIcon({ className }: { className?: string }) {
return (
<svg
@@ -314,14 +294,6 @@ export const WIDGET_REGISTRY: Record<WidgetType, WidgetDefinition> = {
...WIDGET_CONSTRAINTS.favorites,
component: PlaceholderWidget, // Replaced via wireFavoritesWidget()
},
link: {
type: 'link',
nameKey: 'link.name',
descriptionKey: 'link.description',
icon: LinkIcon,
...WIDGET_CONSTRAINTS.link,
component: PlaceholderWidget, // Replaced via wireLinkWidget()
},
stopwatch: {
type: 'stopwatch',
nameKey: 'stopwatch.name',
@@ -384,14 +356,6 @@ export function wireFavoritesWidget(component: ComponentType<WidgetProps>) {
}
}
let linkWired = false;
export function wireLinkWidget(component: ComponentType<WidgetProps>) {
if (!linkWired) {
WIDGET_REGISTRY.link.component = component;
linkWired = true;
}
}
let stopwatchWired = false;
export function wireStopwatchWidget(component: ComponentType<WidgetProps>) {
if (!stopwatchWired) {