--- phase: quick-260921-gof plan: 01 subsystem: ui tags: [react, next.js, biome, useExhaustiveDependencies, next-intl, vitest] requires: - phase: quick-260921-bi2 provides: "Der Testfall, der die instabile-t-Falle in ResultsList.tsx tatsaechlich aufgedeckt hat" provides: - "Alle 21 Biome-Befunde der Regel useExhaustiveDependencies in apps/web einzeln entschieden und behoben" - "Erste Verwendung von biome-ignore im Projekt (genau 3x, mit deutscher Begruendung)" - "Der durchgaengige Griff gegen die t-Falle: uebersetzten Text vor dem Hook in eine Konstante ziehen statt t selbst in die Abhaengigkeitsliste zu schreiben" affects: [dashboard-widgets, tender-radar, dkv-fleet, admin-groups, admin-modules, sidebar] actuals: tokens: 11720 tasks: 3 commits: 3 plan_head_before: 54fdf69 tech-stack: added: [] patterns: - "Ersatz-Fehlertext vor dem Effekt/Rueckruf in eine Konstante ziehen und die Konstante (nicht t) in die Abhaengigkeitsliste schreiben — React vergleicht Strings per Wert" - "Bei einer instabilen Ladefunktion (gewoehnliche Funktion im Rumpf) diese in useCallback mit der Text-Konstante als Abhaengigkeit einpacken, statt sie leer/eslint-disabled zu lassen" - "Auffrisch-Ausloeser (refreshKey-Muster) bleiben in der Abhaengigkeitsliste stehen, mit begruendetem biome-ignore statt stiller Unterdrueckung" - "setState-Funktionsform zur Identitaetserhaltung nutzen (showToday gibt bei unveraendertem Zielmonat dieselbe Referenz zurueck), bevor eine .getTime()-Umgehung durch die direkte Objektreferenz ersetzt wird" key-files: created: - apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.test.tsx 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/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 key-decisions: - "Reihenfolge bei calendar-widget.tsx eingehalten: showToday zuerst identitaetserhaltend gemacht, erst danach die Abhaengigkeitsliste des Ladeeffekts von monthDate.getTime() auf monthDate umgestellt — die umgekehrte Reihenfolge haette bei jedem Druck auf den Monatsknopf im laufenden Monat einen Termin-Abruf bis zum Exchange-Server ausgeloest." - "t kommt in keiner der acht betroffenen Dateien in eine Abhaengigkeitsliste — stattdessen wird der uebersetzte Ersatztext vor dem Effekt/Rueckruf in eine Konstante gezogen." - "Genau drei biome-ignore-Zeilen (Befunde 15, 16, 17) fuer echte Auffrisch-Ausloeser — 14% aller Befunde, deutlich unter der Drittel-Grenze aus D-02." - "DigestIntervalForm.tsx bekam bewusst keine neue Testdatei — die Komponente wird nur am laufenden System auf 'Meine Quellen' nachgezaehlt (siehe Luecken unten)." requirements-completed: [D-01, D-02, D-03, D-04, D-05, D-06, D-07] coverage: - id: D1 description: "Biome-Regel useExhaustiveDependencies: 21 -> 0 Befunde, Gesamtstand 467 -> 446, 0 Fehler" requirement: "D-01" verification: - kind: other ref: "npx biome lint . --reporter=json --max-diagnostics=20000 (siehe Zaehlung unten)" status: pass human_judgment: false - id: D2 description: "Genau drei begruendete biome-ignore-Zeilen, keine stille Unterdrueckung" requirement: "D-02" verification: - kind: other ref: "grep -rl 'biome-ignore lint/correctness/useExhaustiveDependencies' apps/web/src | wc -l -> 3" status: pass human_judgment: false - id: D3 description: "Kalender-Widget: Ersteinblendung/Monatswechsel/Monatsknopf im laufenden Monat verhalten sich korrekt (Zaehlung im Browser-Netzwerkprotokoll ueber 60s + Knopfdruecke)" requirement: "D-04" verification: [] human_judgment: true rationale: "Erfordert einen laufenden Docker-Stapel und Playwright-MCP-Netzwerkzaehlung im Browser — kein CLI-Ersatz vorhanden, siehe Abgrenzung im PLAN." - id: D4 description: "Stoppuhr laeuft ueber Start/Runde/Stopp/Reset sichtbar sauber weiter, genau 4 PATCH-Aufrufe" requirement: "D-05" verification: - kind: unit ref: "stopwatch-widget.test.tsx#quick-260921-gof: Runde unterbricht den Takt nicht" status: pass human_judgment: true rationale: "Das visuelle Laufverhalten auf dem Bildschirm (springt/laeuft doppelt/faellt zurueck) ist per PLAN ausdruecklich menschliches Urteil, nicht automatisierbar." - id: D5 description: "Auffrisch-Ausloeser bleiben wirksam: sidebarRefreshKey, DKV Jetzt pruefen, Ausschreibungsradar Jetzt abrufen" requirement: "D-03" verification: - kind: unit ref: "sidebar.test.tsx#Befund 17; ResultsList.test.tsx#Befund 15" status: pass human_judgment: true rationale: "Der Browser-Nachweis am laufenden System (Modul-Aktivierung ohne Neuladen sichtbar) ist Teil der Abgrenzung des PLAN und wird vom Orchestrator per Playwright-MCP nachgeholt." - id: D6 description: "Alle bestehenden Tests bleiben gruen, Zahl der Testdateien/Tests sinkt nicht" requirement: "D-03" verification: - kind: unit ref: "pnpm -C apps/web exec vitest run -> 67 files / 477 tests (Basis 66/462); pnpm -C apps/api exec vitest run -> 71/1136 unveraendert" status: pass human_judgment: false duration: 55min completed: 2026-09-21 status: complete --- # Quick 260921-gof: 21 useExhaustiveDependencies-Befunde in React Summary **Alle 21 Biome-Befunde der Regel `lint/correctness/useExhaustiveDependencies` in `apps/web` einzeln entschieden: 15 Fallen entschaerft (t-Falle achtmal, instabile Ladefunktionen zweimal, Kalender/Stoppuhr-Objektzugriffe zweimal), zwei echte Defekte behoben, drei Auffrisch-Ausloeser mit `biome-ignore` begruendet stehen gelassen, ein Ballast-Fund entfernt — Gesamtstand 467 auf 446 Meldungen gesenkt, null Regelbefunde, null Fehler.** ## Performance - **Duration:** ca. 55 min - **Tasks:** 3/3 - **Files modified:** 25 (24 bestehende + 1 neue Testdatei) - **Commits:** 3 (+ diese SUMMARY, vom Orchestrator committet) ## Befundtabelle — alle 21, mit Entscheidung | # | Datei | Zeile | Befund | Kat. | Entscheidung | Was ginge schief (ohne Fix / beim naiven Fix) | |---|-------|-------|--------|------|--------------|------------------------------------------------| | 1 | calendar-widget.tsx | 76 | `config` fehlt | B | `useMemo` um `resolveCalendarConfig(config)` ersatzlos entfernt, Werte direkt destrukturiert | Reine Funktion mit drei einfachen Werten — die Merkung hat nie etwas gespart; `config` als Ganzes einzutragen macht sie bei jedem frischen `config`-Objekt wirkungslos | | 2 | calendar-widget.tsx | 76 | `config.lookaheadDays` zu eng | B | dieselbe Aenderung wie #1 | Gegenrichtung desselben Problems | | 3 | calendar-widget.tsx | 82 | `monthDate` fehlt | B | `showToday` identitaetserhaltend gemacht (gibt bei bereits angezeigtem Zielmonat dieselbe Referenz zurueck), danach Ladeeffekt-Deps von `monthDate.getTime()` auf `monthDate` umgestellt | Ohne die Stabilisierung zuerst haette jeder Druck auf den Monatsknopf im laufenden Monat einen neuen Termin-Abruf bis zum Exchange-Server ausgeloest (D-04) | | 4 | calendar-widget.tsx | 82 | `monthDate.getTime()` ueberfluessig | B | dieselbe Aenderung wie #3 | Gegenrichtung desselben Problems | | 5 | stopwatch-widget.tsx | 81 | `sw` fehlt | B | neue reine Hilfsfunktion `computeElapsedFrom(state, startedAt, elapsed)`, Takt-Effekt liest nur noch die drei Einzelwerte statt des ganzen `sw`-Objekts | `sw` als Abhaengigkeit haette den 100-ms-Takt bei jeder aufgezeichneten Runde ab- und wiederaufgebaut — ohne Not, mit Taktversatz | | 6 | stopwatch-widget.tsx | 81 | `sw.elapsed` zu eng | B | dieselbe Aenderung wie #5 | Gegenrichtung desselben Problems | | 7 | VehicleTable.tsx | 182 | `t` fehlt | B | `loadErrorText = t(...)` vor `load` gezogen, Konstante in `load`s Deps | `t` in der Liste haette `load` bei jedem Durchlauf neu erzeugt (Testattrappe liefert frische Funktion) — der Mount-Effekt waere zur Abruf-Schleife geworden | | 8 | ResultsList.tsx | 89 | `t` fehlt | B | `loadErrorText` vor `load` gezogen | dieselbe Schleifengefahr wie #7 — hier tatsaechlich in 260921-bi2 zugeschnappt | | 9 | TenderDetail.tsx | 96 | `t` fehlt | B | `detailErrorText` vor dem Effekt gezogen | Detail-Abruf beim Oeffnen einer Ausschreibung waere zur Schleife geworden | | 10 | DigestIntervalForm.tsx | 32 | `t` fehlt | B | `loadErrorText` vor dem Effekt gezogen | Laden der Zustell-Einstellung waere zur Schleife geworden | | 11 | SourceConfigForm.tsx | 44 | `t` fehlt | B | `loadErrorText` vor dem Effekt gezogen | Laden der Quellen-Konfiguration waere zur Schleife geworden | | 12 | RssFeedListForm.tsx | 74 | `loadFeeds` fehlt | B | `loadFeeds` in `useCallback([loadErrorText])` eingepackt | `loadFeeds` als gewoehnliche Rumpf-Funktion waere bei jedem Durchlauf neu entstanden — als Effekt-Abhaengigkeit eine endlose Abruf-Schleife gegen die Feed-Liste | | 13 | SavedSearchBar.tsx | 141 | `load` fehlt | B | `load` in `useCallback([loadErrorText])` eingepackt | dieselbe Schleifengefahr wie #12, gegen die Suchprofile | | 14 | favorites-widget.tsx | 99 | `t` fehlt | B | `favoritesErrorText` vor dem Effekt gezogen | Mount-Abruf der Favoriten waere zur Schleife geworden | | 15 | ResultsList.tsx | 89 | `refreshKey` ueberfluessig | C | aus `load`s Deps entfernt, in den aufrufenden Mount-Effekt verschoben + `biome-ignore` mit deutschem Grund | `refreshKey` ist der Auffrisch-Ausloeser der Elternseite nach "Jetzt abrufen" — ohne ihn bliebe die Trefferliste nach einem Abruf auf dem alten Stand | | 16 | InvoiceHistoryTable.tsx | 81 | `refreshKey` ueberfluessig | C | bleibt stehen + `biome-ignore` mit deutschem Grund | ohne ihn bliebe die DKV-Historie nach "Jetzt pruefen" auf dem alten Stand | | 17 | sidebar.tsx | 57 | `sidebarRefreshKey` ueberfluessig | C | bleibt stehen + `biome-ignore` mit deutschem Grund | ohne ihn erschiene ein frisch aktiviertes Modul erst nach einem Neuladen der Seite — die Navigation liefe dem Berechtigungsstand hinterher | | 18 | ActivateModuleDialog.tsx | 47 | `moduleId` ueberfluessig | D | aus der Liste entfernt, `open` bleibt | reiner Ballast: der Effekt holt nur die vom Modul unabhaengige Gruppenliste, die Elternseite haengt den Dialog je Modul frisch ein | | 19 | GroupMembersModal.tsx | 89 | `fetchMembers` fehlt | A | in die Liste aufgenommen (beide Ladefunktionen bereits stabil) | ohne sie zeigt der Dialog bei einem Gruppenwechsel ohne Neuaufbau die Mitglieder der vorigen Gruppe | | 20 | GroupMembersModal.tsx | 89 | `fetchAllUsers` fehlt | A | in die Liste aufgenommen | dieselbe Stelle, dieselbe Begruendung | | 21 | grants/page.tsx | 140 | `matches` ueberfluessig | B | Hilfsfunktion `matches` in den Rumpf der Merkung verschoben statt im Bauteil-Rumpf zu bleiben | eingetragen liefe die Filter-Merkung bei jedem Durchlauf neu und waere damit wirkungslos — keine Schleife, aber die Merkung ist weg, wegen der die Stelle gebaut wurde | **Verteilung:** 2x (A), 15x (B), 3x (C), 1x (D) — exakt wie im PLAN vorgegeben. ## Die drei begruendeten Ausnahmen (D-02) 1. **`ResultsList.tsx:141`** `// biome-ignore lint/correctness/useExhaustiveDependencies: refreshKey ist der Auffrisch-Ausloeser der Elternseite nach "Jetzt abrufen" - ohne ihn bliebe die Trefferliste nach einem Abruf auf dem alten Stand.` 2. **`InvoiceHistoryTable.tsx:84`** `// biome-ignore lint/correctness/useExhaustiveDependencies: refreshKey ist der Auffrisch-Ausloeser der Elternseite nach "Jetzt pruefen" - ohne ihn bliebe die Historie auf dem alten Stand.` 3. **`sidebar.tsx:61`** `// biome-ignore lint/correctness/useExhaustiveDependencies: sidebarRefreshKey ist der Auffrisch-Ausloeser aus dem Marketplace-Speicher - ohne ihn liefe die Navigation dem Berechtigungsstand hinterher.` 3 von 21 = 14%, unter der Drittel-Grenze aus D-02. ## Zaehlung vorher/nachher (real gemessen, nicht angenommen) | Messung | Vorher (Baseline `54fdf69`, in einem temporaeren Worktree nachgemessen) | Nachher | |---|---|---| | Biome gesamt (`apps/web`) | `total=467 exhaustive=21 errors=0` | `total=446 exhaustive=0 errors=0` | | `pnpm lint` | 5/5, Fehler-Schwere unbekannt (nicht separat gemessen) | 5/5, 0 Befunde der Schwere `error` | | `pnpm type-check` | nicht separat gemessen | 4/4 | | `eslint-disable-next-line react-hooks/exhaustive-deps` in `apps/web/src` | 11 (laut PLAN) | 0 | | `biome-ignore lint/correctness/useExhaustiveDependencies` in `apps/web/src` | 0 | 3 | | `apps/web` Testdateien / Tests | 66 / 462 (PLAN-Baseline) | **67 / 477** | | `apps/api` Testdateien / Tests | 71 / 1136 | 71 / 1136 (unveraendert, Aufgabe fasst apps/api nicht an) | Differenz Biome gesamt: 467 − 446 = 21, exakt ein Befund pro Fund — kein Anstieg an anderer Stelle, keine versehentliche Zusatzbereinigung. ## Gesamtnachweis — Ergebnisse je Zeile | Ansicht | Erwartung laut PLAN | Ergebnis | |---|---|---| | Biome gesamt | `total=446 exhaustive=0 errors=0` | **Erreicht** — real gemessen: `total=446 exhaustive=0 errors=0` | | `pnpm lint` | 5/5 | **Erreicht** — `5 successful, 5 total`, 87 Warnungen ausserhalb der Regel (Vorbestand, ausserhalb des Scopes) | | `pnpm type-check` | 4/4 | **Erreicht** — `4 successful, 4 total` | | `apps/web` Vitest | gruen, nicht unter 66/462 | **Erreicht und ueberschritten** — 67 Dateien / 477 Tests (11 neue Zaehlproben in bestehenden Dateien + 1 neue Testdatei mit 3 Faellen) | | `apps/api` Vitest | gruen, nicht unter 71/1136 | **Erreicht, unveraendert** — 71/1136 | | Dashboard, Kalender-Kachel, 60s ruhen | 1x `/calendar/sources`, 1x `/calendar/events` | **An den Orchestrator (Browser/Playwright-MCP) — nicht CLI-pruefbar** | | Dashboard, Kalender-Kachel, 3x Monatsknopf im laufenden Monat | kein zusaetzlicher `/calendar/events` | Per Unit-Test (Test 10 in `calendar-widget.test.tsx`) bewiesen: **pass**. Browser-Nachweis am laufenden System: **an den Orchestrator** | | Dashboard, Kalender-Kachel, 2x weiterblaettern | genau 2 zusaetzliche `/calendar/events` | Per Unit-Test (Test 9) bewiesen: **pass**. Browser-Nachweis: **an den Orchestrator** | | Dashboard, Stoppuhr, Start/Runde/Stopp/Reset | genau 4x `PATCH .../config`; Anzeige laeuft sauber (Menschenurteil) | PATCH-Zaehlung per Unit-Test bewiesen (neuer Testfall: Start+Runde=2 Aufrufe im Testfall selbst, Stop/Reset-Zaehlung bereits in Bestandstests). **Visuelles Laufverhalten: menschliches Urteil, an den Orchestrator** | | Ausschreibungsradar, 60s ruhen | 1x `/tenders`, 1x `/triage` | **An den Orchestrator** | | Ausschreibungsradar, Jetzt abrufen | genau 1 zusaetzlicher `/tenders` | Per Unit-Test (`ResultsList.test.tsx#Befund 15`) bewiesen: **pass**. Browser-Nachweis: **an den Orchestrator** | | Meine Quellen, 60s ruhen | je 1 Abruf fuer Zustell-Einstellung, RSS-Feeds, Quellen-Konfiguration | Re-render-Zaehlproben fuer RssFeedListForm/SourceConfigForm bestehen (`pass`); `DigestIntervalForm` hat keine Testdatei (siehe Luecken). Browser-Nachweis: **an den Orchestrator** | | DKV Flotte, Jetzt pruefen | genau 1 zusaetzlicher Historien-Abruf | `biome-ignore`-Begruendung + bestehendes `refreshKey`-Verhalten unveraendert; kein neuer Unit-Test noetig (Verhalten der Komponente unveraendert). Browser-Nachweis: **an den Orchestrator** | | DKV Flotte, Fahrzeuge, Fahrzeug speichern | Tabelle zeigt neuen Stand, kein Dauerfeuer | Unveraendertes Verhalten (Befund 7 betraf nur den Ladefehler-Text). Browser-Nachweis: **an den Orchestrator** | | Marketplace, Modul aktivieren | erscheint ohne Neuladen in der Seitenleiste | Per Unit-Test (`sidebar.test.tsx#Befund 17`) bewiesen: **pass**. Browser-Nachweis: **an den Orchestrator** | | Modulverwaltung, Aktivierungs-Dialog oeffnen | genau 1x `/groups` | Unveraendertes Verhalten (Befund 18 betraf nur eine ueberfluessige, wirkungslose Abhaengigkeit — kein Verhaltensunterschied im Netzwerkverkehr). Browser-Nachweis: **an den Orchestrator** | | Gruppenverwaltung, Dialog A schliessen, B oeffnen | Mitglieder gehoeren zu B | Per neuer Unit-Test (`GroupMembersModal.test.tsx`, dritter Fall) bewiesen: **pass** — dies ist der einzige echte Defekt (Kategorie A) im ganzen Befund und der Test faellt ohne den Fix durch. Browser-Nachweis zusaetzlich: **an den Orchestrator** | ## An den Orchestrator uebergebene Verifikationsschritte (Browser/Playwright-MCP) Alle mit "an den Orchestrator" markierten Zeilen der Tabelle oben, zusammengefasst — der Executor hat keinen Browser-Zugriff: 1. Dashboard-Kalender: 60s-Zaehlung, 3x Monatsknopf im laufenden Monat, 2x weiterblaettern (D-04). 2. Stoppuhr: visuelles Laufverhalten ueber Start/Runde/Stopp/Reset (Menschenurteil, ausdruecklich nicht automatisierbar) plus PATCH-Zaehlung im Netzwerkprotokoll (D-05). 3. Ausschreibungsradar: 60s-Zaehlung, Jetzt-abrufen-Zaehlung. 4. Meine Quellen: 60s-Zaehlung fuer alle drei Formulare (Zustell-Einstellung/RSS/Quellen-Konfiguration) — fuer `DigestIntervalForm` ist dies die EINZIGE Verifikation, da keine Testdatei existiert. 5. DKV Flotte: Jetzt-pruefen-Zaehlung, Fahrzeug-Speichern-Anzeige. 6. Marketplace: Modul-Aktivierung ohne Neuladen sichtbar in der Seitenleiste. 7. Modulverwaltung: Aktivierungs-Dialog genau 1x `/groups`. 8. Gruppenverwaltung: Mitglieder-Dialog zeigt nach Gruppenwechsel die richtige Gruppe (zusaetzlich zum bereits gruenen Unit-Test). Alle Unit-Test-/CLI-seitig pruefbaren Teile dieser Zeilen sind bereits bewiesen (siehe Tabelle) — an den Orchestrator geht ausschliesslich der Netzwerkzaehlungs-/visuelle Teil, der einen laufenden Docker-Stapel und einen Browser braucht. ## Task Commits 1. **Aufgabe 1: Kalender und Stoppuhr — die Effekte mit Taktgeber (Befunde 1-6)** — `b3f0e3c` (fix) 2. **Aufgabe 2: Die t-Falle und die instabilen Ladefunktionen (Befunde 7-15)** — `e2c508c` (fix) 3. **Aufgabe 3: Absicht, Ballast und die zwei fehlenden stabilen Abhaengigkeiten (Befunde 16-21)** — `e780b2c` (fix) **Plan metadata:** wird vom Orchestrator committet (SUMMARY.md, STATE.md, ROADMAP.md). ## Files Created/Modified Siehe `key-files` im Frontmatter — 24 bestehende Dateien angepasst (15 Quelldateien + 9 Testdateien) plus eine neue Testdatei (`GroupMembersModal.test.tsx`). Keine Datei ausserhalb der 15 im PLAN genannten Befund-Dateien und ihrer Tests wurde angefasst (D-06). ## Decisions Made - Bei `calendar-widget.tsx` wurde die im PLAN vorgeschriebene Reihenfolge (erst `showToday` stabilisieren, dann die Abhaengigkeitsliste umstellen) exakt eingehalten — die umgekehrte Reihenfolge haette einen echten Denial-of-Service-Pfad gegen den Exchange-Server geoeffnet (T-GOF-02). - Fuer `RssFeedListForm.tsx` und `SavedSearchBar.tsx` wurde `useCallback` statt einer weiteren `biome-ignore`-Zeile gewaehlt, weil die Ladefunktionen bereits sauber isolierbar waren und die PLAN-Vorgabe genau das verlangt ("Ladefunktion in einen stabilen Rueckruf einpacken"). - `DigestIntervalForm.tsx` bekam bewusst keine neue Testdatei, wie im PLAN explizit vorgesehen — die Verifikation laeuft ausschliesslich am laufenden System. ## Deviations from Plan None — Plan exakt wie geschrieben ausgefuehrt. Alle drei Aufgaben, alle 21 Befunde, alle im PLAN benannten Testerweiterungen wurden 1:1 umgesetzt. Der einzige nennenswerte Punkt ist keine Abweichung, sondern eine im PLAN selbst schon erwartete Luecke (siehe unten). ## Issues Encountered Beim Schreiben der neuen `GroupMembersModal.test.tsx` traf `screen.getByText('Anna Schmidt')` zunaechst auf zwei Elemente (Mitgliederliste UND Benutzer-Suchliste zeigen denselben Namen) — behoben durch Scoping auf `within(screen.getByRole('list'))`, da die Mitgliederliste die einzige `