Files
tessera-ctl/.planning/quick/260916-bwo-dashboard-feineres-raster-spalten-und-ze/260916-bwo-SUMMARY.md
T
schalli 7a6f42ea74
Tessera CI/CD / Lint & Type Check (push) Successful in 48s
Tessera CI/CD / Tests (push) Successful in 1m6s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m3s
docs(quick-260916-bwo): Dashboard-Umbau abgeschlossen, verifiziert 8/8 und im Browser bewiesen — Zusammenfassung, Verifikation, Aktenstand
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 09:36:50 +02:00

31 KiB
Raw Blame History

phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals plan_head_before tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
quick-260916-bwo 01 ui, dashboard
dashboard
react-grid-layout
container-queries
tailwind
zustand
vitest
i18n
phase provides
08-dashboard-widgets WIDGET_CONSTRAINTS, Uhr/Stoppuhr/Rechner-Widgets, Dashboard-Store, Widget-Einstellungen
Dashboard-Raster 24/20/12/8/2 Spalten, 20 px Zeilen, 8 px Abstand; alle 32 Widget-Konstanten verdoppelt
Einmalige Umrechnung gespeicherter Anordnungen (grid-layout-migration.ts) mit Marker __gridVersion: 2 im gespeicherten JSON, nie im Zustand
Uhr, Stoppuhr, Rechner skalieren per CSS-Container-Queries mit der Kachel (Widget-Rumpf ist @container-size)
Uhr: optionale feste Schriftgroesse in Punkt (timeFontSizePt, 8..200, leer = automatisch) mit Zahlenfeld unter Einstellungen -> Dashboard
Abstaende halbiert: Seitenrahmen p-3 (alle Seiten), Dashboard p-2, Grid 8 px, Widget-Innenabstaende
dashboard
alle-seiten-rahmen
anwenderhandbuch
tokens tasks commits
72941 3 4
50f201ecc1
added patterns
Marker im persistierten JSON, nie im Zustand: Store entfernt beim Laden, haengt bei JEDEM Speichern an (Idempotenz-Test + Store-Test pinnen es)
Container-Query-Schriftgroessen als Tailwind-Klassen mit clamp()-Grenzen (jsdom verwirft clamp() im Inline-Style), feste Groesse als Inline-Style nur aus einer geprueften Zahl
Grenzen fuer ein Config-Feld in EINER Quelldatei (clock-font-size.ts), von Formular und Widget benutzt
react-grid-layout-Mock faengt Props per vi.hoisted ein, damit Raster-Konstanten testbar sind
created modified
apps/web/src/lib/grid-layout-migration.ts
apps/web/src/lib/grid-layout-migration.test.ts
apps/web/src/lib/stores/dashboard-store.test.ts
apps/web/src/components/dashboard/widgets/clock-font-size.ts
apps/web/src/components/settings/widget-settings-panel.test.tsx
apps/web/src/components/dashboard/dashboard-grid.tsx
apps/web/src/components/dashboard/dashboard-grid.test.tsx
apps/web/src/components/dashboard/widget-registry.tsx
apps/web/src/components/dashboard/widget-registry.test.tsx
apps/web/src/lib/stores/dashboard-store.ts
apps/api/src/dashboard/dashboard.service.spec.ts
apps/web/src/components/dashboard/widgets/widget-wrapper.tsx
apps/web/src/components/dashboard/widgets/clock-widget.tsx
apps/web/src/components/dashboard/widgets/clock-widget.test.tsx
apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx
apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx
apps/web/src/components/dashboard/widgets/calculator-widget.tsx
apps/web/src/components/dashboard/widgets/calculator-widget.test.tsx
apps/web/src/components/settings/widget-settings-panel.tsx
apps/web/src/messages/de.json
apps/web/src/messages/en.json
apps/web/src/components/dashboard/widgets/link-widget.tsx
apps/web/src/components/dashboard/widgets/favorites-widget.tsx
apps/web/src/components/dashboard/widgets/calendar-widget.tsx
apps/web/src/components/dashboard/widgets/search-widget.tsx
apps/web/src/components/dashboard/widgets/note-widget.tsx
apps/web/src/app/(portal)/page.tsx
apps/web/src/components/layout/app-shell.tsx
docs/anleitung-anwender.md
Umrechnung im Frontend (neben COLS), nicht in der API: Einheiten sind Frontend-Konstanten, kein Schreiben auf GET, API-Dienst/DTOs unangetastet, reine Funktion ohne Prisma testbar; Preis: Marker persistiert erst mit gelungenem PUT, bis dahin rechnet jedes Laden erneut aus den unveraendert alten DB-Werten (korrekt, nie halb geschrieben)
Marker __gridVersion nur im gespeicherten JSON, nie im Zustand (Store iteriert mit Object.keys + .filter); withGridVersion an BEIDEN Speicherstellen
mt-8 vor dem Grid bleibt: Umschalter 36 px hoch (p-2 + 20-px-Symbol) bei 8..44 px; mit mt-4 begaenne das erste Widget bei 32 px im Umschalter, mit mt-8 bei 48 px
Automatische Schriftgroesse als Tailwind-Klasse (clamp/min/cqw/cqh), feste Punktgroesse als Inline-Style aus einer geprueften Zahl — Trennung wegen jsdom UND als einfachere Form
Listen-/Formular-Widgets (note, calendar, favorites, link, search) skalieren bewusst NICHT — mehr Platz zeigt mehr Inhalt, nicht groessere Schrift
Einmalige Datenumrechnung im Client: reine Funktion + Marker + Idempotenz-Test + Store-Test 'jedes Speichern traegt den Marker'
QUICK-260916-BWO
id description requirement verification human_judgment
D1 Raster 24/20/12/8/2, rowHeight 20, margin 8, 32 Konstanten verdoppelt, data-grid-Vorgaben QUICK-260916-BWO
kind ref status
unit apps/web/src/components/dashboard/dashboard-grid.test.tsx#Test 4-5, widget-registry.test.tsx#Tabelle (Falsifizierung c) pass
false
id description requirement verification human_judgment rationale
D2 Einmalige Umrechnung mit Marker, Store rechnet um und speichert sofort, Marker bei jedem Speichern, API reicht durch QUICK-260916-BWO
kind ref status
unit apps/web/src/lib/grid-layout-migration.test.ts#Test 1-7 (Falsifizierung a), dashboard-store.test.ts#Test 1-6, dashboard.service.spec.ts#Test A-B pass
true Dass eine ALTE Anordnung im Browser an derselben optischen Stelle bleibt und nach zwei Ladevorgaengen nicht erneut verdoppelt wird, zeigt nur die SQL-Probe im Browser (unten)
id description requirement verification human_judgment rationale
D3 Container-Query-Skalierung Uhr/Stoppuhr/Rechner, feste Punktgroesse, Einstellungsfeld, i18n QUICK-260916-BWO
kind ref status
unit clock-widget.test.tsx#Test 3-6 (Falsifizierung b), stopwatch-widget.test.tsx#+1, calculator-widget.test.tsx#+1, widget-settings-panel.test.tsx#Test 1-4, umlaut-guard.spec.ts pass
true jsdom rechnet keine Container-Queries; ob cqh im Browser aufloest (definite Hoehe) und die Uhr sichtbar mitwaechst, muss der Browser zeigen
id description requirement verification human_judgment rationale
D4 Abstaende halbiert (Seitenrahmen, Dashboard, Widget-Ruempfe), Handbuch QUICK-260916-BWO
kind ref status
other grep-Gates Task 2/3 (Klassen, Handbuch 2/1) pass
true Bounding-Boxen (28 px Rand, 8 px Luecke, 60 px oben, 12 px auf /admin/users) nur im Browser messbar
18 min (07:04Z bis 07:22Z, davon ca. 4 min Suiten-Laeufe und 5,5 min CI-Beobachtung) 2026-09-16 complete

