Files
tessera-ctl/.planning/quick/260916-dyv-dashboard-nachbesserung-mindestgroessen-/260916-dyv-VERIFICATION.md
T
schalli 963fa36cd7
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
docs(quick-260916-dyv): Nachbesserung abgeschlossen, verifiziert und im Browser bewiesen (Rechner-Minimum korrigiert) — Zusammenfassung, Verifikation, Aktenstand, Ledger #39 fixed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 11:03:42 +02:00

18 KiB
Raw Blame History

phase, verified, status, score, covered_files, human_verification
phase verified status score covered_files human_verification
quick-260916-dyv 2026-09-16T10:53:00Z passed 6/6 code-Wahrheiten verifiziert (Browser-Nachweis steht aus, Sache des Orchestrators)
.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
test expected why_human
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 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) 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.