docs(quick-260921-jt4): Barrierefreiheit 30 -> 1, vier Restposten erledigt
Tessera CI/CD / Lint & Type Check (push) Successful in 53s
Tessera CI/CD / Tests (push) Successful in 1m14s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m38s

Zusammenfassung und STATE.md zum Quick-Vorgang 260921-jt4.

Der Planer hat zwei Vorgaben des Orchestrators widerlegt: der
vorgesehene Rueckfallweg mit role und Tastaturhandler tauscht gemessen
drei Befunde gegen einen neuen, und vier der elf vermeintlichen
Klick-Befunde sind onError-Handler an Bildern, also gar keine
Bedienung. Alle fuenf ARIA-Befunde waren Beschriftungen auf rollenlosen
Elementen, die Vorleseprogramme still verwerfen.

Laufzeitnachweis vom Orchestrator: drei Monatswechsel holen die
Quellenliste nur noch einmal statt viermal, der Termin-Abruf mit
identischem Zeitraum ist weg. Der 5-Minuten-Auffrischer ist per
gestellter Uhr als intakt belegt, nicht per Warten im Browser - zwei
solche Messversuche waren ungueltig, weil das Werkzeug die Seite
zwischendurch neu laedt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 15:23:10 +02:00
parent f471b785db
commit 8d604b855a
2 changed files with 280 additions and 4 deletions
+5 -4
View File
@@ -4,8 +4,8 @@ milestone: v1.2
current_phase: 18
current_phase_name: desktop-client-fertigstellen
status: verified
stopped_at: "Sieben Quick-Vorgaenge am 2026-09-21 abgeschlossen und verifiziert (9ie, a1d, bi2, fi3, gof, i8x, iwr). Der fehlerverdaechtige Lint-Rueckstand ist damit vollstaendig geprueft: zwei echte Fehler gefunden und behoben (tote Passwortwechsel-Sperre an der API, stille 403-Antworten in der Benutzerverwaltung), der Rest war harmlos oder Absicht, je Stelle in den Akten begruendet. Offen und NICHT fehlerverdaechtig: 288 any im Quellcode, 30 a11y-Befunde mit Gestaltungsbedarf. Naechster Auftrag des Nutzers: zwei neue Dashboard-Widgets."
last_updated: "2026-09-21T12:45:00.000Z"
stopped_at: "Acht Quick-Vorgaenge am 2026-09-21 (9ie, a1d, bi2, fi3, gof, i8x, iwr, jt4). OFFEN UND WICHTIG: CI-Lauf 395 war rot wegen eines WACKELTESTS, nicht wegen der Arbeit — apps/web/src/components/bug-report/bug-report-button.test.tsx Test 1 faellt rund einmal in 17 vollen Laeufen durch (Vorschaubild da, Haekchen 'Bildschirmfoto anhaengen' aus). Stammt aus quick-260914-m97. Noch NICHT geklaert, ob das nur im Test so aussieht oder ob ein Nutzer sein Bild in der Vorschau sieht und es trotzdem nicht mitgeschickt wird — das ist der naechste Auftrag. Danach: 288 any in apps/api, dann zwei neue Dashboard-Widgets (eins davon ein Bilderrahmen, das zweite noch unbenannt). jt4 ist noch nicht gepusht."
last_updated: "2026-09-21T13:30:00.000Z"
last_activity: 2026-09-21
last_activity_desc: Quick 260921-9ie, a1d, bi2, fi3 und gof — Lint-Tor scharf, Benutzerverwaltung meldet abgewiesene Aktionen, Lint-Rueckstand 2923 → 446, erzwungener Passwortwechsel an der API durchgesetzt (war eine tote Sperre), 21 Effekt-Abhaengigkeiten einzeln beurteilt; alle fuenf verifiziert, die letzten drei am laufenden System
state_head: 4d485432c003a6caf68f6d85aff7de0bd27794e2
@@ -31,7 +31,7 @@ See: .planning/PROJECT.md (updated 2026-07-17)
Phase: 18 (desktop-client-fertigstellen) — COMPLETE (2026-09-17, Verifikation passed, Windows-Bedienprobe bestanden)
Plan: 6 of 6
Status: Alle 18 Phasen abgeschlossen; Version 1.2.0 freigegeben. Kein laufender Meilenstein. Nach 1.2.0 auf main (Beta): Bildmarke in Akzentfarbe, CI-Desktop-Skip, Favoriten-Symbol/-Sortierung, Desktop-Server-Adresse, Update in der App (signiert), Versionszeile auf der Setup-Seite — alles verifiziert und auf VM/CI nachgewiesen
Last activity: 2026-09-21 - Quick 260921-iwr: 30 Listenschluessel- und Zusicherungs-Stellen geprueft, kein echter Fehler; beide Verdachtsmomente (LDAP-Seite, Cert-Manager) durch Messung widerlegt. Der fehlerverdaechtige Rueckstand ist damit abgearbeitet
Last activity: 2026-09-21 - Quick 260921-jt4: Barrierefreiheit 30 → 1 Befund und die vier Restposten erledigt; zwei Vorgaben des Orchestrators vom Planer widerlegt (Rueckfallweg erzeugt neue Befunde, vier vermeintliche Klicks sind onError-Handler). Kalender-Verschwendung im Browser nachgemessen, 5-Minuten-Auffrischer per gestellter Uhr als intakt belegt
Progress: [██████████] 99%
@@ -451,6 +451,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests.
| 260921-gof | **21 React-Effekt-Abhaengigkeiten einzeln beurteilt — 15 davon waren Fallen, nicht Fehler.** Die Klasse war aus 260921-bi2 zurueckgestellt worden, weil jeder Befund einzeln zu beurteilen ist. Ergebnis: nur **2 echte Defekte** (A), **15 Fallen** (B, das naive Eintragen haette eine Abruf-Schleife erzeugt), **3 Absicht** (C, mit begruendetem `biome-ignore` — erste Verwendung im Projekt), **1 Ballast** (D). **Die gefaehrlichste Stelle:** `calendar-widget.tsx:82` — `showToday` setzt bei jedem Klick ein frisches `Date`; `monthDate` naiv in die Liste einzutragen haette **jeden** Druck auf den Monatstitel einen Termin-Abruf ausloesen lassen, ueber die API bis zum Exchange-Server. Reihenfolge war Pflicht: erst Identitaet stabilisieren, dann die Liste umstellen. **Die haeufigste Falle:** `t` aus `useTranslations` ist in diesem Projekt bei jedem Durchlauf eine frische Funktion (die Test-Attrappen sind nachweislich so gebaut) — 8 Befunde. Griff ohne Ausnahme-Kommentar: den uebersetzten Text vor dem Hook in eine Konstante ziehen und diese eintragen; React vergleicht Zeichenketten per Wert. **Nebenbefund:** 11 `eslint-disable`-Zeilen fuer genau diese Regel waren wirkungslos, seit Biome ESLint abgeloest hat — alle entfernt. **Laufzeitnachweis vom Orchestrator im Browser** (Netzwerkprotokoll, nie `fetch` aus der Seite; gegen neu gebaute Abbilder): Dashboard 62 s Ruhe → Protokoll byte-identisch, genau 1 `calendar/events`; Monatstitel 3x gedrueckt → nur der erste Druck (Bereich aendert sich wirklich) loest einen Abruf aus, Druck 2 und 3 **null**; "Weiter" 3x → 3 Abrufe, korrekt; Stoppuhr 6 s real → Anzeige 00:06, 4 Runden ueber 4,8 s → 16/17/19/20 monoton, kein Ruecksprung. Dazu acht weitere Ansichten je 20-25 s ruhen gelassen (Marktplatz, Modulverwaltung, Gruppenverwaltung, DKV dreimal, Ausschreibungsradar zweimal) — jeder Endpunkt genau einmal. `InvoiceHistoryTable` hatte als einzige Datei keinen Test und ist damit gemessen statt nur gelesen; `ResultsList` ist die Stelle, an der die `t`-Falle in bi2 tatsaechlich zuschnappte. **Zahlen:** Warnungen 467 → 446 (exakt 21, nichts anderswo gewachsen), `useExhaustiveDependencies` 0, web-Tests 66/462 → 67/477, api 71/1136 unveraendert, type-check 4/4, `pnpm lint` 5/5 ohne Fehlerrang. Verifikation passed. **Benannt, nicht behoben:** zwei Verschwendungen im Kalender-Abruffenster (gleicher Zeitbereich zweimal geholt; `calendar/sources` bei jedem Monatswechsel) — vorbestehend; und `t` in vier vorbestehenden Abhaengigkeitslisten ausserhalb des Auftrags, die Biome nie gemeldet hat. | 2026-09-21 | b3f0e3c,e2c508c,e780b2c | [260921-gof-effekt-abhaengigkeiten-in-react-21-befun](./quick/260921-gof-effekt-abhaengigkeiten-in-react-21-befun/) |
| 260921-i8x | **Fuenf fehlerverdaechtige Lint-Klassen geprueft — kein einziger echter Fehler darunter.** Zwoelf Stellen einzeln beurteilt, Ergebnis: 7x gleichwertig oder Absicht, 2x Haertung, 3x idiomatisch korrekt. Das ist das Ergebnis, keine Ausrede — die Klassen klangen gefaehrlicher als sie waren. **Die eine Stelle mit echtem Wert:** `apps/web/src/lib/safe-next.ts`, der Schutz gegen Weiterleitung auf fremde Seiten nach der Anmeldung. Der Kommentar dort behauptete, die Steuerzeichen stuenden als Unicode-Escapes im Muster; die rohen Bytes zeigten das Gegenteil (NUL, 0x1F, 0x7F direkt eingebettet). Funktionierte, war aber zerbrechlich: verschluckt ein Werkzeug das NUL-Byte, wird aus dem Bereich stillschweigend ein anderer und der Schutz loechrig — der Rueckgabewert landet in `login/page.tsx` direkt in `window.location.href`. Jetzt echte Escapes, Datei ohne ein einziges Steuerbyte. **Beweis der Gleichwertigkeit, nicht Behauptung:** ueber alle 65536 Codepunkte dieselbe Menge abgelehnter Zeichen — 54 Stueck (32 Steuerzeichen 0x00-0x1F, dazu 0x7F, Backslash und die 20 Leerraum-Zeichen von `\s`), null Abweichung; vom Orchestrator unabhaengig gegen ein selbst gebautes Referenzmuster nachgerechnet. **Bewusst nicht angefasst:** die NUL-Maskierung in `ldap.service.ts` (RFC 4515) — genau dieses Zeichen zu treffen ist ihr Zweck, wer sie "repariert", oeffnet LDAP-Filter-Injection. Ebenso die drei `while ((m = re.exec(s)))`-Schleifen (idiomatisch, kein verrutschtes Gleichheitszeichen) und `noUselessSwitchCase` aus bi2. **Zwei Korrekturen an frueheren Annahmen:** `sanitizeNextPath` laeuft NICHT in der Edge-Middleware (die importiert nur `buildNextParam`), und das blosse Umschreiben auf Escapes senkt die Warnzahl nicht — Biome beanstandet die Escape-Schreibweise genauso, es braucht zusaetzlich einen einzeiligen Unterdrueckungskommentar. **Werkzeugfalle, dreimal zugeschnappt:** das Schreibwerkzeug wandelt `\uXXXX` still in das echte Zeichen um — der Planer erzeugte so zehn rohe Steuerbytes in seiner ersten Planfassung, der Executor zweimal in Commit-Text und Akte (git verweigerte den Commit wegen eines NUL-Bytes), und der Orchestrator beim Nachrechnen. Umgehung ueber `python3`/`chr(92)` ist im Plan hinterlegt. **Zahlen:** 446 → 434, `noControlCharactersInRegex`/`useIterableCallbackReturn`/`noGlobalIsNan` je 0, `suppressions/unused` 0, web-Tests 67/477 → 68/481, api 71/1136 → 72/1137, type-check 4/4, lint 5/5. | 2026-09-21 | f85c91b,076ca4b,b92dd5d | [260921-i8x-fehlerverdaechtige-lint-klassen-steuerze](./quick/260921-i8x-fehlerverdaechtige-lint-klassen-steuerze/) |
| 260921-iwr | **Listenschluessel und Ausrufezeichen-Zusicherungen: 30 Stellen geprueft, wieder kein echter Fehler.** Damit ist der fehlerverdaechtige Rueckstand abgearbeitet. **Zwei Vorannahmen des Orchestrators widerlegt, beide durch Messung statt Argument:** (1) Die LDAP-Seite galt als heisser Kandidat, weil dort Zuordnungsregeln hinzugefuegt und geloescht werden — die Liste, die tatsaechlich waechst und schrumpft (`config.fieldMappings`), benutzt jedoch laengst `key={mapping.id}`; die sechs Meldungen betreffen zustandslose Textlisten. (2) Im Cert-Manager galt eine Zusicherung auf hochgeladenen Dateiinhalt als moeglicher Absturz — der Planer hat eine 83-Byte-Schrottdatei gebaut, die node-forge `bag.cert = null` setzen laesst, und gegen den echten Dienst laufen lassen: **alle vier Pfade enden mit 400, nie 500**, und `certificateToPem(null)` wirft nachweislich, statt still ein falsches Zertifikat zu bauen. Also weder Verfuegbarkeits- noch Integritaetsluecke, sondern eine irrefuehrende Fehlermeldung. **Ein Fund dreht die Richtung um:** bei `admin/modules/grants/page.tsx:246` waere die Korrektur schaedlich — die Gruppierung fasst nur aufeinanderfolgende Kategorien zusammen, die Positionsnummer ist dort fuer die Eindeutigkeit noetig, ohne sie entstuenden doppelte Schluessel. **Geaendert: 5 Stellen** (drei Waechter im Cert-Manager, die den Meldungstext praezisieren — Status bleibt 400, rot-dann-gruen belegt; zwei ueberfluessige Zusicherungen in `imap.provider.ts`, die imapflow ohnehin als Pflichtfeld typisiert). **25 Stellen bleiben bewusst stehen und bleiben in der Zaehlung sichtbar** — mit Begruendung je Stelle in der Akte, damit der naechste Durchgang sie nicht erneut aufrollt; kein Unterdrueckungskommentar, um die Zahl zu schoenen. **Das Tor hat sich selbst bewaehrt:** der erste Entwurf eines Waechters erzeugte einen neuen Lint-Fund (430 statt 429) und wurde von der Verifikation des Plans gefangen; die Reparatur brach `tsc`, weil `@types/node-forge` `Bag.cert` als `Certificate | undefined` deklariert, waehrend die Bibliothek zur Laufzeit `null` zuweist — Endfassung prueft beides. **Zahlen:** 434 → 429, `noArrayIndexKey` unveraendert 19 (alle geprueft, alle harmlos), `noNonNullAssertion` 11 → 6, web-Tests 68/481 → 69/484, api 72/1137 → 72/1143, type-check 4/4, lint 5/5. | 2026-09-21 | 8716fa5,b4aaed4,27909e4,de69863 | [260921-iwr-listenschluessel-per-positionsnummer-und](./quick/260921-iwr-listenschluessel-per-positionsnummer-und/) |
| 260921-jt4 | **Barrierefreiheit von 30 auf 1 Befund, plus die vier zurueckgestellten Restposten.** Die 30 a11y-Befunde galten seit bi2 als "braucht Bedienentscheidungen"; die hat der Orchestrator getroffen, und der Planer hat **zwei davon widerlegt**: (1) Der vorgesehene Rueckfallweg (`role` + `tabIndex` + Tastaturhandler, wo kein echter Knopf geht) tauscht gemessen drei Befunde gegen einen neuen `useSemanticElements` — eine Regel, die bi2 gerade erst auf 0 gebracht hatte; wird nirgends benutzt, fuer den Verschachtelungsfall (Marktplatz-Karte) tritt eine deckende Geschwister-Schaltflaeche an seine Stelle. (2) **Vier der elf "Klick"-Befunde sind gar keine Klicks**, sondern `onError`-Handler an `<img>` — da gibt es keinen Tastaturweg zu schaffen, sie bekommen `aria-hidden`. **Ein Fund darueber hinaus:** alle fuenf ARIA-Befunde sind `aria-label` auf rollenlosen Elementen — die werden von Vorleseprogrammen still verworfen, die Beschriftungen kamen also bei niemandem an; jetzt mit korrekter Rolle. Sechs Stellen wurden zu echten `<button>` (Aussehen unveraendert), vier `autoFocus` auf Seiten entfernt (auf Seiten reisst er beim Laden den Fokus an sich — im Dialog waere er richtig gewesen, alle vier waren Seiten). **Ein Befund bleibt bewusst stehen und bleibt gezaehlt** (`calculator-widget.tsx:323`), samt ausdruecklich verworfener Umgehung. **Restposten:** ZIP-Name uebersetzt mit getesteter Schutzfunktion `zip-filename.ts` (der frueher genannte Umlaut-Einwand trifft fuer "Zertifikate.zip" nicht zu, die Schutzfunktion sichert kuenftige Uebersetzungen ab); die ueberfluessige `case`-Marke im Normalisierer aufgeloest, Absicht in den Kommentar gewandert; Kalender-Verschwendung abgestellt. **Zur `t`-Frage eine Korrektur an gof:** `use-intl` 4.13 erzeugt `t` in einem `useMemo`, es ist also in der Bibliothek stabil — instabil ist es nur in den Test-Attrappen, und daher kam der Beleg von damals. Die acht Korrekturen aus gof bleiben richtig und schaedlich sind sie nicht, aber die Begruendung war zu breit; ein Test an der Wurzel misst es jetzt. **Laufzeitnachweis vom Orchestrator** (Browser, 90 Tage Vorschau — bei der Voreinstellung 30 tritt der Doppelabruf gar nicht auf, die Messung haette also nichts gezeigt): drei Monatswechsel holen `calendar/sources` nur noch **1x statt 4x**, und der Termin-Abruf mit identischem Zeitraum ist weg (3 Klicks → 2 Abrufe statt 3). Der 5-Minuten-Auffrischer bleibt unangetastet — belegt nicht durch Warten im Browser (zwei Messversuche waren ungueltig, weil das Werkzeug die Seite zwischendurch neu laedt: nach 330 s Wartezeit war das Dokument 37 s alt), sondern durch Test 19 mit gestellter Uhr: nach `advanceTimersByTime(300_000)` werden **beide** Abrufe erneut ausgefuehrt. **Zahlen:** 429 → 399, a11y 30 → 1, web-Tests 69/484 → 73/529, api 72/1143 unveraendert, type-check 4/4, lint 5/5, keine neuen Unterdrueckungen. | 2026-09-21 | a8531d4,3d0bc0b,0c89c13,b601141,e651c24,+7 | [260921-jt4-barrierefreiheit-mit-bedienentscheidunge](./quick/260921-jt4-barrierefreiheit-mit-bedienentscheidunge/) |
## Deferred Items
@@ -496,4 +497,4 @@ Last session: 2026-09-21T04:50:00Z
Resumed: 2026-09-21 — Sitzung ueber /gsd-resume-work fortgesetzt. Stand geprueft: Arbeitsbaum sauber, main == origin/main auf 55aa287, CI-Lauf 387 fuer 55aa287 erfolgreich (Beta-Images gebaut). Push und CI aus dem letzten Stopp-Punkt sind damit erledigt.
Stopped at: Warte auf Nutzerentscheidung, womit weitergearbeitet wird. Offen fuer den User: alpha pullen (web+api) und danach am Windows-VM-Client die echte Fehlermeldung schicken (Betreff `[Desktop/Windows]` + `Herkunft:`-Zeile pruefen); eigenen Arbeitsplatz-Client einmal per Browser-Installer erneuern; Freigabe 1.3.0 auf Zuruf. Technisch offen im Ledger: WINDOWS #35 (Biome laeuft nicht — biome.json:3 `organizeImports` ist in Biome 2.5.0 unbekannt, `biome check` bricht mit Konfigurationsfehler ab, reproduziert 2026-09-21) und WINDOWS #36 (403-Antworten bleiben in handleSubmit/handleDelete ohne sichtbare Reaktion).
Resume file: None
Last activity: 2026-09-21 - Quick 260921-iwr: 30 Listenschluessel- und Zusicherungs-Stellen geprueft, kein echter Fehler; beide Verdachtsmomente (LDAP-Seite, Cert-Manager) durch Messung widerlegt. Der fehlerverdaechtige Rueckstand ist damit abgearbeitet
Last activity: 2026-09-21 - Quick 260921-jt4: Barrierefreiheit 30 → 1 Befund und die vier Restposten erledigt; zwei Vorgaben des Orchestrators vom Planer widerlegt (Rueckfallweg erzeugt neue Befunde, vier vermeintliche Klicks sind onError-Handler). Kalender-Verschwendung im Browser nachgemessen, 5-Minuten-Auffrischer per gestellter Uhr als intakt belegt
@@ -0,0 +1,275 @@
---
phase: quick-260921-jt4
plan: 01
subsystem: ui
tags: [a11y, biome, next-intl, react, calendar, i18n]
requires:
- phase: quick-260921-bi2
provides: "155 a11y-Fixes ueber sechs Regeln; die fuenf hier bearbeiteten Regeln (30 Befunde) bewusst zurueckgestellt (D-05)"
- phase: quick-260921-gof
provides: "21 useExhaustiveDependencies-Befunde beurteilt; die Faustregel 't gehoert in keine Abhaengigkeitsliste' als unbestaetigte Annahme hinterlassen"
provides:
- "30 zurueckgestellte a11y-Befunde auf 1 gesenkt (429 -> 399 gesamt), Weg pro Fundstelle protokolliert"
- "13 Oberflaechendateien mit echten Schaltflaechen statt klickbarer Bereiche statt Mausonly-Bedienung"
- "Kalender-Widget ruft Quellenliste und Termine nicht mehr blind bei jedem Monatswechsel neu ab"
- "t aus useTranslations ist am echten NextIntlClientProvider als identitaetsstabil ueber lokale Zustandswechsel nachgewiesen (widerlegt die gof-Annahme 'bei jedem Render frisch')"
- "Uebersetzter, windows-sicherer ZIP-Dateiname im Zertifikat-Aufteiler (zip-filename.ts)"
affects: [apps/web-a11y, dashboard-calendar-widget, i18n-catalogs]
actuals:
tokens: 21513
tasks: 3
commits: 12
tech-stack:
added: []
patterns:
- "Deckende Geschwister-Schaltflaeche statt role=button+tabIndex+onKeyDown: verschachtelte <button>-Elemente vermieden (MarketplaceCard-Muster, auch fuer Dialoghintergruende), ohne useSemanticElements neu auszuloesen."
- "role=toolbar statt role=group/region fuer beschriftete Schaltflaechenreihen ohne semantisches HTML-Aequivalent — role=group/region loesen useSemanticElements neu aus, role=toolbar nicht (an Biome 2.5.0 mit Projektkonfiguration gemessen)."
- "Ref-basierte Sperrgriffe gegen verschwendete Effekte: hasSourcesRef/lastFetchWindowRef ausserhalb des Render-Zustands, mit einem expliziten force-Parameter fuer den Fall, der den Sperrgriff bewusst umgehen muss (5-Minuten-Auffrischer)."
- "t-Identitaet aus useTranslations gegen den echten NextIntlClientProvider messen (nicht behaupten) — Testkomponente unter dem echten Provider, Zustandswechsel ausloesen, per toBe (Referenzgleichheit) pruefen."
- "Regex mit \\x00-\\x1F-Bereich vermeiden (lint/suspicious/noControlCharactersInRegex) — Steuerzeichen-Filterung zeichenweise statt per Regex, wenn das Filtern von Steuerzeichen der Zweck ist."
key-files:
created:
- apps/web/src/app/(portal)/modules/cert-manager/zip-filename.ts
- apps/web/src/app/(portal)/modules/cert-manager/zip-filename.test.ts
- apps/web/src/components/dashboard/widget-catalog-modal.test.tsx
- apps/web/src/components/settings/calendar-settings-panel.test.tsx
- apps/web/src/lib/translations-identity.test.tsx
modified:
- apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.tsx
- apps/web/src/components/dashboard/widget-catalog-modal.tsx
- apps/web/src/components/settings/calendar-settings-panel.tsx
- apps/web/src/components/dashboard/widgets/note-widget.tsx
- apps/web/src/components/dashboard/widgets/note-task-list.tsx
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx
- apps/web/src/components/dashboard/widgets/calculator-widget.tsx
- apps/web/src/components/dashboard/widgets/favorites-widget.tsx
- apps/web/src/components/layout/header.tsx
- apps/web/src/components/settings/account-settings-form.tsx
- "apps/web/src/app/(auth)/login/page.tsx"
- "apps/web/src/app/(auth)/reset-password/page.tsx"
- "apps/web/src/app/(auth)/reset-password/[token]/page.tsx"
- "apps/web/src/app/(portal)/change-password/page.tsx"
- "apps/web/src/app/(portal)/modules/cert-manager/components/SplitTab.tsx"
- apps/api/src/tenders/tender-normalizer.service.ts
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
key-decisions:
- "D-01-Rueckfall (role=button+tabIndex+onKeyDown) an keiner der sechs Umbaustellen benutzt — der Planer hatte ihn empirisch geprueft und drei Befunde gegen einen neuen useSemanticElements-Befund getauscht; D-07 verbietet Wachstum anderswo. Deckende Geschwister-Schaltflaeche stattdessen bei MarketplaceCard, widget-catalog-modal und calendar-settings-panel."
- "calendar-widget Tageszelle wird NUR bei hasEvents zu einem <button> — 42 neue Tab-Stopps waeren eine Verschlechterung, nicht Tage ohne Termine."
- "Vier autoFocus-Stellen sind alle Seitenformulare, kein Dialog: Attribut entfernt (D-02), der ref+Effekt-Ersatzweg kam an keiner Stelle zum Zug."
- "t-Identitaet gemessen statt der gof-Annahme ein drittes Mal weitergetragen: bleibt bei einem echten NextIntlClientProvider-Test erhalten -> die vier verbliebenen t-Abhaengigkeitsstellen bleiben unveraendert."
- "ZIP-Dateiname darf sich nicht auf einen zufaellig harmlosen Uebersetzungstext verlassen — eigene, fuer sich getestete Schutzfunktion (zip-filename.ts) statt Vertrauen in den Katalogwert."
- "noUselessSwitchCase-Fallmarke in tender-normalizer.service.ts entfernt, Kommentar erweitert statt der Marke selbst eine Bedeutung zuzuschreiben, die die Regel nicht sehen kann — kein Verhaltenswechsel."
patterns-established:
- "Vollstaendige a11y-Regelmenge (Ziel- UND zurueckgestellte Regeln) nach jedem Teilumbau neu messen, nicht nur die Zielregel — ein a11y-Fix kann eine ANDERE Regel neu ausloesen, wenn ein Element seine interaktive Klassifikation verliert."
requirements-completed: [D-01, D-02, D-03, D-04, D-05, D-06, D-07]
duration: ~2h (eine Sitzung)
completed: 2026-09-21
status: complete
---
# Quick-Vorgang 260921-jt4: 30 a11y-Befunde mit Bedienentscheidungen abgearbeitet Summary
**30 zurueckgestellte Barrierefreiheits-Befunde auf 1 gesenkt (429 -> 399 gesamt, 0 Fehler) — 13 Oberflaechendateien mit echten Schaltflaechen statt klickbarer Bereiche, das Kalender-Widget ruft Quellenliste/Termine nicht mehr blind bei jedem Monatswechsel neu ab, und die gof-Annahme "t ist bei jedem Render frisch" ist am echten `NextIntlClientProvider` widerlegt statt ein drittes Mal weitergetragen.**
## Performance
- **Tasks:** 3 von 3 (PLAN.md)
- **Commits:** 12 (siehe Task Commits)
- **Dateien geaendert:** 28 (23 Quell-/Testdateien + 2 i18n-Kataloge + PLAN.md/keine weiteren)
- **apps/web Tests:** 66 -> 73 Dateien, 462 -> 529 Tests (Netto-Zuwachs 7 Dateien / 67 Tests)
- **apps/api Tests:** unveraendert 72 Dateien / 1143 Tests (nur ein Kommentar/eine Fallmarke in tender-normalizer.service.ts geaendert, kein neuer Test noetig — die 18 vorhandenen Tests belegen weiterhin dasselbe Verhalten)
## Accomplishments
- 30 zurueckgestellte a11y-Befunde auf genau 1 gesenkt (der bewusst stehengelassene Taschenrechner-Tastaturhandler, D-07) — Biome-JSON `429 -> 399`, `errors 0` durchgehend
- Fuenf Struktur-Umbauten (MarketplaceCard, widget-catalog-modal, calendar-settings-panel Loeschdialog, note-widget Kaestchen, calendar-widget Tageszelle) auf echte `<button>`-Elemente, jeder Umbau mit einem Test belegt, der eine ECHTE Tastaturbetaetigung ausloest (Enter/Leertaste auf einem fokussierten Element), nicht nur eine Klick-Attrappe
- Kalender-Widget: Quellenliste und Termin-Ladefenster werden nicht mehr blind bei jedem Monatswechsel neu geholt — vier zaehlende Tests belegen Aufbau/abweichendes Fenster/identisches Fenster/erzwungener Auffrischer
- `translations-identity.test.tsx`: `t` aus `useTranslations` ist am echten `NextIntlClientProvider` als identitaetsstabil ueber einen lokalen Zustandswechsel nachgewiesen — widerlegt die 260921-gof-Annahme "t ist bei jedem Render frisch"
- Windows-sicherer, uebersetzter ZIP-Dateiname im Zertifikat-Aufteiler ueber eine neue, fuer sich getestete Schutzfunktion (`zip-filename.ts`), unabhaengig davon, ob der aktuelle Katalogwert zufaellig harmlos ist
- Zwei Restposten aus 260921-bi2/gof geschlossen: ueberfluessige `case`-Marke in `tender-normalizer.service.ts` entfernt; keine neue Unterdrueckung (`biome-ignore`) im gesamten Diff
## Task Commits
Aufgabe 1 (Aus klickbaren Bereichen echte Schaltflaechen machen, 5 Teilumbauten, jeder einzeln committet):
1. **MarketplaceCard.tsx — Karte** - `a8531d4` (fix)
2. **widget-catalog-modal.tsx — Hintergrund** - `3d0bc0b` (fix)
3. **calendar-settings-panel.tsx — Loeschdialog-Hintergrund** - `0c89c13` (fix)
4. **note-widget.tsx + note-task-list.tsx — Aufgabenkaestchen** - `b601141` (fix)
5. **calendar-widget.tsx — Tageszelle** - `e651c24` (fix)
Aufgabe 2 (Rollen, Beschriftungen, Autofokus, zwei Restposten, jeder Teilumbau einzeln committet):
6. **autoFocus von vier Seitenformularen entfernt (D-02)** - `9aa87bd` (fix)
7. **calendar-settings-panel: drei Statussymbole role="img" (D-03)** - `b406a9c` (fix)
8. **calculator-widget/favorites-widget: zwei Schaltflaechenreihen role="toolbar" (D-03)** - `f7b5df4` (fix)
9. **vier `<img onError>` mit aria-hidden (D-03)** - `69fe706` (fix)
10. **tender-normalizer.service.ts: ueberfluessige case-Marke entfernt (Restposten 2)** - `5a03b75` (refactor)
11. **ZIP-Name im Zertifikat-Aufteiler mit Windows-Schutzfunktion (Restposten 1)** - `6c10c9b` (feat)
Aufgabe 3 (Verschwendete Kalender-Abrufe + t-Identitaet, ein Commit):
12. **Kalender-Ladefenster/Quellenliste entdoppelt, t-Identitaet gemessen** - `f471b78` (fix)
_Hinweis: keine separate Plan-Metadaten-Commit — diese SUMMARY und STATE.md/ROADMAP.md werden vom Orchestrator committet._
## Files Created/Modified
- `apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.tsx` - Kartenklick als deckende Geschwister-Schaltflaeche
- `apps/web/src/components/dashboard/widget-catalog-modal.tsx` - Hintergrund als benannte Schaltflaeche, totes stopPropagation entfernt
- `apps/web/src/components/settings/calendar-settings-panel.tsx` - Loeschdialog-Hintergrund als Abbrechen-Schaltflaeche; drei Statussymbole role="img"
- `apps/web/src/components/dashboard/widgets/note-widget.tsx` + `note-task-list.tsx` - Aufgabenkaestchen bedient sich selbst (onChange statt readOnly+delegiertem Klick)
- `apps/web/src/components/dashboard/widgets/calendar-widget.tsx` - Tageszelle als Schaltflaeche bei Terminen; Quellenliste/Ladefenster-Sperrgriffe
- `apps/web/src/components/dashboard/widgets/calculator-widget.tsx` - Speicherzeile role="toolbar", drei Texte in den Katalog gezogen
- `apps/web/src/components/dashboard/widgets/favorites-widget.tsx` - Ansichtsumschalter role="toolbar" mit korrigierter Beschriftung; zwei `<img>` aria-hidden
- `apps/web/src/components/layout/header.tsx` + `apps/web/src/components/settings/account-settings-form.tsx` - Avatar-`<img onError>` aria-hidden
- `apps/web/src/app/(auth)/login/page.tsx`, `reset-password/page.tsx`, `reset-password/[token]/page.tsx`, `apps/web/src/app/(portal)/change-password/page.tsx` - autoFocus entfernt
- `apps/web/src/app/(portal)/modules/cert-manager/zip-filename.ts` (neu) + `.test.ts` (neu) - Windows-sichere ZIP-Namens-Schutzfunktion
- `apps/web/src/app/(portal)/modules/cert-manager/components/SplitTab.tsx` - uebersetzter, sanitierter ZIP-Name
- `apps/api/src/tenders/tender-normalizer.service.ts` - ueberfluessige case-Marke entfernt, Kommentar erweitert
- `apps/web/src/lib/translations-identity.test.tsx` (neu) - t-Identitaet am echten Provider gemessen
- `apps/web/src/messages/de.json` / `en.json` - 20 neue Schluessel, beide Kataloge weiterhin gleich lang (912/912)
## Decisions Made
Siehe `key-decisions` im Frontmatter. Zusammengefasst: der D-01-Rueckfallweg wurde an keiner Stelle benutzt (empirisch als regelvermehrend erkannt); die Kalender-Tageszelle wird nur bei Terminen zu einem Tab-Stopp; alle vier `autoFocus`-Stellen sind Seiten, kein Dialog, deshalb Entfernen statt Ersatz; die t-Identitaetsfrage wurde gemessen statt behauptet und bestaetigt die vier verbliebenen Stellen als unveraendert richtig; der ZIP-Name haengt an einer eigenen Schutzfunktion, nicht am Zufall des Uebersetzungstexts.
## Die 30 a11y-Fundstellen — gewaehlter Weg je Fundstelle
| # | Fundstelle | Regel | Gewaehlter Weg | Begruendung (bei Rueckfall/Stehenlassen) |
|---|---|---|---|---|
| 1 | MarketplaceCard.tsx (Karte) | noNoninteractiveElementInteractions | echter Button (deckendes Geschwister) | Karte enthaelt bereits eine Schaltflaeche im Fuss — unmittelbare `<button>`-Umwandlung waere eine Verschachtelung (Falle wie DropZone.tsx in 260921-bi2) |
| 2 | MarketplaceCard.tsx (Karte) | noStaticElementInteractions | echter Button (deckendes Geschwister) | s. #1 |
| 3 | MarketplaceCard.tsx (Karte) | useKeyWithClickEvents | echter Button (deckendes Geschwister) | s. #1 — der Button traegt den Tastaturweg von selbst |
| 4 | widget-catalog-modal.tsx (Hintergrund) | noNoninteractiveElementInteractions | echter Button | vorher rein optische Flaeche, jetzt fokussierbar und benannt |
| 5 | widget-catalog-modal.tsx (Hintergrund) | noStaticElementInteractions | echter Button | s. #4 |
| 6 | widget-catalog-modal.tsx (Hintergrund) | useKeyWithClickEvents | echter Button | s. #4 |
| 7 | widget-catalog-modal.tsx (Dialogflaeche) | noNoninteractiveElementInteractions | Handler entfaellt ersatzlos | `stopPropagation` wird toter Code, sobald der Hintergrund Geschwister statt Vorfahr der Dialogflaeche ist |
| 8 | widget-catalog-modal.tsx (Dialogflaeche) | useKeyWithClickEvents | Handler entfaellt ersatzlos | s. #7 |
| 9 | calendar-settings-panel.tsx (Loeschdialog-Hintergrund) | noNoninteractiveElementInteractions | echter Button | muss abbrechen, darf nie loeschen (T-JT4-04); Beschriftung nennt ausdruecklich das Abbrechen |
| 10 | calendar-settings-panel.tsx (Loeschdialog-Hintergrund) | noStaticElementInteractions | echter Button | s. #9 |
| 11 | calendar-settings-panel.tsx (Loeschdialog-Hintergrund) | useKeyWithClickEvents | echter Button | s. #9 |
| 12 | note-widget.tsx (Vorschau-Kaestchen) | noNoninteractiveElementInteractions | Kaestchen uebernimmt seinen Handler selbst | `readOnly` entfernt, echtes `onChange` am Kaestchen statt delegiertem Klick am Behaelter |
| 13 | note-widget.tsx (Vorschau-Kaestchen) | noStaticElementInteractions | Kaestchen uebernimmt seinen Handler selbst | s. #12 |
| 14 | note-widget.tsx (Vorschau-Kaestchen) | useKeyWithClickEvents | Kaestchen uebernimmt seinen Handler selbst | s. #12 — ein `<input type="checkbox">` bedient sich per Definition mit der Tastatur |
| 15 | calendar-widget.tsx (Tageszelle) | noNoninteractiveElementInteractions | echter Button + Fokus-Handler, NUR bei Terminen | 42 neue Tab-Stopps waeren eine Verschlechterung; nur Tage mit Terminen werden zu Tab-Stopps |
| 16 | calendar-widget.tsx (Tageszelle) | noStaticElementInteractions | echter Button + Fokus-Handler, NUR bei Terminen | s. #15 |
| 17 | login/page.tsx | noAutofocus | Attribut entfernt | Seite, kein Dialog — D-02 verlangt Entfernen, nicht ref+Effekt |
| 18 | reset-password/[token]/page.tsx | noAutofocus | Attribut entfernt | Seite, kein Dialog |
| 19 | reset-password/page.tsx | noAutofocus | Attribut entfernt | Seite, kein Dialog |
| 20 | change-password/page.tsx | noAutofocus | Attribut entfernt | Seite, kein Dialog — Nebennutzen: Fokus faellt jetzt nicht mehr am Hinweis zum erzwungenen Wechsel vorbei |
| 21 | calendar-settings-panel.tsx (Sync-Fehler-Symbol) | useAriaPropsSupportedByRole | `role="img"` + Katalogschluessel | Beschriftung ist der einzige Text des Symbols (svg bereits aria-hidden); Rolle `generic` verwarf sie bisher stillschweigend |
| 22 | calendar-settings-panel.tsx (Verbindung-OK-Symbol) | useAriaPropsSupportedByRole | `role="img"` + vorhandener Schluessel `connectionSuccess` | s. #21, Text passte woertlich, kein neuer Schluessel noetig |
| 23 | calendar-settings-panel.tsx (Verbindung-fehlgeschlagen-Symbol) | useAriaPropsSupportedByRole | `role="img"` + Katalogschluessel | s. #21 |
| 24 | calculator-widget.tsx (Speicherzeile) | useAriaPropsSupportedByRole | `role="toolbar"` + Katalogschluessel | `role="group"`/`"region"` loesen `useSemanticElements` neu aus (gemessen); `role="toolbar"` nicht |
| 25 | favorites-widget.tsx (Ansichtsumschalter) | useAriaPropsSupportedByRole | `role="toolbar"` + KORRIGIERTE Beschriftung | alte Beschriftung ("Favoriten") war sachlich falsch fuer einen Listen/Kachel-Umschalter — D-03 erlaubt hier das Streichen, eine richtige Beschriftung ist trotzdem besser als keine |
| 26 | header.tsx (Avatar `<img onError>`) | noNoninteractiveElementInteractions | `aria-hidden="true"` | `onError` ist ein Ladefehler, keine Bedienung; `alt=""` bereits vorhanden — richtige Auszeichnung, aber kein Gewinn fuer einen Menschen |
| 27 | account-settings-form.tsx (Avatar `<img onError>`) | noNoninteractiveElementInteractions | `aria-hidden="true"` | s. #26 |
| 28 | favorites-widget.tsx (Symbol-Proxy `<img onError>`) | noNoninteractiveElementInteractions | `aria-hidden="true"` | s. #26 |
| 29 | favorites-widget.tsx (Symbol-Direkt `<img onError>`) | noNoninteractiveElementInteractions | `aria-hidden="true"` | s. #26 |
| 30 | calculator-widget.tsx (Rahmen, `role="application"` + `onKeyDown`) | noNoninteractiveElementInteractions | **bleibt stehen, bleibt gezaehlt** | Der Handler FUEGT einen Tastaturweg hinzu (Ziffern erreichen die Logik, sobald irgendeine Taste fokussiert ist) — das Gegenteil des Schadens, den die Regel beschreibt. Alle drei Rollen-Alternativen gemessen, aendern nichts. `addEventListener` in einem Effekt ausdruecklich NICHT gewaehlt — das waere eine geschoente Zahl ohne Gegenwert (D-07). |
## Die vier Restposten
1. **ZIP-Name im Zertifikat-Aufteiler.** `downloadAllAsZip` liegt ausserhalb der Komponente und konnte den Uebersetzungs-Hook nicht aufrufen — der Name kommt jetzt als Parameter herein (`t('actions.zipFilename')`). Der 260921-bi2-Einwand ("ein uebersetzter Name koenne Umlaute auf eine Windows-Freigabe tragen") **trifft fuer den aktuellen deutschen Katalogwert nicht zu** (kein Umlaut). Die Sicherheit haengt darauf aber NICHT: `zip-filename.ts` schneidet jeden Namen unabhaengig vom Katalogwert auf das fuer Windows Zulaessige zurueck (verbotene Zeichen, Steuerzeichen, Nicht-ASCII, abschliessende Punkte/Leerzeichen, reservierte Geraetenamen, Ersatzname bei leerem Ergebnis, Endung sichergestellt) — elf Testfaelle belegen das einzeln.
2. **`noUselessSwitchCase` in `tender-normalizer.service.ts`.** Die Fallmarke `case 'doe-opendata':` unmittelbar ueber `default:` war ueberfluessig — entfernt, der erweiterte Kommentar traegt jetzt beide Aussagen (DÖE-Quelle landet hier UND kuenftige additive `SourceType`-Mitglieder sollen ebenfalls hier landen). Kein Verhaltenswechsel, belegt durch die 18 vorhandenen Tests.
3. **Doppeltes Kalender-Ladefenster.** Trat nur bei der Vorschau-Einstellung 90 Tage auf (nicht bei der Voreinstellung 30). Der eingefrorene Testzeitpunkt (15.07.2026) zeigt die Kollision zwischen **August und September** — eine Abweichung vom im PLAN genannten Monatspaar Oktober/November (derselbe Mechanismus, ein anderes "heute"; gleiche Art Abweichung wie bereits in Test 8 fuer den 30-Tage-Fall dokumentiert). `lastFetchWindowRef` ueberspringt den Termin-Abruf bei uebereinstimmendem Fenster; der 5-Minuten-Auffrischer umgeht den Sperrgriff immer.
4. **Die vier `t`-Abhaengigkeiten.** Die gof-Annahme "`t` ist bei jedem Render frisch" ist **ausdruecklich WIDERLEGT**: `translations-identity.test.tsx` beweist per Referenzgleichheit (nicht nur `toEqual`) an einem echten `NextIntlClientProvider`, dass `t` bei einem lokalen Zustandswechsel dasselbe Funktionsobjekt bleibt. Bestaetigt durch den `use-intl@4.13.0`-Quelltext: `translate` entsteht in einem `useMemo`, dessen Abhaengigkeiten ausschliesslich aus dem root-staendigen Intl-Kontext stammen. Die vier verbliebenen Stellen (`marketplace/page.tsx`, `admin/users/page.tsx`, `calendar-settings-panel.tsx`, `calendar-source-form.tsx`) bleiben deshalb **unveraendert** — zwei davon haengen ohnehin an keinem Effekt (Ereignisbehandlung), die beiden anderen wurden in 260921-gof bereits korrekt behandelt. Die Faustregel "`t` gehoert in keine Abhaengigkeitsliste" bleibt als Konvention in Ordnung, die zugrunde liegende Begruendung ist jetzt korrigiert.
## Ehrliche Einordnung: die vier `aria-hidden`-Ergaenzungen
Alle vier `<img onError>`-Stellen (header.tsx, account-settings-form.tsx, favorites-widget.tsx zweimal) tragen bereits `alt=""` — sind also schon aus dem Zugaenglichkeitsbaum genommen. `aria-hidden="true"` sagt dasselbe nur ausdruecklich. **Es ist richtige Auszeichnung, aber es verbessert fuer keinen Menschen etwas** — der Befund verschwindet, weil die Regel ein verborgenes Element nicht mehr betrachtet. `onError` ist ein Ladefehler, keine Bedienung: hier gab es nie einen Tastaturweg zu schaffen. Keine Unterdrueckung, kein `biome-ignore`.
## Vorher/Nachher (Biome-JSON, nach Regel)
| Zeitpunkt | gesamt | a11y (5 zurueckgestellte Regeln) | errors |
|---|---|---|---|
| Vor diesem Vorgang (Baseline, vom Planer gemessen) | 429 | 30 | 0 |
| Nach Aufgabe 1 (Struktur-Umbauten) | 413 | 14 (5 = 4 `<img onError>` + Taschenrechner-Rahmen der Klick-Regeln; `useAriaPropsSupportedByRole` noch 5, `noAutofocus` noch 4) | 0 |
| Nach Aufgabe 2 (Rollen/Beschriftungen/Autofokus/Restposten 1+2) | **399** | **1** | 0 |
| Nach Aufgabe 3 (Kalender-Abrufe, t-Identitaet — keine a11y-Aenderung) | **399** | **1** | 0 |
Endstand deckt sich exakt mit dem Zielwert des Plans: `gesamt 399`, `a11y 1` (`calculator-widget.tsx`, `noNoninteractiveElementInteractions`, bewusst stehengelassen), `switch 0`, `errors 0`. Keine neue `biome-ignore`-Zeile im gesamten Diff (`git diff --unified=0 7557c9a.. | grep -c biome-ignore` → 0). Beide Kataloge nach wie vor gleich lang (912/912), keine Datei nur in einem Katalog.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug, waehrend der Arbeit selbst erkannt und vor dem Commit korrigiert] Regex mit Steuerzeichen-Bereich loeste eine neue Lint-Regel aus**
- **Found during:** Aufgabe 2, Umsetzung der ZIP-Namens-Schutzfunktion
- **Issue:** Die urspruengliche Fassung von `zip-filename.ts` nutzte `/[<>:"/\\|?*\x00-\x1f]/g` — ein `\x00-\x1F`-Bereich in einem Regex-Literal, den Biome (`lint/suspicious/noControlCharactersInRegex`) beanstandet, auch wenn die Absicht (Steuerzeichen ausfiltern) hier keine ist. Der Test-Datei-Umbau selbst zog dabei ausserdem `lint/style/useTemplate` (info) und `suppressions/unused` (ein ueberfluessiger `biome-ignore`-Kommentar) nach sich.
- **Fix:** Steuerzeichen-/Sonderzeichen-Filterung auf zeichenweise Iteration umgestellt (kein Regex fuer diesen Teil); String-Verkettung im Test durch ein Template-Literal ersetzt; den ueberfluessigen `biome-ignore` entfernt.
- **Files modified:** `apps/web/src/app/(portal)/modules/cert-manager/zip-filename.ts`, `apps/web/src/app/(portal)/modules/cert-manager/zip-filename.test.ts`
- **Verification:** Volle Regelmessung danach zeigt `gesamt 399` (Zielwert exakt getroffen), 0 Fehler, 0 unbenutzte Unterdrueckungen.
- **Committed in:** `6c10c9b` (im selben Commit korrigiert, nie mit dem Fund committet)
**2. [Rule 1 - Bug, vor dem Commit korrigiert] Write-Werkzeug wandelte `\u0007` still in ein rohes Steuerzeichen um**
- **Found during:** Aufgabe 2, Anlegen von `zip-filename.test.ts`
- **Issue:** Der bekannte Fallstrick aus dem Plan (`\uXXXX`-Folgen werden vom Write-Werkzeug in das tatsaechliche Zeichen umgewandelt) trat ein weiteres Mal ein — ein rohes BEL-Steuerzeichen (0x07) landete zwischen zwei Buchstaben im Testtext, sichtbar erst per `cat -A`.
- **Fix:** Die betroffene Zeile per `python3`-Skript (Bytesuche/-ersatz, `chr(7)`) auf `String.fromCharCode(7)` innerhalb eines Template-Literals umgestellt — erzeugt das Zeichen zur Laufzeit, keine `\u`-Folge mehr im Dateiinhalt. Alle Dateien dieses Vorgangs danach auf verbliebene rohe Steuerbytes durchsucht (0 Treffer).
- **Files modified:** `apps/web/src/app/(portal)/modules/cert-manager/zip-filename.test.ts`
- **Verification:** `cat -A` zeigt keine Steuerbyte-Artefakte mehr; `git commit` nahm die Datei ohne Beanstandung an.
- **Committed in:** `6c10c9b` (vor dem Commit korrigiert)
**3. [Rule 3 - Blocking, vor dem Commit korrigiert] userEvent-Tastaturtest haengt mit Fake-Timern**
- **Found during:** Aufgabe 1, Testerweiterung fuer das Notiz-Kaestchen
- **Issue:** Ein Test mit `userEvent.keyboard(' ')` haengte (5s Timeout) unter den in `beforeEach` global gesetzten Fake-Timern (`vi.useFakeTimers()`), auch mit `advanceTimers`-Option.
- **Fix:** Fuer diesen einen Test `vi.useRealTimers()` gesetzt (das Abhaken speichert sofort ohne Entprellen, echte Zeitgeber sind dafuer unproblematisch); `afterEach` stellt ohnehin `vi.useRealTimers()` global sicher.
- **Files modified:** `apps/web/src/components/dashboard/widgets/note-widget.test.tsx`
- **Verification:** Test gruen, restliche 10 Tests der Datei unbeeinflusst.
- **Committed in:** `b601141`
---
**Total deviations:** 3 auto-fixed (2 Bugs waehrend eigener Arbeit erkannt und vor dem jeweiligen Commit korrigiert, 1 blockierendes Test-Infrastruktur-Problem). Keine der drei hat den Endstand oder den Diff-Umfang ueber das im Plan Vorgesehene hinaus vergroessert — alle drei sind Korrekturen an waehrend dieses Vorgangs selbst neu geschriebenem Code, kein Scope-Creep in bestehende Dateien.
## Issues Encountered
Keine ungeloesten Probleme. Die drei oben dokumentierten Abweichungen sind vor dem jeweiligen Commit vollstaendig geloest.
## Known Stubs
Keine. Alle Aenderungen sind vollstaendige, funktionierende Korrekturen; keine Platzhalter, keine leeren Datenquellen.
## Threat Flags
Keine neue Angriffsflaeche. Der Umbau des Loeschdialog-Hintergrunds (`calendar-settings-panel.tsx`) und des Dialog-Hintergrunds (`widget-catalog-modal.tsx`) macht ein zuvor rein optisches Element zu einem fokussierbaren `<button>` — das ist die im `threat_model` als `accept` eingestufte Informationsfreigabe T-JT4-06 (Dialoginhalt war ohnehin sichtbar), keine neue Flag.
## User Setup Required
None — keine externe Konfiguration erforderlich.
## Was an den Orchestrator geht (kein Browser hier verfuegbar)
Der Ausfuehrende hat keinen Browser und darf die Browser-Messung aus Aufgabe 3 nicht behaupten. An den Orchestrator zu uebergeben:
- **Kalender-Widget, Netzwerkprotokoll bei 90 Tagen Vorschau:** Stack bauen, anmelden, Kalender-Kachel mit Vorschau-Einstellung 90 Tage aufs Dashboard legen, Netzwerkprotokoll leeren, dreimal "Weiter" druecken.
- Erwartung: `calendar/sources` genau 1 (vorher 3); `calendar/events` weniger als 3, kein Paar mit identischem `from`/`to`.
- Gegenprobe: rund fuenf Minuten ruhen lassen, danach je ein weiterer `calendar/events`- und `calendar/sources`-Abruf.
- Datenbank/Modul-Aktivierung danach unveraendert lassen, Stack stoppen.
Alle anderen Verifikationsschritte des Plans (Biome-Zaehlung, Katalog-Gleichstand, `apps/web`/`apps/api`-Testlaeufe, `pnpm type-check`, `pnpm lint --force`, `git diff`-Kontrollen) wurden hier bereits ausgefuehrt und sind oben mit Ergebnissen belegt.
## Next Phase Readiness
Der a11y-Rueckstand aus 260921-bi2 (30 zurueckgestellte Befunde ueber fuenf Regeln) ist vollstaendig abgearbeitet: 29 behoben, 1 bewusst und begruendet stehengelassen. Alle vier vom Nutzer/Orchestrator benannten Restposten aus den heutigen Vorgaengen bi2 und gof sind geschlossen. Kein Blocker fuer laufenden Betrieb: `pnpm lint --force` bleibt gruen (5/5), CI-Tor unveraendert scharf. Offen bleibt ausschliesslich die Browser-Gegenprobe fuer die Kalender-Netzwerkzaehlung (siehe Abschnitt oben) — reine Bestaetigung, kein bekannter Defekt.
## Self-Check: PASSED
Alle referenzierten Dateien auf Datentraeger gefunden (`zip-filename.ts`, `zip-filename.test.ts`, `translations-identity.test.tsx`, `widget-catalog-modal.test.tsx`, `calendar-settings-panel.test.tsx`, diese SUMMARY). Alle referenzierten Commit-Hashes (`a8531d4`, `3d0bc0b`, `0c89c13`, `b601141`, `e651c24`, `9aa87bd`, `b406a9c`, `f7b5df4`, `69fe706`, `5a03b75`, `6c10c9b`, `f471b78`) im Verlauf gefunden (`git log --oneline 7557c9a..HEAD`).
---
*Vorgang: quick-260921-jt4*
*Abgeschlossen: 2026-09-21*