Quick 260916-bwo Plan 01: Dashboard — feineres Raster, skalierende Widget-Inhalte, halbe Abstaende — Summary

Das Dashboard-Raster ist doppelt so fein (24 statt 12 Spalten, 20 statt 40 px Zeilenhoehe, 8 statt 16 px Abstand) bei optisch gleich grossen Widgets (alle 32 Konstanten in WIDGET_CONSTRAINTS verdoppelt und im Test festgenagelt); gespeicherte Anordnungen in alten Einheiten werden beim Laden GENAU EINMAL mit 2 multipliziert und mit __gridVersion: 2 im gespeicherten JSON markiert (reine Funktion migrateGridLayouts, Marker nie im Zustand, Idempotenz-Test); der Widget-Rumpf ist ein CSS-Groessen-Container, sodass Uhrzeit, Stoppuhr-Anzeige und Rechner (Anzeige und Tasten) per cqw/cqh mit der Kachel wachsen; die Uhrzeit laesst sich unter Einstellungen -> Dashboard -> Uhr in Punkt fest einstellen (8..200, leer = automatisch, Fremdwerte fallen auf automatisch zurueck, T-BWO-01); Seitenrahmen (app-shell.tsx, alle Seiten), Dashboard-Container, Grid-Abstand und Widget-Innenabstaende sind halbiert. Vier Commits auf main (3f5afb0, 2d8efe1, a175c00, 1aefaa3), gepusht, CI-Lauf 351 success in 5 min 26 s, api:beta traegt 1aefaa3 beta.

Ausgangslage und Bezugspunkt

Alle Gates gegen 5f50c5f (Code unangetastet seit Planung; HEAD bei Start 50f201e = Plan-Commit, Arbeitsbaum sauber). git status -sb bei Start: ## main...origin/main [voraus 2] — die beiden Plan-Commits des Orchestrators (7eb2516, 50f201e) waren noch nicht gepusht; sie gingen mit dem Push in Task 3 mit (5f50c5f..1aefaa3). Vorbedingung Task 3: curl localhost:3002/api/v1/version -> {"version":"1.26.2"}, docker ps | grep -c '^gitea-runner$' -> 1.

