Files
tessera-ctl/.planning/quick/260922-m1h-dashboard-widgets-ein-modul-bringt-seine/260922-m1h-SUMMARY.md
T
schalli 1315f370a9
Tessera CI/CD / Lint & Type Check (push) Successful in 46s
Tessera CI/CD / Tests (push) Successful in 1m15s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m7s
docs(quick-260922-m1h): Akte - Widget-Aufraeumen, Rundgang verhaltensneutral bestaetigt
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:12:17 +02:00

12 KiB
Raw Blame History

phase, plan, subsystem, tags, status, requires, provides, affects, tech-stack, key-files, decisions, metrics, actuals, plan_head_before
phase plan subsystem tags status requires provides affects tech-stack key-files decisions metrics actuals plan_head_before
quick-260922-m1h 01 apps/web/src/components/dashboard
refactor
dashboard
widgets
module-access
complete
WIDGET_TYPES/WidgetType/WIDGET_MODULE_SLUGS als geteilte Quelle in packages/shared
registerWidget() als einzige Anmeldestelle je Kachel
visibleWidgetTypes() — Katalogfilter nach Modulzugriff, fail-closed
apps/api/src/dashboard
apps/web/src/app/(portal)/page.tsx
added patterns
apps/web haengt jetzt auf @tessera/shared (workspace:*)
erster Laufzeit-Import aus @tessera/shared (bisher nur import type)
created modified
apps/api/src/dashboard/widget-module-map.spec.ts
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
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
duration completed
~70 min 2026-09-22
tokens tasks commits
21000 2 2
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.