Files
tessera-ctl/.planning/quick/260922-vdk-dashboard-raster-misst-seine-breite-nich/260922-vdk-PLAN.md
T

24 KiB
Raw Blame History

phase, plan, type, wave, depends_on, autonomous, requirements, files_modified, estimate, must_haves
phase plan type wave depends_on autonomous requirements files_modified estimate must_haves
quick-260922-vdk 01 execute 1
true
QUICK-260922-VDK
apps/web/src/components/dashboard/dashboard-grid.tsx
apps/web/src/components/dashboard/dashboard-grid.test.tsx
CHANGELOG.md
tokens raw_tokens tasks confidence
55000 55000 2 low
truths artifacts key_links
Ein Dashboard, das beim Öffnen leer ist, misst die verfügbare Breite, sobald die erste Kachel erscheint: das Raster füllt den Inhaltsbereich bis zum rechten Rand, rechts bleibt kein toter Streifen, in den sich keine Kachel ziehen lässt.
Die Messung überlebt den Wechsel Leerzustand → gefüllt, weil sie am eingehängten Knoten selbst hängt (Ref-Rückruf) und nicht an einem Effekt, der nur beim ersten Einhängen läuft.
Gemessen wird synchron in der Commit-Phase, bevor gezeichnet wird — der bisherige Zwischenzustand mit dem angenommenen Startwert wird nie sichtbar (auch nicht auf dem heute schon funktionierenden Pfad „Neuladen mit Kacheln“).
Ändert sich die Fenstergröße, folgt die Rasterbreite; ein Fenster-Horcher ist das Sicherheitsnetz für den Fall, dass der ResizeObserver nichts meldet.
Das Ziehverhalten bleibt exakt wie heute: FREE_PLACEMENT_COMPACTOR mit preventCollision, ein belegtes Feld bleibt blockiert, nichts wird zur Seite geschoben (Nutzerentscheidung 22.09.2026); Leerzustand, BREAKPOINTS, COLS, rowHeight, margin und applyConstraintMinima sind unverändert.
Alle Tore grün: 12 Tests in dashboard-grid.test.tsx (10 alte unverändert + 2 neue), Web gesamt ≥ 661 Tests in 81 Dateien, `tsc --noEmit` 4/4, Biome 5/5 mit weiterhin genau 53 Warnungen in web, `as unknown as` in web weiterhin 6.
apps/web/src/components/dashboard/dashboard-grid.tsx — Messung über den Ref-Rückruf `measureRef` (synchrone Erstmessung + ResizeObserver am jeweils eingehängten Knoten, Trennen im null-Zweig), `applyWidth`-Wächter, Fenster-Horcher; deutscher Kommentarblock `quick-260922-vdk` mit dem Warum
apps/web/src/components/dashboard/dashboard-grid.test.tsx — zwei neue Fälle (Leerzustand → gefüllt misst 1000; resize-Ereignis misst 1600) und `vi.unstubAllGlobals()` im bestehenden `afterEach`
CHANGELOG.md — neuer Abschnitt „### Behoben“ unter „Unveröffentlicht“ mit einem Stichpunkt in Alltagssprache
Kachel hinzufügen → `widgets.length` wird 1 → der Zweig mit dem Raster rendert → React hängt den `<div>` ein → `measureRef(node)` → synchrone Messung + `observer.observe(node)` → `setWidth` noch vor dem Zeichnen → `Responsive width` → Spaltenbreite und Breakpoint stimmen
Letzte Kachel entfernt → Knoten wird ausgehängt → `measureRef(null)` → `observerRef.current.disconnect()` (kein zurückgegebener Aufräum-Rückgabewert, damit React den null-Aufruf beibehält) → kein weiterlaufender Beobachter
`window` resize → Horcher → `nodeRef.current.getBoundingClientRect().width` → `applyWidth` (verwirft 0 und nicht endliche Werte) → `setWidth`

Quick-Aufgabe 260922-vdk: Das Dashboard-Raster misst seine Breite auch aus dem Leerzustand heraus

