--- phase: quick-260921-gof plan: 01 type: execute wave: 1 depends_on: [] files_modified: - apps/web/src/components/dashboard/widgets/calendar-widget.tsx - apps/web/src/components/dashboard/widgets/calendar-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/favorites-widget.tsx - apps/web/src/components/dashboard/widgets/favorites-widget.test.tsx - apps/web/src/components/layout/sidebar.tsx - apps/web/src/components/layout/sidebar.test.tsx - apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.tsx - apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.test.tsx - apps/web/src/app/(portal)/admin/modules/components/ActivateModuleDialog.tsx - apps/web/src/app/(portal)/admin/modules/grants/page.tsx - apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.test.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/components/DigestIntervalForm.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.test.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx autonomous: true requirements: [D-01, D-02, D-03, D-04, D-05, D-06, D-07] estimate: tokens: 115000 raw_tokens: 115000 tasks: 3 confidence: low must_haves: truths: - "Nach dem Umbau meldet Biome fuer die Regel useExhaustiveDependencies keinen einzigen Befund mehr: weder einen offenen noch einen unbegruendet stillgelegten (D-01)." - "Jede der drei stehengelassenen Abhaengigkeiten traegt ein biome-ignore mit deutschem Grund im Quelltext; es gibt keine stille Unterdrueckung (D-01, D-02)." - "Das Kalender-Widget holt im Browser-Netzwerkprotokoll ueber 60 Sekunden genau einen Termin-Abruf, pro Monatswechsel genau einen weiteren und beim Druck auf den Monatsknopf im laufenden Monat keinen zusaetzlichen (D-03, D-04)." - "Die Stoppuhr laeuft ueber Start, Runde, Stopp und Reset weiter, ohne zu springen, ohne doppelte Geschwindigkeit und ohne Ruecksetzer; pro Klick geht genau ein PATCH an die API (D-03, D-05)." - "Die Auffrisch-Ausloeser bleiben wirksam: ein Bump von sidebarRefreshKey, ein Klick auf Jetzt pruefen in DKV und ein Klick auf Jetzt abrufen im Ausschreibungsradar loesen je genau einen zusaetzlichen Abruf aus (D-03)." - "pnpm lint bleibt 5/5 ohne Befund der Schwere error, der Gesamtstand sinkt von 467 auf 446 Meldungen und steigt an keiner anderen Stelle (D-07)." - "Alle bestehenden Tests bleiben gruen und die Zahl der Testdateien und Tests in apps/web und apps/api sinkt nicht (D-03)." - "Es kommt kein Paket hinzu, keine Version wird angehoben und keine Datei ausserhalb der 15 Befund-Dateien und ihrer Tests wird angefasst (D-06)." artifacts: - "apps/web/src/components/dashboard/widgets/calendar-widget.tsx — ohne ueberfluessiges useMemo, Ladeeffekt an monthDate statt an monthDate.getTime()" - "apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx — Takt-Effekt greift nur noch auf Einzelwerte zu, nicht mehr auf das ganze sw-Objekt" - "apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx — biome-ignore mit deutschem Grund fuer refreshKey" - "apps/web/src/components/layout/sidebar.tsx — biome-ignore mit deutschem Grund fuer sidebarRefreshKey" - "apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.test.tsx — neue Abruf-Zaehlprobe" - ".planning/quick/260921-gof-effekt-abhaengigkeiten-in-react-21-befun/260921-gof-SUMMARY.md — Tabelle mit allen 21 Befunden, Kategorie und Begruendung" key_links: - "useTranslations -> t -> Abhaengigkeitsliste: t wird nirgends mehr in einer Liste gefuehrt, stattdessen wird der uebersetzte Text vor dem Effekt in eine Zeichenkette gelegt (Zeichenketten vergleicht React per Wert, Funktionen per Identitaet)" - "monthDate -> Ladeeffekt -> fetchEvents -> API -> Exchange/EWS: die Kette darf pro Monatswechsel genau einmal auslaufen" - "Marketplace-Store sidebarRefreshKey -> Sidebar-Effekt -> GET /modules/active: bleibt bestehen, sonst zeigt die Seitenleiste nach einer Modul-Aktivierung den alten Stand" - "Eltern-Zaehler refreshKey -> InvoiceHistoryTable / ResultsList -> Neuladen nach Jetzt pruefen bzw. Jetzt abrufen" --- Alle 21 Befunde der Biome-Regel `lint/correctness/useExhaustiveDependencies` in `apps/web` einzeln beurteilen und beheben — kein Sammel-Fix, kein Sammel-Ignorieren. Zweck: Diese Regel wurde beim Lint-Rueckbau am 21.09. bewusst ausgespart, weil hinter jedem Befund ein echter React-Fehler stecken kann. Beim Schreiben der Tests im Vorgang 260921-bi2 ist genau so einer aufgetaucht: ein instabiles `t` aus `useTranslations` in einer Abhaengigkeitsliste hat eine Abruf-Schleife gegen den Server ausgeloest. Diese Klasse wird hier abgearbeitet. Ergebnis: 15 Quelldateien bereinigt, drei bewusst stehengelassene Abhaengigkeiten im Quelltext begruendet, rund zehn neue Zaehlproben in den Tests, die einen Rueckfall sofort sichtbar machen, und ein Nachweis am laufenden System fuer die drei gefaehrlichen Ecken (Kalender, Stoppuhr, Auffrisch-Ausloeser). ## Ausgangsmessung (21.09.2026, selbst nachgemessen) `npx biome lint . --reporter=json --max-diagnostics=20000` meldet 467 Diagnosen (466 Warnungen + 1 Info), davon 21 `useExhaustiveDependencies`, 0 der Schwere `error`. Die 21 Befunde verteilen sich auf 16 Hook-Stellen in 15 Dateien — mehrere Befunde koennen auf derselben Hook-Zeile liegen. ## Die vier Klassen Die Vorgabe kennt drei Kategorien. Biome meldet aber zwei Richtungen: fehlende UND ueberfluessige Abhaengigkeiten. Fuer ueberfluessige, die weder Absicht noch Falle sind, gibt es hier eine vierte Klasse: - **(A) Defekt** — der Effekt liest einen Wert, der sich aendern kann, und laeuft nicht neu. Abhaengigkeit ergaenzen. - **(B) Falle** — die Abhaengigkeit unveraendert zu ergaenzen erzeugt eine Schleife oder macht eine Merkung wirkungslos, weil der Wert bei jedem Durchlauf neu entsteht. Identitaet stabilisieren statt ergaenzen. - **(C) Absicht** — die Abhaengigkeit ist ein Auffrisch-Ausloeser oder der Effekt soll genau einmal laufen. Stehenlassen und mit `biome-ignore` auf Deutsch begruenden. - **(D) Ballast** — ueberfluessige Abhaengigkeit ohne jede Wirkung. Entfernen. Verteilung: 2x (A), 15x (B), 3x (C), 1x (D). Der Anteil (C) liegt bei 14 Prozent und damit deutlich unter der Drittel-Grenze aus D-02. ## Befundtabelle — alle 21, verbindlich | # | Datei | Zeile | Befund | Kat. | Begruendung (was ginge schief) | Aufgabe | |---|-------|-------|--------|------|-------------------------------|---------| | 1 | calendar-widget.tsx | 76 | `config` fehlt | B | Die Merkung liest das ganze `config`-Objekt, die Liste nennt nur drei Felder. `config` als Ganzes einzutragen macht die Merkung wirkungslos, sobald die Kachel-Umgebung ein frisches Objekt reicht. Die Merkung bringt ohnehin nichts, weil nur drei Zahlen/Schalter herausfallen. | 1 | | 2 | calendar-widget.tsx | 76 | `config.lookaheadDays` zu eng | B | Gleiche Stelle, Gegenrichtung desselben Problems. | 1 | | 3 | calendar-widget.tsx | 82 | `monthDate` fehlt | B | Der Ladeeffekt haengt an `monthDate.getTime()`, einem Funktionsaufruf, den Biome nicht verfolgen kann. `monthDate` direkt einzutragen loest bei jedem Druck auf den Monatsknopf im laufenden Monat einen neuen Termin-Abruf aus, weil `showToday` heute immer ein frisches Datum setzt — ueber die API bis zum Exchange-Server. | 1 | | 4 | calendar-widget.tsx | 82 | `monthDate.getTime()` ueberfluessig | B | Gleiche Stelle, Gegenrichtung. | 1 | | 5 | stopwatch-widget.tsx | 81 | `sw` fehlt | B | Der Takt-Effekt greift auf das ganze Zustandsobjekt zu, die Liste nennt drei Felder. `sw` einzutragen baut den 100-ms-Takt bei jeder aufgezeichneten Runde ab und neu auf — ohne Not und mit Taktversatz. | 1 | | 6 | stopwatch-widget.tsx | 81 | `sw.elapsed` zu eng | B | Gleiche Stelle, Gegenrichtung. | 1 | | 7 | VehicleTable.tsx | 182 | `t` fehlt | B | `t` steckt nur in der Fehlermeldung des Ladens. Eingetragen wird `load` bei jedem Durchlauf neu und der Mount-Effekt daran wird zur Abruf-Schleife — der Kommentar an der Stelle beschreibt genau das. | 2 | | 8 | ResultsList.tsx | 89 | `t` fehlt | B | Dieselbe Falle, und dies ist die Stelle, an der sie im Vorgang 260921-bi2 tatsaechlich zugeschnappt ist. | 2 | | 9 | TenderDetail.tsx | 96 | `t` fehlt | B | Dieselbe Falle; getroffen waere der Detail-Abruf beim Oeffnen einer Ausschreibung. | 2 | | 10 | DigestIntervalForm.tsx | 32 | `t` fehlt | B | Dieselbe Falle; getroffen waere das Laden der Zustell-Einstellung. | 2 | | 11 | SourceConfigForm.tsx | 44 | `t` fehlt | B | Dieselbe Falle; getroffen waere das Laden der Quellen-Konfiguration. | 2 | | 12 | RssFeedListForm.tsx | 74 | `loadFeeds` fehlt | B | `loadFeeds` ist eine gewoehnliche Funktion im Rumpf und entsteht bei jedem Durchlauf neu. Eingetragen ergibt das eine endlose Abruf-Schleife gegen die Feed-Liste. | 2 | | 13 | SavedSearchBar.tsx | 141 | `load` fehlt | B | Gleiche Bauart wie 12, gleiche Folge. | 2 | | 14 | favorites-widget.tsx | 99 | `t` fehlt | B | Dieselbe `t`-Falle; der Kommentar an der Stelle nennt sie bereits. | 2 | | 15 | ResultsList.tsx | 89 | `refreshKey` ueberfluessig | C | `refreshKey` ist der Auffrisch-Ausloeser der Elternseite nach Jetzt abrufen. Entfernt man ihn, laedt die Trefferliste nach einem Abruf nicht mehr nach. | 2 | | 16 | InvoiceHistoryTable.tsx | 81 | `refreshKey` ueberfluessig | C | Gleiches Muster: ohne ihn bleibt die DKV-Historie nach Jetzt pruefen auf dem alten Stand. | 3 | | 17 | sidebar.tsx | 57 | `sidebarRefreshKey` ueberfluessig | C | Ausloeser aus dem Marketplace-Speicher. Ohne ihn zeigt die Seitenleiste ein frisch aktiviertes Modul erst nach einem Neuladen der Seite — eine Anzeige, die hinter dem Berechtigungsstand zurueckbleibt. | 3 | | 18 | ActivateModuleDialog.tsx | 47 | `moduleId` ueberfluessig | D | Der Effekt holt `/groups`, was vom Modul nicht abhaengt. Die Elternseite haengt den Dialog je Modul frisch ein und `open` ist dort fest `true` — `moduleId` kann sich waehrend der Lebenszeit gar nicht aendern. Reiner Ballast. | 3 | | 19 | GroupMembersModal.tsx | 89 | `fetchMembers` fehlt | A | Beide Funktionen sind bereits stabil gehalten; `fetchMembers` wechselt nur mit `group.id`. Fehlt sie, zeigt der Dialog bei einem Gruppenwechsel ohne Neuaufbau die Mitglieder der vorigen Gruppe — heute ueber die Oberflaeche nicht erreichbar, aber eine Zeile Umbau davon entfernt. | 3 | | 20 | GroupMembersModal.tsx | 89 | `fetchAllUsers` fehlt | A | Gleiche Stelle, gleiche Begruendung. | 3 | | 21 | grants/page.tsx | 140 | `matches` fehlt | B | `matches` ist eine Pfeilfunktion im Rumpf und entsteht bei jedem Durchlauf neu. Eingetragen laeuft die Filter-Merkung bei jedem Durchlauf neu und ist damit wirkungslos — keine Schleife, aber genau die Merkung weg, wegen der die Stelle gebaut wurde. | 3 | ## Der durchgaengige Griff gegen die `t`-Falle Acht Befunde sind dieselbe Sache: `t` aus `useTranslations` wird ausschliesslich fuer einen Ersatz-Fehlertext innerhalb eines Effekts oder Rueckrufs benutzt. In diesem Projekt ist belegt, dass `t` nicht als stabil angenommen werden darf: die Testattrappen in `apps/web` (zum Beispiel in `ResultsList.test.tsx` und `calendar-widget.test.tsx`) liefern bei jedem Durchlauf eine frische Funktion. `t` in eine Abhaengigkeitsliste einzutragen ist damit in diesem Projekt nachweislich eine Schleife. Der Griff lautet deshalb ueberall gleich und kommt ohne Ausnahme-Kommentar und ohne Referenz-Tricks aus: **den uebersetzten Text vor dem Hook in eine gewoehnliche Konstante legen und diese Konstante in die Abhaengigkeitsliste schreiben.** React vergleicht Zeichenketten per Wert, nicht per Identitaet — die Liste ist damit von Durchlauf zu Durchlauf gleich, der Effekt laeuft weiterhin genau einmal, und die Abhaengigkeit ist ehrlich benannt statt versteckt. ## Regeln fuer diesen Vorgang - `t` kommt in keine Abhaengigkeitsliste. Nirgends. - Ein `biome-ignore` gibt es nur dreimal (Befunde 15, 16, 17), jeweils mit deutschem Grund in einer Zeile. - Die vorhandenen `eslint-disable`-Zeilen fuer diese Regel sind wirkungslos: in diesem Projekt gibt es keine ESLint-Konfiguration mehr, Biome hat sie abgeloest. Alle 11 liegen in Dateien, die hier angefasst werden, und verschwinden dabei — entweder ersatzlos oder durch die `biome-ignore`-Zeile ersetzt. - Kein Umbau ueber den Befund hinaus, kein neues Paket, keine Versionsanhebung, keine Formatierung fremder Dateien (D-06). @~/.claude/gsd-core/workflows/execute-plan.md @~/.claude/gsd-core/templates/summary.md @.planning/STATE.md @CLAUDE.md @apps/web/src/components/dashboard/widgets/calendar-month.ts @apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx @apps/web/src/app/(portal)/modules/tender-radar/page.tsx Aufgabe 1: Kalender und Stoppuhr — die Effekte mit Taktgeber (Befunde 1-6) apps/web/src/components/dashboard/widgets/calendar-widget.tsx, apps/web/src/components/dashboard/widgets/calendar-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/calendar-widget.tsx Zeilen 60-150 (Zustand, Merkung, Ladeeffekt, showPrev/showNext/showToday) - apps/web/src/components/dashboard/widgets/calendar-month.ts Zeilen 40-70 (`resolveCalendarConfig`) und `computeFetchWindow` - apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx Zeilen 45-105 (`computeElapsed`, Zustand, Takt-Effekt) - beide zugehoerigen Testdateien, jeweils Kopf und die vorhandenen Faelle - Kalender, Ersteinblendung: genau ein `fetchSources` und genau ein `fetchEvents`. - Kalender, ein Monat weiter: genau ein zusaetzlicher `fetchEvents`. - Kalender, Druck auf den Monatsknopf waehrend der laufende Monat bereits angezeigt wird: KEIN zusaetzlicher `fetchEvents`. Das ist die Probe, die den naiven Griff auffliegen laesst. - Kalender, gleiche Konfiguration, erneutes Zeichnen der Kachel: KEIN zusaetzlicher Abruf. - Stoppuhr, laufend, eine Runde aufgezeichnet: die Anzeige laeuft weiter, springt nicht zurueck auf null und ueberspringt keine Sekunde. - Stoppuhr, Start-Runde-Stopp-Reset: genau vier Schreibvorgaenge an `updateWidgetConfig`, einer je Klick. Befunde 1 und 2, `calendar-widget.tsx` Zeile 76: Die Merkung um `resolveCalendarConfig(config)` ersatzlos aufloesen und die drei Werte direkt beim Zeichnen bestimmen — also `showMonth`, `maxEvents` und `lookaheadDays` unmittelbar aus `resolveCalendarConfig(config)` herausziehen, ohne `useMemo`. Das ist zulaessig, weil `resolveCalendarConfig` eine reine Funktion ohne Seiteneffekte ist und ausschliesslich drei einfache Werte liefert: die Merkung hat nie etwas gespart, und weil nur einfache Werte weitergereicht werden, bleibt jeder nachgelagerte Vergleich unveraendert. Die zugehoerige wirkungslose `eslint-disable`-Zeile entfaellt mit. Kein `biome-ignore` an dieser Stelle. Befunde 3 und 4, `calendar-widget.tsx` Zeile 82: Zuerst `showToday` identitaetserhaltend machen — den Monatszustand ueber die Aktualisierungsform setzen und das bisherige Datum unveraendert zurueckgeben, wenn es bereits auf dem gewuenschten Monatsersten steht; nur bei echtem Monatswechsel ein neues Datum einsetzen. Erst danach die Abhaengigkeitsliste des Ladeeffekts von dem Aufruf `monthDate.getTime()` auf `monthDate` selbst umstellen. Die Reihenfolge ist Pflicht: ohne den ersten Schritt loest der zweite bei jedem Druck auf den Monatsknopf einen Termin-Abruf aus, der ueber die API bis zum Exchange-Server durchschlaegt (D-04). Der Kommentar oberhalb des Effekts wird auf den neuen Stand gebracht: das Ladefenster bleibt auf lokale Tagesgrenzen gerundet, damit der Zwischenspeicher-Schluessel des Backends ueber die Fuenf-Minuten-Auffrischung stabil bleibt — daran aendert sich nichts. Die wirkungslose `eslint-disable`-Zeile entfaellt. Befunde 5 und 6, `stopwatch-widget.tsx` Zeile 81: Auf Modulebene eine kleine reine Hilfsfunktion ergaenzen, die aus Startzeitpunkt und bereits gesammelter Dauer die aktuelle Dauer rechnet; `computeElapsed` ruft sie im laufenden Fall auf, sodass es bei identischem Ergebnis bleibt. Im Bauteil die drei benoetigten Werte vor dem Effekt aus dem Zustandsobjekt herausziehen und im Effekt ausschliesslich diese drei Werte und die neue Hilfsfunktion verwenden — das ganze Zustandsobjekt wird im Effekt nicht mehr angefasst. Die Abhaengigkeitsliste bleibt inhaltlich dieselbe und benennt jetzt genau das, was der Effekt liest. Ergebnis: der Takt wird weiterhin nur beim Wechsel zwischen laufend und angehalten sowie bei einer Zeitkorrektur neu aufgesetzt, nicht beim Aufzeichnen einer Runde. Die wirkungslose `eslint-disable`-Zeile entfaellt. Kein `biome-ignore` an dieser Stelle. Danach die beiden Testdateien erweitern. In `calendar-widget.test.tsx` zwei Faelle ergaenzen, die den Aufrufzaehler der Attrappe `mockFetchEvents` pruefen: zweimal weiterblaettern ergibt drei Abrufe, dreimal auf den Monatsknopf druecken waehrend der laufende Monat angezeigt wird ergibt weiterhin einen. Beide Proben sind Rueckfallsicherungen, keine RED-zuerst-Proben: sie sind auch vor dem Umbau gruen und schlagen genau dann fehl, wenn jemand den naiven Griff waehlt — das im Testkommentar so hinschreiben. In `stopwatch-widget.test.tsx` einen Fall mit gestellter Uhr ergaenzen: starten, Zeit vorruecken, eine Runde aufzeichnen, weiter vorruecken, und pruefen, dass die angezeigte Dauer danach groesser ist als vor der Runde und nicht auf null zurueckgefallen ist; zusaetzlich den Aufrufzaehler der Schreib-Attrappe gegen die Zahl der Klicks pruefen. cd /home/vicolab/projects/tessera-ctl && npx biome lint apps/web/src/components/dashboard/widgets/calendar-widget.tsx apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx --reporter=json --max-diagnostics=2000 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const j=JSON.parse(s);const ds=j.diagnostics||[];const e=ds.filter(x=>(x.category||"").includes("useExhaustiveDependencies"));console.log("total="+ds.length+" exhaustive="+e.length);process.exit(e.length===0?0:1);})' cd /home/vicolab/projects/tessera-ctl && pnpm -C apps/web exec vitest run src/components/dashboard/widgets/calendar-widget.test.tsx src/components/dashboard/widgets/stopwatch-widget.test.tsx cd /home/vicolab/projects/tessera-ctl && test "$(grep -c 'eslint-disable-next-line react-hooks/exhaustive-deps' apps/web/src/components/dashboard/widgets/calendar-widget.tsx apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx | awk -F: '{s+=$2} END {print s}')" = "0" Am laufenden System, Kalender (D-04): Stapel mit `docker compose up -d --build db api web` starten, im Browser auf http://localhost:3000 als admin/admin123 anmelden, das Dashboard mit der Kalender-Kachel oeffnen. Ueber das Netzwerkprotokoll des Browsers (Playwright-MCP `browser_network_requests`, NICHT per fetch aus der Seite heraus) 60 Sekunden lang zaehlen: genau ein `/calendar/sources` und genau ein `/calendar/events`. Dann dreimal auf den Monatsknopf in der Mitte druecken — die Zahl der `/calendar/events` darf sich NICHT erhoehen. Dann zweimal weiterblaettern — genau zwei zusaetzliche. Steigt die Zahl beim Monatsknopf, ist der naive Griff drin und die Aufgabe ist nicht erledigt. Am laufenden System, Stoppuhr (D-05): Stoppuhr-Kachel starten, 20 Sekunden zusehen, eine Runde aufzeichnen, weitere 10 Sekunden zusehen, stoppen, zuruecksetzen. Zu beurteilen ist, was auf dem Bildschirm passiert — laeuft die Zeit gleichmaessig weiter, springt sie beim Aufzeichnen der Runde, laeuft sie doppelt so schnell, faellt sie zurueck? Das kann nur ein Mensch beurteilen. Parallel im Netzwerkprotokoll: genau vier `PATCH /dashboard/widgets/.../config`, einer je Klick, kein Dauerfeuer. Die Befunde 1 bis 6 sind weg, beide Widget-Testdateien laufen gruen und enthalten die neuen Zaehlproben, der Monatsknopf loest im laufenden Monat keinen Abruf aus, und die Stoppuhr laeuft ueber Runde und Stopp hinweg sichtbar sauber weiter. Aufgabe 2: Die t-Falle und die instabilen Ladefunktionen (Befunde 7-15) apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx, apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.test.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/DigestIntervalForm.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.test.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx, apps/web/src/components/dashboard/widgets/favorites-widget.tsx, apps/web/src/components/dashboard/widgets/favorites-widget.test.tsx - je Datei nur der Hook-Bereich laut Befundtabelle, plus rund 15 Zeilen davor und danach - apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx Zeilen 1-65 (Attrappen-Muster fuer next-intl und den API-Client) - apps/web/src/app/(portal)/modules/tender-radar/page.tsx Zeilen 40-105 (wer `refreshKey` hochzaehlt) - Jedes der acht Bauteile fuehrt beim Einhaengen genau einen Abruf aus. - Ein erneutes Zeichnen desselben Bauteils mit unveraenderten Eigenschaften fuehrt zu KEINEM weiteren Abruf. Das ist die entscheidende Probe: die Uebersetzungs-Attrappe in den Tests liefert bei jedem Durchlauf eine frische Funktion, eine Rueckkehr von `t` in die Abhaengigkeitsliste faellt hier sofort auf. - Trefferliste: ein Bump von `refreshKey` fuehrt zu genau einem weiteren Abruf. - Die angezeigten Fehlermeldungen bleiben Wort fuer Wort dieselben wie bisher. Fuer die Befunde 7 bis 14 ueberall derselbe Griff, Datei fuer Datei einzeln durchgehen und jeweils die Ersatz-Fehlertexte aus dem Effekt beziehungsweise dem Rueckruf herausziehen: den Aufruf von `t` mit dem festen Schluessel direkt im Bauteil-Rumpf ausfuehren, das Ergebnis einer sprechend benannten Konstanten zuweisen (etwa `loadErrorText`, `saveErrorText`) und im Effekt beziehungsweise Rueckruf nur noch diese Konstante verwenden. Anschliessend die Konstante in die Abhaengigkeitsliste aufnehmen. Weil es sich um eine Zeichenkette handelt, vergleicht React per Wert: die Liste ist bei jedem Durchlauf gleich, der Effekt laeuft weiterhin genau einmal beim Einhaengen. Die wirkungslosen `eslint-disable`-Zeilen entfallen dabei. Betroffen sind im Einzelnen: `VehicleTable.tsx` Zeile 182 (Ladefunktion, Ersatztext des Ladefehlers — der vorhandene Kommentar ueber die ausgelassene Abhaengigkeit wird durch eine kurze Notiz ersetzt, die den neuen Griff erklaert), `TenderDetail.tsx` Zeile 96, `DigestIntervalForm.tsx` Zeile 32, `SourceConfigForm.tsx` Zeile 44 und `favorites-widget.tsx` Zeile 99. `RssFeedListForm.tsx` Zeile 74 und `SavedSearchBar.tsx` Zeile 141 brauchen einen Schritt mehr: dort ist die Ladefunktion eine gewoehnliche Funktion im Rumpf, die bei jedem Durchlauf neu entsteht. Nach dem Herausziehen des Ersatz-Fehlertextes die Ladefunktion in einen stabilen Rueckruf einpacken, dessen Abhaengigkeitsliste nur noch die Text-Konstante enthaelt, und im Effekt die Ladefunktion als einzige Abhaengigkeit fuehren. Die Ladefunktion wird an beiden Stellen auch aus Bedienschritten heraus aufgerufen — das bleibt unveraendert moeglich. `ResultsList.tsx` Zeilen 89 bis 130: zuerst derselbe Griff fuer den Ersatz-Fehlertext (Befund 8). Fuer Befund 15 zusaetzlich `refreshKey` aus der Abhaengigkeitsliste der Ladefunktion herausnehmen und stattdessen in die Liste des Effekts schreiben, der die Ladefunktion aufruft. Das ist dieselbe Bauform, die `InvoiceHistoryTable` bereits verwendet, und verhaelt sich identisch: ein Bump loest weiterhin genau einen Abruf aus. Der Vorteil: die Ausnahme beschraenkt sich damit auf den winzigen Effekt, waehrend die grosse Ladefunktion vollstaendig unter der Regel bleibt. Ueber diesen Effekt eine `biome-ignore`-Zeile fuer `lint/correctness/useExhaustiveDependencies` setzen, mit einem deutschen Grund in einer Zeile, der sagt, dass der Zaehler der Elternseite ein Auffrisch-Ausloeser ist und die Trefferliste ohne ihn nach Jetzt abrufen auf dem alten Stand bliebe. Danach die Tests. In den sieben vorhandenen Testdateien (`VehicleTable`, `ResultsList`, `TenderDetail`, `SavedSearchBar`, `RssFeedListForm`, `SourceConfigForm`, `favorites-widget`) je einen Fall ergaenzen, der zaehlt statt zu zeichnen: Bauteil einhaengen, das Ende des ersten Abrufs abwarten, dasselbe Bauteil mit unveraenderten Eigenschaften erneut zeichnen lassen, und pruefen, dass die Attrappe des API-Clients weiterhin genau einmal aufgerufen wurde. Im Testkommentar festhalten, wogegen die Probe sichert: die Uebersetzungs-Attrappe liefert bei jedem Durchlauf eine frische Funktion, also faellt eine Rueckkehr von `t` in die Abhaengigkeitsliste hier sofort auf. In `ResultsList.test.tsx` zusaetzlich einen Fall, der nach einem Bump von `refreshKey` genau einen weiteren Abruf erwartet — damit ist die stehengelassene Abhaengigkeit als tragend belegt und nicht nur behauptet. Fuer `DigestIntervalForm` gibt es heute keine Testdatei. Hier wird bewusst keine angelegt: das Bauteil wird stattdessen am laufenden System auf der Seite Meine Quellen nachgezaehlt. Diese Luecke gehoert so in das SUMMARY. cd /home/vicolab/projects/tessera-ctl && npx biome lint "apps/web/src/app/(portal)/modules" apps/web/src/components/dashboard/widgets/favorites-widget.tsx --reporter=json --max-diagnostics=5000 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const j=JSON.parse(s);const e=(j.diagnostics||[]).filter(x=>(x.category||"").includes("useExhaustiveDependencies"));console.log("exhaustive="+e.length);process.exit(e.length===0?0:1);})' cd /home/vicolab/projects/tessera-ctl && pnpm -C apps/web exec vitest run "src/app/(portal)/modules" src/components/dashboard/widgets/favorites-widget.test.tsx cd /home/vicolab/projects/tessera-ctl && test "$(grep -c 'biome-ignore lint/correctness/useExhaustiveDependencies' "apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx")" = "1" cd /home/vicolab/projects/tessera-ctl && pnpm -C apps/web type-check Am laufenden System: Ausschreibungsradar oeffnen, 60 Sekunden im Netzwerkprotokoll des Browsers zaehlen — genau ein `/tenders` und ein `/triage`, kein Nachschlag. Dann Jetzt abrufen druecken: genau ein weiterer `/tenders`. Dann Meine Quellen oeffnen und 60 Sekunden zaehlen — je genau ein Abruf fuer Zustell-Einstellung, RSS-Feeds und Quellen-Konfiguration. Dann in DKV Flotte die Fahrzeugtabelle oeffnen, ein Fahrzeug speichern und pruefen, dass die Tabelle den neuen Stand zeigt (Gegenprobe gegen eingefrorene Anzeige) und dabei kein Dauerfeuer entsteht. Die Befunde 7 bis 15 sind weg, `ResultsList.tsx` traegt genau eine begruendete Ausnahme, alle sieben erweiterten Testdateien laufen gruen und enthalten je eine Zaehlprobe, die Typpruefung von apps/web ist sauber, und am laufenden System entsteht auf keiner der drei Seiten ein wiederholter Abruf. Aufgabe 3: Absicht, Ballast und die zwei fehlenden stabilen Abhaengigkeiten (Befunde 16-21) apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx, apps/web/src/components/layout/sidebar.tsx, apps/web/src/components/layout/sidebar.test.tsx, apps/web/src/app/(portal)/admin/modules/components/ActivateModuleDialog.tsx, apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.tsx, apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.test.tsx, apps/web/src/app/(portal)/admin/modules/grants/page.tsx - apps/web/src/app/(portal)/admin/modules/grants/grants-matrix.test.tsx (Attrappen-Muster fuer globales fetch auf den Verwaltungsseiten — Vorlage fuer die neue Testdatei) - apps/web/src/components/layout/sidebar.test.tsx Kopf und vorhandene Faelle - apps/web/src/app/(portal)/admin/modules/page.tsx Zeilen 225-250 (wie der Aktivierungs-Dialog eingehaengt wird) - apps/web/src/app/(portal)/admin/groups/page.tsx Zeilen 260-275 (wie der Mitglieder-Dialog eingehaengt wird) - Seitenleiste: Einhaengen ergibt genau einen Abruf der aktiven Module; ein Bump des Auffrisch-Zaehlers ergibt genau einen weiteren; ein erneutes Zeichnen ohne Bump ergibt keinen weiteren. - Mitglieder-Dialog: Einhaengen ergibt genau einen Abruf der Mitglieder und genau einen der Benutzer; erneutes Zeichnen ergibt keine weiteren; ein Wechsel der Gruppe ergibt genau einen weiteren Mitglieder-Abruf mit der neuen Kennung. - Freigabe-Matrix: die bestehenden Suchfaelle verhalten sich unveraendert — ein Begriff, der nur eine Achse trifft, leert die andere Achse nicht. Befund 16, `InvoiceHistoryTable.tsx` Zeile 81: `refreshKey` bleibt in der Liste des Effekts stehen. Darueber eine `biome-ignore`-Zeile fuer `lint/correctness/useExhaustiveDependencies` setzen mit einem deutschen Grund in einer Zeile: der Zaehler der Elternseite ist ein Auffrisch-Ausloeser, ohne ihn bliebe die Historie nach Jetzt pruefen auf dem alten Stand. Befund 17, `sidebar.tsx` Zeile 57: genauso — der Auffrisch-Zaehler aus dem Marketplace-Speicher bleibt stehen, mit deutscher Begruendung darueber: ohne ihn erscheint ein frisch aktiviertes Modul in der Seitenleiste erst nach einem Neuladen der Seite, die Navigation liefe also dem Berechtigungsstand hinterher. Befund 18, `ActivateModuleDialog.tsx` Zeile 47: `moduleId` aus der Abhaengigkeitsliste entfernen, `open` bleibt. Belegt ist, dass das folgenlos ist: die Elternseite haengt den Dialog innerhalb einer Bedingung je gewaehltem Modul frisch ein und gibt `open` dort fest als wahr mit — `moduleId` kann sich waehrend der Lebenszeit des Dialogs nicht aendern, und der Effekt holt ohnehin nur die Gruppenliste, die vom Modul unabhaengig ist. Kein `biome-ignore` an dieser Stelle. Befunde 19 und 20, `GroupMembersModal.tsx` Zeile 89: beide Ladefunktionen in die Abhaengigkeitsliste des Effekts aufnehmen und die leere Liste ersetzen. Das ist gefahrlos, weil beide bereits in stabile Rueckrufe eingepackt sind: die eine haengt nur an der Gruppenkennung, die andere an nichts. Die wirkungslose `eslint-disable`-Zeile und der Kommentar ueber die vermeintlich stabile Gruppenkennung entfallen — die Stabilitaet steht jetzt in der Liste statt in einem Kommentar. Befund 21, `grants/page.tsx` Zeile 140: die Hilfsfunktion fuer den Suchabgleich aus dem Bauteil-Rumpf in den Rumpf der Merkung verschieben, sodass die Merkung nur noch die Modulliste, die Gruppenliste und den kleingeschriebenen Suchbegriff liest — allesamt bereits in der Liste. Die Wahrheitstabelle aus dem vorhandenen Kommentar bleibt Wort fuer Wort gueltig und unveraendert; verschoben wird nur, wo die Funktion steht. Die wirkungslose `eslint-disable`-Zeile entfaellt. Kein `biome-ignore` an dieser Stelle. Danach die Tests. In `sidebar.test.tsx` einen Fall ergaenzen, der belegt, dass die stehengelassene Abhaengigkeit tragend ist: Seitenleiste einhaengen, Abruf abwarten, den Auffrisch-Zaehler im Speicher hochsetzen, und pruefen, dass genau ein weiterer Abruf der aktiven Module erfolgt ist — und dass ein erneutes Zeichnen ohne Bump keinen weiteren ausloest. Zu `GroupMembersModal` eine neue Testdatei anlegen, nach dem Attrappen-Muster der Freigabe-Matrix (globales fetch als Attrappe, next-intl als Attrappe): einhaengen ergibt genau einen Mitglieder- und einen Benutzer-Abruf, erneutes Zeichnen keine weiteren, und ein Wechsel der uebergebenen Gruppe genau einen weiteren Mitglieder-Abruf mit der neuen Kennung — der letzte Fall ist der eigentliche Beleg fuer die Einstufung als Defekt und faellt ohne den Fix durch. Zum Abschluss den Gesamtstand messen und das SUMMARY schreiben: die Tabelle aller 21 Befunde mit Datei, Zeile, Kategorie und der Begruendung in einer Zeile, dazu die drei begruendeten Ausnahmen mit Fundstelle, die Zaehlung vorher und nachher, die Ergebnisse der Proben am laufenden System und die offen benannte Luecke bei `DigestIntervalForm` (keine Testdatei, nur am laufenden System geprueft). Wortlaut wie Dependency hinzugefuegt genuegt als Begruendung nicht (D-01). cd /home/vicolab/projects/tessera-ctl && npx biome lint . --reporter=json --max-diagnostics=20000 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const j=JSON.parse(s);const ds=j.diagnostics||[];const e=ds.filter(x=>(x.category||"").includes("useExhaustiveDependencies"));const err=ds.filter(x=>x.severity==="error");console.log("total="+ds.length+" exhaustive="+e.length+" errors="+err.length);process.exit(ds.length===446&&e.length===0&&err.length===0?0:1);})' cd /home/vicolab/projects/tessera-ctl && pnpm lint 2>&1 | tail -3 cd /home/vicolab/projects/tessera-ctl && test "$(grep -rl 'eslint-disable-next-line react-hooks/exhaustive-deps' apps/web/src | wc -l)" = "0" cd /home/vicolab/projects/tessera-ctl && test "$(grep -rl 'biome-ignore lint/correctness/useExhaustiveDependencies' apps/web/src | wc -l)" = "3" cd /home/vicolab/projects/tessera-ctl && pnpm -C apps/web exec vitest run 2>&1 | tail -6 cd /home/vicolab/projects/tessera-ctl && pnpm -C apps/api exec vitest run 2>&1 | tail -6 cd /home/vicolab/projects/tessera-ctl && pnpm type-check 2>&1 | tail -3 Am laufenden System: als admin ein Modul im Marketplace aktivieren und pruefen, dass es ohne Neuladen in der Seitenleiste erscheint (Beleg fuer Befund 17). In der Modulverwaltung den Aktivierungs-Dialog oeffnen und im Netzwerkprotokoll pruefen, dass genau ein `/groups` geholt wird (Befund 18). In der Gruppenverwaltung den Mitglieder-Dialog einer Gruppe oeffnen, schliessen, den einer zweiten Gruppe oeffnen und pruefen, dass die angezeigten Mitglieder zur zweiten Gruppe gehoeren und nicht zur ersten (Befunde 19/20). In DKV Flotte Jetzt pruefen druecken und pruefen, dass die Historie genau einen zusaetzlichen Abruf macht (Befund 16). Alle 21 Befunde sind entschieden und umgesetzt, der Gesamtstand liegt bei 446 Meldungen mit null Befunden der Regel und null Fehlern, `pnpm lint` bleibt 5/5, beide Testlaeufe sind gruen und ihre Zahlen liegen nicht unter dem Ausgangsstand, in `apps/web/src` steht keine wirkungslose `eslint`-Ausnahme mehr und genau drei begruendete `biome-ignore`-Zeilen, und das SUMMARY traegt die vollstaendige Tabelle mit Begruendungen. ## Vertrauensgrenzen | Grenze | Beschreibung | |--------|--------------| | Browser -> eigene API (:3001) | Die Zahl der Anfragen wird allein vom Browser-Code bestimmt. Eine Effekt-Schleife ist eine selbst verursachte Last, gegen die die API keine Drosselung besitzt. | | API -> Exchange/EWS | Das Kalender-Widget fragt ueber die API einen fremden Mail-Server ab. Ein Zwischenspeicher mit fuenf Minuten Haltezeit liegt dazwischen, dessen Schluessel aus dem auf Tagesgrenzen gerundeten Ladefenster entsteht. | | API -> Ausschreibungsportale und RSS-Quellen | Das Ausschreibungsradar holt ueber die API Daten aus fremden Quellen. | | Browser -> Berechtigungsanzeige | Seitenleiste, Freigabe-Matrix und Mitglieder-Dialog zeigen Berechtigungszustand. Ein Effekt, der nicht neu laeuft, zeigt einen alten Stand. | ## STRIDE-Register | Threat ID | Kategorie | Komponente | Schwere | Umgang | Massnahme | |-----------|-----------|------------|---------|--------|-----------| | T-GOF-01 | Denial of Service | Alle 16 Hook-Stellen in apps/web | high | mitigate | `t` und im Rumpf neu entstehende Funktionen kommen in keine Abhaengigkeitsliste; stattdessen wird der uebersetzte Text als Zeichenkette vorgezogen (Wertvergleich) beziehungsweise die Funktion stabil eingepackt. Nachweis nicht per Behauptung, sondern per Zaehlung: rund zehn Zaehlproben in den Tests plus Zaehlung im Netzwerkprotokoll des Browsers ueber 60 Sekunden je Seite. | | T-GOF-02 | Denial of Service | calendar-widget.tsx, Ladeeffekt Zeile 82 | high | mitigate | Der naive Griff (`monthDate` direkt eintragen) wuerde bei jedem Druck auf den Monatsknopf einen Termin-Abruf ausloesen, der ueber die API bis zum Exchange-Server durchschlaegt und den Dienst-Zugang drosseln oder sperren koennte. Deshalb zuerst `showToday` identitaetserhaltend machen, erst danach die Liste umstellen; Nachweis durch dreimaliges Druecken des Monatsknopfs ohne Anstieg der Abrufzahl. Das auf Tagesgrenzen gerundete Ladefenster bleibt unangetastet, damit der Zwischenspeicher des Backends weiter greift. | | T-GOF-03 | Denial of Service | ResultsList.tsx, RssFeedListForm.tsx | high | mitigate | Beide Bauteile haengen an fremden Quellen (Ausschreibungsportale, RSS-Feeds). Die Ladefunktionen werden stabil eingepackt statt Abhaengigkeiten blind zu ergaenzen; die Zaehlprobe nach erneutem Zeichnen ist Teil der Abnahme. | | T-GOF-04 | Tampering | sidebar.tsx Zeile 57, grants/page.tsx Zeile 140 | medium | mitigate | Der Auffrisch-Ausloeser der Seitenleiste bleibt erhalten und wird begruendet, damit die Navigation nach einer Modul-Aktivierung nicht hinter dem Berechtigungsstand zurueckbleibt; ein Testfall belegt, dass der Ausloeser wirkt. Bei der Freigabe-Matrix wird nur verschoben, wo die Hilfsfunktion steht — die bestehenden Suchfaelle bleiben als Beleg gruen. | | T-GOF-05 | Information Disclosure | GroupMembersModal.tsx Zeile 89 | medium | mitigate | Ohne die beiden stabilen Abhaengigkeiten zeigt der Dialog bei einem Gruppenwechsel ohne Neuaufbau die Mitglieder der vorigen Gruppe. Die Abhaengigkeiten werden ergaenzt und der Gruppenwechsel wird mit einem eigenen Testfall belegt. | | T-GOF-06 | Tampering | Paketinstallationen | low | accept | Dieser Vorgang installiert nichts und hebt keine Version an (D-06). Es gibt keine Installationsaufgabe, damit keine Angriffsflaeche ueber die Paketbeschaffung. | ## Gesamtnachweis **Maschinell, am Schluss von Aufgabe 3:** 1. Biome gesamt: `total=446 exhaustive=0 errors=0` (heute `total=467 exhaustive=21 errors=0`). Die Differenz ist genau 21 — je Befund eine Meldung weniger, kein Anstieg an anderer Stelle (D-07). Liegt die Zahl ueber 446, hat der Umbau oder eine der neuen Testdateien eine neue Meldung erzeugt: die ist zu bereinigen, nicht wegzuerklaeren. Liegt sie darunter, ist versehentlich an anderer Stelle mitaufgeraeumt worden — auch das gehoert benannt, weil es den Vergleich verfaelscht. 2. `pnpm lint` bleibt 5/5. 3. `pnpm type-check` bleibt 4/4. 4. `pnpm -C apps/web exec vitest run`: gruen, Dateien und Faelle nicht unter 66 / 462 (Ausgangsstand 21.09.). 5. `pnpm -C apps/api exec vitest run`: gruen, nicht unter 71 / 1136 — diese Aufgabe fasst apps/api nicht an, der Lauf ist die Gegenprobe. 6. In `apps/web/src` keine Datei mehr mit einer wirkungslosen `eslint`-Ausnahme fuer diese Regel, genau drei Dateien mit einer begruendeten `biome-ignore`-Zeile. **Am laufenden System** (Stapel: `docker compose up -d --build db api web`, Web auf 3000, API auf 3001, Anmeldung admin/admin123 — die Datenbank bleibt, wie sie ist): Gemessen wird ausschliesslich ueber das Netzwerkprotokoll des Browsers (Playwright-MCP `browser_network_requests`) oder ersatzweise ueber den Netzwerk-Reiter der Entwicklerwerkzeuge von Hand. Niemals per `fetch` aus der Seite heraus — das misst etwas anderes und taeuscht in beide Richtungen. | Ansicht | Zaehlung | Erwartung | |---------|----------|-----------| | Dashboard, Kalender-Kachel | 60 s ruhen lassen | 1x `/calendar/sources`, 1x `/calendar/events` | | Dashboard, Kalender-Kachel | 3x Monatsknopf im laufenden Monat | kein zusaetzlicher `/calendar/events` | | Dashboard, Kalender-Kachel | 2x weiterblaettern | genau 2 zusaetzliche `/calendar/events` | | Dashboard, Stoppuhr | Start, Runde, Stopp, Reset | genau 4x `PATCH .../config`; Anzeige laeuft sichtbar sauber (Menschenurteil) | | Ausschreibungsradar | 60 s ruhen lassen | 1x `/tenders`, 1x `/triage` | | Ausschreibungsradar | Jetzt abrufen | genau 1 zusaetzlicher `/tenders` | | Meine Quellen | 60 s ruhen lassen | je 1 Abruf fuer Zustell-Einstellung, RSS-Feeds, Quellen-Konfiguration | | DKV Flotte | Jetzt pruefen | genau 1 zusaetzlicher Historien-Abruf | | DKV Flotte, Fahrzeuge | Fahrzeug speichern | Tabelle zeigt den neuen Stand, kein Dauerfeuer | | Marketplace | Modul aktivieren | Modul erscheint ohne Neuladen in der Seitenleiste | | Modulverwaltung | Aktivierungs-Dialog oeffnen | genau 1x `/groups` | | Gruppenverwaltung | Dialog Gruppe A schliessen, Gruppe B oeffnen | angezeigte Mitglieder gehoeren zu Gruppe B | **Ausdruecklich menschliches Urteil** (nicht automatisierbar, nicht als maschinelle Probe verkleidet): das Laufverhalten der Stoppuhr auf dem Bildschirm ueber Start, Runde und Stopp hinweg — springt die Zeit, laeuft sie doppelt so schnell, faellt sie zurueck? Und die Frage, ob die Kalenderkachel nach dem Blaettern die Termine des richtigen Monats zeigt. ## Abgrenzung Phasen-Pruefpunkte (API-Abdeckungsmatrix, Annahme-Abgleich, Schema-Tor) entfallen: Schnellvorgang ohne Roadmap-Phase, ohne neue Fremd-API, ohne Schema-Aenderung. Es wird keine COVERAGE.md erzeugt. - Alle 21 Befunde sind einzeln entschieden, die Entscheidung steht mit Begruendung im SUMMARY, und keine Begruendung lautet sinngemaess nur, dass eine Abhaengigkeit ergaenzt wurde (D-01). - Genau drei Befunde stehen als Absicht mit begruendetem `biome-ignore` im Quelltext — 14 Prozent, deutlich unter der Drittel-Grenze (D-02). - Verhalten unveraendert bei den Kategorien Absicht, Falle und Ballast; veraendert nur in der beabsichtigten Richtung bei den beiden Defekten; nachgewiesen durch Zaehlungen, nicht durch Augenschein allein (D-03). - Kalender und Stoppuhr sind am laufenden System geprueft, nicht nur gelesen (D-04, D-05). - Kein neues Paket, keine Versionsanhebung, kein Umbau ueber die Befunde hinaus (D-06). - Biome gesamt 446 statt 467, null Befunde der Regel, null Fehler, `pnpm lint` 5/5 (D-07). - Beide Testlaeufe gruen, Zahlen nicht unter dem Ausgangsstand. Create `.planning/quick/260921-gof-effekt-abhaengigkeiten-in-react-21-befun/260921-gof-SUMMARY.md` when done. Pflichtbestandteile des SUMMARY: 1. Tabelle aller 21 Befunde: Datei, Zeile, Kategorie (A/B/C/D), Entscheidung, Begruendung in einer Zeile. 2. Die drei begruendeten Ausnahmen mit Fundstelle und Wortlaut des Grundes. 3. Zaehlung vorher/nachher: Biome gesamt, Regel-Befunde, Fehler, Testdateien und Faelle je Anwendung. 4. Ergebnisse jeder Zeile der Tabelle aus dem Abschnitt Gesamtnachweis, mit den tatsaechlich gezaehlten Zahlen. 5. Offen benannte Luecken — mindestens: `DigestIntervalForm` hat keine Testdatei und ist nur am laufenden System geprueft.