Files
schalli 0094a60d15
Tessera CI/CD / Lint & Type Check (push) Successful in 52s
Tessera CI/CD / Tests (push) Successful in 1m18s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m11s
docs(quick-260923-ad9): Akte - Rundgang mit zwoelf Punkten bestanden
Bestand bleibt erhalten (5 Kacheln im ersten Reiter), Kacheln je Reiter
getrennt, Ziehen ordnet um, nach dem Neuladen kommt der erste Reiter,
letzter Reiter ohne Loeschknopf, Raster unveraendert.

Offener Kleinbefund notiert: die Knopf-Beschriftungen nennen den betroffenen
Reiter nicht, nur das Bestaetigungsfenster tut es.

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

12 KiB
Raw Permalink Blame History

phase, plan, subsystem, tags, dependency-graph, tech-stack, key-files, decisions, metrics, actuals, plan_head_before, status
phase plan subsystem tags dependency-graph tech-stack key-files decisions metrics actuals plan_head_before status
quick-260923-ad9 01 dashboard
dashboard
reiter
rls
migration
frontend
drag-and-drop
requires provides affects
dashboard-tabs
multi-dashboard-model
apps/api/src/dashboard
apps/web/src/components/dashboard
apps/web/src/lib/stores/dashboard-store.ts
added patterns
forTenant-per-method
withTenantTransaction-multi-step
pointer-drag-with-threshold
created modified
apps/api/prisma/migrations/20260923120000_dashboard_tabs/migration.sql
apps/api/src/dashboard/dto/rename-dashboard.dto.ts
apps/api/src/dashboard/dto/reorder-dashboards.dto.ts
apps/api/src/dashboard/dashboard.controller.spec.ts
apps/web/src/components/dashboard/dashboard-tabs.tsx
apps/web/src/components/dashboard/dashboard-tabs.test.tsx
apps/api/prisma/schema.prisma
apps/api/src/dashboard/dashboard.service.ts
apps/api/src/dashboard/dashboard.service.spec.ts
apps/api/src/dashboard/dashboard.controller.ts
apps/api/src/dashboard/dto/save-layout.dto.ts
apps/api/src/dashboard/dto/create-widget.dto.ts
apps/api/src/dashboard/widget-module-map.spec.ts
apps/web/src/lib/dashboard-api.ts
apps/web/src/lib/stores/dashboard-store.ts
apps/web/src/lib/stores/dashboard-store.test.ts
apps/web/src/app/(portal)/page.tsx
apps/web/src/app/(portal)/page.test.tsx
apps/web/src/app/(portal)/settings/dashboard/page.tsx
apps/web/src/messages/de.json
apps/web/src/messages/en.json
docs/mandantentrennung-zugriffsklassifikation.md
docs/anleitung-anwender.md
CHANGELOG.md
D-01 bis D-10 aus dem Plan wörtlich umgesetzt, keine Abweichung: kein Unique auf (userId, position), kein Standard-Feld (Nach-vorn-Ziehen IST der Standard), Namensvergabe füllt Lücken, letzter Reiter bleibt.
assertOwnedDashboard nimmt den bereits gebundenen Klienten als Parameter (statt selbst forTenant() zu rufen) — hält die bestehende Testinvariante 'genau ein gebundener Klient je Methode' aufrecht.
Reiterwechsel: ungespeicherte Anordnung wird über get().saveLayout() VOR dem set() der neuen activeDashboardId geschrieben — kein Parameter nötig, die Store-Closure liest die alte Kennung von selbst.
Ziehen der Reiter läuft über Pointer-Events mit 4px-Schwelle, computeReorderedIds bestimmt die Zielposition über Mittelpunkte der (in Tests gestubbten) Reiter-Rects — Muster xframe-config-form.tsx, keine neue Abhängigkeit (D-05).
duration completed
~2.5h 2026-09-23
tokens tasks commits
45514 5 5
84fe73e complete