Auf einem Dashboard, das beim Öffnen leer ist, bleibt das Raster für die ganze Sitzung bei der angenommenen Breite von 1200 Pixeln stehen. Die erste hinzugefügte Kachel rechnet deshalb mit 20 statt 24 Spalten und 51,6 px Spaltenbreite, das Raster endet bei 1200 px und rechts davon liegt ein toter Bereich (gemessen: ~460 px bei 1920 px Bildschirmbreite), in den sich keine Kachel ziehen lässt. Diese Aufgabe hängt die Messung an den Knoten statt an den ersten Einhäng-Zeitpunkt, misst vor dem ersten Zeichnen und ergänzt einen Fenster-Horcher als Netz.

Purpose: Fehlerbehebung aus einer bereits abgeschlossenen Messung — die Ursache steht fest, dieser Plan setzt nur noch um und sichert sie mit einem Regressionstest ab. Output: geänderte dashboard-grid.tsx mit deutschem Warum-Kommentar, zwei neue Tests (zuerst rot), ein Changelog-Stichpunkt, alle Tore grün, Prüfliste für den Browser-Rundgang im SUMMARY.

Befund (gemessen, nicht neu zu untersuchen)

Der bisherige Code legt den Beobachter in einem Effekt mit leerer Abhängigkeitsliste an und bricht ab, wenn der Ref noch leer ist. Hängt DashboardGrid ein, während das Dashboard null Kacheln hat, greift der frühe Rücksprung in den Leerzustand vor dem <div> mit dem Ref: der Ref ist leer, der Effekt bricht ab — und läuft wegen der leeren Abhängigkeitsliste nie wieder, auch nicht, wenn später Kacheln erscheinen und der <div> tatsächlich entsteht. Die Breite bleibt für die ganze Sitzung beim Startwert.

Belege (bestätigt, nicht zu wiederholen):

  • Echter Linux-Client (Tessera-1.3.0.AppImage, WebKitGTK), leeres Dashboard, eine Kalender-Kachel hinzugefügt: Kachel 469 px breit in einem 1000 px breiten Container — das ist die Rechnung für 1200.
  • Derselbe Client nach einem Neuladen mit vorhandener Kachel: 459 px in 1176 px, also richtig — weil die Ladeschranke in (portal)/page.tsx das Raster aus- und wieder einhängt, der Ref beim Einhängen also existiert.
  • Screenshot des Nutzers (1920×1045): Spaltenbreite 51,5 px, Platzhalter klebt an Spalte 13, rechte Rasterkante bei x = 1459 bei einem Inhaltsbereich bis ~1920.

react-grid-layout vergleicht den Breakpoint strikt größer als (width > breakpoint), 1200 ist damit nicht lg, sondern md → 20 Spalten.

Gebundene Entscheidungen (nicht neu verhandeln)

  1. Ref-Rückruf statt Einmal-Effekt. Die Messung hängt am jeweils eingehängten Knoten und überlebt Aus- und Einhängen.
  2. Synchron vor dem ersten Zeichnen. Der Ref-Rückruf läuft in der Commit-Phase; die dort ausgelöste Zustandsänderung wird vor dem Zeichnen abgearbeitet. Ein zusätzlicher useLayoutEffect ist damit überflüssig.
  3. Fenster-Horcher als Netz, zusätzlich zum ResizeObserver, nicht statt seiner.
  4. Verhalten sonst unverändert. FREE_PLACEMENT_COMPACTOR mit preventCollision bleibt (Nutzerentscheidung 22.09.2026: ein belegtes Feld bleibt blockiert, nichts wird zur Seite geschoben). Leerzustand, BREAKPOINTS, COLS, rowHeight, margin, applyConstraintMinima bleiben wortgleich.
  5. Startwert 1200 bleibt. Er lebt nur noch bis zur Commit-Phase desselben Einhängens. Genau diesen Übergang 1200 → gemessen macht der Pfad „Neuladen mit Kacheln“ heute schon in Produktion, und er ist nachweislich richtig (459 px in 1176 px) — ein anderer Startwert würde eine bisher unerprobte Breakpoint-Folge einführen, ohne etwas zu verbessern.
  6. Nur apps/web. Keine API, kein Prisma, kein Docker, keine neuen Pakete (ResizeObserver und resize sind Browser-Schnittstellen).

