Ein neuer Widget-Typ war an sieben Stellen einzutragen; vergass man eine,
fehlte die Kachel im Katalog oder die API lehnte sie mit 400 ab.
- WIDGET_TYPES/WidgetType/WIDGET_MODULE_SLUGS stehen jetzt einmal in
packages/shared; Registry, Katalog und die @IsIn-Whitelist der API
leiten davon ab
- neun wireXWidget()-Funktionen durch ein generisches registerWidget()
ersetzt (idempotent, unbekannter Typ wirft in der Entwicklung)
- der Katalog fuehrt keine zweite Typliste mehr, sondern leitet sie aus
der Registry ab und filtert nach Modulzugriff (fail-closed, wenn die
Modulliste unbekannt ist); der Abruf von /modules/active liegt auf der
Dashboard-Seite, nicht im Dialog
- widget-module-map.ts liest die geteilte Tabelle statt einer Kopie, die
oeffentliche Funktion bleibt unveraendert
Der Katalogfilter ist Komfort (T-M1H-01) — verbindlich bleibt der
serverseitige Filter in DashboardService.getWidgets.
Abweichung vom Plan: apps/web hing entgegen der Planannahme noch nicht
von @tessera/shared ab; die Abhaengigkeit wurde ergaenzt (Lockfile). Die
Dockerfiles kopieren packages/shared bereits, der Produktionsbau von
Next.js und der nest build laufen unveraendert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- xframe-config.ts: Resolver (https-Pruefung via isHttpsUrl des Bilderrahmens,
Titel bis 100 Zeichen, Neuladen 0/60/300/600/1800/3600 s geklemmt),
XFRAME_SANDBOX ohne allow-top-navigation und allow-modals; 12 Tests zuerst rot
- xframe-widget.tsx: genau ein <iframe> (sandbox, allow="", no-referrer, lazy),
Kopfleiste mit Titel oder Ecksymbol "In neuem Tab oeffnen", Neuladen ueber
key-Wechsel mit Timer-Raeumung, transparente Flaeche im Bearbeitungsmodus
damit die Kachel Ziehgriff bleibt; 12 Tests zuerst rot
- xframe-config-form.tsx: Adresse/Titel mit Uebernahme bei Blur/Enter, http wird
mit Meldung abgewiesen und nicht gespeichert, Intervall-Auswahl, dauerhafter
Hinweis auf verweigertes Einbetten; 8 Tests
- Panel-Zweig samt "— Titel" in der Kopfzeile, Registry (12x12, Fenster-Symbol),
Katalog, Seite, DTO @IsIn, de/en widgets.xframe (15 Schluessel), Umlaut-Allowlist
"neuem"; der Server ruft die Adresse nie ab
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- picture-frame-config.ts: Eintragstyp als Vereinigung (upload | url) in EINER
geordneten Liste, resolvePictureFrameConfig laesst alles ausser https weg
(T-PI9-07), Intervall 0 oder 5..3600 s, pickNextIndex (Zufall nie dasselbe)
- dashboard-images-api.ts: Upload als FormData-Feld image ohne eigenen
Content-Type, 413 -> deutsche Meldung, Proxy-Pfad fuer <img src>
- PictureFrameWidget: Leerzustand, <img referrerPolicy=no-referrer> (Browser
laedt Fremdbilder, Server nie), object-contain/cover, Unterschrift-Streifen,
Wechsel per Timer mit Raeumung, kaputte Bilder verlassen den Umlauf,
Grossansicht nur ausserhalb des Bearbeitungsmodus mit Fokus-Rueckgabe und
Pause des Wechsels; im Bearbeitungsmodus kein Knopf (Karte bleibt Griff)
- PictureFrameConfigForm im WidgetSettingsPanel: Ausschnitt, Intervall,
Reihenfolge, Liste mit Vorschau/Unterschrift/Pfeilen/Entfernen (Upload wird
auch serverseitig geloescht), Datei hochladen, https-Adresse hinzufuegen
- Registry (4x4 min, 8x8 Vorgabe), Katalog, Seite, Uebersetzungen de/en
(widgets.pictureFrame, 28 Schluessel), bestehende Tests auf acht Typen
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Aeussere Flaeche verliert onClick, die bislang nur optische
Hintergrund-Flaeche wird eine benannte <button> und traegt onClose
(D-01); verliert dabei aria-hidden, weil ein fokussierbares Element
nicht verborgen sein darf
- stopPropagation auf der Dialogflaeche entfaellt als toter Code, weil
der Hintergrund jetzt Geschwister statt Vorfahr ist
- Fest verdrahtetes englisches aria-label="Close" durch common.close
ersetzt
- Neuer Katalogschluessel widgets.catalogClose in de.json/en.json
- Neue Testdatei: Hintergrund schliesst (Klick + Tastatur), Dialogklick
schliesst nicht, Escape weiterhin, Kartenauswahl fuegt Widget hinzu
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
useButtonType: "Widget hinzufuegen"-, Bearbeiten-Modus- und Katalog-Buttons
(kein Formular in diesen Dateien) erhalten type="button".
noSvgWithoutTitle: Widget-Icons in der Katalog-Kachel (immer neben dem
Widget-Namen) und Buttons mit bestehendem aria-label/title werden dekorativ
(aria-hidden="true"); der bislang unbeschriftete Such-Button im Such-Widget
erhaelt aria-label={t('search.searchButton')} (neuer Schluessel, siehe
vorherige i18n-Festschreibung).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
- CR-01: fix SSRF bypass — isPrivateIpv6 now delegates ::ffff:<ipv4> to
isPrivateIpv4, covering 172.16-31.x and 169.254.x ranges
- CR-02: add ParseUUIDPipe to GET /favorites widgetId param + service guard
so missing widgetId returns 400 instead of leaking all user favorites
- WR-01: link-widget — replace raw 'link.error' key with t('link.error') (4 sites)
- WR-02: favorites-widget — fix load-path error to use t('favorites.error')
- WR-03: widget-catalog-modal — move aria-hidden from outer wrapper to backdrop
- WR-04: calculator — remove duplicate M button (MR clone); MC/MR/M+/M−/MS remain
- WR-05: schema — add FavoriteLink→WidgetInstance FK with onDelete:Cascade
- IN-01: create-widget.dto.ts — update comment from four to eight supported types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Install react-grid-layout@2.2.3 and react-resizable
- Create widget-registry.tsx with all 4 widget types, WIDGET_CONSTRAINTS, WidgetProps
- Create dashboard-api.ts with fetch/save layout and widget CRUD functions
- Create dashboard-store.ts (Zustand, NO persist — D-05) with edit mode and auto-save on exit
- Create DashboardGrid with react-grid-layout v2 Responsive, ResizeObserver width
- Create ClockWidget using Intl.DateTimeFormat (no manual UTC offsets)
- Create WidgetWrapper with drag handle and delete button in edit mode
- Create EditModeToggle (pencil/checkmark), WidgetCatalogModal (2x2 grid)
- Rewrite portal page.tsx as dashboard with grid, edit toggle, widget catalog
- Add ResizeObserver polyfill in test setup, CSS mock support in vitest config
- All 5 tests green (dashboard grid + clock widget)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>