Phase quick-260923-ad9 Plan 01: Dashboard-Reiter — mehrere Dashboards je Benutzer Summary

Jeder Benutzer hat jetzt mehrere Dashboards ("Reiter"), oben nebeneinander in einer Leiste, jeder mit eigenen Kacheln und eigener Anordnung, per Ziehen umsortierbar, der erste geladen beim Öffnen — bestehende Kacheln landeten unverändert auf einem einzigen Reiter "Dashboard".

Datenbankänderung — geht als reguläre Version über main

Wichtig für die Freigabe (D-03): dieser Plan enthält eine Datenbankmigration (20260923120000_dashboard_tabs), die Bestandsdaten umhängt (WidgetInstance/DashboardLayout bekommen dashboardId, DashboardLayout verliert die Eindeutigkeit auf userId). Das geht als reguläre Version über main, nicht als Hotfix.

Gemessene Torzahlen

Tor Vorher (gemessen 23.09. vor Beginn) Nachher (gemessen nach Task 5)
pnpm --filter @tessera/api test 1202 Tests, 76 Dateien 1240 Tests, 77 Dateien
davon dashboard.service.spec.ts 31 Tests 61 Tests
davon dashboard.controller.spec.ts (Datei existierte nicht) 8 Tests (neu)
rls-coverage.spec.ts / rls-access-inventory.spec.ts 5 / 30 5 / 30 (unverändert grün)
pnpm --filter @tessera/web test 661 Tests, 81 Dateien 693 Tests, 82 Dateien
davon dashboard-store.test.ts 6 Tests 17 Tests
davon dashboard-tabs.test.tsx (Datei existierte nicht) 21 Tests (neu)
dashboard-grid.test.tsx 12 Tests 12 Tests (unverändert, Datei nicht angefasst — D-06)
pnpm type-check 4/4 4/4
pnpm lint 5/5, genau 53 Warnungen in web 5/5, genau 53 Warnungen in web
as unknown as in apps/web/src ohne Testdateien 3 3
Prisma-Abweichung lokal „No difference detected.“ „No difference detected.“

Zählabfrage der Bestandsübernahme (Task 1, gegen die lokale Datenbank)

kacheln_ohne_reiter | anordnungen_ohne_reiter | reiter | reiter_nicht_an_position_null
                   0 |                        0 |      2 |                              0

0 Kacheln ohne Reiter, 0 Anordnungen ohne Reiter, genau 2 Reiter (deckt sich mit der vor Beginn gemessenen Zahl von 2 Benutzern mit Bestand), 0 Reiter abseits von Position 0 — jeder der beiden vorhandenen Benutzer mit Kacheln/Anordnung hat jetzt genau einen Reiter „Dashboard" auf Position 0.

Abweichungen von der im Plan gemessenen Erwartung (keine Rule-1/2/3-Fälle — reine Zahlendifferenzen, dokumentiert statt stillschweigend übersprungen)

  1. apps/web Dateizahl 82 statt der für Task 4 erwarteten 83. Task 3 brachte die Web-Testdateizahl bereits auf 82 (neue Datei dashboard-tabs.test.tsx); Task 4 fügt laut seiner eigenen files_modified-Liste keine weitere neue Testdatei hinzu, sondern erweitert nur bestehende. Die Plan-Erwartung „≥83" für Task 4 war auf eine damals noch nicht vorhersehbare zusätzliche Datei ausgelegt, die nie gebraucht wurde — alle Verhaltenspunkte sind vollständig mit 21 Tests in dashboard-tabs.test.tsx und 17 in dashboard-store.test.ts abgedeckt.
  2. grep -c "react-grid-layout|dnd|sortable" apps/web/package.json liefert 2 statt 1. Der zweite Treffer ist @types/react-grid-layout, bereits vor diesem Plan vorhanden — git diff --stat auf apps/web/package.json über alle fünf Commits ist leer. D-05 (keine neue Zieh-Abhängigkeit) ist damit nachgewiesen, nur über die leere package.json-Diff statt über die im Plan vorausgesagte Grep-Zahl.

