docs(quick-260922-m1h): Akte - Widget-Aufraeumen, Rundgang verhaltensneutral bestaetigt
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+3
-2
@@ -5,9 +5,9 @@ current_phase: 18
|
|||||||
current_phase_name: desktop-client-fertigstellen
|
current_phase_name: desktop-client-fertigstellen
|
||||||
status: verified
|
status: verified
|
||||||
stopped_at: "22.09.2026: 1.3.0 freigegeben; danach quick-260922-hk4 — Bilderrahmen-Bilder liegen jetzt im Dateibereich (user-files) statt in der Datenbank, Umzug laeuft automatisch beim Start, Selbstheilung aus der alten data-Spalte eingebaut; im Browser nachgewiesen. NAECHSTER SCHRITT, vom Nutzer noch nicht bestaetigt: (1) einmaliges Aufraeumen, damit ein Modul seine Dashboard-Kachel selbst mitbringt (heute sieben Hartkodierungen je Kachel; Katalog zeigt auch Kacheln gesperrter Module; gesperrte Kachel bleibt leer statt zu erklaeren) — das Geruest WIDGET_MODULE_MAP existiert und ist leer; (2) danach das Proxmox-Modul (PVE/PBS/PMG) und seine Kachel. Offen beim Nutzer: Live-Server auf 1.3.0 ziehen, neuen Client per Browser installieren."
|
stopped_at: "22.09.2026: 1.3.0 freigegeben; danach quick-260922-hk4 — Bilderrahmen-Bilder liegen jetzt im Dateibereich (user-files) statt in der Datenbank, Umzug laeuft automatisch beim Start, Selbstheilung aus der alten data-Spalte eingebaut; im Browser nachgewiesen. NAECHSTER SCHRITT, vom Nutzer noch nicht bestaetigt: (1) einmaliges Aufraeumen, damit ein Modul seine Dashboard-Kachel selbst mitbringt (heute sieben Hartkodierungen je Kachel; Katalog zeigt auch Kacheln gesperrter Module; gesperrte Kachel bleibt leer statt zu erklaeren) — das Geruest WIDGET_MODULE_MAP existiert und ist leer; (2) danach das Proxmox-Modul (PVE/PBS/PMG) und seine Kachel. Offen beim Nutzer: Live-Server auf 1.3.0 ziehen, neuen Client per Browser installieren."
|
||||||
last_updated: "2026-09-22T13:40:00.000Z"
|
last_updated: "2026-09-22T14:15:00.000Z"
|
||||||
last_activity: 2026-09-21
|
last_activity: 2026-09-21
|
||||||
last_activity_desc: Quick 260922-hk4 — Bilderrahmen-Bilder in den Dateibereich umgezogen (automatisch beim Start, Selbstheilung aus der alten Spalte), im Browser nachgewiesen; davor Freigabe 1.3.0
|
last_activity_desc: Quick 260922-m1h — Widget-Typen an einer Stelle, Katalog filtert nach Modulzugriff, Kachel kennt ihr Modul (Vorarbeit Proxmox); im Browser als verhaltensneutral bestaetigt
|
||||||
state_head: 4d485432c003a6caf68f6d85aff7de0bd27794e2
|
state_head: 4d485432c003a6caf68f6d85aff7de0bd27794e2
|
||||||
progress:
|
progress:
|
||||||
total_phases: 18
|
total_phases: 18
|
||||||
@@ -462,6 +462,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests.
|
|||||||
| fast-260922-b | **Desktop-App: Download-Knoepfe in der App ohne Funktion (fast, 747a4d4).** Befund des Nutzers: „Herunterladen“ unter Einstellungen → Desktop-App tut in der App nichts (Windows und Linux). Ursache: die Webansicht hatte keinen Download-Handler — webkit2gtk verwirft Downloads dann still, WebView2 zeigte ebenfalls nichts. Fix: Hauptfenster entsteht im Code (`app.windows` in tauri.conf.json leer), weil nur `WebviewWindowBuilder` `on_download` annimmt; der Handler bricht den Download in der App ab und oeffnet die Adresse per Opener im System-Browser (Fortschritt, Speicherort, Passwortfenster fuer den Proxy). Capability `main` unveraendert. cargo fmt/clippy/test gruen. Nicht am laufenden Client geprueft (kein Display auf dem Dev-Host) — CI baut, Nachweis beim Nutzer oder auf der Windows-VM. | 2026-09-22 | 747a4d4 | — |
|
| fast-260922-b | **Desktop-App: Download-Knoepfe in der App ohne Funktion (fast, 747a4d4).** Befund des Nutzers: „Herunterladen“ unter Einstellungen → Desktop-App tut in der App nichts (Windows und Linux). Ursache: die Webansicht hatte keinen Download-Handler — webkit2gtk verwirft Downloads dann still, WebView2 zeigte ebenfalls nichts. Fix: Hauptfenster entsteht im Code (`app.windows` in tauri.conf.json leer), weil nur `WebviewWindowBuilder` `on_download` annimmt; der Handler bricht den Download in der App ab und oeffnet die Adresse per Opener im System-Browser (Fortschritt, Speicherort, Passwortfenster fuer den Proxy). Capability `main` unveraendert. cargo fmt/clippy/test gruen. Nicht am laufenden Client geprueft (kein Display auf dem Dev-Host) — CI baut, Nachweis beim Nutzer oder auf der Windows-VM. | 2026-09-22 | 747a4d4 | — |
|
||||||
| 260922-ge2 | **XFrame: Ausschnitt der Seite waehlen und einpassen, Zoom, „Nur anzeigen“.** Wunsch des Nutzers: nur einen bestimmten Ausschnitt der eingebetteten Seite zeigen, und die Groesse soll skalieren. Config: `crop {x,y,w,h}` in Seitenpixeln bei fester Layoutbreite 1280 (`XFRAME_PAGE_WIDTH`, keine UI), Klemmung ueber EINE Funktion `clampXframeCrop` (x+w ≤ 1280 verschiebt x; w ≥ 100, h ≥ 60, y+h ≤ 4000); `zoom` (50…150 %, nur Ganzseiten-Modus); `readOnly` (transparente Flaeche ueber dem Rahmen im Ansichtsmodus). Kachel: `computeCropLayout` (contain + Zentrierung, Massstab darf > 1 sein), der `<iframe>` wird selbst verschoben und skaliert (cross-origin — die Seite laesst sich von aussen nicht scrollen), Kachelmass per ResizeObserver. Einstellungen: Vorschau der Seite bei 1280 px (Stage 3000 Seitenpixel hoch, eigener Bildlauf), Rahmen als `<fieldset>` (Biome `useSemanticElements`) mit vier Eckgriffen, Ziehen per Pointer-Events mit lokalem Entwurf und genau einem PATCH beim Loslassen, Zahlenfelder als Tastaturweg; Zoom-Auswahl nur ohne Ausschnitt; Aktivieren setzt `readOnly` mit. **Befund im Browser-Rundgang, behoben (cf70a19):** Kachel und Vorschau hatten verschiedene Rahmenhoehen (max(y+h,720) vs. 3000) — bei vh-relativen Seiten (example.com `margin: 15vh`) lag derselbe Inhalt an verschiedenen Stellen, der gewaehlte Ausschnitt haette in der Kachel daneben gelegen; jetzt dieselbe Layouthoehe. Neun Pruefpunkte bestanden (Verschieben, Ecken mit fester Gegenecke und Mindestbreite, Klemmung der Zahlenfelder, Einpassen und Mitskalieren bei Kachelgroesse, Nur-anzeigen, Zoom 60 %, verweigernde Seite). Playwright kann in einem per `transform` skalierten iframe nicht selbst klicken — per `elementFromPoint` + `mouse.click` umgangen, ist eine Werkzeuggrenze. Test-Helfer `src/test/fake-resize-observer.ts`. **Zahlen:** web 604 → 640, api 1175, type-check 4/4, lint 5/5 (web 53 Warnungen unveraendert), `as unknown as` 27/6, Umlaut-Allowlist + „Ausschnitt“. | 2026-09-22 | 445b1d3,30fdd99,cf70a19 | [260922-ge2-xframe-widget-ausschnitt-der-eingebettet](./quick/260922-ge2-xframe-widget-ausschnitt-der-eingebettet/) |
|
| 260922-ge2 | **XFrame: Ausschnitt der Seite waehlen und einpassen, Zoom, „Nur anzeigen“.** Wunsch des Nutzers: nur einen bestimmten Ausschnitt der eingebetteten Seite zeigen, und die Groesse soll skalieren. Config: `crop {x,y,w,h}` in Seitenpixeln bei fester Layoutbreite 1280 (`XFRAME_PAGE_WIDTH`, keine UI), Klemmung ueber EINE Funktion `clampXframeCrop` (x+w ≤ 1280 verschiebt x; w ≥ 100, h ≥ 60, y+h ≤ 4000); `zoom` (50…150 %, nur Ganzseiten-Modus); `readOnly` (transparente Flaeche ueber dem Rahmen im Ansichtsmodus). Kachel: `computeCropLayout` (contain + Zentrierung, Massstab darf > 1 sein), der `<iframe>` wird selbst verschoben und skaliert (cross-origin — die Seite laesst sich von aussen nicht scrollen), Kachelmass per ResizeObserver. Einstellungen: Vorschau der Seite bei 1280 px (Stage 3000 Seitenpixel hoch, eigener Bildlauf), Rahmen als `<fieldset>` (Biome `useSemanticElements`) mit vier Eckgriffen, Ziehen per Pointer-Events mit lokalem Entwurf und genau einem PATCH beim Loslassen, Zahlenfelder als Tastaturweg; Zoom-Auswahl nur ohne Ausschnitt; Aktivieren setzt `readOnly` mit. **Befund im Browser-Rundgang, behoben (cf70a19):** Kachel und Vorschau hatten verschiedene Rahmenhoehen (max(y+h,720) vs. 3000) — bei vh-relativen Seiten (example.com `margin: 15vh`) lag derselbe Inhalt an verschiedenen Stellen, der gewaehlte Ausschnitt haette in der Kachel daneben gelegen; jetzt dieselbe Layouthoehe. Neun Pruefpunkte bestanden (Verschieben, Ecken mit fester Gegenecke und Mindestbreite, Klemmung der Zahlenfelder, Einpassen und Mitskalieren bei Kachelgroesse, Nur-anzeigen, Zoom 60 %, verweigernde Seite). Playwright kann in einem per `transform` skalierten iframe nicht selbst klicken — per `elementFromPoint` + `mouse.click` umgangen, ist eine Werkzeuggrenze. Test-Helfer `src/test/fake-resize-observer.ts`. **Zahlen:** web 604 → 640, api 1175, type-check 4/4, lint 5/5 (web 53 Warnungen unveraendert), `as unknown as` 27/6, Umlaut-Allowlist + „Ausschnitt“. | 2026-09-22 | 445b1d3,30fdd99,cf70a19 | [260922-ge2-xframe-widget-ausschnitt-der-eingebettet](./quick/260922-ge2-xframe-widget-ausschnitt-der-eingebettet/) |
|
||||||
| 260922-hk4 | **Bilderrahmen-Bilder liegen jetzt im Dateibereich statt in der Datenbank.** Frage des Nutzers nach der Freigabe 1.3.0, ob `bytea` auf Dauer sinnvoll ist. Befund: Geschwindigkeit ist NICHT das Argument (ein Bild wird je Browser einmal taeglich geladen), die SICHERUNG ist es — gesichert wird von Hand per `pg_dump`, und 30 Bilder à 5 MiB je Benutzer waeren im Extremfall 150 MB pro Benutzer in jedem Abzug (alpha-DB heute 18 MB). Dazu Einheitlichkeit: Profilbilder (`user-files/avatars`, `User.avatarPath`) und DKV-Exporte liegen laengst im Volume. Umsetzung: Spalte `storagePath`, Ablage `user-files/dashboard-images/<userId>/<uuid>.<ext>` — Dateiname IMMER vom Server (UUID + Endung aus dem erkannten Mime-Typ), `originalName` nie im Pfad; ein eigener Ordner je Benutzer ist ausdruecklich KEIN Schutz, es entscheidet weiterhin die Besitzpruefung im Dienst. Umzug laeuft automatisch beim Start (`onApplicationBootstrap` ueber `forSystem()`), idempotent; die Spalte `data` bleibt bewusst vorerst stehen (Todo fuer den DROP, erst wenn alpha und live einmal gelaufen sind). **Befund im Rundgang, eigener Commit:** eine Zeile zeigte auf eine fehlende Datei (lokal Host vs. Container-Volume; im Betrieb: alter `pg_dump` + leeres Volume) — `getBytes` stellt die Datei jetzt aus der noch vorhandenen Spalte `data` wieder her, statt 404 zu melden. **Zahlen:** api 1175 → 1188, web 640, type-check 4/4, lint 5/5, RLS-Waechter 78/78. | 2026-09-22 | 9039cea,8cbfb8b,82472ee | [260922-hk4-bilderrahmen-bilder-auf-die-festplatte](./quick/260922-hk4-bilderrahmen-bilder-auf-die-festplatte/) |
|
| 260922-hk4 | **Bilderrahmen-Bilder liegen jetzt im Dateibereich statt in der Datenbank.** Frage des Nutzers nach der Freigabe 1.3.0, ob `bytea` auf Dauer sinnvoll ist. Befund: Geschwindigkeit ist NICHT das Argument (ein Bild wird je Browser einmal taeglich geladen), die SICHERUNG ist es — gesichert wird von Hand per `pg_dump`, und 30 Bilder à 5 MiB je Benutzer waeren im Extremfall 150 MB pro Benutzer in jedem Abzug (alpha-DB heute 18 MB). Dazu Einheitlichkeit: Profilbilder (`user-files/avatars`, `User.avatarPath`) und DKV-Exporte liegen laengst im Volume. Umsetzung: Spalte `storagePath`, Ablage `user-files/dashboard-images/<userId>/<uuid>.<ext>` — Dateiname IMMER vom Server (UUID + Endung aus dem erkannten Mime-Typ), `originalName` nie im Pfad; ein eigener Ordner je Benutzer ist ausdruecklich KEIN Schutz, es entscheidet weiterhin die Besitzpruefung im Dienst. Umzug laeuft automatisch beim Start (`onApplicationBootstrap` ueber `forSystem()`), idempotent; die Spalte `data` bleibt bewusst vorerst stehen (Todo fuer den DROP, erst wenn alpha und live einmal gelaufen sind). **Befund im Rundgang, eigener Commit:** eine Zeile zeigte auf eine fehlende Datei (lokal Host vs. Container-Volume; im Betrieb: alter `pg_dump` + leeres Volume) — `getBytes` stellt die Datei jetzt aus der noch vorhandenen Spalte `data` wieder her, statt 404 zu melden. **Zahlen:** api 1175 → 1188, web 640, type-check 4/4, lint 5/5, RLS-Waechter 78/78. | 2026-09-22 | 9039cea,8cbfb8b,82472ee | [260922-hk4-bilderrahmen-bilder-auf-die-festplatte](./quick/260922-hk4-bilderrahmen-bilder-auf-die-festplatte/) |
|
||||||
|
| 260922-m1h | **Ein Modul bringt seine Dashboard-Kachel jetzt selbst mit (Vorarbeit fuer Proxmox).** Bestandsaufnahme (lesend) hatte ergeben: ein neuer Widget-Typ war an SIEBEN Stellen hartkodiert (Union-Typ, Constraints, Registry, eigene `wireXWidget()` je Typ, Aufruf in page.tsx, zweite Liste im Katalogfenster, `@IsIn` im API-DTO); die Verbindung Kachel↔Modul existierte als `WIDGET_MODULE_MAP` in `dashboard.service.ts` (filtert fail-closed), war aber nie befuellt; der Katalog zeigte jedem alle Kacheln, auch die gesperrter Module. Umbau: `WIDGET_TYPES`/`WidgetType`/`WIDGET_MODULE_SLUGS` in `packages/shared` als EINE Quelle (API validiert per `@IsIn` gegen genau sie), ein generisches `registerWidget()` statt neun Funktionen, Katalog leitet seine Liste aus der Registry ab und filtert ueber `/modules/active` (fail-closed bei Fehler, reine Funktion `visibleWidgetTypes`), nicht verfuegbare Kachel zeigt `widgets.unavailable` statt leer zu bleiben. Deckungsgleichheits-Test faengt kuenftig jede vergessene Stelle. **Befund des Executors, geprueft statt vermutet:** `apps/web` hatte KEINE Abhaengigkeit auf `@tessera/shared` (frueher bewusst) — vor der Umsetzung nachgemessen, dass Bau und Produktions-Abbild das tragen (node:24-alpine strippt die Typen nativ); Folgeregel „nur loeschbare Syntax in shared“ steht als Warnung in der Datei. Verhalten der neun Kacheln unveraendert, im Browser bestaetigt (Reihenfolge, Anlegen, Entfernen, keine rohen Schluessel). Bewusst offen: der Einstellungs-Zweig je Typ in `widget-settings-panel.tsx` und die Live-Aktualisierung des Katalogs. **Zahlen:** api 1188 → 1202, web 640 → 659, type-check 4/4, lint 5/5 (74/53 wie Basis). | 2026-09-22 | 56c07c3,8be0725 | [260922-m1h-dashboard-widgets-ein-modul-bringt-seine](./quick/260922-m1h-dashboard-widgets-ein-modul-bringt-seine/) |
|
||||||
|
|
||||||
## Deferred Items
|
## Deferred Items
|
||||||
|
|
||||||
|
|||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
---
|
||||||
|
phase: quick-260922-m1h
|
||||||
|
plan: 01
|
||||||
|
type: refactor
|
||||||
|
autonomous: true
|
||||||
|
subsystem: apps/web/src/components/dashboard
|
||||||
|
requirements: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# Quick-Aufgabe 260922-m1h: Ein Modul bringt seine Dashboard-Kachel selbst mit
|
||||||
|
|
||||||
|
## Warum (Auftrag des Nutzers, 22.09.2026)
|
||||||
|
|
||||||
|
Als Naechstes kommt ein Proxmox-Modul (PVE/PBS/PMG), das zusaetzlich als
|
||||||
|
kompakte Kachel auf dem Dashboard erscheinen soll — und kuenftig sollen weitere
|
||||||
|
Module dasselbe tun (PBS: Sicherungsstatus, PMG: Mail-Zahlen). Eine
|
||||||
|
Bestandsaufnahme (lesend, 22.09.) hat ergeben:
|
||||||
|
|
||||||
|
- **Ein neuer Widget-Typ ist heute an SIEBEN Stellen hartkodiert**: `WidgetType`
|
||||||
|
(Union), `WIDGET_CONSTRAINTS`, `WIDGET_REGISTRY`, eine eigene `wireXWidget()`
|
||||||
|
je Typ, der Aufruf in `(portal)/page.tsx`, die ZWEITE Liste `WIDGET_TYPES` in
|
||||||
|
`widget-catalog-modal.tsx` und die `@IsIn`-Whitelist in
|
||||||
|
`apps/api/src/dashboard/dto/create-widget.dto.ts`. Vergisst man eine, fehlt die
|
||||||
|
Kachel im Katalog oder die API lehnt sie mit 400 ab.
|
||||||
|
- **Die Verbindung Kachel↔Modul existiert schon, ist aber leer:**
|
||||||
|
`apps/api/src/dashboard/widget-module-map.ts` (`WIDGET_MODULE_MAP = {}`),
|
||||||
|
gelesen von `dashboard.service.ts` — `getWidgets()` filtert Kacheln aus, deren
|
||||||
|
Modul der Benutzer nicht hat (fail-closed, Zeile ~164-205). Das funktioniert,
|
||||||
|
wurde nur nie benutzt.
|
||||||
|
- **Zwei Luecken:** (a) der Katalog („Widget hinzufuegen") zeigt JEDEM alle
|
||||||
|
Kacheln, auch die gesperrter Module — anlegen geht, danach verschwindet die
|
||||||
|
Kachel kommentarlos; (b) eine Kachel mit unbekanntem Typ rendert leer, ohne
|
||||||
|
Erklaerung.
|
||||||
|
|
||||||
|
Diese Aufgabe raeumt das auf, BEVOR Proxmox kommt. Kein neues Modul, keine neue
|
||||||
|
Kachel — reiner Umbau mit unveraendertem Verhalten fuer die neun vorhandenen
|
||||||
|
Kacheln.
|
||||||
|
|
||||||
|
## Gebundene Entscheidungen (Orchestrator)
|
||||||
|
|
||||||
|
1. **Eine Quelle fuer die Typliste, geteilt zwischen Web und API.** In
|
||||||
|
`packages/shared/src/index.ts` (wird von beiden Apps bereits importiert, z. B.
|
||||||
|
`desktop.service.ts`, `apps/web/src/lib/app-version.ts`) kommt:
|
||||||
|
```ts
|
||||||
|
export const WIDGET_TYPES = ['clock','search','calendar','note','calculator','favorites','stopwatch','picture-frame','xframe'] as const;
|
||||||
|
export type WidgetType = (typeof WIDGET_TYPES)[number];
|
||||||
|
/** Kachel → Modul-Slug; eine Kachel ohne Eintrag ist immer sichtbar. */
|
||||||
|
export const WIDGET_MODULE_SLUGS: Partial<Record<WidgetType, string>> = {};
|
||||||
|
```
|
||||||
|
`create-widget.dto.ts` validiert mit `@IsIn([...WIDGET_TYPES])`, das Frontend
|
||||||
|
leitet `WidgetType` von dort ab. `widget-module-map.ts` behaelt seine
|
||||||
|
oeffentliche Funktion `getModuleSlugForWidgetType()`, liest aber
|
||||||
|
`WIDGET_MODULE_SLUGS` aus `@tessera/shared` statt einer eigenen Kopie
|
||||||
|
(Kommentar: eine Tabelle fuer beide Seiten, damit Katalogfilter und
|
||||||
|
Server-Filter nicht auseinanderlaufen).
|
||||||
|
2. **Eine Anmeldestelle je Kachel.** Statt neun `wireXWidget()`-Funktionen mit je
|
||||||
|
eigenem Bool-Flag ein generisches `registerWidget(type, component)` in
|
||||||
|
`widget-registry.tsx`; `(portal)/page.tsx` ruft es je Kachel einmal auf (die
|
||||||
|
Datei bleibt die Stelle, an der die Komponenten importiert werden — der
|
||||||
|
Zirkelimport-Grund aus dem Bestandskommentar gilt weiter, also NICHT die
|
||||||
|
Komponenten direkt in der Registry importieren). Mehrfachanmeldung desselben
|
||||||
|
Typs ist ein No-Op (wie die bisherigen Flags); Anmeldung eines unbekannten
|
||||||
|
Typs wirft in der Entwicklung und wird in der Produktion ignoriert.
|
||||||
|
3. **`WIDGET_REGISTRY` bekommt `moduleSlug?: string`** je Eintrag, befuellt aus
|
||||||
|
`WIDGET_MODULE_SLUGS`. Heute bleibt es fuer alle neun Kacheln leer.
|
||||||
|
4. **Der Katalog leitet seine Liste aus der Registry ab** (`Object.keys` in der
|
||||||
|
Reihenfolge der Registry-Definition, die heutige Reihenfolge bleibt erhalten —
|
||||||
|
Test darauf) und **filtert nach Modulzugriff**: `widget-catalog-modal.tsx`
|
||||||
|
bekommt eine Liste der zugaenglichen Modul-Slugs als Prop von der Seite, die
|
||||||
|
sie ueber den vorhandenen Weg `/modules/active` holt (Muster
|
||||||
|
`apps/web/src/components/layout/sidebar.tsx` — dort wird genau dieser Endpunkt
|
||||||
|
schon gefetcht; dieselbe Hilfsfunktion nutzen, nicht neu bauen). Eine Kachel
|
||||||
|
ohne `moduleSlug` ist immer sichtbar; eine mit `moduleSlug` nur, wenn der Slug
|
||||||
|
in der Liste steht. Schlaegt der Abruf fehl, werden Kacheln MIT `moduleSlug`
|
||||||
|
ausgeblendet (fail-closed, wie serverseitig).
|
||||||
|
5. **Gesperrte/unbekannte Kachel erklaert sich.** `widget-wrapper.tsx` rendert
|
||||||
|
heute nichts, wenn `definition?.component` fehlt. Neu: ein zentrierter grauer
|
||||||
|
Hinweistext `widgets.unavailable` („Diese Kachel steht nicht zur Verfügung —
|
||||||
|
das zugehörige Modul ist nicht freigegeben.") in de und en. Der Fall tritt
|
||||||
|
erst mit Proxmox real auf, ist aber ab jetzt abgedeckt.
|
||||||
|
6. **Verhalten der neun vorhandenen Kacheln aendert sich NICHT.** Gleiche Namen,
|
||||||
|
gleiche Reihenfolge im Katalog, gleiche Groessenvorgaben, gleiche Einstellungen.
|
||||||
|
Der Einstellungs-Zweig je Typ in `widget-settings-panel.tsx` bleibt wie er ist —
|
||||||
|
den generisch zu machen waere ein eigener Umbau und gehoert NICHT in diese
|
||||||
|
Aufgabe (im SUMMARY als bewusst offen gelassen nennen).
|
||||||
|
7. Keine neuen Abhaengigkeiten. Keine Aenderung an der Datenbank.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Aufgabe 1: Typliste nach @tessera/shared, generische Anmeldung, Katalog aus der Registry</name>
|
||||||
|
<files>packages/shared/src/index.ts, apps/api/src/dashboard/dto/create-widget.dto.ts, apps/api/src/dashboard/widget-module-map.ts, apps/api/src/dashboard/widget-module-map.spec.ts, apps/web/src/components/dashboard/widget-registry.tsx, apps/web/src/components/dashboard/widget-registry.test.tsx, apps/web/src/app/(portal)/page.tsx, apps/web/src/app/(portal)/page.test.tsx, apps/web/src/components/dashboard/widget-catalog-modal.tsx, apps/web/src/components/dashboard/widget-catalog-modal.test.tsx</files>
|
||||||
|
<action>
|
||||||
|
Entscheidungen 1-4 umsetzen. Reihenfolge: shared zuerst (beide Apps bauen dagegen), dann API-DTO und `widget-module-map.ts`, dann Registry + `registerWidget`, dann `page.tsx`, zuletzt der Katalog.
|
||||||
|
Tests zuerst anpassen/ergaenzen, wo sie die alten Namen festhalten (`widget-registry.test.tsx` prueft heute die Typliste und die Constraints-Tabelle; `widget-catalog-modal.test.tsx` die Eintraege). Neu mindestens: Katalogreihenfolge entspricht der Registry-Reihenfolge; eine Kachel mit `moduleSlug` fehlt im Katalog, wenn der Slug nicht in den zugaenglichen Modulen steht, und erscheint, wenn doch; fehlgeschlagener Modulabruf blendet Kacheln mit `moduleSlug` aus; `registerWidget` ist idempotent; `WIDGET_TYPES` aus shared und die Registry-Schluessel sind deckungsgleich (ein Test, der kuenftig jede vergessene Stelle faengt).
|
||||||
|
Fuer den Katalog-Test eine Kachel mit `moduleSlug` brauchen, ohne eine echte zu erfinden: die Registry im Test per Hilfsfunktion um einen Testeintrag erweitern ODER den Filter als reine Funktion `visibleWidgetTypes(registry, accessibleSlugs | null)` auslagern und diese direkt testen — die reine Funktion ist vorzuziehen (Muster `picture-frame-config.ts`).
|
||||||
|
Commit: `refactor(quick-260922-m1h): Widget-Typen an einer Stelle, Katalog aus der Registry, Kachel kennt ihr Modul`
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run src/components/dashboard "src/app/(portal)/page.test.tsx" && pnpm --filter @tessera/api exec vitest run src/dashboard && pnpm type-check && pnpm lint</automated>
|
||||||
|
</verify>
|
||||||
|
<done>`WIDGET_TYPES`/`WidgetType`/`WIDGET_MODULE_SLUGS` stehen in `packages/shared`; API-DTO und Web leiten davon ab; genau EINE `registerWidget`-Funktion (kein `wireXWidget` mehr); Katalogliste kommt aus der Registry (keine zweite Liste); Deckungsgleichheits-Test vorhanden und gruen. Alle bestehenden Tests gruen, Reihenfolge und Namen der neun Kacheln unveraendert.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Aufgabe 2: Gesperrte Kachel erklaert sich, Uebersetzungen, Changelog, Entwicklerdoku</name>
|
||||||
|
<files>apps/web/src/components/dashboard/widgets/widget-wrapper.tsx, apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json, CHANGELOG.md, docs/anleitung-entwicklung.md</files>
|
||||||
|
<action>
|
||||||
|
Entscheidung 5 umsetzen (Hinweistext statt leerer Kachel, Test dafuer), Schluessel `widgets.unavailable` in beiden Sprachdateien.
|
||||||
|
`docs/anleitung-entwicklung.md`: den vorhandenen Modul-Walkthrough (Abschnitt um Zeile 372-400) um einen kurzen Abschnitt „Eine Kachel zum Modul" ergaenzen — welche drei Stellen es NACH diesem Umbau noch sind (Komponente schreiben, `registerWidget` in `page.tsx`, Eintrag in `WIDGET_TYPES` + optional `WIDGET_MODULE_SLUGS` in `packages/shared`, plus Uebersetzungen und Groessenvorgaben) und dass eine Kachel mit `moduleSlug` automatisch aus Katalog und Dashboard verschwindet, wenn das Modul fehlt.
|
||||||
|
CHANGELOG unter „Unveröffentlicht → Geändert": „Dashboard: Kacheln, die zu einem Modul gehören, erscheinen nur noch für Benutzer, die dieses Modul nutzen dürfen; eine nicht mehr freigegebene Kachel erklärt das jetzt, statt leer zu bleiben" (Stichpunkt, kein Fliesstext).
|
||||||
|
Volle Tore am Ende.
|
||||||
|
Commit: `docs(quick-260922-m1h): Hinweis bei gesperrter Kachel, Changelog und Entwicklerdoku`
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run src/components/dashboard src/messages && pnpm type-check && pnpm lint && pnpm --filter @tessera/api test && pnpm --filter @tessera/web test</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Unbekannter/gesperrter Typ zeigt den Hinweistext (Test); beide Sprachdateien tragen den Schluessel; Changelog-Zeile steht; Entwicklerdoku nennt die verbliebenen Schritte; alle Tore gruen; genau zwei Commits mit Scope `quick-260922-m1h`.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
## Hinweise fuer den Executor
|
||||||
|
|
||||||
|
- HEAD ist `ee2b025`, Arbeitsbaum sauber, Zweig `main`. Version 1.3.0 wurde heute freigegeben; dieser Umbau geht in die naechste Freigabe. Zweig `live` und Tags NICHT anfassen.
|
||||||
|
- `packages/shared` wird von beiden Apps importiert (`@tessera/shared`); pruefen, ob ein Build-Schritt noetig ist (`pnpm --filter @tessera/shared build`?) — turbo erledigt das ueblicherweise, im Zweifel `pnpm build` fuer shared vor dem Typecheck.
|
||||||
|
- Qualitaetsregeln: keine neue `any`, `as unknown as` api 27 / web 6 unveraendert, keine `!`, kein `biome-ignore`, web-Warnungen bleiben 53, api 74.
|
||||||
|
- Commits: Conventional Commits, Scope `quick-260922-m1h`, deutscher Betreff im Stil von `git log --oneline -15`, Commit-Body endet mit
|
||||||
|
`Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>`
|
||||||
|
- `.planning/**` NICHT committen.
|
||||||
|
- Testserver nicht anfassen. Lokaler Docker-Stack laeuft, nicht noetig fuer diese Aufgabe.
|
||||||
|
- SUMMARY nach `/home/vicolab/projects/tessera-ctl/.planning/quick/260922-m1h-dashboard-widgets-ein-modul-bringt-seine/260922-m1h-SUMMARY.md` (`status: complete`), mit: was jetzt noch zu tun ist, um eine Modul-Kachel hinzuzufuegen (die kurze Liste), Abweichungen, Zahlen, und einer kurzen Browser-Pruefliste fuer mich.
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
ASVS 1, block on high.
|
||||||
|
|
||||||
|
| ID | Bedrohung | Schwere | Disposition |
|
||||||
|
|---|---|---|---|
|
||||||
|
| T-M1H-01 | Katalogfilter clientseitig = Umgehung moeglich (Kachel per API trotzdem anlegen) | medium | Der Katalogfilter ist Komfort, die Durchsetzung bleibt serverseitig in `dashboard.service.ts` (`getWidgets()` filtert fail-closed) und im Modul-Guard der jeweiligen Daten-Endpunkte. Im Code so kommentieren. Akzeptiert. |
|
||||||
|
| T-M1H-02 | Kachel eines gesperrten Moduls zeigt weiter Daten | high | Daten holt jede Kachel ueber ihre eigenen Modul-Endpunkte, die `@UseModule(slug)` tragen muessen — fuer Proxmox in der naechsten Aufgabe verbindlich. Diese Aufgabe aendert daran nichts und schwaecht nichts ab. |
|
||||||
|
| T-M1H-03 | Typliste in `packages/shared` als neue Vertrauensgrenze | low | Reine Konstantenliste, keine Laufzeitdaten; die API validiert weiterhin mit `@IsIn` gegen genau diese Liste. Mitigiert. |
|
||||||
|
| T-M1H-04 | Fehlender Modulabruf oeffnet den Katalog | medium | Fail-closed: bei Fehler werden Kacheln MIT `moduleSlug` ausgeblendet (Entscheidung 4), Test dafuer. Mitigiert. |
|
||||||
|
</threat_model>
|
||||||
+215
@@ -0,0 +1,215 @@
|
|||||||
|
---
|
||||||
|
phase: quick-260922-m1h
|
||||||
|
plan: 01
|
||||||
|
subsystem: apps/web/src/components/dashboard
|
||||||
|
tags: [refactor, dashboard, widgets, module-access]
|
||||||
|
status: complete
|
||||||
|
requires: []
|
||||||
|
provides:
|
||||||
|
- "WIDGET_TYPES/WidgetType/WIDGET_MODULE_SLUGS als geteilte Quelle in packages/shared"
|
||||||
|
- "registerWidget() als einzige Anmeldestelle je Kachel"
|
||||||
|
- "visibleWidgetTypes() — Katalogfilter nach Modulzugriff, fail-closed"
|
||||||
|
affects:
|
||||||
|
- apps/api/src/dashboard
|
||||||
|
- apps/web/src/app/(portal)/page.tsx
|
||||||
|
tech-stack:
|
||||||
|
added:
|
||||||
|
- "apps/web haengt jetzt auf @tessera/shared (workspace:*)"
|
||||||
|
patterns:
|
||||||
|
- "erster Laufzeit-Import aus @tessera/shared (bisher nur import type)"
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- apps/api/src/dashboard/widget-module-map.spec.ts
|
||||||
|
modified:
|
||||||
|
- packages/shared/src/index.ts
|
||||||
|
- apps/web/src/components/dashboard/widget-registry.tsx
|
||||||
|
- apps/web/src/components/dashboard/widget-catalog-modal.tsx
|
||||||
|
- apps/web/src/components/dashboard/widgets/widget-wrapper.tsx
|
||||||
|
- apps/web/src/app/(portal)/page.tsx
|
||||||
|
- apps/api/src/dashboard/dto/create-widget.dto.ts
|
||||||
|
- apps/api/src/dashboard/widget-module-map.ts
|
||||||
|
decisions:
|
||||||
|
- "Typliste als Laufzeit-Konstante in packages/shared statt gespiegelter Kopien — traegt, weil Node 24 rohes TypeScript per Type-Stripping laedt"
|
||||||
|
- "apps/web bekommt die Abhaengigkeit auf @tessera/shared; die frueher dokumentierte Gegenbegruendung war ueberholt"
|
||||||
|
- "Katalogfilter als reine Funktion visibleWidgetTypes(registry, slugs|null) statt Logik im Dialog"
|
||||||
|
metrics:
|
||||||
|
duration: "~70 min"
|
||||||
|
completed: 2026-09-22
|
||||||
|
actuals:
|
||||||
|
tokens: 21000
|
||||||
|
tasks: 2
|
||||||
|
commits: 2
|
||||||
|
plan_head_before: ee2b025
|
||||||
|
---
|
||||||
|
|
||||||
|
# Quick-Aufgabe 260922-m1h: Ein Modul bringt seine Dashboard-Kachel selbst mit — Zusammenfassung
|
||||||
|
|
||||||
|
Die Kachel-Typliste stand an sieben Stellen; sie steht jetzt an einer. Der
|
||||||
|
Katalog fuehrt keine zweite Liste mehr und blendet Kacheln gesperrter Module
|
||||||
|
aus, eine Kachel ohne Bauteil erklaert sich mit einem Satz statt leer zu
|
||||||
|
bleiben. Die neun vorhandenen Kacheln verhalten sich unveraendert.
|
||||||
|
|
||||||
|
## So fuegt man kuenftig eine Modul-Kachel hinzu
|
||||||
|
|
||||||
|
Vorher sieben Stellen, jetzt drei (plus das Uebliche an Text und Maßen):
|
||||||
|
|
||||||
|
1. **Kachel-Komponente schreiben** — `apps/web/src/components/dashboard/widgets/<name>-widget.tsx`,
|
||||||
|
nimmt `WidgetProps` (`instanceId`, `config`, `isEditMode`).
|
||||||
|
2. **Typ eintragen** — in `WIDGET_TYPES` in `packages/shared/src/index.ts`. Gehoert die Kachel zu
|
||||||
|
einem Modul, zusaetzlich `WIDGET_MODULE_SLUGS['<typ>'] = '<modul-slug>'` in derselben Datei.
|
||||||
|
Das ist die einzige Liste — die API validiert per `@IsIn` gegen genau sie.
|
||||||
|
3. **Anmelden** — `registerWidget('<typ>', <Name>Widget)` in `apps/web/src/app/(portal)/page.tsx`.
|
||||||
|
|
||||||
|
Dazu wie bei jeder Oberflaeche: Uebersetzungsschluessel `<typ>.name` und `<typ>.description` unter
|
||||||
|
`widgets` in **de.json und en.json**, ein Inline-SVG-Symbol und die Groessenvorgaben in
|
||||||
|
`WIDGET_CONSTRAINTS` — Symbol und Maße in `widget-registry.tsx`.
|
||||||
|
|
||||||
|
Eine Kachel mit `moduleSlug` verschwindet danach **von selbst** aus Katalog und Dashboard, wenn der
|
||||||
|
Benutzer das Modul nicht nutzen darf. Vergisst man eine der drei Stellen, schlaegt der
|
||||||
|
Deckungsgleichheits-Test in `widget-registry.test.tsx` fehl, statt dass die Kachel im Katalog fehlt
|
||||||
|
oder die API mit 400 antwortet.
|
||||||
|
|
||||||
|
Dieselbe Liste steht als Abschnitt „Eine Kachel zum Modul" in
|
||||||
|
`docs/anleitung-entwicklung.md`.
|
||||||
|
|
||||||
|
## Was gebaut wurde
|
||||||
|
|
||||||
|
**Aufgabe 1 — `56c07c3`** (`refactor`)
|
||||||
|
|
||||||
|
- `packages/shared/src/index.ts`: `WIDGET_TYPES`, `WidgetType`, `WIDGET_MODULE_SLUGS`.
|
||||||
|
- `create-widget.dto.ts`: `@IsIn([...WIDGET_TYPES])` statt handgepflegter Liste.
|
||||||
|
- `widget-module-map.ts`: liest `WIDGET_MODULE_SLUGS` statt einer eigenen Kopie; die oeffentliche
|
||||||
|
Funktion `getModuleSlugForWidgetType()` ist unveraendert, damit `dashboard.service.spec.ts`
|
||||||
|
sie weiter mocken kann.
|
||||||
|
- `widget-registry.tsx`: neun `wireXWidget()` → ein `registerWidget()` (idempotent; unbekannter Typ
|
||||||
|
wirft in der Entwicklung, wird in der Produktion ignoriert). `WidgetDefinition` traegt
|
||||||
|
`moduleSlug?`. Neue reine Funktion `visibleWidgetTypes(registry, slugs|null)`.
|
||||||
|
- `widget-catalog-modal.tsx`: Liste kommt aus der Registry (Reihenfolge erhalten, Test darauf),
|
||||||
|
gefiltert nach Modulzugriff; neue Prop `accessibleModuleSlugs`.
|
||||||
|
- `(portal)/page.tsx`: neun `registerWidget`-Aufrufe; holt `/modules/active` im Muster der
|
||||||
|
Seitenleiste (`credentials: 'include'`, Fehler still) und reicht die Slugs an den Katalog durch.
|
||||||
|
|
||||||
|
**Aufgabe 2 — `8be0725`** (`docs`)
|
||||||
|
|
||||||
|
- `widget-wrapper.tsx`: Kachel ohne Bauteil zeigt `widgets.unavailable` zentriert und grau statt des
|
||||||
|
rohen Typnamens; Schluessel in de.json und en.json.
|
||||||
|
- Changelog-Stichpunkt unter „Unveroeffentlicht → Geaendert"; Entwicklerdoku-Abschnitt.
|
||||||
|
|
||||||
|
## Abweichungen vom Plan
|
||||||
|
|
||||||
|
**1. [Rule 3 — blockierend] Die Planannahme „apps/web importiert @tessera/shared bereits" war falsch**
|
||||||
|
|
||||||
|
- **Gefunden bei:** Aufgabe 1, vor der ersten Zeile Code.
|
||||||
|
- **Befund:** `apps/web` hatte **keine** Abhaengigkeit auf `@tessera/shared`. Zwei Kommentare
|
||||||
|
(`lib/app-version.ts`, `lib/desktop.ts`) dokumentierten das sogar ausdruecklich als Absicht und
|
||||||
|
begruendeten damit gespiegelte Typen. Ohne Abhaengigkeit ist Entscheidung 1 des Plans nicht
|
||||||
|
umsetzbar. Zudem waren **alle** bisherigen `@tessera/shared`-Importe in `apps/api` reine
|
||||||
|
`import type` — die Typliste ist aber ein Laufzeitwert.
|
||||||
|
- **Geprueft statt vermutet:**
|
||||||
|
- `nest build` mit einem Laufzeit-Import: laeuft; das Ergebnis laedt `@tessera/shared` im
|
||||||
|
fertigen `dist` tatsaechlich (nachgestellt, 9 Typen).
|
||||||
|
- `packages/shared` liefert rohes TypeScript ohne Bauschritt — in `node:24-alpine` direkt
|
||||||
|
geprueft: Node 24 laedt es per nativem Type-Stripping (`OK [ 'clock', 'xframe' ] {}`).
|
||||||
|
- Die alte Gegenbegruendung ist ueberholt: der Web-Dockerfile kopiert `packages/shared` in
|
||||||
|
deps- **und** builder-Stufe bereits. Es aendert sich nur das Lockfile (3 Zeilen).
|
||||||
|
- `pnpm --filter @tessera/web build` laeuft durch — ohne `transpilePackages`.
|
||||||
|
- **Umsetzung:** `@tessera/shared: workspace:*` in `apps/web/package.json`. Die beiden Kommentare,
|
||||||
|
deren Begruendung dadurch unwahr wurde, sagen jetzt den aktuellen Stand; die Typ-Spiegel selbst
|
||||||
|
blieben bewusst unangetastet (nicht Teil dieser Aufgabe).
|
||||||
|
- **Nebenwirkung fuer die Zukunft:** `packages/shared/src/index.ts` darf nur noch loeschbare Syntax
|
||||||
|
enthalten — kein `enum`, kein `namespace`, keine Parameter-Eigenschaften. Steht als Warnung in
|
||||||
|
der Datei.
|
||||||
|
|
||||||
|
**2. [Abweichung vom Auftrag des Orchestrators] Keine gemeinsame Hilfsfunktion fuer `/modules/active`**
|
||||||
|
|
||||||
|
Der Auftrag nannte „dieselbe Hilfsfunktion wie die Seitenleiste". Eine solche gibt es nicht: die
|
||||||
|
Seitenleiste hat einen eingebauten `fetch`, und `lib/api.ts#getActiveModules` ist serverseitig
|
||||||
|
(Cookie-Header, kein `credentials`). Die Dashboard-Seite benutzt daher dasselbe **Muster** wie die
|
||||||
|
Seitenleiste. Eine Hilfsfunktion herauszuloesen haette `sidebar.tsx` angefasst — ausserhalb dieser
|
||||||
|
Aufgabe.
|
||||||
|
|
||||||
|
## Bewusst offen gelassen
|
||||||
|
|
||||||
|
- **`widget-settings-panel.tsx`** — der Einstellungs-Zweig je Typ bleibt wie er war. Den generisch
|
||||||
|
zu machen ist ein eigener Umbau (so im Plan festgelegt). Die Datei wurde nicht angefasst.
|
||||||
|
- **Die Typ-Spiegel** in `lib/app-version.ts` und `lib/desktop.ts` koennten jetzt echte Importe
|
||||||
|
werden. Nicht gemacht, nur die Kommentare richtiggestellt.
|
||||||
|
- **Katalog aktualisiert sich nicht live**, wenn im Marketplace gerade ein Modul freigeschaltet
|
||||||
|
wird — die Seitenleiste tut das ueber `sidebarRefreshKey`, die Dashboard-Seite holt die Liste nur
|
||||||
|
beim Aufbau. Heute ohne Wirkung (keine Kachel hat einen `moduleSlug`); mit Proxmox reicht ein
|
||||||
|
Neuladen der Seite. Bewusst so, weil der Auffrisch-Ausloeser einen `biome-ignore` erzwungen
|
||||||
|
haette, den die Qualitaetsregeln dieser Aufgabe ausschliessen.
|
||||||
|
|
||||||
|
## Keine Stubs
|
||||||
|
|
||||||
|
Es wurden keine Platzhalter, leeren Rueckgaben oder „coming soon"-Texte eingebaut.
|
||||||
|
`WIDGET_MODULE_SLUGS` ist leer — das ist kein Stub, sondern der korrekte Zustand: alle neun Kacheln
|
||||||
|
sind Plattform-Kacheln. Die erste Modul-Kachel (Proxmox) traegt sich dort ein.
|
||||||
|
|
||||||
|
## Bedrohungsmodell
|
||||||
|
|
||||||
|
| ID | Stand |
|
||||||
|
|---|---|
|
||||||
|
| T-M1H-01 | Akzeptiert wie geplant. Der Katalogfilter ist Komfort; im Code an drei Stellen so kommentiert. Durchsetzung bleibt `DashboardService.getWidgets` (unveraendert, 85 Tests gruen). |
|
||||||
|
| T-M1H-02 | Unveraendert — diese Aufgabe schwaecht nichts ab. Fuer Proxmox bleibt `@UseModule(slug)` verbindlich. |
|
||||||
|
| T-M1H-03 | Mitigiert. Reine Konstantenliste, keine Laufzeitdaten; die API validiert weiterhin `@IsIn` gegen genau diese Liste — jetzt nachweislich (Test validiert alle neun Typen und lehnt einen unbekannten ab). |
|
||||||
|
| T-M1H-04 | Mitigiert. `accessibleModuleSlugs === null` blendet Kacheln MIT `moduleSlug` aus; Test im Katalog und in `visibleWidgetTypes`. |
|
||||||
|
|
||||||
|
## Zahlen
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Commits | 2 (`56c07c3`, `8be0725`), Basis `ee2b025` |
|
||||||
|
| Dateien geaendert | 20 (1 neu) |
|
||||||
|
| Zeilen | +610 / −150 (gemessen: `git diff --shortstat ee2b025 HEAD`) |
|
||||||
|
| Neue Tests | 29 (Registry 9, Katalog 3, Seite 2, `widget-module-map.spec.ts` 14, Wrapper 1) |
|
||||||
|
| API-Tests | 1202 gruen (76 Dateien) |
|
||||||
|
| Web-Tests | 659 gruen (81 Dateien) |
|
||||||
|
| type-check | sauber (4 Pakete) |
|
||||||
|
| lint | api 74 / web 53 Warnungen — **unveraendert** zur Basis |
|
||||||
|
| `as unknown as` | api 27 / web 6 — **unveraendert** |
|
||||||
|
| neue `any` / `!` / `biome-ignore` | 0 / 0 / 0 |
|
||||||
|
| Next.js-Produktionsbau | laeuft |
|
||||||
|
| `nest build` | laeuft |
|
||||||
|
|
||||||
|
## Browser-Pruefliste
|
||||||
|
|
||||||
|
Der Umbau ist verhaltensneutral — die Pruefung soll vor allem bestaetigen, dass **nichts** anders
|
||||||
|
aussieht. Lokalen Stack neu bauen (`--build`), dann im Portal:
|
||||||
|
|
||||||
|
1. **Dashboard oeffnen.** Alle bisherigen Kacheln stehen an ihrem Platz und funktionieren wie
|
||||||
|
vorher (Uhr laeuft, Kalender zeigt Termine, Bilderrahmen wechselt, XFrame laedt).
|
||||||
|
2. **Stift → „Widget hinzufuegen".** Der Katalog zeigt **neun** Kacheln in genau dieser Reihenfolge:
|
||||||
|
Uhr, Suchleiste, Kalender, Notiz, Taschenrechner, Favoriten, Stoppuhr, Bilderrahmen, XFrame.
|
||||||
|
Namen und Beschreibungen unveraendert.
|
||||||
|
3. **Eine Kachel anlegen** (z. B. Stoppuhr) — sie erscheint, laesst sich ziehen, vergroessern und
|
||||||
|
wieder entfernen. Kein 400-Fehler.
|
||||||
|
4. **Groessen pruefen:** eine frisch angelegte Kachel hat dieselbe Startgroesse wie frueher, und
|
||||||
|
sie laesst sich nicht kleiner ziehen als bisher.
|
||||||
|
5. **Einstellungen → Dashboard:** die Einstellungen je Kachel sind unveraendert da (dieser Bereich
|
||||||
|
wurde bewusst nicht angefasst).
|
||||||
|
6. **Sprache auf Englisch umstellen** — der Katalog bleibt vollstaendig, keine rohen Schluessel wie
|
||||||
|
`clock.name` sichtbar.
|
||||||
|
7. *(optional, zeigt das Neue)* Der Hinweis bei einer nicht verfuegbaren Kachel laesst sich heute
|
||||||
|
nur kuenstlich ausloesen — er greift erst mit der ersten Modul-Kachel. Wer ihn sehen will: in der
|
||||||
|
Datenbank den `widgetType` einer vorhandenen Kachel auf `proxmox` setzen und die Seite neu laden;
|
||||||
|
die Kachel zeigt dann „Diese Kachel steht nicht zur Verfuegung — das zugehoerige Modul ist nicht
|
||||||
|
freigegeben." statt leer zu bleiben. Danach zuruecksetzen.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
- `apps/api/src/dashboard/widget-module-map.spec.ts` vorhanden.
|
||||||
|
- Commits `56c07c3` und `8be0725` in `git log` gefunden.
|
||||||
|
- `git diff --diff-filter=D ee2b025..HEAD` — keine geloeschten Dateien.
|
||||||
|
- `git rev-list --count ee2b025..HEAD` = 2, gemessen.
|
||||||
|
- `.planning/**` nicht committet.
|
||||||
|
|
||||||
|
## Rundgang durch den Orchestrator (22.09.2026, lokaler Stack aus 8be0725)
|
||||||
|
|
||||||
|
Bestanden, keine Abweichung zum Stand vorher:
|
||||||
|
|
||||||
|
- Dashboard zeigt die bestehenden Kacheln (Kalender, Notizen, Favoriten, Bilderrahmen, XFrame) unveraendert, keine Konsolenfehler.
|
||||||
|
- Katalog zeigt **neun** Kacheln in der alten Reihenfolge: Uhr, Suchleiste, Kalender, Notizen, Taschenrechner, Favoriten, Stoppuhr, Bilderrahmen, XFrame; Namen und Beschreibungen unveraendert, keine rohen Schluessel.
|
||||||
|
- Stoppuhr angelegt → erscheint (396x160 px), wird gespeichert (`stopwatch` in `GET /dashboard/widgets`), kein 400; danach wieder entfernt, Liste sauber.
|
||||||
|
- `/modules/active` wird beim Seitenaufbau abgerufen (4x 200) — der Katalogfilter hat seine Datenquelle.
|
||||||
|
- Der Hinweis bei nicht verfuegbarer Kachel liess sich nicht echt ausloesen (es gibt noch keine Modul-Kachel); er ist durch den Test in `widget-wrapper.test.tsx` gedeckt und greift mit Proxmox.
|
||||||
Reference in New Issue
Block a user