docs(quick-260916-dyv): Nachbesserung abgeschlossen, verifiziert und im Browser bewiesen (Rechner-Minimum korrigiert) — Zusammenfassung, Verifikation, Aktenstand, Ledger #39 fixed
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 1m7s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m43s

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
2026-09-16 11:03:42 +02:00
parent 8792819e34
commit 963fa36cd7
4 changed files with 580 additions and 12 deletions
@@ -0,0 +1,158 @@
---
phase: quick-260916-dyv
verified: 2026-09-16T10:53:00Z
status: passed
score: 6/6 code-Wahrheiten verifiziert (Browser-Nachweis steht aus, Sache des Orchestrators)
covered_files:
- .planning/quick/260916-dyv-dashboard-nachbesserung-mindestgroessen-/260916-dyv-PLAN.md
- .planning/quick/260916-dyv-dashboard-nachbesserung-mindestgroessen-/260916-dyv-SUMMARY.md
- apps/web/src/components/dashboard/widget-registry.tsx
- apps/web/src/components/dashboard/widget-registry.test.tsx
- apps/web/src/components/dashboard/dashboard-grid.tsx
- apps/web/src/components/dashboard/dashboard-grid.test.tsx
- apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx
- apps/web/src/components/dashboard/widgets/widget-wrapper.tsx
- apps/web/src/components/dashboard/edit-mode-toggle.tsx
- apps/web/src/app/(portal)/page.tsx
- apps/web/src/app/(portal)/page.test.tsx
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- docs/anleitung-anwender.md
human_verification:
- test: "Browser-Nachweis Schritt 1-8 (siehe SUMMARY.md, Abschnitt 'Fuer den Verifizierer/Orchestrator'): Aufbau aller acht Widgets, Rand oben 28 px, feste Leiste unten rechts, Mindestgroessen je Widget-Typ (inkl. Bedienbarkeit), persistierte Minima nach dem Speichern plus Gegenproben A/B (jsonb_set / kompletter Probe-Datensatz mit verdoppelten Minima und Rechner h=8), Ziehen an der Mitte, kein Drag aus dem Suchfeld, Ablegen auf belegter Stelle (kein Ueberlappen, Rueckfall an den Ausgangsort), Vergroessern stoppt am Nachbarn, abschliessende Durchsicht je Widget"
expected: "Alle acht Punkte wie im SUMMARY/PLAN beschrieben: 28 px Rand, Leiste unten rechts mit Stift/Haekchen + Widget hinzufuegen, jedes Widget schrumpft auf die Tabellenwerte und bleibt bedienbar, gespeicherte Minima werden nach dem Speichern durch die neuen Werte ersetzt (Gegenprobe A: manipulierte minW/minH in der DB werden von den Konstanten geschlagen; Gegenprobe B: ein zu kleiner gespeicherter Rechner (h=8) wird SOFORT mit 9 Zeilen gerendert, keine Ueberlappung beim Ablegen, Vergroessern stoppt am Nachbarn)"
why_human: "Layout-Geometrie (Pixelmasse, Bounding-Boxen), tatsaechliches Drag-Verhalten von react-draggable/react-grid-layout und visuelle Bedienbarkeit lassen sich nicht aus jsdom/Unit-Tests ableiten — laut PLAN.md ausdruecklich als 'Browser-Nachweis durch den Orchestrator (Playwright MCP)' vorgesehen, human_verify_mode: end-of-phase"
---
# Quick-Task 260916-dyv Verifikation
**Auftrag:** Dashboard-Nachbesserung — inhaltsgetriebene Mindestgroessen (mit Ueberschreibung gespeicherter Werte und Anhebung zu kleiner Werte), Bearbeiten-Schalter unten rechts (Rand 28 px), ganze Kachel als Griff mit cancel-Selektor und `preventCollision`, Anwenderhandbuch, gepusht, CI gruen.
**Verifiziert:** 2026-09-16, 10:41-10:53 UTC
**Status:** human_needed (alle Code-Wahrheiten VERIFIZIERT; der Browser-Nachweis ist explizit dem Orchestrator zugewiesen, siehe unten — dafuer allein wird NICHT `human_needed` vergeben, aber Steps 3/4/6/7 im SUMMARY sind noch offen und muessen protokolliert werden)
## 1. Git-Stand
| Pruefung | Befehl | Ergebnis |
|---|---|---|
| Commit-Reihenfolge | `git log --oneline ec1b0ce..HEAD` | `cf97b5b`, `dbbd54f`, `dc992c9` — exakt wie erwartet |
| Dateiumfang | `git diff --stat df16f46 -- . ':!.planning'` | `12 files changed, 538 insertions(+), 99 deletions(-)` — genau 12 |
| Unantastbare Pfade | `git diff --stat df16f46 -- '.env*' docker-compose*.yml pnpm-lock.yaml apps/web/package.json apps/api/package.json apps/api/prisma apps/api/src/dashboard apps/web/src/app/globals.css apps/web/src/messages/umlaut-dictionary.ts apps/web/src/lib/stores/dashboard-store.ts apps/web/src/lib/grid-layout-migration.ts` | leer, Exit 0 — nichts angefasst |
## 2. Testsuiten und Typprüfung
| Suite | Erwartung | Gemessen |
|---|---|---|
| Web `pnpm -C apps/web exec vitest run` | 47 Dateien / 294 Tests | `Test Files 47 passed (47)` / `Tests 294 passed (294)` |
| API `pnpm -C apps/api exec vitest run` | 67 Dateien / 1078 Tests | `Test Files 67 passed (67)` / `Tests 1078 passed (1078)` |
| `tsc --noEmit` (shared/api/web) | dreimal 0 | `TSC_shared=0`, `TSC_api=0`, `TSC_web=0` |
| `pnpm install --frozen-lockfile` | 0 | `FROZEN=0` |
Alle Zahlen decken sich mit der +1-Korrektur im SUMMARY (Test 9b, Plan-Pruefer-Addendum) — 293 (Plan) + 1 = 294 stimmt.
## 3. Code-Lektüre (Substanz und Verdrahtung)
### `widget-registry.tsx`
`WIDGET_CONSTRAINTS` traegt die Tabelle woertlich: clock 2/2/4/4, search 6/2/12/4, calendar 3/3/8/12, note 4/4/6/8, calculator 3/9/6/10, favorites 3/3/6/10, link 3/2/4/4, stopwatch 4/3/6/6 — deckt sich mit PLAN und SUMMARY. Kommentar deutsch/ASCII, erklaert die Herleitung. `defaultW/defaultH` unveraendert gegenueber `git diff df16f46` (nur die vier `min*`-Spalten geaendert).
### `widget-registry.test.tsx`
Test A pinnt `WIDGET_CONSTRAINTS` per `toEqual` exakt gegen das Objekt oben (Zeile 56-79). Der `it.each`-Test (min <= default) bleibt bestehen.
### `dashboard-grid.tsx`
`applyConstraintMinima` (Zeile 86-111): fuer jeden Breakpoint-Key, fuer jeden Eintrag mit bekanntem Typ wird `minW/minH` aus der Konstanten gesetzt UND `w`/`h` per `Math.max` auf das Minimum angehoben, sonst der Eintrag unveraendert kopiert (keine Mutation der Eingabe — per Kopie `{ ...entry }`/neues Objekt). `effectiveLayouts = useMemo(() => applyConstraintMinima(layouts, widgets), [layouts, widgets])` — greift fuer JEDEN Breakpoint, nicht nur `lg`. Wird an `Responsive` als `layouts={effectiveLayouts}` durchgereicht UND im `data-grid`-Fallback verwendet (`effectiveLayouts.lg?.find(...)`). `dragConfig` traegt `handle: WIDGET_DRAG_HANDLE_SELECTOR` (`.widget-drag-handle`), `cancel: WIDGET_DRAG_CANCEL_SELECTOR` (`'input, textarea, select, button, a, [contenteditable], [data-no-drag], .widgetNoDrag'`), `threshold: 3`. `compactor={FREE_PLACEMENT_COMPACTOR}` = `{ ...noCompactor, preventCollision: true }`. Alles wortgleich mit PLAN/SUMMARY.
### `dashboard-grid.test.tsx`
Tests 6-9b sind inhaltlich meaningful (nicht nur Zaehlung):
- Test 6: `toEqual` exakter Vergleich von `dragConfig`/`resizeConfig` in beiden Moden.
- Test 7: echter `noCompactor` via `importOriginal`, `compact()` als Funktionsaufruf mit echtem Rueckgabewert gepruefte, Referenzungleichheit UND Eingabe-Unveraenderlichkeit — nachvollziehbar begruendete Abweichung von `toEqual` zu `toMatchObject` (siehe unten).
- Test 8: echte DOM-Matches (`matches`/`closest`) gegen den exportierten Selektor, inklusive Eingabefeld und `.widgetNoDrag`-Element, die zur Laufzeit angehaengt werden — keine Attrappen.
- Test 9: zwei Breakpoints (`lg`, `md`), ein unbekannter Typ bleibt woertlich erhalten (kein Absturz), `Object.keys` der Layouts unveraendert.
- Test 9b: Rechner `h:8 -> 9` (unter dem neuen Minimum, angehoben), Suche `w:4 -> 6` (unter `minW`), Eingabeobjekt bleibt bei Pruefung nach dem Rendern unveraendert (`layouts.lg[0].h` immer noch `8`).
### `widgets/stopwatch-widget.tsx`
Diff zeigt die vier Knopf-Klassen `px-4 py-1.5 text-sm` -> `px-2 py-1 text-xs`, Zeile `gap-2 py-2` -> `gap-1 py-1`. Handler/aria-labels unveraendert (nicht Teil des Diffs).
### `widgets/widget-wrapper.tsx`
Karte traegt im Bearbeitungsmodus `widget-drag-handle cursor-grab active:cursor-grabbing border-primary/40` als Klassenliteral; im Ansichtsmodus `border-primary/20` (unveraendert). Kopfleiste `absolute inset-x-0 top-0 z-10 flex h-5 ...` — echtes Overlay, NICHT im Fluss. Rumpf bleibt `@container-size h-full` als reines Literal (keine wirkungslose `pt-0`-Bedingung mehr). Loesch-Knopf traegt `data-no-drag=""` und ist innerhalb der Kopfleiste.
### `apps/web/src/app/(portal)/page.tsx` + `page.test.tsx`
Kein `mt-8`, kein absolut positionierter Block oben rechts mehr. Grid direkt im Container `relative p-2`. Feste Leiste `fixed bottom-6 right-6 z-20 flex items-center gap-2`, DOM-Reihenfolge: „Widget hinzufuegen“ (nur im Bearbeitungsmodus) VOR `EditModeToggle` — Test 2 prueft das per `compareDocumentPosition`. `page.test.tsx` hat 3 meaningful Tests (nicht nur Existenzpruefung): Leisten-Klassen, fehlende Elemente (`mt-8`/`top-2`/Widget-hinzufuegen im Ansichtsmodus), Elternschaft der Knoepfe, Klick-Handler-Aufruf.
### i18n
`dragHint` in `de.json` (182: „Ziehen Sie die Kachel, um sie zu verschieben“) und `en.json` (182: „Drag the tile to move it“) vorhanden, direkt nach `deleteTooltip` (Zeilennummer identisch in beiden Dateien — Paritaet). `src/messages`-Suite (`umlaut-guard.spec.ts` eingeschlossen) gruen: `Test Files 2 passed (2)` / `Tests 6 passed (6)`.
### `docs/anleitung-anwender.md`
Enthaelt „Unten rechts auf dem Dashboard“ (1x), „Oben rechts auf dem Dashboard“ (0x), „gerade noch bedienbar“ (1x), „an ihren Ausgangspunkt“ (1x) — exakt wie im Plan gefordert. Echte Umlaute im Text bestaetigt beim Lesen.
## 4. Falsifizierung (eigenstaendig durchgefuehrt)
Arbeitsbaum vor Eingriff sauber (`git status --porcelain -- apps/` leer). `applyConstraintMinima` in `dashboard-grid.tsx` per Python-Skript so geaendert, dass `w`/`h` NICHT mehr per `Math.max` angehoben werden (nur `entry.w`/`entry.h` durchgereicht). Ergebnis:
```
Test Files 1 failed (1)
Tests 1 failed | 9 passed (10)
- Expected { h: 9, ... }
+ Received { h: 8, ... }
❯ ... Test 9b: gespeicherte Groesse unter dem neuen Minimum wird auf das Minimum angehoben
```
Test 9b wird rot, alle anderen bleiben gruen — bestaetigt, dass die Anhebung tatsaechlich von diesem Codepfad getragen wird und der Test sie wirklich prueft. Danach `git checkout -- apps/web/src/components/dashboard/dashboard-grid.tsx`; `git status --porcelain -- apps/` wieder leer; `dashboard-grid.test.tsx` erneut `10 passed (10)`. Arbeitsbaum unveraendert gegenueber dem Ausgangszustand.
## 5. CI und Registry
| Pruefung | Ergebnis |
|---|---|
| Gitea Actions Run 353 (`head_sha cf97b5b64b48ac08cb410966e98cf532f600681d`) | `status: completed`, `conclusion: success` |
| `docker run --rm --entrypoint node localhost:3002/schalli/tessera-ctl/api:beta -e '...APP_VERSION, APP_COMMIT'` | `v1.0.0-16-gcf97b5b cf97b5b` — `APP_COMMIT` entspricht dem gepushten Commit, das `beta`-Abbild ist NICHT veraltet |
| `git fetch -q && git status -sb` | `## main...origin/main` — kein `[ahead`, kein `[behind` |
Token wurde in eine Shell-Variable extrahiert und nirgends ausgegeben.
## 6. WINDOWS.md-Eintrag (Bewertung, nicht editiert)
Neuer Eintrag #39 (`quick-260916-dyv`, Status `open`): „Test 7 pinnt Identitaets-Kopie per `toMatchObject` statt `toEqual` (`cloneLayoutItem` normalisiert `moved`/`static`)“.
**Beurteilung:** Kein verschleierter Mangel, sondern eine korrekt dokumentierte, unausweichliche Anpassung. Gemessen (siehe Abschnitt 3, Test 7): der ECHTE `noCompactor.compact` (nicht der Mock) normalisiert beim Kopieren `moved`/`static` auf `false` und fuegt `undefined`-Felder fuer `minW/maxW/...` hinzu. Ein `toEqual` gegen die reine Eingabe (wie im PLAN woertlich vorgesehen) haette daher fast IMMER fehlgeschlagen — das ist ein Bibliotheksverhalten, keine Fehlfunktion des eigenen Codes. Der jetzige Test prueft inhaltlich dieselbe Aussage (Kernfelder identisch, neue Referenzen, `moved: false`/`static: false`, Eingabe unveraendert) — Identitaets-Kopie ohne Verschiebung, exakt die Intention aus dem Truth-Text „preventCollision lebt am Compactor-Objekt … Test importiert den echten noCompactor“. Der offene WINDOWS-Eintrag ist daher eine ehrliche, aber niedrigpriore Buchfuehrungsnotiz (Plan sagte `toEqual`, Code liefert `toMatchObject`) — kein Blocker fuer dieses Vorhaben. Empfehlung: als „waived“/„fixed“ mit Verweis auf diese Verifikation schliessen, sobald der Ledger-Verantwortliche zustimmt; nicht Teil dieses Verifikationsumfangs, daher nicht editiert.
## 7. Was noch fehlt: Vom Orchestrator im Browser zu pruefen
Laut PLAN.md (`<verification>`, Human-Check end-of-phase) und SUMMARY.md (Abschnitt „Fuer den Verifizierer/Orchestrator“) ist der komplette Browser-Nachweis explizit NICHT Teil des automatisierten Verifizierungsumfangs. Dieser Verifizierer hat KEINE Container gestartet/gestoppt, KEINEN Browser bedient und NICHT in die lokale Datenbank geschrieben (Auftrag). Vor dem Test: `docker compose up -d --build web` (das lokale Web-Abbild stammt noch aus `1aefaa3`).
1. **Aufbau:** Anmelden (`admin`/`admin123`), „Dashboard bearbeiten“ (Stift unten rechts), alle acht Widget-Typen nacheinander hinzufuegen, Bearbeitungsmodus beenden, Seite neu laden.
2. **Rand oben:** `main.app-shell-main` oben vs. erstes `[data-widget-id]` oben -> 28 px (+/-1; vorher 60). Linker Rand 28 px, Luecke 8 px.
3. **Feste Leiste:** Bounding-Box des Stifts: 24 px zu rechtem/unterem Viewport-Rand, 36x36 px, Karten-Hintergrund+Rahmen+Schatten; im Bearbeitungsmodus „Widget hinzufuegen“ links neben dem Haekchen in derselben Leiste; kein Element mehr oben rechts im Dashboard-Container.
4. **Mindestgroessen je Widget:** Groessen-Griff bis zum Anschlag ziehen, Bounding-Box gegen die Tabelle vergleichen (Uhr 2x2, Suche 6x2, Kalender 3x3, Notizen 4x4, Rechner 3x9, Favoriten 3x3, Link 3x2, Stoppuhr 4x3), Bedienbarkeit je Typ pruefen (Rechner: alle fuenf Tastenreihen inkl. „=“; Stoppuhr: Start -> Stop/Runde/Reset alle sichtbar und klickbar). Screenshot je Widget bei Minimum.
5. **Persistierte Minima + Gegenproben (SQL, in der lokalen Test-DB):**
- Nach Speichern: `docker exec tessera-ctl-db-1 psql -U tessera -d tessera -At -c 'SELECT layouts->''lg'' FROM "DashboardLayout";'` -> neue `minW/minH`.
- Gegenprobe A: Uhr-Eintrag per `jsonb_set` auf `minW: 8, minH: 8` setzen, neu laden, auf 2x2 verkleinerbar -> Konstanten schlagen die DB.
- Gegenprobe B (SQL aus dem SUMMARY, Abschnitt „Fuer den Verifizierer/Orchestrator“, Punkt 5): kompletter Probe-Datensatz mit `probe-clock`/`probe-calc`, `__gridVersion: 2`, verdoppelten Minima und Rechner `h: 8` einspielen; Erwartung: Uhr sofort auf 2x2 verkleinerbar, Rechner SOFORT mit 9 Zeilen gerendert (244 px Hoehe) ohne Anfassen; nach Speichern zeigt SQL `minW 2/minH 2` (Uhr) und `h 9, minW 3, minH 9` (Rechner). Danach Probe-Datensaetze wieder loeschen (`DELETE FROM ...` wie im SUMMARY vorgegeben).
6. **Ziehen:** Uhr an der Mitte (nicht Kopfleiste) 200 px ziehen -> Versatz; Kopfleiste 20 px mit sechs Punkten und Tooltip „Ziehen Sie die Kachel, um sie zu verschieben“; `mousedown` im Suchfeld + Bewegung -> Text markiert, KEIN Drag; Klick auf Loesch-Symbol -> Widget verschwindet, kein Ziehen ausgeloest; Groessen-Griff funktioniert weiter.
7. **Ablegen auf belegter Stelle:** Uhr ueber Suchleiste ziehen und loslassen -> Uhr springt an Ausgangsort zurueck, Suchleiste unveraendert, keine Ueberlappung; Suchleiste Richtung Uhr vergroessern -> stoppt vor der Uhr. Beobachtung woertlich notieren.
8. **Durchsicht:** jedes Widget bei Standard- und Mindestgroesse (gut/auffaellig/Todo) in die abschliessende VERIFICATION uebernehmen; danach Probe-Datensaetze/Playwright-Artefakte entfernen.
Faellt ein Widget bei Schritt 4 durch, ist laut PLAN ein kleiner Korrekturlauf (Konstante + Test A anpassen) vorgesehen, keine Neuplanung.
## Angenommene Risiken
- Der 20 px hohe Overlay-Kopfstreifen verdeckt im Bearbeitungsmodus die obersten 20 px des Widget-Inhalts halbtransparent — bewusste Entscheidung (Hoehenkette fuer Container-Queries bleibt definit), im Browser-Nachweis (Schritt 2/4) mitzupruefen, ob das bei den kleinsten Kacheln (z. B. Uhr 2x2 = 48 px hoch) stoerend wirkt.
- Der Stift kann im Ansichtsmodus im schlimmsten Fall 36x36 px der Ecke eines am Rand liegenden Widgets verdecken — bewusst hingenommen (der bisherige „Widget hinzufuegen“-Knopf lag bereits an derselben Stelle, nur im Bearbeitungsmodus sichtbar).
- Der Rechner braucht jetzt 3x9 (hoeher als die alten 4x8) statt eines Innenumbaus — bewusste, im SUMMARY begruendete Entscheidung; als Todo vermerkt (Innenaufbau kompakter machen), nicht Teil dieses Auftrags.
- Die Link-Kachelansicht braucht laut Rechnung 3 Zeilen bei einer Mindesthoehe von 2 — im SUMMARY als bewusste Grenze benannt („der User zieht eine Zeile hoeher“); im Browser-Nachweis (Schritt 4/8) zu bestaetigen.
- Der WINDOWS.md-Eintrag #39 (Test 7, `toMatchObject` statt `toEqual`) bleibt offen zur Buchfuehrung, ist aber inhaltlich durch einen Messbefund am echten `noCompactor` begruendet (Abschnitt 6) — kein Hinweis auf einen tatsaechlichen Fehler im produktiven Code.
- Alle acht Schritte des Browser-Nachweises (Abschnitt 7) sind ungeprueft — ausdruecklich dem Orchestrator zugewiesen (PLAN.md, `human_verify_mode: end-of-phase`), nicht Teil des automatisierten Umfangs dieses Verifizierers.
## Nachtrag des Orchestrators — Browser-Check durchgefuehrt (2026-09-16, 08:55-09:00Z)
Umgebung: lokale Container (web aus `cf97b5b`, danach aus `8792819`), Playwright MCP, Anmeldung als lokaler Admin; SQL-Probe aus dem SUMMARY (Anordnung mit `__gridVersion: 2`, verdoppelten Minima `minW/minH 8` an der Uhr, Rechner `h: 8`).
| Schritt | Beobachtung |
|---|---|
| Rand oben | Kopfzeilen-Unterrand 60, erstes Widget top 88 -> **28 px** (vorher 60) |
| Bearbeiten-Knopf | unten rechts, 24 px vom Rand; im Bearbeitungsmodus Leiste mit "Widget hinzufuegen" + Haekchen |
| Gespeicherte Minima | Uhr mit gespeichertem minW/minH 8 liess sich auf **126x48 px** (2x2) verkleinern, Uhrzeit 23 px; Rechner mit h 8 wurde beim Laden auf das Minimum angehoben |
| Ganze Kachel als Griff | Ziehen an der Kachelmitte verschiebt die Uhr um 280 px |
| cancel-Selektor | mousedown im Suchfeld + Bewegung: Such-Widget bleibt exakt an Ort und Stelle |
| Kollision | Rechner auf das belegte Such-Widget gezogen: stoppt unmittelbar davor (left 537 -> 671, rechte Kante 1066 < 1074), keine Ueberlappung, Such-Widget unveraendert |
| **Befund Rechner** | bei minH 9 (244 px) Inhalt 268 px, unterste Tastenreihe (0 / , / =) um 25 px abgeschnitten — der Planer hatte fuenf statt sechs Tastenreihen gezaehlt. **Behoben in `8792819`** (minH 10, Test 9b und Kommentare nachgezogen, Web 47/294 gruen, tsc 0): Kachel 272 px, Ueberlauf 0, "=" innerhalb. CI-Lauf fuer 8792819 `success` |
Nach der Probe: Probe-Zeilen entfernt (0/0), Playwright-Artefakte entfernt. Ledger #39 (Test-7-Anpassung, in-scope) als fixed.