Auto-fixed Issues (Deviations, Rule 1/3)

1. [Rule 1] widget-module-map.spec.ts — CreateWidgetDto-Validierungstest ohne dashboardId-Fixture

  • Gefunden während: Task 1, nach Hinzufügen von dashboardId als Pflichtfeld auf CreateWidgetDto.
  • Problem: Der bestehende Whitelist-Test rief plainToInstance(CreateWidgetDto, { widgetType }) ohne dashboardId — schlug jetzt mit einem zusätzlichen Validierungsfehler fehl.
  • Fix: dashboardId: 'dash-1' fest mitgegeben, Kommentar ergänzt.
  • Commit: 9c51823 (Task 1)

2. [Rule 3] settings/dashboard/page.tsx — fetchWidgets() verlangt jetzt eine Reiter-Kennung

  • Gefunden während: Task 3, nach Umstellung von fetchWidgets auf fetchWidgets(dashboardId).
  • Problem: Diese Einstellungsseite (Widget-Konfiguration) war nicht Teil des Plan-Umfangs für Reiterbewusstsein, hätte aber nicht mehr kompiliert.
  • Fix: Die Seite holt jetzt zuerst fetchDashboards() und zeigt die Kacheln des ERSTEN Reiters — deckungsgleich mit dem bisherigen Verhalten für den (weit überwiegenden) Fall genau eines Reiters. Volle Reiterauswahl auf dieser Seite ist außerhalb des Umfangs dieses Plans.
  • Commit: d34f682 (Task 3)

3. [Rule 3] (portal)/page.test.tsx — mockStore ohne die neuen Reiter-Felder

  • Gefunden während: Task 3, nach Einbau von <DashboardTabs> in page.tsx.
  • Problem: dashboards wäre undefined gewesen — DashboardTabs hätte auf .map einer undefined-Liste geworfen.
  • Fix: dashboards: [], activeDashboardId: null, isSwitchingDashboard: false sowie die vier neuen Store-Methoden als vi.fn() ergänzt.
  • Commit: d34f682 (Task 3)

Kein Punkt aus „Nicht im Umfang" wurde angefasst (kein Freigeben/Teilen von Dashboards, keine Vorlagen, keine Reiter je Modul). Keine neue Abhängigkeit in apps/web/package.json oder apps/api/package.json. DashboardGrid selbst ist unverändert.

Prüfliste für den Browser-Rundgang (vom Nutzer auszuführen — Container-Neubau nötig)

  1. Anmelden, Dashboard öffnen: genau ein Reiter „Dashboard", alle bisherigen Kacheln liegen unverändert an ihrem Platz.
  2. Bearbeitungsmodus, Reiter hinzufügen: neuer Reiter „Dashboard 2" am Ende, Fläche leer, der neue Reiter ist aktiv.
  3. Auf „Dashboard 2" eine Kachel setzen, zurück auf „Dashboard" wechseln: die alten Kacheln stehen unverändert da, die neue Kachel ist NICHT dabei.
  4. Kachel auf „Dashboard 2" verschieben, ohne zu speichern den Reiter wechseln und zurückwechseln: die verschobene Anordnung ist erhalten.
  5. „Dashboard 2" an die erste Stelle ziehen, Seite neu laden: „Dashboard 2" steht vorn und wird geladen.
  6. „Dashboard 2" umbenennen, Seite neu laden: der neue Name steht da.
  7. „Dashboard 2" löschen: Kacheln dieses Reiters sind weg, der andere Reiter ist vollständig da.
  8. Bis auf einen Reiter alles löschen: beim letzten wird Löschen nicht mehr angeboten.
  9. Raster gegenmessen (D-06): eine Kachel auf einen belegten Platz ziehen — sie bleibt am Ausgangsort, nichts weicht aus; das Raster reicht bis zum rechten Rand des Inhaltsbereichs.