Baseline vor jeder Aenderung (frisch gemessen, identisch mit der Planung):

Suite Vorher Nachher (Ziel des Plans)
Web pnpm -C apps/web exec vitest run Test Files 43 passed (43) / Tests 260 passed (260) Test Files 46 passed (46) / Tests 286 passed (286)
API pnpm -C apps/api exec vitest run Test Files 67 passed (67) / Tests 1076 passed (1076) Test Files 67 passed (67) / Tests 1078 passed (1078)
tsc --noEmit shared / api / web 0 / 0 / 0 0 / 0 / 0

Bestand an Anordnungen (nur lesend, docker exec tessera-ctl-db-1 psql -U tessera -d tessera -At): lokal DashboardLayout 0, WidgetInstance 0 (erneut gemessen 07:19Z); alpha 0/0 laut Planung (nicht erneut gemessen); live nicht gemessen — die Umrechnung bleibt Pflicht.

Task 1 — Raster verdoppelt, Umrechnung, Store, API-Durchreich-Specs (3f5afb0, 9 Dateien)

RED (Schritt A) pnpm -C apps/web exec vitest run src/lib/grid-layout-migration.test.ts src/lib/stores/dashboard-store.test.ts src/components/dashboard:

FAIL  src/lib/grid-layout-migration.test.ts — Failed to resolve import "./grid-layout-migration"
 × dashboard-store Test 1: expected { i: 'a', x: 1, y: 1, w: 2, h: 2 } to deeply equal { i: 'a', x: 2, y: 2, w: 4, h: 4 }
 × dashboard-store Test 2: expected [ 'lg', 'md', 'sm', 'xs', 'xxs', …(1) ] to not include '__gridVersion'
 × dashboard-store Test 4: expected "vi.fn()" to be called with arguments: [ ObjectContaining{…} ]
 × dashboard-store Test 5: expected 1 to be 2
 × dashboard-store Test 6: expected [ { i: 'n1', x: +0, y: +0, …(2) } ] to deep equally contain { i: 'n1', x: +0, y: +0, w: 4, h: 4 }
 × dashboard-grid Test 4: expected { Object (lg, md, ...) } to deeply equal { lg: 24, md: 20, sm: 12, xs: 8, …(1) }
 × dashboard-grid Test 5: expected { x: +0, y: +0, w: 2, h: 2, …(2) } to deeply equal { x: +0, y: +0, w: 4, h: 4, …(2) }
 × widget-registry Tabelle: expected { …(8) } to deeply equal { …(8) }
 Test Files  4 failed | 8 passed (12)
      Tests  8 failed | 55 passed (63)

(Store-Test 3 „leer -> kein Speichern“ war nach altem Code bereits gruen — erwartbar, alter Code speicherte beim Laden nie.)