<execution_context> @/.claude/gsd-core/workflows/execute-plan.md @/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/STATE.md @apps/web/src/components/dashboard/dashboard-grid.tsx @apps/web/src/components/dashboard/dashboard-grid.test.tsx @apps/web/src/test/fake-resize-observer.ts @apps/web/src/test/setup.ts

Schnittstellen, die der Executor kennen muss

  • apps/web/src/test/fake-resize-observer.ts exportiert stubResizeObserver({ width, height }): void; es ersetzt den globalen ResizeObserver per vi.stubGlobal durch eine Klasse, deren observe() den Rückruf sofort und synchron mit new DOMRectReadOnly(0, 0, width, height) aufruft. Zurückgesetzt wird nur durch vi.unstubAllGlobals() — vi.restoreAllMocks() reicht dafür nicht.
  • apps/web/src/test/setup.ts legt global einen ResizeObserver-Ersatz an, der immer 1200×800 meldet. Ohne stubResizeObserver misst jeder Test also 1200 — der neue Test wäre damit blind für genau diesen Fehler.
  • dashboard-grid.test.tsx ersetzt Responsive durch einen Durchreicher, der die Props in captured.props ablegt (vi.hoisted, Mock per importOriginal, damit noCompactor echt bleibt). Die gemessene Breite ist dadurch als captured.props?.width prüfbar.
  • jsdom liefert für getBoundingClientRect() ohne Zutun 0 — die synchrone Erstmessung schlägt im Test also nicht durch, der gestubbte Beobachter liefert den Wert. Für den Fenster-Test wird Element.prototype.getBoundingClientRect gezielt überschrieben.
  • new DOMRect(0, 0, w, h) gibt es in jsdom (die Datei fake-resize-observer.ts nutzt bereits DOMRectReadOnly) — damit braucht der Test keinen Cast, und der Zähler as unknown as bleibt bei 6.
Aufgabe 1: Messung an den Knoten hängen (Ref-Rückruf, synchrone Erstmessung, Fenster-Horcher) — Ende-zu-Ende „leeres Dashboard → erste Kachel füllt die volle Breite“, zuerst rot apps/web/src/components/dashboard/dashboard-grid.test.tsx, apps/web/src/components/dashboard/dashboard-grid.tsx Zuerst die Tests, beide müssen vor der Änderung rot sein:
- **Test 10 — Leerzustand → gefüllt:** `stubResizeObserver({ width: 1000, height: 800 })`; `captured.props` auf null setzen; mit `widgets: []` und leeren Layouts rendern → die Überschrift des Leerzustands steht da und `captured.props` ist weiterhin null (das Raster ist nicht eingehängt); danach **`rerender`** derselben Instanz mit einer Uhr-Kachel und einem passenden `lg`-Eintrag → `captured.props?.width` ist 1000. Rot vorher: der Wert bleibt 1200.
- **Test 11 — Fenstergröße:** `stubResizeObserver({ width: 1000, height: 800 })`; mit einer Uhr-Kachel rendern → Breite 1000; danach `Element.prototype.getBoundingClientRect` per `vi.spyOn(...).mockReturnValue(new DOMRect(0, 0, 1600, 800))` überschreiben und `window.dispatchEvent(new Event('resize'))` in `act(...)` auslösen → `captured.props?.width` ist 1600. Rot vorher: der Wert bleibt 1000, weil es keinen Fenster-Horcher gibt.

