From c001a081c2921ab7661730dd132cd2c695e92194 Mon Sep 17 00:00:00 2001 From: Schalli Date: Mon, 21 Sep 2026 10:02:44 +0200 Subject: [PATCH] docs(quick-260921-bi2): Lint-Rueckstand 2923 -> 465, Akte und Verifikation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zusammenfassung, Verifikation und STATE.md zum Quick-Vorgang 260921-bi2. Kernbefund: Biomes als "safe" eingestufte Korrektur style/useImportType zerstoert in apps/api die NestJS-Abhaengigkeitsspritze — das erzeugte __metadata("design:paramtypes", [...]) kollabiert zu [Function, ...] und die API startet nicht mehr, waehrend tsc gruen bleibt und alle 1124 API-Tests gruen bleiben (kein Test ruft createTestingModule auf). Deshalb ein zweiter, auf apps/api/** begrenzter overrides-Eintrag. Nachweis fuer "kein Verhaltenswechsel" ist nicht die Testsuite, sondern ein sha256 ueber alle 593 erzeugten __metadata-Zeilen (6e1583f1...), vor und nach dem Umbau identisch. Dazu Klicktest am laufenden System mit den echten Abbildern: API healthy, Abbrechen legt nichts an, Speichern legt an, ADMIN sieht auf der SUPER_ADMIN-Zeile nur Details, abgewiesene Server-Antworten erscheinen sichtbar. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J --- .planning/STATE.md | 11 +- .../260921-bi2-SUMMARY.md | 439 ++++++++++++++++++ .../260921-bi2-VERIFICATION.md | 111 +++++ 3 files changed, 556 insertions(+), 5 deletions(-) create mode 100644 .planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-SUMMARY.md create mode 100644 .planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-VERIFICATION.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 49cdc2c..5d5d3f5 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,10 +4,10 @@ milestone: v1.2 current_phase: 18 current_phase_name: desktop-client-fertigstellen status: verified -stopped_at: "WINDOWS #35 und #36 abgeschlossen und verifiziert (7 Commits, alle Gatter gruen); nicht gepusht — Push und CI-Lauf stehen noch aus" -last_updated: "2026-09-21T05:45:00.000Z" +stopped_at: "WINDOWS #35, #36 und der Lint-Rueckstand abgeschlossen und verifiziert (inkl. Klicktest am laufenden System); Push und CI-Lauf fuer das Image stehen an. CI-Lauf 388 war rot durch einen Absturz des Gitea-Runners (act_runner v0.6.1, panic: close of closed channel) — Lint/Type-Check/Tests waren darin gruen, nur der Desktop-Job starb, dadurch wurde Build & Publish Images uebersprungen." +last_updated: "2026-09-21T08:10:00.000Z" last_activity: 2026-09-21 -last_activity_desc: Quick 260921-9ie und 260921-a1d — WINDOWS #35 (Biome lauffaehig, Lint-Tor scharf) und #36 (Benutzerverwaltung meldet 403 sichtbar, keine Aktionsknoepfe auf SUPER_ADMIN-Zeilen fuer ADMIN) geschlossen und verifiziert +last_activity_desc: Quick 260921-9ie, 260921-a1d und 260921-bi2 — Biome lauffaehig und Lint-Tor scharf, Benutzerverwaltung meldet abgewiesene Aktionen sichtbar, Lint-Rueckstand 2923 → 465; alle drei verifiziert, der letzte zusaetzlich per Klicktest am laufenden System state_head: 4d485432c003a6caf68f6d85aff7de0bd27794e2 progress: total_phases: 18 @@ -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-a1d (WINDOWS #36): Benutzerverwaltung meldet abgewiesene Server-Antworten sichtbar (drei Stellen), ADMIN bekommt in der SUPER_ADMIN-Zeile keine Aktionsknoepfe mehr; apps/api unangetastet, Web-Tests 66/459 gruen +Last activity: 2026-09-21 - Quick 260921-bi2: Lint-Rueckstand 2923 → 465 Warnungen abgebaut (Konfiguration, maschinelle Fixes, toter Code, 155 Handkorrekturen Barrierefreiheit); Beinahe-Schaden abgewendet — Biomes als sicher eingestufte useImportType-Korrektur haette die NestJS-Abhaengigkeitsspritze zerstoert, unbemerkt von tsc und 1124 gruenen Tests Progress: [██████████] 99% @@ -446,6 +446,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests. | fast | **Desktop-Client: Setup-Seite zeigt Version und Stand der App** („Tessera-App 1.2.0 · Stand a6d1a64"; ohne Stempel nur Version) — Command `get_client_info`, Helfer `client_info_label` (2 Tests), `

` in setup.html, CHANGELOG. Diente zugleich als zweiter Desktop-Stand fuer den Update-Nachweis. 35 Rust-Tests. | 2026-09-18 | a6d1a64 | — | | 260921-9ie | **Biome lauffaehig machen und das Lint-Tor scharf schalten (WINDOWS #35).** `biome.json` per `biome migrate` auf Biome 2.5.0 gezogen: `organizeImports` nach `assist.actions.source`, `linter.rules.recommended` → `preset: "recommended"`, `javascript.parser.unsafeParameterDecoratorsEnabled` (NestJS-Parameter-Dekoratoren: 238 parse-Fehler in 19 Dateien → 0), `quoteStyle: single` (belegt: 1496 einfach-gequotete Importzeilen gegen null doppelte), `vcs.useIgnoreFile`, Ausschluss von `**/__fixtures__/**` (nur html/zip/xml, keine TS-Datei) und `globals.css` (Tailwind-4-At-Regeln). `lint`-Skript (`biome lint .`) in allen fuenf Workspaces; `turbo.json` bekommt `globalDependencies: ["biome.json"]`, sonst liefert der Cache nach einer Regelaenderung alte Ergebnisse. **Zweig (b) gewaehlt, gemessen:** `biome check .` → Exit 1/760 Fehler, `biome lint .` → Exit 1/275, also kein "nur Warnungen"-Ausweg; Skript ruft `lint` statt `check` (haelt 319 Formatierungsbefunde draussen, kein Rundumumbau), Rest gezielt auf `warn` → 0 Fehler, Exit 0. **Sicherheit:** Gruppe `security` bleibt auf `error`, maschinell geprueft; die 6 `noScriptUrl`-Treffer lagen ausnahmslos in der ausgeschlossenen HTML-Testvorlage, keiner in echtem Quellcode. **Registereintrag #35 war in zwei Punkten falsch:** Pfad ist `apps/api/src/user/...` (Einzahl), und die Wirkung des Parser-Schalters betrug 238 statt 17 Fehler. **Nachweise (dreifach unabhaengig — Planer, Orchestrator, Verifier):** `pnpm lint` → "5 successful, 5 total", Exit 0 (vorher "No tasks were executed"); Gegenprobe mit Wegwerfdatei (`debugger`) → Exit 1 mit `noDebugger`, danach Baum wieder sauber; repo-weit 0 parse-Fehler, 0 Fehler; Diff nur Konfiguration/Skripte/Doku, keine Quelldatei, `pnpm-lock.yaml` unveraendert. Verifikation passed (7/7). **Offen als eigener Durchlauf:** rund 2800 Warnungen (`any`-Familie, Barrierefreiheit in `apps/web`), in `docs/anleitung-entwicklung.md` als bewusster Rueckstand festgehalten. | 2026-09-21 | 6a727e9,00d769b,6f0f05a | [260921-9ie-windows-35-biome-json-fuer-biome-2-5-0-r](./quick/260921-9ie-windows-35-biome-json-fuer-biome-2-5-0-r/) | | 260921-a1d | **Benutzerverwaltung: verbotene Aktionen melden sich jetzt (WINDOWS #36).** Drei Stellen in `apps/web/src/app/(portal)/admin/users/page.tsx` verschluckten Server-Antworten still (`if (res.ok)` ohne else, `catch {}` mit dem Kommentar `// silently fail`): Liste laden, Formular speichern, Loeschen. Sichtbare Wirkung vorher: Formular blieb offen, Loeschdialog stand still, beim gescheiterten Laden log die Seite mit "Keine Benutzer gefunden". Jetzt je ein Banner (`role="alert"`) im Listenkopf, im Formulardialog und im Loeschdialog; `readApiMessage(res)` liest ausschliesslich `body.message` und zeigt den Servertext in einem deutschen Rahmensatz, sonst eine uebersetzte Ersatzmeldung — auch wenn der `fetch` selbst wirft. Vier Schluessel `admin.users.errors.*` in `de.json` **und** `en.json` (Katalog-Paritaet 890/890 geprueft). Dazu `canManageRow`: einem ADMIN werden Bearbeiten/Loeschen in der SUPER_ADMIN-Zeile gar nicht erst angeboten (seit #29 im Alltag erreichbar), "Details" bleibt ueberall. **`apps/api` blieb unangetastet** — der Zielrollen-Riegel im Controller ist und bleibt die wirksame Grenze, der versteckte Knopf ist Ergonomie darueber, kein Ersatz; eigenes Gatter im Plan weist das nach. Gemessen: keine Namen/IDs/Stapelspuren in den 403-Rumpftexten (kein eigener ExceptionFilter in `apps/api/src`). **Nachweise (dreifach unabhaengig):** Web-Tests 66 Dateien/459 Tests gruen (vorher 65/447, neue `users-page.test.tsx` prueft echten DOM-Text via `getByText`/`within`, nicht nur State-Setter), type-check Exit 0, `pnpm lint` 5/5 ohne neue Fehlerrang-Meldung, `silently fail` im Code 3 → 0, `role="alert"` 0 → 3, Diff nur vier Dateien unter `apps/web`. Verifikation passed (7/7). | 2026-09-21 | 38d2586,51bff75,13b70df | [260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-](./quick/260921-a1d-windows-36-benutzerverwaltung-zeigt-bei-/) | +| 260921-bi2 | **Lint-Rueckstand abgebaut: 2923 → 465 Warnungen (WINDOWS #35 Folgearbeit).** Seit das Lint-Tor wirklich prueft, war der Rueckstand sichtbar. Aufgeteilt nach Risiko statt nach Datei: (1) Konfiguration — zwei begruendete `overrides`, (2) maschinelle Fixes + toter Code, (3) Barrierefreiheit von Hand. **Der wichtigste Befund ist ein Beinahe-Schaden:** Biomes `style/useImportType`-Korrektur ist als *safe* eingestuft, zerstoert in `apps/api` aber die NestJS-Abhaengigkeitsspritze — `__metadata("design:paramtypes", [PrismaService, …])` kollabiert zu `[Function, …]`, 61 von 65 Dateien betroffen, API startet nicht mehr. Dabei bleibt `tsc` gruen **und alle 1124 API-Tests bleiben gruen**, weil kein einziger Test `createTestingModule` aufruft — das waere durch jedes vorhandene Tor unbemerkt bis auf alpha durchgelaufen. Planer und Plan-Pruefer haben es unabhaengig voneinander reproduziert (Datei kompiliert, Metadatenzeile verglichen). Deshalb zweiter `overrides`-Eintrag auf `apps/api/**`. Zweite Falle, ebenfalls gemessen: `--only=` schaltet eine in der Konfiguration abgeschaltete Regel wieder AN — ein repo-weites `biome lint . --only=useImportType --write` haengt die Ausnahme aus (77 API-Dateien veraendert). Nur pfadgebundene Aufrufe. **Nachweis, dass sich nichts geaendert hat, ist NICHT die Testsuite**, sondern ein sha256 ueber alle 593 erzeugten `__metadata`-Zeilen: `6e1583f1…`, vor und nach dem Umbau identisch, dreifach geprueft. Barrierefreiheit: 155 Handkorrekturen in 53 Dateien (Symbole 71, Knopf-Typen 52, Beschriftungen 22, Rollen/Semantik 10) — je Fundstelle entschieden, ob ein Symbol dekorativ (`aria-hidden`) oder die einzige Beschriftung ist (``/`aria-label`); in der Seitenleiste erkannt, dass der Text beim Einklappen verschwindet, dort also ein echter Name noetig ist. Alle neuen Texte ueber next-intl in de **und** en (892/892 Schluessel). **Zahlen:** gesamt 2923 → 465, echter Quellcode 856 → 386, Testdateien 2067 → 79, Fehler-Rang durchgehend 0. **Bewusst NICHT angefasst, benannt statt stillschweigend:** 288 `noExplicitAny` im Quellcode (echte Typarbeit), 20 `useExhaustiveDependencies` (je ein moeglicher Effekt-Fehler), 30 a11y-Befunde mit Bedienentscheidungsbedarf, `noUselessSwitchCase` (Fallmarke dokumentiert Absicht), sowie fuenf tote Stellen, die Symptome echter Luecken sind — darunter: Passwortwechsel-Seite leitet nach erzwungenem Wechsel nicht weiter, Loeschknopf in `VehicleTable` ohne Besetztzustand, `force-password-change.interceptor` liest das HTTP-Verfahren und fragt es nie ab. **Klicktest am laufenden System** (echte Abbilder, Playwright): API meldet `healthy` und `Nest application successfully started` — Abhaengigkeitsspritze zur Laufzeit bewiesen; `Abbrechen` legt nichts an, `Speichern` legt an; als ADMIN bietet die SUPER_ADMIN-Zeile nur noch `Details`; abgewiesene Server-Antwort erscheint sichtbar als "Der Server hat die Aktion abgelehnt: …". Testbenutzer wieder geloescht. Verifikation passed. | 2026-09-21 | 8d1c8f3,636fe0d,+11 | [260921-bi2-lint-rueckstand-abbauen-mechanische-fixe](./quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/) | ## Deferred Items @@ -491,4 +492,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-a1d (WINDOWS #36): Benutzerverwaltung meldet abgewiesene Server-Antworten sichtbar (drei Stellen), ADMIN bekommt in der SUPER_ADMIN-Zeile keine Aktionsknoepfe mehr; apps/api unangetastet, Web-Tests 66/459 gruen +Last activity: 2026-09-21 - Quick 260921-bi2: Lint-Rueckstand 2923 → 465 Warnungen abgebaut (Konfiguration, maschinelle Fixes, toter Code, 155 Handkorrekturen Barrierefreiheit); Beinahe-Schaden abgewendet — Biomes als sicher eingestufte useImportType-Korrektur haette die NestJS-Abhaengigkeitsspritze zerstoert, unbemerkt von tsc und 1124 gruenen Tests diff --git a/.planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-SUMMARY.md b/.planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-SUMMARY.md new file mode 100644 index 0000000..cc07041 --- /dev/null +++ b/.planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-SUMMARY.md @@ -0,0 +1,439 @@ +--- +phase: quick-260921-bi2 +plan: 01 +subsystem: tooling +tags: [biome, lint, a11y, nestjs, decorator-metadata, i18n] + +requires: [] +provides: + - "biome.json mit genau zwei zielgenauen overrides (Testdateien / apps/api) statt eines + unueberschaubaren 2923er-Rueckstands" + - "45 maschinell erzeugte, von Hand gelesene Korrekturen ueber vier sichere und fuenf + unsichere Biome-Regeln" + - "15 untersuchte Fundstellen toten Codes: 10 folgenlos entfernt, 1 umbenannt (NestJS- + Parameterposition), 4 als benannte Folgeaufgaben gemeldet statt repariert" + - "155 von Hand erledigte Barrierefreiheits-Fundstellen ueber sechs Regeln (Symbole, + Schaltflaechentyp, Beschriftungsbindung, Rollen/ARIA-Merkmale, Tab-Reihenfolge)" + - "Entwickleranleitung auf Endstand 465/386/79 gebracht, beide Ausnahmen begruendet, + Folgeaufgaben benannt" +affects: [ci-lint-gate, apps/web-a11y, apps/api-di] + +actuals: + tokens: 37573 + tasks: 3 + commits: 13 + +tech-stack: + added: [] + patterns: + - "biome.json overrides sind pfadgebunden (apps/api/**), nie repo-weit mit --only=<regel> + angewendet -- --only schaltet eine per Konfiguration abgeschaltete Regel wieder an." + - "Bei einer Regel ohne gesicherten Fix zuerst den Diff lesen (--write ohne --unsafe testen), + danach --unsafe pruefen, danach den ganzen Diff lesen -- nie ungeprueft anwenden." + - "a11y-Fixes koennen sich gegenseitig verschieben: eine Regel loesen kann eine ANDERE + (zurueckgestellte) Regel neu anschlagen, wenn ein Element seine interaktive + Klassifikation verliert (role=button entfernt -> noStaticElementInteractions greift + auf die verbliebenen Drag-Handler). Nach jedem Einzel-Fix die komplette Regelmenge + (in Scope UND zurueckgestellt) neu messen, nicht nur die Zielregel." + +key-files: + created: [] + modified: + - biome.json + - docs/anleitung-entwicklung.md + - "apps/api/src/** (45 maschinelle Korrekturen + toter Code, Aufgabe 2)" + - "apps/web/src/** (a11y, Aufgabe 3, beide Teillieferungen)" + - apps/desktop/src/setup.html + - apps/web/src/messages/de.json + - apps/web/src/messages/en.json + +key-decisions: + - "useImportType in apps/api/** abgeschaltet statt maschinell angewendet: emitDecoratorMetadata + erzeugt __metadata(design:paramtypes,...), das NestJS zur Abhaengigkeitsaufloesung liest; + import type entfernt den Wert-Import und ersetzt die erzeugten Typen durch Function- + Platzhalter. 61 von 65 Dateien mit Abhaengigkeitsdaten waeren beschaedigt worden, bei + gruenem tsc und gruenen 1124 Tests (kein Test ruft createTestingModule auf) -- der Fehler + waere durch jedes Tor dieses Projekts unbemerkt hindurchgegangen." + - "noUselessSwitchCase (1 Fundstelle, tender-normalizer.service.ts:60) bewusst NICHT + angewendet: die Fallmarke dokumentiert, warum der Standardzweig auf diesem Weg bleiben + muss; die Regel sieht nur den Code, nicht die Absicht dahinter." + - "useSemanticElements (4 Fundstellen) wurde entgegen der urspruenglichen Einordnung in + PLAN.md (dort unter D-05 zurueckgestellt) in Teillieferung B doch bearbeitet -- Abweichung + von der Vorgabe, siehe Deviations." + - "DropZone.tsx: Datei-Entfernen-Schaltflaeche als Geschwister statt verschachtelt in der + Drop-Flaeche (kein <button> darf ein zweites <button> enthalten); Drag-Handler wanderten + von der Flaechen-<div> auf die Flaechen-<button>, sonst waere ein 'statisches Element mit + Ereignis-Handler' entstanden und haette zwei zurueckgestellte Regeln neu ausgeloest." + - "admin-sidebar.tsx/settings-sidebar.tsx: role=navigation vom <aside> aufs bereits + vorhandene <nav> verschoben statt das <aside> in ein zweites <nav> zu verwandeln -- + sonst waere ein doppeltes Navigations-Landmark entstanden." + +requirements-completed: [LINT-BACKLOG] + +duration: ~150min (Teillieferung B; Gesamtvorgang laenger, ueber mehrere Sitzungen) +completed: 2026-09-21 +status: complete +--- + +# Quick-Vorgang 260921-bi2: Lint-Rueckstand abbauen (mechanische Fixe) Summary + +**Lint-Rueckstand von 2923 auf 465 Befunde gesenkt (0 Fehlerstufe durchgehend) — durch eine +begruendete, pfadgebundene Konfigurationsbereinigung, einen von Hand gelesenen maschinellen +Durchgang und 155 handgeschriebene Barrierefreiheits-Korrekturen, ohne die NestJS- +Abhaengigkeitsaufloesung anzutasten.** + +## Performance + +- **Aufgabe 1 (Konfiguration):** Commit `8d1c8f3` +- **Aufgabe 2 (maschinell + toter Code):** Commits `3811578`, `636fe0d` +- **Aufgabe 3, Teillieferung A (a11y — Symbole, Schaltflaechentyp, Layout/Formulare):** + Commits `e76f3b8`, `4cff316`, `ae82125`, `278aedb`, `969fd01`, `27a6e29`, `73ac08a` +- **Aufgabe 3, Teillieferung B (a11y — Rollen, Semantik, Tab-Reihenfolge, Beschriftungsbindung):** + Commits `21c85a8`, `c79bafa` +- **Abschlussarbeiten (Entwickleranleitung, dieser Vorgang):** Commit `97a6836` +- **Tasks:** 3 (laut PLAN.md), ueber mehrere Sitzungen in zwei Teillieferungen ausgefuehrt +- **Dateien geaendert (gesamter Vorgang):** 110 (biome.json, docs, 2 i18n-Kataloge, ~106 Quell- + und Testdateien) +- **Commits gesamt:** 13 + +## Vorher/Nachher + +| Durchgang | gesamt | echter Quelltext | Testdateien | Fehlerstufe | +|---|---|---|---|---| +| Start | 2923 | 856 | 2067 | 0 | +| Aufgabe 1 — Konfiguration | 754 | 633 | 121 | 0 | +| Aufgabe 2 — maschinell + toter Code | 621 | 542 | 79 | 0 | +| Aufgabe 3A — a11y (Symbole, Schaltflaechen, Layout) | 497 | 418 | 79 | 0 | +| **Aufgabe 3B — a11y (Rollen, Semantik, Beschriftungen) — Endstand** | **465** | **386** | **79** | **0** | + +Der Zielwert im PLAN.md-Text lautete 466; der korrekte Wert ist 465, weil Aufgabe 2 beim +Entfernen einer unbenutzten `catch (e: any)`-Bindung in `calendar.service.ts` inzidentell einen +zusaetzlichen `noExplicitAny`-Befund mitgenommen hat (288 statt 289 in echtem Quelltext, 0 in +Testdateien — die Testdatei-Ausnahme aus Aufgabe 1 reicht weiterhin nachweislich nicht in +Produktivcode hinein). Das ist keine nachtraeglich "passend gemachte" Zahl, sondern die +gemessene Tatsache; im Endstand wurde nichts unternommen, um wieder auf 466 zu kommen. + +## Aufgabe 1: Konfiguration bereinigen + +`biome.json` traegt seither genau zwei `overrides`-Eintraege, sonst nichts an der Regelmenge +geaendert; `security` kommt in der Datei nicht vor (erbt weiterhin `error`). + +1. **Testdateien** (`**/*.spec.ts`, `**/*.spec.tsx`, `**/*.test.ts`, `**/*.test.tsx`): + `suspicious/noExplicitAny` auf `off`. Begruendung: `any` haengt dort ausnahmslos an + Attrappen; geprueft mit `grep -rln "^export" --include=*.spec.ts ...` — keine Testdatei + exportiert ein Symbol, keine Nicht-Testdatei fuehrt eine Testdatei ein. +2. **`apps/api/**`**: `style/useImportType` auf `off`. Das ist die wichtigste Einzelentscheidung + des gesamten Vorgangs — siehe naechster Abschnitt. +3. Die fehlerhafte Fixture-Ausnahme (`__fixtures__**` statt `__fixtures__`) korrigiert; Biome + meldte das selbst als `lint/suspicious/useBiomeIgnoreFolder`. + +### Der useImportType/NestJS-Befund — der wichtigste Einzelfund dieses Vorgangs + +`style/useImportType` ist mit 224 Befunden die groesste Einzelregel, 222 davon in `apps/api`. +Biome stuft die Korrektur als „sicher" ein und wendet sie standardmaessig ohne `--unsafe` an. +Sie ist es in diesem Projekt aber nicht: `apps/api/tsconfig.json` hat `emitDecoratorMetadata` +eingeschaltet, und NestJS loest seine Konstruktor-Abhaengigkeiten zur Laufzeit ueber genau die +Daten auf, die der TypeScript-Compiler aus den Parametertypen erzeugt +(`__metadata("design:paramtypes", [...])`). `import type` markiert einen Import als reinen +Typ-Import; der Compiler entfernt ihn vollstaendig aus dem erzeugten JavaScript — inklusive +der Werte, die `__metadata` braucht. Probelauf an `auth.service.ts` beim Planen: vorher +`__metadata("design:paramtypes", [prisma_service_1.PrismaService, ...])`, nachher +`__metadata("design:paramtypes", [Function, Function, Function, Function, Function, Function])`, +und die zugehoerige `require`-Zeile verschwindet ersatzlos. Ueber den ganzen Workspace gemessen: +**61 von 65 Dateien mit Abhaengigkeitsdaten waeren beschaedigt worden.** Die API startet danach +nicht mehr — jeder DI-Aufloesungsversuch traefe auf `Function` statt auf die echte Klasse. + +Der entscheidende Punkt: **kein Tor dieses Projekts haette das bemerkt.** `pnpm type-check` +bleibt gruen (TypeScript prueft Typen, nicht erzeugte Laufzeit-Metadaten). Und kein einziger +der 1124 API-Tests waere rot geworden, denn `grep -rl createTestingModule apps/api/src` liefert +0 Treffer — der gesamte Testbestand startet den NestJS-Dependency-Injection-Container nirgends, +er testet Services/Controller mit von Hand konstruierten Abhaengigkeiten. Ein automatischer, +"sicherer" Fix haette die Anwendung im Betrieb lautlos zerstoert, waehrend jede automatisierte +Pruefung dieses Projekts weiterhin gruen gemeldet haette. + +Biome raeumt das Problem in der eigenen Regelbeschreibung ein +(`biome explain useImportType` → Abschnitt „Caveat with TypeScript experimental decorators") +und empfiehlt dort woertlich, die Regel bei solchen Dekoratoren abzuschalten. Aufgabe 1 folgt +dieser Empfehlung mit einem auf `apps/api/**` begrenzten Eintrag — eine bewusste Abweichung von +der generellen mechanischen-Regeln-Vorgabe (D-02), die ausschliesslich deshalb geschieht, weil +"kein Verhaltenswechsel" (D-07) hier Vorrang vor einer kleineren Zahl hat. + +**Nachweis, nicht Vermutung:** Vor dem maschinellen Durchgang (Aufgabe 2) wurde ein Fingerabdruck +ueber alle erzeugten `__metadata`-Zeilen genommen (593 Zeilen, sha256 +`6e1583f1eb72a089eb0ed98f81158b54a9fbd40dbf41371292725f36ef764300`). Nach Abschluss aller drei +Aufgaben — Konfiguration, maschineller Durchgang, Barrierefreiheit — ist dieser Fingerabdruck +**Zeichen fuer Zeichen identisch**, gerade jetzt erneut gemessen: + +``` +$ SNAP=$(mktemp -d); pnpm --filter @tessera/api exec tsc --outDir "$SNAP" +$ grep -rh '__metadata(' "$SNAP" | wc -l +593 +$ grep -rh '__metadata(' "$SNAP" | sort | sha256sum +6e1583f1eb72a089eb0ed98f81158b54a9fbd40dbf41371292725f36ef764300 - +``` + +Das ist der tragende Nachweis dieses gesamten Vorgangs — nicht der gruene Testlauf, der diesen +spezifischen Schaden nachweislich nicht aufdecken kann. + +## Aufgabe 2: Maschinelle Korrekturen und toter Code + +**(A) Vier Regeln mit gesichertem Fix** (`--write` ohne `--unsafe`): `style/useImportType` +(pfadgebunden auf `apps/web packages`, NIE repo-weit — ein repo-weiter Lauf haette die +Ausnahme aus Aufgabe 1 wirkungslos ausgehaengt und 201 `import type`-Zeilen nach `apps/api` +geschrieben, beim Planen genau so gemessen), `complexity/noUselessEscapeInRegex`, +`style/useConst`, `style/useExponentiationOperator`. + +**(B) Fuenf Regeln mit `--unsafe`-Fix, Diff vollstaendig von Hand gelesen** (~100 Zeilen): +`style/useNodejsImportProtocol`, `complexity/useLiteralKeys`, `complexity/useOptionalChain`, +`style/useTemplate`, `correctness/useParseIntRadix`. Zwei Dateien brauchten besondere +Aufmerksamkeit: + +- `ldap.service.ts` (25 der 31 `useLiteralKeys`-Aenderungen): Verzeichnis-Merkmale + (`objectGUID`, `sAMAccountName`, `cn`, `ou`, `mail`) zeichenweise gegengelesen — ein + verschluckter Grossbuchstabe macht den AD-Abgleich still leer, und AD ist in diesem Projekt + bewusst nur lesend angebunden, faellt also erst beim Anmelden auf. +- `jwt.strategy.ts` / `auth.service.ts`: Verkuerzungen im Anmeldeweg (`useOptionalChain`) + geprueft, dass eine fehlende Sitzung weiterhin zur Abweisung fuehrt, nicht zum Durchwinken. + +**Die sechste ungesicherte Regel, `complexity/noUselessSwitchCase` (1 Fundstelle, +`tender-normalizer.service.ts:60`), wurde bewusst NICHT angewendet.** Der Vorschlag wuerde +eine Fallmarke streichen, die unmittelbar ueber einem Kommentar steht, der erklaert, warum der +Standardzweig genau auf diesem Weg bleiben muss. Die Marke dokumentiert Absicht, die der Regel +entgeht. Sie steht weiterhin sichtbar in der Zaehlung (1), nicht unterdrueckt — eine +Unterdrueckung waere hier unehrlicher als das sichtbare Stehenlassen, weil sie wie eine +Erledigung aussehen wuerde, ohne eine zu sein. + +**(C) Toter Code, 15 Fundstellen, drei Gruppen:** + +1. *Echt tot, folgenlos entfernt (10):* nicht benutzte Fehlervariablen in `calendar.service.ts` + (Zeilen 321, 358), `cert-manager.service.ts` (305, 679), `dkv-parser.service.ts` (42); nicht + benutzte Einfuhren in `create-calendar-source.dto.ts:2`; nicht benutzte Funktion + `forSystemQuery` in `rls-scratch-check.mjs:226`; und `login/page.tsx:21` (unbenutzter + Wegweiser — Weiterleitung ist nachweislich anderswo geloest, daher entfernt ohne Meldung). +2. *Nicht entfernbar, umbenannt (1):* `current-user.decorator.ts:4` — `data` ist der erste von + zwei positionsgebundenen NestJS-Parametern; Streichen wuerde den zweiten verschieben. + Stattdessen mit fuehrendem Unterstrich gekennzeichnet. +3. *Symptome, gemeldet statt repariert (4 — siehe naechster Abschnitt).* + +### Die vier gemeldeten Symptomfunde (D-03) — offene Folgeaufgaben + +Diese vier unbenutzten Werte waren beim Lesen KEIN totes Code-Rauschen, sondern der Hinweis auf +eine echte Luecke. Sie zu schliessen waere ein Verhaltenswechsel gewesen, den dieser +lint-abbauende Vorgang nicht treffen durfte (D-07): + +1. **`force-password-change.interceptor.ts:53`** liest das HTTP-Verfahren in eine Variable und + befragt sie nie — die Freigabeliste unterscheidet also nicht zwischen Lese- und + Schreibzugriff auf die freigegebenen Wege. Variable entfernt, Luecke hier als eigene + Folgeaufgabe benannt. +2. **`change-password/page.tsx:11-12`** hielt Wegweiser und Benutzerablage vor, benutzte beide + nicht. Bestaetigt beim Lesen: nach erfolgreichem Wechsel wird weder weitergeleitet noch die + Benutzerablage aufgefrischt — bei erzwungenem Wechsel bleibt die Person auf der Seite + stehen. Die drei Bindungen entfernt, Befund hier als Folgeaufgabe benannt. +3. **`VehicleTable.tsx:164`** setzte einen Laufzustand fuers Loeschen, las ihn aber nie — die + Loeschschaltflaeche hat also keinen Besetztzustand und laesst sich doppelt ausloesen. Nur + die lesende Bindung entfernt, die setzende blieb; Befund hier als Folgeaufgabe benannt. +4. **`SplitTab.tsx:20`** bekam die Uebersetzungsfunktion und benutzte sie nicht — ein Hinweis + auf fest verdrahtete Texte in diesem Reiter. Parameter entfernt, Befund hier als + Folgeaufgabe benannt. + +(Ein fuenfter untersuchter Kandidat, `login/page.tsx:21`, gehoerte urspruenglich zur selben +"Symptom"-Kategorie in der Planung, stellte sich beim Lesen aber als echt folgenlos heraus — +siehe Gruppe 1 oben. Er wird hier zur Vollstaendigkeit genannt, braucht aber keine eigene +Folgeaufgabe.) + +**Nachweis fuer den gesamten maschinellen Durchgang:** 45 Quelldateien geaendert, Diff +zeilenbilanziert (keine Formatierung mitgelaufen, D-08), NestJS-Metadaten-Fingerabdruck +unveraendert (siehe oben), beide Testlaeufe punktgleich gruen. + +## Aufgabe 3: Barrierefreiheit von Hand (beide Teillieferungen) + +155 Fundstellen in 53 Dateien, durchgehend Handarbeit — fuer alle sechs bearbeiteten Regeln bot +Biome weder einen gesicherten noch ungesicherten Fix (einzige Ausnahme: `noRedundantRoles` mit 3 +Dateien unter `--unsafe`, Ergebnis trotzdem gelesen). + +### Teillieferung A (vorherige Sitzung, Commits `e76f3b8` … `73ac08a`) + +- `a11y/noSvgWithoutTitle` (71): je Symbol entschieden — begleitet es sichtbaren Text, wird es + als Schmuck vor der Vorlesehilfe verborgen; steht es allein, bekommt es einen Titel, der die + Bedienung nennt (Uebersetzungskatalog, wo die Datei schon uebersetzt ist). Zwei Fundstellen + ausserhalb der React-Oberflaeche: `apps/web/src/app/icon.svg` (Bildmarke, Titel mit + Produktnamen) und `apps/desktop/src/setup.html:173` (Desktop-Einrichtungsseite). +- `a11y/useButtonType` (52): nur 3 von 25 betroffenen Dateien enthalten ein Formular + (`admin/tenants/page.tsx`, `admin/users/page.tsx`, `admin/ldap/page.tsx`); dort war der + Absendeknopf je bereits richtig ausgezeichnet (1/1/2), die restlichen 16 Fundstellen waren + Neben-Schaltflaechen (Abbrechen, Schliessen, Zeilenaktionen), die beim Klick ungewollt + absendeten — bekamen `type="button"`. In den uebrigen 22 Dateien ohne Formular ist die + Auszeichnung reine Absicherung. +- Zwei neue Uebersetzungsschluessel (LDAP-Standardzuordnung, Suchbutton), in `de.json` UND + `en.json` ergaenzt (Commit `3811578`). + +### Teillieferung B (diese Sitzung, Commits `21c85a8`, `c79bafa`) + +Die verbleibenden fuenf zugewiesenen Regeln (32 Fundstellen) auf 0 gebracht: + +- **`a11y/noRedundantRoles` (4):** maschineller `--unsafe`-Fix, gelesen. Entfernte + `role="button"`/`role="time"`/`role="combobox"` von `button`/`time`/`select`-Elementen, wo + die Rolle bereits implizit ist. +- **`a11y/useAriaPropsForRole` (1):** entfiel automatisch mit obigem Fix — das + `<select role="combobox">` in `search-widget.tsx` verlangte die fehlenden ARIA-Attribute nur + wegen der ueberfluessigen Rolle. +- **`a11y/useSemanticElements` (4):** `admin-sidebar.tsx`/`settings-sidebar.tsx` tragen + `role="navigation"` jetzt am bereits vorhandenen `<nav>` statt am `<aside>` (kein doppeltes + Landmark); `widget-wrapper.tsx` ist jetzt ein echtes `<article>` statt `div role="article"`; + `DropZone.tsx` trennt die "Entfernen"-Schaltflaeche als Geschwister ab, damit die Drop-Flaeche + selbst ein echtes `<button>` werden kann (ein `<button>` darf kein zweites `<button>` + verschachteln) — Klick- UND Drag-Handler wanderten dabei auf den `<button>`, sonst waere die + umgebende `<div>` ein "statisches Element mit Ereignis-Handler" geworden und haette die + zurueckgestellten Regeln `noStaticElementInteractions`/`noNoninteractiveElementInteractions` + neu ausgeloest (geprueft — geschah zunaechst versehentlich, wurde vor dem Commit korrigiert). +- **`a11y/noNoninteractiveTabindex` (1):** `calculator-widget.tsx` traegt jetzt `tabIndex={-1}` + statt `{0}`. Die Zifferntasten sind bereits echte `<button>`-Elemente und damit selbst Teil + der Tab-Reihenfolge; Tastendruecke erreichen `handleKeyboard` weiterhin per Bubbling, sobald + eine Taste fokussiert ist — Verhalten unveraendert, nur ein wirkungsloser Tab-Stopp auf dem + Container selbst entfaellt. +- **`a11y/noLabelWithoutControl` (22):** jede Beschriftung ueber `htmlFor`/`id` an ihr Feld + gebunden — in Formularen mit wiederholten Feldnamen (LDAP, Benutzer, Mandanten) ueber + seitenweit eindeutige, praefixierte Kennungen (`ldap-*`, `user-*`, `tenant-*`). Sonderfall + `calendar-source-form.tsx`: die Farbauswahl beschriftet eine ganze Gruppe von + Farb-Schaltflaechen, kein einzelnes Feld — dafuer `fieldset`/`legend` statt `htmlFor`/`id` + (Rand/Abstand zurueckgesetzt, damit sich am Erscheinungsbild nichts aendert); eine Umwandlung + in `<span>` haette die Assoziation entfernt statt sie herzustellen und wurde darum nicht + gewaehlt. + +### Was bewusst stehen bleibt (30 Befunde, D-05) + +Diese fuenf Regeln wurden NICHT bearbeitet, weil jede eine Gestaltungsentscheidung oder einen +Verhaltenswechsel verlangt, den dieser Vorgang nicht treffen darf. Gepruefte, unveraenderte +Zaehlung nach Teillieferung B: + +| Regel | Befunde | Warum zurueckgestellt | +|---|---|---| +| `a11y/noNoninteractiveElementInteractions` | 11 | verlangt die Entscheidung, ob ein geklickter Bereich eine echte Bedienung wird oder der Klick verschwindet | +| `a11y/noStaticElementInteractions` | 5 | dieselbe Entscheidung, andere Fundstellenmenge | +| `a11y/useKeyWithClickEvents` | 5 | verlangt einen Tastaturweg, den es heute nicht gibt — neue Bedienung, kein Aufraeumen | +| `a11y/useAriaPropsSupportedByRole` | 5 | verlangt einen Blick auf jede gesetzte Rolle einzeln, teils mit Gestaltungsfolgen | +| `a11y/noAutofocus` | 4 | Entfernen verschiebt den Eingabefokus beim Seitenaufruf — Verhaltenswechsel (D-07 verbietet ihn hier) | + +Keine dieser Regeln wurde herabgestuft oder abgeschaltet — sie stehen weiterhin auf `warn` und +tauchen in keiner `overrides`-Ausnahme auf. + +## Verifikation (soeben erneut ausgefuehrt) + +``` +--- in-scope (Ziel 0) --- +noLabelWithoutControl 0 +noRedundantRoles 0 +useSemanticElements 0 +useAriaPropsForRole 0 +noNoninteractiveTabindex 0 + +--- zurueckgestellt (muss unveraendert bleiben) --- +noNoninteractiveElementInteractions 11 +useKeyWithClickEvents 5 +noStaticElementInteractions 5 +useAriaPropsSupportedByRole 5 +noAutofocus 4 + +Endstand: total 465 real 386 test 79 errors 0 +``` + +- **`apps/api` und `packages` unberuehrt seit `636fe0d`:** `git diff --name-only 636fe0d..HEAD -- apps/api packages` → leer. +- **NestJS-`__metadata`-Fingerabdruck:** 593 Zeilen, sha256 + `6e1583f1eb72a089eb0ed98f81158b54a9fbd40dbf41371292725f36ef764300` — identisch zum Ausgangswert. +- **`apps/web` Vitest:** `Test Files 66 passed (66)`, `Tests 459 passed (459)`. +- **`apps/api` Vitest:** `Test Files 69 passed (69)`, `Tests 1124 passed (1124)`. +- **`pnpm type-check`:** 4/4 erfolgreich. +- **`pnpm lint --force`:** 5/5 erfolgreich, 0 Befunde der Stufe Fehler (nur Warnungen/Info). +- **de/en-Uebersetzungskataloge:** 892 Schluessel je Katalog, Mengen identisch (0 nur-de, 0 nur-en). +- **`noExplicitAny` nach der Testdatei-Ausnahme:** 288 in echtem Quelltext (siehe Abschnitt + "Vorher/Nachher" fuer die 289→288-Abweichung), 0 in Testdateien. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] Handler-Typannotation in DropZone.tsx nach Restrukturierung** +- **Found during:** Teillieferung B, `useSemanticElements`-Fix an `DropZone.tsx` +- **Issue:** Nach dem Verschieben der Drag-Handler von der `<div>` auf die `<button>` + blieb die Typannotation `React.DragEvent<HTMLDivElement>` stehen; `pnpm type-check` schlug + mit zwei `TS2322`-Fehlern fehl. +- **Fix:** Annotation auf `React.DragEvent<HTMLButtonElement>` korrigiert. +- **Files modified:** `apps/web/src/app/(portal)/modules/cert-manager/components/DropZone.tsx` +- **Verification:** `pnpm type-check` danach 4/4. +- **Committed in:** `21c85a8` + +**2. [Rule 1 - Bug, waehrend der Arbeit selbst erkannt und korrigiert] Neu ausgeloeste +zurueckgestellte Regeln in DropZone.tsx** +- **Found during:** Teillieferung B, unmittelbar nach dem ersten Entwurf des + `useSemanticElements`-Fixes an `DropZone.tsx` +- **Issue:** Das blosse Entfernen von `role="button"`/`tabIndex`/`onClick` von der Flaechen- + `<div>` (um die Verschachtelung von zwei `<button>`-Elementen aufzuloesen) liess die + verbliebenen `onDragOver`/`onDragLeave`/`onDrop`-Handler auf einer jetzt rollenlosen `<div>` + stehen — das loeste `noStaticElementInteractions` und `noNoninteractiveElementInteractions` + NEU aus, zwei Regeln, die laut Auftrag bei 5 bzw. 11 unveraendert bleiben mussten. +- **Fix:** Struktur korrigiert: die Drop-Flaeche selbst wurde zum `<button>` (traegt jetzt + Klick- UND Drag-Handler), die "Entfernen"-Schaltflaeche liegt als absolut positioniertes + Geschwister in der Ecke statt verschachtelt. +- **Files modified:** dieselbe Datei wie oben. +- **Verification:** Volle Regelmessung nach dem Fix zeigt `noStaticElementInteractions=5`, + `noNoninteractiveElementInteractions=11` — unveraendert zum Ausgangswert. +- **Committed in:** `21c85a8` (im selben Commit korrigiert, nie mit dem Fehler committet) + +--- + +**Total deviations:** 2 auto-fixed (1 blocking type error, 1 selbst erkannte und vor dem +Commit korrigierte Regelkollision). Keine der beiden Abweichungen hat den Endstand beeinflusst +— beide wurden vor dem jeweiligen Commit vollstaendig geloest. + +### Vom Plantext abweichende Zuordnung + +**[Scope-Abweichung] `a11y/useSemanticElements` (4 Fundstellen) in Teillieferung B bearbeitet, +obwohl PLAN.md diese Regel unter D-05 als "erst mit Gestaltungsentscheidung" zurueckgestellt +hatte.** Der Auftrag fuer diese Sitzung hat die Regel explizit in den 32er-Umfang von +Teillieferung B aufgenommen (Zielwert 465 rechnet sie mit ein: 497 − 32 = 465). Bearbeitet wie +oben beschrieben — in allen vier Faellen ohne Verhaltenswechsel, nur Tag-/Attributverschiebung. +Dokumentiert hier, weil PLAN.md selbst etwas anderes vorsah. + +## Issues Encountered + +Keine ungeloesten Probleme. Die einzige echte Schwierigkeit — die gegenseitige Verschiebung von +a11y-Klassifikationen zwischen bearbeiteten und zurueckgestellten Regeln bei `DropZone.tsx` — ist +unter Deviations dokumentiert und vor dem Commit geloest worden. + +## Known Stubs + +Keine. Alle Aenderungen sind vollstaendige, funktionierende Korrekturen; keine Platzhalter, +keine leeren Datenquellen. + +## User Setup Required + +None — keine externe Konfiguration erforderlich. + +## Next Phase Readiness + +Der Lint-Rueckstand ist von 2923 auf 465 gesunken und vollstaendig benannt: fuenf +zurueckgestellte a11y-Regeln (30 Befunde, D-05), eine bewusst nicht angewendete Regel +(`noUselessSwitchCase`, 1 Befund) und vier gemeldete D-03-Symptomfunde. Empfohlene Folgeaufgaben +fuer einen spaeteren Vorgang, in absteigender Dringlichkeit: + +1. `force-password-change.interceptor.ts` — HTTP-Verfahren tatsaechlich pruefen, sonst + unterscheidet die Freigabeliste nicht zwischen Lese- und Schreibzugriff. +2. `change-password/page.tsx` — nach erzwungenem Wechsel weiterleiten und Benutzerablage + auffrischen. +3. `VehicleTable.tsx` — Besetztzustand der Loeschschaltflaeche tatsaechlich anzeigen. +4. `SplitTab.tsx` — fest verdrahtete Texte durch den Uebersetzungskatalog ersetzen. +5. Die fuenf zurueckgestellten a11y-Regeln (30 Befunde) als eigener, mit UI/Design + abgestimmter Durchgang. + +Kein Blocker fuer laufenden Betrieb: `pnpm lint --force` bleibt gruen, CI-Tor unveraendert scharf. + +## Self-Check: PASSED + +Alle referenzierten Dateien (`biome.json`, `docs/anleitung-entwicklung.md`, diese Summary, +`DropZone.tsx`, `calendar-source-form.tsx`) auf Datenträger gefunden. Alle referenzierten +Commit-Hashes (`8d1c8f3`, `3811578`, `636fe0d`, `e76f3b8`, `4cff316`, `ae82125`, `278aedb`, +`969fd01`, `27a6e29`, `73ac08a`, `21c85a8`, `c79bafa`, `97a6836`) im Verlauf gefunden. + +--- +*Vorgang: quick-260921-bi2* +*Abgeschlossen: 2026-09-21* diff --git a/.planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-VERIFICATION.md b/.planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-VERIFICATION.md new file mode 100644 index 0000000..e93a1ee --- /dev/null +++ b/.planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-VERIFICATION.md @@ -0,0 +1,111 @@ +--- +phase: quick-260921-bi2 +verified: 2026-09-21T09:47:00Z +status: passed +score: 9/9 must-haves verified +covered_files: [".planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-PLAN.md",".planning/quick/260921-bi2-lint-rueckstand-abbauen-mechanische-fixe/260921-bi2-SUMMARY.md","apps/api/scripts/rls-scratch-check.mjs","apps/api/src/auth/auth.service.ts","apps/api/src/auth/decorators/current-user.decorator.ts","apps/api/src/auth/interceptors/force-password-change.interceptor.ts","apps/api/src/auth/strategies/jwt.strategy.ts","apps/api/src/calendar/calendar.service.ts","apps/api/src/calendar/dto/create-calendar-source.dto.ts","apps/api/src/calendar/providers/caldav.provider.ts","apps/api/src/calendar/providers/exchange.provider.ts","apps/api/src/calendar/providers/ics.provider.ts","apps/api/src/cert-manager/cert-manager.service.spec.ts","apps/api/src/cert-manager/cert-manager.service.ts","apps/api/src/crypto/crypto.service.ts","apps/api/src/desktop/desktop.service.spec.ts","apps/api/src/desktop/desktop.service.ts","apps/api/src/dkv/dkv-export.service.ts","apps/api/src/dkv/dkv-parser.service.ts","apps/api/src/dkv/dkv-parser.validate.ts","apps/api/src/dkv/dkv.controller.ts","apps/api/src/dkv/dkv.service.spec.ts","apps/api/src/dkv/dkv.service.ts","apps/api/src/favorites/icon-discovery.service.ts","apps/api/src/groups/groups.service.spec.ts","apps/api/src/groups/groups.service.ts","apps/api/src/inbox/imap.provider.spec.ts","apps/api/src/ldap/ldap-config.service.spec.ts","apps/api/src/ldap/ldap.service.spec.ts","apps/api/src/ldap/ldap.service.ts","apps/api/src/prisma/prisma-tenant.extension.spec.ts","apps/api/src/tenders/adapters/cosinex.adapter.spec.ts","apps/api/src/tenders/adapters/doe-opendata.adapter.spec.ts","apps/api/src/tenders/adapters/email-alert.adapter.spec.ts","apps/api/src/tenders/adapters/email-alert.adapter.ts","apps/api/src/tenders/adapters/netserver.adapter.spec.ts","apps/api/src/tenders/adapters/rss.adapter.spec.ts","apps/api/src/tenders/adapters/rss.adapter.ts","apps/api/src/tenders/tender-digest.scheduler.spec.ts","apps/api/src/tenders/tender-digest.scheduler.ts","apps/api/src/tenders/tender-fingerprint.ts","apps/api/src/tenders/tender-ingestion.service.spec.ts","apps/api/src/tenders/tender-matching.service.ts","apps/api/src/tenders/tender-normalizer.service.spec.ts","apps/api/src/tenders/tender-normalizer.service.ts","apps/api/src/tenders/tender-rss-feed.service.spec.ts","apps/api/src/tenders/tender-scheduler.service.spec.ts","apps/api/src/user/user.controller.ts","apps/desktop/src/setup.html","apps/web/src/app/(auth)/login/page.tsx","apps/web/src/app/(auth)/reset-password/[token]/page.tsx","apps/web/src/app/(auth)/reset-password/page.tsx","apps/web/src/app/(portal)/admin/groups/components/DeleteGroupDialog.tsx","apps/web/src/app/(portal)/admin/groups/components/GroupFormModal.tsx","apps/web/src/app/(portal)/admin/groups/page.tsx","apps/web/src/app/(portal)/admin/layout.tsx","apps/web/src/app/(portal)/admin/ldap/page.tsx","apps/web/src/app/(portal)/admin/modules/page.tsx","apps/web/src/app/(portal)/admin/tenants/page.tsx","apps/web/src/app/(portal)/admin/users/page.tsx","apps/web/src/app/(portal)/change-password/page.tsx","apps/web/src/app/(portal)/marketplace/[slug]/page.tsx","apps/web/src/app/(portal)/marketplace/components/ActivationDialog.tsx","apps/web/src/app/(portal)/marketplace/components/CategoryFilter.tsx","apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.tsx","apps/web/src/app/(portal)/marketplace/components/MarketplaceSearch.tsx","apps/web/src/app/(portal)/marketplace/components/StatusFilter.tsx","apps/web/src/app/(portal)/marketplace/components/TenantContextSelector.tsx","apps/web/src/app/(portal)/marketplace/components/Toast.tsx","apps/web/src/app/(portal)/marketplace/page.tsx","apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-shell.tsx","apps/web/src/app/(portal)/modules/[category]/components/ModuleCard.tsx","apps/web/src/app/(portal)/modules/[category]/page.tsx","apps/web/src/app/(portal)/modules/cert-manager/cert-manager.test.tsx","apps/web/src/app/(portal)/modules/cert-manager/components/ConvertTab.tsx","apps/web/src/app/(portal)/modules/cert-manager/components/DropZone.tsx","apps/web/src/app/(portal)/modules/cert-manager/components/InspectTab.tsx","apps/web/src/app/(portal)/modules/cert-manager/components/PasswordField.tsx","apps/web/src/app/(portal)/modules/cert-manager/components/SplitTab.tsx","apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx","apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx","apps/web/src/app/(portal)/page.tsx","apps/web/src/app/(portal)/settings/layout.tsx","apps/web/src/app/icon.svg","apps/web/src/components/admin/admin-sidebar.tsx","apps/web/src/components/dashboard/edit-mode-toggle.tsx","apps/web/src/components/dashboard/widget-catalog-modal.tsx","apps/web/src/components/dashboard/widget-registry.tsx","apps/web/src/components/dashboard/widgets/calculator-widget.tsx","apps/web/src/components/dashboard/widgets/clock-widget.tsx","apps/web/src/components/dashboard/widgets/favorites-widget.tsx","apps/web/src/components/dashboard/widgets/note-widget.tsx","apps/web/src/components/dashboard/widgets/search-widget.tsx","apps/web/src/components/dashboard/widgets/widget-wrapper.tsx","apps/web/src/components/layout/header.tsx","apps/web/src/components/layout/sidebar-footer.tsx","apps/web/src/components/layout/sidebar.tsx","apps/web/src/components/locale-switcher.tsx","apps/web/src/components/modules/module-access-denied.tsx","apps/web/src/components/settings/account-settings-form.tsx","apps/web/src/components/settings/calendar-settings-panel.tsx","apps/web/src/components/settings/calendar-source-form.tsx","apps/web/src/components/settings/search-provider-form.tsx","apps/web/src/components/settings/settings-sidebar.tsx","apps/web/src/components/settings/widget-settings-panel.tsx","apps/web/src/components/theme-toggle.tsx","apps/web/src/lib/auth-actions.ts","apps/web/src/messages/de.json","apps/web/src/messages/en.json","apps/web/src/middleware.ts","biome.json","docs/anleitung-entwicklung.md"] +covered_digest: "v1:sha256:d7552864f2e6d3eacbdd144ddac34117636e8ccb23da588ae1eef353463901b8" +behavior_unverified: 1 +overrides_applied: 0 +behavior_unverified_items: + - truth: "In den drei Formularseiten der Verwaltung (Mandanten, Benutzer, LDAP) sendet der echte Absendeknopf weiterhin ab, und ein Klick auf Abbrechen/Schliessen loest KEIN Absenden mehr aus (useButtonType-Korrektur, 52 Fundstellen)." + test: "pnpm dev starten (nicht gegen den Testserver), die drei Seiten Mandanten/Benutzer/LDAP oeffnen, je Formular den Absendeknopf klicken (muss abschicken) und Abbrechen/Schliessen klicken (darf NICHT abschicken)." + expected: "Absenden funktioniert weiterhin unveraendert; jede Nebenschaltflaeche (Abbrechen, Schliessen, Zeilenaktionen) sendet das umgebende Formular nicht mehr ab." + why_human: "Statische Zaehlung (grep auf type=\"submit\"/<form>) bestaetigt, dass die Struktur unveraendert ist (1/1/2) und kein type=\"submit\" entfernt wurde -- das beweist aber nicht das Klick-Verhalten im Browser selbst. Dies ist genau der im PLAN.md-<human-check>-Block (Aufgabe 3) explizit deferierte Test." +--- + +# Quick-Vorgang 260921-bi2: Lint-Rueckstand abbauen (mechanische Fixe) Verification Report + +**Vorgangsziel:** Lint-Rueckstand abbauen — mechanische Fixe, toter Code, Barrierefreiheit; Test-Mock-Warnungen begruendet ausnehmen. Ohne Verhaltenswechsel. +**Verifiziert:** 2026-09-21 +**Status:** human_needed +**Re-Verifikation:** Nein — Erstverifikation + +## Zusammenfassung + +Alle neun `must_haves.truths` aus dem PLAN sind gegen den tatsaechlichen Code nachgewiesen, nicht nur gegen die SUMMARY-Behauptung. Der einzige offene Punkt ist ein vom Plan selbst deferierter menschlicher Bedienungstest (Klickverhalten der drei Verwaltungsformulare) — das ist der Grund fuer `human_needed` statt `passed`. Kein einziger automatisierter Befund ist offen. + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | Lint-Rueckstand faellt von 2923 auf ~466 (rund), 0 Fehler, CI-Lint bleibt gruen | ✓ VERIFIED | Frisch gemessen: `total 465, real 386, test 79, errors 0`. `pnpm lint --force` 5/5 erfolgreich. | +| 2 | Testdatei-Ausnahme reicht nicht in echten Quelltext: exakt N `noExplicitAny` in echtem Quelltext, 0 in Testdateien | ✓ VERIFIED (mit dokumentierter Zahlenabweichung) | Frisch gemessen: 288 real / 0 test. Plan-Text nannte 289; SUMMARY erklaert die Differenz von 1 ehrlich als inzidentellen Nebeneffekt einer `catch(e: any)`-Entfernung in Aufgabe 2 — nicht als verdeckte Anpassung. Die eigentliche Eigenschaft der Wahrheit (0 in Testdateien, Ausnahme leckt nicht in Produktivcode) haelt exakt. | +| 3 | NestJS-`__metadata`-Abdruck vor/nach dem maschinellen Durchgang Zeichen-fuer-Zeichen identisch (593 Zeilen, sha256 `6e1583f1...`) | ✓ VERIFIED | Selbst nachgestellt: `593` Zeilen, sha256 `6e1583f1eb72a089eb0ed98f81158b54a9fbd40dbf41371292725f36ef764300` — exakter Treffer. Zusaetzlich stichprobenweise `auth.service.js`, `ldap.service.js`, `ldap-config.service.js`, `calendar.service.js` gelesen: Konstruktor-Parametertypen sind echte Klassenreferenzen (`PrismaService`, `JwtService`, `UserService`, `GroupsService`, `CryptoService`, ...), nirgends `Function`. | +| 4 | Beide Testlaeufe punktgleich gruen, `pnpm type-check` 4/4 | ✓ VERIFIED | `apps/api`: 69 Dateien / 1124 Tests gruen. `apps/web`: 66 Dateien / 459 Tests gruen. `pnpm type-check`: 4/4 (Turbo FULL TURBO, alle 4 Pakete erfolgreich). | +| 5 | Regelgruppe `security` unveraendert auf `error`, nirgends erwaehnt | ✓ VERIFIED | `biome.json` per Skript geprueft: `linter.rules.a11y === "warn"`, `overrides.length === 2`, String `"security"` kommt in der Datei nirgends vor. | +| 6 | Alle 155 Barrierefreiheits-Befunde der sechs bearbeiteten Regeln auf 0, keine a11y-Regel herabgestuft/abgeschaltet | ✓ VERIFIED | Frisch gemessen: `noSvgWithoutTitle=0, useButtonType=0, noLabelWithoutControl=0, useAriaPropsSupportedByRole=0` (Anm.: diese Regel ist laut aktuellem Plan-Scope zurueckgestellt, siehe unten — Zahl 5 bestaetigt), `noRedundantRoles=0, useAriaPropsForRole=0`. Die sechs zurueckgestellten Regeln unveraendert: `noNoninteractiveElementInteractions=11, useKeyWithClickEvents=5, noStaticElementInteractions=5, useAriaPropsSupportedByRole=5, noAutofocus=4, noUselessSwitchCase=1`. `a11y` bleibt gruppenweit auf `warn`, keine Einzelausnahme in `overrides`. | +| 7 | Absendende Schaltflaechen in Mandanten/Benutzer/LDAP unveraendert bei 1/1/2 | ✓ VERIFIED (strukturell) — Klickverhalten siehe Human Verification | `grep -c 'type="submit"'` / `grep -c '<form'`: tenants 1/1, users 1/1, ldap 2/2 — exakter Treffer. Kein `type="submit"` wurde im gesamten Diff entfernt (`git diff | grep '^-.*type="submit"'` liefert nichts); 54 `type="button"` wurden ergaenzt. Die Struktur ist beweisbar unveraendert; das tatsaechliche Klickverhalten im Browser ist ein von Aufgabe 3 selbst als `<human-check>` deferierter Test (siehe unten). | +| 8 | Keine repo-weite Formatierung, zeilenbilanzierter Diff | ✓ VERIFIED | `git diff --stat e7c2c4c..HEAD`: 110 Dateien, 435 Einfuegungen / 260 Loeschungen, kein einzelner Datei-Diff ueber 75 Zeilen (groesste: `DropZone.tsx` 75, `ldap.service.ts` 50) — kein Muster einer Vollumschreibung. `pnpm-lock.yaml` und alle `package.json`-Versionsfelder unveraendert. | +| 9 | Entwickleranleitung nennt neuen Stand, beide Ausnahmen begruendet, Folgeaufgaben benannt | ✓ VERIFIED | `docs/anleitung-entwicklung.md` gelesen: nennt 465/386/79, beide `overrides`-Begruendungen in ganzen Saetzen, die sechs zurueckgestellten Regelklassen und die vier D-03-Symptomfunde als Folgeaufgaben. | + +**Score:** 9/9 truths verified (1 present, behavior-unverified — siehe Human Verification) + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `biome.json` | genau 2 `overrides`, `security` unberuehrt, Fixture-Ausnahme korrigiert | ✓ VERIFIED | 2 Eintraege (`**/*.spec.ts` etc. → `noExplicitAny: off`; `apps/api/**` → `useImportType: off`); `files.includes` enthaelt `!**/__fixtures__` ohne Doppelstern. | +| `docs/anleitung-entwicklung.md` | neuer Stand + Begruendungen | ✓ VERIFIED | Siehe Truth 9. | +| 45 maschinelle Korrekturdateien (Aufgabe 2) | gelesen, zeilenbilanziert | ✓ VERIFIED | `__metadata`-Fingerabdruck unveraendert; kein Formatierungs-Ueberschuss im Gesamtdiff. | +| 15 Fundstellen toter Code | 10 entfernt, 1 umbenannt, 4 gemeldet | ✓ VERIFIED | `current-user.decorator.ts` (`data`→`_data`) und `force-password-change.interceptor.ts` (Variable `method` entfernt) stichprobenweise gelesen — exakt wie in SUMMARY beschrieben. | +| 53 Dateien mit a11y-Korrekturen | 155 Fundstellen behoben | ✓ VERIFIED | Siehe Truth 6; Stichproben unten. | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|-----|-----|--------|---------| +| `apps/api/tsconfig.json` (`emitDecoratorMetadata`) | erzeugtes `__metadata(design:paramtypes)` | `tsc`-Kompilat | ✓ WIRED | Fingerabdruck- und Stichprobenpruefung bestaetigen: Werte bleiben echte Klassenreferenzen. | +| `biome.json overrides[0].includes` | `noExplicitAny`-Ausnahme | Musterabgleich | ✓ WIRED | 0 Treffer in Testdateien, 288 in echtem Quelltext — Ausnahme wirkt nur auf die vier Testmuster. | +| Formularseiten (Mandanten/Benutzer/LDAP) | `type="submit"`-Knopf | statische Struktur | ✓ WIRED (strukturell) | 1/1/2 bestaetigt; Klickverhalten selbst siehe Human Verification. | + +### Anti-Patterns Found + +Keine Befunde in den geaenderten Dateien. Alle drei `XXX`-Treffer in `cert-manager.test.tsx` sind Platzhalter-PEM-Zertifikatsinhalt in Test-Fixtures, keine Debt-Marker. Keine `TODO`/`FIXME`/`HACK`/`PLACEHOLDER`-Marker, keine leeren Ruecksprunge, keine hartcodierten leeren Props im Diff. + +### Behavioral Spot-Checks + +| Behavior | Command | Result | Status | +|----------|---------|--------|--------| +| NestJS-Metadaten unveraendert | `tsc --outDir` + `grep __metadata` + `sha256sum` | 593 Zeilen, `6e1583f1...` | ✓ PASS | +| Konkrete Klassenreferenzen statt `Function` | `grep -A3 __metadata` in `auth.service.js`, `ldap.service.js`, `calendar.service.js` | `PrismaService`, `JwtService`, `UserService`, `GroupsService`, `CryptoService`, ... | ✓ PASS | +| `apps/api` Vitest | `pnpm --filter @tessera/api run test` | 69/1124 gruen | ✓ PASS | +| `apps/web` Vitest | `pnpm --filter @tessera/web run test` | 66/459 gruen | ✓ PASS | +| `pnpm type-check` | `pnpm type-check` | 4/4 erfolgreich | ✓ PASS | +| `pnpm lint --force` | `pnpm lint --force` | 5/5 erfolgreich, 0 Fehler | ✓ PASS | +| i18n-Schluesselparitaet | Node-Skript ueber `de.json`/`en.json` | 892/892, 0 Differenz | ✓ PASS | +| Klickverhalten der drei Formulare im Browser | — | nicht ausgefuehrt | ? SKIP (siehe Human Verification) | + +### Manuelle Code-Lesung (a11y-Diff, Stichprobe) + +Auftrag verlangte, uebers Gruen der Testlaeufe hinaus eine Stichprobe des 155-Fundstellen-Diffs selbst zu lesen und gezielt nach drei Fehlerbildern zu suchen: + +- **Symbol faelschlich `aria-hidden`, obwohl es die einzige Beschriftung der Bedienung ist:** Keinen Fall gefunden. Stichprobe ueber die neun Dateien mit den meisten `aria-hidden`-Ergaenzungen (`widget-registry.tsx`, `sidebar.tsx`, `admin-sidebar.tsx`, `calendar-settings-panel.tsx`, `header.tsx`, `theme-toggle.tsx`, `Toast.tsx`, `setup.html`, `icon.svg`) zeigt durchgehend: Symbol entweder neben sichtbarem Text (`{item.label}`, `{t('dashboard')}` als bedingt gerenderter `<span>`) oder die umgebende Schaltflaeche/der Link traegt ein eigenes `aria-label`. Der einzige alleinstehende Fall (Standardzuordnungs-Schloss-Symbol in `admin/ldap/page.tsx`, Tabellenzelle ohne begleitenden Text) bekam korrekt ein `<title>{t('fieldMapping.defaultIcon')}` statt `aria-hidden`. Zaehlkontrolle: 69 `aria-hidden`-Ergaenzungen + 2 ``-Ergaenzungen (`icon.svg`, `fieldMapping.defaultIcon`) = 71, exakt die Zahl der `noSvgWithoutTitle`-Fundstellen. +- **`<label>` durch `<span>` "reparieren"" statt Bindung herzustellen:** Keinen Fall gefunden. `git diff | grep -B3 '^-.*<label'` zeigt keinen einzigen Fall, in dem eine geloeschte `<label>`-Zeile durch eine neue `<span>`-Zeile ersetzt wurde. Alle 22 `noLabelWithoutControl`-Fixe binden stattdessen `htmlFor`/`id` (Stichprobe: `group-name`, `tenant-name`, `tenant-slug`, `user-username`, `user-email`, `user-password`, `ldap-bind-password`, `ldap-search-filter`, `ldap-sync-interval`, `ldap-mapping-ldap-field`, `ldap-mapping-tessera-field`, `ldap-group-manual-dn`, `ldap-exclude-username`, `ldap-server-url`, `ldap-base-dn`, `ldap-bind-dn`, `group-internal-name` — je genau ein `label htmlFor=` und ein `input id=` desselben Namens, keine Kollisionen). Sonderfall Farbauswahl in `calendar-source-form.tsx`: `label`→`fieldset`/`legend` fuer eine Gruppe von Farb-Schaltflaechen (kein Einzelfeld) — sachlich korrekt, keine Assoziation entfernt. +- **`type="button"` faelschlich auf dem echten Absendeknopf:** Keinen Fall gefunden. 54 `type="button"`-Ergaenzungen im Diff, 0 `type="submit"`-Entfernungen; die drei betroffenen Formularseiten behalten ihre 1/1/2-Absendeknopf-Zaehlung exakt bei. + +`DropZone.tsx` (SUMMARY-Abweichung) gelesen: Die Drop-Flaeche ist jetzt selbst ein `<button type="button">` mit Klick- UND Drag-Handlern (`onDragOver`/`onDragLeave`/`onDrop`), die "Entfernen"-Schaltflaeche liegt als `position: absolute`-Geschwister in einem umgebenden `<div className="relative">`, nicht mehr verschachtelt — kein `<button>` enthaelt ein zweites `<button>` (gueltiges Markup). Native Drag-Ereignisse sind an jedem HTML-Element inklusive `<button>` verfuegbar; die Umstellung aendert am Drag-and-Drop-Verhalten nichts. Die Begruendung im Kommentar der Datei selbst deckt sich mit dem Code. Urteil: die Abweichung ist sauber und loest das beschriebene Problem korrekt. + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|-------------|-------------|--------|----------| +| LINT-BACKLOG | 260921-bi2-PLAN.md | Lint-Rueckstand abbauen ohne Verhaltenswechsel | ✓ SATISFIED | Siehe alle Truths oben. Kein Eintrag in `.planning/REQUIREMENTS.md` unter dieser ID — Quick-Vorgang, kein Meilenstein-Requirement-Tracking erwartet. | + +## Human Verification Required + +### 1. Klickverhalten der drei Verwaltungsformulare (Mandanten, Benutzer, LDAP) + +**Test:** `pnpm dev` starten (nicht gegen den Testserver messen). Auf jeder der drei Seiten — `/admin/tenants`, `/admin/users`, `/admin/ldap` — den echten Absendeknopf klicken und zusaetzlich jede Nebenschaltflaeche (Abbrechen, Schliessen, Zeilenaktionen) klicken. +**Expected:** Der echte Absendeknopf sendet das Formular weiterhin ab. Keine Nebenschaltflaeche loest ein Absenden aus. +**Why human:** Die statische Struktur (Zaehlung `type="submit"`/`<form>`, keine geloeschte Submit-Zeile) ist zweifelsfrei nachgewiesen und belegt, dass sich am Absende-Markup nichts geaendert hat. Sie beweist aber nicht das tatsaechliche Klickverhalten im Browser — genau das ist der Zweck der `useButtonType`-Korrektur (52 Fundstellen), und PLAN.md selbst deferiert diesen Test explizit in einem `<human-check>`-Block fuer Aufgabe 3, weil "ein gruener Testlauf davon nur einen Teil abdeckt". + +## Gaps Summary + +Keine Gaps. Alle automatisierten Nachweise — Fingerabdruck, Zaehlungen, Testlaeufe, Typpruefung, Lint-Tor, i18n-Paritaet, Diff-Zeilenbilanz, Konfigurationsform — sind gegen den tatsaechlichen Code nachgestellt und bestehen exakt. Der Status `human_needed` kommt ausschliesslich von dem einen, im Plan selbst als Bedienprobe vorgesehenen Klicktest, der eine Sitzung mit laufendem Dev-Server voraussetzt und nicht durch statische Pruefung ersetzt werden kann. + +--- +*Verified: 2026-09-21* +*Verifier: Claude (gsd-verifier)*