` `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`).
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 ``, 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. |
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.
- [ ] 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