Die zehn bestehenden Fälle bleiben unverändert und grün — insbesondere Test 4 (Raster-Konstanten), Test 7 (Compactor-Pin mit preventCollision) und Test 9/9b (Minima).
1. **`dashboard-grid.test.tsx`** (zuerst, rot): `act` zusätzlich aus `@testing-library/react` importieren, `stubResizeObserver` aus `@/test/fake-resize-observer`. Im bestehenden `afterEach` nach `vi.restoreAllMocks()` eine Zeile `vi.unstubAllGlobals()` ergänzen (ohne sie bliebe der gestubbte Beobachter für alle folgenden Dateien stehen). Am Ende des bestehenden `describe('DashboardGrid', …)` die zwei Fälle aus dem ``-Block anfügen, benannt „quick-260922-vdk Test 10: …“ und „quick-260922-vdk Test 11: …“, Stil und Kommentarsprache wie die Nachbarfälle (deutsch, mit dem Warum in einer Zeile). Rot-Lauf ausführen und die Fehlermeldungen für das SUMMARY festhalten.
2. **`dashboard-grid.tsx`**: `useCallback` zum Import aus `react` hinzufügen. Die bisherige Kombination aus `containerRef` und dem Effekt mit leerer Abhängigkeitsliste ersetzen durch:
   - zwei Refs: `nodeRef` (Typ `HTMLDivElement | null`, hält den gerade eingehängten Knoten für den Fenster-Horcher) und `observerRef` (Typ `ResizeObserver | null`, hält den laufenden Beobachter);
   - `applyWidth` als `useCallback` mit leerer Abhängigkeitsliste: nimmt eine Zahl, ruft `setWidth` nur bei endlichem Wert größer 0 auf. React verwirft gleiche Werte selbst, ein zusätzlicher Vergleich ist unnötig und eine Rückkopplungsschleife damit ausgeschlossen (T-VDK-01);
   - `measureRef` als `useCallback` über `applyWidth`, Signatur nimmt `HTMLDivElement | null` und gibt **nichts** zurück: erst einen eventuell laufenden Beobachter trennen und `observerRef` leeren, dann `nodeRef` auf den Knoten setzen, bei `null` zurückspringen, sonst `applyWidth(node.getBoundingClientRect().width)` (das ist die synchrone Erstmessung in der Commit-Phase), dann einen neuen `ResizeObserver` anlegen, der `entries[0].contentRect.width` an `applyWidth` weitergibt, ihn auf den Knoten setzen und in `observerRef` merken. Wichtig: keine Aufräumfunktion zurückgeben — React 19 ruft den Rückruf sonst beim Aushängen nicht mehr mit `null` auf, und genau dieser Zweig ist hier der Aufräumpfad;
   - einen `useEffect` über `applyWidth`, der `resize` am `window` anmeldet und im Aufräumschritt wieder abmeldet; der Horcher misst `nodeRef` erneut, wenn dort ein Knoten liegt;
   - am Raster-`<div>` `ref={measureRef}` setzen (exakt dieser Name, ein Tor prüft ihn).

   Alle vier Hooks stehen **vor** dem frühen Rücksprung in den Leerzustand, damit die Hook-Reihenfolge stabil bleibt — derselbe Grund, den der Kommentar bei `effectiveLayouts` bereits festhält.

3. **Warum-Kommentar** über der Messung, deutsch, im Stil der vorhandenen Blöcke (`quick-260916-dyv`, `quick-260916-bwo`), Präfix `quick-260922-vdk:`. Inhalt in eigenen Worten: der frühe Rücksprung in den Leerzustand rendert den gemessenen Knoten gar nicht erst, ein Effekt mit leerer Abhängigkeitsliste sieht ihn deshalb nie wieder; der Ref-Rückruf folgt dem Knoten über Aus- und Einhängen hinweg und misst in der Commit-Phase, also vor dem Zeichnen; die Folge der alten Annahme war ein `md`-Breakpoint mit 20 Spalten (strikter Größer-Vergleich in RGL), 51,6 px Spaltenbreite und ein toter Streifen rechts; der Fenster-Horcher ist ein Netz für Fälle, in denen der Beobachter nichts meldet; der Wächter verwirft 0 und nicht endliche Werte, damit eine kurzzeitig zusammengefallene Fläche das Raster nicht auf Null setzt. Den Startwert-Absatz (Entscheidung 5 oben) mit aufnehmen.

4. Nichts anderes anfassen: Leerzustand, `BREAKPOINTS`, `COLS`, `rowHeight`, `margin`, das fehlende `containerPadding`, `dragConfig`, `resizeConfig`, `FREE_PLACEMENT_COMPACTOR`, `applyConstraintMinima` und die `data-grid`-Erzeugung bleiben wortgleich.