API-Spec (+2) pnpm -C apps/api exec vitest run src/dashboard/dashboard.service.spec.ts -> Test Files 1 passed (1) / Tests 31 passed (31) (vorher 29 it(-Bloecke). Beide neuen Tests waren nach heutigem Code sofort gruen — das ist der Beleg der Durchreichung (@IsObject(), return record.layouts, { ...widget.config, ...dto.config }), kein RED.

GREEN (Schritt E) und Verify-Block:

Web (4 Dateien):  Test Files  4 passed (4) / Tests  29 passed (29)
API:              Test Files  1 passed (1) / Tests  31 passed (31)
greps: COLS 1, rowHeight 1, margin 1, clock 1, search 1, calculator 1, withGridVersion( 2, migrateGridLayouts( 1, GRID_VERSION 1
U_EXIT=0 U_EMPTY=0 (dashboard.service.ts, dto/, prisma/ unangetastet)
TSC_web=0 TSC_api=0

Messung widerspricht dem Plan (beide festgehalten): Der Plan nennt fuer die vier Web-Dateien Tests 22 passed (22) („5 + 4 + 7 + 6“); gemessen 29. Ursache: widget-registry.test.tsx hatte vorher 10 Testfaelle (1 + it.each ueber 8 Typen + 1), der Plan zaehlte 3 it(-Bloecke. Mit +1 sind es 11 (pnpm -C apps/web exec vitest run src/components/dashboard/widget-registry.test.tsx -> Tests 11 passed (11)): 5 + 11 + 7 + 6 = 29. Die Zielzahl der Gesamtsuite (260 + 26 = 286) ist davon nicht betroffen und wurde exakt erreicht.

Task 2 — Teil 2a: Skalierung, Punktgroesse, i18n (2d8efe1, 12 Dateien)

RED (Schritt A) pnpm -C apps/web exec vitest run src/components/dashboard/widgets src/components/settings/widget-settings-panel.test.tsx:

FAIL  src/components/dashboard/widgets/clock-widget.test.tsx — Failed to resolve import "./clock-font-size"
 × stopwatch quick-260916-bwo: expected 'font-mono font-semibold tabular-nums …' to match /cqw/
 × calculator quick-260916-bwo: expected 'flex min-h-[2.5rem] items-end justify…' to match /cqw/
 × widget-settings-panel Test 1-4: It looks like undefined was passed instead of a matcher (de.widgets.clock.fontSizeLabel fehlte noch)
 Test Files  4 failed | 5 passed (9)
      Tests  6 failed | 39 passed (45)

GREEN (Schritt H) ... src/components/dashboard/widgets src/components/settings/widget-settings-panel.test.tsx src/messages -> Test Files 11 passed (11) / Tests 57 passed (57); je Datei: clock 6, stopwatch 8, calculator 7, widget-settings-panel 4, src/messages 6 (Umlaut-Waechter und tenderRadar-Paritaet gruen OHNE Aenderung an umlaut-dictionary.ts); TSC_web=0.

Messung widerspricht dem Plan (beide festgehalten): Der Plan nennt fuer Commit 2a „genau 11 Dateien“, zaehlt in Klammern aber 12 auf (widget-wrapper, clock-font-size, clock + Test, stopwatch + Test, calculator + Test, widget-settings-panel + Test, de.json, en.json). git show --stat 2d8efe1 -> 12 files changed. 9 + 12 + 7 + 1 = 29 stimmt mit der Gesamtzahl des Plans ueberein; die „11“ ist ein Zaehlfehler des Plans, nicht des Commits.

Task 2 — Teil 2b: Abstaende halbiert (a175c00, 7 Dateien)

Tabelle aus dem Plan eins zu eins umgesetzt (link 238 p-1, favorites 174 p-1, calendar 112 p-1.5 und 80/91/102 p-2, search 89 px-1.5, note 89 px-1.5 py-1.5, page.tsx relative p-2 / right-2 top-2 / mt-8 bleibt mit Kommentar, app-shell main p-3 mit Kommentar).

Verify-Block Task 2 (volle Web-Suite und 29 greps):

Test Files  46 passed (46) / Tests  286 passed (286)
@container-size 2 | Uhr-Klasse 1 | data-font-mode 1 | vw clock 0 | vw stopwatch 0 | cqw stopwatch 1 | cqw calculator 5 | h-full min-h-7 1
MIN 1 | MAX 1 | id=clock-font-size 1 | fontSizeLabel de 1 | fontSizeInvalid en 1 | D2_EXIT=0 DICT_UNCHANGED=0
relative p-2 1 | right-2 top-2 1 | "mt-8" 1 | app-shell p-3 2 | app-shell " p-6" 0 (nach Korrektur, s. Abweichung 2)
link 1 | favorites 1 | calendar Rumpf 1 | calendar Zustandsansichten 3 | search 1 | note 1 | clock 1 | stopwatch 1 | calculator 1 | TSC_web=0

Task 3 — Handbuch, Abschluss-Gates, Push, CI (1aefaa3, 1 Datei)

grep -c "Schriftgröße" docs/anleitung-anwender.md -> 2; grep -c "feinen Schritten" -> 1.

Abschluss-Gates (nach dem Handbuch, vor dem Commit):

Web:   Test Files  46 passed (46) / Tests  286 passed (286)
API:   Test Files  67 passed (67) / Tests  1078 passed (1078)
TSC_packages/shared=0  TSC_apps/api=0  TSC_apps/web=0
FROZEN=0                                   (pnpm install --frozen-lockfile)
GIT_EXIT=0   29 files changed, 895 insertions(+), 60 deletions(-)   (git diff --stat 5f50c5f -- . ':!.planning')
U_EXIT=0  U_EMPTY=0   (.env*, Compose, Lockfile, package.json, prisma, dashboard.service.ts, dto/, globals.css unangetastet)

Push 07:15:03Z: 5f50c5f..1aefaa3 main -> main; git fetch -q && git status -sb | head -1 -> ## main...origin/main (kein [ahead, kein [behind).

CI-Lauf nach dem Push

Feld Versuch 1 (einziger)
Lauf-ID 351 (event push, ref main, head_sha 1aefaa36c1e3ae7c3055e714b059223a1e5aff77)
status / conclusion completed / success
started_at / completed_at 09:15:08 / 09:20:34 (+02:00) — 5 min 26 s (17 Polls a 20 s, kein Warten in der Warteschlange: der Lauf war beim ersten Poll 11 s nach dem Push bereits in_progress)
Jobs Lint & Type Check success (07:15:08-07:15:57Z), Tests success (07:16:00-07:16:57Z), Build & Publish Images success (07:16:59-07:20:33Z)
api:beta node APP_VERSION APP_CHANNEL APP_COMMIT v1.0.0-10-g1aefaa3 beta 1aefaa3 — Messinstrument-Befund: der Plan erwartet fuer APP_VERSION den „kurzen SHA“; seit dem Tag v1.0.0 (260915) liefert git describe v1.0.0-10-g1aefaa3, der kurze SHA steht in APP_COMMIT = 1aefaa3 = git rev-parse --short HEAD. Beide Werte sind das erwartete Abbild des gepushten Stands
docker image inspect Created web:beta / api:beta 2026-09-16T09:18:31+02:00 / 2026-09-16T09:19:29+02:00 (nach dem Lauf-Start; fuer die Probe wurde api:beta per docker pull geholt, kein Container gebaut oder gestartet)

Falsifizierungen (a)-(c) — rot/gruen

Jede einmal durchgefuehrt, Rueckstellung per git checkout -- <Datei>, danach git status --porcelain leer.

Falsifizierung Eingriff Rot (Testnamen woertlich) Zurueckgesetzt
(a) Marker-Pruefung entfernt grid-layout-migration.ts: const needsScaling = true; `Tests 4 failed 9 passed (13)`: Migration „Test 2: markierte Anordnung (__gridVersion 2) bleibt unveraendert, migrated false“, „Test 4: Idempotenz — einmal umgerechnet und markiert wird nicht erneut verdoppelt (T-BWO-02)“, „Test 6: Zukunft und Robustheit …“; Store „Test 2: markierte Anordnung bleibt unveraendert, kein Speichern, kein Marker im Zustand“
(b) resolveClockTimeFontSizePt ohne Typpruefung clock-font-size.ts: const value = Number(config.timeFontSizePt) `Tests 2 failed 4 passed (6): „Test 3 (quick-260916-bwo): resolveClockTimeFontSizePt laesst nur endliche Zahlen 8..200 durch (T-BWO-01)“ (expected 36 to be null), „Test 6 (quick-260916-bwo): Zeichenketten-Wert wird ignoriert — auto, kein Inline-Style (T-BWO-01)“ (expected '36pt' to be '') — genau der Angriff: '36'` wuerde zum Style
(c1) eine Konstante auf den alten Wert widget-registry.tsx: clock.minW: 2 `Tests 2 failed 14 passed (16)`: „quick-260916-bwo: jede Groesse ist exakt das Doppelte der alten 12-Spalten-Werte“, dashboard-grid „quick-260916-bwo Test 5: Widget ohne gespeicherten Eintrag bekommt die verdoppelten Vorgaben als data-grid“
(c2) rowHeight auf 40 dashboard-grid.tsx `Tests 1 failed 4 passed (5)`: „quick-260916-bwo Test 4: Grid-Props — 24/20/12/8/2 Spalten, rowHeight 20, margin 8, Breakpoints unveraendert, containerPadding folgt dem margin“

Gemessene Befunde und Entscheidungen (Kurzform)

  • jsdom 29.1.1 / cssstyle (Planungsmessung, in der Ausfuehrung durch die Tests bestaetigt): style.fontSize = 'clamp(...)' wird verworfen (''), '36pt' bleibt. Folge: automatische Groessen als Tailwind-Klassen (text-[clamp(12px,min(20cqw,50cqh),400px)] usw.), feste Punktgroesse als Inline-Style; Tests lesen className, data-font-mode und style.fontSize — Test 5/6 der Uhr laufen exakt so gruen.
  • Tailwind 4.3.1 (Planungsmessung): @container-size -> container-type: size; text-[clamp(12px,min(20cqw,50cqh),400px)] kompiliert mit verschachtelten Funktionen. Alle Klassen stehen woertlich im JSX bzw. als String-Literale in derselben Datei (Scanner). Nicht erneut kompiliert — der Browser-Nachweis prueft das Ergebnis.
  • Umrechnung im Frontend (siehe key-decisions): Einheiten sind Frontend-Konstanten; die API reicht durch (Spec Test A/B pinnen es); kein Schreiben auf GET; reine Funktion ohne Prisma testbar. Preis: bis zum ersten gelungenen PUT rechnet jedes Laden erneut aus den unveraenderten alten DB-Werten — korrekt, weil saveLayout Marker und Werte immer gemeinsam schreibt.
  • mt-8 bleibt: Umschalter p-2 + 20-px-Symbol = 36 px, mit top-2 bei 8..44 px; erstes Widget mit mt-8 bei 8+32+8 = 48 px (4 px Abstand), mit mt-4 bei 32 px (12 px IM Umschalter). Kommentar in page.tsx.
  • Annahme „Listen-Widgets skalieren nicht“: note, calendar, favorites, link, search behalten ihre Schriftgroessen — bei mehr Platz zeigen sie mehr Inhalt. Nur ihre Aussen-Innenabstaende sind halbiert.
  • Beobachtung: Das ClockConfig-Formular traegt weiterhin ein hart englisches Label „Timezone“ und die Zeile „Saving...“ (ausserhalb des Auftrags, unveraendert). Ebenso „Title“ im Notiz-Formular und der englische Kalender-Hinweis.
  • Rechner-Speicherzeile (h-7 text-xs) bleibt bewusst klein; die Tasten fuellen ihre Rasterzelle (h-full min-h-7), damit das 4x5-Raster mitwaechst.

Task Commits

  1. Task 1: Raster, Umrechnung, Store, API-Specs — 3f5afb0 (feat) — 9 Dateien
  2. Task 2a: Skalierung, Punktgroesse, i18n — 2d8efe1 (feat) — 12 Dateien
  3. Task 2b: Abstaende halbiert — a175c00 (feat) — 7 Dateien
  4. Task 3: Anwenderhandbuch — 1aefaa3 (docs) — 1 Datei

commits: 4 gemessen aus git rev-list --count 50f201e..HEAD (Ledger plan_head_before = 50f201ecc1292232ff96e13226b62386cfc36e76). actuals.tokens = 291.763 Zeichen ueber die 29 geaenderten Dateien / 4 = 72.941 (Methode wie 260914-m97; der reine Diff waere 65.872 Zeichen = 16.468). Der Plan schaetzte 120.000 bei confidence: low.

git log --oneline 50f201e..HEAD (vor dem SUMMARY, 07:21Z):

1aefaa3 docs(quick-260916-bwo): Anwenderhandbuch — feines Raster, Uhrzeit skaliert mit der Kachel, Schriftgroesse in Punkt
a175c00 feat(quick-260916-bwo): Abstaende halbiert — Seitenrahmen p-3 (alle Seiten), Dashboard p-2, Grid 8 px, Widget-Innenabstaende
2d8efe1 feat(quick-260916-bwo): Widget-Inhalt skaliert per Container-Queries (Uhr, Stoppuhr, Rechner), Punktgroesse der Uhrzeit einstellbar
3f5afb0 feat(quick-260916-bwo): Dashboard-Raster verdoppelt (24 Spalten, 20 px, 8 px Abstand), Konstanten x2, einmalige Umrechnung gespeicherter Anordnungen mit Marker __gridVersion

git status --porcelain (vor dem SUMMARY): leer. git status -sb | head -1: ## main...origin/main.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Versehentliches git stash waehrend Task 1, sofort zurueckgeholt

  • Found during: Task 1, zwischen GREEN-Lauf und Commit
  • Issue: In einem Messbefehl (Zaehlen der Registry-Testfaelle) lief ein git stash -q mit, das die sechs geaenderten, noch nicht committeten Dateien in stash@{0} verschob (die drei neuen Dateien blieben als unversioniert liegen). Die Stash-Liste war vor Beginn leer (geprueft), der einzige Eintrag war mein eigener auf HEAD 50f201e; kein Worktree, keine Nebenlaeufer.
  • Fix: git stash pop (Ausgabe: alle sechs Dateien wieder geaendert, refs/stash@{0} … geloescht), Stash-Liste danach leer; Inhalt per grep und erneutem Testlauf (Test Files 4 passed (4) / Tests 29 passed (29)) bestaetigt, dann erst committet.
  • Files modified: keine (Wiederherstellung des Arbeitsbaums)
  • Commit: — (3f5afb0 enthaelt den unveraenderten Stand)

2. [Rule 1 - Bug] Gate grep -c " p-6" in app-shell.tsx musste 0 sein, lieferte 1

  • Found during: Task 2b, Verify-Block
  • Issue: Die Klasse p-6 war korrekt durch p-3 ersetzt, aber mein neuer JSX-Kommentar enthielt wörtlich „statt p-6 (24 px)“ und traf das Muster.
  • Fix: Kommentar umformuliert („statt vorher 24 px (Stufe 6)“); Gate danach 0, p-3 weiterhin 2 (Kommentar + Klasse), tsc 0. Innerhalb der Allowlist, vor dem Commit 2b.
  • Files modified: apps/web/src/components/layout/app-shell.tsx
  • Commit: a175c00

Messungen, die dem Plan widersprechen (beide Zahlen oben festgehalten)

  1. Task 1 Web-Vitest-Zeile: Plan 22, gemessen 29 — it.each ueber 8 Typen in widget-registry.test.tsx (Plan zaehlte it(-Bloecke). Gesamtsuite 286 wie geplant.
  2. Commit 2a: Plan „11 Dateien“, gemessen 12 — der Plan listet selbst 12 auf; 9 + 12 + 7 + 1 = 29 stimmt.
  3. Abbild-Probe: Plan „APP_VERSION -> kurzer SHA“, gemessen v1.0.0-10-g1aefaa3 (git describe seit dem Tag v1.0.0); der kurze SHA 1aefaa3 steht in APP_COMMIT.

Was bewusst offen bleibt

  • Browser-Nachweis (Bounding-Boxen, Rasterstufe, Skalierung, Punktgroesse, SQL-Probe der Umrechnung, Rechner bei Mindestgroesse): nicht im Executor moeglich (jsdom rechnet keine Container-Queries, keine Container gebaut) — Human-Check end-of-phase, Anleitung unten.
  • Rechner bei Mindestgroesse (4x8 = 8 Zeilen x 20 px + 7 x 8 px = 216 px): Der Plan nennt den heutigen Ueberlauf bei alter Mindestgroesse; ob der Rechner mit h-full min-h-7-Tasten (5 x 28 px = 140 px + Anzeige 40 + Speicherzeile 28 + Luecken ca. 24 = ca. 232 px) bei 216 px bedienbar bleibt, ist im Browser zu messen (Punkt 7 unten). Faellt er durch, ist die Rueckfalloption des Plans „nur die Anzeige skalieren“ — vorher messen.
  • Hart englische Texte im Einstellungsformular („Timezone“, „Saving...“, „Title“, Kalender-Hinweis): ausserhalb des Auftrags, unveraendert.
  • Marker-Persistenz bei dauerhaft scheiterndem PUT: bis zum ersten gelungenen Speichern rechnet jedes Laden erneut aus den alten DB-Werten (korrekt, aber jedes Laden loest einen PUT-Versuch aus, console.error je Versuch).
  • Live (tessera.ctl.de): Bestand nicht gemessen; nach dem naechsten Deploy rechnet jeder Benutzer seine Anordnung beim ersten Laden selbst um.

Fuer den Verifizierer/Orchestrator (Browser-Nachweis)

Vorher docker compose up -d --build web (Web-Abbild vom 14.09.; up allein baut nicht neu). Die API braucht keinen Neubau. Anmelden mit Benutzername admin, Kennwort admin123. Alle Bounding-Boxen per Playwright boundingBox(), nie per fetch aus der Seite.

  1. Anordnung anlegen: Dashboard -> „Dashboard bearbeiten“ -> Uhr und Suchleiste hinzufuegen, Uhr rechts neben die Suchleiste ziehen, Bearbeitungsmodus beenden (speichert).
  2. Abstaende: main.app-shell-main links vs. erstes [data-widget-id] links -> 28 px (+/-1; vorher 56); zwei horizontal benachbarte Widgets -> Luecke 8 px (+/-1; vorher 16); main oben vs. erstes Widget -> 60 px (+/-1; vorher 88). Zweite Seite /admin/users: main-Rand zum ersten Inhaltselement 12 px (vorher 24).
  3. Raster: Uhr im Bearbeitungsmodus um EINE Stufe nach rechts ziehen -> Versatz eine Spaltenbreite (Containerbreite - 8*23 - 8*2) / 24 px (bei 1200 px ca. 41 px, vorher ca. 83 px).
  4. Skalierung: Uhr auf 12x8 vergroessern -> getComputedStyle(time).fontSize deutlich groesser als bei 4x4 (Zahlen notieren; Erwartung 4x4 ca. 38-42 px, 12x8 ca. 80+ px); verkleinern -> schrumpft; time.dataset.fontMode === 'auto'. Ist cqh 0 (Uhr unsichtbar klein), fehlt die definite Hoehe — dann widget-wrapper.tsx pruefen (Rumpf h-full in der Karte h-full).
  5. Punktgroesse: Einstellungen -> Dashboard -> Widgets, „Uhr #1“ aufklappen, „Schriftgröße der Uhrzeit (Punkt)“ auf 36, Feld verlassen -> zurueck zum Dashboard: time.style.fontSize === '36pt', getComputedStyle(time).fontSize === '48px' bei 4x4 UND 12x8, data-font-mode="fixed", Datum 14.4pt. Dann 300 -> Fehlermeldung „Bitte geben Sie eine Zahl zwischen 8 und 200 ein oder lassen Sie das Feld leer.“, kein Speichern (Netzwerk: kein PATCH). Feld leeren, Enter -> wieder automatisch.
  6. Umrechnungs-Probe (SQL, lokale DB, Rolle tessera hat BYPASSRLS, Schalter AUS). Zuerst den gespeicherten Stand lesen und die Kennungen notieren:
    docker exec tessera-ctl-db-1 psql -U tessera -d tessera -At -c 'SELECT "userId", layouts::text FROM "DashboardLayout";'
    docker exec tessera-ctl-db-1 psql -U tessera -d tessera -At -c 'SELECT id, "widgetType" FROM "WidgetInstance" ORDER BY "createdAt";'
    
    Erwartung nach Schritt 1: __gridVersion: 2, Suchleiste x: 0, w: 12, h: 4, Uhr x: 12, w: 4, h: 4 (o. ae.). Dann die Zeile in ALTE Einheiten ohne Marker zurueckschreiben (<uhr-id>/<such-id> aus der zweiten Abfrage; userId des lokalen Admins ist 1166431d-a556-47d0-9e19-6bd3616f148f):
    UPDATE "DashboardLayout"
    SET layouts = jsonb_build_object(
      'lg', jsonb_build_array(
        jsonb_build_object('i','<uhr-id>','x',6,'y',0,'w',2,'h',2),
        jsonb_build_object('i','<such-id>','x',0,'y',0,'w',6,'h',2)),
      'md','[]'::jsonb,'sm','[]'::jsonb,'xs','[]'::jsonb,'xxs','[]'::jsonb),
      "updatedAt" = now()
    WHERE "userId" = '1166431d-a556-47d0-9e19-6bd3616f148f';
    
    Falls noch keine Widgets/Anordnung existieren (lokal waren es zum Zeitpunkt der Ausfuehrung 0/0), stattdessen alles in einem Rutsch anlegen — Uhr und Suchleiste als WidgetInstance plus die Anordnung in ALTEN Einheiten (Admin-User 1166431d-a556-47d0-9e19-6bd3616f148f, Mandant e2592907-0118-4cce-a407-7e27d375b68d; beide Kennungen am 16.09. aus "User" gelesen, bei einer neu aufgesetzten DB erneut per SELECT id, "tenantId" FROM "User" WHERE username = 'admin'; holen):
    INSERT INTO "WidgetInstance" (id, "userId", "tenantId", "widgetType", config, "createdAt", "updatedAt") VALUES
      ('probe-suche-1', '1166431d-a556-47d0-9e19-6bd3616f148f', 'e2592907-0118-4cce-a407-7e27d375b68d', 'search', '{}'::jsonb, now(), now()),
      ('probe-uhr-1',   '1166431d-a556-47d0-9e19-6bd3616f148f', 'e2592907-0118-4cce-a407-7e27d375b68d', 'clock',  '{"timezone":"Europe/Berlin","showDate":true}'::jsonb, now() + interval '1 second', now());
    INSERT INTO "DashboardLayout" (id, "userId", "tenantId", layouts, "createdAt", "updatedAt") VALUES
      ('probe-layout-1', '1166431d-a556-47d0-9e19-6bd3616f148f', 'e2592907-0118-4cce-a407-7e27d375b68d',
       '{"lg":[{"i":"probe-suche-1","x":0,"y":0,"w":6,"h":2},{"i":"probe-uhr-1","x":6,"y":0,"w":2,"h":2}],"md":[],"sm":[],"xs":[],"xxs":[]}'::jsonb,
       now(), now());
    
    (Alte Einheiten: Suchleiste 6x2 ab Spalte 0, Uhr 2x2 ab Spalte 6 — die frueheren Vorgabegroessen, Uhr direkt rechts neben der Suchleiste.) Seite laden -> Uhr steht rechts neben der Suchleiste (Uhr links = Suchleiste rechts + 8 px, Bounding-Boxen), nicht halb so gross und nicht verrutscht; danach:
    docker exec tessera-ctl-db-1 psql -U tessera -d tessera -At -c 'SELECT layouts->''__gridVersion'', layouts->''lg'' FROM "DashboardLayout";'
    
    -> 2 und Suchleiste x: 0, w: 12, h: 4, Uhr x: 12, w: 4, h: 4. Seite ein ZWEITES Mal laden -> Werte unveraendert (keine erneute Verdopplung). Aufraeumen der Probe: DELETE FROM "DashboardLayout" WHERE id = 'probe-layout-1'; DELETE FROM "WidgetInstance" WHERE id IN ('probe-suche-1','probe-uhr-1'); (nur falls per INSERT angelegt).
  7. Rechner und Stoppuhr: beide hinzufuegen, vergroessern -> Anzeige und Tasten wachsen mit, das Tastenraster bleibt 4x5 und ueberlaeuft nicht; auf Mindestgroesse (Rechner 4x8, Stoppuhr 4x4) verkleinern -> Rechner bedienbar, Stoppuhr-Anzeige lesbar. Ueberlaeuft der Rechner bei 4x8: Hoehe der Tasten und der Anzeige notieren (siehe „Was bewusst offen bleibt“).

Fuer den Changelog

  • Das Dashboard-Raster ist doppelt so fein: Widgets lassen sich in kleineren Schritten verschieben und in der Groesse ziehen, bleiben dabei aber genauso gross wie bisher. Bereits gespeicherte Anordnungen werden beim ersten Aufruf automatisch uebernommen und verrutschen nicht.
  • Uhrzeit, Stoppuhr und Rechner wachsen und schrumpfen jetzt mit ihrer Kachel — eine grosse Uhr-Kachel zeigt eine grosse Uhrzeit.
  • Die Uhr hat eine neue Einstellung: unter Einstellungen > Dashboard laesst sich die Schriftgroesse der Uhrzeit fest in Punkt (8 bis 200) vorgeben; leer gelassen passt sie sich weiter automatisch an.
  • Die Raender sind ueberall enger: der aeussere Seitenrahmen auf allen Seiten, der Abstand zwischen den Widgets und die Innenabstaende in den Widgets sind halbiert.
  • Das Anwenderhandbuch beschreibt das feine Raster, die mitwachsende Uhrzeit und die neue Schriftgroessen-Einstellung.

Self-Check: PASSED

  • Dateien: alle 5 neu angelegten Dateien vorhanden (grid-layout-migration.ts/.test.ts, dashboard-store.test.ts, clock-font-size.ts, widget-settings-panel.test.tsx).
  • Commits: 3f5afb0, 2d8efe1, a175c00, 1aefaa3 in git log --oneline --all gefunden; git ls-remote origin main = 1aefaa3; main == origin/main.
  • commits: 4 = git rev-list --count 50f201e..HEAD; git status --porcelain leer vor dem SUMMARY.