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:
+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