Commit: `fix(quick-260922-vdk): Dashboard-Raster misst seine Breite auch aus dem Leerzustand heraus` (Wortlaut frei, Stil der Nachbarcommits, Co-Authored-By-Zeile).
cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run src/components/dashboard/dashboard-grid.test.tsx && pnpm --filter @tessera/web exec tsc --noEmit && grep -q "ref={measureRef}" apps/web/src/components/dashboard/dashboard-grid.tsx && grep -q "addEventListener('resize'" apps/web/src/components/dashboard/dashboard-grid.tsx && grep -q "unstubAllGlobals" apps/web/src/components/dashboard/dashboard-grid.test.tsx && grep -q "quick-260922-vdk" apps/web/src/components/dashboard/dashboard-grid.tsx `dashboard-grid.test.tsx` hat 12 grüne Fälle (10 alte wortgleich, 2 neue); die zwei neuen waren nachweislich zuerst rot, die Rot-Meldungen („1200 statt 1000“ bzw. „1000 statt 1600“) stehen im SUMMARY. `tsc --noEmit` ohne Befund. Die Messung hängt am Ref-Rückruf `measureRef`, trennt den Beobachter im null-Zweig, misst synchron bei jedem Einhängen und hat einen Fenster-Horcher. Compactor, Konstanten, Leerzustand und `applyConstraintMinima` sind unverändert (Tests 4/7/9/9b belegen es). Ein Commit mit Scope `quick-260922-vdk`. Aufgabe 2: Changelog-Stichpunkt, volle Tore, Zähler, Prüfliste für den Browser-Rundgang CHANGELOG.md 1. **`CHANGELOG.md`**: unter „## Unveröffentlicht“ nach der bestehenden Liste „### Geändert“ einen neuen Abschnitt „### Behoben“ anlegen (Reihenfolge wie in 1.3.0: Neu, Geändert, Behoben) mit genau einem Stichpunkt in Alltagssprache, Tonlage der Nachbarzeilen, ohne Fachbegriffe: „Dashboard: war das Dashboard beim Öffnen leer, nutzte die erste hinzugefügte Kachel nur einen Teil der Breite — rechts blieb ein toter Streifen, in den sich keine Kachel ziehen ließ; das Raster misst die verfügbare Breite jetzt in jedem Fall und folgt auch einer Änderung der Fenstergröße“.
2. **Volle Tore** ausführen und die Zahlen ins SUMMARY schreiben: `pnpm type-check` (4/4), `pnpm lint` (5/5), `pnpm --filter @tessera/web test`. Ausgangsmessung dieses Plans (22.09., vor der Änderung): Web 81 Dateien / 659 Tests grün, Biome web genau 53 Warnungen, `as unknown as` in web 6. Erwartet nachher: 81 Dateien / 661 Tests, Warnungen und Zähler unverändert. Weicht eine Zahl ab, im SUMMARY benennen statt stillschweigend anpassen.

3. **Prüfliste** für den Orchestrator ins SUMMARY schreiben (Browser, lokal, Playwright-MCP — **nicht** auf dem Testserver), Punkt für Punkt abhakbar; ausdrücklich dazuschreiben, dass Breiten am DOM gemessen werden (`getBoundingClientRect` der Elemente), **nie** per `fetch` aus der Seite heraus:
   (a) Dashboard eines Benutzers ohne Kacheln öffnen (oder alle Kacheln entfernen und neu laden) → Leerzustand mit Bildmarke;
   (b) Bearbeiten → „Widget hinzufügen“ → Uhr: die Kachel erscheint, und beim Ziehen reicht der Platzhalter bis an den rechten Rand des Inhaltsbereichs — kein toter Streifen;
   (c) messen: Breite von `.react-grid-layout` gleicht der Breite des umgebenden Inhaltsbereichs (Abweichung höchstens der Rand von 8 px); vorher lag sie bei 1200 px unabhängig von der Fensterbreite;
   (d) eine zweite Kachel auf ein belegtes Feld ziehen → sie springt zurück, nichts wird zur Seite geschoben (unverändert gewollt); Größe ziehen stoppt am Nachbarn;
   (e) Fenster schmaler und wieder breiter ziehen → das Raster folgt, Kacheln bleiben heil;
   (f) Seite mit vorhandenen Kacheln neu laden → weiterhin richtig (der bisher schon funktionierende Pfad), und beim ersten Zeichnen ist kein Sprung von schmal auf breit zu sehen;
   (g) letzte Kachel entfernen → Leerzustand erscheint, danach eine neue Kachel hinzufügen → wieder volle Breite (der Beobachter wurde sauber getrennt und neu angehängt).

