Files
tessera-ctl/.planning/quick/260921-qd3-dashboard-widget-xframe-eine-webseite-pe/260921-qd3-PLAN.md
T
schalli 31ca115796
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 1m9s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m46s
docs(quick-260921-qd3): Akte - XFrame-Widget gebaut, Browser-Rundgang ohne Befund; Stand nach beiden Widgets
Plan, Zusammenfassung mit abgehakter Pruefliste und die Zeile in der
Quick-Tabelle von STATE.md; Stopp-Punkt: beide bestellten Widgets fertig,
nichts offen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 19:29:07 +02:00

47 KiB
Raw Blame History

phase, plan, type, wave, depends_on, autonomous, requirements, files_modified, estimate, must_haves
phase plan type wave depends_on autonomous requirements files_modified estimate must_haves
quick-260921-qd3 01 execute 1
true
QUICK-260921-QD3
apps/api/src/dashboard/dto/create-widget.dto.ts
apps/web/src/components/dashboard/widgets/xframe-config.ts
apps/web/src/components/dashboard/widgets/xframe-config.test.ts
apps/web/src/components/dashboard/widgets/xframe-widget.tsx
apps/web/src/components/dashboard/widgets/xframe-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/widget-catalog-modal.test.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/web/src/messages/umlaut-dictionary.ts
CHANGELOG.md
docs/anleitung-anwender.md
tokens raw_tokens tasks confidence
110000 110000 2 low
truths artifacts key_links
Im Widget-Katalog gibt es „XFrame“ (Beschreibung „Webseite einbetten“ / „Embed a web page“) mit Fenster-Symbol; eine frisch platzierte Kachel (12×8) zeigt zentriert grau „Keine Adresse eingestellt — über die Einstellungen festlegen“ und keinen Rahmen.
Unter Einstellungen → Dashboard → XFrame trägt der Benutzer eine https-Adresse (Übernahme bei Blur/Enter), optional einen Titel (höchstens 100 Zeichen) und ein Neuladen-Intervall (Nie / 1 / 5 / 10 / 30 Minuten / 1 Stunde) ein; eine http-, data- oder javascript-Adresse wird mit deutscher `role="alert"`-Meldung abgewiesen und NICHT gespeichert; dauerhaft steht der Hinweis, dass manche Webseiten das Einbetten verweigern.
Die Kachel rendert genau ein `<iframe>` mit `src` = Adresse, `sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"` (keine Freigabe der Navigation des obersten Fensters), `referrerPolicy="no-referrer"`, `loading="lazy"`, leerem `allow`; mit Titel als schmale Kopfleiste samt Knopf „In neuem Tab öffnen“, ohne Titel derselbe Knopf als kleines Symbol in der Ecke; der Knopf ist ein `<a target="_blank" rel="noopener noreferrer">` mit `aria-label` und per Tastatur erreichbar.
Bei Neuladen-Intervall > 0 wird der Rahmen im Takt neu eingehängt (`key` aus Adresse + Zähler, Zähler sichtbar als `data-reload-nonce`); der Timer wird beim Aushängen geräumt; im Bearbeitungsmodus läuft kein Timer.
Im Bearbeitungsmodus liegt eine transparente Fläche über dem Rahmen, damit die ganze Kachel Ziehgriff bleibt (der Rahmen schluckt sonst die Mausereignisse); im Ansichtsmodus gibt es diese Fläche nicht.
Der Server ruft die Adresse nie ab (kein Proxy, kein Fetch); die API lässt `xframe` als Widget-Typ zu und prüft die Konfiguration wie bisher nicht inhaltlich — die https-Prüfung läuft web-seitig zweifach (Formular UND `resolveXframeConfig` beim Rendern).
Alle Tore bleiben grün: `pnpm type-check` 4/4, `pnpm lint` 5/5, API-Tests mindestens 1175 (Stand nach pi9), Web-Tests mindestens 590 (heute 569), Umlaut-Wächter grün; `as unknown as` api 27 / web 6, `noNonNullAssertion` 56, `noExplicitAny` ≤ 13, `biome-ignore` 1, kein `!`, keine neue `any`.
apps/web/src/components/dashboard/widgets/xframe-config.ts — `XframeConfig`, `resolveXframeConfig`, `XFRAME_SANDBOX`, `XFRAME_RELOAD_OPTIONS`, `XFRAME_RELOAD_MIN/MAX`, `XFRAME_TITLE_MAX`, Re-Export `isHttpsUrl` aus picture-frame-config.ts; ohne React-Import
apps/web/src/components/dashboard/widgets/xframe-widget.tsx — Kachel mit Kopfleiste/Ecksymbol, `<iframe>`, Neulade-Timer, Bearbeitungs-Overlay, Leerzustand
apps/web/src/components/settings/widget-settings-panel.tsx — Zweig `widget.widgetType === 'xframe'` mit `XframeConfig`-Formular (Adresse, Titel, Intervall, Hinweis) und Titel in der Instanz-Kopfzeile
apps/web/src/components/dashboard/widget-registry.tsx — `WidgetType` + `'xframe'`, `WIDGET_CONSTRAINTS.xframe = { minW: 4, minH: 4, defaultW: 12, defaultH: 8 }`, `XframeIcon`, Registry-Eintrag, `wireXframeWidget`
apps/api/src/dashboard/dto/create-widget.dto.ts — `'xframe'` in `@IsIn([...])`
apps/web/src/messages/de.json + en.json — Namensraum `widgets.xframe`, identischer Schlüsselsatz
CHANGELOG.md — Stichpunkt unter „Unveröffentlicht → Neu“; docs/anleitung-anwender.md — Zeile in der Widget-Tabelle + Satz im Abschnitt Dashboard > Widgets
Katalog `WIDGET_TYPES` -> `addWidget('xframe')` -> `POST /dashboard/widgets` mit `widgetType: 'xframe'` -> `CreateWidgetDto @IsIn` (ohne den Eintrag 400) -> Kachel über `WIDGET_REGISTRY.xframe.component` (verdrahtet in `(portal)/page.tsx`)
Formular `commitUrl` -> `isHttpsUrl` -> `onChange({ url })` -> `PATCH /dashboard/widgets/:id/config` (flache Zusammenführung, bestehend) -> Kachel `resolveXframeConfig(config)` -> `url` nur wenn https, sonst `null` -> Leerzustand
`reloadSeconds > 0 && !isEditMode` -> `setInterval` -> `reloadNonce + 1` -> `key` wechselt -> `<iframe>` wird neu eingehängt; Aufräumfunktion `clearInterval`
`isEditMode` -> `<div className="absolute inset-0" aria-hidden>` NACH dem `<iframe>` im DOM -> Mausereignisse treffen die Fläche, nicht den Rahmen -> `mousedown` steigt zur Karte `.widget-drag-handle` auf (dashboard-grid.tsx: `handle` ganze Karte, `cancel` nur input/textarea/select/button/a/[data-no-drag]/.widgetNoDrag)

Quick-Aufgabe 260921-qd3: Dashboard-Widget „XFrame“

Ein neues Dashboard-Widget „XFrame“ (Widget-Typ `xframe`, Übersetzungs-Namensraum `widgets.xframe`): eine Webseite wird per https-Adresse als eingebetteter Rahmen (`