diff --git a/.planning/STATE.md b/.planning/STATE.md
index 01a31f2..b1c058b 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -4,8 +4,8 @@ milestone: v1.2
current_phase: 18
current_phase_name: desktop-client-fertigstellen
status: verified
-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"
+stopped_at: "Neun Quick-Vorgaenge am 2026-09-21 (9ie, a1d, bi2, fi3, gof, i8x, iwr, jt4, ldf). Der Wackeltest aus CI-Lauf 395 ist geklaert und war ein Produktfehler. Offen: 288 any in apps/api (reine Typarbeit), danach zwei neue Dashboard-Widgets. Das erste ist ein Bilderrahmen: Bilder werden hochgeladen ODER per https-Webadresse eingebunden (Browser laedt direkt, kein Server-Abruf, damit keine SSRF-Flaeche); Einstellungen fuer Bildausschnitt, Wechselintervall, Reihenfolge/Zufall, Bildunterschrift, Klick zeigt gross. Das zweite Widget hat der Nutzer noch nicht benannt. ldf ist noch nicht gepusht."
+last_updated: "2026-09-21T14:15: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-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
+Last activity: 2026-09-21 - Quick 260921-ldf: der Wackeltest aus CI-Lauf 395 war ein echter Produktfehler — der Fehler-melden-Dialog stand bei jedem Oeffnen kurz mit ausgeschaltetem Haekchen da; Ursache behoben, nicht der Test beruhigt
Progress: [██████████] 99%
@@ -452,6 +452,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests.
| 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 `` — 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 `