Commit: `docs(quick-260922-vdk): Changelog - Dashboard-Raster misst seine Breite auch aus dem Leerzustand` (nur CHANGELOG.md; Akte und STATE macht der Orchestrator; Co-Authored-By-Zeile).
cd /home/vicolab/projects/tessera-ctl && grep -q 'rechts blieb ein toter Streifen' CHANGELOG.md && awk '/^## Unveröffentlicht/{f=1} /^## 1\.3\.0/{f=0} f&&/^### Behoben/{n++} END{exit !(n==1)}' CHANGELOG.md && pnpm type-check && pnpm lint && pnpm --filter @tessera/web lint 2>&1 | grep -q 'Found 53 warnings' && test "$(grep -rn 'as unknown as' apps/web/src --include=*.ts --include=*.tsx | wc -l)" -eq 6 && pnpm --filter @tessera/web test Am Ende der Aufgabe: der Orchestrator geht die siebenpunktige Prüfliste im Browser durch (lokal, Playwright-MCP). Entscheidend ist Punkt (b)/(c): auf einem beim Öffnen leeren Dashboard füllt die erste Kachel den Inhaltsbereich bis zum rechten Rand, und die gemessene Rasterbreite stimmt mit der Breite des Inhaltsbereichs überein. Der Changelog trägt unter „Unveröffentlicht“ genau einen neuen Abschnitt „### Behoben“ mit dem einen Stichpunkt. `pnpm type-check` 4/4 und `pnpm lint` 5/5 ohne Befund der Stufe `error`, Biome web weiterhin genau 53 Warnungen, `as unknown as` in web weiterhin 6, Web-Tests 81 Dateien / 661 grün. Die siebenpunktige Prüfliste steht im SUMMARY. Insgesamt genau zwei Commits mit Scope `quick-260922-vdk` (`git log --oneline -2`).

<threat_model> ASVS-Stufe 1, Blockschwelle high (jede high-Bedrohung MUSS mitigiert sein).

Vertrauensgrenzen

Grenze Beschreibung
Browser-Layout → React-Zustand Gemessene Breiten (ResizeObserver, getBoundingClientRect, resize) werden zu Zustand und steuern die Rastergeometrie; die Werte sind nicht vertrauenswürdig im Sinne von „immer sinnvoll“ (0 während eines Übergangs, nicht endlich bei zusammengefallener Fläche)
Knoten-Lebensdauer → Beobachter-Lebensdauer Der ResizeObserver hängt an einem Knoten, der beim Wechsel Leerzustand ↔ gefüllt aus- und eingehängt wird
Neue Vertrauensgrenze Keine. Kein Netzverkehr, keine API, keine Benutzereingabe, keine Persistenz in diesem Plan

STRIDE-Register