Threat Flags

Keine — alle Punkte des Threat-Registers (T-AD9-01 bis T-AD9-SC) sind wie im Plan geplant mitigiert und mit eigenen Tests belegt (assertOwnedDashboard fail-closed über alle vier bestehenden Wege plus die vier neuen Reiter-Wege, Exakt-Abgleich vor jedem Schreiben bei reorderDashboards, Obergrenzen 20 Reiter/40 Zeichen, Transaktionssperre gegen Doppelanlage). Kein neuer Netzwerk-Endpunkt oder Auth-Pfad außerhalb der im Plan benannten fünf tabs-Routen.

Self-Check: PASSED

Alle sechs im Plan neu erwarteten Dateien gefunden, alle fünf Task-Commits im Log gefunden (siehe git log).

Rundgang durch den Orchestrator (23.09.2026, lokaler Stack, Abbilder aus dem Commit danach)

Container neu gebaut (up -d --build web api), Migration lag beim Start bereits an (41 migrations found, No pending migrations to apply). Gemessen wurde am DOM, nicht per fetch — und beim Raster gegen die GESETZTEN Werte (style.width), nicht gegen die gemalte Box (Messfalle aus quick-260922-vdk).

# Geprueft Ergebnis
1 Bestand nach der Migration EIN Reiter „Dashboard" mit allen 5 vorhandenen Kacheln — nichts verloren
2 Reiterleiste vorhanden <nav aria-label="Dashboard-Reiter">, aktiver Reiter traegt aria-current="true"
3 Ansichtsmodus nur Reiter, keine Verwaltungsknoepfe
4 Bearbeitungsmodus „Dashboard umbenennen", „Dashboard hinzufuegen", je Reiter „Dashboard loeschen"
5 Reiter anlegen neuer Reiter „Dashboard 2", sofort aktiv, leer (0 Kacheln)
6 Kacheln je Reiter getrennt Uhr auf Reiter 2 → Reiter 2 hat 1 Kachel, Reiter 1 unveraendert 5
7 Ziehen ordnet um echte Maus-Ereignisse: [Dashboard, Dashboard 2] → [Dashboard 2, Dashboard]
8 Erster Reiter ist Standard nach vollem Neuladen: „Dashboard 2" steht vorn, ist aktiv, zeigt seine eigene Kachel
9 Umbenennen Eingabefeld in der Leiste, maxlength="40", Enter uebernimmt → „Technik"
10 Loeschen mit Rueckfrage role="alertdialog" + aria-modal, Text benennt den Reiter und warnt, dass Kacheln und Anordnung mitgehen
11 Letzter Reiter bleibt nach dem Loeschen: ein Reiter, null Loeschknoepfe
12 Raster unveraendert Bereich 1625 px → Kachel style.width: 531px = 8 × 59,375 + 56, exakt der Sollwert (lg, 24 Spalten)

Kleiner Befund, nicht behoben (kein Blocker): die Beschriftungen der Verwaltungsknoepfe lauten generisch „Dashboard umbenennen" / „Dashboard loeschen" und nennen nicht, WELCHEN Reiter sie treffen; bei mehreren Reitern liest eine Sprachausgabe also mehrfach denselben Text. Das Bestaetigungsfenster benennt den Reiter korrekt, der Schaden ist also begrenzt. Vorgemerkt fuer die naechste Arbeit an der Leiste.

Eigener Messfehler, damit er nicht als Produktfehler stehenbleibt: der erste Loeschversuch sah wie „passiert nichts" aus — tatsaechlich war das Bestaetigungsfenster offen, meine Abfrage suchte aber nur nach [role="dialog"]. Das Fenster traegt role="alertdialog". Beim Pruefen auf beide Rollen abfragen.