Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
36 KiB
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-dyv | 01 | ui, dashboard |
|
|
|
|
|
ec1b0ce582 |
|
|
|
|
|
|
15min | 2026-09-16 | complete |
Quick 260916-dyv: Dashboard-Nachbesserung — Mindestgroessen inhaltsgetrieben, Schalter unten rechts, ganze Kachel als Griff Summary
Jedes Widget schrumpft wieder auf seine kleinste bedienbare Kachel — auch die bereits gespeicherten, weil dashboard-grid.tsx die in der Datenbank persistierten minW/minH beim Rendern aus WIDGET_CONSTRAINTS ueberschreibt (und zu kleine gespeicherte Groessen auf das Minimum anhebt); der Bearbeiten-Schalter sitzt in einer festen Leiste unten rechts, sodass das Grid 28 statt 60 px unter der Kopfzeile beginnt; im Bearbeitungsmodus ist die ganze Kachel der Griff, Eingabefelder/Knoepfe/Links/.widgetNoDrag starten kein Ziehen (cancel), und preventCollision: true am noCompactor verhindert Ueberlappen beim Ablegen und beim Vergroessern in einen Nachbarn. Drei Commits auf main (dc992c9, dbbd54f, cf97b5b), gepusht (7a6f42e..cf97b5b, nimmt die zwei Akten-Commits df16f46, ec1b0ce mit), CI-Lauf siehe unten.
Performance
- Duration: 15 min bis zum Push (Start 08:32:57Z, Push 08:43:00Z), CI-Ende 08:47:45Z
- Tasks: 3/3
- Files: 12 (11 Web, 1 Handbuch) —
git diff --stat df16f46 -- . ':!.planning'->12 files changed, 538 insertions(+), 99 deletions(-)
Ausgangslage und Bezugspunkt
HEAD bei Start ec1b0ce (Plan-Commit), Arbeitsbaum sauber, main 2 Akten-Commits vor origin/main (df16f46, ec1b0ce). Code-Gates gegen df16f46 (Code seit 1aefaa3 unveraendert). Vorbedingung Task 3: curl localhost:3002/api/v1/version -> {"version":"1.26.2"}, docker ps | grep -c '^gitea-runner$' -> 1.
Baseline frisch (Dashboard-Suite): Test Files 10 passed (10) / Tests 63 passed (63) — identisch mit der Planung.
Zweig-Hinweis: Alle Commits liegen auf main — Projektpraxis (branching_strategy: none in .planning/config.json, alle Vorgaenger-Quick-Tasks und der Plan-Commit ebenfalls auf main), vom Orchestrator so vorgeschrieben (Commit + git push). Der generische Executor-Schutz „nicht auf den Default-Zweig committen“ wurde deshalb bewusst nicht angewandt.
Abweichung von den Planzahlen (Addendum des Plan-Pruefers)
Der Plan-Pruefer hat eine Test-9-Variante verlangt (gespeicherte Groesse unter dem neuen Minimum wird angehoben). Umgesetzt als Test 9b in dashboard-grid.test.tsx. Dadurch verschieben sich alle Zaehlungen um +1:
| Gate | Plan | Gemessen | Befehl |
|---|---|---|---|
| Task 1 Dashboard-Suite | 10 / 64 | Test Files 10 passed (10) / Tests 65 passed (65) |
pnpm -C apps/web exec vitest run src/components/dashboard |
dashboard-grid.test.tsx nach Task 1 |
6 | Tests 7 passed (7) |
pnpm -C apps/web exec vitest run src/components/dashboard/dashboard-grid.test.tsx |
| Task 2 Teil-Suite | 13 / 76 | Test Files 13 passed (13) / Tests 77 passed (77) |
pnpm -C apps/web exec vitest run src/components/dashboard "src/app/(portal)/page.test.tsx" src/messages |
dashboard-grid.test.tsx nach Task 2 |
9 | Tests 10 passed (10) |
wie oben, einzelne Datei |
| Web gesamt | 47 / 293 | Test Files 47 passed (47) / Tests 294 passed (294) |
pnpm -C apps/web exec vitest run |
Unveraendert wie geplant: widget-registry.test.tsx 11, stopwatch-widget.test.tsx 8, page.test.tsx 3, src/messages 6, API Test Files 67 passed (67) / Tests 1078 passed (1078).
Was react-grid-layout mit einem zu kleinen Eintrag sonst taete (gemessen, 2.2.3): synchronizeLayoutWithChildren (chunk-WGL5FSZH.mjs:559-562) klont den gespeicherten Eintrag woertlich — h: 8 bleibt h: 8, auch wenn minH (aus der Ueberschreibung) 9 ist. Geklemmt wird nur beim Vergroessern/Verkleinern: minMaxSize.constrainSize (chunk-KDANGDDL.mjs:26-31, clamp(h, item.minH ?? 1, ...)) und die Pixel-minConstraints an Resizable (chunk-WGL5FSZH.mjs:472-475). correctBounds (chunk-76RTO6EO.mjs:257-277) prueft nur Spaltenueberlauf, nicht Minima. Folge ohne Anhebung: ein gespeicherter Rechner mit h: 8 bliebe bis zum ersten Anfassen des Groessen-Griffs unten abgeschnitten (unterste Tastenreihe fehlt) und spraenge dann auf 9. Mit der Anhebung in applyConstraintMinima (w: Math.max(entry.w, minW), h: Math.max(entry.h, minH)) ist er sofort vollstaendig; beim naechsten Speichern landen 9 Zeilen in der DB.
Task 1 — Mindestgroessen, Ueberschreibung, Stoppuhr (dc992c9, 5 Dateien)
RED pnpm -C apps/web exec vitest run src/components/dashboard/widget-registry.test.tsx src/components/dashboard/dashboard-grid.test.tsx:
× Test A (quick-260916-dyv): Minima = kleinste bedienbare Kachel je Typ, Vorgaben unveraendert
× quick-260916-bwo Test 5: Widget ohne gespeicherten Eintrag bekommt die Vorgaben und die inhaltsgetriebenen Minima als data-grid
× quick-260916-dyv Test 9: gespeicherte minW/minH werden in JEDEM Breakpoint aus WIDGET_CONSTRAINTS ueberschrieben, x/y/w/h bleiben, unbekannte Typen und Schluessel unveraendert
× quick-260916-dyv Test 9b: gespeicherte Groesse unter dem neuen Minimum wird auf das Minimum angehoben (Rechner h 8 -> 9, w bleibt wenn >= minW)
Tests 4 failed | 14 passed (18)
(Plan erwartete 3 failed; +1 durch Test 9b.)
GREEN — WIDGET_CONSTRAINTS auf die Tabelle, applyConstraintMinima + useMemo (effectiveLayouts, vor dem Leerzustand wegen Hook-Reihenfolge) in dashboard-grid.tsx, layouts={effectiveLayouts} und data-grid-Fallback aus effectiveLayouts.lg, Stoppuhr-Knoepfe px-2 py-1 text-xs und Zeile gap-1 py-1.
Gates: Dashboard-Suite 10 passed (10) / 65 passed (65); Greps minW: 3, minH: 9 1, minW: 2, minH: 2, defaultW: 4, defaultH: 4 1, defaultW: 12, defaultH: 4 1; effectiveLayouts 3; Stoppuhr px-2 py-1 text-xs 4, px-4 py-1.5 text-sm 0; TSC_web=0.
Falsifizierung (a) — clock.minW/minH in widget-registry.tsx auf den bwo-Wert 4/4: Tests 3 failed | 15 passed (18): „Test A (quick-260916-dyv): Minima = kleinste bedienbare Kachel je Typ, Vorgaben unveraendert“, „quick-260916-bwo Test 5: …“, „quick-260916-dyv Test 9: …“. Zurueckgesetzt, gruen. (Lehre: git checkout -- <Datei> an der noch unkommittierten Datei hat die gesamte Task-1-Aenderung zurueckgesetzt — erneut angewandt, Gates erneut gruen; bei (b)/(c) deshalb per sed/Python zurueckgestellt.)
Task 2 — Leiste unten rechts, Griff, cancel, preventCollision (dbbd54f, 8 Dateien)
RED pnpm -C apps/web exec vitest run src/components/dashboard/dashboard-grid.test.tsx "src/app/(portal)/page.test.tsx":
× Test 1: Ansichtsmodus — Stift in fester Leiste unten rechts, kein "Widget hinzufuegen", kein mt-8/top-2, Grid direkt im Container p-2
× Test 2: Bearbeitungsmodus — "Widget hinzufuegen" links neben dem Haekchen in derselben Leiste, Grid im Bearbeitungsmodus
× quick-260916-dyv Test 6: dragConfig-Pin — handle Karte, cancel fuer Interaktives, threshold 3; resizeConfig folgt dem Bearbeitungsmodus
× quick-260916-dyv Test 7: Compactor-Pin — echter noCompactor plus preventCollision: true, compact ist Identitaets-Kopie (freie Platzierung)
× quick-260916-dyv Test 8: cancel/handle-Semantik im DOM — Karte ist Griff, Loesch-Knopf/Eingaben/widgetNoDrag passen auf cancel, Kopfleiste als Overlay nur im Bearbeitungsmodus
Tests 5 failed | 8 passed (13)
page-Test 3 (Umschalten) war wie vorhergesagt bereits gruen. importOriginal fuer react-grid-layout laeuft in jsdom — kein Rueckfall auf den Objekt-Mock noetig.
Messbefund Test 7: Der echte noCompactor.compact ist cloneLayout -> cloneLayoutItem (chunk-76RTO6EO.mjs:204-223): kopiert i/x/y/w/h unveraendert, normalisiert aber moved/static zu false und fuegt minW/maxW/minH/maxH/... als undefined hinzu. Das im Plan vorgesehene toEqual gegen die Eingabe scheitert deshalb an moved: false, static: false (erster GREEN-Lauf: 1 failed | 76 passed (77)). Test 7 pinnt nun toMatchObject({ i, x, y, w, h, moved: false, static: false }), neue Referenzen fuer Array und Element, und dass die Eingabe unveraendert bleibt — inhaltlich dieselbe Aussage (Identitaet als Kopie, keine Verschiebung).
GREEN — WIDGET_DRAG_HANDLE_SELECTOR, WIDGET_DRAG_CANCEL_SELECTOR, FREE_PLACEMENT_COMPACTOR: Compactor = { ...noCompactor, preventCollision: true }, dragConfig mit handle/cancel/threshold: 3; widget-wrapper.tsx neu (Karte im Bearbeitungsmodus widget-drag-handle cursor-grab active:cursor-grabbing border-primary/40, Overlay-Kopfleiste absolute inset-x-0 top-0 z-10 flex h-5 ... bg-muted/70 mit Sechs-Punkte-SVG, Tooltip dragHint, data-testid="widget-drag-head", Loesch-Knopf h-5 w-5 mit data-no-drag=""; Rumpf @container-size h-full als reines Literal); edit-mode-toggle.tsx shadow-lg, inaktiv border border-border bg-card; page.tsx feste Leiste fixed bottom-6 right-6 z-20 flex items-center gap-2 (erst „Widget hinzufuegen“, dann Umschalter), Grid direkt im Container; dragHint in de/en direkt nach deleteTooltip.
Gates: Teil-Suite 13 passed (13) / 77 passed (77) (dashboard-grid 10, page 3, messages 6); Greps preventCollision: true 2 (Definition + Kommentar), WIDGET_DRAG_CANCEL_SELECTOR 2, threshold: 3 2 (Wert + Kommentar), mt-8 0, top-2 0, Leiste 1, h-[6px] 0, data-no-drag 3, cursor-grab 2, "dragHint" je 1, W_EXIT=0, W_EMPTY=0, TSC_web=0.
Falsifizierungen (b) und (c) (Rueckstellung per sed/Python, danach Tests 13 passed (13) und nur die Arbeitsdateien im Diff):
| Falsifizierung | Eingriff | Rot (Testname woertlich) |
|---|---|---|
| (b1) | Zeile cancel: WIDGET_DRAG_CANCEL_SELECTOR, aus dragConfig entfernt |
`Tests 1 failed |
| (b2) | FREE_PLACEMENT_COMPACTOR = { ...noCompactor } (ohne preventCollision) |
`Tests 1 failed |
| (c) | <div className="mt-8"> um DashboardGrid wieder eingefuegt |
`Tests 1 failed |
Task 3 — Handbuch, Abschluss-Gates, Push, CI (cf97b5b, 1 Datei)
Handbuch-Greps: Unten rechts auf dem Dashboard 1, Oben rechts auf dem Dashboard 0, gerade noch bedienbar 1, an ihren Ausgangspunkt 1.
Abschluss-Gates (alle nach dem Handbuch-Edit, vor dem Commit):
| Gate | Ergebnis |
|---|---|
Web pnpm -C apps/web exec vitest run |
Test Files 47 passed (47) / Tests 294 passed (294) (Plan 293, +1 Test 9b) |
API pnpm -C apps/api exec vitest run |
Test Files 67 passed (67) / Tests 1078 passed (1078) |
tsc --noEmit shared / api / web |
TSC_packages/shared=0, TSC_apps/api=0, TSC_apps/web=0 |
pnpm install --frozen-lockfile |
FROZEN=0 |
git diff --stat df16f46 -- . ':!.planning' |
GIT_EXIT=0, 12 files changed, 538 insertions(+), 99 deletions(-) |
Unantastbar-Stichprobe (.env*, Compose, Lockfile, package.json, prisma, api/src/dashboard, globals.css, umlaut-dictionary.ts, dashboard-store.ts, grid-layout-migration.ts) |
U_EXIT=0, U_EMPTY=0 (leer) |
git status -sb vor Push / nach git fetch |
## main...origin/main [voraus 5] / ## main...origin/main |
Push: 7a6f42e..cf97b5b main -> main (fuenf Commits: df16f46, ec1b0ce Akten; dc992c9, dbbd54f, cf97b5b Code/Handbuch).
CI-Lauf nach dem Push
| Feld | Versuch 1 (einziger) |
|---|---|
| Lauf-ID | 353 (event push, ref main, head_sha cf97b5b64b48ac08cb410966e98cf532f600681d) |
| status / conclusion | completed / success |
| started_at / completed_at | 10:43:05 / 10:47:45 (+02:00) — 4 min 40 s (15 Polls a 20 s; beim ersten Poll 7 s nach dem Push bereits in_progress, keine Warteschlange) |
| Jobs | Lint & Type Check success (08:43:05-08:43:53Z), Tests success (08:43:55-08:44:51Z), Build & Publish Images success (08:44:53-08:47:45Z) |
api:beta node APP_VERSION APP_CHANNEL APP_COMMIT |
v1.0.0-16-gcf97b5b beta cf97b5b — APP_COMMIT = git rev-parse --short HEAD = cf97b5b; APP_VERSION im git describe-Format (wie in 260916-bwo gemessen) |
docker image inspect Created api:beta |
2026-09-16T10:46:42+02:00 (per docker pull geholt, kein Container gebaut oder gestartet) |
Beobachtung per Hintergrund-Skript (watch-ci.sh im Scratchpad, Token nur in einer Shell-Variablen, nie ausgegeben), Gitea-API GET /repos/schalli/tessera-ctl/actions/runs?limit=5, Eintrag mit head_sha == PUSHED. Keine Infrastruktur- oder Code-Fehler, kein erneuter Lauf noetig.
Git-Stand vor dem SUMMARY (Wahrheit)
git status --porcelain (leer):
git log --oneline ec1b0ce..HEAD:
cf97b5b docs(quick-260916-dyv): Anwenderhandbuch — Bearbeiten-Schalter unten rechts, ganze Kachel ziehbar, Mindestgroessen
dbbd54f feat(quick-260916-dyv): Bearbeiten-Schalter unten rechts (Rand oben 28 px), ganze Kachel als Griff mit Kopfleiste, cancel-Selektor, kein Ueberlappen beim Ablegen
dc992c9 fix(quick-260916-dyv): Mindestgroessen inhaltsgetrieben (kleinste bedienbare Kachel je Typ), gespeicherte Minima ueberschrieben, Stoppuhr-Bedienleiste kompakt
commits: 3 gemessen: git rev-list --count ec1b0ce5823f5f5275d0e79e231e8378f04ad0ea..HEAD -> 3. actuals.tokens 11339 = Zeichen des Diffs git diff df16f46 -- . ':!.planning' (45356) / 4; zum Vergleich Zeichen der 12 geaenderten Dateien gesamt 170986 / 4 = 42746. Plan-Schaetzung 90000 (confidence low) — deutlich zu hoch fuer den Diff.
Gemessene Befunde und Entscheidungen
- Zweiter Grund fuer „nicht klein genug“ (der im Auftrag nicht genannte): Gespeicherte
minW/minHschlagendata-grid.synchronizeLayoutWithChildrenliestdata-gridnur fuer Kinder ohne Eintrag; alle bestehenden Widgets haben einen Eintrag mit den in 260916-bwo verdoppelten Minima. OhneeffectiveLayoutsaendert die Konstantentabelle fuer den User NICHTS Sichtbares — Test 9 ist der entscheidende Test, nicht Test A. - Kollision und
preventCollision:noCompactor = { type: null, allowOverlap: false, compact: cloneLayout },preventCollisionfehlt (false). Beim Ziehen auf ein belegtes Feld (moveElement->moveElementAwayFromCollision,chunk-76RTO6EO.mjs:279-328, ZweigcollisionNorth && compactType === null): das GEZOGENE springt auf die Zeile des getroffenen (collidesWith.y = itemToMove.y), das getroffene rutscht um seine EIGENE Hoehe nach unten (itemToMove.y += itemToMove.h), keine Kaskade, keine Nachpruefung;compactist Identitaet — Ueberlappungen bleiben. Beim Vergroessern mit demse-Griff istshouldMoveItemfalse,moveElementwird nicht gerufen: die Ueberlappung entsteht stumm. MitpreventCollision: true:if (hasCollisions && preventCollision) { l.x = oldX; l.y = oldY; l.moved = false; return layout; }— Platzhalter bleibt am Ursprung; Vergroessern prueftgetAllCollisionsam neuen Mass und behaelt das alte. Entscheidung:preventCollisionstattverticalCompactor, weil die freie Platzierung (Commitc8f3361, „prevent auto-compaction on drag“) gewollt ist — Kompaktierung wuerde Luecken automatisch schliessen.preventCollisionlebt am Compactor-Objekt (compactor.preventCollision ?? false,chunk-WGL5FSZH.mjs:666), daher ein eigenes Objekt. - Griff/cancel: react-draggable 4.7.0 prueft
cancelNACHhandle, beides vom Ereignisziel aufwaerts bis zum RGL-Element (Draggable.js:417,matchesSelectorAndParentsTo). Die Karte ist Kind des RGL-Elements, also passt jedes Ziel in der Karte auf den Griff;cancelgewinnt fuer Eingabefelder/Knoepfe/Links/[contenteditable]/[data-no-drag]/.widgetNoDrag. RGL haengt.react-resizable-handleselbst voran (chunk-WGL5FSZH.mjs:526). Die 12 bislang totenwidgetNoDrag-Stellen (Favoriten 5, Link 7) sind damit wirksam. Test 8 fuehrtmatches/closestmit dem echten Selektor in jsdom aus (T-DYV-02: ungueltiger Selektor wuerfe dort). - Overlay-Kopfleiste statt Kopfleiste im Fluss: Der Rumpf bleibt
h-full, die Hoehenkette Karte -> Rumpf bleibt definit,cqhloest weiter auf (Grund fuerh-fullin 260916-bwo). Eine Kopfleiste im Fluss haette den Rumpf im Bearbeitungsmodus um 20 px gekuerzt und den Rechner bei Mindestgroesse beschnitten. Preis: die obersten 20 px des Widget-Inhalts liegen im Bearbeitungsmodus unter der halbtransparenten Leiste (bg-muted/70) — im Ansichtsmodus gibt es keine Leiste. - Stoppuhr-Bedienleiste als einzige Innen-Aenderung: Bei Mindestbreite 4 Spalten (lg 191 px, Rumpf 179 px) ueberlief die laufende Stoppuhr (Stop + Runde + Reset mit
px-4 text-smca. 222 px) — Reset abgeschnitten; kompakt (px-2 text-xs) ca. 151 px passt, Zeile 32 statt 48 px. Ohne diese Aenderung waere das inhaltsgetriebene Minimum 6x4 — BREITER als heute, das Gegenteil des Auftrags. Der Rechner wird NICHT umgebaut: er bekommt das hoehere Minimum 3x9 (heute 4x8), weil bei 8 Zeilen (216 px) die unterste Tastenreihe (+/-, 0, Komma, =) abgeschnitten ist (Anzeige 40 + Speicherzeile 28 + 5 Reihen 28 + Abstaende = 240 px > 216 px). - Stift unten rechts: Im Ansichtsmodus kann der 36x36-px-Stift (bei
bottom-6 right-6) im schlimmsten Fall die untere rechte Ecke eines Widgets verdecken, das bis an den rechten Rand und bis unter den Viewport-Rand reicht — hingenommen (der bisherige „Widget hinzufuegen“-Knopf lag bereits dort, nur im Bearbeitungsmodus). fixedundz-20:fixedbezieht sich auf das Viewport (kein transformierter Vorfahr — der bisherige Knopf funktionierte an derselben Stelle);z-20liegt ueber RGL-Elementen (gezogenes Elementz-index: 3).- Mock per
importOriginal: laeuft in jsdom;noCompactorist echt,Responsivebleibt Mock.
Mindestgroessen-Tabelle (gerechnet; Browser-Bestaetigung durch den Orchestrator)
Spaltenbreite lg bei 1200 px = 41,67 px (Kachelbreite(w) = 49,67w - 8), beim Orchestrator-Viewport von 260916-bwo 60 px; Kachelhoehe(h) = 28h - 8 px.
| Typ | bwo-Minimum | neu | px bei Spalte 41,67 / 60 | Rechnung | Bewusste Grenze |
|---|---|---|---|---|---|
| clock | 4/4 | 2/2 | 91x48 / 128x48 | Zeit min(20cqw, 50cqh) ca. 17-20 px, 8 Zeichen tabular ca. 80 px in 83 px |
Datum (falls an) 10 px darunter, eng |
| search | 6/4 | 6/2 | 290x48 / 400x48 | Auswahl 120 + Eingabe >= 80 + Knopf 40 + Abstaende 28 = 268 <= 278 Rumpf; Eingabe h-8 32 px in 48 px |
3 Spalten (141 px) waeren unbrauchbar — Breite bleibt 6 |
| calendar | 6/6 | 3/3 | 141x76 / 196x76 | eine Terminzeile 48 px + p-1.5 = 60 <= 76; Leertext 3 Zeilen text-sm 60 px |
knapp; mehr Termine brauchen mehr Hoehe |
| note | 4/6 | 4/4 | 191x104 / 264x104 | Kopfzeile ca. 36 + Vorschau >= 2 Zeilen (40) = 76 <= 104; Editiermodus Werkzeugleiste ca. 29 + 2 Zeilen | — |
| calculator | 4/8 | 3/9 | 141x244 / 196x244 | Breite 4 Tasten x >= 30 + 3x4 + p-1 8 = 140 <= 141; Hoehe 40 + 4 + 28 + 4 + 5x28 + 4x4 = 156 + p-1 8 = 240 <= 244 |
Minimum HOEHER als 4x8, weil bei 8 Zeilen die unterste Tastenreihe abgeschnitten ist; gespeicherte 4x8-Rechner werden auf h 9 angehoben |
| favorites | 4/6 | 3/3 | 141x76 / 196x76 | Umschaltzeile (nur Bearbeitungsmodus) ca. 110 <= 133; drei Listenzeilen (20 + 4) in 68 px; Kachelraster grid-cols-3 ca. 40-px-Kacheln |
— |
| link | 4/4 | 3/2 | 141x48 / 196x48 | Listenzeile Symbol 20 + Titel in 40 px Rumpf; Umschaltzeile <= 133 | Kachelansicht (Symbol 32 + Text 16 + Abstand) braucht 3 Zeilen — der User zieht eine Zeile hoeher |
| stopwatch | 4/4 | 4/3 | 191x76 / 264x76 | NUR mit kompakter Leiste: laufend Stop+Runde+Reset ca. 151 <= 179; Zeile 32 px; Anzeige min(16cqw, 35cqh) = 26,6 px im Rest von 32 px |
Rundenliste (max-h-32) braucht mehr Hoehe — Nebenfunktion |
Vorgaben (defaultW/defaultH) unveraendert: clock 4/4, search 12/4, calendar 8/12, note 6/8, calculator 6/10, favorites 6/10, link 4/4, stopwatch 6/6 — neue Widgets erscheinen wie gewohnt; alle Minima <= Vorgabe (it.each prueft es).
Durchsicht Dashboard (gerechnet; Browser-Bestaetigung durch den Orchestrator)
| Widget | Standardgroesse | Mindestgroesse | Befund |
|---|---|---|---|
| Uhr | 4x4, Zeit skaliert (bwo) | 2x2: Zeit ca. 17-20 px lesbar | gut |
| Suche | 12x4 | 6x2: Auswahl, Eingabe >= 80 px, Knopf | gut |
| Kalender | 8x12 | 3x3: Leertext oder eine Terminzeile | auffaellig: knapp, aber bedienbar |
| Notizen | 6x8 | 4x4: Titel + 2 Zeilen | gut |
| Rechner | 6x10, Tasten skalieren (bwo) | 3x9: alle 5 Tastenreihen inkl. „=“ | gut; Todo: Innenaufbau kompakter, damit < 9 Zeilen reichen |
| Favoriten | 6x10 | 3x3: Umschaltzeile + Listenzeilen | gut |
| Link | 4x4 | 3x2: eine Listenzeile | auffaellig: Kachelansicht braucht 3 Zeilen |
| Stoppuhr | 6x6 | 4x3: Anzeige 26,6 px, Stop/Runde/Reset sichtbar | gut; Todo: Rundenliste bei Minimum |
Bleibt als Todo (nicht Teil dieses Auftrags):
- Rechner-Innenaufbau kompakter (Speicherzeile/Anzeige), damit weniger als 9 Zeilen reichen.
- Rundenliste / laufende Stoppuhr bei Minimum 4x3: die Liste (
max-h-32) hat dort keinen Platz. - Hart englische Texte im Einstellungsformular (aus 260916-bwo: „Timezone“, „Saving...“, „Title“, englischer Kalender-Hinweis).
- Link-Kachelansicht bei 2 Zeilen (braucht 3).
Deviations from Plan
Auto-fixed Issues
1. [Addendum des Plan-Pruefers] Zu kleine gespeicherte w/h werden angehoben
- Found during: Task 1 (vom Pruefer vorgegeben)
- Issue: Ein gespeicherter Rechner mit
h: 8bliebe trotzminH: 9unten abgeschnitten, bis er einmal angefasst wird (RGL klemmt nur beim Resize). - Fix:
applyConstraintMinimasetztw: Math.max(w, minW),h: Math.max(h, minH)im selben Durchlauf; Test 9b. - Files modified:
dashboard-grid.tsx,dashboard-grid.test.tsx - Commit:
dc992c9
2. [Rule 1 - Messbefund] Test 7 nicht per toEqual gegen die Eingabe
- Found during: Task 2 GREEN
- Issue: Der echte
cloneLayoutItemnormalisiertmoved/staticzufalse;toEqualscheiterte. - Fix:
toMatchObjectauf Kernfelder +moved: false, static: false, Referenzen ungleich, Eingabe unveraendert. - Files modified:
dashboard-grid.test.tsx - Commit:
dbbd54f
3. [Prozess] Falsifizierung (a) hat per git checkout die unkommittierte Registry-Aenderung mit zurueckgesetzt
- Fix: Aenderung erneut angewandt, alle Task-1-Gates erneut gemessen (identisch), dann committet. (b)/(c) per
sed/Python zurueckgestellt.
Sonst: Plan exakt wie geschrieben ausgefuehrt. Keine neuen Pakete, kein Schema, keine .env*/Compose/Lockfile-Aenderung, umlaut-dictionary.ts unangetastet.
Threat Flags
Keine neue Angriffsflaeche ausserhalb des <threat_model>: keine API-Aenderung, keine Benutzerdaten in Selektoren oder Styles, effectiveLayouts ersetzt manipulierte Minima (T-DYV-01, Test 9 mit unbekanntem Typ ohne Absturz).
Known Stubs
Keine.
Was bewusst offen bleibt
- Der Browser-Nachweis (8 Schritte, unten) — Sache des Orchestrators/Verifizierers; das lokale Web-Abbild stammt aus
1aefaa3und muss VORHER neu gebaut werden (docker compose up -d --build web). - Die vier Todos aus der Durchsicht (Rechner-Innenaufbau, Rundenliste, englische Formulartexte, Link-Kachelansicht).
- Die 20 px hohe Overlay-Kopfleiste verdeckt im Bearbeitungsmodus den obersten Streifen des Widget-Inhalts halbtransparent — bewusst (Hoehenkette), im Browser beurteilen.
- Der Stift kann im Ansichtsmodus 36x36 px eines Eck-Widgets verdecken.
- Mandantenfaehigkeit/alpha/live: nichts geaendert; gespeicherte Anordnungen dort tragen die alten Minima, bis der Benutzer einmal speichert — sichtbar ist das nicht, weil
effectiveLayoutssie beim Rendern ohnehin ersetzt.
Fuer den Verifizierer/Orchestrator (Browser-Nachweis)
Vorher: docker compose up -d --build web (die API braucht keinen Neubau). Playwright MCP gegen http://localhost:3000, Bounding-Boxen per boundingBox(), nie per fetch aus der Seite. Anmelden admin / admin123.
-
Aufbau: „Dashboard bearbeiten“ (Stift UNTEN RECHTS), nacheinander Uhr, Suchleiste, Rechner, Stoppuhr, Notizen, Kalender, Favoriten, Link hinzufuegen (jedes erscheint unter dem vorigen), Bearbeitungsmodus beenden (Haekchen unten rechts), Seite neu laden.
-
Rand oben:
main.app-shell-mainoben vs. erstes[data-widget-id]oben -> 28 px (+/-1; vorher 60). Linker Rand 28 px, Luecke zwischen Nachbarn 8 px (unveraendert). -
Feste Leiste: Bounding-Box des Stifts (aria-label „Dashboard bearbeiten“): Abstand zum rechten und unteren Viewport-Rand je 24 px, 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.
-
Mindestgroessen: je Widget den Groessen-Griff unten rechts weit nach oben links ziehen; Bounding-Box mit der Tabelle vergleichen (Spaltenbreite
(Containerbreite - 200)/24px, Zeile 20 px + 8 px): Uhr 2x2, Suche 6x2, Kalender 3x3, Notizen 4x4, Rechner 3x9, Favoriten 3x3, Link 3x2, Stoppuhr 4x3. Screenshot je Widget. Bedienbarkeit: Uhrzeit lesbar; Suchfeld >= 80 px, Knopf sichtbar; Rechner ALLE fuenf Tastenreihen inkl. „=“; Stoppuhr starten -> Stop, Runde, Reset sichtbar und klickbar; Notiz Titel + 2 Zeilen; Kalender Leertext/Terminzeile; Favoriten Umschaltzeile + Listenzeilen; Link eine Listenzeile. Faellt ein Widget durch: Kachelgroesse und Grund notieren (dann Konstante + Test A anpassen — kleiner Korrekturlauf). -
Persistierte Minima: nach einem Speichern (Haekchen)
docker exec tessera-ctl-db-1 psql -U tessera -d tessera -At -c 'SELECT layouts->''lg'' FROM "DashboardLayout";'-> Eintraege tragen die neuenminW/minH(und der Rechnerh >= 9). Gegenprobe A (jsonb_set): beim Uhr-EintragminW: 8, minH: 8setzen, Seite neu laden, Uhr auf 2x2 verkleinern -> geht. Gegenprobe B (kompletter Probe-Datensatz in NEUEN Raster-Einheiten mit verdoppelten Minima und einem Rechner mit h 8) — setzt voraus, dass fueradminnoch keineDashboardLayout-Zeile existiert (sonst vorherDELETE FROM "DashboardLayout" WHERE "userId" = (SELECT id FROM "User" WHERE username = 'admin');) und dass zweiWidgetInstance-Zeilen mit den Idsprobe-clockundprobe-calcangelegt werden;__gridVersion: 2ist Pflicht, sonst verdoppeltmigrateGridLayoutsbeim Laden erneut:INSERT INTO "WidgetInstance" (id, "userId", "tenantId", "widgetType", config, "createdAt", "updatedAt") SELECT 'probe-clock', u.id, u."tenantId", 'clock', '{}'::jsonb, now(), now() FROM "User" u WHERE u.username = 'admin'; INSERT INTO "WidgetInstance" (id, "userId", "tenantId", "widgetType", config, "createdAt", "updatedAt") SELECT 'probe-calc', u.id, u."tenantId", 'calculator', '{}'::jsonb, now(), now() FROM "User" u WHERE u.username = 'admin'; INSERT INTO "DashboardLayout" (id, "userId", "tenantId", layouts, "createdAt", "updatedAt") SELECT gen_random_uuid()::text, u.id, u."tenantId", '{"__gridVersion": 2, "lg": [{"i":"probe-clock","x":0,"y":0,"w":4,"h":4,"minW":8,"minH":8}, {"i":"probe-calc","x":4,"y":0,"w":6,"h":8,"minW":4,"minH":8}], "md": [{"i":"probe-clock","x":0,"y":0,"w":4,"h":4,"minW":8,"minH":8}, {"i":"probe-calc","x":4,"y":0,"w":6,"h":8,"minW":4,"minH":8}], "sm": [], "xs": [], "xxs": []}'::jsonb, now(), now() FROM "User" u WHERE u.username = 'admin';Erwartung nach Neuladen: die Uhr (gespeichert
minW/minH 8, also groesser als ihre eigene Kachel 4x4) laesst sich auf 2x2 verkleinern; der Rechner wird SOFORT mit 9 Zeilen gerendert (Bounding-Box-Hoehe 244 px statt 216 px), alle fuenf Tastenreihen sichtbar, ohne dass er angefasst wurde. Nach einem Speichern zeigt die SQL-AbfrageminW 2/minH 2bei der Uhr undh 9, minW 3, minH 9beim Rechner. Danach aufraeumen:DELETE FROM "DashboardLayout" WHERE "userId" = (SELECT id FROM "User" WHERE username = 'admin'); DELETE FROM "WidgetInstance" WHERE id IN ('probe-clock','probe-calc'); -
Ziehen: im Bearbeitungsmodus die Uhr an ihrer MITTE (nicht an der Kopfleiste) 200 px nach rechts ziehen -> sie rastet versetzt ein; Kopfleiste 20 px hoch mit sechs Griff-Punkten und Tooltip „Ziehen Sie die Kachel, um sie zu verschieben“;
mousedownim Suchfeld + 100 px Bewegung -> Text markiert, Suchleiste bewegt sich NICHT; Klick auf das Loesch-Symbol rechts in der Kopfleiste -> Widget verschwindet, kein Ziehen; Groessen-Griff funktioniert weiter. -
Ablegen auf belegter Stelle: die Uhr ueber die Suchleiste ziehen und loslassen -> die Uhr steht wieder am Ausgangsort, die Suchleiste ist nicht verschoben, nichts ueberlappt; die Suchleiste in Richtung Uhr vergroessern -> die Groesse stoppt vor der Uhr. Beobachtetes Verhalten woertlich notieren.
-
Durchsicht: jedes Widget bei Standard- und Mindestgroesse (gut / auffaellig / Todo) in die VERIFICATION uebernehmen.
Nach der Probe: Probe-Datensaetze entfernen, Playwright-Artefakte entfernen.
Fuer den Changelog
- Widgets lassen sich wieder deutlich kleiner ziehen — jedes Widget hat jetzt genau die Mindestgroesse, bei der es gerade noch bedienbar ist; das gilt auch fuer bereits platzierte Widgets.
- Der Bearbeiten-Schalter des Dashboards sitzt jetzt unten rechts; dadurch beginnen die Widgets direkt unter der Kopfzeile.
- Verschieben ist einfacher: im Bearbeitungsmodus laesst sich jedes Widget an einer beliebigen Stelle anfassen (ausser an Eingabefeldern und Knoepfen), ein grauer Griff am oberen Rand zeigt das an. Widgets ueberlappen sich beim Ablegen nicht mehr — ueber einer belegten Stelle springt das Widget an seinen Ausgangspunkt zurueck.
- Die Stoppuhr hat kompaktere Knoepfe und passt so auch in kleine Kacheln.
- Das Anwenderhandbuch beschreibt den neuen Schalter, das Ziehen und die Mindestgroessen.
Self-Check: PASSED
Alle 12 Dateien vorhanden (10 Code/Handbuch-Pfade einzeln geprueft, de.json/en.json eingeschlossen), Commits dc992c9, dbbd54f, cf97b5b in git log --all; nach git fetch: ## main...origin/main (nicht voraus, nicht zurueck); git status --porcelain zeigt nur das ungetrackte SUMMARY (Akten-Commit durch den Orchestrator).