ID Kategorie Komponente Schwere Disposition Maßnahme
T-VDK-01 Denial of Service (Rückkopplung Messung → Zustand → Layout → Messung, bis der Tab steht) applyWidth / ResizeObserver-Rückruf in dashboard-grid.tsx medium mitigate applyWidth ruft setWidth nur bei endlichem Wert größer 0; React verwirft gleiche Werte (Object.is) und rendert dann gar nicht neu — der Beobachter meldet nach dem Einschwingen denselben Wert und die Kette endet. Zusätzlich beobachtet der Beobachter den äußeren <div>, dessen Breite nicht von der Rastergeometrie abhängt.
T-VDK-02 Denial of Service (Ressourcenleck: Beobachter oder Fenster-Horcher überleben das Aushängen, bei jedem Wechsel Leerzustand ↔ gefüllt einer mehr) measureRef null-Zweig, useEffect-Aufräumschritt medium mitigate Der Ref-Rückruf trennt den laufenden Beobachter als erste Handlung und gibt bewusst keine Aufräumfunktion zurück, damit React ihn beim Aushängen mit null aufruft; der Fenster-Horcher meldet sich im Aufräumschritt des Effekts ab. Punkt (g) der Prüfliste geht den Wechsel im Browser durch.
T-VDK-03 Denial of Service (Messung auf einer zusammengefallenen Fläche setzt die Breite auf 0 → Raster unbedienbar, Kacheln unerreichbar) Synchrone Erstmessung in jsdom-losen Übergängen, getBoundingClientRect low mitigate Wächter „größer 0 und endlich“; bleibt eine Messung aus, gilt weiter der letzte gültige Wert statt 0.
T-VDK-04 Tampering (stille Verhaltensänderung beim Ziehen: ein belegtes Feld würde plötzlich nachgeben) FREE_PLACEMENT_COMPACTOR, dragConfig, resizeConfig medium mitigate Diese Stellen werden nicht angefasst; Test 7 pinnt den Compactor als echten noCompactor plus preventCollision: true, Test 6 die Zieh-Konfiguration, Test 4 die Raster-Konstanten — alle laufen im Tor der Aufgabe 1 mit. Prüfliste (d) belegt es zusätzlich im Browser.
T-VDK-05 Information Disclosure — low accept Es werden nur Layout-Maße des eigenen Fensters gelesen; nichts verlässt den Browser, nichts wird geloggt oder gespeichert.
T-VDK-06 Repudiation — low accept Reine Anzeige-Geometrie ohne Fremdwirkung; kein Audit-Log nötig, ASVS 1 genügt.
T-VDK-SC Tampering (Lieferkette) npm-Installationen high mitigate Nicht ausgelöst: KEINE neuen Pakete — ResizeObserver, getBoundingClientRect, window-Ereignisse und DOMRect sind Browser-Schnittstellen, act und vi.spyOn sind bereits vorhanden. Will der Executor doch etwas installieren: Stopp und Rückfrage an den Orchestrator, keine Installation ohne ausdrückliche Freigabe.
</threat_model>
Automatisch (Executor, in den ``-Blöcken): gezielter Testlauf von `dashboard-grid.test.tsx` (12 Fälle), `tsc --noEmit`, Struktur-Tore auf Ref-Rückruf, Fenster-Horcher, `unstubAllGlobals` und den Warum-Kommentar; danach Changelog-Tore, `pnpm type-check` 4/4, `pnpm lint` 5/5, Biome web genau 53 Warnungen, `as unknown as` web 6, voller Web-Testlauf.

Rot-Nachweis (Aufgabe 1): beide neuen Fälle laufen vor der Änderung rot; die Meldungen gehören ins SUMMARY.

Manuell (Orchestrator, Prüfliste aus Aufgabe 2 Punkt 3, lokal im Browser, nicht auf dem Testserver): leeres Dashboard → erste Kachel füllt die Breite; gemessene Rasterbreite gleicht der Breite des Inhaltsbereichs; belegtes Feld bleibt blockiert; Fenstergröße; Neuladen mit Kacheln unverändert; Leerzustand → gefüllt → Leerzustand → gefüllt.

<success_criteria>

  • Auf einem beim Öffnen leeren Dashboard füllt die erste hinzugefügte Kachel den Inhaltsbereich; rechts kein toter Streifen (Test 10 + Prüfliste b/c)
  • Die Messung folgt dem Knoten über Aus- und Einhängen und läuft synchron vor dem Zeichnen (Ref-Rückruf measureRef, Struktur-Tor + Prüfliste f/g)
  • Fenstergröße ändern misst neu (Test 11 + Prüfliste e)
  • Ziehverhalten unverändert: belegtes Feld bleibt blockiert, nichts wird verschoben (Tests 4/6/7/9/9b grün, Prüfliste d)
  • 12 Fälle in dashboard-grid.test.tsx, Web gesamt 81 Dateien / 661 Tests grün
  • pnpm type-check 4/4, pnpm lint 5/5, Biome web 53 Warnungen, as unknown as web 6
  • Changelog: genau ein Stichpunkt unter „Unveröffentlicht → Behoben“
  • Genau zwei Commits mit Scope quick-260922-vdk
  • Nur apps/web und CHANGELOG.md angefasst; keine neuen Pakete </success_criteria>
`.planning/quick/260922-vdk-dashboard-raster-misst-seine-breite-nich/260922-vdk-SUMMARY.md` schreiben, wenn beide Aufgaben fertig sind — mit Rot-Meldungen der zwei neuen Tests, den gemessenen Zahlen (Tests, Warnungen, Zähler) und der siebenpunktigen Prüfliste für den Browser-Rundgang.