12 KiB
12 KiB
phase, plan, type, autonomous, subsystem, requirements
| phase | plan | type | autonomous | subsystem | requirements |
|---|---|---|---|---|---|
| quick-260922-m1h | 01 | refactor | true | apps/web/src/components/dashboard |
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 eigenewireXWidget()je Typ, der Aufruf in(portal)/page.tsx, die ZWEITE ListeWIDGET_TYPESinwidget-catalog-modal.tsxund die@IsIn-Whitelist inapps/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 vondashboard.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)
- 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: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.tsvalidiert mit@IsIn([...WIDGET_TYPES]), das Frontend leitetWidgetTypevon dort ab.widget-module-map.tsbehaelt seine oeffentliche FunktiongetModuleSlugForWidgetType(), liest aberWIDGET_MODULE_SLUGSaus@tessera/sharedstatt einer eigenen Kopie (Kommentar: eine Tabelle fuer beide Seiten, damit Katalogfilter und Server-Filter nicht auseinanderlaufen). - Eine Anmeldestelle je Kachel. Statt neun
wireXWidget()-Funktionen mit je eigenem Bool-Flag ein generischesregisterWidget(type, component)inwidget-registry.tsx;(portal)/page.tsxruft 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. WIDGET_REGISTRYbekommtmoduleSlug?: stringje Eintrag, befuellt ausWIDGET_MODULE_SLUGS. Heute bleibt es fuer alle neun Kacheln leer.- Der Katalog leitet seine Liste aus der Registry ab (
Object.keysin der Reihenfolge der Registry-Definition, die heutige Reihenfolge bleibt erhalten — Test darauf) und filtert nach Modulzugriff:widget-catalog-modal.tsxbekommt eine Liste der zugaenglichen Modul-Slugs als Prop von der Seite, die sie ueber den vorhandenen Weg/modules/activeholt (Musterapps/web/src/components/layout/sidebar.tsx— dort wird genau dieser Endpunkt schon gefetcht; dieselbe Hilfsfunktion nutzen, nicht neu bauen). Eine Kachel ohnemoduleSlugist immer sichtbar; eine mitmoduleSlugnur, wenn der Slug in der Liste steht. Schlaegt der Abruf fehl, werden Kacheln MITmoduleSlugausgeblendet (fail-closed, wie serverseitig). - Gesperrte/unbekannte Kachel erklaert sich.
widget-wrapper.tsxrendert heute nichts, wenndefinition?.componentfehlt. Neu: ein zentrierter grauer Hinweistextwidgets.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. - 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.tsxbleibt wie er ist — den generisch zu machen waere ein eigener Umbau und gehoert NICHT in diese Aufgabe (im SUMMARY als bewusst offen gelassen nennen). - Keine neuen Abhaengigkeiten. Keine Aenderung an der Datenbank.
Aufgaben
Aufgabe 1: Typliste nach @tessera/shared, generische Anmeldung, Katalog aus der Registry 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 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` 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 `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. Aufgabe 2: Gesperrte Kachel erklaert sich, Uebersetzungen, Changelog, Entwicklerdoku 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 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` 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 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`.Hinweise fuer den Executor
- HEAD ist
ee2b025, Arbeitsbaum sauber, Zweigmain. Version 1.3.0 wurde heute freigegeben; dieser Umbau geht in die naechste Freigabe. Zweigliveund Tags NICHT anfassen. packages/sharedwird von beiden Apps importiert (@tessera/shared); pruefen, ob ein Build-Schritt noetig ist (pnpm --filter @tessera/shared build?) — turbo erledigt das ueblicherweise, im Zweifelpnpm buildfuer shared vor dem Typecheck.- Qualitaetsregeln: keine neue
any,as unknown asapi 27 / web 6 unveraendert, keine!, keinbiome-ignore, web-Warnungen bleiben 53, api 74. - Commits: Conventional Commits, Scope
quick-260922-m1h, deutscher Betreff im Stil vongit log --oneline -15, Commit-Body endet mitCo-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> |